/* * palmas-charger.c -- Palmas TPS80036 Charger driver * * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. * * Author: Darbha Sriharsha * 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 #define VBUS_REGULATOR_ENABLE_TIME 500000 #define NV_CHARGER_CURRENT_LIMIT 2000 #define PALMAS_RESTART_CHARGING_AFTER_DONE (15 * 60) enum charging_states { ENABLED_HALF_IBAT = 1, ENABLED_FULL_IBAT, DISABLED, }; struct palmas_charger_chip { struct device *dev; int irq; 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; 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 chg_status; struct palmas *palmas; bool battery_presense; int charging_state; int cable_connected; int chg_restart_time_sec; int last_charging_current; }; /* input current limit */ static const unsigned int iinlim[] = { 100, 150, 500, 900, 1200, 1500, 2000, 3000, }; /* Kthread scheduling parameters */ struct sched_param palmas_param = { .sched_priority = MAX_RT_PRIO - 1, }; 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 palmas_charger_enable(struct palmas_charger_chip *palmas_chip) { int ret; if (palmas_chip->battery_presense) { dev_info(palmas_chip->dev, "Charging enabled\n"); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, PALMAS_ENABLE_CHARGE_MASK, PALMAS_ENABLE_CHARGE); } else { ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, PALMAS_ENABLE_CHARGE_MASK, PALMAS_DISABLE_CHARGE); } if (ret < 0) dev_err(palmas_chip->dev, "register update failed, err %d\n", ret); return ret; } int palmas_charger_disable(struct palmas_charger_chip *palmas_chip) { int ret; dev_info(palmas_chip->dev, "Charging disabled\n"); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, PALMAS_ENABLE_CHARGE_MASK, PALMAS_DISABLE_CHARGE); if (ret < 0) { dev_err(palmas_chip->dev, "register update failed, err %d\n", ret); return ret; } palmas_chip->charging_state = DISABLED; return ret; } static int palmas_vbus_regulator_enable_time(struct regulator_dev *rdev) { return VBUS_REGULATOR_ENABLE_TIME; } static int palmas_vbus_enable(struct regulator_dev *rdev) { struct palmas_charger_chip *palmas_chip = rdev_get_drvdata(rdev); int ret; dev_info(palmas_chip->dev, "VBUS enabled, charging disabled\n"); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, PALMAS_ENABLE_CHARGE_MASK, PALMAS_ENABLE_VBUS); if (ret < 0) dev_err(palmas_chip->dev, "PWR_ON_REG update failed %d", ret); return ret; } static int palmas_vbus_disable(struct regulator_dev *rdev) { struct palmas_charger_chip *palmas_chip = rdev_get_drvdata(rdev); int ret; dev_info(palmas_chip->dev, "VBUS disabled, charging enabled\n"); ret = palmas_charger_enable(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Charger enable failed %d", ret); return ret; } return ret; } static int palmas_vbus_is_enabled(struct regulator_dev *rdev) { struct palmas_charger_chip *palmas_chip = rdev_get_drvdata(rdev); int ret; unsigned int data; ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, &data); if (ret < 0) { dev_err(palmas_chip->dev, "PWR_ON_REG read failed %d", ret); return ret; } return (data & PALMAS_ENABLE_CHARGE_MASK) == PALMAS_ENABLE_VBUS; } static struct regulator_ops palmas_vbus_ops = { .enable = palmas_vbus_enable, .disable = palmas_vbus_disable, .is_enabled = palmas_vbus_is_enabled, .enable_time = palmas_vbus_regulator_enable_time, }; static int palmas_set_usbsuspend(struct palmas_charger_chip *palmas_chip, unsigned int enable) { struct palmas *palmas = palmas_chip->palmas; int ret; int reg; reg = enable << 2; ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE, PALMAS_USB_CHGCTL1, PALMAS_USB_CHGCTL1_USB_SUSPEND, reg); if (ret < 0) { dev_err(palmas_chip->dev, "Unable to update usb suspend\n"); return ret; } return 0; } static int palmas_init(struct palmas_charger_chip *palmas_chip) { int val, ret = 0; /* Clear EN_HIZ */ ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG00, PALMAS_EN_HIZ, 0); if (ret < 0) { dev_err(palmas_chip->dev, "error reading reg: 0x%x\n", PALMAS_CHARGER_REG00); return ret; } /* Configure input current limit */ val = current_to_reg(iinlim, ARRAY_SIZE(iinlim), palmas_chip->in_current_limit); if (val < 0) return 0; val &= ~(PALMAS_INPUT_VOLTAGE_MASK); /* * Configure inout voltage to 4.52 in case of NV * NV charger. */ if (palmas_chip->in_current_limit == 2000 || palmas_chip->in_current_limit == 1000) val |= PALMAS_NVCHARGER_INPUT_VOL_SEL; else val |= PALMAS_DEFAULT_INPUT_VOL_SEL; ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG00, PALMAS_CONFIG_MASK | PALMAS_INPUT_VOLTAGE_MASK, val); if (ret < 0) dev_err(palmas_chip->dev, "error reading reg: 0x%x\n", PALMAS_CHARGER_REG00); palmas_set_usbsuspend(palmas_chip, 0); return ret; } int palmas_full_current_enable(struct palmas_charger_chip *palmas_chip) { int ret; palmas_charger_enable(palmas_chip); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG04, PALMAS_CHARGE_VOLTAGE_MASK, PALMAS_CHARGE_VOLTAGE_4112MV); if (ret < 0) { dev_err(palmas_chip->dev, "Failed to update charge voltage"); return ret; } ret = palmas_init(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Failed to set full charging current"); return ret; } palmas_chip->charging_state = ENABLED_FULL_IBAT; return 0; } int palmas_half_current_enable(struct palmas_charger_chip *palmas_chip) { int ret, temp; palmas_charger_enable(palmas_chip); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG04, PALMAS_CHARGE_VOLTAGE_MASK, PALMAS_CHARGE_VOLTAGE_4048MV); if (ret < 0) { dev_err(palmas_chip->dev, "Failed to update charge voltage"); return ret; } temp = palmas_chip->in_current_limit; palmas_chip->in_current_limit = palmas_chip->in_current_limit/2; ret = palmas_init(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Failed to set half charging current"); return ret; } palmas_chip->in_current_limit = temp; palmas_chip->charging_state = ENABLED_HALF_IBAT; return 0; } static int palmas_charger_init(struct palmas_charger_chip *palmas_chip) { int ret; /* Configure Output Current Control to 3A*/ ret = palmas_write(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG02, PALMAS_CHRG_CTRL_REG_3A); if (ret < 0) { dev_err(palmas_chip->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 = palmas_write(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG00, PALMAS_OTP_CURRENT_500MA); if (ret < 0) dev_err(palmas_chip->dev, "INPUT_SRC_REG write failed %d\n", ret); ret = palmas_update_bits(palmas_chip->palmas, PALMAS_PMU_CONTROL_BASE, PALMAS_USB_CHGCTL2, PALMAS_USB_CHGCTL2_BOOST_EN, PALMAS_USB_CHGCTL2_BOOST_EN); if (ret < 0) dev_err(palmas_chip->dev, "BOOST mode enable failed\n"); return ret; } static int palmas_reset_wdt(struct palmas_charger_chip *palmas_chip, const char *from) { int ret = 0; unsigned int reg01; mutex_lock(&palmas_chip->mutex); if (palmas_chip->suspended) goto scrub; dev_info(palmas_chip->dev, "%s() from %s()\n", __func__, from); /* Clear EN_HIZ */ ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG00, PALMAS_EN_HIZ, 0); if (ret < 0) { dev_err(palmas_chip->dev, "INPUT_SRC_REG update failed:%d\n", ret); goto scrub; } ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, ®01); if (ret < 0) { dev_err(palmas_chip->dev, "PWR_ON_REG read failed: %d\n", ret); goto scrub; } reg01 |= BIT(6); /* Write two times to make sure reset WDT */ ret = palmas_write(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, reg01); if (ret < 0) { dev_err(palmas_chip->dev, "PWR_ON_REG write failed: %d\n", ret); goto scrub; } ret = palmas_write(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG01, reg01); if (ret < 0) { dev_err(palmas_chip->dev, "PWR_ON_REG write failed: %d\n", ret); goto scrub; } scrub: mutex_unlock(&palmas_chip->mutex); return ret; } static int palmas_set_charging_current(struct regulator_dev *rdev, int min_uA, int max_uA) { struct palmas_charger_chip *palmas_chip = rdev_get_drvdata(rdev); int ret = 0; int val; dev_info(palmas_chip->dev, "Setting charging current %d uA\n", max_uA); /* System status register gets updated after a delay of about 200ms*/ palmas_chip->chg_status = BATTERY_DISCHARGING; msleep(200); ret = palmas_charger_enable(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Charger enable failed %d", ret); return ret; } ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG08, &val); if (ret < 0) { dev_err(palmas_chip->dev, "error reading reg: 0x%x\n", PALMAS_CHARGER_REG08); } if (max_uA == 0 && val != 0) { palmas_set_usbsuspend(palmas_chip, 1); return ret; } palmas_chip->in_current_limit = max_uA/1000; palmas_chip->last_charging_current = max_uA; if ((val & PALMAS_VBUS_STAT) == PALMAS_VBUS_UNKNOWN) { palmas_chip->cable_connected = 0; palmas_chip->in_current_limit = 500; palmas_full_current_enable(palmas_chip); if (ret < 0) goto error; battery_charging_status_update(palmas_chip->bc_dev, BATTERY_DISCHARGING); battery_charger_thermal_stop_monitoring(palmas_chip->bc_dev); } else { palmas_chip->cable_connected = 1; palmas_chip->chg_status = BATTERY_CHARGING; palmas_full_current_enable(palmas_chip); if (ret < 0) goto error; battery_charging_status_update(palmas_chip->bc_dev, BATTERY_CHARGING); battery_charger_thermal_start_monitoring(palmas_chip->bc_dev); } return 0; error: dev_err(palmas_chip->dev, "Charger enable failed, err = %d\n", ret); return ret; } static struct regulator_ops palmas_tegra_regulator_ops = { .set_current_limit = palmas_set_charging_current, }; static int palmas_fault_clear_sts(struct palmas_charger_chip *palmas_chip) { int ret; unsigned int reg09; ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG09, ®09); if (ret < 0) dev_err(palmas_chip->dev, "FAULT_REG read failed: %d\n", ret); return ret; } static int palmas_watchdog_init(struct palmas_charger_chip *palmas_chip, int timeout, const char *from) { int ret, val; unsigned int reg05; if (!timeout) { ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG05, PALMAS_WD_MASK, 0); if (ret < 0) dev_err(palmas_chip->dev, "TIME_CTRL_REG read failed: %d\n", ret); return ret; } /* * Choose a kernel wdt refresh thread timeout value below the * watchdog expiry timeout */ if (timeout <= 60) { val = PALMAS_WD_40ms; palmas_chip->wdt_refresh_timeout = 15; } else if (timeout <= 120) { val = PALMAS_WD_80ms; palmas_chip->wdt_refresh_timeout = 40; } else { val = PALMAS_WD_160ms; palmas_chip->wdt_refresh_timeout = 105; } ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG05, ®05); if (ret < 0) { dev_err(palmas_chip->dev, "TIME_CTRL_REG read failed:%d\n", ret); return ret; } if ((reg05 & PALMAS_WD_MASK) != val) { ret = palmas_update_bits(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG05, PALMAS_WD_MASK, val); if (ret < 0) { dev_err(palmas_chip->dev, "TIME_CTRL_REG read failed: %d\n", ret); return ret; } } ret = palmas_reset_wdt(palmas_chip, from); if (ret < 0) dev_err(palmas_chip->dev, "palmas_reset_wdt failed: %d\n", ret); return ret; } static void palmas_work_thread(struct kthread_work *work) { struct palmas_charger_chip *palmas_chip = container_of(work, struct palmas_charger_chip, bq_wdt_work); int ret; for (;;) { if (palmas_chip->stop_thread) return; if (palmas_chip->chg_restart_timeout) { mutex_lock(&palmas_chip->mutex); palmas_chip->chg_restart_timeout--; if (!palmas_chip->chg_restart_timeout) { ret = palmas_charger_enable(palmas_chip); if (ret < 0) dev_err(palmas_chip->dev, "Charger enable failed %d", ret); } if (palmas_chip->suspended) palmas_chip->chg_restart_timeout = 0; mutex_unlock(&palmas_chip->mutex); } ret = palmas_reset_wdt(palmas_chip, "THREAD"); if (ret < 0) dev_err(palmas_chip->dev, "palmas_reset_wdt failed: %d\n", ret); msleep(palmas_chip->wdt_refresh_timeout * 1000); } } static irqreturn_t palmas_charger_irq(int irq, void *data) { struct palmas_charger_chip *palmas_chip = data; irqreturn_t ret; unsigned int val; ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG09, &val); if (ret < 0) { dev_err(palmas_chip->dev, "FAULT_REG read failed %d\n", ret); return ret; } dev_info(palmas_chip->dev, "%s() Irq %d status 0x%02x\n", __func__, irq, val); if (val & PALMAS_FAULT_BOOST_FAULT) dev_err(palmas_chip->dev, "Charging Fault: VBUS Overloaded\n"); if (!palmas_chip->battery_presense) return IRQ_HANDLED; if (val & PALMAS_FAULT_WATCHDOG_FAULT) { dev_err(palmas_chip->dev, "Charging Fault: Watchdog Timer Expired\n"); ret = palmas_watchdog_init(palmas_chip, palmas_chip->wdt_time_sec, "ISR"); if (ret < 0) { dev_err(palmas_chip->dev, "BQWDT init failed %d\n", ret); return IRQ_NONE; } ret = palmas_charger_init(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Charger init failed: %d\n", ret); return IRQ_NONE; } ret = palmas_init(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "palmas init failed: %d\n", ret); return IRQ_NONE; } } switch (val & PALMAS_FAULT_CHRG_FAULT_MASK) { case PALMAS_FAULT_CHRG_INPUT: dev_err(palmas_chip->dev, "Charging Fault: " "Input Fault (VBUS OVP or VBATdev, "Charging Fault: Thermal shutdown\n"); break; case PALMAS_FAULT_CHRG_SAFTY: dev_err(palmas_chip->dev, "Charging Fault: Safety timer expiration\n"); palmas_chip->chg_restart_timeout = palmas_chip->chg_restart_time / palmas_chip->wdt_refresh_timeout; break; default: break; } if (val & PALMAS_FAULT_NTC_FAULT) dev_err(palmas_chip->dev, "Charging Fault: NTC fault %d\n", val & PALMAS_FAULT_NTC_FAULT); ret = palmas_fault_clear_sts(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "fault clear status failed %d\n", ret); return IRQ_NONE; } ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG08, &val); if (ret < 0) { dev_err(palmas_chip->dev, "SYS_STAT_REG read failed %d\n", ret); return IRQ_NONE; } if ((val & PALMAS_CHRG_STATE_MASK) == PALMAS_CHRG_STATE_CHARGE_DONE) { dev_info(palmas_chip->dev, "Charging completed\n"); battery_charging_status_update(palmas_chip->bc_dev, BATTERY_CHARGING_DONE); battery_charger_thermal_stop_monitoring(palmas_chip->bc_dev); battery_charging_restart(palmas_chip->bc_dev, palmas_chip->chg_restart_time_sec); } return IRQ_HANDLED; } static int palmas_init_charger_regulator( struct palmas_charger_chip *palmas_chip, struct palmas_charger_platform_data *pdata) { int ret = 0; struct regulator_config rconfig = { }; palmas_chip->chg_reg_desc.name = "palmas-charger"; palmas_chip->chg_reg_desc.ops = &palmas_tegra_regulator_ops; palmas_chip->chg_reg_desc.type = REGULATOR_CURRENT; palmas_chip->chg_reg_desc.owner = THIS_MODULE; palmas_chip->chg_reg_init_data.supply_regulator = NULL; palmas_chip->chg_reg_init_data.regulator_init = NULL; palmas_chip->chg_reg_init_data.num_consumer_supplies = pdata->bcharger_pdata->num_consumer_supplies; palmas_chip->chg_reg_init_data.consumer_supplies = pdata->bcharger_pdata->consumer_supplies; palmas_chip->chg_reg_init_data.driver_data = palmas_chip; palmas_chip->chg_reg_init_data.constraints.name = "palmas-charger"; palmas_chip->chg_reg_init_data.constraints.min_uA = 0; palmas_chip->chg_reg_init_data.constraints.max_uA = pdata->bcharger_pdata->max_charge_current_mA * 1000; palmas_chip->chg_reg_init_data.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; palmas_chip->chg_reg_init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_CURRENT; rconfig.dev = palmas_chip->dev; rconfig.of_node = NULL; rconfig.init_data = &palmas_chip->chg_reg_init_data; rconfig.driver_data = palmas_chip; palmas_chip->chg_rdev = regulator_register(&palmas_chip->chg_reg_desc, &rconfig); if (IS_ERR(palmas_chip->chg_rdev)) { ret = PTR_ERR(palmas_chip->chg_rdev); dev_err(palmas_chip->dev, "vbus-charger regulator register failed %d\n", ret); } return ret; } static int palmas_init_vbus_regulator(struct palmas_charger_chip *palmas_chip, struct palmas_charger_platform_data *pdata) { int ret = 0; struct regulator_config rconfig = { }; if (!pdata->vbus_pdata) { dev_err(palmas_chip->dev, "No vbus platform data\n"); return 0; } palmas_chip->vbus_reg_desc.name = "palmas-vbus"; palmas_chip->vbus_reg_desc.ops = &palmas_vbus_ops; palmas_chip->vbus_reg_desc.type = REGULATOR_VOLTAGE; palmas_chip->vbus_reg_desc.owner = THIS_MODULE; palmas_chip->vbus_reg_init_data.supply_regulator = NULL; palmas_chip->vbus_reg_init_data.regulator_init = NULL; palmas_chip->vbus_reg_init_data.num_consumer_supplies = pdata->vbus_pdata->num_consumer_supplies; palmas_chip->vbus_reg_init_data.consumer_supplies = pdata->vbus_pdata->consumer_supplies; palmas_chip->vbus_reg_init_data.driver_data = palmas_chip; palmas_chip->vbus_reg_init_data.constraints.name = "palmas-vbus"; palmas_chip->vbus_reg_init_data.constraints.min_uV = 0; palmas_chip->vbus_reg_init_data.constraints.max_uV = 5000000, palmas_chip->vbus_reg_init_data.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; palmas_chip->vbus_reg_init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_VOLTAGE; /* Enable boost mode */ ret = palmas_update_bits(palmas_chip->palmas, PALMAS_PMU_CONTROL_BASE, PALMAS_USB_CHGCTL2, PALMAS_USB_CHGCTL2_BOOST_EN, PALMAS_USB_CHGCTL2_BOOST_EN); if (ret < 0) { dev_err(palmas_chip->dev, "USB_CHGCTL2 update failed: %d\n", ret); return ret; } /* Register the regulators */ rconfig.dev = palmas_chip->dev; rconfig.of_node = NULL; rconfig.init_data = &palmas_chip->vbus_reg_init_data; rconfig.driver_data = palmas_chip; palmas_chip->vbus_rdev = regulator_register(&palmas_chip->vbus_reg_desc, &rconfig); if (IS_ERR(palmas_chip->vbus_rdev)) { ret = PTR_ERR(palmas_chip->vbus_rdev); dev_err(palmas_chip->dev, "VBUS regulator register failed %d\n", ret); return ret; } /* Disable the VBUS regulator and enable charging */ ret = palmas_charger_enable(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Charging enable failed %d", ret); goto scrub_reg; } return ret; scrub_reg: regulator_unregister(palmas_chip->vbus_rdev); palmas_chip->vbus_rdev = NULL; return ret; } static int palmas_show_charger_chip_version( struct palmas_charger_chip *palmas_chip) { int ret; unsigned int val; ret = palmas_read(palmas_chip->palmas, PALMAS_CHARGER_BASE, PALMAS_CHARGER_REG10, &val); if (ret < 0) { dev_err(palmas_chip->dev, "REVISION_REG read failed: %d\n", ret); return ret; } if ((val & BQ24190_IC_VER) == BQ24190_IC_VER) dev_info(palmas_chip->dev, "chip type BQ24190 detected\n"); else if ((val & BQ24192_IC_VER) == BQ24192_IC_VER) dev_info(palmas_chip->dev, "chip type PALMAS/3 detected\n"); else if ((val & BQ24192i_IC_VER) == BQ24192i_IC_VER) dev_info(palmas_chip->dev, "chip type PALMASi detected\n"); return 0; } static int palmas_bcharger_init(struct palmas_charger_chip *palmas_chip, struct palmas_charger_platform_data *pdata) { int ret = 0; palmas_chip->rtc_alarm_time = pdata->bcharger_pdata->rtc_alarm_time; palmas_chip->wdt_time_sec = pdata->bcharger_pdata->wdt_timeout; palmas_chip->chg_restart_time = pdata->bcharger_pdata->chg_restart_time; palmas_chip->rtc = alarmtimer_get_rtcdev(); palmas_chip->suspended = 0; palmas_chip->chg_restart_timeout = 0; ret = palmas_charger_init(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "Charger init failed: %d\n", ret); return ret; } ret = palmas_init_charger_regulator(palmas_chip, pdata); if (ret < 0) { dev_err(palmas_chip->dev, "Charger regualtor init failed %d\n", ret); return ret; } init_kthread_worker(&palmas_chip->bq_kworker); palmas_chip->bq_kworker_task = kthread_run(kthread_worker_fn, &palmas_chip->bq_kworker, dev_name(palmas_chip->dev)); if (IS_ERR(palmas_chip->bq_kworker_task)) { ret = PTR_ERR(palmas_chip->bq_kworker_task); dev_err(palmas_chip->dev, "Kworker task creation failed %d\n", ret); goto scrub_chg_reg; } init_kthread_work(&palmas_chip->bq_wdt_work, palmas_work_thread); sched_setscheduler(palmas_chip->bq_kworker_task, SCHED_FIFO, &palmas_param); queue_kthread_work(&palmas_chip->bq_kworker, &palmas_chip->bq_wdt_work); ret = palmas_watchdog_init(palmas_chip, palmas_chip->wdt_time_sec, "PROBE"); if (ret < 0) { dev_err(palmas_chip->dev, "BQWDT init failed %d\n", ret); goto scrub_kthread; } /* enable charging */ ret = palmas_charger_enable(palmas_chip); if (ret < 0) goto scrub_kthread; return 0; scrub_kthread: palmas_chip->stop_thread = true; flush_kthread_worker(&palmas_chip->bq_kworker); kthread_stop(palmas_chip->bq_kworker_task); scrub_chg_reg: regulator_unregister(palmas_chip->chg_rdev); return ret; } static int palmas_bcharger_deinit(struct palmas_charger_chip *palmas_chip) { palmas_chip->stop_thread = true; flush_kthread_worker(&palmas_chip->bq_kworker); kthread_stop(palmas_chip->bq_kworker_task); regulator_unregister(palmas_chip->chg_rdev); return 0; } static int palmas_charger_get_status(struct battery_charger_dev *bc_dev) { struct palmas_charger_chip *chip = battery_charger_get_drvdata(bc_dev); return chip->chg_status; } static int palams_charger_thermal_configure( struct battery_charger_dev *bc_dev, int temp, bool enable_charger, bool enable_charg_half_current, int battery_voltage) { struct palmas_charger_chip *chip = battery_charger_get_drvdata(bc_dev); int temperature; if (!chip->cable_connected) return 0; temperature = temp; dev_info(chip->dev, "Battery temp %d\n", temperature); if (enable_charger) { if (!enable_charg_half_current && chip->charging_state != ENABLED_FULL_IBAT) { palmas_full_current_enable(chip); battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING); } else if (enable_charg_half_current && chip->charging_state != ENABLED_HALF_IBAT) palmas_half_current_enable(chip); battery_charging_status_update(chip->bc_dev, BATTERY_CHARGING); } else { if (chip->charging_state != DISABLED) { palmas_charger_disable(chip); battery_charging_status_update(chip->bc_dev, BATTERY_DISCHARGING); } } return 0; } static int palmas_charger_restart_charging(struct battery_charger_dev *bc_dev) { struct palmas_charger_chip *chip = battery_charger_get_drvdata(bc_dev); int ret; if (!chip->cable_connected) return 0; dev_info(chip->dev, "Restarting the charging\n"); ret = palmas_set_charging_current(chip->chg_rdev, chip->last_charging_current, chip->last_charging_current); if (ret < 0) { dev_err(chip->dev, "restarting of charging failed: %d\n", ret); battery_charging_restart(chip->bc_dev, chip->chg_restart_time_sec); } return ret; } static struct battery_charging_ops palmas_charger_bci_ops = { .get_charging_status = palmas_charger_get_status, .thermal_configure = palams_charger_thermal_configure, .restart_charging = palmas_charger_restart_charging, }; static struct battery_charger_info palmas_charger_bci = { .cell_id = 0, .bc_ops = &palmas_charger_bci_ops, }; static int palmas_probe(struct platform_device *pdev) { int ret = 0; struct palmas_charger_chip *palmas_chip; struct palmas_charger_platform_data *pdata; struct palmas_platform_data *palmas_pdata; palmas_pdata = dev_get_platdata(pdev->dev.parent); if (!palmas_pdata || !palmas_pdata->charger_pdata) { dev_err(&pdev->dev, "No platform data\n"); return -ENODEV; } palmas_chip = devm_kzalloc(&pdev->dev, sizeof(*palmas_chip), GFP_KERNEL); if (!palmas_chip) { dev_err(&pdev->dev, "Memory allocation failed\n"); return -ENOMEM; } pdata = palmas_pdata->charger_pdata; palmas_chip->dev = &pdev->dev; palmas_chip->irq = platform_get_irq(pdev, 0); palmas_chip->palmas = dev_get_drvdata(pdev->dev.parent); mutex_init(&palmas_chip->mutex); dev_set_drvdata(&pdev->dev, palmas_chip); ret = palmas_show_charger_chip_version(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "version read failed %d\n", ret); goto scrub_mutex; } ret = palmas_init_vbus_regulator(palmas_chip, pdata); if (ret < 0) { dev_err(palmas_chip->dev, "VBUS regualtor init failed %d\n", ret); goto scrub_mutex; } if (!pdata->bcharger_pdata) { dev_info(&pdev->dev, "No battery charger supported\n"); ret = palmas_watchdog_init(palmas_chip, 0, "PROBE"); if (ret < 0) { dev_err(palmas_chip->dev, "WDT disable failed: %d\n", ret); goto scrub_mutex; } goto skip_bcharger_init; } palmas_chip->battery_presense = true; ret = palmas_bcharger_init(palmas_chip, pdata); if (ret < 0) { dev_err(palmas_chip->dev, "hw init failed %d\n", ret); goto scrub_vbus_reg; } palmas_chip->chg_restart_time_sec = PALMAS_RESTART_CHARGING_AFTER_DONE; palmas_charger_bci.polling_time_sec = pdata->bcharger_pdata->temperature_poll_period_secs; palmas_charger_bci.tz_name = pdata->bcharger_pdata->battery_tz_name; palmas_chip->bc_dev = battery_charger_register(&pdev->dev, &palmas_charger_bci, palmas_chip); if (IS_ERR(palmas_chip->bc_dev)) { ret = PTR_ERR(palmas_chip->bc_dev); dev_err(&pdev->dev, "battery charger register failed: %d\n", ret); goto scrub_bcharger_init; } skip_bcharger_init: ret = palmas_fault_clear_sts(palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "fault clear status failed %d\n", ret); goto scrub_bcharger_reg; } ret = request_threaded_irq(palmas_chip->irq, NULL, palmas_charger_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_EARLY_RESUME, "palmas-charger", palmas_chip); if (ret < 0) { dev_err(palmas_chip->dev, "request IRQ %d fail, err = %d\n", palmas_chip->irq, ret); goto scrub_bcharger_reg; } if (pdata->bcharger_pdata) { /* enable charging */ ret = palmas_charger_enable(palmas_chip); if (ret < 0) goto scrub_irq; } return 0; scrub_irq: free_irq(palmas_chip->irq, palmas_chip); scrub_bcharger_reg: if (pdata->bcharger_pdata) battery_charger_unregister(palmas_chip->bc_dev); scrub_bcharger_init: if (pdata->bcharger_pdata) palmas_bcharger_deinit(palmas_chip); scrub_vbus_reg: regulator_unregister(palmas_chip->vbus_rdev); scrub_mutex: mutex_destroy(&palmas_chip->mutex); return ret; } static int palmas_remove(struct platform_device *pdev) { struct palmas_charger_chip *palmas_chip = dev_get_drvdata(&pdev->dev); free_irq(palmas_chip->irq, palmas_chip); if (palmas_chip->battery_presense) { battery_charger_unregister(palmas_chip->bc_dev); palmas_bcharger_deinit(palmas_chip); } regulator_unregister(palmas_chip->vbus_rdev); mutex_destroy(&palmas_chip->mutex); return 0; } static struct platform_driver palmas_platform_driver = { .probe = palmas_probe, .remove = palmas_remove, .driver = { .name = "palmas-charger", .owner = THIS_MODULE, }, }; static inline int palmas_charger_platform_init(void) { return platform_driver_register(&palmas_platform_driver); } static inline void palmas_charger_platform_exit(void) { return platform_driver_unregister(&palmas_platform_driver); } static int __init palmas_module_init(void) { palmas_charger_platform_init(); return 0; } subsys_initcall(palmas_module_init); static void __exit palmas_cleanup(void) { palmas_charger_platform_exit(); } module_exit(palmas_cleanup); MODULE_DESCRIPTION("palmas battery charger driver"); MODULE_AUTHOR("Darbha Sriharsha "); MODULE_AUTHOR("Syed Rafiuddin "); MODULE_LICENSE("GPL v2");