blob: e5e9594fefa0b9c61bfae8b120e9e59ff17f90d8 [file] [log] [blame]
/*
* palmas-charger.c -- Palmas TPS80036 Charger driver
*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
*
* Author: Darbha Sriharsha <dsriharsha@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/init.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/palmas.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>
#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, &reg01);
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, &reg09);
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, &reg05);
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 VBAT<VBUS<3.8V)\n");
break;
case PALMAS_FAULT_CHRG_THERMAL:
dev_err(palmas_chip->dev, "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 <dsriharsha@nvidia.com>");
MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>");
MODULE_LICENSE("GPL v2");