| /* |
| * bq2419x-charger.c -- BQ24190/BQ24192/BQ24192i/BQ24193 Charger driver |
| * |
| * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. |
| * |
| * Author: Laxman Dewangan <ldewangan@nvidia.com> |
| * Author: Syed Rafiuddin <srafiuddin@nvidia.com> |
| * |
| * 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 <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/kthread.h> |
| #include <linux/sched.h> |
| #include <linux/sched/rt.h> |
| #include <linux/time.h> |
| #include <linux/timer.h> |
| #include <linux/gpio.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/power/bq2419x-charger.h> |
| #include <linux/regmap.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/slab.h> |
| #include <linux/rtc.h> |
| #include <linux/alarmtimer.h> |
| #include <linux/power/battery-charger-gauge-comm.h> |
| |
| /* input current limit */ |
| static const unsigned int iinlim[] = { |
| 100, 150, 500, 900, 1200, 1500, 2000, 3000, |
| }; |
| |
| /* Kthread scheduling parameters */ |
| struct sched_param bq2419x_param = { |
| .sched_priority = MAX_RT_PRIO - 1, |
| }; |
| |
| static const struct regmap_config bq2419x_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = BQ2419X_MAX_REGS, |
| }; |
| |
| struct bq2419x_chip { |
| struct device *dev; |
| struct regmap *regmap; |
| int irq; |
| int gpio_otg_iusb; |
| int wdt_refresh_timeout; |
| int wdt_time_sec; |
| |
| struct mutex mutex; |
| int in_current_limit; |
| int rtc_alarm_time; |
| |
| struct regulator_dev *chg_rdev; |
| struct regulator_desc chg_reg_desc; |
| struct regulator_init_data chg_reg_init_data; |
| |
| struct regulator_dev *vbus_rdev; |
| struct regulator_desc vbus_reg_desc; |
| struct regulator_init_data vbus_reg_init_data; |
| |
| struct battery_charger_dev *bc_dev; |
| int chg_status; |
| |
| struct kthread_worker bq_kworker; |
| struct task_struct *bq_kworker_task; |
| struct kthread_work bq_wdt_work; |
| struct rtc_device *rtc; |
| int stop_thread; |
| int suspended; |
| int chg_restart_timeout; |
| int chg_restart_time; |
| int battery_presense; |
| }; |
| |
| 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"); |
| ret = regmap_update_bits(bq2419x->regmap, BQ2419X_PWR_ON_REG, |
| BQ2419X_ENABLE_CHARGE_MASK, 0); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, |
| "register update failed, err %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_vbus_regulator_enable_time(struct regulator_dev *rdev) |
| { |
| return 500000; |
| } |
| |
| 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"); |
| |
| 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); |
| |
| 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"); |
| ret = bq2419x_charger_enable(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Charger enable failed %d", ret); |
| return ret; |
| } |
| |
| 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, |
| .enable_time = bq2419x_vbus_regulator_enable_time, |
| }; |
| |
| static int bq2419x_init(struct bq2419x_chip *bq2419x) |
| { |
| int val = 0; |
| int ret = 0; |
| int floor = 0; |
| |
| /* Configure input voltage to 4.52 in case of NV charger */ |
| if (bq2419x->in_current_limit == 2000) |
| val |= BQ2419x_NVCHARGER_INPUT_VOL_SEL; |
| else |
| val |= BQ2419x_DEFAULT_INPUT_VOL_SEL; |
| |
| /* Clear EN_HIZ */ |
| ret = regmap_update_bits(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, |
| BQ2419X_EN_HIZ | BQ2419x_INPUT_VOLTAGE_MASK, val); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "INPUT_SRC_REG update failed %d\n", ret); |
| return ret; |
| } |
| |
| /* Configure input current limit */ |
| val = current_to_reg(iinlim, ARRAY_SIZE(iinlim), |
| bq2419x->in_current_limit); |
| |
| /* Start from 500mA and then step to val */ |
| floor = current_to_reg(iinlim, ARRAY_SIZE(iinlim), 500); |
| if (val < 0 || floor < 0) |
| return 0; |
| |
| for (; floor <= val; floor++) { |
| udelay(BQ2419x_CHARGING_CURRENT_STEP_DELAY_US); |
| 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); |
| } |
| return ret; |
| } |
| |
| static int bq2419x_charger_init(struct bq2419x_chip *bq2419x) |
| { |
| int ret; |
| |
| /* Configure Output Current Control to 2.25A*/ |
| ret = regmap_write(bq2419x->regmap, BQ2419X_CHRG_CTRL_REG, 0x6c); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "CHRG_CTRL_REG write failed %d\n", ret); |
| return ret; |
| } |
| |
| /* |
| * Configure Input voltage limit reset to OTP value, |
| * and charging current to 500mA. |
| */ |
| ret = regmap_write(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, 0x32); |
| if (ret < 0) |
| dev_err(bq2419x->dev, "INPUT_SRC_REG write failed %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int bq2419x_set_charging_current(struct regulator_dev *rdev, |
| int min_uA, int max_uA) |
| { |
| struct bq2419x_chip *bq_charger = rdev_get_drvdata(rdev); |
| int ret = 0; |
| int val; |
| |
| dev_info(bq_charger->dev, "Setting charging current %d\n", max_uA/1000); |
| msleep(200); |
| bq_charger->chg_status = BATTERY_DISCHARGING; |
| |
| ret = bq2419x_charger_enable(bq_charger); |
| if (ret < 0) { |
| dev_err(bq_charger->dev, "Charger enable failed %d", ret); |
| return ret; |
| } |
| |
| ret = regmap_read(bq_charger->regmap, BQ2419X_SYS_STAT_REG, &val); |
| if (ret < 0) |
| dev_err(bq_charger->dev, "error reading reg: 0x%x\n", |
| BQ2419X_SYS_STAT_REG); |
| |
| if (max_uA == 0 && val != 0) |
| return ret; |
| |
| bq_charger->in_current_limit = max_uA/1000; |
| if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_UNKNOWN) { |
| bq_charger->in_current_limit = 500; |
| bq_charger->chg_status = BATTERY_DISCHARGING; |
| } else { |
| bq_charger->chg_status = BATTERY_CHARGING; |
| } |
| ret = bq2419x_init(bq_charger); |
| if (ret < 0) |
| goto error; |
| battery_charging_status_update(bq_charger->bc_dev, |
| bq_charger->chg_status); |
| return 0; |
| error: |
| dev_err(bq_charger->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_reset_wdt(struct bq2419x_chip *bq2419x, const char *from) |
| { |
| int ret = 0; |
| unsigned int reg01; |
| |
| mutex_lock(&bq2419x->mutex); |
| if (bq2419x->suspended) |
| goto scrub; |
| |
| dev_info(bq2419x->dev, "%s() from %s()\n", __func__, from); |
| |
| /* Clear EN_HIZ */ |
| 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); |
| goto scrub; |
| } |
| |
| ret = regmap_read(bq2419x->regmap, BQ2419X_PWR_ON_REG, ®01); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "PWR_ON_REG read failed: %d\n", ret); |
| goto scrub; |
| } |
| |
| reg01 |= BIT(6); |
| |
| /* Write two times to make sure reset WDT */ |
| ret = regmap_write(bq2419x->regmap, BQ2419X_PWR_ON_REG, reg01); |
| 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, reg01); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "PWR_ON_REG write failed: %d\n", ret); |
| goto scrub; |
| } |
| |
| scrub: |
| mutex_unlock(&bq2419x->mutex); |
| return ret; |
| } |
| |
| static int bq2419x_fault_clear_sts(struct bq2419x_chip *bq2419x) |
| { |
| int ret; |
| unsigned int reg09; |
| |
| ret = regmap_read(bq2419x->regmap, BQ2419X_FAULT_REG, ®09); |
| 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); |
| if (ret < 0) |
| dev_err(bq2419x->dev, "FAULT_REG read failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int bq2419x_watchdog_init(struct bq2419x_chip *bq2419x, |
| int timeout, const char *from) |
| { |
| int ret, val; |
| unsigned int reg05; |
| |
| if (!timeout) { |
| 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); |
| return ret; |
| } |
| |
| if (timeout <= 60) { |
| val = BQ2419X_WD_40ms; |
| bq2419x->wdt_refresh_timeout = 25; |
| } else if (timeout <= 120) { |
| val = BQ2419X_WD_80ms; |
| bq2419x->wdt_refresh_timeout = 50; |
| } else { |
| val = BQ2419X_WD_160ms; |
| bq2419x->wdt_refresh_timeout = 125; |
| } |
| |
| ret = regmap_read(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, ®05); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, |
| "TIME_CTRL_REG read failed:%d\n", ret); |
| return ret; |
| } |
| |
| if ((reg05 & BQ2419X_WD_MASK) != val) { |
| ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, |
| BQ2419X_WD_MASK, 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; |
| } |
| |
| static void bq2419x_work_thread(struct kthread_work *work) |
| { |
| struct bq2419x_chip *bq2419x = container_of(work, |
| struct bq2419x_chip, bq_wdt_work); |
| int ret; |
| int val = 0; |
| |
| for (;;) { |
| if (bq2419x->stop_thread) |
| return; |
| |
| if (bq2419x->chg_restart_timeout) { |
| mutex_lock(&bq2419x->mutex); |
| bq2419x->chg_restart_timeout--; |
| if (!bq2419x->chg_restart_timeout) { |
| ret = bq2419x_charger_enable(bq2419x); |
| if (ret < 0) |
| dev_err(bq2419x->dev, |
| "Charger enable failed %d", 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); |
| /* |
| * Update Charging status based on STAT register |
| */ |
| if ((val & BQ2419x_CHRG_STATE_MASK) == |
| BQ2419x_CHRG_STATE_NOTCHARGING) { |
| bq2419x->chg_status = BATTERY_DISCHARGING; |
| battery_charging_status_update(bq2419x->bc_dev, |
| bq2419x->chg_status); |
| |
| bq2419x->chg_restart_timeout = |
| bq2419x->chg_restart_time / |
| bq2419x->wdt_refresh_timeout; |
| } else { |
| bq2419x->chg_status = BATTERY_CHARGING; |
| battery_charging_status_update(bq2419x->bc_dev, |
| bq2419x->chg_status); |
| } |
| |
| } |
| |
| if (bq2419x->suspended) |
| bq2419x->chg_restart_timeout = 0; |
| |
| mutex_unlock(&bq2419x->mutex); |
| } |
| |
| ret = bq2419x_reset_wdt(bq2419x, "THREAD"); |
| if (ret < 0) |
| dev_err(bq2419x->dev, |
| "bq2419x_reset_wdt failed: %d\n", ret); |
| |
| msleep(bq2419x->wdt_refresh_timeout * 1000); |
| } |
| } |
| |
| static int bq2419x_reset_safety_timer(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_REG update failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = regmap_update_bits(bq2419x->regmap, BQ2419X_TIME_CTRL_REG, |
| BQ2419X_EN_SFT_TIMER_MASK, BQ2419X_EN_SFT_TIMER_MASK); |
| if (ret < 0) |
| dev_err(bq2419x->dev, |
| "TIME_CTRL_REG update failed: %d\n", ret); |
| return ret; |
| } |
| |
| static irqreturn_t bq2419x_irq(int irq, void *data) |
| { |
| struct bq2419x_chip *bq2419x = data; |
| int ret; |
| unsigned int val; |
| int check_chg_state = 0; |
| |
| ret = regmap_read(bq2419x->regmap, BQ2419X_FAULT_REG, &val); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "FAULT_REG read failed %d\n", ret); |
| return ret; |
| } |
| |
| dev_info(bq2419x->dev, "%s() Irq %d status 0x%02x\n", |
| __func__, irq, val); |
| |
| if (val & BQ2419x_FAULT_BOOST_FAULT) |
| dev_err(bq2419x->dev, "Charging Fault: VBUS Overloaded\n"); |
| |
| if (!bq2419x->battery_presense) |
| return IRQ_HANDLED; |
| |
| if (val & BQ2419x_FAULT_WATCHDOG_FAULT) { |
| dev_err(bq2419x->dev, |
| "Charging Fault: Watchdog Timer Expired\n"); |
| ret = bq2419x_watchdog_init(bq2419x, bq2419x->wdt_time_sec, |
| "ISR"); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "BQWDT init failed %d\n", ret); |
| return ret; |
| } |
| |
| ret = bq2419x_charger_init(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Charger init failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = bq2419x_init(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "bq2419x init failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| switch (val & BQ2419x_FAULT_CHRG_FAULT_MASK) { |
| case BQ2419x_FAULT_CHRG_INPUT: |
| dev_err(bq2419x->dev, "Charging Fault: " |
| "Input Fault (VBUS OVP or VBAT<VBUS<3.8V)\n"); |
| break; |
| case BQ2419x_FAULT_CHRG_THERMAL: |
| dev_err(bq2419x->dev, "Charging Fault: Thermal shutdown\n"); |
| check_chg_state = 1; |
| break; |
| case BQ2419x_FAULT_CHRG_SAFTY: |
| dev_err(bq2419x->dev, |
| "Charging Fault: Safety timer expiration\n"); |
| bq2419x->chg_restart_timeout = bq2419x->chg_restart_time / |
| bq2419x->wdt_refresh_timeout; |
| ret = bq2419x_reset_safety_timer(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Reset safety timer failed %d\n", |
| ret); |
| return ret; |
| } |
| |
| check_chg_state = 1; |
| break; |
| default: |
| break; |
| } |
| |
| if (val & BQ2419x_FAULT_NTC_FAULT) { |
| dev_err(bq2419x->dev, "Charging Fault: NTC fault %d\n", |
| val & BQ2419x_FAULT_NTC_FAULT); |
| check_chg_state = 1; |
| } |
| |
| ret = bq2419x_fault_clear_sts(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "fault clear status failed %d\n", 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); |
| return ret; |
| } |
| |
| if ((val & BQ2419x_CHRG_STATE_MASK) == |
| BQ2419x_CHRG_STATE_CHARGE_DONE) { |
| bq2419x->chg_restart_timeout = bq2419x->chg_restart_time / |
| bq2419x->wdt_refresh_timeout; |
| dev_info(bq2419x->dev, "Charging completed\n"); |
| bq2419x->chg_status = BATTERY_CHARGING_DONE; |
| battery_charging_status_update(bq2419x->bc_dev, |
| bq2419x->chg_status); |
| } |
| |
| /* |
| * Update Charging status based on STAT register |
| */ |
| if (check_chg_state) { |
| if ((val & BQ2419x_CHRG_STATE_MASK) == |
| BQ2419x_CHRG_STATE_NOTCHARGING) { |
| bq2419x->chg_status = BATTERY_DISCHARGING; |
| battery_charging_status_update(bq2419x->bc_dev, |
| bq2419x->chg_status); |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| 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.name = "bq2419x-charger"; |
| bq2419x->chg_reg_desc.ops = &bq2419x_tegra_regulator_ops; |
| bq2419x->chg_reg_desc.type = REGULATOR_CURRENT; |
| bq2419x->chg_reg_desc.owner = THIS_MODULE; |
| |
| bq2419x->chg_reg_init_data.supply_regulator = NULL; |
| bq2419x->chg_reg_init_data.regulator_init = NULL; |
| bq2419x->chg_reg_init_data.num_consumer_supplies = |
| pdata->bcharger_pdata->num_consumer_supplies; |
| bq2419x->chg_reg_init_data.consumer_supplies = |
| pdata->bcharger_pdata->consumer_supplies; |
| bq2419x->chg_reg_init_data.driver_data = bq2419x; |
| bq2419x->chg_reg_init_data.constraints.name = "bq2419x-charger"; |
| bq2419x->chg_reg_init_data.constraints.min_uA = 0; |
| bq2419x->chg_reg_init_data.constraints.max_uA = |
| pdata->bcharger_pdata->max_charge_current_mA * 1000; |
| |
| bq2419x->chg_reg_init_data.constraints.valid_modes_mask = |
| REGULATOR_MODE_NORMAL | |
| REGULATOR_MODE_STANDBY; |
| |
| bq2419x->chg_reg_init_data.constraints.valid_ops_mask = |
| REGULATOR_CHANGE_MODE | |
| REGULATOR_CHANGE_STATUS | |
| REGULATOR_CHANGE_CURRENT; |
| |
| rconfig.dev = bq2419x->dev; |
| rconfig.of_node = NULL; |
| rconfig.init_data = &bq2419x->chg_reg_init_data; |
| rconfig.driver_data = bq2419x; |
| bq2419x->chg_rdev = regulator_register(&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.name = "bq2419x-vbus"; |
| bq2419x->vbus_reg_desc.ops = &bq2419x_vbus_ops; |
| bq2419x->vbus_reg_desc.type = REGULATOR_VOLTAGE; |
| bq2419x->vbus_reg_desc.owner = THIS_MODULE; |
| |
| bq2419x->vbus_reg_init_data.supply_regulator = NULL; |
| bq2419x->vbus_reg_init_data.regulator_init = NULL; |
| bq2419x->vbus_reg_init_data.num_consumer_supplies = |
| pdata->vbus_pdata->num_consumer_supplies; |
| bq2419x->vbus_reg_init_data.consumer_supplies = |
| pdata->vbus_pdata->consumer_supplies; |
| bq2419x->vbus_reg_init_data.driver_data = bq2419x; |
| |
| bq2419x->vbus_reg_init_data.constraints.name = "bq2419x-vbus"; |
| bq2419x->vbus_reg_init_data.constraints.min_uV = 0; |
| bq2419x->vbus_reg_init_data.constraints.max_uV = 5000000, |
| bq2419x->vbus_reg_init_data.constraints.valid_modes_mask = |
| REGULATOR_MODE_NORMAL | |
| REGULATOR_MODE_STANDBY; |
| bq2419x->vbus_reg_init_data.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 = NULL; |
| rconfig.init_data = &bq2419x->vbus_reg_init_data; |
| rconfig.driver_data = bq2419x; |
| bq2419x->vbus_rdev = regulator_register(&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_reg; |
| } |
| return ret; |
| |
| scrub_reg: |
| regulator_unregister(bq2419x->vbus_rdev); |
| bq2419x->vbus_rdev = NULL; |
| scrub: |
| if (gpio_is_valid(bq2419x->gpio_otg_iusb)) |
| gpio_free(bq2419x->gpio_otg_iusb); |
| return ret; |
| } |
| |
| 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 & BQ24190_IC_VER) == BQ24190_IC_VER) |
| dev_info(bq2419x->dev, "chip type BQ24190 detected\n"); |
| else if ((val & BQ24192_IC_VER) == BQ24192_IC_VER) |
| dev_info(bq2419x->dev, "chip type BQ2419X/3 detected\n"); |
| else if ((val & BQ24192i_IC_VER) == BQ24192i_IC_VER) |
| dev_info(bq2419x->dev, "chip type BQ2419Xi detected\n"); |
| return 0; |
| } |
| |
| static int bq2419x_wakealarm(struct bq2419x_chip *bq2419x, int time_sec) |
| { |
| int ret; |
| unsigned long now; |
| struct rtc_wkalrm alm; |
| int alarm_time = time_sec; |
| |
| if (!alarm_time) |
| return 0; |
| |
| alm.enabled = true; |
| ret = rtc_read_time(bq2419x->rtc, &alm.time); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "RTC read time failed %d\n", ret); |
| return ret; |
| } |
| rtc_tm_to_time(&alm.time, &now); |
| |
| rtc_time_to_tm(now + alarm_time, &alm.time); |
| ret = rtc_set_alarm(bq2419x->rtc, &alm); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "RTC set alarm failed %d\n", ret); |
| alm.enabled = false; |
| return ret; |
| } |
| alm.enabled = false; |
| 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 struct battery_charging_ops bq2419x_charger_bci_ops = { |
| .get_charging_status = bq2419x_charger_get_status, |
| }; |
| |
| static struct battery_charger_info bq2419x_charger_bci = { |
| .cell_id = 0, |
| .bc_ops = &bq2419x_charger_bci_ops, |
| }; |
| |
| static int bq2419x_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct bq2419x_chip *bq2419x; |
| struct bq2419x_platform_data *pdata; |
| int ret = 0; |
| |
| pdata = client->dev.platform_data; |
| if (!pdata) { |
| dev_err(&client->dev, "No Platform data"); |
| return -EINVAL; |
| } |
| |
| bq2419x = devm_kzalloc(&client->dev, sizeof(*bq2419x), GFP_KERNEL); |
| if (!bq2419x) { |
| dev_err(&client->dev, "Memory allocation failed\n"); |
| return -ENOMEM; |
| } |
| |
| 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; |
| |
| if (pdata->bcharger_pdata) { |
| bq2419x->rtc_alarm_time = pdata->bcharger_pdata->rtc_alarm_time; |
| bq2419x->wdt_time_sec = pdata->bcharger_pdata->wdt_timeout; |
| bq2419x->chg_restart_time = |
| pdata->bcharger_pdata->chg_restart_time; |
| bq2419x->battery_presense = true; |
| } |
| |
| bq2419x->wdt_refresh_timeout = 25; |
| i2c_set_clientdata(client, bq2419x); |
| bq2419x->irq = client->irq; |
| |
| if (bq2419x->rtc_alarm_time) |
| bq2419x->rtc = alarmtimer_get_rtcdev(); |
| |
| mutex_init(&bq2419x->mutex); |
| bq2419x->suspended = 0; |
| bq2419x->chg_restart_timeout = 0; |
| |
| ret = bq2419x_show_chip_version(bq2419x); |
| if (ret < 0) { |
| dev_err(&client->dev, "version read failed %d\n", ret); |
| goto scrub_mutex; |
| } |
| |
| ret = bq2419x_init_vbus_regulator(bq2419x, pdata); |
| if (ret < 0) { |
| dev_err(&client->dev, "VBUS regulator init failed %d\n", ret); |
| goto scrub_mutex; |
| } |
| |
| if (!pdata->bcharger_pdata) { |
| dev_info(&client->dev, "No battery charger supported\n"); |
| ret = bq2419x_watchdog_init(bq2419x, 0, "PROBE"); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "WDT disable failed: %d\n", ret); |
| goto scrub_vbus_reg; |
| } |
| goto skip_bcharger_init; |
| } |
| |
| ret = bq2419x_charger_init(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Charger init failed: %d\n", ret); |
| goto scrub_vbus_reg; |
| } |
| |
| ret = bq2419x_init_charger_regulator(bq2419x, pdata); |
| if (ret < 0) { |
| dev_err(&client->dev, |
| "Charger regualtor init failed %d\n", ret); |
| goto scrub_vbus_reg; |
| } |
| |
| 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_chg_reg; |
| } |
| init_kthread_worker(&bq2419x->bq_kworker); |
| bq2419x->bq_kworker_task = kthread_run(kthread_worker_fn, |
| &bq2419x->bq_kworker, |
| dev_name(bq2419x->dev)); |
| if (IS_ERR(bq2419x->bq_kworker_task)) { |
| ret = PTR_ERR(bq2419x->bq_kworker_task); |
| dev_err(&client->dev, "Kworker task creation failed %d\n", ret); |
| goto scrub_bchg_reg; |
| } |
| |
| init_kthread_work(&bq2419x->bq_wdt_work, bq2419x_work_thread); |
| sched_setscheduler(bq2419x->bq_kworker_task, |
| SCHED_FIFO, &bq2419x_param); |
| queue_kthread_work(&bq2419x->bq_kworker, &bq2419x->bq_wdt_work); |
| |
| ret = bq2419x_watchdog_init(bq2419x, bq2419x->wdt_time_sec, "PROBE"); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "BQWDT init failed %d\n", ret); |
| goto scrub_kthread; |
| } |
| |
| skip_bcharger_init: |
| ret = bq2419x_fault_clear_sts(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); |
| goto scrub_kthread; |
| } |
| |
| ret = request_threaded_irq(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 intierrupt\n"); |
| ret = 0; |
| } |
| |
| if (!pdata->bcharger_pdata) { |
| /* enable charging */ |
| ret = bq2419x_charger_enable(bq2419x); |
| if (ret < 0) |
| goto scrub_irq; |
| } |
| |
| return 0; |
| scrub_irq: |
| if (bq2419x->irq) |
| free_irq(bq2419x->irq, bq2419x); |
| scrub_kthread: |
| if (pdata->bcharger_pdata) { |
| bq2419x->stop_thread = true; |
| flush_kthread_worker(&bq2419x->bq_kworker); |
| kthread_stop(bq2419x->bq_kworker_task); |
| } |
| scrub_bchg_reg: |
| if (pdata->bcharger_pdata) |
| battery_charger_unregister(bq2419x->bc_dev); |
| scrub_chg_reg: |
| if (pdata->bcharger_pdata) |
| regulator_unregister(bq2419x->chg_rdev); |
| scrub_vbus_reg: |
| regulator_unregister(bq2419x->vbus_rdev); |
| scrub_mutex: |
| mutex_destroy(&bq2419x->mutex); |
| return ret; |
| } |
| |
| static int bq2419x_remove(struct i2c_client *client) |
| { |
| struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); |
| |
| if (bq2419x->irq) |
| free_irq(bq2419x->irq, bq2419x); |
| if (bq2419x->battery_presense) { |
| battery_charger_unregister(bq2419x->bc_dev); |
| regulator_unregister(bq2419x->chg_rdev); |
| bq2419x->stop_thread = true; |
| flush_kthread_worker(&bq2419x->bq_kworker); |
| kthread_stop(bq2419x->bq_kworker_task); |
| } |
| regulator_unregister(bq2419x->vbus_rdev); |
| mutex_destroy(&bq2419x->mutex); |
| return 0; |
| } |
| |
| static void bq2419x_shutdown(struct i2c_client *client) |
| { |
| int ret = 0; |
| struct bq2419x_chip *bq2419x = i2c_get_clientdata(client); |
| int alarm_time = bq2419x->rtc_alarm_time; |
| |
| if (!bq2419x->battery_presense) |
| return; |
| |
| if (bq2419x->irq) |
| disable_irq(bq2419x->irq); |
| |
| if (alarm_time && !bq2419x->rtc) |
| bq2419x->rtc = alarmtimer_get_rtcdev(); |
| |
| if (alarm_time && (bq2419x->in_current_limit > 500)) { |
| dev_info(bq2419x->dev, "HighCurrent %dmA charger is connectd\n", |
| bq2419x->in_current_limit); |
| ret = bq2419x_reset_wdt(bq2419x, "shutdown"); |
| if (ret < 0) |
| dev_err(bq2419x->dev, |
| "bq2419x_reset_wdt failed: %d\n", ret); |
| alarm_time = 20; |
| } |
| |
| mutex_lock(&bq2419x->mutex); |
| bq2419x->suspended = 1; |
| mutex_unlock(&bq2419x->mutex); |
| |
| ret = bq2419x_charger_enable(bq2419x); |
| if (ret < 0) |
| dev_err(bq2419x->dev, "Charger enable failed %d", ret); |
| |
| if (alarm_time && (bq2419x->in_current_limit <= 500)) { |
| /* Configure charging current to 500mA */ |
| ret = regmap_write(bq2419x->regmap, |
| BQ2419X_INPUT_SRC_REG, 0x32); |
| if (ret < 0) |
| dev_err(bq2419x->dev, |
| "INPUT_SRC_REG write failed %d\n", ret); |
| } |
| |
| ret = bq2419x_wakealarm(bq2419x, alarm_time); |
| if (ret < 0) |
| dev_err(bq2419x->dev, "RTC wake alarm config failed %d\n", ret); |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int bq2419x_suspend(struct device *dev) |
| { |
| int ret = 0; |
| struct bq2419x_chip *bq2419x = dev_get_drvdata(dev); |
| |
| if (!bq2419x->battery_presense) |
| return 0; |
| |
| mutex_lock(&bq2419x->mutex); |
| bq2419x->suspended = 1; |
| mutex_unlock(&bq2419x->mutex); |
| disable_irq(bq2419x->irq); |
| ret = bq2419x_charger_enable(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Charger enable failed %d", ret); |
| return ret; |
| } |
| |
| /* Configure charging current to 500mA */ |
| ret = regmap_write(bq2419x->regmap, BQ2419X_INPUT_SRC_REG, 0x32); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "INPUT_SRC_REG write failed %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int bq2419x_resume(struct device *dev) |
| { |
| int ret = 0; |
| struct bq2419x_chip *bq2419x = dev_get_drvdata(dev); |
| unsigned int val; |
| |
| if (!bq2419x->battery_presense) |
| return 0; |
| |
| ret = regmap_read(bq2419x->regmap, BQ2419X_FAULT_REG, &val); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "FAULT_REG read failed %d\n", ret); |
| return ret; |
| } |
| |
| if (val & BQ2419x_FAULT_WATCHDOG_FAULT) { |
| dev_err(bq2419x->dev, |
| "Charging Fault: Watchdog Timer Expired\n"); |
| |
| ret = bq2419x_watchdog_init(bq2419x, bq2419x->wdt_time_sec, |
| "RESUME"); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "BQWDT init failed %d\n", ret); |
| return ret; |
| } |
| } |
| |
| ret = bq2419x_fault_clear_sts(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "fault clear status failed %d\n", ret); |
| return ret; |
| } |
| |
| ret = bq2419x_charger_init(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "Charger init failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = bq2419x_init(bq2419x); |
| if (ret < 0) { |
| dev_err(bq2419x->dev, "bq2419x init failed: %d\n", ret); |
| return ret; |
| } |
| |
| mutex_lock(&bq2419x->mutex); |
| bq2419x->suspended = 0; |
| mutex_unlock(&bq2419x->mutex); |
| if (gpio_is_valid(bq2419x->gpio_otg_iusb)) |
| gpio_set_value(bq2419x->gpio_otg_iusb, 1); |
| |
| enable_irq(bq2419x->irq); |
| 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 <ldewangan@nvidia.com>"); |
| MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com"); |
| MODULE_LICENSE("GPL v2"); |