| /* Copyright 2017 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. |
| * |
| * Richtek rt946x, Mediatek mt6370 battery charger driver. |
| */ |
| |
| #include "battery.h" |
| #include "battery_smart.h" |
| #include "charger.h" |
| #include "charge_manager.h" |
| #include "common.h" |
| #include "compile_time_macros.h" |
| #include "config.h" |
| #include "console.h" |
| #include "extpower.h" |
| #include "hooks.h" |
| #include "i2c.h" |
| #include "printf.h" |
| #include "driver/wpc/p9221.h" |
| #include "rt946x.h" |
| #include "task.h" |
| #include "tcpm.h" |
| #include "timer.h" |
| #include "usb_charge.h" |
| #include "usb_pd.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) |
| #define CPRINTS(format, args...) \ |
| cprints(CC_CHARGER, "%s " format, "RT946X", ## args) |
| |
| /* Charger parameters */ |
| static const struct charger_info rt946x_charger_info = { |
| .name = CHARGER_NAME, |
| .voltage_max = CHARGE_V_MAX, |
| .voltage_min = CHARGE_V_MIN, |
| .voltage_step = CHARGE_V_STEP, |
| .current_max = CHARGE_I_MAX, |
| .current_min = CHARGE_I_MIN, |
| .current_step = CHARGE_I_STEP, |
| .input_current_max = INPUT_I_MAX, |
| .input_current_min = INPUT_I_MIN, |
| .input_current_step = INPUT_I_STEP, |
| }; |
| |
| static const struct rt946x_init_setting default_init_setting = { |
| .eoc_current = 400, |
| .mivr = 4000, |
| .ircmp_vclamp = 32, |
| .ircmp_res = 25, |
| .boost_voltage = 5050, |
| .boost_current = 1500, |
| }; |
| |
| __attribute__((weak)) |
| const struct rt946x_init_setting *board_rt946x_init_setting(void) |
| { |
| return &default_init_setting; |
| } |
| |
| enum rt946x_ilmtsel { |
| RT946X_ILMTSEL_PSEL_OTG, |
| RT946X_ILMTSEL_AICR = 2, |
| RT946X_ILMTSEL_LOWER_LEVEL, /* lower of above two */ |
| }; |
| |
| enum rt946x_chg_stat { |
| RT946X_CHGSTAT_READY = 0, |
| RT946X_CHGSTAT_IN_PROGRESS, |
| RT946X_CHGSTAT_DONE, |
| RT946X_CHGSTAT_FAULT, |
| }; |
| |
| static struct mutex adc_access_lock; |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| /* |
| * Unit for each ADC parameter |
| * 0 stands for reserved |
| */ |
| static const int mt6370_adc_unit[MT6370_ADC_MAX] = { |
| 0, |
| MT6370_ADC_UNIT_VBUS_DIV5, |
| MT6370_ADC_UNIT_VBUS_DIV2, |
| MT6370_ADC_UNIT_VSYS, |
| MT6370_ADC_UNIT_VBAT, |
| 0, |
| MT6370_ADC_UNIT_TS_BAT, |
| 0, |
| MT6370_ADC_UNIT_IBUS, |
| MT6370_ADC_UNIT_IBAT, |
| 0, |
| MT6370_ADC_UNIT_CHG_VDDP, |
| MT6370_ADC_UNIT_TEMP_JC, |
| }; |
| |
| static const int mt6370_adc_offset[MT6370_ADC_MAX] = { |
| 0, |
| MT6370_ADC_OFFSET_VBUS_DIV5, |
| MT6370_ADC_OFFSET_VBUS_DIV2, |
| MT6370_ADC_OFFSET_VSYS, |
| MT6370_ADC_OFFSET_VBAT, |
| 0, |
| MT6370_ADC_OFFSET_TS_BAT, |
| 0, |
| MT6370_ADC_OFFSET_IBUS, |
| MT6370_ADC_OFFSET_IBAT, |
| 0, |
| MT6370_ADC_OFFSET_CHG_VDDP, |
| MT6370_ADC_OFFSET_TEMP_JC, |
| }; |
| |
| static int hidden_mode_cnt = 0; |
| static struct mutex hidden_mode_lock; |
| static const unsigned char mt6370_reg_en_hidden_mode[] = { |
| MT6370_REG_HIDDENPASCODE1, |
| MT6370_REG_HIDDENPASCODE2, |
| MT6370_REG_HIDDENPASCODE3, |
| MT6370_REG_HIDDENPASCODE4, |
| }; |
| |
| static const unsigned char mt6370_val_en_hidden_mode[] = { |
| 0x96, 0x69, 0xC3, 0x3C, |
| }; |
| |
| static const unsigned char mt6370_val_en_test_mode[] = { |
| 0x69, 0x96, 0x63, 0x70, |
| }; |
| #endif /* CONFIG_CHARGER_MT6370 */ |
| |
| #if defined(CONFIG_CHARGER_RT9466) || defined(CONFIG_CHARGER_RT9467) |
| enum rt946x_irq { |
| RT946X_IRQ_CHGSTATC = 0, |
| RT946X_IRQ_CHGFAULT, |
| RT946X_IRQ_TSSTATC, |
| RT946X_IRQ_CHGIRQ1, |
| RT946X_IRQ_CHGIRQ2, |
| RT946X_IRQ_CHGIRQ3, |
| #ifdef CONFIG_CHARGER_RT9467 |
| RT946X_IRQ_DPDMIRQ, |
| #endif |
| RT946X_IRQ_COUNT, |
| }; |
| |
| static uint8_t rt946x_irqmask[RT946X_IRQ_COUNT] = { |
| 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, |
| #ifdef CONFIG_CHARGER_RT9467 |
| 0xFC, |
| #endif |
| }; |
| |
| static const uint8_t rt946x_irq_maskall[RT946X_IRQ_COUNT] = { |
| 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, |
| #ifdef CONFIG_CHARGER_RT9467 |
| 0xFF, |
| #endif |
| }; |
| #elif defined(CONFIG_CHARGER_MT6370) |
| enum rt946x_irq { |
| MT6370_IRQ_CHGSTAT1 = 0, |
| MT6370_IRQ_CHGSTAT2, |
| MT6370_IRQ_CHGSTAT3, |
| MT6370_IRQ_CHGSTAT4, |
| MT6370_IRQ_CHGSTAT5, |
| MT6370_IRQ_CHGSTAT6, |
| MT6370_IRQ_DPDMSTAT, |
| MT6370_IRQ_DICHGSTAT, |
| MT6370_IRQ_OVPCTRLSTAT, |
| MT6370_IRQ_FLEDSTAT1, |
| MT6370_IRQ_FLEDSTAT2, |
| MT6370_IRQ_BASESTAT, |
| MT6370_IRQ_LDOSTAT, |
| MT6370_IRQ_RGBSTAT, |
| MT6370_IRQ_BLSTAT, |
| MT6370_IRQ_DBSTAT, |
| RT946X_IRQ_COUNT, |
| }; |
| |
| static uint8_t rt946x_irqmask[RT946X_IRQ_COUNT] = { |
| 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, |
| }; |
| |
| static const uint8_t rt946x_irq_maskall[RT946X_IRQ_COUNT] = { |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, |
| }; |
| #endif |
| |
| static enum ec_error_list rt946x_set_current(int chgnum, int current); |
| static enum ec_error_list rt946x_get_current(int chgnum, int *current); |
| static enum ec_error_list rt946x_set_voltage(int chgnum, int voltage); |
| static enum ec_error_list rt946x_enable_otg_power(int chgnum, int enabled); |
| static const struct charger_info *rt946x_get_info(int chgnum); |
| |
| /* Must be in ascending order */ |
| static const uint16_t rt946x_boost_current[] = { |
| 500, 700, 1100, 1300, 1800, 2100, 2400, |
| }; |
| |
| static enum ec_error_list rt946x_read8(int chgnum, int reg, int *val) |
| { |
| return i2c_read8(chg_chips[chgnum].i2c_port, |
| chg_chips[chgnum].i2c_addr_flags, reg, val); |
| } |
| |
| static enum ec_error_list rt946x_write8(int chgnum, int reg, int val) |
| { |
| return i2c_write8(chg_chips[chgnum].i2c_port, |
| chg_chips[chgnum].i2c_addr_flags, reg, val); |
| } |
| |
| static enum ec_error_list rt946x_block_write(int chgnum, int reg, |
| const uint8_t *val, int len) |
| { |
| return i2c_write_block(chg_chips[chgnum].i2c_port, |
| chg_chips[chgnum].i2c_addr_flags, |
| reg, val, len); |
| } |
| |
| static int rt946x_update_bits(int chgnum, int reg, int mask, int val) |
| { |
| int rv; |
| int reg_val = 0; |
| |
| rv = rt946x_read8(chgnum, reg, ®_val); |
| if (rv) |
| return rv; |
| reg_val &= ~mask; |
| reg_val |= (mask & val); |
| rv = rt946x_write8(chgnum, reg, reg_val); |
| return rv; |
| } |
| |
| static inline int rt946x_set_bit(int chgnum, int reg, int mask) |
| { |
| return rt946x_update_bits(chgnum, reg, mask, mask); |
| } |
| |
| static inline int rt946x_clr_bit(int chgnum, int reg, int mask) |
| { |
| return rt946x_update_bits(chgnum, reg, mask, 0x00); |
| } |
| |
| static inline int mt6370_pmu_reg_test_bit(int chgnum, int cmd, int shift, |
| int *is_one) |
| { |
| int rv, data; |
| |
| rv = rt946x_read8(chgnum, cmd, &data); |
| if (rv) { |
| *is_one = 0; |
| return rv; |
| } |
| |
| *is_one = !!(data & BIT(shift)); |
| return rv; |
| } |
| |
| static inline uint8_t rt946x_closest_reg(uint16_t min, uint16_t max, |
| uint16_t step, uint16_t target) |
| { |
| if (target < min) |
| return 0; |
| if (target >= max) |
| return ((max - min) / step); |
| return (target - min) / step; |
| } |
| |
| static int rt946x_get_ieoc(int chgnum, uint32_t *ieoc) |
| { |
| int ret, reg_ieoc; |
| |
| ret = rt946x_read8(chgnum, RT946X_REG_CHGCTRL9, ®_ieoc); |
| if (ret) |
| return ret; |
| |
| *ieoc = RT946X_IEOC_MIN + |
| RT946X_IEOC_STEP * |
| ((reg_ieoc & RT946X_MASK_IEOC) >> RT946X_SHIFT_IEOC); |
| |
| return EC_SUCCESS; |
| } |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| static int mt6370_enable_hidden_mode(int chgnum, int en) |
| { |
| int rv = 0; |
| |
| if (in_interrupt_context()) { |
| CPRINTS("Err: use hidden mode in IRQ"); |
| return EC_ERROR_INVAL; |
| } |
| |
| mutex_lock(&hidden_mode_lock); |
| if (en) { |
| if (hidden_mode_cnt == 0) { |
| rv = rt946x_block_write(chgnum, |
| mt6370_reg_en_hidden_mode[0], |
| mt6370_val_en_hidden_mode, |
| ARRAY_SIZE(mt6370_val_en_hidden_mode)); |
| if (rv) |
| goto out; |
| } |
| hidden_mode_cnt++; |
| } else { |
| if (hidden_mode_cnt == 1) /* last one */ |
| rv = rt946x_write8(chgnum, mt6370_reg_en_hidden_mode[0], |
| 0x00); |
| hidden_mode_cnt--; |
| if (rv) |
| goto out; |
| } |
| |
| out: |
| mutex_unlock(&hidden_mode_lock); |
| return rv; |
| } |
| |
| /* |
| * Vsys short protection: |
| * When the system is charging at 500mA, and if Isys > 3600mA, the |
| * power path will be turned off and cause the system shutdown. |
| * When Ichg < 400mA, then power path is roughly 1/8 of the original. |
| * When Isys > 3600mA, this cause the voltage between Vbat and Vsys too |
| * huge (Vbat - Vsys > Vsys short portection) and turns off the power |
| * path. |
| * To workaround this, |
| * 1. disable Vsys short protection when Ichg is set below 900mA |
| * 2. forbids Ichg <= 400mA (this is done natually on mt6370, since mt6370's |
| * minimum current is 512) |
| */ |
| static int mt6370_ichg_workaround(int chgnum, int new_ichg) |
| { |
| int rv = EC_SUCCESS; |
| int curr_ichg; |
| |
| /* |
| * TODO(b:144532905): The workaround should be applied to rt9466 as |
| * well. But this needs rt9466's hidden register datasheet. Enable |
| * this if we need it in the future. |
| */ |
| if (!IS_ENABLED(CONFIG_CHARGER_MT6370)) |
| return EC_SUCCESS; |
| |
| rv = rt946x_get_current(chgnum, &curr_ichg); |
| if (rv) |
| return rv; |
| |
| mt6370_enable_hidden_mode(chgnum, 1); |
| |
| /* disable Vsys protect if if the new ichg is below 900mA */ |
| if (curr_ichg >= 900 && new_ichg < 900) |
| rv = rt946x_update_bits(chgnum, RT946X_REG_CHGHIDDENCTRL7, |
| RT946X_MASK_HIDDENCTRL7_VSYS_PROTECT, |
| 0); |
| /* enable Vsys protect if the new ichg is above 900mA */ |
| else if (new_ichg >= 900 && curr_ichg < 900) |
| rv = rt946x_update_bits(chgnum, RT946X_REG_CHGHIDDENCTRL7, |
| RT946X_MASK_HIDDENCTRL7_VSYS_PROTECT, |
| RT946X_ENABLE_VSYS_PROTECT); |
| |
| mt6370_enable_hidden_mode(chgnum, 0); |
| return rv; |
| } |
| #endif /* CONFIG_CHARGER_MT6370 */ |
| |
| static inline int rt946x_enable_wdt(int chgnum, int en) |
| { |
| return (en ? rt946x_set_bit : rt946x_clr_bit) |
| (chgnum, RT946X_REG_CHGCTRL13, RT946X_MASK_WDT_EN); |
| } |
| |
| /* Enable high-impedance mode */ |
| static inline int rt946x_enable_hz(int chgnum, int en) |
| { |
| return (en ? rt946x_set_bit : rt946x_clr_bit) |
| (chgnum, RT946X_REG_CHGCTRL1, RT946X_MASK_HZ_EN); |
| } |
| |
| int rt946x_por_reset(void) |
| { |
| int rv, val; |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| /* Soft reset. It takes only 1ns for resetting. b/116682788 */ |
| val = RT946X_MASK_SOFT_RST; |
| /* |
| * MT6370 has to set passcodes before resetting all the registers and |
| * logics. |
| */ |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_REG_RSTPASCODE1, |
| MT6370_MASK_RSTPASCODE1); |
| rv |= rt946x_write8(CHARGER_SOLO, MT6370_REG_RSTPASCODE2, |
| MT6370_MASK_RSTPASCODE2); |
| #else |
| /* Hard reset, may take several milliseconds. */ |
| val = RT946X_MASK_RST; |
| rv = rt946x_enable_hz(CHARGER_SOLO, 0); |
| #endif |
| if (rv) |
| return rv; |
| |
| return rt946x_set_bit(CHARGER_SOLO, RT946X_REG_CORECTRL_RST, val); |
| } |
| |
| static int rt946x_reset_to_zero(int chgnum) |
| { |
| int rv; |
| |
| rv = rt946x_set_current(chgnum, 0); |
| if (rv) |
| return rv; |
| |
| rv = rt946x_set_voltage(chgnum, 0); |
| if (rv) |
| return rv; |
| |
| return rt946x_enable_hz(chgnum, 1); |
| } |
| |
| static int rt946x_enable_bc12_detection(int chgnum, int en) |
| { |
| #if defined(CONFIG_CHARGER_RT9467) || defined(CONFIG_CHARGER_MT6370) |
| int rv; |
| |
| if (en) { |
| #ifdef CONFIG_CHARGER_MT6370_BC12_GPIO |
| gpio_set_level(GPIO_BC12_DET_EN, 1); |
| #endif /* CONFIG_CHARGER_MT6370_BC12_GPIO */ |
| return rt946x_set_bit(chgnum, RT946X_REG_DPDM1, |
| RT946X_MASK_USBCHGEN); |
| } |
| |
| rv = rt946x_clr_bit(chgnum, RT946X_REG_DPDM1, RT946X_MASK_USBCHGEN); |
| #ifdef CONFIG_CHARGER_MT6370_BC12_GPIO |
| gpio_set_level(GPIO_BC12_DET_EN, 0); |
| #endif /* CONFIG_CHARGER_MT6370_BC12_GPIO */ |
| return rv; |
| #endif |
| return 0; |
| } |
| |
| static int rt946x_set_ieoc(int chgnum, unsigned int ieoc) |
| { |
| uint8_t reg_ieoc; |
| |
| reg_ieoc = rt946x_closest_reg(RT946X_IEOC_MIN, RT946X_IEOC_MAX, |
| RT946X_IEOC_STEP, ieoc); |
| |
| CPRINTS("ieoc=%d", ieoc); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL9, RT946X_MASK_IEOC, |
| reg_ieoc << RT946X_SHIFT_IEOC); |
| } |
| |
| static int rt946x_set_mivr(int chgnum, unsigned int mivr) |
| { |
| uint8_t reg_mivr = 0; |
| |
| reg_mivr = rt946x_closest_reg(RT946X_MIVR_MIN, RT946X_MIVR_MAX, |
| RT946X_MIVR_STEP, mivr); |
| |
| CPRINTS("mivr=%d", mivr); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL6, RT946X_MASK_MIVR, |
| reg_mivr << RT946X_SHIFT_MIVR); |
| } |
| |
| static int rt946x_set_boost_voltage(int chgnum, unsigned int voltage) |
| { |
| uint8_t reg_voltage = 0; |
| |
| reg_voltage = rt946x_closest_reg(RT946X_BOOST_VOLTAGE_MIN, |
| RT946X_BOOST_VOLTAGE_MAX, RT946X_BOOST_VOLTAGE_STEP, voltage); |
| |
| CPRINTS("voltage=%d", voltage); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL5, |
| RT946X_MASK_BOOST_VOLTAGE, |
| reg_voltage << RT946X_SHIFT_BOOST_VOLTAGE); |
| } |
| |
| static int rt946x_set_boost_current(int chgnum, unsigned int current) |
| { |
| int i; |
| |
| /* |
| * Find the smallest output current threshold which can support |
| * our requested output current. Use the greatest achievable |
| * boost current (2.4A) if requested current is too large. |
| */ |
| for (i = 0; i < ARRAY_SIZE(rt946x_boost_current) - 1; i++) { |
| if (current < rt946x_boost_current[i]) |
| break; |
| } |
| |
| CPRINTS("current=%d", current); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL10, |
| RT946X_MASK_BOOST_CURRENT, |
| i << RT946X_SHIFT_BOOST_CURRENT); |
| } |
| |
| static int rt946x_set_ircmp_vclamp(int chgnum, unsigned int vclamp) |
| { |
| uint8_t reg_vclamp = 0; |
| |
| reg_vclamp = rt946x_closest_reg(RT946X_IRCMP_VCLAMP_MIN, |
| RT946X_IRCMP_VCLAMP_MAX, RT946X_IRCMP_VCLAMP_STEP, vclamp); |
| |
| CPRINTS("vclamp=%d", vclamp); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL18, |
| RT946X_MASK_IRCMP_VCLAMP, |
| reg_vclamp << RT946X_SHIFT_IRCMP_VCLAMP); |
| } |
| |
| static int rt946x_set_ircmp_res(int chgnum, unsigned int res) |
| { |
| uint8_t reg_res = 0; |
| |
| reg_res = rt946x_closest_reg(RT946X_IRCMP_RES_MIN, RT946X_IRCMP_RES_MAX, |
| RT946X_IRCMP_RES_STEP, res); |
| |
| CPRINTS("res=%d", res); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL18, |
| RT946X_MASK_IRCMP_RES, |
| reg_res << RT946X_SHIFT_IRCMP_RES); |
| } |
| |
| static int rt946x_set_vprec(int chgnum, unsigned int vprec) |
| { |
| uint8_t reg_vprec = 0; |
| |
| reg_vprec = rt946x_closest_reg(RT946X_VPREC_MIN, RT946X_VPREC_MAX, |
| RT946X_VPREC_STEP, vprec); |
| |
| CPRINTS("vprec=%d", vprec); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL8, |
| RT946X_MASK_VPREC, |
| reg_vprec << RT946X_SHIFT_VPREC); |
| } |
| |
| static int rt946x_set_iprec(int chgnum, unsigned int iprec) |
| { |
| uint8_t reg_iprec = 0; |
| |
| reg_iprec = rt946x_closest_reg(RT946X_IPREC_MIN, RT946X_IPREC_MAX, |
| RT946X_IPREC_STEP, iprec); |
| |
| CPRINTS("iprec=%d", iprec); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL8, |
| RT946X_MASK_IPREC, |
| reg_iprec << RT946X_SHIFT_IPREC); |
| } |
| |
| static int rt946x_init_irq(int chgnum) |
| { |
| int rv = 0; |
| int unused; |
| int i; |
| |
| /* Mask all interrupts */ |
| rv = rt946x_block_write(chgnum, RT946X_REG_CHGSTATCCTRL, |
| rt946x_irq_maskall, RT946X_IRQ_COUNT); |
| if (rv) |
| return rv; |
| |
| /* Clear all interrupt flags */ |
| for (i = 0; i < RT946X_IRQ_COUNT; i++) { |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGSTATC + i, &unused); |
| if (rv) |
| return rv; |
| } |
| |
| /* Init interrupt */ |
| return rt946x_block_write(chgnum, RT946X_REG_CHGSTATCCTRL, |
| rt946x_irqmask, ARRAY_SIZE(rt946x_irqmask)); |
| } |
| |
| static int rt946x_init_setting(int chgnum) |
| { |
| int rv = 0; |
| const struct battery_info *batt_info = battery_get_info(); |
| const struct rt946x_init_setting *setting = board_rt946x_init_setting(); |
| |
| #ifdef CONFIG_BATTERY_SMART |
| /* Disable EOC */ |
| rv = rt946x_enable_charge_eoc(0); |
| if (rv) |
| return rv; |
| #endif |
| |
| #ifdef CONFIG_CHARGER_OTG |
| /* Disable boost-mode output voltage */ |
| rv = rt946x_enable_otg_power(chgnum, 0); |
| if (rv) |
| return rv; |
| #endif |
| /* Disable BC 1.2 detection by default. It will be enabled on demand */ |
| rv = rt946x_enable_bc12_detection(chgnum, 0); |
| if (rv) |
| return rv; |
| /* Disable WDT */ |
| rv = rt946x_enable_wdt(chgnum, 0); |
| if (rv) |
| return rv; |
| /* Disable battery thermal protection */ |
| rv = rt946x_clr_bit(chgnum, RT946X_REG_CHGCTRL16, RT946X_MASK_JEITA_EN); |
| if (rv) |
| return rv; |
| /* Disable charge timer */ |
| rv = rt946x_clr_bit(chgnum, RT946X_REG_CHGCTRL12, RT946X_MASK_TMR_EN); |
| if (rv) |
| return rv; |
| rv = rt946x_set_mivr(chgnum, setting->mivr); |
| if (rv) |
| return rv; |
| rv = rt946x_set_ieoc(chgnum, setting->eoc_current); |
| if (rv) |
| return rv; |
| rv = rt946x_set_boost_voltage(chgnum, |
| setting->boost_voltage); |
| if (rv) |
| return rv; |
| rv = rt946x_set_boost_current(chgnum, |
| setting->boost_current); |
| if (rv) |
| return rv; |
| rv = rt946x_set_ircmp_vclamp(chgnum, setting->ircmp_vclamp); |
| if (rv) |
| return rv; |
| rv = rt946x_set_ircmp_res(chgnum, setting->ircmp_res); |
| if (rv) |
| return rv; |
| rv = rt946x_set_vprec(chgnum, batt_info->precharge_voltage ? |
| batt_info->precharge_voltage : batt_info->voltage_min); |
| if (rv) |
| return rv; |
| rv = rt946x_set_iprec(chgnum, batt_info->precharge_current); |
| if (rv) |
| return rv; |
| |
| #ifdef CONFIG_CHARGER_MT6370_BACKLIGHT |
| rt946x_write8(chgnum, MT6370_BACKLIGHT_BLEN, |
| MT6370_MASK_BLED_EXT_EN | MT6370_MASK_BLED_EN | |
| MT6370_MASK_BLED_1CH_EN | MT6370_MASK_BLED_2CH_EN | |
| MT6370_MASK_BLED_3CH_EN | MT6370_MASK_BLED_4CH_EN | |
| MT6370_BLED_CODE_LINEAR); |
| rt946x_update_bits(chgnum, MT6370_BACKLIGHT_BLPWM, |
| MT6370_MASK_BLPWM_BLED_PWM, |
| BIT(MT6370_SHIFT_BLPWM_BLED_PWM)); |
| #endif |
| |
| return rt946x_init_irq(chgnum); |
| } |
| |
| #ifdef CONFIG_CHARGER_OTG |
| static enum ec_error_list rt946x_enable_otg_power(int chgnum, int enabled) |
| { |
| return (enabled ? rt946x_set_bit : rt946x_clr_bit) |
| (chgnum, RT946X_REG_CHGCTRL1, RT946X_MASK_OPA_MODE); |
| } |
| |
| static int rt946x_is_sourcing_otg_power(int chgnum, int port) |
| { |
| int val; |
| |
| if (rt946x_read8(CHARGER_SOLO, RT946X_REG_CHGCTRL1, &val)) |
| return 0; |
| |
| return !!(val & RT946X_MASK_OPA_MODE); |
| } |
| #endif |
| |
| static enum ec_error_list rt946x_set_input_current(int chgnum, |
| int input_current) |
| { |
| uint8_t reg_iin = 0; |
| const struct charger_info * const info = rt946x_get_info(chgnum); |
| |
| reg_iin = rt946x_closest_reg(info->input_current_min, |
| info->input_current_max, info->input_current_step, |
| input_current); |
| |
| CPRINTS("iin=%d", input_current); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL3, RT946X_MASK_AICR, |
| reg_iin << RT946X_SHIFT_AICR); |
| } |
| |
| static enum ec_error_list rt946x_get_input_current(int chgnum, |
| int *input_current) |
| { |
| int rv; |
| int val = 0; |
| const struct charger_info * const info = rt946x_get_info(chgnum); |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL3, &val); |
| if (rv) |
| return rv; |
| |
| val = (val & RT946X_MASK_AICR) >> RT946X_SHIFT_AICR; |
| *input_current = val * info->input_current_step |
| + info->input_current_min; |
| |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_manufacturer_id(int chgnum, int *id) |
| { |
| return EC_ERROR_UNIMPLEMENTED; |
| } |
| |
| static enum ec_error_list rt946x_device_id(int chgnum, int *id) |
| { |
| int rv; |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_DEVICEID, id); |
| if (rv == EC_SUCCESS) |
| *id &= RT946X_MASK_VENDOR_ID; |
| return rv; |
| } |
| |
| static enum ec_error_list rt946x_get_option(int chgnum, int *option) |
| { |
| /* Ignored: does not exist */ |
| *option = 0; |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_set_option(int chgnum, int option) |
| { |
| /* Ignored: does not exist */ |
| return EC_SUCCESS; |
| } |
| |
| static const struct charger_info *rt946x_get_info(int chgnum) |
| { |
| return &rt946x_charger_info; |
| } |
| |
| static enum ec_error_list rt946x_get_status(int chgnum, int *status) |
| { |
| int rv; |
| int val = 0; |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL2, &val); |
| if (rv) |
| return rv; |
| val = (val & RT946X_MASK_CHG_EN) >> RT946X_SHIFT_CHG_EN; |
| if (!val) |
| *status |= CHARGER_CHARGE_INHIBITED; |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGFAULT, &val); |
| if (rv) |
| return rv; |
| if (val & RT946X_MASK_CHG_VBATOV) |
| *status |= CHARGER_VOLTAGE_OR; |
| |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGNTC, &val); |
| if (rv) |
| return rv; |
| val = (val & RT946X_MASK_BATNTC_FAULT) >> RT946X_SHIFT_BATNTC_FAULT; |
| |
| switch (val) { |
| case RT946X_BATTEMP_WARM: |
| *status |= CHARGER_RES_HOT; |
| break; |
| case RT946X_BATTEMP_COOL: |
| *status |= CHARGER_RES_COLD; |
| break; |
| case RT946X_BATTEMP_COLD: |
| *status |= CHARGER_RES_COLD; |
| *status |= CHARGER_RES_UR; |
| break; |
| case RT946X_BATTEMP_HOT: |
| *status |= CHARGER_RES_HOT; |
| *status |= CHARGER_RES_OR; |
| break; |
| default: |
| break; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_set_mode(int chgnum, int mode) |
| { |
| int rv; |
| |
| if (mode & CHARGE_FLAG_POR_RESET) { |
| rv = rt946x_por_reset(); |
| if (rv) |
| return rv; |
| } |
| |
| if (mode & CHARGE_FLAG_RESET_TO_ZERO) { |
| rv = rt946x_reset_to_zero(chgnum); |
| if (rv) |
| return rv; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_get_current(int chgnum, int *current) |
| { |
| int rv; |
| int val = 0; |
| const struct charger_info * const info = rt946x_get_info(chgnum); |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL7, &val); |
| if (rv) |
| return rv; |
| |
| val = (val & RT946X_MASK_ICHG) >> RT946X_SHIFT_ICHG; |
| *current = val * info->current_step + info->current_min; |
| |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_set_current(int chgnum, int current) |
| { |
| int rv; |
| uint8_t reg_icc; |
| static int workaround; |
| const struct charger_info *const info = rt946x_get_info(chgnum); |
| |
| /* |
| * mt6370's minimum regulated current is 500mA REG17[7:2] 0b100, |
| * values below 0b100 are preserved. |
| */ |
| if (IS_ENABLED(CONFIG_CHARGER_MT6370)) |
| current = MAX(500, current); |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| rv = mt6370_ichg_workaround(chgnum, current); |
| if (rv) |
| return rv; |
| #endif |
| |
| reg_icc = rt946x_closest_reg(info->current_min, info->current_max, |
| info->current_step, current); |
| |
| rv = rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL7, RT946X_MASK_ICHG, |
| reg_icc << RT946X_SHIFT_ICHG); |
| if (rv) |
| return rv; |
| |
| if (IS_ENABLED(CONFIG_CHARGER_RT9466) || |
| IS_ENABLED(CONFIG_CHARGER_MT6370)) { |
| uint32_t curr_ieoc; |
| |
| /* |
| * workaround to make IEOC accurate: |
| * witht normal charging (ICC >= 900mA), the power path is fully |
| * turned on. But at low charging current state (ICC < 900mA), |
| * the power path will only be partially turned on. So under |
| * such situation, the IEOC is inaccurate. |
| */ |
| rv = rt946x_get_ieoc(chgnum, &curr_ieoc); |
| if (rv) |
| return rv; |
| |
| if (current < 900 && !workaround) { |
| /* raise IEOC if charge current is under 900 */ |
| rv = rt946x_set_ieoc(chgnum, curr_ieoc + 100); |
| workaround = 1; |
| } else if (current >= 900 && workaround) { |
| /* reset IEOC if charge current is above 900 */ |
| workaround = 0; |
| rv = rt946x_set_ieoc(chgnum, curr_ieoc - 100); |
| } |
| } |
| |
| return rv; |
| } |
| |
| static enum ec_error_list rt946x_get_voltage(int chgnum, int *voltage) |
| { |
| int rv; |
| int val = 0; |
| const struct charger_info * const info = rt946x_get_info(chgnum); |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL4, &val); |
| if (rv) |
| return rv; |
| |
| val = (val & RT946X_MASK_CV) >> RT946X_SHIFT_CV; |
| *voltage = val * info->voltage_step + info->voltage_min; |
| |
| return EC_SUCCESS; |
| } |
| |
| static enum ec_error_list rt946x_set_voltage(int chgnum, int voltage) |
| { |
| uint8_t reg_cv = 0; |
| const struct charger_info * const info = rt946x_get_info(chgnum); |
| |
| reg_cv = rt946x_closest_reg(info->voltage_min, info->voltage_max, |
| info->voltage_step, voltage); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL4, RT946X_MASK_CV, |
| reg_cv << RT946X_SHIFT_CV); |
| } |
| |
| static enum ec_error_list rt946x_discharge_on_ac(int chgnum, int enable) |
| { |
| return rt946x_enable_hz(chgnum, enable); |
| } |
| |
| /* Setup sourcing current to prevent overload */ |
| #ifdef CONFIG_CHARGER_ILIM_PIN_DISABLED |
| static int rt946x_enable_ilim_pin(int chgnum, int en) |
| { |
| int ret; |
| |
| ret = (en ? rt946x_set_bit : rt946x_clr_bit) |
| (chgnum, RT946X_REG_CHGCTRL3, RT946X_MASK_ILIMEN); |
| |
| return ret; |
| } |
| |
| static int rt946x_select_ilmt(int chgnum, enum rt946x_ilmtsel sel) |
| { |
| int ret; |
| |
| ret = rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL2, |
| RT946X_MASK_ILMTSEL, |
| sel << RT946X_SHIFT_ILMTSEL); |
| |
| return ret; |
| } |
| #endif /* CONFIG_CHARGER_ILIM_PIN_DISABLED */ |
| |
| /* Charging power state initialization */ |
| static enum ec_error_list rt946x_post_init(int chgnum) |
| { |
| #ifdef CONFIG_CHARGER_ILIM_PIN_DISABLED |
| int rv; |
| |
| rv = rt946x_select_ilmt(chgnum, RT946X_ILMTSEL_AICR); |
| if (rv) |
| return rv; |
| |
| /* Need 5ms to ramp after choose current limit source */ |
| msleep(5); |
| |
| /* Disable ILIM pin */ |
| rv = rt946x_enable_ilim_pin(chgnum, 0); |
| if (rv) |
| return rv; |
| #endif |
| return EC_SUCCESS; |
| } |
| |
| /* Hardware current ramping (aka AICL: Average Input Current Level) */ |
| #ifdef CONFIG_CHARGE_RAMP_HW |
| static int rt946x_get_mivr(int chgnum, int *mivr) |
| { |
| int rv; |
| int val = 0; |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL6, &val); |
| if (rv) |
| return rv; |
| |
| val = (val & RT946X_MASK_MIVR) >> RT946X_SHIFT_MIVR; |
| *mivr = val * RT946X_MIVR_STEP + RT946X_MIVR_MIN; |
| |
| return EC_SUCCESS; |
| } |
| |
| static int rt946x_set_aicl_vth(int chgnum, uint8_t aicl_vth) |
| { |
| uint8_t reg_aicl_vth = 0; |
| |
| reg_aicl_vth = rt946x_closest_reg(RT946X_AICLVTH_MIN, |
| RT946X_AICLVTH_MAX, RT946X_AICLVTH_STEP, aicl_vth); |
| |
| return rt946x_update_bits(chgnum, RT946X_REG_CHGCTRL14, |
| RT946X_MASK_AICLVTH, |
| reg_aicl_vth << RT946X_SHIFT_AICLVTH); |
| } |
| |
| static enum ec_error_list rt946x_set_hw_ramp(int chgnum, int enable) |
| { |
| int rv; |
| unsigned int mivr = 0; |
| |
| if (!enable) { |
| rv = rt946x_clr_bit(chgnum, RT946X_REG_CHGCTRL14, |
| RT946X_MASK_AICLMEAS); |
| return rv; |
| } |
| |
| rv = rt946x_get_mivr(chgnum, &mivr); |
| if (rv < 0) |
| return rv; |
| |
| /* |
| * Check if there's a suitable AICL_VTH. |
| * The vendor suggests setting AICL_VTH as (MIVR + 200mV). |
| */ |
| if ((mivr + 200) > RT946X_AICLVTH_MAX) { |
| CPRINTS("mivr(%d) too high", mivr); |
| return EC_ERROR_INVAL; |
| } |
| |
| rv = rt946x_set_aicl_vth(chgnum, mivr + 200); |
| if (rv < 0) |
| return rv; |
| |
| return rt946x_set_bit(chgnum, RT946X_REG_CHGCTRL14, |
| RT946X_MASK_AICLMEAS); |
| } |
| |
| static int rt946x_ramp_is_stable(int chgnum) |
| { |
| int rv; |
| int val = 0; |
| |
| rv = rt946x_read8(chgnum, RT946X_REG_CHGCTRL14, &val); |
| val = (val & RT946X_MASK_AICLMEAS) >> RT946X_SHIFT_AICLMEAS; |
| |
| return (!rv && !val); |
| } |
| |
| static int rt946x_ramp_is_detected(int chgnum) |
| { |
| return 1; |
| } |
| |
| static int rt946x_ramp_get_current_limit(int chgnum) |
| { |
| int rv; |
| int input_current = 0; |
| |
| rv = rt946x_get_input_current(chgnum, &input_current); |
| |
| return rv ? -1 : input_current; |
| } |
| #endif /* CONFIG_CHARGE_RAMP_HW */ |
| |
| static void rt946x_init(int chgnum) |
| { |
| int ret = rt946x_init_setting(chgnum); |
| |
| CPRINTS("init%d %s(%d)", chgnum, ret ? "fail" : "good", ret); |
| } |
| |
| #ifdef HAS_TASK_USB_CHG |
| #ifdef CONFIG_CHARGER_MT6370 |
| static int mt6370_detect_apple_samsung_ta(int chgnum, int usb_stat) |
| { |
| int ret, reg; |
| int chg_type = |
| (usb_stat & MT6370_MASK_USB_STATUS) >> MT6370_SHIFT_USB_STATUS; |
| int dp_2_3v, dm_2_3v; |
| |
| /* Only SDP/CDP/DCP could possibly be Apple/Samsung TA */ |
| if (chg_type != MT6370_CHG_TYPE_SDPNSTD && |
| chg_type != MT6370_CHG_TYPE_CDP && |
| chg_type != MT6370_CHG_TYPE_DCP) |
| return chg_type; |
| |
| if (chg_type == MT6370_CHG_TYPE_SDPNSTD || |
| chg_type == MT6370_CHG_TYPE_CDP) |
| if (!(usb_stat & MT6370_MASK_DCD_TIMEOUT)) |
| return chg_type; |
| |
| /* Check D+ > 0.9V */ |
| ret = rt946x_update_bits(chgnum, MT6370_REG_QCSTATUS2, |
| MT6360_MASK_CHECK_DPDM, |
| MT6370_MASK_APP_SS_EN | MT6370_MASK_APP_SS_PL); |
| ret |= rt946x_read8(chgnum, MT6370_REG_QCSTATUS2, ®); |
| |
| if (ret) |
| return chg_type; |
| |
| /* Normal port (D+ < 0.9V) */ |
| if (!(reg & MT6370_MASK_SS_OUT)) |
| return chg_type; |
| |
| /* Samsung charger (D+ < 1.5V) */ |
| if (!(reg & MT6370_MASK_APP_OUT)) |
| return MT6370_CHG_TYPE_SAMSUNG_CHARGER; |
| |
| /* Check D+ > 2.3 V */ |
| ret = rt946x_update_bits(chgnum, MT6370_REG_QCSTATUS2, |
| MT6360_MASK_CHECK_DPDM, |
| MT6370_MASK_APP_REF | MT6370_MASK_APP_SS_PL | |
| MT6370_MASK_APP_SS_EN); |
| ret |= rt946x_read8(chgnum, MT6370_REG_QCSTATUS2, ®); |
| dp_2_3v = reg & MT6370_MASK_APP_OUT; |
| |
| /* Check D- > 2.3 V */ |
| ret |= rt946x_update_bits(chgnum, |
| MT6370_REG_QCSTATUS2, MT6360_MASK_CHECK_DPDM, |
| MT6370_MASK_APP_REF | MT6370_MASK_APP_DPDM_IN | |
| MT6370_MASK_APP_SS_PL | MT6370_MASK_APP_SS_EN); |
| ret |= rt946x_read8(chgnum, MT6370_REG_QCSTATUS2, ®); |
| dm_2_3v = reg & MT6370_MASK_APP_OUT; |
| |
| if (ret) |
| return chg_type; |
| |
| /* Apple charger */ |
| if (!dp_2_3v && !dm_2_3v) |
| /* Apple 2.5W charger */ |
| return MT6370_CHG_TYPE_APPLE_0_5A_CHARGER; |
| else if (!dp_2_3v && dm_2_3v) |
| /* Apple 5W charger */ |
| return MT6370_CHG_TYPE_APPLE_1_0A_CHARGER; |
| else if (dp_2_3v && !dm_2_3v) |
| /* Apple 10W charger */ |
| return MT6370_CHG_TYPE_APPLE_2_1A_CHARGER; |
| else |
| /* Apple 12W charger */ |
| return MT6370_CHG_TYPE_APPLE_2_4A_CHARGER; |
| } |
| #endif |
| |
| static int mt6370_get_bc12_device_type(int charger_type) |
| { |
| switch (charger_type) { |
| case MT6370_CHG_TYPE_SDP: |
| case MT6370_CHG_TYPE_SDPNSTD: |
| return CHARGE_SUPPLIER_BC12_SDP; |
| case MT6370_CHG_TYPE_CDP: |
| return CHARGE_SUPPLIER_BC12_CDP; |
| case MT6370_CHG_TYPE_DCP: |
| case MT6370_CHG_TYPE_SAMSUNG_CHARGER: |
| case MT6370_CHG_TYPE_APPLE_0_5A_CHARGER: |
| case MT6370_CHG_TYPE_APPLE_1_0A_CHARGER: |
| case MT6370_CHG_TYPE_APPLE_2_1A_CHARGER: |
| case MT6370_CHG_TYPE_APPLE_2_4A_CHARGER: |
| return CHARGE_SUPPLIER_BC12_DCP; |
| default: |
| return CHARGE_SUPPLIER_NONE; |
| } |
| } |
| |
| /* Returns a mt6370 charger_type. */ |
| static int mt6370_get_charger_type(int chgnum) |
| { |
| #ifdef CONFIG_CHARGER_MT6370 |
| int reg; |
| |
| if (rt946x_read8(chgnum, MT6370_REG_USBSTATUS1, ®)) |
| return CHARGE_SUPPLIER_NONE; |
| return mt6370_detect_apple_samsung_ta(chgnum, reg); |
| #else |
| return CHARGE_SUPPLIER_NONE; |
| #endif |
| } |
| |
| /* |
| * The USB Type-C specification limits the maximum amount of current from BC 1.2 |
| * suppliers to 1.5A. Technically, proprietary methods are not allowed, but we |
| * will continue to allow those. |
| */ |
| static int mt6370_get_bc12_ilim(int charge_supplier) |
| { |
| switch (charge_supplier) { |
| case MT6370_CHG_TYPE_APPLE_0_5A_CHARGER: |
| return 500; |
| case MT6370_CHG_TYPE_APPLE_1_0A_CHARGER: |
| return 1000; |
| case MT6370_CHG_TYPE_APPLE_2_1A_CHARGER: |
| case MT6370_CHG_TYPE_APPLE_2_4A_CHARGER: |
| case MT6370_CHG_TYPE_DCP: |
| case MT6370_CHG_TYPE_CDP: |
| case MT6370_CHG_TYPE_SAMSUNG_CHARGER: |
| return USB_CHARGER_MAX_CURR_MA; |
| case MT6370_CHG_TYPE_SDP: |
| default: |
| return USB_CHARGER_MIN_CURR_MA; |
| } |
| } |
| |
| static int rt946x_get_bc12_device_type(int chgnum, int charger_type) |
| { |
| int reg; |
| |
| if (rt946x_read8(chgnum, RT946X_REG_DPDM1, ®)) |
| return CHARGE_SUPPLIER_NONE; |
| |
| switch (reg & RT946X_MASK_BC12_TYPE) { |
| case RT946X_MASK_SDP: |
| return CHARGE_SUPPLIER_BC12_SDP; |
| case RT946X_MASK_CDP: |
| return CHARGE_SUPPLIER_BC12_CDP; |
| case RT946X_MASK_DCP: |
| return CHARGE_SUPPLIER_BC12_DCP; |
| default: |
| return CHARGE_SUPPLIER_NONE; |
| } |
| } |
| |
| static int rt946x_get_bc12_ilim(int charge_supplier) |
| { |
| switch (charge_supplier) { |
| case CHARGE_SUPPLIER_BC12_DCP: |
| if (IS_ENABLED(CONFIG_CHARGE_RAMP_SW) || |
| IS_ENABLED(CONFIG_CHARGE_RAMP_HW)) |
| /* A conservative value to prevent a bad charger. */ |
| return RT946X_AICR_TYP2MAX(USB_CHARGER_MAX_CURR_MA); |
| /* fallback */ |
| case CHARGE_SUPPLIER_BC12_CDP: |
| return USB_CHARGER_MAX_CURR_MA; |
| case CHARGE_SUPPLIER_BC12_SDP: |
| default: |
| return USB_CHARGER_MIN_CURR_MA; |
| } |
| } |
| |
| static void check_ac_state(void) |
| { |
| static uint8_t ac; |
| |
| if (ac != extpower_is_present()) { |
| ac = !ac; |
| hook_notify(HOOK_AC_CHANGE); |
| } |
| } |
| DECLARE_DEFERRED(check_ac_state); |
| |
| void rt946x_interrupt(enum gpio_signal signal) |
| { |
| task_wake(TASK_ID_USB_CHG); |
| /* |
| * Generally, VBUS detection can be done immediately when the port |
| * plug/unplug happens. But if it's a PD plug(and will generate an |
| * interrupt), then it will take a few milliseconds to raise VBUS |
| * by PD negotiation. |
| */ |
| hook_call_deferred(&check_ac_state_data, 100 * MSEC); |
| } |
| |
| int rt946x_toggle_bc12_detection(void) |
| { |
| int rv; |
| rv = rt946x_enable_bc12_detection(CHARGER_SOLO, 0); |
| if (rv) |
| return rv; |
| /* mt6370 requires 40us delay to toggle RT946X_MASK_USBCHGEN */ |
| udelay(40); |
| return rt946x_enable_bc12_detection(CHARGER_SOLO, 1); |
| } |
| |
| static void check_pd_capable(void) |
| { |
| const int port = TASK_ID_TO_USB_CHG_PORT(TASK_ID_USB_CHG); |
| |
| if (!pd_capable(port)) { |
| enum tcpc_cc_voltage_status cc1, cc2; |
| |
| tcpm_get_cc(port, &cc1, &cc2); |
| /* if CC is not changed. */ |
| if (cc_is_rp(cc1) || cc_is_rp(cc2)) |
| rt946x_toggle_bc12_detection(); |
| } |
| } |
| DECLARE_DEFERRED(check_pd_capable); |
| |
| static void rt946x_usb_connect(void) |
| { |
| const int port = TASK_ID_TO_USB_CHG_PORT(TASK_ID_USB_CHG); |
| enum tcpc_cc_voltage_status cc1, cc2; |
| |
| tcpm_get_cc(port, &cc1, &cc2); |
| |
| /* |
| * Only detect BC1.2 device when USB-C device recognition is |
| * finished to prevent a potential race condition with USB enumeration. |
| * If CC exists RP, then it might be a BC12 or a PD capable device. |
| * Check this later to ensure it's not PD capable. |
| */ |
| if (cc_is_rp(cc1) || cc_is_rp(cc2)) |
| /* delay extra 50 ms to ensure SrcCap received */ |
| hook_call_deferred(&check_pd_capable_data, |
| PD_T_SINK_WAIT_CAP + 50 * MSEC); |
| hook_call_deferred(&check_ac_state_data, 0); |
| } |
| DECLARE_HOOK(HOOK_USB_PD_CONNECT, rt946x_usb_connect, HOOK_PRIO_DEFAULT); |
| |
| static void rt946x_pd_disconnect(void) |
| { |
| /* Type-C disconnected, disable deferred check. */ |
| hook_call_deferred(&check_pd_capable_data, -1); |
| hook_call_deferred(&check_ac_state_data, 0); |
| } |
| DECLARE_HOOK(HOOK_USB_PD_DISCONNECT, rt946x_pd_disconnect, HOOK_PRIO_DEFAULT); |
| |
| int rt946x_get_adc(enum rt946x_adc_in_sel adc_sel, int *adc_val) |
| { |
| int rv, i, adc_start, adc_result = 0; |
| int adc_data_h, adc_data_l, aicr; |
| const int max_wait_times = 6; |
| |
| if (in_interrupt_context()) { |
| CPRINTS("Err: use ADC in IRQ"); |
| return EC_ERROR_INVAL; |
| } |
| mutex_lock(&adc_access_lock); |
| #ifdef CONFIG_CHARGER_MT6370 |
| mt6370_enable_hidden_mode(CHARGER_SOLO, 1); |
| #endif |
| |
| /* Select ADC to desired channel */ |
| rv = rt946x_update_bits(CHARGER_SOLO, RT946X_REG_CHGADC, |
| RT946X_MASK_ADC_IN_SEL, |
| adc_sel << RT946X_SHIFT_ADC_IN_SEL); |
| if (rv) |
| goto out; |
| |
| if (adc_sel == MT6370_ADC_IBUS) { |
| rv = charger_get_input_current(CHARGER_SOLO, &aicr); |
| if (rv) |
| goto out; |
| } |
| |
| /* Start ADC conversation */ |
| rv = rt946x_set_bit(CHARGER_SOLO, RT946X_REG_CHGADC, |
| RT946X_MASK_ADC_START); |
| if (rv) |
| goto out; |
| |
| for (i = 0; i < max_wait_times; i++) { |
| msleep(35); |
| rv = mt6370_pmu_reg_test_bit(CHARGER_SOLO, RT946X_REG_CHGADC, |
| RT946X_SHIFT_ADC_START, |
| &adc_start); |
| if (!adc_start && rv == 0) |
| break; |
| } |
| if (i == max_wait_times) |
| CPRINTS("conversion fail sel=%d", adc_sel); |
| |
| /* Read ADC data */ |
| rv = rt946x_read8(CHARGER_SOLO, RT946X_REG_ADCDATAH, &adc_data_h); |
| rv = rt946x_read8(CHARGER_SOLO, RT946X_REG_ADCDATAL, &adc_data_l); |
| if (rv) |
| goto out; |
| |
| #if defined(CONFIG_CHARGER_RT9466) || defined(CONFIG_CHARGER_RT9467) |
| if (adc_sel == RT946X_ADC_VBUS_DIV5) |
| adc_result = ((adc_data_h << 8) | adc_data_l) * 25; |
| else |
| CPRINTS("unsupported channel %d", adc_sel); |
| *adc_val = adc_result; |
| #elif defined(CONFIG_CHARGER_MT6370) |
| /* Calculate ADC value */ |
| adc_result = (adc_data_h * 256 + adc_data_l) |
| * mt6370_adc_unit[adc_sel] + mt6370_adc_offset[adc_sel]; |
| |
| /* For TS_BAT/TS_BUS, the real unit is 0.25, here we use 25(unit) */ |
| if (adc_sel == MT6370_ADC_TS_BAT) |
| adc_result /= 100; |
| #endif |
| |
| out: |
| #ifdef CONFIG_CHARGER_MT6370 |
| if (adc_sel == MT6370_ADC_IBUS) { |
| if (aicr < 400) /* 400mA */ |
| adc_result = adc_result * 67 / 100; |
| } |
| |
| if (adc_sel != MT6370_ADC_TS_BAT && adc_sel != MT6370_ADC_TEMP_JC) |
| *adc_val = adc_result / 1000; |
| else |
| *adc_val = adc_result; |
| mt6370_enable_hidden_mode(CHARGER_SOLO, 0); |
| #endif |
| mutex_unlock(&adc_access_lock); |
| return rv; |
| } |
| |
| static enum ec_error_list rt946x_get_vbus_voltage(int chgnum, int port, |
| int *voltage) |
| { |
| int vbus_mv; |
| int rv; |
| |
| rv = rt946x_get_adc(RT946X_ADC_VBUS_DIV5, &vbus_mv); |
| *voltage = vbus_mv; |
| |
| return rv; |
| } |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| static int mt6370_toggle_cfo(void) |
| { |
| int rv, data; |
| |
| rv = rt946x_read8(CHARGER_SOLO, MT6370_REG_FLEDEN, &data); |
| if (rv) |
| return rv; |
| |
| if (data & MT6370_STROBE_EN_MASK) |
| return rv; |
| |
| /* read data */ |
| rv = rt946x_read8(CHARGER_SOLO, RT946X_REG_CHGCTRL2, &data); |
| if (rv) |
| return rv; |
| |
| /* cfo off */ |
| data &= ~RT946X_MASK_CFO_EN; |
| rv = rt946x_write8(CHARGER_SOLO, RT946X_REG_CHGCTRL2, data); |
| if (rv) |
| return rv; |
| |
| /* cfo on */ |
| data |= RT946X_MASK_CFO_EN; |
| return rt946x_write8(CHARGER_SOLO, RT946X_REG_CHGCTRL2, data); |
| } |
| |
| static int mt6370_pmu_chg_mivr_irq_handler(int chgnum) |
| { |
| int rv, ibus = 0, mivr_stat; |
| |
| rv = mt6370_pmu_reg_test_bit(chgnum, MT6370_REG_CHGSTAT1, |
| MT6370_SHIFT_MIVR_STAT, &mivr_stat); |
| if (rv) |
| return rv; |
| |
| if (!mivr_stat) { |
| CPRINTS("no mivr stat"); |
| return rv; |
| } |
| |
| rv = rt946x_get_adc(MT6370_ADC_IBUS, &ibus); |
| if (rv) |
| return rv; |
| |
| if (ibus < 100) /* 100mA */ |
| rv = mt6370_toggle_cfo(); |
| |
| return rv; |
| } |
| |
| static int mt6370_irq_handler(int chgnum) |
| { |
| int data, mask, ret, reg_val; |
| int stat_chg, valid_chg, stat_old, stat_new; |
| |
| ret = rt946x_write8(chgnum, MT6370_REG_IRQMASK, MT6370_IRQ_MASK_ALL); |
| if (ret) |
| return ret; |
| |
| ret = rt946x_read8(chgnum, MT6370_REG_IRQIND, ®_val); |
| if (ret) |
| return ret; |
| |
| /* read stat before reading irq evt */ |
| ret = rt946x_read8(chgnum, MT6370_REG_CHGSTAT1, &stat_old); |
| if (ret) |
| return ret; |
| |
| /* workaround for irq, divided irq event into upper and lower */ |
| ret = rt946x_read8(chgnum, MT6370_REG_CHGIRQ1, &data); |
| if (ret) |
| return ret; |
| |
| /* read stat after reading irq evt */ |
| ret = rt946x_read8(chgnum, MT6370_REG_CHGSTAT1, &stat_new); |
| if (ret) |
| return ret; |
| |
| ret = rt946x_read8(chgnum, MT6370_REG_CHGMASK1, &mask); |
| if (ret) |
| return ret; |
| |
| ret = rt946x_write8(chgnum, MT6370_REG_IRQMASK, 0x00); |
| if (ret) |
| return ret; |
| |
| stat_chg = stat_old ^ stat_new; |
| valid_chg = (stat_new & 0xF1) | (~stat_new & 0xF1); |
| data |= (stat_chg & valid_chg); |
| data &= ~mask; |
| if (data) |
| ret = mt6370_pmu_chg_mivr_irq_handler(chgnum); |
| return ret; |
| } |
| #endif /* CONFIG_CHARGER_MT6370 */ |
| |
| static void rt946x_bc12_workaround(void) |
| { |
| /* |
| * There is a parasitic capacitance on D+, |
| * which results in pulling D+ up too slow while detecting BC1.2. |
| * So we try to fix this in two steps: |
| * 1. Pull D+ up to a voltage under 0.6V |
| * 2. re-toggling and pull D+ up to 0.6V (again) |
| * and then detect the voltage of D-. |
| */ |
| rt946x_toggle_bc12_detection(); |
| msleep(10); |
| rt946x_toggle_bc12_detection(); |
| } |
| DECLARE_DEFERRED(rt946x_bc12_workaround); |
| |
| static void rt946x_usb_charger_task(const int unused) |
| { |
| struct charge_port_info chg; |
| int bc12_type = CHARGE_SUPPLIER_NONE; |
| int chg_type; |
| int reg = 0; |
| int bc12_cnt = 0; |
| const int max_bc12_cnt = 3; |
| int voltage; |
| |
| chg.voltage = USB_CHARGER_VOLTAGE_MV; |
| while (1) { |
| #ifdef CONFIG_CHARGER_MT6370 |
| mt6370_irq_handler(CHARGER_SOLO); |
| #endif /* CONFIG_CHARGER_MT6370 */ |
| |
| rt946x_read8(CHARGER_SOLO, RT946X_REG_DPDMIRQ, ®); |
| |
| /* VBUS attach event */ |
| if (reg & RT946X_MASK_DPDMIRQ_ATTACH) { |
| charger_get_vbus_voltage(0, &voltage); |
| CPRINTS("VBUS attached: %dmV", voltage); |
| if (IS_ENABLED(CONFIG_CHARGER_MT6370)) { |
| chg_type = |
| mt6370_get_charger_type(CHARGER_SOLO); |
| bc12_type = |
| mt6370_get_bc12_device_type(chg_type); |
| chg.current = mt6370_get_bc12_ilim(bc12_type); |
| } else { |
| bc12_type = |
| rt946x_get_bc12_device_type(CHARGER_SOLO, |
| chg_type); |
| chg.current = rt946x_get_bc12_ilim(bc12_type); |
| } |
| CPRINTS("BC12 type %d", bc12_type); |
| if (bc12_type == CHARGE_SUPPLIER_NONE) |
| goto bc12_none; |
| if (IS_ENABLED(CONFIG_WIRELESS_CHARGER_P9221_R7) && |
| bc12_type == CHARGE_SUPPLIER_BC12_SDP && |
| wpc_chip_is_online()) { |
| p9221_notify_vbus_change(1); |
| CPRINTS("WPC ON"); |
| } |
| if (bc12_type == CHARGE_SUPPLIER_BC12_SDP && |
| ++bc12_cnt < max_bc12_cnt) { |
| /* |
| * defer the workaround and awaiting for |
| * waken up by the interrupt. |
| */ |
| hook_call_deferred( |
| &rt946x_bc12_workaround_data, 5); |
| goto wait_event; |
| } |
| |
| charge_manager_update_charge(bc12_type, 0, &chg); |
| bc12_none: |
| rt946x_enable_bc12_detection(CHARGER_SOLO, 0); |
| } |
| |
| /* VBUS detach event */ |
| if (reg & RT946X_MASK_DPDMIRQ_DETACH && |
| bc12_type != CHARGE_SUPPLIER_NONE) { |
| CPRINTS("VBUS detached"); |
| bc12_cnt = 0; |
| #ifdef CONFIG_WIRELESS_CHARGER_P9221_R7 |
| p9221_notify_vbus_change(0); |
| #endif |
| charge_manager_update_charge(bc12_type, 0, NULL); |
| } |
| |
| wait_event: |
| task_wait_event(-1); |
| } |
| } |
| |
| static int rt946x_ramp_allowed(int supplier) |
| { |
| return supplier == CHARGE_SUPPLIER_BC12_DCP; |
| } |
| |
| static int rt946x_ramp_max(int supplier, int sup_curr) |
| { |
| return rt946x_get_bc12_ilim(supplier); |
| } |
| #endif /* HAS_TASK_USB_CHG */ |
| |
| /* Non-standard interface functions */ |
| |
| int rt946x_enable_charger_boost(int en) |
| { |
| return (en ? rt946x_set_bit : rt946x_clr_bit) |
| (CHARGER_SOLO, RT946X_REG_CHGCTRL2, RT946X_MASK_CHG_EN); |
| } |
| |
| /* |
| * rt946x reports VBUS ready after VBUS is up for ~500ms. |
| * Check if this works for the use case before calling this function. |
| */ |
| int rt946x_is_vbus_ready(void) |
| { |
| int val = 0; |
| |
| return rt946x_read8(CHARGER_SOLO, RT946X_REG_CHGSTATC, &val) ? |
| 0 : !!(val & RT946X_MASK_PWR_RDY); |
| } |
| |
| int rt946x_is_charge_done(void) |
| { |
| int val = 0; |
| |
| if (rt946x_read8(CHARGER_SOLO, RT946X_REG_CHGSTAT, &val)) |
| return 0; |
| |
| val = (val & RT946X_MASK_CHG_STAT) >> RT946X_SHIFT_CHG_STAT; |
| |
| return val == RT946X_CHGSTAT_DONE; |
| } |
| |
| int rt946x_cutoff_battery(void) |
| { |
| #ifdef CONFIG_CHARGER_MT6370 |
| /* |
| * We should lock ADC usage to prevent from using ADC while |
| * cut-off. Or this might cause the ADC power not turning off. |
| */ |
| |
| int rv; |
| |
| mutex_lock(&adc_access_lock); |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_REG_RSTPASCODE1, |
| MT6370_MASK_RSTPASCODE1); |
| if (rv) |
| goto out; |
| |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_REG_RSTPASCODE2, |
| MT6370_MASK_RSTPASCODE2); |
| if (rv) |
| goto out; |
| |
| /* reset all chg/fled/ldo/rgb/bl/db reg and logic */ |
| rv = rt946x_write8(CHARGER_SOLO, RT946X_REG_CORECTRL2, 0x7F); |
| if (rv) |
| goto out; |
| |
| /* disable chg auto sensing */ |
| mt6370_enable_hidden_mode(CHARGER_SOLO, 1); |
| rv = rt946x_clr_bit(CHARGER_SOLO, MT6370_REG_CHGHIDDENCTRL15, |
| MT6370_MASK_ADC_TS_AUTO); |
| mt6370_enable_hidden_mode(CHARGER_SOLO, 0); |
| if (rv) |
| goto out; |
| msleep(50); |
| /* enter shipping mode */ |
| rv = rt946x_set_bit(CHARGER_SOLO, RT946X_REG_CHGCTRL2, |
| RT946X_MASK_SHIP_MODE); |
| |
| out: |
| mutex_unlock(&adc_access_lock); |
| return rv; |
| #endif |
| /* enter shipping mode */ |
| return rt946x_set_bit(CHARGER_SOLO, RT946X_REG_CHGCTRL2, |
| RT946X_MASK_SHIP_MODE); |
| } |
| |
| int rt946x_enable_charge_termination(int en) |
| { |
| return (en ? rt946x_set_bit : rt946x_clr_bit) |
| (CHARGER_SOLO, RT946X_REG_CHGCTRL2, RT946X_MASK_TE); |
| } |
| |
| int rt946x_enable_charge_eoc(int en) |
| { |
| return (en ? rt946x_set_bit : rt946x_clr_bit) |
| (CHARGER_SOLO, RT946X_REG_CHGCTRL9, RT946X_MASK_EOC); |
| } |
| |
| #ifdef CONFIG_CHARGER_MT6370 |
| /* MT6370 LDO */ |
| |
| int mt6370_set_ldo_voltage(int mv) |
| { |
| int rv; |
| int vout_val; |
| const int vout_mask = MT6370_MASK_LDOVOUT_EN | MT6370_MASK_LDOVOUT_VOUT; |
| |
| /* LDO output-off mode to floating. */ |
| rv = rt946x_update_bits(CHARGER_SOLO, MT6370_REG_LDOCFG, |
| MT6370_MASK_LDOCFG_OMS, 0); |
| if (rv) |
| return rv; |
| |
| /* Disable LDO if voltage is zero. */ |
| if (mv == 0) |
| return rt946x_clr_bit(CHARGER_SOLO, MT6370_REG_LDOVOUT, |
| MT6370_MASK_LDOVOUT_EN); |
| |
| vout_val = 1 << MT6370_SHIFT_LDOVOUT_EN; |
| vout_val |= rt946x_closest_reg(MT6370_LDO_MIN, MT6370_LDO_MAX, |
| MT6370_LDO_STEP, mv); |
| return rt946x_update_bits(CHARGER_SOLO, MT6370_REG_LDOVOUT, vout_mask, |
| vout_val); |
| } |
| |
| /* MT6370 Display bias */ |
| int mt6370_db_external_control(int en) |
| { |
| return rt946x_update_bits(CHARGER_SOLO, MT6370_REG_DBCTRL1, |
| MT6370_MASK_DB_EXT_EN, |
| en << MT6370_SHIFT_DB_EXT_EN); |
| } |
| |
| int mt6370_db_set_voltages(int vbst, int vpos, int vneg) |
| { |
| int rv; |
| |
| /* set display bias VBST */ |
| rv = rt946x_update_bits(CHARGER_SOLO, MT6370_REG_DBVBST, |
| MT6370_MASK_DB_VBST, |
| rt946x_closest_reg(MT6370_DB_VBST_MIN, |
| MT6370_DB_VBST_MAX, |
| MT6370_DB_VBST_STEP, vbst)); |
| |
| /* set display bias VPOS */ |
| rv |= rt946x_update_bits(CHARGER_SOLO, MT6370_REG_DBVPOS, |
| MT6370_MASK_DB_VPOS, |
| rt946x_closest_reg(MT6370_DB_VPOS_MIN, |
| MT6370_DB_VPOS_MAX, |
| MT6370_DB_VPOS_STEP, vpos)); |
| |
| /* set display bias VNEG */ |
| rv |= rt946x_update_bits(CHARGER_SOLO, MT6370_REG_DBVNEG, |
| MT6370_MASK_DB_VNEG, |
| rt946x_closest_reg(MT6370_DB_VNEG_MIN, |
| MT6370_DB_VNEG_MAX, |
| MT6370_DB_VNEG_STEP, vneg)); |
| |
| /* Enable VNEG/VPOS discharge when VNEG/VPOS rails disabled. */ |
| rv |= rt946x_update_bits(CHARGER_SOLO, |
| MT6370_REG_DBCTRL2, |
| MT6370_MASK_DB_VNEG_DISC | MT6370_MASK_DB_VPOS_DISC, |
| MT6370_MASK_DB_VNEG_DISC | MT6370_MASK_DB_VPOS_DISC); |
| |
| return rv; |
| } |
| |
| /* MT6370 BACKLIGHT LED */ |
| |
| int mt6370_backlight_set_dim(uint16_t dim) |
| { |
| int rv; |
| |
| /* datasheet suggests that update BLDIM2 first then BLDIM */ |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_BACKLIGHT_BLDIM2, |
| dim & MT6370_MASK_BLDIM2); |
| |
| if (rv) |
| return rv; |
| |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_BACKLIGHT_BLDIM, |
| (dim >> MT6370_SHIFT_BLDIM_MSB) & MT6370_MASK_BLDIM); |
| |
| return rv; |
| } |
| |
| /* MT6370 RGB LED */ |
| |
| int mt6370_led_set_dim_mode(enum mt6370_led_index index, |
| enum mt6370_led_dim_mode mode) |
| { |
| if (index <= MT6370_LED_ID_OFF || index >= MT6370_LED_ID_COUNT) |
| return EC_ERROR_INVAL; |
| |
| rt946x_update_bits(CHARGER_SOLO, MT6370_REG_RGBDIM_BASE + index, |
| MT6370_MASK_RGB_DIMMODE, |
| mode << MT6370_SHIFT_RGB_DIMMODE); |
| return EC_SUCCESS; |
| } |
| |
| int mt6370_led_set_color(uint8_t mask) |
| { |
| return rt946x_update_bits(CHARGER_SOLO, MT6370_REG_RGBEN, |
| MT6370_MASK_RGB_ISNK_ALL_EN, mask); |
| } |
| |
| int mt6370_led_set_brightness(enum mt6370_led_index index, uint8_t brightness) |
| { |
| if (index >= MT6370_LED_ID_COUNT || index <= MT6370_LED_ID_OFF) |
| return EC_ERROR_INVAL; |
| |
| rt946x_update_bits(CHARGER_SOLO, MT6370_REG_RGBISNK_BASE + index, |
| MT6370_MASK_RGBISNK_CURSEL, |
| brightness << MT6370_SHIFT_RGBISNK_CURSEL); |
| return EC_SUCCESS; |
| } |
| |
| int mt6370_led_set_pwm_dim_duty(enum mt6370_led_index index, uint8_t dim_duty) |
| { |
| if (index >= MT6370_LED_ID_COUNT || index <= MT6370_LED_ID_OFF) |
| return EC_ERROR_INVAL; |
| |
| rt946x_update_bits(CHARGER_SOLO, MT6370_REG_RGBDIM_BASE + index, |
| MT6370_MASK_RGB_DIMDUTY, |
| dim_duty << MT6370_SHIFT_RGB_DIMDUTY); |
| return EC_SUCCESS; |
| } |
| |
| int mt6370_led_set_pwm_frequency(enum mt6370_led_index index, |
| enum mt6370_led_pwm_freq freq) |
| { |
| if (index >= MT6370_LED_ID_COUNT || index <= MT6370_LED_ID_OFF) |
| return EC_ERROR_INVAL; |
| |
| rt946x_update_bits(CHARGER_SOLO, MT6370_REG_RGBISNK_BASE + index, |
| MT6370_MASK_RGBISNK_DIMFSEL, |
| freq << MT6370_SHIFT_RGBISNK_DIMFSEL); |
| return EC_SUCCESS; |
| } |
| |
| int mt6370_reduce_db_bl_driving(void) |
| { |
| int rv; |
| |
| /* Enter test mode */ |
| rv = rt946x_block_write(CHARGER_SOLO, MT6370_REG_TM_PAS_CODE1, |
| mt6370_val_en_test_mode, |
| ARRAY_SIZE(mt6370_val_en_test_mode)); |
| if (rv) |
| return rv; |
| msleep(1); |
| rv = rt946x_write8(CHARGER_SOLO, MT6370_REG_BANK, MT6370_MASK_REG_TM); |
| if (rv) |
| return rv; |
| msleep(1); |
| /* reduce bl driving */ |
| rv = rt946x_update_bits(CHARGER_SOLO, MT6370_TM_REG_BL3, |
| MT6370_TM_MASK_BL3_SL, MT6370_TM_REDUCE_BL3_SL); |
| if (rv) |
| return rv; |
| msleep(1); |
| /* reduce db driving */ |
| rv = rt946x_update_bits(CHARGER_SOLO, MT6370_TM_REG_DSV1, |
| MT6370_TM_MASK_DSV1_SL, |
| MT6370_TM_REDUCE_DSV1_SL); |
| if (rv) |
| return rv; |
| msleep(1); |
| /* Leave test mode */ |
| return rt946x_write8(CHARGER_SOLO, MT6370_REG_TM_PAS_CODE1, |
| MT6370_LEAVE_TM); |
| } |
| #endif /* CONFIG_CHARGER_MT6370 */ |
| |
| const struct charger_drv rt946x_drv = { |
| .init = &rt946x_init, |
| .post_init = &rt946x_post_init, |
| .get_info = &rt946x_get_info, |
| .get_status = &rt946x_get_status, |
| .set_mode = &rt946x_set_mode, |
| .enable_otg_power = &rt946x_enable_otg_power, |
| .is_sourcing_otg_power = &rt946x_is_sourcing_otg_power, |
| .get_current = &rt946x_get_current, |
| .set_current = &rt946x_set_current, |
| .get_voltage = &rt946x_get_voltage, |
| .set_voltage = &rt946x_set_voltage, |
| .discharge_on_ac = &rt946x_discharge_on_ac, |
| .get_vbus_voltage = &rt946x_get_vbus_voltage, |
| .set_input_current = &rt946x_set_input_current, |
| .get_input_current = &rt946x_get_input_current, |
| .manufacturer_id = &rt946x_manufacturer_id, |
| .device_id = &rt946x_device_id, |
| .get_option = &rt946x_get_option, |
| .set_option = &rt946x_set_option, |
| #ifdef CONFIG_CHARGE_RAMP_HW |
| .set_hw_ramp = &rt946x_set_hw_ramp, |
| .ramp_is_stable = &rt946x_ramp_is_stable, |
| .ramp_is_detected = &rt946x_ramp_is_detected, |
| .ramp_get_current_limit = &rt946x_ramp_get_current_limit, |
| #endif |
| }; |
| |
| #ifdef HAS_TASK_USB_CHG |
| const struct bc12_drv rt946x_bc12_drv = { |
| .usb_charger_task = rt946x_usb_charger_task, |
| .ramp_allowed = rt946x_ramp_allowed, |
| .ramp_max = rt946x_ramp_max, |
| }; |
| |
| #ifdef CONFIG_BC12_SINGLE_DRIVER |
| /* provide a default bc12_ports[] for backward compatibility */ |
| struct bc12_config bc12_ports[CHARGE_PORT_COUNT] = { |
| [0 ... (CHARGE_PORT_COUNT - 1)] = { |
| .drv = &rt946x_bc12_drv, |
| }, |
| }; |
| #endif /* CONFIG_BC12_SINGLE_DRIVER */ |
| #endif |