/* * rk817 charger driver * * Copyright (C) 2018 Rockchip Electronics Co., Ltd * xsf * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int dbg_enable; module_param_named(dbg_level, dbg_enable, int, 0644); #define DBG(args...) \ do { \ if (dbg_enable) { \ pr_info(args); \ } \ } while (0) #define CHARGE_DRIVER_VERSION "1.0" #define DISABLE 0x00 #define ENABLE 0x01 #define OTG_SLP_ENABLE 0x03 #define OTG_SLP_DISABLE 0x00 #define OTG_ENABLE 0x11 #define OTG_DISABLE 0x10 #define RK817_BOOST_ENABLE 0x11 #define RK817_BOOST_DISABLE 0x10 #define OTG_MODE 0x01 #define OTG_MODE_ON 0x01 #define DEFAULT_INPUT_VOLTAGE 4500 #define DEFAULT_INPUT_CURRENT 2000 #define DEFAULT_CHRG_VOLTAGE 4200 #define DEFAULT_CHRG_CURRENT 1400 #define DEFAULT_CHRG_TERM_MODE 1 #define DEFAULT_CHRG_TERM_CUR 150 #define SAMPLE_RES_10MR 10 #define SAMPLE_RES_20MR 20 #define SAMPLE_RES_DIV1 1 #define SAMPLE_RES_DIV2 2 #define INPUT_450MA 450 #define INPUT_1500MA 1500 #define CURRENT_TO_ADC(current, samp_res) \ (current * 1000 * samp_res / 172) enum charge_current { CHRG_CUR_1000MA, CHRG_CUR_1500MA, CHRG_CUR_2000MA, CHRG_CUR_2500MA, CHRG_CUR_2750MA, CHRG_CUR_3000MA, CHRG_CUR_3500MA, CHRG_CUR_500MA, }; enum charge_voltage { CHRG_VOL_4100MV, CHRG_VOL_4150MV, CHRG_VOL_4200MV, CHRG_VOL_4250MV, CHRG_VOL_4300MV, CHRG_VOL_4350MV, CHRG_VOL_4400MV, CHRG_VOL_4450MV, }; enum input_voltage { INPUT_VOL_4000MV, INPUT_VOL_4100MV, INPUT_VOL_4200MV, INPUT_VOL_4300MV, INPUT_VOL_4400MV, INPUT_VOL_4500MV, INPUT_VOL_4600MV, INPUT_VOL_4700MV, }; enum input_current { INPUT_CUR_450MA, INPUT_CUR_80MA, INPUT_CUR_850MA, INPUT_CUR_1500MA, INPUT_CUR_1750MA, INPUT_CUR_2000MA, INPUT_CUR_2500MA, INPUT_CUR_3000MA, }; enum charge_clk { CHRG_CLK_1M, CHRG_CLK_2M, }; enum charge_term_sel { CHRG_TERM_150MA, CHRG_TERM_200MA, CHRG_TERM_300MA, CHRG_TERM_400MA, }; enum charge_timer_trickle { CHRG_TIMER_TRIKL_30MIN, CHRG_TIMER_TRIKL_45MIN, CHRG_TIMER_TRIKL_60MIN, CHRG_TIMER_TRIKL_90MIN, CHRG_TIMER_TRIKL_120MIN, CHRG_TIMER_TRIKL_150MIN, CHRG_TIMER_TRIKL_180MIN, CHRG_TIMER_TRIKL_210MIN, }; enum charge_timer_cccv { CHRG_TIMER_CCCV_4H, CHRG_TIMER_CCCV_5H, CHRG_TIMER_CCCV_6H, CHRG_TIMER_CCCV_8H, CHRG_TIMER_CCCV_10H, CHRG_TIMER_CCCV_12H, CHRG_TIMER_CCCV_14H, CHRG_TIMER_CCCV_16H, }; enum charge_status { CHRG_OFF, DEAD_CHRG, TRICKLE_CHRG, CC_OR_CV_CHRG, CHRG_TERM, USB_OVER_VOL, BAT_TMP_ERR, BAT_TIM_ERR, }; enum discharge_ilimit { DISCHRG_2000MA, DISCHRG_2500MA, DISCHRG_3000MA, DISCHRG_3500MA, DISCHRG_4000MA, }; enum bat_system_comp_time { DLY_20US, DLY_10US, DLY_40US, DLY_20US_AGAIN, }; enum charge_term_mode { CHRG_ANALOG, CHRG_DIGITAL, }; enum charger_t { USB_TYPE_UNKNOWN_CHARGER, USB_TYPE_NONE_CHARGER, USB_TYPE_USB_CHARGER, USB_TYPE_AC_CHARGER, USB_TYPE_CDP_CHARGER, DC_TYPE_DC_CHARGER, DC_TYPE_NONE_CHARGER, }; enum rk817_charge_fields { BOOST_EN, OTG_EN, OTG_SLP_EN, CHRG_CLK_SEL, CHRG_EN, CHRG_VOL_SEL, CHRG_CT_EN, CHRG_CUR_SEL, USB_VLIM_EN, USB_VLIM_SEL, USB_ILIM_EN, USB_ILIM_SEL, SYS_CAN_SD, USB_SYS_EN, BAT_OVP_EN, CHRG_TERM_ANA_DIG, CHRG_TERM_ANA_SEL, CHRG_TERM_DIG, BAT_HTS_TS, BAT_LTS_TS, CHRG_TIMER_TRIKL_EN, CHRG_TIMER_TRIKL, CHRG_TIMER_CCCV_EN, CHRG_TIMER_CCCV, BAT_EXS, CHG_STS, BAT_OVP_STS, CHRG_IN_CLAMP, USB_EXS, USB_EFF, BAT_DIS_ILIM_STS, BAT_SYS_CMP_DLY, BAT_DIS_ILIM_EN, BAT_DISCHRG_ILIM, PLUG_IN_STS, SOC_REG0, SOC_REG1, SOC_REG2, F_MAX_FIELDS }; static const struct reg_field rk817_charge_reg_fields[] = { [SOC_REG0] = REG_FIELD(0x9A, 0, 7), [SOC_REG1] = REG_FIELD(0x9B, 0, 7), [SOC_REG2] = REG_FIELD(0x9C, 0, 7), [BOOST_EN] = REG_FIELD(0xB4, 1, 5), [OTG_EN] = REG_FIELD(0xB4, 2, 6), [OTG_SLP_EN] = REG_FIELD(0xB5, 5, 6), [CHRG_EN] = REG_FIELD(0xE4, 7, 7), [CHRG_VOL_SEL] = REG_FIELD(0xE4, 4, 6), [CHRG_CT_EN] = REG_FIELD(0xE4, 3, 3), [CHRG_CUR_SEL] = REG_FIELD(0xE4, 0, 2), [USB_VLIM_EN] = REG_FIELD(0xE5, 7, 7), [USB_VLIM_SEL] = REG_FIELD(0xE5, 4, 6), [USB_ILIM_EN] = REG_FIELD(0xE5, 3, 3), [USB_ILIM_SEL] = REG_FIELD(0xE5, 0, 2), [SYS_CAN_SD] = REG_FIELD(0xE6, 7, 7), [USB_SYS_EN] = REG_FIELD(0xE6, 6, 6), [BAT_OVP_EN] = REG_FIELD(0xE6, 3, 3), [CHRG_TERM_ANA_DIG] = REG_FIELD(0xE6, 2, 2), [CHRG_TERM_ANA_SEL] = REG_FIELD(0xE6, 0, 1), [CHRG_TERM_DIG] = REG_FIELD(0xE7, 0, 7), [BAT_HTS_TS] = REG_FIELD(0xE8, 0, 7), [BAT_LTS_TS] = REG_FIELD(0xE9, 0, 7), [CHRG_TIMER_TRIKL_EN] = REG_FIELD(0xEA, 7, 7), [CHRG_TIMER_TRIKL] = REG_FIELD(0xEA, 4, 6), [CHRG_TIMER_CCCV_EN] = REG_FIELD(0xEA, 3, 3), [CHRG_TIMER_CCCV] = REG_FIELD(0xEA, 0, 2), [BAT_EXS] = REG_FIELD(0xEB, 7, 7), [CHG_STS] = REG_FIELD(0xEB, 4, 6), [BAT_OVP_STS] = REG_FIELD(0xEB, 3, 3), [CHRG_IN_CLAMP] = REG_FIELD(0xEB, 2, 2), [USB_EXS] = REG_FIELD(0xEB, 1, 1), [USB_EFF] = REG_FIELD(0xEB, 0, 0), [BAT_DIS_ILIM_STS] = REG_FIELD(0xEC, 6, 6), [BAT_SYS_CMP_DLY] = REG_FIELD(0xEC, 4, 5), [BAT_DIS_ILIM_EN] = REG_FIELD(0xEC, 3, 3), [BAT_DISCHRG_ILIM] = REG_FIELD(0xEC, 0, 2), [PLUG_IN_STS] = REG_FIELD(0xf0, 6, 6), [CHRG_CLK_SEL] = REG_FIELD(0xF3, 6, 6), }; struct charger_platform_data { u32 max_input_current; u32 min_input_voltage; u32 max_chrg_current; u32 max_chrg_voltage; u32 chrg_finish_cur; u32 chrg_term_mode; u32 power_dc2otg; u32 dc_det_level; int dc_det_pin; bool support_dc_det; int virtual_power; int sample_res; int otg5v_suspend_enable; bool extcon; }; struct rk817_charger { struct i2c_client *client; struct platform_device *pdev; struct device *dev; struct rk808 *rk817; struct regmap *regmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; struct power_supply *ac_psy; struct power_supply *usb_psy; struct extcon_dev *cable_edev; struct charger_platform_data *pdata; struct workqueue_struct *usb_charger_wq; struct workqueue_struct *dc_charger_wq; struct delayed_work dc_work; struct delayed_work usb_work; struct delayed_work host_work; struct delayed_work discnt_work; struct delayed_work irq_work; struct notifier_block bc_nb; struct notifier_block cable_cg_nb; struct notifier_block cable_host_nb; struct notifier_block cable_discnt_nb; unsigned int bc_event; enum charger_t usb_charger; enum charger_t dc_charger; u8 ac_in; u8 usb_in; u8 otg_in; u8 dc_in; u8 prop_status; u32 max_input_current; u32 min_input_voltage; u32 max_chrg_current; u32 max_chrg_voltage; u32 chrg_finish_cur; u32 chrg_term_mode; u8 res_div; u8 otg_slp_state; u8 plugin_trigger; u8 plugout_trigger; int plugin_irq; int plugout_irq; }; static enum power_supply_property rk817_ac_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_CURRENT_MAX, }; static enum power_supply_property rk817_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_CURRENT_MAX, }; static int rk817_charge_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rk817_charger *charge = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: if (charge->pdata->virtual_power) val->intval = 1; else val->intval = (charge->ac_in | charge->dc_in); DBG("report online: %d\n", val->intval); break; case POWER_SUPPLY_PROP_STATUS: if (charge->pdata->virtual_power) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = charge->prop_status; DBG("report prop: %d\n", val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: val->intval = charge->max_chrg_voltage; break; case POWER_SUPPLY_PROP_CURRENT_MAX: val->intval = charge->max_chrg_current; break; default: ret = -EINVAL; break; } return ret; } static int rk817_charge_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rk817_charger *charge = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: if (charge->pdata->virtual_power) val->intval = 1; else val->intval = charge->usb_in; DBG("report online: %d\n", val->intval); break; case POWER_SUPPLY_PROP_STATUS: if (charge->pdata->virtual_power) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = charge->prop_status; DBG("report prop: %d\n", val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: val->intval = charge->max_chrg_voltage; break; case POWER_SUPPLY_PROP_CURRENT_MAX: val->intval = charge->max_chrg_current; break; default: ret = -EINVAL; break; } return ret; } static const struct power_supply_desc rk817_ac_desc = { .name = "ac", .type = POWER_SUPPLY_TYPE_MAINS, .properties = rk817_ac_props, .num_properties = ARRAY_SIZE(rk817_ac_props), .get_property = rk817_charge_ac_get_property, }; static const struct power_supply_desc rk817_usb_desc = { .name = "usb", .type = POWER_SUPPLY_TYPE_USB, .properties = rk817_usb_props, .num_properties = ARRAY_SIZE(rk817_usb_props), .get_property = rk817_charge_usb_get_property, }; static int rk817_charge_init_power_supply(struct rk817_charger *charge) { struct power_supply_config psy_cfg = { .drv_data = charge, }; charge->usb_psy = devm_power_supply_register(charge->dev, &rk817_usb_desc, &psy_cfg); if (IS_ERR(charge->usb_psy)) { dev_err(charge->dev, "register usb power supply fail\n"); return PTR_ERR(charge->usb_psy); } charge->ac_psy = devm_power_supply_register(charge->dev, &rk817_ac_desc, &psy_cfg); if (IS_ERR(charge->ac_psy)) { dev_err(charge->dev, "register ac power supply fail\n"); return PTR_ERR(charge->ac_psy); } return 0; } static int rk817_charge_field_read(struct rk817_charger *charge, enum rk817_charge_fields field_id) { int ret; int val; ret = regmap_field_read(charge->rmap_fields[field_id], &val); if (ret < 0) return ret; return val; } static int rk817_charge_field_write(struct rk817_charger *charge, enum rk817_charge_fields field_id, unsigned int val) { return regmap_field_write(charge->rmap_fields[field_id], val); } static int rk817_charge_get_otg_state(struct rk817_charger *charge) { return ((rk817_charge_field_read(charge, OTG_EN) & OTG_MODE) == OTG_MODE_ON); } static void rk817_charge_boost_disable(struct rk817_charger *charge) { rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_DISABLE); } static void rk817_charge_boost_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_ENABLE); } static void rk817_charge_otg_disable(struct rk817_charger *charge) { rk817_charge_field_write(charge, OTG_EN, OTG_DISABLE); } static void rk817_charge_otg_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, OTG_EN, OTG_ENABLE); } #ifdef CONFIG_PM_SLEEP static int rk817_charge_get_otg_slp_state(struct rk817_charger *charge) { return (rk817_charge_field_read(charge, OTG_SLP_EN) & OTG_SLP_ENABLE); } static void rk817_charge_otg_slp_disable(struct rk817_charger *charge) { rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_DISABLE); } static void rk817_charge_otg_slp_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_ENABLE); } #endif static int rk817_charge_get_charge_state(struct rk817_charger *charge) { return rk817_charge_field_read(charge, CHRG_EN); } static void rk817_charge_enable_charge(struct rk817_charger *charge) { rk817_charge_field_write(charge, CHRG_EN, ENABLE); } static void rk817_charge_usb_to_sys_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, USB_SYS_EN, ENABLE); } static void rk817_charge_sys_can_sd_disable(struct rk817_charger *charge) { rk817_charge_field_write(charge, SYS_CAN_SD, DISABLE); } static int rk817_charge_get_charge_status(struct rk817_charger *charge) { int status; status = rk817_charge_field_read(charge, CHG_STS); switch (status) { case CHRG_OFF: DBG("charge off...\n"); break; case DEAD_CHRG: DBG("dead charge...\n"); break; case TRICKLE_CHRG: DBG("trickle charge...\n"); break; case CC_OR_CV_CHRG: DBG("CC or CV charge...\n"); break; case CHRG_TERM: DBG("charge TERM...\n"); break; case USB_OVER_VOL: DBG("USB over voltage...\n"); break; case BAT_TMP_ERR: DBG("battery temperature error...\n"); break; case BAT_TIM_ERR: DBG("battery timer error..\n"); break; default: break; } return status; } static int rk817_charge_get_plug_in_status(struct rk817_charger *charge) { return rk817_charge_field_read(charge, PLUG_IN_STS); } static void rk817_charge_set_charge_clock(struct rk817_charger *charge, enum charge_clk clock) { rk817_charge_field_write(charge, CHRG_CLK_SEL, clock); } static int is_battery_exist(struct rk817_charger *charge) { return rk817_charge_field_read(charge, BAT_EXS); } static void rk817_charge_set_chrg_voltage(struct rk817_charger *charge, int chrg_vol) { int voltage; if (chrg_vol < 4100 || chrg_vol > 4500) { dev_err(charge->dev, "the charge voltage is error!\n"); } else { voltage = (chrg_vol - 4100) / 50; rk817_charge_field_write(charge, CHRG_VOL_SEL, CHRG_VOL_4100MV + voltage); } } static void rk817_charge_set_chrg_current(struct rk817_charger *charge, int chrg_current) { if (chrg_current < 500 || chrg_current > 3500) dev_err(charge->dev, "the charge current is error!\n"); if (chrg_current < 1000) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_500MA); else if (chrg_current < 1500) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1000MA); else if (chrg_current < 2000) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1500MA); else if (chrg_current < 2500) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2000MA); else if (chrg_current < 3000) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2500MA); else if (chrg_current < 3500) rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3000MA); else rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3500MA); } static void rk817_charge_vlimit_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, USB_VLIM_EN, ENABLE); } static void rk817_charge_set_input_voltage(struct rk817_charger *charge, int input_voltage) { int voltage; if (input_voltage < 4000) dev_err(charge->dev, "the input voltage is error.\n"); voltage = INPUT_VOL_4000MV + (input_voltage - 4000) / 100; rk817_charge_field_write(charge, USB_VLIM_SEL, voltage); rk817_charge_vlimit_enable(charge); } static void rk817_charge_ilimit_enable(struct rk817_charger *charge) { rk817_charge_field_write(charge, USB_ILIM_EN, ENABLE); } static void rk817_charge_set_input_current(struct rk817_charger *charge, int input_current) { if (input_current < 80 || input_current > 3000) dev_err(charge->dev, "the input current is error.\n"); if (input_current < 450) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_80MA); else if (input_current < 850) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_450MA); else if (input_current < 1500) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_850MA); else if (input_current < 1750) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_1500MA); else if (input_current < 2000) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_1750MA); else if (input_current < 2500) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_2000MA); else if (input_current < 3000) rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_2500MA); else rk817_charge_field_write(charge, USB_ILIM_SEL, INPUT_CUR_3000MA); rk817_charge_ilimit_enable(charge); } static void rk817_charge_set_chrg_term_mod(struct rk817_charger *charge, int mode) { rk817_charge_field_write(charge, CHRG_TERM_ANA_DIG, mode); } static void rk817_charge_set_term_current_analog(struct rk817_charger *charge, int chrg_current) { int value; if (chrg_current < 200) value = CHRG_TERM_150MA; else if (chrg_current < 300) value = CHRG_TERM_200MA; else if (chrg_current < 400) value = CHRG_TERM_300MA; else value = CHRG_TERM_400MA; rk817_charge_field_write(charge, CHRG_TERM_ANA_SEL, value); } static void rk817_charge_set_term_current_digital(struct rk817_charger *charge, int chrg_current) { int value; u8 current_adc; value = CURRENT_TO_ADC(chrg_current, charge->res_div); value &= (0xff << 5); current_adc = value >> 5; rk817_charge_field_write(charge, CHRG_TERM_DIG, current_adc); } static void rk817_charge_set_chrg_finish_condition(struct rk817_charger *charge) { if (charge->chrg_term_mode == CHRG_ANALOG) rk817_charge_set_term_current_analog(charge, charge->chrg_finish_cur); else rk817_charge_set_term_current_digital(charge, charge->chrg_finish_cur); rk817_charge_set_chrg_term_mod(charge, charge->chrg_term_mode); } static int rk817_charge_online(struct rk817_charger *charge) { return (charge->ac_in | charge->usb_in | charge->dc_in); } static int rk817_charge_get_dsoc(struct rk817_charger *charge) { int soc_save; soc_save = rk817_charge_field_read(charge, SOC_REG0); soc_save |= (rk817_charge_field_read(charge, SOC_REG1) << 8); soc_save |= (rk817_charge_field_read(charge, SOC_REG2) << 16); return soc_save / 1000; } static void rk817_charge_set_chrg_param(struct rk817_charger *charge, enum charger_t charger) { switch (charger) { case USB_TYPE_NONE_CHARGER: charge->usb_in = 0; charge->ac_in = 0; if (charge->dc_in == 0) { charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING; rk817_charge_set_input_current(charge, INPUT_450MA); } power_supply_changed(charge->usb_psy); power_supply_changed(charge->ac_psy); break; case USB_TYPE_USB_CHARGER: charge->usb_in = 1; charge->ac_in = 0; charge->prop_status = POWER_SUPPLY_STATUS_CHARGING; if (charge->dc_in == 0) rk817_charge_set_input_current(charge, INPUT_450MA); power_supply_changed(charge->usb_psy); power_supply_changed(charge->ac_psy); break; case USB_TYPE_AC_CHARGER: case USB_TYPE_CDP_CHARGER: charge->ac_in = 1; charge->usb_in = 0; charge->prop_status = POWER_SUPPLY_STATUS_CHARGING; if (charger == USB_TYPE_AC_CHARGER) rk817_charge_set_input_current(charge, charge->max_input_current); else rk817_charge_set_input_current(charge, INPUT_1500MA); power_supply_changed(charge->usb_psy); power_supply_changed(charge->ac_psy); break; case DC_TYPE_DC_CHARGER: charge->dc_in = 1; charge->prop_status = POWER_SUPPLY_STATUS_CHARGING; rk817_charge_set_input_current(charge, charge->max_input_current); power_supply_changed(charge->usb_psy); power_supply_changed(charge->ac_psy); break; case DC_TYPE_NONE_CHARGER: charge->dc_in = 0; if (!rk817_charge_get_plug_in_status(charge)) { charge->ac_in = 0; charge->usb_in = 0; charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING; rk817_charge_set_input_current(charge, INPUT_450MA); } else if (charge->usb_in) { rk817_charge_set_input_current(charge, INPUT_450MA); charge->prop_status = POWER_SUPPLY_STATUS_CHARGING; } power_supply_changed(charge->usb_psy); power_supply_changed(charge->ac_psy); break; default: charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING; rk817_charge_set_input_current(charge, INPUT_450MA); break; } if (rk817_charge_online(charge) && rk817_charge_get_dsoc(charge) == 100) charge->prop_status = POWER_SUPPLY_STATUS_FULL; } static void rk817_charge_set_otg_state(struct rk817_charger *charge, int state) { switch (state) { case USB_OTG_POWER_ON: if (charge->otg_in) { DBG("otg5v is on yet, ignore..\n"); } else { charge->otg_in = 1; if (!rk817_charge_get_otg_state(charge)) { rk817_charge_otg_enable(charge); if (!rk817_charge_get_otg_state(charge)) { DBG("enable otg5v failed\n"); return; } } disable_irq(charge->plugin_irq); disable_irq(charge->plugout_irq); DBG("enable otg5v\n"); } break; case USB_OTG_POWER_OFF: if (!charge->otg_in) { DBG("otg5v is off yet, ignore..\n"); } else { charge->otg_in = 0; if (rk817_charge_get_otg_state(charge)) { rk817_charge_otg_disable(charge); if (rk817_charge_get_otg_state(charge)) { DBG("disable otg5v failed\n"); return; } } enable_irq(charge->plugin_irq); enable_irq(charge->plugout_irq); DBG("disable otg5v\n"); } break; default: dev_err(charge->dev, "error otg type\n"); break; } } static irqreturn_t rk817_charge_dc_det_isr(int irq, void *charger) { struct rk817_charger *charge = (struct rk817_charger *)charger; if (gpio_get_value(charge->pdata->dc_det_pin)) irq_set_irq_type(irq, IRQF_TRIGGER_LOW); else irq_set_irq_type(irq, IRQF_TRIGGER_HIGH); queue_delayed_work(charge->dc_charger_wq, &charge->dc_work, msecs_to_jiffies(10)); return IRQ_HANDLED; } static enum charger_t rk817_charge_get_dc_state(struct rk817_charger *charge) { int level; if (!gpio_is_valid(charge->pdata->dc_det_pin)) return DC_TYPE_NONE_CHARGER; level = gpio_get_value(charge->pdata->dc_det_pin); return (level == charge->pdata->dc_det_level) ? DC_TYPE_DC_CHARGER : DC_TYPE_NONE_CHARGER; } static void rk817_charge_dc_det_worker(struct work_struct *work) { enum charger_t charger; struct rk817_charger *charge = container_of(work, struct rk817_charger, dc_work.work); charger = rk817_charge_get_dc_state(charge); if (charger == DC_TYPE_DC_CHARGER) { DBG("detect dc charger in..\n"); rk817_charge_set_chrg_param(charge, DC_TYPE_DC_CHARGER); rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF); rk817_charge_boost_disable(charge); } else { DBG("detect dc charger out..\n"); rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER); rk817_charge_boost_enable(charge); rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF); } } static int rk817_charge_init_dc(struct rk817_charger *charge) { int ret, level; unsigned long irq_flags; unsigned int dc_det_irq; charge->dc_charger_wq = alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk817-dc-wq"); INIT_DELAYED_WORK(&charge->dc_work, rk817_charge_dc_det_worker); charge->dc_charger = DC_TYPE_NONE_CHARGER; if (!charge->pdata->support_dc_det) return 0; ret = devm_gpio_request(charge->dev, charge->pdata->dc_det_pin, "rk817_dc_det"); if (ret < 0) { dev_err(charge->dev, "failed to request gpio %d\n", charge->pdata->dc_det_pin); return ret; } ret = gpio_direction_input(charge->pdata->dc_det_pin); if (ret) { dev_err(charge->dev, "failed to set gpio input\n"); return ret; } level = gpio_get_value(charge->pdata->dc_det_pin); if (level == charge->pdata->dc_det_level) charge->dc_charger = DC_TYPE_DC_CHARGER; else charge->dc_charger = DC_TYPE_NONE_CHARGER; if (level) irq_flags = IRQF_TRIGGER_LOW; else irq_flags = IRQF_TRIGGER_HIGH; dc_det_irq = gpio_to_irq(charge->pdata->dc_det_pin); ret = devm_request_irq(charge->dev, dc_det_irq, rk817_charge_dc_det_isr, irq_flags, "rk817_dc_det", charge); if (ret != 0) { dev_err(charge->dev, "rk817_dc_det_irq request failed!\n"); return ret; } enable_irq_wake(dc_det_irq); if (charge->dc_charger != DC_TYPE_NONE_CHARGER) rk817_charge_set_chrg_param(charge, charge->dc_charger); return 0; } static void rk817_charge_host_evt_worker(struct work_struct *work) { struct rk817_charger *charge = container_of(work, struct rk817_charger, host_work.work); struct extcon_dev *edev = charge->cable_edev; /* Determine cable/charger type */ if (extcon_get_cable_state_(edev, EXTCON_USB_VBUS_EN) > 0) { DBG("receive type-c notifier event: OTG ON...\n"); if (charge->dc_in && charge->pdata->power_dc2otg) DBG("otg power from dc adapter\n"); else rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON); } else if (extcon_get_cable_state_(edev, EXTCON_USB_VBUS_EN) == 0) { DBG("receive type-c notifier event: OTG OFF...\n"); rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF); } } static void rk817_charger_evt_worker(struct work_struct *work) { struct rk817_charger *charge = container_of(work, struct rk817_charger, usb_work.work); struct extcon_dev *edev = charge->cable_edev; enum charger_t charger = USB_TYPE_UNKNOWN_CHARGER; static const char * const event[] = {"UN", "NONE", "USB", "AC", "CDP1.5A"}; /* Determine cable/charger type */ if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_SDP) > 0) charger = USB_TYPE_USB_CHARGER; else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_DCP) > 0) charger = USB_TYPE_AC_CHARGER; else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_CDP) > 0) charger = USB_TYPE_CDP_CHARGER; if (charger != USB_TYPE_UNKNOWN_CHARGER) { DBG("receive type-c notifier event: %s...\n", event[charger]); charge->usb_charger = charger; rk817_charge_set_chrg_param(charge, charger); } } static void rk817_charge_discnt_evt_worker(struct work_struct *work) { struct rk817_charger *charge = container_of(work, struct rk817_charger, discnt_work.work); if (extcon_get_cable_state_(charge->cable_edev, EXTCON_USB) == 0) { DBG("receive type-c notifier event: DISCNT...\n"); rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER); } } static void rk817_charge_bc_evt_worker(struct work_struct *work) { struct rk817_charger *charge = container_of(work, struct rk817_charger, usb_work.work); static const char * const event_name[] = {"DISCNT", "USB", "AC", "CDP1.5A", "UNKNOWN", "OTG ON", "OTG OFF"}; switch (charge->bc_event) { case USB_BC_TYPE_DISCNT: rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER); break; case USB_BC_TYPE_SDP: rk817_charge_set_chrg_param(charge, USB_TYPE_USB_CHARGER); break; case USB_BC_TYPE_DCP: rk817_charge_set_chrg_param(charge, USB_TYPE_AC_CHARGER); break; case USB_BC_TYPE_CDP: rk817_charge_set_chrg_param(charge, USB_TYPE_CDP_CHARGER); break; case USB_OTG_POWER_ON: if (charge->pdata->power_dc2otg && charge->dc_in) DBG("otg power from dc adapter\n"); else rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON); break; case USB_OTG_POWER_OFF: rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF); break; default: break; } DBG("receive bc notifier event: %s..\n", event_name[charge->bc_event]); } static int rk817_charger_evt_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct rk817_charger *charge = container_of(nb, struct rk817_charger, cable_cg_nb); queue_delayed_work(charge->usb_charger_wq, &charge->usb_work, msecs_to_jiffies(10)); return NOTIFY_DONE; } static int rk817_charge_host_evt_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct rk817_charger *charge = container_of(nb, struct rk817_charger, cable_host_nb); queue_delayed_work(charge->usb_charger_wq, &charge->host_work, msecs_to_jiffies(10)); return NOTIFY_DONE; } static int rk817_charge_discnt_evt_notfier(struct notifier_block *nb, unsigned long event, void *ptr) { struct rk817_charger *charge = container_of(nb, struct rk817_charger, cable_discnt_nb); queue_delayed_work(charge->usb_charger_wq, &charge->discnt_work, msecs_to_jiffies(10)); return NOTIFY_DONE; } static int rk817_charge_bc_evt_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct rk817_charger *charge = container_of(nb, struct rk817_charger, bc_nb); charge->bc_event = event; queue_delayed_work(charge->usb_charger_wq, &charge->usb_work, msecs_to_jiffies(10)); return NOTIFY_DONE; } static int rk817_charge_usb_init(struct rk817_charger *charge) { enum charger_t charger; enum bc_port_type bc_type; struct extcon_dev *edev; struct device *dev = charge->dev; int ret; charge->usb_charger_wq = alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk817-usb-wq"); /* type-C */ if (charge->pdata->extcon) { edev = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(edev)) { if (PTR_ERR(edev) != -EPROBE_DEFER) dev_err(dev, "Invalid or missing extcon\n"); return PTR_ERR(edev); } /* Register chargers */ INIT_DELAYED_WORK(&charge->usb_work, rk817_charger_evt_worker); charge->cable_cg_nb.notifier_call = rk817_charger_evt_notifier; ret = extcon_register_notifier(edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); if (ret < 0) { dev_err(dev, "failed to register notifier for SDP\n"); return ret; } ret = extcon_register_notifier(edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); if (ret < 0) { dev_err(dev, "failed to register notifier for DCP\n"); extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); return ret; } ret = extcon_register_notifier(edev, EXTCON_CHG_USB_CDP, &charge->cable_cg_nb); if (ret < 0) { dev_err(dev, "failed to register notifier for CDP\n"); extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); return ret; } /* Register host */ INIT_DELAYED_WORK(&charge->host_work, rk817_charge_host_evt_worker); charge->cable_host_nb.notifier_call = rk817_charge_host_evt_notifier; ret = extcon_register_notifier(edev, EXTCON_USB_VBUS_EN, &charge->cable_host_nb); if (ret < 0) { dev_err(dev, "failed to register notifier for HOST\n"); extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP, &charge->cable_cg_nb); return ret; } /* Register discnt usb */ INIT_DELAYED_WORK(&charge->discnt_work, rk817_charge_discnt_evt_worker); charge->cable_discnt_nb.notifier_call = rk817_charge_discnt_evt_notfier; ret = extcon_register_notifier(edev, EXTCON_USB, &charge->cable_discnt_nb); if (ret < 0) { dev_err(dev, "failed to register notifier for HOST\n"); extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP, &charge->cable_cg_nb); extcon_unregister_notifier(edev, EXTCON_USB_VBUS_EN, &charge->cable_host_nb); return ret; } charge->cable_edev = edev; schedule_delayed_work(&charge->host_work, 0); schedule_delayed_work(&charge->usb_work, 0); DBG("register typec extcon evt notifier\n"); } else { INIT_DELAYED_WORK(&charge->usb_work, rk817_charge_bc_evt_worker); charge->bc_nb.notifier_call = rk817_charge_bc_evt_notifier; ret = rk_bc_detect_notifier_register(&charge->bc_nb, &bc_type); if (ret) { dev_err(dev, "failed to register notifier for bc\n"); return -EINVAL; } switch (bc_type) { case USB_BC_TYPE_DISCNT: charger = USB_TYPE_NONE_CHARGER; break; case USB_BC_TYPE_SDP: case USB_BC_TYPE_CDP: charger = USB_TYPE_USB_CHARGER; break; case USB_BC_TYPE_DCP: charger = USB_TYPE_AC_CHARGER; break; default: charger = USB_TYPE_NONE_CHARGER; break; } charge->usb_charger = charger; if (charge->dc_charger != DC_TYPE_NONE_CHARGER) rk817_charge_set_chrg_param(charge, charge->usb_charger); DBG("register bc evt notifier\n"); } return 0; } static void rk817_charge_pre_init(struct rk817_charger *charge) { charge->max_chrg_current = charge->pdata->max_chrg_current; charge->max_input_current = charge->pdata->max_input_current; charge->max_chrg_voltage = charge->pdata->max_chrg_voltage; charge->min_input_voltage = charge->pdata->min_input_voltage; charge->chrg_finish_cur = charge->pdata->chrg_finish_cur; charge->chrg_term_mode = charge->pdata->chrg_term_mode; rk817_charge_set_input_voltage(charge, charge->min_input_voltage); rk817_charge_set_chrg_voltage(charge, charge->max_chrg_voltage); rk817_charge_set_chrg_current(charge, charge->max_chrg_current); rk817_charge_set_chrg_finish_condition(charge); rk817_charge_otg_disable(charge); rk817_charge_sys_can_sd_disable(charge); rk817_charge_usb_to_sys_enable(charge); rk817_charge_enable_charge(charge); rk817_charge_set_charge_clock(charge, CHRG_CLK_2M); } static void rk817_chage_debug(struct rk817_charger *charge) { rk817_charge_get_charge_status(charge); DBG("OTG state : %d\n", rk817_charge_get_otg_state(charge)); DBG("charge state: %d\n", rk817_charge_get_charge_state(charge)); DBG("max_chrg_current: %d\n" "max_input_current: %d\n" "min_input_voltage: %d\n" "max_chrg_voltage: %d\n" "max_chrg_finish_cur: %d\n" "chrg_term_mode: %d\n", charge->max_chrg_current, charge->max_input_current, charge->min_input_voltage, charge->max_chrg_voltage, charge->chrg_finish_cur, charge->chrg_term_mode); } #ifdef CONFIG_OF static int rk817_charge_parse_dt(struct rk817_charger *charge) { struct charger_platform_data *pdata; enum of_gpio_flags flags; struct device *dev = charge->dev; struct device_node *np = charge->dev->of_node; int ret; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; charge->pdata = pdata; pdata->max_chrg_current = DEFAULT_CHRG_CURRENT; pdata->max_input_current = DEFAULT_INPUT_CURRENT; pdata->max_chrg_voltage = DEFAULT_CHRG_VOLTAGE; pdata->min_input_voltage = DEFAULT_INPUT_VOLTAGE; pdata->chrg_finish_cur = DEFAULT_CHRG_TERM_CUR; pdata->chrg_term_mode = DEFAULT_CHRG_TERM_MODE; pdata->extcon = of_property_read_bool(np, "extcon"); ret = of_property_read_u32(np, "max_chrg_current", &pdata->max_chrg_current); if (ret < 0) dev_err(dev, "max_chrg_current missing!\n"); ret = of_property_read_u32(np, "max_input_current", &pdata->max_input_current); if (ret < 0) dev_err(dev, "max_input_current missing!\n"); ret = of_property_read_u32(np, "max_chrg_voltage", &pdata->max_chrg_voltage); if (ret < 0) dev_err(dev, "max_chrg_voltage missing!\n"); ret = of_property_read_u32(np, "min_input_voltage", &pdata->min_input_voltage); if (ret < 0) dev_WARN(dev, "min_input_voltage missing!\n"); ret = of_property_read_u32(np, "chrg_finish_cur", &pdata->chrg_finish_cur); if (ret < 0) dev_WARN(dev, "chrg_term_mode missing!\n"); ret = of_property_read_u32(np, "chrg_term_mode", &pdata->chrg_term_mode); if (ret < 0) dev_WARN(dev, "chrg_term_mode missing!\n"); ret = of_property_read_u32(np, "virtual_power", &pdata->virtual_power); if (ret < 0) dev_err(dev, "virtual_power missing!\n"); ret = of_property_read_u32(np, "power_dc2otg", &pdata->power_dc2otg); if (ret < 0) dev_err(dev, "power_dc2otg missing!\n"); ret = of_property_read_u32(np, "sample_res", &pdata->sample_res); if (ret < 0) { pdata->sample_res = SAMPLE_RES_10MR; dev_err(dev, "sample_res missing!\n"); } ret = of_property_read_u32(np, "otg5v_suspend_enable", &pdata->otg5v_suspend_enable); if (ret < 0) { pdata->otg5v_suspend_enable = 1; dev_err(dev, "otg5v_suspend_enable missing!\n"); } if (!is_battery_exist(charge)) pdata->virtual_power = 1; charge->res_div = (charge->pdata->sample_res == SAMPLE_RES_10MR) ? SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2; if (!of_find_property(np, "dc_det_gpio", &ret)) { pdata->support_dc_det = false; DBG("not support dc\n"); } else { pdata->support_dc_det = true; pdata->dc_det_pin = of_get_named_gpio_flags(np, "dc_det_gpio", 0, &flags); if (gpio_is_valid(pdata->dc_det_pin)) { DBG("support dc\n"); pdata->dc_det_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1; } else { dev_err(dev, "invalid dc det gpio!\n"); return -EINVAL; } } DBG("input_current:%d\n" "input_min_voltage: %d\n" "chrg_current:%d\n" "chrg_voltage:%d\n" "sample_res:%d\n" "extcon:%d\n" "virtual_power:%d\n" "power_dc2otg:%d\n", pdata->max_input_current, pdata->min_input_voltage, pdata->max_chrg_current, pdata->max_chrg_voltage, pdata->sample_res, pdata->extcon, pdata->virtual_power, pdata->power_dc2otg); return 0; } #else static int rk817_charge_parse_dt(struct rk817_charger *charge) { return -ENODEV; } #endif static void rk817_charge_irq_delay_work(struct work_struct *work) { struct rk817_charger *charge = container_of(work, struct rk817_charger, irq_work.work); if (charge->plugin_trigger) { DBG("pmic: plug in\n"); charge->plugin_trigger = 0; if (charge->pdata->extcon) queue_delayed_work(charge->usb_charger_wq, &charge->usb_work, msecs_to_jiffies(10)); } else if (charge->plugout_trigger) { DBG("pmic: plug out\n"); charge->plugout_trigger = 0; rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER); rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER); } else { DBG("pmic: unknown irq\n"); } } static irqreturn_t rk817_plug_in_isr(int irq, void *cg) { struct rk817_charger *charge; charge = (struct rk817_charger *)cg; charge->plugin_trigger = 1; queue_delayed_work(charge->usb_charger_wq, &charge->irq_work, msecs_to_jiffies(10)); return IRQ_HANDLED; } static irqreturn_t rk817_plug_out_isr(int irq, void *cg) { struct rk817_charger *charge; charge = (struct rk817_charger *)cg; charge->plugout_trigger = 1; queue_delayed_work(charge->usb_charger_wq, &charge->irq_work, msecs_to_jiffies(10)); return IRQ_HANDLED; } static int rk817_charge_init_irqs(struct rk817_charger *charge) { struct rk808 *rk817 = charge->rk817; struct platform_device *pdev = charge->pdev; int ret, plug_in_irq, plug_out_irq; plug_in_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_IN); if (plug_in_irq < 0) { dev_err(charge->dev, "plug_in_irq request failed!\n"); return plug_in_irq; } plug_out_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_OUT); if (plug_out_irq < 0) { dev_err(charge->dev, "plug_out_irq request failed!\n"); return plug_out_irq; } ret = devm_request_threaded_irq(charge->dev, plug_in_irq, NULL, rk817_plug_in_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_in", charge); if (ret) { dev_err(&pdev->dev, "plug_in_irq request failed!\n"); return ret; } ret = devm_request_threaded_irq(charge->dev, plug_out_irq, NULL, rk817_plug_out_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_out", charge); if (ret) { dev_err(&pdev->dev, "plug_out_irq request failed!\n"); return ret; } charge->plugin_irq = plug_in_irq; charge->plugout_irq = plug_out_irq; INIT_DELAYED_WORK(&charge->irq_work, rk817_charge_irq_delay_work); return 0; } static const struct of_device_id rk817_charge_of_match[] = { { .compatible = "rk817,charger", }, { }, }; static int rk817_charge_probe(struct platform_device *pdev) { struct rk808 *rk817 = dev_get_drvdata(pdev->dev.parent); const struct of_device_id *of_id = of_match_device(rk817_charge_of_match, &pdev->dev); struct i2c_client *client = rk817->i2c; struct rk817_charger *charge; int i; int ret; if (!of_id) { dev_err(&pdev->dev, "Failed to find matching dt id\n"); return -ENODEV; } charge = devm_kzalloc(&pdev->dev, sizeof(*charge), GFP_KERNEL); if (!charge) return -EINVAL; charge->rk817 = rk817; charge->pdev = pdev; charge->dev = &pdev->dev; charge->client = client; platform_set_drvdata(pdev, charge); charge->regmap = rk817->regmap; if (IS_ERR(charge->regmap)) { dev_err(charge->dev, "Failed to initialize regmap\n"); return -EINVAL; } for (i = 0; i < ARRAY_SIZE(rk817_charge_reg_fields); i++) { const struct reg_field *reg_fields = rk817_charge_reg_fields; charge->rmap_fields[i] = devm_regmap_field_alloc(charge->dev, charge->regmap, reg_fields[i]); if (IS_ERR(charge->rmap_fields[i])) { dev_err(charge->dev, "cannot allocate regmap field\n"); return PTR_ERR(charge->rmap_fields[i]); } } ret = rk817_charge_parse_dt(charge); if (ret < 0) { dev_err(charge->dev, "charge parse dt failed!\n"); return ret; } rk817_charge_pre_init(charge); ret = rk817_charge_init_power_supply(charge); if (ret) { dev_err(charge->dev, "init power supply fail!\n"); return ret; } ret = rk817_charge_init_dc(charge); if (ret) { dev_err(charge->dev, "init dc failed!\n"); return ret; } ret = rk817_charge_usb_init(charge); if (ret) { dev_err(charge->dev, "init usb failed!\n"); return ret; } ret = rk817_charge_init_irqs(charge); if (ret) { dev_err(charge->dev, "init irqs failed!\n"); goto irq_fail; } rk817_chage_debug(charge); DBG("driver version: %s\n", CHARGE_DRIVER_VERSION); return 0; irq_fail: if (charge->pdata->extcon) { cancel_delayed_work_sync(&charge->host_work); cancel_delayed_work_sync(&charge->discnt_work); } cancel_delayed_work_sync(&charge->usb_work); cancel_delayed_work_sync(&charge->dc_work); cancel_delayed_work_sync(&charge->irq_work); destroy_workqueue(charge->usb_charger_wq); destroy_workqueue(charge->dc_charger_wq); if (charge->pdata->extcon) { extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_CDP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_USB_VBUS_EN, &charge->cable_host_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_USB, &charge->cable_discnt_nb); } else { rk_bc_detect_notifier_unregister(&charge->bc_nb); } return ret; } #ifdef CONFIG_PM_SLEEP static int rk817_charge_pm_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct rk817_charger *charge = dev_get_drvdata(&pdev->dev); charge->otg_slp_state = rk817_charge_get_otg_slp_state(charge); /* enable sleep boost5v and otg5v */ if (charge->pdata->otg5v_suspend_enable) { if ((charge->otg_in && !charge->dc_in) || (charge->otg_in && charge->dc_in && !charge->pdata->power_dc2otg)) { rk817_charge_otg_slp_enable(charge); DBG("suspend: otg 5v on\n"); return 0; } } /* disable sleep otg5v */ rk817_charge_otg_slp_disable(charge); DBG("suspend: otg 5v off\n"); return 0; } static int rk817_charge_pm_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct rk817_charger *charge = dev_get_drvdata(&pdev->dev); /* resume sleep boost5v and otg5v */ if (charge->otg_slp_state) rk817_charge_otg_slp_enable(charge); return 0; } #endif static SIMPLE_DEV_PM_OPS(rk817_charge_pm_ops, rk817_charge_pm_suspend, rk817_charge_pm_resume); static void rk817_charger_shutdown(struct platform_device *dev) { struct rk817_charger *charge = platform_get_drvdata(dev); /* type-c only */ if (charge->pdata->extcon) { cancel_delayed_work_sync(&charge->host_work); cancel_delayed_work_sync(&charge->discnt_work); } rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF); rk817_charge_boost_disable(charge); disable_irq(charge->plugin_irq); disable_irq(charge->plugout_irq); cancel_delayed_work_sync(&charge->usb_work); cancel_delayed_work_sync(&charge->dc_work); cancel_delayed_work_sync(&charge->irq_work); flush_workqueue(charge->usb_charger_wq); flush_workqueue(charge->dc_charger_wq); if (charge->pdata->extcon) { extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_SDP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_DCP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_CHG_USB_CDP, &charge->cable_cg_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_USB_VBUS_EN, &charge->cable_host_nb); extcon_unregister_notifier(charge->cable_edev, EXTCON_USB, &charge->cable_discnt_nb); } else { rk_bc_detect_notifier_unregister(&charge->bc_nb); } DBG("shutdown: ac=%d usb=%d dc=%d otg=%d\n", charge->ac_in, charge->usb_in, charge->dc_in, charge->otg_in); } static struct platform_driver rk817_charge_driver = { .probe = rk817_charge_probe, .shutdown = rk817_charger_shutdown, .driver = { .name = "rk817-charger", .pm = &rk817_charge_pm_ops, .of_match_table = of_match_ptr(rk817_charge_of_match), }, }; static int __init rk817_charge_init(void) { return platform_driver_register(&rk817_charge_driver); } fs_initcall_sync(rk817_charge_init); static void __exit rk817_charge_exit(void) { platform_driver_unregister(&rk817_charge_driver); } module_exit(rk817_charge_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("shengfeixu "); MODULE_DESCRIPTION("rk817 charge driver");