/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * LION Semiconductor LN-9310 switched capacitor converter. */ #include "common.h" #include "console.h" #include "driver/ln9310.h" #include "hooks.h" #include "i2c.h" #include "util.h" #include "timer.h" #define CPUTS(outstr) cputs(CC_I2C, outstr) #define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) #define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) static int power_good; int ln9310_power_good(void) { return power_good; } static inline int raw_read8(int offset, int *value) { return i2c_read8(ln9310_config.i2c_port, ln9310_config.i2c_addr_flags, offset, value); } static inline int field_update8(int offset, int mask, int value) { /* Clear mask and then set value in i2c reg value */ return i2c_field_update8(ln9310_config.i2c_port, ln9310_config.i2c_addr_flags, offset, mask, value); } static void ln9310_irq_deferred(void) { int status, val, pg_2to1, pg_3to1; status = raw_read8(LN9310_REG_INT1, &val); if (status) { CPRINTS("LN9310 reading INT1 failed"); return; } CPRINTS("LN9310 received interrupt: 0x%x", val); /* Don't care other interrupts except mode change */ if (!(val & LN9310_INT1_MODE)) return; /* Check if the device is active in 2:1 or 3:1 switching mode */ status = raw_read8(LN9310_REG_SYS_STS, &val); if (status) { CPRINTS("LN9310 reading SYS_STS failed"); return; } CPRINTS("LN9310 system status: 0x%x", val); /* Either 2:1 or 3:1 mode active is treated as PGOOD */ pg_2to1 = !!(val & LN9310_SYS_SWITCHING21_ACTIVE); pg_3to1 = !!(val & LN9310_SYS_SWITCHING31_ACTIVE); power_good = pg_2to1 || pg_3to1; } DECLARE_DEFERRED(ln9310_irq_deferred); void ln9310_interrupt(enum gpio_signal signal) { hook_call_deferred(&ln9310_irq_deferred_data, 0); } static int is_battery_gt_10v(void) { int status, val, gt_10v; CPRINTS("LN9310 checking input voltage, threshold=10V"); /* * Turn on INFET_OUT_SWITCH_OK comparator; * configure INFET_OUT_SWITCH_OK to 10V. */ field_update8(LN9310_REG_TRACK_CTRL, LN9310_TRACK_INFET_OUT_SWITCH_OK_EN_MASK | LN9310_TRACK_INFET_OUT_SWITCH_OK_CFG_MASK, LN9310_TRACK_INFET_OUT_SWITCH_OK_EN_ON | LN9310_TRACK_INFET_OUT_SWITCH_OK_CFG_10V); /* Read INFET_OUT_SWITCH_OK comparator */ status = raw_read8(LN9310_REG_BC_STS_B, &val); if (status) { CPRINTS("LN9310 reading BC_STS_B failed"); return -1; } CPRINTS("LN9310 BC_STS_B: 0x%x", val); /* * If INFET_OUT_SWITCH_OK=0, VIN < 10V * If INFET_OUT_SWITCH_OK=1, VIN > 10V */ gt_10v = !!(val & LN9310_BC_STS_B_INFET_OUT_SWITCH_OK); CPRINTS("LN9310 battery %s 10V", gt_10v ? ">" : "<"); /* Turn off INFET_OUT_SWITCH_OK comparator */ field_update8(LN9310_REG_TRACK_CTRL, LN9310_TRACK_INFET_OUT_SWITCH_OK_EN_MASK, LN9310_TRACK_INFET_OUT_SWITCH_OK_EN_OFF); return gt_10v; } static int ln9310_update_startup_seq(void) { CPRINTS("LN9310 update startup sequence"); /* Startup sequence instruction swap */ field_update8(LN9310_REG_LION_CTRL, LN9310_LION_CTRL_MASK, LN9310_LION_CTRL_UNLOCK); field_update8(LN9310_REG_SWAP_CTRL_0, 0xff, 0x3f); field_update8(LN9310_REG_SWAP_CTRL_1, 0xff, 0x51); field_update8(LN9310_REG_SWAP_CTRL_2, 0xff, 0x19); field_update8(LN9310_REG_SWAP_CTRL_3, 0xff, 0x02); /* Startup sequence settings */ field_update8(LN9310_REG_CFG_4, LN9310_CFG_4_SC_OUT_PRECHARGE_EN_TIME_CFG_MASK | LN9310_CFG_4_SW1_VGS_SHORT_EN_MSK_MASK | LN9310_CFG_4_BSTH_BSTL_HIGH_ROUT_CFG_MASK, LN9310_CFG_4_SC_OUT_PRECHARGE_EN_TIME_CFG_ON | LN9310_CFG_4_SW1_VGS_SHORT_EN_MSK_OFF | LN9310_CFG_4_BSTH_BSTL_HIGH_ROUT_CFG_LOWEST); /* SW4 before BSTH_BSTL */ field_update8(LN9310_REG_SPARE_0, LN9310_SPARE_0_SW4_BEFORE_BSTH_BSTL_EN_CFG_MASK, LN9310_SPARE_0_SW4_BEFORE_BSTH_BSTL_EN_CFG_ON); field_update8(LN9310_REG_LION_CTRL, LN9310_LION_CTRL_MASK, LN9310_LION_CTRL_LOCK); return EC_SUCCESS; } static int ln9310_init_3to1(void) { CPRINTS("LN9310 init (3:1 operation)"); /* Enable track protection and SC_OUT configs for 3:1 switching */ field_update8(LN9310_REG_MODE_CHANGE_CFG, LN9310_MODE_TM_TRACK_MASK | LN9310_MODE_TM_SC_OUT_PRECHG_MASK | LN9310_MODE_TM_VIN_OV_CFG_MASK, LN9310_MODE_TM_TRACK_SWITCH31 | LN9310_MODE_TM_SC_OUT_PRECHG_SWITCH31 | LN9310_MODE_TM_VIN_OV_CFG_3S); /* Enable 3:1 operation mode */ field_update8(LN9310_REG_PWR_CTRL, LN9310_PWR_OP_MODE_MASK, LN9310_PWR_OP_MODE_SWITCH31); /* 3S lower bounde delta configurations */ field_update8(LN9310_REG_LB_CTRL, LN9310_LB_DELTA_MASK, LN9310_LB_DELTA_3S); /* * TODO(waihong): The LN9310_REG_SYS_CTR was set to a wrong value * accidentally. Override it to 0. This may not need. */ field_update8(LN9310_REG_SYS_CTRL, 0xff, 0); return EC_SUCCESS; } static int ln9310_init_2to1(void) { CPRINTS("LN9310 init (2:1 operation)"); if (is_battery_gt_10v()) { CPRINTS("LN9310 init stop. Input voltage is too high."); return EC_ERROR_UNKNOWN; } /* Enable track protection and SC_OUT configs for 2:1 switching */ field_update8(LN9310_REG_MODE_CHANGE_CFG, LN9310_MODE_TM_TRACK_MASK | LN9310_MODE_TM_SC_OUT_PRECHG_MASK, LN9310_MODE_TM_TRACK_SWITCH21 | LN9310_MODE_TM_SC_OUT_PRECHG_SWITCH21); /* Enable 2:1 operation mode */ field_update8(LN9310_REG_PWR_CTRL, LN9310_PWR_OP_MODE_MASK, LN9310_PWR_OP_MODE_SWITCH21); /* 2S lower bounde delta configurations */ field_update8(LN9310_REG_LB_CTRL, LN9310_LB_DELTA_MASK, LN9310_LB_DELTA_2S); /* * TODO(waihong): The LN9310_REG_SYS_CTR was set to a wrong value * accidentally. Override it to 0. This may not need. */ field_update8(LN9310_REG_SYS_CTRL, 0xff, 0); return EC_SUCCESS; } static int ln9310_update_infet(void) { CPRINTS("LN9310 update infet configuration"); field_update8(LN9310_REG_LION_CTRL, LN9310_LION_CTRL_MASK, LN9310_LION_CTRL_UNLOCK); /* Update Infet register settings */ field_update8(LN9310_REG_CFG_5, LN9310_CFG_5_INGATE_PD_EN_MASK, LN9310_CFG_5_INGATE_PD_EN_OFF); field_update8(LN9310_REG_CFG_5, LN9310_CFG_5_INFET_CP_PD_BIAS_CFG_MASK, LN9310_CFG_5_INFET_CP_PD_BIAS_CFG_LOWEST); /* enable automatic infet control */ field_update8(LN9310_REG_PWR_CTRL, LN9310_PWR_INFET_AUTO_MODE_MASK, LN9310_PWR_INFET_AUTO_MODE_ON); /* disable LS_HELPER during IDLE by setting MSK bit high */ field_update8(LN9310_REG_CFG_0, LN9310_CFG_0_LS_HELPER_IDLE_MSK_MASK, LN9310_CFG_0_LS_HELPER_IDLE_MSK_ON); field_update8(LN9310_REG_LION_CTRL, LN9310_LION_CTRL_MASK, LN9310_LION_CTRL_LOCK); return EC_SUCCESS; } void ln9310_init(void) { int status, val; enum battery_cell_type batt; /* Update INFET configuration */ status = ln9310_update_infet(); if (status != EC_SUCCESS) return; /* * Set OPERATION_MODE update method * - OP_MODE_MANUAL_UPDATE = 0 * - OP_MODE_SELF_SYNC_EN = 1 */ field_update8(LN9310_REG_PWR_CTRL, LN9310_PWR_OP_MODE_MANUAL_UPDATE_MASK, LN9310_PWR_OP_MODE_MANUAL_UPDATE_OFF); field_update8(LN9310_REG_TIMER_CTRL, LN9310_TIMER_OP_SELF_SYNC_EN_MASK, LN9310_TIMER_OP_SELF_SYNC_EN_ON); /* * Use VIN for VDR, not EXT_5V. The following usleep will give * circuit time to settle. */ field_update8(LN9310_REG_STARTUP_CTRL, LN9310_STARTUP_SELECT_EXT_5V_FOR_VDR, 0); field_update8(LN9310_REG_LB_CTRL, LN9310_LB_MIN_FREQ_EN, LN9310_LB_MIN_FREQ_EN); /* Set minimum switching frequency to 25 kHz */ field_update8(LN9310_REG_SPARE_0, LN9310_SPARE_0_LB_MIN_FREQ_SEL_MASK, LN9310_SPARE_0_LB_MIN_FREQ_SEL_ON); usleep(LN9310_CDC_DELAY); CPRINTS("LN9310 OP_MODE Update method: Self-sync"); /* Update Startup sequence */ status = ln9310_update_startup_seq(); if (status != EC_SUCCESS) return; batt = board_get_battery_cell_type(); if (batt == BATTERY_CELL_TYPE_3S) { status = ln9310_init_3to1(); } else if (batt == BATTERY_CELL_TYPE_2S) { status = ln9310_init_2to1(); } else { CPRINTS("LN9310 not supported battery type: %d", batt); return; } if (status != EC_SUCCESS) return; /* Unmask the MODE change interrupt */ field_update8(LN9310_REG_INT1_MSK, LN9310_INT1_MODE, 0); /* Dummy clear all interrupts */ status = raw_read8(LN9310_REG_INT1, &val); if (status) { CPRINTS("LN9310 reading INT1 failed"); return; } /* Clear the STANDBY_EN bit */ field_update8(LN9310_REG_STARTUP_CTRL, LN9310_STARTUP_STANDBY_EN, 0); CPRINTS("LN9310 cleared interrupts: 0x%x", val); }