/* * bq2419x-charger.c -- BQ24190/BQ24192/BQ24192i/BQ24193 Charger driver * * Copyright (c) 2013-2017, NVIDIA CORPORATION. All rights reserved. * * Author: Laxman Dewangan * Author: Syed Rafiuddin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, * whether express or implied; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_STR_PRINT 50 #define bq_chg_err(bq, fmt, ...) \ dev_err(bq->dev, "Charging Fault: " fmt, ##__VA_ARGS__) #define BQ2419X_INPUT_VINDPM_OFFSET 3880 #define BQ2419X_CHARGE_ICHG_OFFSET 512 #define BQ2419X_PRE_CHG_IPRECHG_OFFSET 128 #define BQ2419X_PRE_CHG_TERM_OFFSET 128 #define BQ2419X_CHARGE_VOLTAGE_OFFSET 3504 #define BQ2419x_OTG_ENABLE_TIME msecs_to_jiffies(30000) #define BQ2419X_PC_USB_LP0_THRESHOLD 95 #define BQ2419x_TEMP_H_CHG_DISABLE 50 #define BQ2419x_TEMP_L_CHG_DISABLE 0 /* input current limit */ static const unsigned int iinlim[] = { 100, 150, 500, 900, 1200, 1500, 2000, 3000, }; static const struct regmap_config bq2419x_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = BQ2419X_MAX_REGS, }; struct bq2419x_reg_info { u8 mask; u8 val; }; static const unsigned int bq2419x_extcon_cable[] = { EXTCON_USB, EXTCON_NONE, }; struct bq2419x_chip { struct device *dev; struct regmap *regmap; int irq; int gpio_otg_iusb; int auto_rechg_power_on_time; bool emulate_input_disconnected; struct mutex mutex; struct mutex otg_mutex; int in_current_limit; struct regulator_dev *chg_rdev; struct regulator_desc chg_reg_desc; struct regulator_init_data *chg_ridata; struct device_node *chg_np; struct regulator_dev *vbus_rdev; struct regulator_desc vbus_reg_desc; struct regulator_init_data *vbus_ridata; struct device_node *vbus_np; struct battery_charger_dev *bc_dev; int chg_status; struct delayed_work otg_reset_work; struct delayed_work wdt_restart_wq; int is_otg_connected; int battery_presense; bool cable_connected; int last_charging_current; bool disable_suspend_during_charging; bool wake_lock_released; int last_temp; bool shutdown_complete; bool charging_disabled_on_suspend; bool thermal_chg_disable; struct bq2419x_reg_info input_src; struct bq2419x_reg_info chg_current_control; struct bq2419x_reg_info prechg_term_control; struct bq2419x_reg_info ir_comp_therm; struct bq2419x_reg_info chg_voltage_control; struct bq2419x_reg_info watchdog_timer; struct bq2419x_vbus_platform_data *vbus_pdata; struct bq2419x_charger_platform_data *charger_pdata; struct delayed_work thermal_init_work; struct thermal_zone_device *die_tz_device; int thermal_init_retry; struct extcon_dev edev; const char *ext_name; int last_input_voltage; int wdt_refresh_timeout; int wdt_time_sec; }; static int current_to_reg(const unsigned int *tbl, size_t size, unsigned int val) { size_t i; for (i = 0; i < size; i++) if (val < tbl[i]) break; return i > 0 ? i - 1 : -EINVAL; } static int bq2419x_charger_enable(struct bq2419x_chip *bq2419x) { int ret; if (bq2419x->battery_presense) { dev_info(bq2419x->dev, "Charging enabled\n"); /* set default Charge regulation voltage */ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_VOLT_CTRL_REG, bq2419x->chg_voltage_control.mask, bq2419x->chg_voltage_control.val); if (ret < 0) { dev_err(bq2419x->dev, "VOLT_CTRL_REG update failed %d\n", ret); return ret; } ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_CHARGE); } else { dev_info(bq2419x->dev, "Charging disabled\n"); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_DISABLE_CHARGE); } if (ret < 0) dev_err(bq2419x->dev, "register update failed, err %d\n", ret); return ret; } static int bq2419x_reset_wdt(struct bq2419x_chip *bq2419x, const char *from) { int ret = 0; unsigned int reg_val; int timeout; if (!bq2419x->wdt_refresh_timeout) return -EINVAL; dev_info(bq2419x->dev, "%s() from %s()\n", __func__, from); ret = regmap_read(bq2419x->regmap, BQ2419X_PWR_ON_REG, ®_val); if (ret < 0) { dev_err(bq2419x->dev, "PWR_ON_REG read failed: %d\n", ret); goto scrub; } reg_val |= BIT(6); /* Write two times to make sure reset WDT */ ret = regmap_write(bq2419x->regmap, BQ2419X_PWR_ON_REG, reg_val); if (ret < 0) { dev_err(bq2419x->dev, "PWR_ON_REG write failed: %d\n", ret); goto scrub; } ret = regmap_write(bq2419x->regmap, BQ2419X_PWR_ON_REG, reg_val); if (ret < 0) { dev_err(bq2419x->dev, "PWR_ON_REG write failed: %d\n", ret); goto scrub; } scrub: timeout = bq2419x->wdt_refresh_timeout; schedule_delayed_work(&bq2419x->wdt_restart_wq, msecs_to_jiffies(timeout * 1000)); return ret; } static int bq2419x_watchdog_disable(struct bq2419x_chip *bq2419x) { int ret; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, BQ2419X_WD_MASK, 0); if (ret < 0) dev_err(bq2419x->dev, "TIME_CTRL_REG read failed: %d\n", ret); else dev_info(bq2419x->dev, "Charging WATCHDOG timer disabled\n"); return ret; } static int bq2419x_watchdog_enable(struct bq2419x_chip *bq2419x, const char *from) { int ret = 0; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, bq2419x->watchdog_timer.mask, bq2419x->watchdog_timer.val); if (ret < 0) { dev_err(bq2419x->dev, "TIME_CTRL_REG read failed: %d\n", ret); return ret; } ret = bq2419x_reset_wdt(bq2419x, from); if (ret < 0) { dev_err(bq2419x->dev, "bq2419x_reset_wdt failed: %d\n", ret); return ret; } dev_info(bq2419x->dev, "Charger Watchdog timer enabled in OTG mode\n"); return ret; } static void bq2419x_wdt_restart_wq(struct work_struct *work) { struct bq2419x_chip *bq2419x; int ret; bq2419x = container_of(work, struct bq2419x_chip, wdt_restart_wq.work); ret = bq2419x_reset_wdt(bq2419x, "Thread"); if (ret < 0) dev_err(bq2419x->dev, "bq2419x_reset_wdt failed: %d\n", ret); } static int bq2419x_vbus_enable(struct regulator_dev *rdev) { struct bq2419x_chip *bq2419x = rdev_get_drvdata(rdev); int ret; dev_info(bq2419x->dev, "VBUS enabled, charging disabled\n"); mutex_lock(&bq2419x->otg_mutex); bq2419x->is_otg_connected = true; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_VBUS); if (ret < 0) dev_err(bq2419x->dev, "PWR_ON_REG update failed %d", ret); if (bq2419x->wdt_refresh_timeout) bq2419x_watchdog_enable(bq2419x, "OTG-ENABLE"); mutex_unlock(&bq2419x->otg_mutex); return ret; } static int bq2419x_vbus_disable(struct regulator_dev *rdev) { struct bq2419x_chip *bq2419x = rdev_get_drvdata(rdev); int ret; dev_info(bq2419x->dev, "VBUS disabled, charging enabled\n"); mutex_lock(&bq2419x->otg_mutex); if (bq2419x->wdt_refresh_timeout) { cancel_delayed_work_sync(&bq2419x->wdt_restart_wq); bq2419x_watchdog_disable(bq2419x); } bq2419x->is_otg_connected = false; ret = bq2419x_charger_enable(bq2419x); if (ret < 0) dev_err(bq2419x->dev, "Charger enable failed %d", ret); mutex_unlock(&bq2419x->otg_mutex); return ret; } static int bq2419x_vbus_is_enabled(struct regulator_dev *rdev) { struct bq2419x_chip *bq2419x = rdev_get_drvdata(rdev); int ret; unsigned int data; ret = regmap_read(bq2419x->regmap, BQ2419X_PWR_ON_REG, &data); if (ret < 0) { dev_err(bq2419x->dev, "PWR_ON_REG read failed %d", ret); return ret; } return (data & BQ2419X_ENABLE_CHARGE_MASK) == BQ2419X_ENABLE_VBUS; } static struct regulator_ops bq2419x_vbus_ops = { .enable = bq2419x_vbus_enable, .disable = bq2419x_vbus_disable, .is_enabled = bq2419x_vbus_is_enabled, }; static int bq2419x_val_to_reg(int val, int offset, int div, int nbits, bool roundup) { int max_val = offset + (BIT(nbits) - 1) * div; if (val <= offset) return 0; if (val >= max_val) return BIT(nbits) - 1; if (roundup) return DIV_ROUND_UP(val - offset, div); else return (val - offset) / div; } static int bq2419x_process_charger_plat_data(struct bq2419x_chip *bq2419x, struct bq2419x_charger_platform_data *chg_pdata) { int voltage_input; int fast_charge_current; int pre_charge_current; int termination_current; int ir_compensation_resistor; int ir_compensation_voltage; int thermal_regulation_threshold; int charge_voltage_limit; int vindpm, ichg, iprechg, iterm, bat_comp, vclamp, treg, vreg, wtreg; if (chg_pdata) { voltage_input = chg_pdata->input_voltage_limit_mV ?: 4200; fast_charge_current = chg_pdata->fast_charge_current_limit_mA ?: 4544; pre_charge_current = chg_pdata->pre_charge_current_limit_mA ?: 256; termination_current = chg_pdata->termination_current_limit_mA ?: 128; ir_compensation_resistor = chg_pdata->ir_compensation_resister_ohm ?: 70; ir_compensation_voltage = chg_pdata->ir_compensation_voltage_mV ?: 112; thermal_regulation_threshold = chg_pdata->thermal_regulation_threshold_degC ?: 100; charge_voltage_limit = chg_pdata->charge_voltage_limit_mV ?: 4208; } else { voltage_input = 4200; fast_charge_current = 4544; pre_charge_current = 256; termination_current = 128; ir_compensation_resistor = 70; ir_compensation_voltage = 112; thermal_regulation_threshold = 100; charge_voltage_limit = 4208; } vindpm = bq2419x_val_to_reg(voltage_input, BQ2419X_INPUT_VINDPM_OFFSET, 80, 4, 0); bq2419x->input_src.mask = BQ2419X_INPUT_VINDPM_MASK; bq2419x->input_src.val = vindpm << 3; bq2419x->input_src.mask |= BQ2419X_INPUT_IINLIM_MASK; bq2419x->input_src.val |= 0x2; ichg = bq2419x_val_to_reg(fast_charge_current, BQ2419X_CHARGE_ICHG_OFFSET, 64, 6, 0); bq2419x->chg_current_control.mask = BQ2419X_CHRG_CTRL_ICHG_MASK; bq2419x->chg_current_control.val = ichg << 2; iprechg = bq2419x_val_to_reg(pre_charge_current, BQ2419X_PRE_CHG_IPRECHG_OFFSET, 128, 4, 0); bq2419x->prechg_term_control.mask = BQ2419X_CHRG_TERM_PRECHG_MASK; bq2419x->prechg_term_control.val = iprechg << 4; iterm = bq2419x_val_to_reg(termination_current, BQ2419X_PRE_CHG_TERM_OFFSET, 128, 4, 0); bq2419x->prechg_term_control.mask |= BQ2419X_CHRG_TERM_TERM_MASK; bq2419x->prechg_term_control.val |= iterm; bat_comp = ir_compensation_resistor / 10; bq2419x->ir_comp_therm.mask = BQ2419X_THERM_BAT_COMP_MASK; bq2419x->ir_comp_therm.val = bat_comp << 5; vclamp = ir_compensation_voltage / 16; bq2419x->ir_comp_therm.mask |= BQ2419X_THERM_VCLAMP_MASK; bq2419x->ir_comp_therm.val |= vclamp << 2; bq2419x->ir_comp_therm.mask |= BQ2419X_THERM_TREG_MASK; if (thermal_regulation_threshold <= 60) treg = 0; else if (thermal_regulation_threshold <= 80) treg = 1; else if (thermal_regulation_threshold <= 100) treg = 2; else treg = 3; bq2419x->ir_comp_therm.val |= treg; vreg = bq2419x_val_to_reg(charge_voltage_limit, BQ2419X_CHARGE_VOLTAGE_OFFSET, 16, 6, 1); bq2419x->chg_voltage_control.mask = BQ2419X_CHG_VOLT_LIMIT_MASK; bq2419x->chg_voltage_control.val = vreg << 2; if (!bq2419x->wdt_time_sec) goto done; bq2419x->watchdog_timer.mask = BQ2419X_WD_MASK; if (bq2419x->wdt_time_sec <= 60) { wtreg = BQ2419X_WD_40ms; bq2419x->wdt_refresh_timeout = 25; } else if (bq2419x->wdt_time_sec <= 120) { wtreg = BQ2419X_WD_80ms; bq2419x->wdt_refresh_timeout = 50; } else { wtreg = BQ2419X_WD_160ms; bq2419x->wdt_refresh_timeout = 125; } bq2419x->watchdog_timer.val |= wtreg; done: return 0; } static int bq2419x_safetytimer_disable(struct bq2419x_chip *bq2419x) { int ret; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, BQ2419X_EN_SFT_TIMER_MASK, 0); if (ret < 0) dev_err(bq2419x->dev, "TIME_CTRL update failed: %d\n", ret); else dev_info(bq2419x->dev, "Charging SAFETY timer disabled\n"); return ret; } static int bq2419x_charger_init(struct bq2419x_chip *bq2419x) { int ret; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_CHRG_CTRL_REG, bq2419x->chg_current_control.mask, bq2419x->chg_current_control.val); if (ret < 0) { dev_err(bq2419x->dev, "CHRG_CTRL_REG write failed %d\n", ret); return ret; } ret = regmap_update_bits(bq2419x->regmap, BQ2419X_CHRG_TERM_REG, bq2419x->prechg_term_control.mask, bq2419x->prechg_term_control.val); if (ret < 0) { dev_err(bq2419x->dev, "CHRG_TERM_REG write failed %d\n", ret); return ret; } ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, bq2419x->input_src.mask, bq2419x->input_src.val); if (ret < 0) dev_err(bq2419x->dev, "INPUT_SRC_REG write failed %d\n", ret); bq2419x->last_input_voltage = (bq2419x->input_src.val >> 3) & 0xF; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_THERM_REG, bq2419x->ir_comp_therm.mask, bq2419x->ir_comp_therm.val); if (ret < 0) dev_err(bq2419x->dev, "THERM_REG write failed: %d\n", ret); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_VOLT_CTRL_REG, bq2419x->chg_voltage_control.mask, bq2419x->chg_voltage_control.val); if (ret < 0) dev_err(bq2419x->dev, "VOLT_CTRL update failed: %d\n", ret); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, BQ2419X_TIME_JEITA_ISET | BQ2419x_EN_CHARGE_TERM, 0); if (ret < 0) dev_err(bq2419x->dev, "TIME_CTRL update failed: %d\n", ret); /* disable safety timer */ bq2419x_safetytimer_disable(bq2419x); /* disable sWDT timer */ if (!bq2419x->is_otg_connected) ret = bq2419x_watchdog_disable(bq2419x); return ret; } static void bq2419x_otg_reset_work_handler(struct work_struct *work) { int ret; struct bq2419x_chip *bq2419x = container_of(to_delayed_work(work), struct bq2419x_chip, otg_reset_work); if (!mutex_is_locked(&bq2419x->otg_mutex)) { mutex_lock(&bq2419x->otg_mutex); if (bq2419x->is_otg_connected) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_VBUS); if (ret < 0) dev_err(bq2419x->dev, "PWR_ON_REG update failed %d", ret); } if (bq2419x->wdt_refresh_timeout) bq2419x_watchdog_enable(bq2419x, "OTG-RESET-WORK"); mutex_unlock(&bq2419x->otg_mutex); } } static int bq2419x_disable_otg_mode(struct bq2419x_chip *bq2419x) { int ret = 0; mutex_lock(&bq2419x->otg_mutex); if (bq2419x->is_otg_connected) { if (bq2419x->wdt_refresh_timeout) { cancel_delayed_work_sync(&bq2419x->wdt_restart_wq); bq2419x_watchdog_disable(bq2419x); } ret = bq2419x_charger_enable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charger enable failed %d", ret); mutex_unlock(&bq2419x->otg_mutex); return ret; } schedule_delayed_work(&bq2419x->otg_reset_work, BQ2419x_OTG_ENABLE_TIME); } mutex_unlock(&bq2419x->otg_mutex); return ret; } static int bq2419x_charger_input_voltage_configure( struct battery_charger_dev *bc_dev, int battery_soc) { struct bq2419x_chip *bq2419x = battery_charger_get_drvdata(bc_dev); struct bq2419x_charger_platform_data *chg_pdata; u32 input_voltage_limit = 0; int ret; int i; int vreg; chg_pdata = bq2419x->charger_pdata; if (!bq2419x->cable_connected || !chg_pdata->n_soc_profile) return 0; for (i = 0; i < chg_pdata->n_soc_profile; ++i) { if (battery_soc < chg_pdata->soc_range[i]) { if (chg_pdata->input_voltage_soc_limit) input_voltage_limit = chg_pdata->input_voltage_soc_limit[i]; break; } } if (!input_voltage_limit) return 0; /*Configure input voltage limit */ vreg = bq2419x_val_to_reg(input_voltage_limit, BQ2419X_INPUT_VINDPM_OFFSET, 80, 4, 0); if (bq2419x->last_input_voltage == vreg) return 0; dev_info(bq2419x->dev, "Changing VINDPM to soc:voltage:vreg %d:%d:%d\n", battery_soc, input_voltage_limit, vreg); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, BQ2419X_INPUT_VINDPM_MASK, (vreg << 3)); if (ret < 0) { dev_err(bq2419x->dev, "INPUT_VOLTAGE update failed %d\n", ret); return ret; } bq2419x->last_input_voltage = vreg; return 0; } static int bq2419x_configure_charging_current(struct bq2419x_chip *bq2419x, int in_current_limit) { int val = 0; int ret = 0; int floor = 0; int battery_soc = 0; /* Clear EN_HIZ */ if (!bq2419x->emulate_input_disconnected) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, BQ2419X_EN_HIZ, 0); if (ret < 0) { dev_err(bq2419x->dev, "INPUT_SRC_REG update failed %d\n", ret); return ret; } } /* Configure input voltage limit*/ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, bq2419x->input_src.mask, bq2419x->input_src.val); if (ret < 0) { dev_err(bq2419x->dev, "INPUT_SRC_REG update failed %d\n", ret); return ret; } bq2419x->last_input_voltage = (bq2419x->input_src.val >> 3) & 0xF; /* Configure input current limit in steps */ val = current_to_reg(iinlim, ARRAY_SIZE(iinlim), in_current_limit); floor = current_to_reg(iinlim, ARRAY_SIZE(iinlim), 500); if (val < 0 || floor < 0) return 0; for (; floor <= val; floor++) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, BQ2419x_CONFIG_MASK, floor); if (ret < 0) dev_err(bq2419x->dev, "INPUT_SRC_REG update failed: %d\n", ret); udelay(BQ2419x_CHARGING_CURRENT_STEP_DELAY_US); } bq2419x->in_current_limit = in_current_limit; if (bq2419x->charger_pdata->n_soc_profile) { battery_soc = battery_gauge_get_battery_soc(bq2419x->bc_dev); if (battery_soc > 0) bq2419x_charger_input_voltage_configure( bq2419x->bc_dev, battery_soc); } return ret; } static int bq2419x_set_charging_current(struct regulator_dev *rdev, int min_uA, int max_uA) { struct bq2419x_chip *bq2419x = rdev_get_drvdata(rdev); int in_current_limit; int old_current_limit; int ret = 0; int val; dev_info(bq2419x->dev, "Setting charging current %d\n", max_uA/1000); bq2419x->chg_status = BATTERY_DISCHARGING; if (!bq2419x->is_otg_connected) { ret = bq2419x_charger_enable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charger enable failed %d", ret); return ret; } } ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, &val); if (ret < 0) dev_err(bq2419x->dev, "SYS_STAT_REG read failed: %d\n", ret); if (max_uA == 0 && val != 0) return ret; if (!max_uA) bq2419x->wake_lock_released = false; old_current_limit = bq2419x->in_current_limit; bq2419x->last_charging_current = max_uA; if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_UNKNOWN) { in_current_limit = 500; bq2419x->cable_connected = 0; bq2419x->chg_status = BATTERY_DISCHARGING; battery_charger_thermal_stop_monitoring( bq2419x->bc_dev); /* Clear JEITA_VSET bit if cable disconnected */ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_MISC_OPER_REG, BQ2419X_JEITA_VSET_MASK, 0); if (ret < 0) dev_err(bq2419x->dev, "JEITA_VSET update failed: %d\n", ret); } else if ((val & BQ2419x_CHRG_STATE_MASK) == BQ2419x_CHRG_STATE_CHARGE_DONE) { dev_dbg(bq2419x->dev, "Charging completed\n"); bq2419x->chg_status = BATTERY_CHARGING_DONE; bq2419x->cable_connected = 1; in_current_limit = max_uA/1000; battery_charger_thermal_stop_monitoring( bq2419x->bc_dev); } else { in_current_limit = max_uA/1000; bq2419x->cable_connected = 1; bq2419x->chg_status = BATTERY_CHARGING; battery_charger_thermal_start_monitoring( bq2419x->bc_dev); /* Set JEITA_VSET bit if charger cable connected */ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_MISC_OPER_REG, BQ2419X_JEITA_VSET_MASK, BQ2419X_JEITA_VSET_42V); if (ret < 0) dev_err(bq2419x->dev, "JEITA_VSET update failed: %d\n", ret); } if (bq2419x->wake_lock_released) in_current_limit = 500; ret = bq2419x_configure_charging_current(bq2419x, in_current_limit); if (ret < 0) goto error; battery_charging_status_update(bq2419x->bc_dev, bq2419x->chg_status); if (bq2419x->disable_suspend_during_charging) { if (bq2419x->cable_connected && in_current_limit > 500 && (bq2419x->chg_status != BATTERY_CHARGING_DONE)) battery_charger_acquire_wake_lock(bq2419x->bc_dev); else if (!bq2419x->cable_connected && old_current_limit > 500) battery_charger_release_wake_lock(bq2419x->bc_dev); } return 0; error: dev_err(bq2419x->dev, "Charger enable failed, err = %d\n", ret); return ret; } static struct regulator_ops bq2419x_tegra_regulator_ops = { .set_current_limit = bq2419x_set_charging_current, }; static int bq2419x_set_charging_current_suspend(struct bq2419x_chip *bq2419x, int in_current_limit) { int ret; int val; dev_info(bq2419x->dev, "Setting charging current %d mA\n", in_current_limit); if (!bq2419x->is_otg_connected) { ret = bq2419x_charger_enable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charger enable failed %d", ret); return ret; } } ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, &val); if (ret < 0) dev_err(bq2419x->dev, "SYS_STAT_REG read failed: %d\n", ret); if (!bq2419x->cable_connected) { ret = bq2419x_configure_charging_current(bq2419x, in_current_limit); if (ret < 0) return ret; } return 0; } static int bq2419x_reconfigure_charger_param(struct bq2419x_chip *bq2419x, const char *from) { int ret; dev_info(bq2419x->dev, "Reconfiguring charging param from %s\n", from); ret = bq2419x_charger_init(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charger init failed: %d\n", ret); return ret; } ret = bq2419x_configure_charging_current(bq2419x, bq2419x->in_current_limit); if (ret < 0) { dev_err(bq2419x->dev, "Current config failed: %d\n", ret); return ret; } mutex_lock(&bq2419x->otg_mutex); if (bq2419x->is_otg_connected) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_VBUS); if (ret < 0) dev_err(bq2419x->dev, "PWR_ON_REG update failed %d", ret); ret = bq2419x_watchdog_enable(bq2419x, from); if (ret < 0) dev_err(bq2419x->dev, "BQWDT enable failed %d\n", ret); } else { ret = bq2419x_watchdog_disable(bq2419x); if (ret < 0) dev_err(bq2419x->dev, "BQWDT enable failed %d\n", ret); } mutex_unlock(&bq2419x->otg_mutex); return ret; } static int bq2419x_thermal_read_temp(void *data, int *temp) { struct bq2419x_chip *bq2419x = data; unsigned int reg_06; unsigned int reg_08; int treg; int ret; long curr_temp; ret = regmap_read(bq2419x->regmap, BQ2419X_THERM_REG, ®_06); if (ret < 0) { dev_err(bq2419x->dev, "BQ2419X_THERM_REG rd failed %d\n", ret); return ret; } ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, ®_08); if (ret < 0) { dev_err(bq2419x->dev, "BQ2419X_SYS_STAT_REG rd failed %d\n", ret); return ret; } treg = reg_06 & BQ2419x_TREG; curr_temp = (treg * 20 + 60) * 1000; if (reg_08 & BQ2419x_THERM_STAT) curr_temp += 5000; else curr_temp -= 5000; *temp = curr_temp; return 0; } #ifdef CONFIG_THERMAL static struct thermal_zone_of_device_ops bq2419x_thermal_ops = { .get_temp = bq2419x_thermal_read_temp, }; static int bq2419x_thermal_check_therm_regulation(struct bq2419x_chip *bq2419x) { if (bq2419x->die_tz_device) thermal_zone_device_update(bq2419x->die_tz_device, THERMAL_EVENT_UNSPECIFIED); return 0; } static void bq2419x_thermal_init_work(struct work_struct *work) { int ret; struct bq2419x_chip *bq2419x = container_of(to_delayed_work(work), struct bq2419x_chip, thermal_init_work); struct thermal_zone_device *die_tz_device; die_tz_device = devm_thermal_zone_of_sensor_register(bq2419x->dev, 0, bq2419x, &bq2419x_thermal_ops); if (IS_ERR(die_tz_device)) { ret = PTR_ERR(die_tz_device); if ((ret == -EPROBE_DEFER) && (bq2419x->thermal_init_retry)) { schedule_delayed_work(&bq2419x->thermal_init_work, msecs_to_jiffies(1000)); bq2419x->thermal_init_retry--; } return; } bq2419x->die_tz_device = die_tz_device; dev_info(bq2419x->dev, "Thermal Zone registration success\n"); } #endif static int bq2419x_fault_clear_sts(struct bq2419x_chip *bq2419x, unsigned int *reg09_val) { int ret; unsigned int reg09_1, reg09_2; ret = regmap_read(bq2419x->regmap, BQ2419X_FAULT_REG, ®09_1); if (ret < 0) { dev_err(bq2419x->dev, "FAULT_REG read failed: %d\n", ret); return ret; } ret = regmap_read(bq2419x->regmap, BQ2419X_FAULT_REG, ®09_2); if (ret < 0) dev_err(bq2419x->dev, "FAULT_REG read failed: %d\n", ret); if (reg09_val) { unsigned int reg09 = 0; if ((reg09_1 | reg09_2) & BQ2419x_FAULT_WATCHDOG_FAULT) reg09 |= BQ2419x_FAULT_WATCHDOG_FAULT; if ((reg09_1 | reg09_2) & BQ2419x_FAULT_BOOST_FAULT) reg09 |= BQ2419x_FAULT_BOOST_FAULT; if ((reg09_1 | reg09_2) & BQ2419x_FAULT_BAT_FAULT) reg09 |= BQ2419x_FAULT_BAT_FAULT; if (((reg09_1 & BQ2419x_FAULT_CHRG_FAULT_MASK) == BQ2419x_FAULT_CHRG_SAFTY) || ((reg09_2 & BQ2419x_FAULT_CHRG_FAULT_MASK) == BQ2419x_FAULT_CHRG_SAFTY)) reg09 |= BQ2419x_FAULT_CHRG_SAFTY; else if (((reg09_2 & BQ2419x_FAULT_CHRG_FAULT_MASK) == BQ2419x_FAULT_CHRG_INPUT)) reg09 |= BQ2419x_FAULT_CHRG_INPUT; else if (((reg09_1 & BQ2419x_FAULT_CHRG_FAULT_MASK) == BQ2419x_FAULT_CHRG_THERMAL) || ((reg09_2 & BQ2419x_FAULT_CHRG_FAULT_MASK) == BQ2419x_FAULT_CHRG_THERMAL)) reg09 |= BQ2419x_FAULT_CHRG_THERMAL; reg09 |= reg09_2 &BQ2419x_FAULT_NTC_FAULT; *reg09_val = reg09; } return ret; } static int bq2419x_extcon_cable_update(struct bq2419x_chip *bq2419x, unsigned int val) { if ((val & BQ2419x_VBUS_PG_STAT) == BQ2419x_PG_VBUS_USB) { extcon_set_state(&bq2419x->edev, EXTCON_USB, true); dev_dbg(bq2419x->dev, "USB is connected\n"); } else if ((val & BQ2419x_VBUS_PG_STAT) == BQ2419x_VBUS_UNKNOWN) { extcon_set_state(&bq2419x->edev, EXTCON_USB, false); dev_info(bq2419x->dev, "USB is disconnected\n"); } return 0; } static irqreturn_t bq2419x_irq(int irq, void *data) { struct bq2419x_chip *bq2419x = data; int ret; unsigned int val; int check_chg_state = 0; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) goto out; ret = bq2419x_fault_clear_sts(bq2419x, &val); if (ret < 0) { dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); goto out; } dev_dbg(bq2419x->dev, "%s() Irq %d status 0x%02x\n", __func__, irq, val); if (val & BQ2419x_FAULT_BOOST_FAULT) { bq_chg_err(bq2419x, "VBUS Overloaded\n"); ret = bq2419x_disable_otg_mode(bq2419x); if (ret < 0) { bq_chg_err(bq2419x, "otg mode disable failed\n"); goto out; } regulator_notifier_call_chain(bq2419x->vbus_rdev, REGULATOR_EVENT_OVER_CURRENT, NULL); } if (!bq2419x->battery_presense) goto sys_stat_read; if (val & BQ2419x_FAULT_WATCHDOG_FAULT) { bq_chg_err(bq2419x, "Charger WatchDog Timer expired\n"); ret = bq2419x_reconfigure_charger_param(bq2419x, "WDT-EXP-ISR"); if (ret < 0) dev_err(bq2419x->dev, "BQ reconfig failed %d\n", ret); } switch (val & BQ2419x_FAULT_CHRG_FAULT_MASK) { case BQ2419x_FAULT_CHRG_INPUT: bq_chg_err(bq2419x, "Input Fault (VBUS OVP or VBATis_otg_connected) regulator_notifier_call_chain(bq2419x->vbus_rdev, REGULATOR_EVENT_OVER_TEMP, NULL); break; default: break; } if (val & BQ2419x_FAULT_NTC_FAULT) { bq_chg_err(bq2419x, "NTC fault %d\n", val & BQ2419x_FAULT_NTC_FAULT); check_chg_state = 1; } sys_stat_read: ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, &val); if (ret < 0) { dev_err(bq2419x->dev, "SYS_STAT_REG read failed %d\n", ret); goto out; } bq2419x_extcon_cable_update(bq2419x, val); if (!bq2419x->battery_presense) goto out; #ifdef CONFIG_THERMAL if (val & BQ2419x_THERM_STAT) { dev_info(bq2419x->dev, "Charger in thermal regulation\n"); bq2419x_thermal_check_therm_regulation(bq2419x); } #endif if ((val & BQ2419x_CHRG_STATE_MASK) == BQ2419x_CHRG_STATE_CHARGE_DONE) { dev_dbg(bq2419x->dev, "Charging completed\n"); bq2419x->chg_status = BATTERY_CHARGING_DONE; battery_charging_status_update(bq2419x->bc_dev, bq2419x->chg_status); if (bq2419x->disable_suspend_during_charging) battery_charger_release_wake_lock(bq2419x->bc_dev); battery_charger_thermal_stop_monitoring( bq2419x->bc_dev); } if ((val & BQ2419x_VSYS_STAT_MASK) == BQ2419x_VSYS_STAT_BATT_LOW) dev_info(bq2419x->dev, "In VSYSMIN regulation, battery is too low\n"); /* Update Charging status based on STAT register */ if (check_chg_state && ((val & BQ2419x_CHRG_STATE_MASK) == BQ2419x_CHRG_STATE_NOTCHARGING)) { bq2419x->chg_status = BATTERY_DISCHARGING; battery_charging_status_update(bq2419x->bc_dev, bq2419x->chg_status); if (bq2419x->disable_suspend_during_charging) battery_charger_release_wake_lock(bq2419x->bc_dev); } out: mutex_unlock(&bq2419x->mutex); return IRQ_HANDLED; } static struct regulator_desc bq2419x_chg_reg_desc = { .name = "bq2419x-charger", .ops = &bq2419x_tegra_regulator_ops, .type = REGULATOR_CURRENT, .owner = THIS_MODULE, }; static struct regulator_desc bq2419x_vbus_reg_desc = { .name = "bq2419x-vbus", .ops = &bq2419x_vbus_ops, .type = REGULATOR_VOLTAGE, .enable_time = 220000, .owner = THIS_MODULE, }; static int bq2419x_init_charger_regulator(struct bq2419x_chip *bq2419x, struct bq2419x_platform_data *pdata) { int ret = 0; struct regulator_config rconfig = { }; if (!pdata->bcharger_pdata) { dev_err(bq2419x->dev, "No charger platform data\n"); return 0; } bq2419x->chg_reg_desc = bq2419x_chg_reg_desc; bq2419x->chg_ridata = pdata->bcharger_pdata->ridata; bq2419x->chg_ridata->driver_data = bq2419x; bq2419x->chg_ridata->constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; bq2419x->chg_ridata->constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_CURRENT; rconfig.dev = bq2419x->dev; rconfig.of_node = bq2419x->chg_np; rconfig.init_data = bq2419x->chg_ridata; rconfig.driver_data = bq2419x; bq2419x->chg_rdev = devm_regulator_register(bq2419x->dev, &bq2419x->chg_reg_desc, &rconfig); if (IS_ERR(bq2419x->chg_rdev)) { ret = PTR_ERR(bq2419x->chg_rdev); dev_err(bq2419x->dev, "vbus-charger regulator register failed %d\n", ret); } return ret; } static int bq2419x_init_vbus_regulator(struct bq2419x_chip *bq2419x, struct bq2419x_platform_data *pdata) { int ret = 0; struct regulator_config rconfig = { }; if (!pdata->vbus_pdata) { dev_err(bq2419x->dev, "No vbus platform data\n"); return 0; } bq2419x->gpio_otg_iusb = pdata->vbus_pdata->gpio_otg_iusb; bq2419x->vbus_reg_desc = bq2419x_vbus_reg_desc; bq2419x->vbus_ridata = pdata->vbus_pdata->ridata; bq2419x->vbus_ridata->constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; bq2419x->vbus_ridata->constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_VOLTAGE; if (gpio_is_valid(bq2419x->gpio_otg_iusb)) { ret = gpio_request_one(bq2419x->gpio_otg_iusb, GPIOF_OUT_INIT_HIGH, dev_name(bq2419x->dev)); if (ret < 0) { dev_err(bq2419x->dev, "gpio request failed %d\n", ret); return ret; } } /* Register the regulators */ rconfig.dev = bq2419x->dev; rconfig.of_node = bq2419x->vbus_np; rconfig.init_data = bq2419x->vbus_ridata; rconfig.driver_data = bq2419x; bq2419x->vbus_rdev = devm_regulator_register(bq2419x->dev, &bq2419x->vbus_reg_desc, &rconfig); if (IS_ERR(bq2419x->vbus_rdev)) { ret = PTR_ERR(bq2419x->vbus_rdev); dev_err(bq2419x->dev, "VBUS regulator register failed %d\n", ret); goto scrub; } /* Disable the VBUS regulator and enable charging */ ret = bq2419x_charger_enable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charging enable failed %d", ret); goto scrub; } return ret; scrub: if (gpio_is_valid(bq2419x->gpio_otg_iusb)) gpio_free(bq2419x->gpio_otg_iusb); return ret; } #ifdef CONFIG_DEBUG_FS #include #include static struct dentry *debugfs_root; static ssize_t bq2419x_show_suspend_state(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct i2c_client *client = file->private_data; struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); char buf[64] = { 0, }; ssize_t ret = 0; if (bq2419x->wake_lock_released || (bq2419x->chg_status == BATTERY_CHARGING_DONE) || (bq2419x->in_current_limit <= 500)) ret = snprintf(buf, sizeof(buf), "Wake lock disabled\n"); else if (!bq2419x->wake_lock_released || (bq2419x->in_current_limit > 500)) ret = snprintf(buf, sizeof(buf), "Wake lock enabled\n"); return simple_read_from_buffer(user_buf, count, ppos, buf, ret); } static ssize_t bq2419x_enable_suspend_on_charging(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct i2c_client *client = file->private_data; struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); char buf[64] = { 0, }; ssize_t buf_size; bool enabled; int ret; if (!bq2419x->cable_connected || (bq2419x->chg_status == BATTERY_CHARGING_DONE)) return -EINVAL; if (!bq2419x->disable_suspend_during_charging) return -EINVAL; buf_size = min(count, (sizeof(buf)-1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; if ((*buf == 'E') || (*buf == 'e')) enabled = true; else if ((*buf == 'D') || (*buf == 'd')) enabled = false; else return -EINVAL; if (enabled && !bq2419x->wake_lock_released) { if (bq2419x->in_current_limit == 500) return -EINVAL; ret = bq2419x_configure_charging_current(bq2419x, 500); if (ret < 0) { dev_err(bq2419x->dev, "Charging Current config faild: %d\n", ret); return ret; } battery_charger_release_wake_lock(bq2419x->bc_dev); bq2419x->wake_lock_released = true; } else if (!enabled && bq2419x->wake_lock_released) { bq2419x->wake_lock_released = false; ret = bq2419x_configure_charging_current(bq2419x, bq2419x->last_charging_current/1000); if (ret < 0) { dev_err(bq2419x->dev, "Charging Current config faild: %d\n", ret); return ret; } battery_charger_acquire_wake_lock(bq2419x->bc_dev); } return count; } static const struct file_operations bq2419x_debug_fops = { .open = simple_open, .write = bq2419x_enable_suspend_on_charging, .read = bq2419x_show_suspend_state, }; static int bq2419x_debugfs_init(struct i2c_client *client) { debugfs_root = debugfs_create_dir("battery_charger", NULL); if (!debugfs_root) pr_warn("bq2419x: Failed to create debugfs directory\n"); debugfs_create_file("allow_suspend_on_charging", S_IRUGO | S_IWUSR, debugfs_root, (void *)client, &bq2419x_debug_fops); return 0; } #else static int bq2419x_debugfs_init(struct i2c_client *client) { return 0; } #endif static int bq2419x_show_chip_version(struct bq2419x_chip *bq2419x) { int ret; unsigned int val; ret = regmap_read(bq2419x->regmap, BQ2419X_REVISION_REG, &val); if (ret < 0) { dev_err(bq2419x->dev, "REVISION_REG read failed: %d\n", ret); return ret; } if ((val & BQ2419X_IC_VER_MASK) == BQ24190_IC_VER) dev_info(bq2419x->dev, "chip type BQ24190 detected\n"); else if ((val & BQ2419X_IC_VER_MASK) == BQ24192_IC_VER) dev_info(bq2419x->dev, "chip type BQ2419X/3 detected\n"); else if ((val & BQ2419X_IC_VER_MASK) == BQ24192i_IC_VER) dev_info(bq2419x->dev, "chip type BQ2419Xi detected\n"); return 0; } static ssize_t bq2419x_show_input_charging_current(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); unsigned int reg_val; int ret; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } ret = regmap_read(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, ®_val); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(bq2419x->dev, "INPUT_SRC read failed: %d\n", ret); return ret; } ret = iinlim[BQ2419x_CONFIG_MASK & reg_val]; return snprintf(buf, MAX_STR_PRINT, "%d mA\n", ret); } static ssize_t bq2419x_set_input_charging_current(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); int ret; int in_current_limit; char *p = (char *)buf; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } in_current_limit = memparse(p, &p); ret = bq2419x_configure_charging_current(bq2419x, in_current_limit); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(dev, "Current %d mA configuration faild: %d\n", in_current_limit, ret); return ret; } return count; } static ssize_t bq2419x_show_charging_state(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); unsigned int reg_val; int ret; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } ret = regmap_read(bq2419x->regmap, BQ2419X_PWR_ON_REG, ®_val); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(dev, "BQ2419X_PWR_ON register read failed: %d\n", ret); return ret; } if ((reg_val & BQ2419X_ENABLE_CHARGE_MASK) == BQ2419X_ENABLE_CHARGE) return snprintf(buf, MAX_STR_PRINT, "enabled\n"); else return snprintf(buf, MAX_STR_PRINT, "disabled\n"); } static ssize_t bq2419x_set_charging_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); bool enabled; int ret; if ((*buf == 'E') || (*buf == 'e')) enabled = true; else if ((*buf == 'D') || (*buf == 'd')) enabled = false; else return -EINVAL; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } if (enabled) ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_CHARGE); else ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_DISABLE_CHARGE); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(bq2419x->dev, "register update failed, %d\n", ret); return ret; } return count; } static ssize_t bq2419x_show_input_cable_state(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); unsigned int reg_val; int ret; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } ret = regmap_read(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, ®_val); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(dev, "BQ2419X_PWR_ON register read failed: %d\n", ret); return ret; } if ((reg_val & BQ2419X_EN_HIZ) == BQ2419X_EN_HIZ) return snprintf(buf, MAX_STR_PRINT, "Disconnected\n"); else return snprintf(buf, MAX_STR_PRINT, "Connected\n"); } static ssize_t bq2419x_set_input_cable_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); bool connect; int ret; if ((*buf == 'C') || (*buf == 'c')) connect = true; else if ((*buf == 'D') || (*buf == 'd')) connect = false; else return -EINVAL; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } if (connect) { bq2419x->emulate_input_disconnected = false; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, BQ2419X_EN_HIZ, 0); } else { bq2419x->emulate_input_disconnected = true; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, BQ2419X_EN_HIZ, BQ2419X_EN_HIZ); } mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(bq2419x->dev, "register update failed, %d\n", ret); return ret; } if (connect) dev_info(bq2419x->dev, "Emulation of charger cable disconnect disabled\n"); else dev_info(bq2419x->dev, "Emulated as charger cable Disconnected\n"); return count; } static ssize_t bq2419x_show_output_charging_current(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); int ret; unsigned int data; mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } ret = regmap_read(bq2419x->regmap, BQ2419X_CHRG_CTRL_REG, &data); mutex_unlock(&bq2419x->mutex); if (ret < 0) { dev_err(bq2419x->dev, "CHRG_CTRL read failed %d", ret); return ret; } data >>= 2; data = data * 64 + BQ2419X_CHARGE_ICHG_OFFSET; return snprintf(buf, MAX_STR_PRINT, "%u mA\n", data); } static ssize_t bq2419x_set_output_charging_current(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); int curr_val, ret; int ichg; if (kstrtouint(buf, 0, &curr_val)) { dev_err(dev, "\nfile: %s, line=%d return %s()", __FILE__, __LINE__, __func__); return -EINVAL; } mutex_lock(&bq2419x->mutex); if (bq2419x->shutdown_complete) { mutex_unlock(&bq2419x->mutex); return -EIO; } ichg = bq2419x_val_to_reg(curr_val, BQ2419X_CHARGE_ICHG_OFFSET, 64, 6, 0); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_CHRG_CTRL_REG, BQ2419X_CHRG_CTRL_ICHG_MASK, ichg << 2); mutex_unlock(&bq2419x->mutex); return count; } static ssize_t bq2419x_show_output_charging_current_values(struct device *dev, struct device_attribute *attr, char *buf) { int i, ret = 0; for (i = 0; i <= 63; i++) ret += snprintf(buf + strlen(buf), MAX_STR_PRINT, "%d mA\n", i * 64 + BQ2419X_CHARGE_ICHG_OFFSET); return ret; } static DEVICE_ATTR(output_charging_current, (S_IRUGO | (S_IWUSR | S_IWGRP)), bq2419x_show_output_charging_current, bq2419x_set_output_charging_current); static DEVICE_ATTR(output_current_allowed_values, S_IRUGO, bq2419x_show_output_charging_current_values, NULL); static DEVICE_ATTR(input_charging_current_mA, (S_IRUGO | (S_IWUSR | S_IWGRP)), bq2419x_show_input_charging_current, bq2419x_set_input_charging_current); static DEVICE_ATTR(charging_state, (S_IRUGO | (S_IWUSR | S_IWGRP)), bq2419x_show_charging_state, bq2419x_set_charging_state); static DEVICE_ATTR(input_cable_state, (S_IRUGO | (S_IWUSR | S_IWGRP)), bq2419x_show_input_cable_state, bq2419x_set_input_cable_state); static struct attribute *bq2419x_attributes[] = { &dev_attr_output_charging_current.attr, &dev_attr_output_current_allowed_values.attr, &dev_attr_input_charging_current_mA.attr, &dev_attr_charging_state.attr, &dev_attr_input_cable_state.attr, NULL }; static const struct attribute_group bq2419x_attr_group = { .attrs = bq2419x_attributes, }; static int bq2419x_get_input_current_limit(struct battery_charger_dev *bc_dev) { struct bq2419x_chip *bq2419x = battery_charger_get_drvdata(bc_dev); if (!bq2419x->cable_connected) return 0; return bq2419x->in_current_limit; } static int bq2419x_charger_termination_configure( struct battery_charger_dev *bc_dev, int full_charge_state) { struct bq2419x_chip *bq2419x = battery_charger_get_drvdata(bc_dev); int ret; dev_err(bq2419x->dev, "Configure charge termination: %d\n", full_charge_state); ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, BQ2419x_EN_CHARGE_TERM, (full_charge_state ? BQ2419x_EN_CHARGE_TERM : 0)); if (ret < 0) { dev_err(bq2419x->dev, "TIME_CTRL update failed: %d\n", ret); return ret; } return 0; } static int bq2419x_charger_get_status(struct battery_charger_dev *bc_dev) { struct bq2419x_chip *bq2419x = battery_charger_get_drvdata(bc_dev); return bq2419x->chg_status; } static int bq2419x_charger_thermal_configure( struct battery_charger_dev *bc_dev, int temp, bool enable_charger, bool enable_charg_half_current, int battery_voltage) { struct bq2419x_chip *bq2419x = battery_charger_get_drvdata(bc_dev); struct bq2419x_charger_platform_data *chg_pdata; int fast_charge_current = 0; u32 charge_voltage_limit = 0; int ichg; int ret; int i; int curr_ichg, vreg; if (bq2419x->shutdown_complete) return 0; chg_pdata = bq2419x->charger_pdata; if (!bq2419x->cable_connected || !chg_pdata->n_temp_profile) return 0; if (bq2419x->last_temp == temp) return 0; bq2419x->last_temp = temp; dev_info(bq2419x->dev, "Battery temp %d\n", temp); if ((temp > BQ2419x_TEMP_H_CHG_DISABLE || temp < BQ2419x_TEMP_L_CHG_DISABLE) && !bq2419x->thermal_chg_disable) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_DISABLE_CHARGE); if (ret < 0) { dev_err(bq2419x->dev, "REG update failed, %d\n", ret); return ret; } dev_info(bq2419x->dev, "Thermal: Charging disabled\n"); bq2419x->thermal_chg_disable = true; } if ((temp <= BQ2419x_TEMP_H_CHG_DISABLE && temp >= BQ2419x_TEMP_L_CHG_DISABLE) && bq2419x->thermal_chg_disable) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_CHARGE); if (ret < 0) { dev_err(bq2419x->dev, "REG update failed, %d\n", ret); return ret; } dev_info(bq2419x->dev, "Thermal: Charging enabled\n"); bq2419x->thermal_chg_disable = false; } for (i = 0; i < chg_pdata->n_temp_profile; ++i) { if (temp <= chg_pdata->temp_range[i]) { fast_charge_current = chg_pdata->chg_current_limit[i]; if (chg_pdata->chg_thermal_voltage_limit) charge_voltage_limit = chg_pdata->chg_thermal_voltage_limit[i]; break; } } if (!fast_charge_current || !temp) { dev_info(bq2419x->dev, "Disable charging done by HW\n"); return 0; } /* Fast charger become 50% when temp is at < 10 degC */ if (temp <= 10) fast_charge_current *= 2; curr_ichg = bq2419x->chg_current_control.val >> 2; ichg = bq2419x_val_to_reg(fast_charge_current, BQ2419X_CHARGE_ICHG_OFFSET, 64, 6, 0); if (curr_ichg == ichg) return 0; bq2419x->chg_current_control.val = ichg << 2; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_CHRG_CTRL_REG, BQ2419X_CHRG_CTRL_ICHG_MASK, ichg << 2); if (ret < 0) { dev_err(bq2419x->dev, "CHRG_CTRL_REG update failed %d\n", ret); return ret; } if (!charge_voltage_limit) return 0; /* Charge voltage limit */ vreg = bq2419x_val_to_reg(charge_voltage_limit, BQ2419X_CHARGE_VOLTAGE_OFFSET, 16, 6, 1); bq2419x->chg_voltage_control.mask = BQ2419X_CHG_VOLT_LIMIT_MASK; bq2419x->chg_voltage_control.val = vreg << 2; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_VOLT_CTRL_REG, bq2419x->chg_voltage_control.mask, bq2419x->chg_voltage_control.val); if (ret < 0) { dev_err(bq2419x->dev, "VOLT_CTRL_REG update failed %d\n", ret); return ret; } return 0; } static struct battery_charging_ops bq2419x_charger_bci_ops = { .get_charging_status = bq2419x_charger_get_status, .charge_term_configure = bq2419x_charger_termination_configure, .get_input_current_limit = bq2419x_get_input_current_limit, .input_voltage_configure = bq2419x_charger_input_voltage_configure, .thermal_configure = bq2419x_charger_thermal_configure, }; static struct battery_charger_info bq2419x_charger_bci = { .cell_id = 0, .bc_ops = &bq2419x_charger_bci_ops, }; static struct bq2419x_platform_data *bq2419x_dt_parse(struct i2c_client *client, struct device_node **chg_np, struct device_node **vbus_np) { struct device_node *np = client->dev.of_node; struct bq2419x_platform_data *pdata; struct device_node *batt_reg_node; struct device_node *vbus_reg_node; int ret; pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); batt_reg_node = of_find_node_by_name(np, "charger"); if (batt_reg_node) { int auto_rechg_power_on_time; int count; int soc_range_len, inut_volt_lim_len = 0; int temp_range_len, chg_current_lim_len, chg_voltage_lim_len; int temp_polling_time; int wdt_timeout; struct bq2419x_charger_platform_data *chg_pdata; struct bq2419x_charger_platform_data *bcharger_pdata; u32 pval; if (!of_device_is_available(batt_reg_node)) { dev_info(&client->dev, "charger node status is disabled\n"); goto vbus_node; } pdata->bcharger_pdata = devm_kzalloc(&client->dev, sizeof(*(pdata->bcharger_pdata)), GFP_KERNEL); if (!pdata->bcharger_pdata) return ERR_PTR(-ENOMEM); bcharger_pdata = pdata->bcharger_pdata; chg_pdata = pdata->bcharger_pdata; chg_pdata->ridata = of_get_regulator_init_data(&client->dev, batt_reg_node, &bq2419x_chg_reg_desc); if (!chg_pdata->ridata) return ERR_PTR(-EINVAL); ret = of_property_read_u32(batt_reg_node, "ti,input-voltage-limit-millivolt", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,input-voltage-limit-mv", &pval); if (!ret) bcharger_pdata->input_voltage_limit_mV = pval; ret = of_property_read_u32(batt_reg_node, "ti,fast-charge-current-limit-milliamp", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,fast-charge-current-limit-ma", &pval); if (!ret) bcharger_pdata->fast_charge_current_limit_mA = pval; ret = of_property_read_u32(batt_reg_node, "ti,pre-charge-current-limit-milliamp", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,pre-charge-current-limit-ma", &pval); if (!ret) bcharger_pdata->pre_charge_current_limit_mA = pval; ret = of_property_read_u32(batt_reg_node, "ti,charge-term-current-limit-milliamp", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,charge-term-current-limit-ma", &pval); if (!ret) bcharger_pdata->termination_current_limit_mA = pval; ret = of_property_read_u32(batt_reg_node, "ti,ir-comp-resister-ohm", &pval); if (!ret) bcharger_pdata->ir_compensation_resister_ohm = pval; ret = of_property_read_u32(batt_reg_node, "ti,ir-comp-voltage-millivolt", &pval); if (!ret) bcharger_pdata->ir_compensation_voltage_mV = pval; ret = of_property_read_u32(batt_reg_node, "ti,thermal-regulation-threshold-degc", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,thermal-reg-threshold-degc", &pval); if (!ret) bcharger_pdata->thermal_regulation_threshold_degC = pval; ret = of_property_read_u32(batt_reg_node, "ti,charge-voltage-limit-millivolt", &pval); if (ret < 0) ret = of_property_read_u32(batt_reg_node, "ti,charge-voltage-limit-mv", &pval); if (!ret) pdata->bcharger_pdata->charge_voltage_limit_mV = pval; pdata->bcharger_pdata->disable_suspend_during_charging = of_property_read_bool(batt_reg_node, "ti,disbale-suspend-during-charging"); if (!pdata->bcharger_pdata->disable_suspend_during_charging) pdata->bcharger_pdata->disable_suspend_during_charging = of_property_read_bool(batt_reg_node, "ti,disable-suspend-on-charging"); ret = of_property_read_u32(batt_reg_node, "ti,auto-rechg-power-on-time", &auto_rechg_power_on_time); if (!ret) pdata->bcharger_pdata->auto_rechg_power_on_time = auto_rechg_power_on_time; else pdata->bcharger_pdata->auto_rechg_power_on_time = 25; ret = of_property_read_u32(batt_reg_node, "ti,watchdog-timeout", &wdt_timeout); if (!ret) pdata->bcharger_pdata->wdt_timeout = wdt_timeout; count = of_property_count_u32_elems(batt_reg_node, "ti,soc-range"); soc_range_len = (count > 0) ? count : 0; if (soc_range_len) { chg_pdata->n_soc_profile = soc_range_len; chg_pdata->soc_range = devm_kzalloc(&client->dev, sizeof(u32) * soc_range_len, GFP_KERNEL); if (!chg_pdata->soc_range) return ERR_PTR(-ENOMEM); ret = of_property_read_u32_array(batt_reg_node, "ti,soc-range", chg_pdata->soc_range, soc_range_len); if (ret < 0) return ERR_PTR(ret); count = of_property_count_u32_elems(batt_reg_node, "ti,input-voltage-soc-limit"); inut_volt_lim_len = (count > 0) ? count : 0; } if (inut_volt_lim_len) { chg_pdata->input_voltage_soc_limit = devm_kzalloc(&client->dev, sizeof(u32) * inut_volt_lim_len, GFP_KERNEL); if (!chg_pdata->input_voltage_soc_limit) return ERR_PTR(-ENOMEM); ret = of_property_read_u32_array(batt_reg_node, "ti,input-voltage-soc-limit", chg_pdata->input_voltage_soc_limit, inut_volt_lim_len); if (ret < 0) return ERR_PTR(ret); } ret = of_property_read_u32(batt_reg_node, "ti,temp-polling-time-sec", &temp_polling_time); if (!ret) bcharger_pdata->temp_polling_time_sec = temp_polling_time; chg_pdata->tz_name = of_get_property(batt_reg_node, "ti,thermal-zone", NULL); count = of_property_count_u32_elems(batt_reg_node, "ti,temp-range"); temp_range_len = (count > 0) ? count : 0; count = of_property_count_u32_elems(batt_reg_node, "ti,charge-current-limit"); if (count <= 0) count = of_property_count_u32_elems(batt_reg_node, "ti,charge-thermal-current-limit"); chg_current_lim_len = (count > 0) ? count : 0; count = of_property_count_u32_elems(batt_reg_node, "ti,charge-thermal-voltage-limit"); chg_voltage_lim_len = (count > 0) ? count : 0; if (!temp_range_len) goto vbus_node; if (temp_range_len != chg_current_lim_len) { dev_info(&client->dev, "current thermal profile is not correct\n"); goto vbus_node; } if (chg_voltage_lim_len && (temp_range_len != chg_voltage_lim_len)) { dev_info(&client->dev, "voltage thermal profile is not correct\n"); goto vbus_node; } chg_pdata->temp_range = devm_kzalloc(&client->dev, sizeof(u32) * temp_range_len, GFP_KERNEL); if (!chg_pdata->temp_range) return ERR_PTR(-ENOMEM); ret = of_property_read_u32_array(batt_reg_node, "ti,temp-range", chg_pdata->temp_range, temp_range_len); if (ret < 0) return ERR_PTR(ret); chg_pdata->chg_current_limit = devm_kzalloc(&client->dev, sizeof(u32) * temp_range_len, GFP_KERNEL); if (!chg_pdata->chg_current_limit) return ERR_PTR(-ENOMEM); ret = of_property_read_u32_array(batt_reg_node, "ti,charge-current-limit", chg_pdata->chg_current_limit, temp_range_len); if (ret < 0) ret = of_property_read_u32_array(batt_reg_node, "ti,charge-thermal-current-limit", chg_pdata->chg_current_limit, temp_range_len); if (ret < 0) return ERR_PTR(ret); if (!chg_voltage_lim_len) goto skip_thermal_volt_profle; chg_pdata->chg_thermal_voltage_limit = devm_kzalloc(&client->dev, sizeof(u32) * temp_range_len, GFP_KERNEL); if (!chg_pdata->chg_thermal_voltage_limit) return ERR_PTR(-ENOMEM); ret = of_property_read_u32_array(batt_reg_node, "ti,charge-thermal-voltage-limit", chg_pdata->chg_thermal_voltage_limit, temp_range_len); if (ret < 0) return ERR_PTR(ret); skip_thermal_volt_profle: chg_pdata->n_temp_profile = temp_range_len; } vbus_node: vbus_reg_node = of_find_node_by_name(np, "vbus"); if (vbus_reg_node) { pdata->vbus_pdata = devm_kzalloc(&client->dev, sizeof(*(pdata->vbus_pdata)), GFP_KERNEL); if (!pdata->vbus_pdata) return ERR_PTR(-ENOMEM); pdata->vbus_pdata->ridata = of_get_regulator_init_data( &client->dev, vbus_reg_node, &bq2419x_vbus_reg_desc); if (!pdata->vbus_pdata->ridata) return ERR_PTR(-EINVAL); pdata->vbus_pdata->gpio_otg_iusb = of_get_named_gpio(vbus_reg_node, "ti,otg-iusb-gpio", 0); } *chg_np = batt_reg_node; *vbus_np = vbus_reg_node; return pdata; } static int bq2419x_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct bq2419x_chip *bq2419x; struct bq2419x_platform_data *pdata = NULL; struct device_node *chg_np = NULL; struct device_node *vbus_np = NULL; int ret = 0; int val = 0; if (client->dev.platform_data) pdata = client->dev.platform_data; if (!pdata && client->dev.of_node) { pdata = bq2419x_dt_parse(client, &chg_np, &vbus_np); if (IS_ERR(pdata)) { ret = PTR_ERR(pdata); dev_err(&client->dev, "Parsing of node failed, %d\n", ret); return ret; } } if (!pdata) { dev_err(&client->dev, "No Platform data"); return -EINVAL; } bq2419x = devm_kzalloc(&client->dev, sizeof(*bq2419x), GFP_KERNEL); if (!bq2419x) return -ENOMEM; bq2419x->charger_pdata = pdata->bcharger_pdata; bq2419x->vbus_pdata = pdata->vbus_pdata; bq2419x->ext_name = pdata->ext_name; bq2419x->chg_np = chg_np; bq2419x->vbus_np = vbus_np; bq2419x->regmap = devm_regmap_init_i2c(client, &bq2419x_regmap_config); if (IS_ERR(bq2419x->regmap)) { ret = PTR_ERR(bq2419x->regmap); dev_err(&client->dev, "regmap init failed with err %d\n", ret); return ret; } bq2419x->dev = &client->dev; i2c_set_clientdata(client, bq2419x); bq2419x->irq = client->irq; mutex_init(&bq2419x->otg_mutex); bq2419x->is_otg_connected = 0; bq2419x->shutdown_complete = 0; bq2419x->wake_lock_released = false; bq2419x->charging_disabled_on_suspend = 0; ret = bq2419x_show_chip_version(bq2419x); if (ret < 0) { dev_err(&client->dev, "version read failed %d\n", ret); return ret; } ret = sysfs_create_group(&client->dev.kobj, &bq2419x_attr_group); if (ret < 0) { dev_err(&client->dev, "sysfs create failed %d\n", ret); return ret; } bq2419x_debugfs_init(client); bq2419x->edev.supported_cable = bq2419x_extcon_cable; bq2419x->edev.name = bq2419x->ext_name; bq2419x->edev.dev.parent = bq2419x->dev; ret = devm_extcon_dev_register(bq2419x->dev, &bq2419x->edev); if (ret < 0) { dev_err(bq2419x->dev, "failed to register extcon device\n"); return ret; } ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, &val); if (ret < 0) { dev_err(bq2419x->dev, "SYS_STAT_REG rd failed %d\n", ret); return ret; } ret = bq2419x_extcon_cable_update(bq2419x, val); mutex_init(&bq2419x->mutex); if (!pdata->bcharger_pdata) { dev_info(&client->dev, "No battery charger supported\n"); ret = bq2419x_watchdog_disable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "WDT disable failed: %d\n", ret); goto scrub_mutex; } /* disable safety timer */ ret = bq2419x_safetytimer_disable(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Safety timer disable failed: %d\n", ret); goto scrub_mutex; } /* disable chrg and bat fault interrupt */ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_MISC_OPER_REG, BQ2419x_CHRG_BAT_FAULT_MASK, BQ2419x_CHRG_BAT_FAULT_DISABLE); if (ret < 0) dev_err(bq2419x->dev, "Chrg and Bat fault interrupt disable failed %d\n", ret); ret = bq2419x_fault_clear_sts(bq2419x, NULL); if (ret < 0) { dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); goto scrub_mutex; } goto skip_bcharger_init; } bq2419x->battery_presense = true; bq2419x->disable_suspend_during_charging = pdata->bcharger_pdata->disable_suspend_during_charging; bq2419x->auto_rechg_power_on_time = pdata->bcharger_pdata->auto_rechg_power_on_time; bq2419x->wdt_time_sec = pdata->bcharger_pdata->wdt_timeout; bq2419x_process_charger_plat_data(bq2419x, pdata->bcharger_pdata); ret = bq2419x_charger_init(bq2419x); if (ret < 0) { dev_err(bq2419x->dev, "Charger init failed: %d\n", ret); goto scrub_mutex; } ret = bq2419x_init_charger_regulator(bq2419x, pdata); if (ret < 0) { dev_err(&client->dev, "Charger regualtor init failed %d\n", ret); goto scrub_mutex; } bq2419x_charger_bci.polling_time_sec = pdata->bcharger_pdata->temp_polling_time_sec; bq2419x_charger_bci.tz_name = pdata->bcharger_pdata->tz_name; bq2419x->bc_dev = battery_charger_register(bq2419x->dev, &bq2419x_charger_bci, bq2419x); if (IS_ERR(bq2419x->bc_dev)) { ret = PTR_ERR(bq2419x->bc_dev); dev_err(bq2419x->dev, "battery charger register failed: %d\n", ret); goto scrub_mutex; } ret = bq2419x_fault_clear_sts(bq2419x, NULL); if (ret < 0) { dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); goto scrub_wq; } INIT_DELAYED_WORK(&bq2419x->wdt_restart_wq, bq2419x_wdt_restart_wq); skip_bcharger_init: ret = devm_request_threaded_irq(bq2419x->dev, bq2419x->irq, NULL, bq2419x_irq, IRQF_ONESHOT | IRQF_TRIGGER_FALLING, dev_name(bq2419x->dev), bq2419x); if (ret < 0) { dev_warn(bq2419x->dev, "request IRQ %d fail, err = %d\n", bq2419x->irq, ret); dev_info(bq2419x->dev, "Supporting bq driver without interrupt\n"); ret = 0; } device_set_wakeup_capable(bq2419x->dev, true); device_wakeup_enable(bq2419x->dev); ret = bq2419x_init_vbus_regulator(bq2419x, pdata); if (ret < 0) { dev_err(&client->dev, "VBUS regulator init failed %d\n", ret); goto scrub_wq; } /* enable charging */ ret = bq2419x_charger_enable(bq2419x); if (ret < 0) goto scrub_wq; if (bq2419x->battery_presense) { bq2419x->thermal_init_retry = 10; #ifdef CONFIG_THERMAL INIT_DELAYED_WORK(&bq2419x->thermal_init_work, bq2419x_thermal_init_work); bq2419x_thermal_init_work(&bq2419x->thermal_init_work.work); #endif } INIT_DELAYED_WORK(&bq2419x->otg_reset_work, bq2419x_otg_reset_work_handler); return 0; scrub_wq: if (pdata->bcharger_pdata) battery_charger_unregister(bq2419x->bc_dev); scrub_mutex: extcon_dev_unregister(&bq2419x->edev); mutex_destroy(&bq2419x->mutex); mutex_destroy(&bq2419x->otg_mutex); return ret; } static int bq2419x_remove(struct i2c_client *client) { struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); if (bq2419x->battery_presense) battery_charger_unregister(bq2419x->bc_dev); if (bq2419x->is_otg_connected) { cancel_delayed_work_sync(&bq2419x->otg_reset_work); bq2419x_vbus_disable(bq2419x->vbus_rdev); } mutex_destroy(&bq2419x->mutex); mutex_destroy(&bq2419x->otg_mutex); cancel_delayed_work_sync(&bq2419x->thermal_init_work); return 0; } static void bq2419x_shutdown(struct i2c_client *client) { struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); struct device *dev = &client->dev; int ret; int next_poweron_time = 0; mutex_lock(&bq2419x->mutex); bq2419x->shutdown_complete = 1; mutex_unlock(&bq2419x->mutex); if (bq2419x->is_otg_connected) { cancel_delayed_work_sync(&bq2419x->otg_reset_work); bq2419x_vbus_disable(bq2419x->vbus_rdev); } if (!bq2419x->battery_presense) return; if (!bq2419x->cable_connected) goto end; /* Clear JEITA_VSET bit if cable connected while device off */ ret = regmap_update_bits(bq2419x->regmap, BQ2419X_MISC_OPER_REG, BQ2419X_JEITA_VSET_MASK, 0); if (ret < 0) dev_err(bq2419x->dev, "JEITA_VSET update failed: %d\n", ret); battery_charger_thermal_stop_monitoring(bq2419x->bc_dev); if (bq2419x->chg_status == BATTERY_CHARGING_DONE) { dev_info(bq2419x->dev, "Battery charging done\n"); goto end; } if (bq2419x->in_current_limit <= 500) dev_info(bq2419x->dev, "Battery charging with 500mA\n"); else { dev_info(bq2419x->dev, "Battery charging with high current\n"); next_poweron_time = bq2419x->auto_rechg_power_on_time; } if (next_poweron_time) { ret = battery_charging_system_reset_after(bq2419x->bc_dev, next_poweron_time); if (ret < 0) dev_err(dev, "System poweron after %d config failed %d\n", next_poweron_time, ret); } end: if (next_poweron_time) dev_info(dev, "System-charger will power-ON after %d sec\n", next_poweron_time); else dev_info(bq2419x->dev, "System-charger will not power-ON\n"); battery_charging_system_power_on_usb_event(bq2419x->bc_dev); cancel_delayed_work_sync(&bq2419x->thermal_init_work); } #ifdef CONFIG_PM_SLEEP static int bq2419x_suspend(struct device *dev) { struct bq2419x_chip *bq2419x = dev_get_drvdata(dev); int ret = 0; int battery_soc = 0; if (device_may_wakeup(bq2419x->dev) && (bq2419x->irq > 0)) enable_irq_wake(bq2419x->irq); if (!bq2419x->battery_presense) return 0; mutex_lock(&bq2419x->mutex); if (bq2419x->is_otg_connected) { ret = bq2419x_reset_wdt(bq2419x, "Suspend"); if (ret < 0) dev_err(bq2419x->dev, "Reset WDT failed: %d\n", ret); } mutex_unlock(&bq2419x->mutex); battery_soc = battery_gauge_get_battery_soc(bq2419x->bc_dev); if (battery_soc > BQ2419X_PC_USB_LP0_THRESHOLD && (bq2419x->in_current_limit <= 500) && !bq2419x->is_otg_connected) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_DISABLE_CHARGE); if (ret < 0) dev_err(bq2419x->dev, "REG update failed: err %d\n", ret); else bq2419x->charging_disabled_on_suspend = true; } if (!bq2419x->cable_connected) goto end; if (bq2419x->chg_status == BATTERY_CHARGING_DONE) { dev_info(bq2419x->dev, "Battery charging done\n"); goto end; } if (bq2419x->in_current_limit <= 500) { dev_info(bq2419x->dev, "Battery charging with 500mA\n"); ret = bq2419x_set_charging_current_suspend(bq2419x, 500); if (ret < 0) dev_err(bq2419x->dev, "Config of charging failed: %d\n", ret); if (battery_soc > BQ2419X_PC_USB_LP0_THRESHOLD && !bq2419x->is_otg_connected) { ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_DISABLE_CHARGE); if (ret < 0) dev_err(bq2419x->dev, "REG update failed: err %d\n", ret); else bq2419x->charging_disabled_on_suspend = true; } } else dev_info(bq2419x->dev, "Battery charging with high current\n"); end: return ret; } static int bq2419x_resume(struct device *dev) { int ret = 0; struct bq2419x_chip *bq2419x = dev_get_drvdata(dev); unsigned int val; if (device_may_wakeup(bq2419x->dev) && (bq2419x->irq > 0)) disable_irq_wake(bq2419x->irq); if (bq2419x->charging_disabled_on_suspend) { bq2419x->charging_disabled_on_suspend = false; ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, BQ2419X_ENABLE_CHARGE_MASK, BQ2419X_ENABLE_CHARGE); if (ret < 0) dev_err(bq2419x->dev, "register update failed: err %d\n", ret); } ret = regmap_read(bq2419x->regmap, BQ2419X_SYS_STAT_REG, &val); if (ret < 0) { dev_err(bq2419x->dev, "SYS_STAT_REG read failed %d\n", ret); return IRQ_HANDLED; } if ((val & BQ2419x_VBUS_PG_STAT) == BQ2419x_PG_VBUS_USB) { extcon_set_cable_state_(&bq2419x->edev, bq2419x_extcon_cable[0], true); if (!bq2419x->cable_connected) dev_info(bq2419x->dev, "USB is connected\n"); } else if ((val & BQ2419x_VBUS_PG_STAT) == BQ2419x_VBUS_UNKNOWN) { extcon_set_cable_state_(&bq2419x->edev, bq2419x_extcon_cable[0], false); if (bq2419x->cable_connected) dev_info(bq2419x->dev, "USB is disconnected\n"); } if (!bq2419x->battery_presense) return 0; ret = bq2419x_fault_clear_sts(bq2419x, &val); if (ret < 0) { dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); return ret; } if (val & BQ2419x_FAULT_WATCHDOG_FAULT) { bq_chg_err(bq2419x, "Watchdog Timer Expired\n"); ret = bq2419x_reconfigure_charger_param(bq2419x, "WDT-EXP-RESUME"); if (ret < 0) dev_err(bq2419x->dev, "BQ reconfig failed %d\n", ret); } return 0; }; #endif static const struct dev_pm_ops bq2419x_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(bq2419x_suspend, bq2419x_resume) }; static const struct i2c_device_id bq2419x_id[] = { {.name = "bq2419x",}, {}, }; MODULE_DEVICE_TABLE(i2c, bq2419x_id); static struct i2c_driver bq2419x_i2c_driver = { .driver = { .name = "bq2419x", .owner = THIS_MODULE, .pm = &bq2419x_pm_ops, }, .probe = bq2419x_probe, .remove = bq2419x_remove, .shutdown = bq2419x_shutdown, .id_table = bq2419x_id, }; static int __init bq2419x_module_init(void) { return i2c_add_driver(&bq2419x_i2c_driver); } subsys_initcall(bq2419x_module_init); static void __exit bq2419x_cleanup(void) { i2c_del_driver(&bq2419x_i2c_driver); } module_exit(bq2419x_cleanup); MODULE_DESCRIPTION("BQ24190/BQ24192/BQ24192i/BQ24193 battery charger driver"); MODULE_AUTHOR("Laxman Dewangan "); MODULE_AUTHOR("Syed Rafiuddin