summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorLaxman Dewangan <ldewangan@nvidia.com>2011-08-21 20:23:44 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:48:32 -0800
commit6a0a7949845f43929161081d811d84d27366bd4a (patch)
tree63d531015e59ef174316c538b6459f53519f8fb3 /drivers/power
parent6d84ef18a8da2a7faddd41631bca401e1647fa1a (diff)
power: tps80031-charger: Add battery charger driver
Adding battery charger driver for tps80031. bug 841080 Original-Change-Id: Id5bd717f4784b9bb48b2c2cb0b1b16a8a85aa830 Reviewed-on: http://git-master/r/48361 Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com> Tested-by: Laxman Dewangan <ldewangan@nvidia.com> Rebase-Id: Rae0d4723273b65c6a30820a876c8a8499bb69cf3
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig7
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/tps80031-charger.c423
3 files changed, 431 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index c12421d688d7..c47e58b89fce 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -149,6 +149,13 @@ config BATTERY_BQ27X00_PLATFORM
help
Say Y here to enable support for batteries with BQ27000 (HDQ) chips.
+config CHARGER_TPS8003X
+ tristate "TPS8003x battery charger driver"
+ depends on MFD_TPS80031
+ default n
+ help
+ Say Y here to enable support for battery charging with TPS80031x chips.
+
config BATTERY_DA9030
tristate "DA9030 battery driver"
depends on PMIC_DA903X
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 72124bbf6776..f599a513c63b 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_BQ20Z75) += bq20z75.o
obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
+obj-$(CONFIG_CHARGER_TPS8003X) += tps80031-charger.o
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
diff --git a/drivers/power/tps80031-charger.c b/drivers/power/tps80031-charger.c
new file mode 100644
index 000000000000..cd449c2a19a7
--- /dev/null
+++ b/drivers/power/tps80031-charger.c
@@ -0,0 +1,423 @@
+/*
+ * drivers/power/tps80031_charger.c
+ *
+ * Battery charger driver for TI's tps80031
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/tps80031.h>
+#include <linux/tps80031-charger.h>
+
+#define CONTROLLER_CTRL1 0xe1
+#define CONTROLLER_STAT1 0xe3
+#define CHARGERUSB_CTRL2 0xe9
+#define CHARGERUSB_CTRL3 0xea
+#define CHARGERUSB_VOREG 0xec
+#define CHARGERUSB_VICHRG 0xed
+#define CHARGERUSB_CINLIMIT 0xee
+#define CHARGERUSB_CTRLLIMIT2 0xf0
+#define CHARGERUSB_CTRLLIMIT1 0xef
+#define CHARGERUSB_VICHRG_PC 0xdd
+#define CONTROLLER_WDG 0xe2
+
+#define TPS80031_VBUS_DET BIT(2)
+#define TPS80031_VAC_DET BIT(3)
+
+enum charging_state {
+ INIT = 0,
+ PROGRESS = 1,
+ DONE = 2,
+};
+
+struct tps80031_charger {
+ int max_charge_current_mA;
+ int max_charge_volt_mV;
+ struct device *dev;
+ struct regulator_dev *rdev;
+ struct regulator_desc reg_desc;
+ struct regulator_init_data reg_init_data;
+ struct tps80031_charger_platform_data *pdata;
+ int (*board_init)(void *board_data);
+ void *board_data;
+ int irq_base;
+ int watch_time_sec;
+ enum charging_state state;
+ int charging_term_current_mA;
+};
+
+static int set_charge_current_limit(struct regulator_dev *rdev,
+ int min_uA, int max_uA)
+{
+ struct tps80031_charger *charger = rdev_get_drvdata(rdev);
+ int max_vbus_current = 1500;
+ int max_charge_current = 1500;
+ int ret;
+
+ dev_info(charger->dev, "%s(): Min curr %dmA and max current %dmA\n",
+ __func__, min_uA/1000, max_uA/1000);
+
+ if (!max_uA) {
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_CTRL1, 0x0);
+ if (ret < 0)
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_CTRL1);
+
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_WDG, 0x0);
+ if (ret < 0)
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_WDG);
+ charger->state = INIT;
+ return ret;
+ }
+
+ max_vbus_current = min(max_uA/1000, max_vbus_current);
+ max_vbus_current = max_vbus_current/50;
+ if (max_vbus_current)
+ max_vbus_current--;
+ ret = tps80031_update(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CINLIMIT,
+ (uint8_t)max_vbus_current, 0x3F);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_CINLIMIT);
+ return ret;
+ }
+
+ max_charge_current = min(max_uA/1000, max_charge_current);
+ if (max_charge_current <= 300)
+ max_charge_current = 0;
+ else if ((max_charge_current > 300) && (max_charge_current <= 500))
+ max_charge_current = (max_charge_current - 300)/50;
+ else
+ max_charge_current = (max_charge_current - 500) / 100 + 4;
+ ret = tps80031_update(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_VICHRG, (uint8_t)max_charge_current, 0xF);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_VICHRG);
+ return ret;
+ }
+
+ /* Enable watchdog timer */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_WDG, charger->watch_time_sec);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_WDG);
+ return ret;
+ }
+
+ /* Enable the charging */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_CTRL1, 0x30);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_CTRL1);
+ return ret;
+ }
+ charger->state = PROGRESS;
+ return 0;
+}
+
+static struct regulator_ops tegra_regulator_ops = {
+ .set_current_limit = set_charge_current_limit,
+};
+
+static int configure_charging_parameter(struct tps80031_charger *charger)
+{
+ int ret;
+ int max_charge_current;
+ int max_charge_volt;
+ int term_current;
+
+ /* Disable watchdog timer */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_WDG, 0x0);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_WDG);
+ return ret;
+ }
+
+ /* Disable the charging if any */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_CTRL1, 0x0);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_CTRL1);
+ return ret;
+ }
+
+ if (charger->board_init) {
+ ret = charger->board_init(charger->board_data);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in board init\n",
+ __func__);
+ return ret;
+ }
+ }
+
+ /* Unlock value */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CTRLLIMIT2, 0);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_CTRLLIMIT2);
+ return ret;
+ }
+
+ /* Set max current limit */
+ max_charge_current = min(1500, charger->max_charge_current_mA);
+ if (max_charge_current < 100)
+ max_charge_current = 0;
+ else
+ max_charge_current = (max_charge_current - 100)/100;
+ max_charge_current &= 0xF;
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CTRLLIMIT2, (uint8_t)max_charge_current);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register "
+ "0x%02x\n", __func__, CHARGERUSB_CTRLLIMIT2);
+ return ret;
+ }
+
+ /* Set max voltage limit */
+ max_charge_volt = min(4760, charger->max_charge_volt_mV);
+ max_charge_volt = max(3500, max_charge_volt);
+ max_charge_volt -= 3500;
+ max_charge_volt = max_charge_volt/20;
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CTRLLIMIT1, (uint8_t)max_charge_volt);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_CTRLLIMIT1);
+ return ret;
+ }
+
+ /* Lock value */
+ ret = tps80031_set_bits(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CTRLLIMIT2, (1 << 4));
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_CTRLLIMIT2);
+ return ret;
+ }
+
+ /* set Pre Charge current to 400mA */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2, 0xDD, 0x3);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, 0xDD);
+ return ret;
+ }
+
+ /* set charging termination current*/
+ if (charger->charging_term_current_mA > 400)
+ term_current = 7;
+ else
+ term_current = (charger->charging_term_current_mA - 50)/50;
+ term_current = term_current << 5;
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CHARGERUSB_CTRL2, term_current);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CHARGERUSB_CTRL2);
+ return ret;
+ }
+
+ return 0;
+}
+
+static irqreturn_t linch_status_isr(int irq, void *dev_id)
+{
+ struct tps80031_charger *charger = dev_id;
+ dev_info(charger->dev, "%s() got called\n", __func__);
+ return IRQ_HANDLED;
+
+}
+
+static irqreturn_t watchdog_expire_isr(int irq, void *dev_id)
+{
+ struct tps80031_charger *charger = dev_id;
+ int ret;
+
+ dev_info(charger->dev, "%s()\n", __func__);
+ if (charger->state != PROGRESS)
+ return IRQ_HANDLED;
+
+ /* Enable watchdog timer again*/
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2, CONTROLLER_WDG,
+ charger->watch_time_sec);
+ if (ret < 0)
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_WDG);
+
+ /* Rewrite to enable the charging */
+ ret = tps80031_write(charger->dev->parent, SLAVE_ID2,
+ CONTROLLER_CTRL1, 0x30);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n",
+ __func__, CONTROLLER_CTRL1);
+ return ret;
+ }
+ return IRQ_HANDLED;
+}
+
+static int tps80031_charger_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct device *dev = &pdev->dev;
+ struct tps80031_charger *charger;
+ struct tps80031_charger_platform_data *pdata = pdev->dev.platform_data;
+
+ dev_info(dev, "%s()\n", __func__);
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (!charger) {
+ dev_err(dev, "failed to allocate memory status\n");
+ return -ENOMEM;
+ }
+
+ charger->dev = &pdev->dev;
+
+ charger->max_charge_current_mA = (pdata->max_charge_current_mA) ?
+ pdata->max_charge_current_mA : 1000;
+ charger->max_charge_volt_mV = (pdata->max_charge_volt_mV) ?
+ pdata->max_charge_volt_mV : 4200;
+ charger->irq_base = pdata->irq_base;
+ charger->watch_time_sec = min(pdata->watch_time_sec, 127);
+ if (!charger->watch_time_sec)
+ charger->watch_time_sec = 127;
+ charger->charging_term_current_mA =
+ min(50, pdata->charging_term_current_mA);
+ if (charger->charging_term_current_mA < 50)
+ charger->charging_term_current_mA = 50;
+
+ charger->reg_desc.name = "vbus_charger";
+ charger->reg_desc.id = pdata->regulator_id;
+ charger->reg_desc.ops = &tegra_regulator_ops;
+ charger->reg_desc.type = REGULATOR_CURRENT;
+ charger->reg_desc.owner = THIS_MODULE;
+
+ charger->reg_init_data.supply_regulator = NULL;
+ charger->reg_init_data.supply_regulator_dev = NULL;
+ charger->reg_init_data.num_consumer_supplies =
+ pdata->num_consumer_supplies;
+ charger->reg_init_data.consumer_supplies = pdata->consumer_supplies;
+ charger->reg_init_data.regulator_init = NULL;
+ charger->reg_init_data.driver_data = charger;
+ charger->reg_init_data.constraints.name = "vbus_charger";
+ charger->reg_init_data.constraints.min_uA = 0;
+ charger->reg_init_data.constraints.max_uA =
+ pdata->max_charge_current_mA * 1000;
+ charger->reg_init_data.constraints.valid_modes_mask =
+ REGULATOR_MODE_NORMAL |
+ REGULATOR_MODE_STANDBY;
+ charger->reg_init_data.constraints.valid_ops_mask =
+ REGULATOR_CHANGE_MODE |
+ REGULATOR_CHANGE_STATUS |
+ REGULATOR_CHANGE_CURRENT;
+
+ charger->board_init = pdata->board_init;
+ charger->board_data = pdata->board_data;
+ charger->state = INIT;
+
+ charger->rdev = regulator_register(&charger->reg_desc, &pdev->dev,
+ &charger->reg_init_data, charger);
+ if (IS_ERR(charger->rdev)) {
+ dev_err(&pdev->dev, "failed to register %s\n",
+ charger->reg_desc.name);
+ ret = PTR_ERR(charger->rdev);
+ goto regulator_fail;
+ }
+
+ ret = request_threaded_irq(charger->irq_base + TPS80031_INT_LINCH_GATED,
+ NULL, linch_status_isr, 0, "tps80031-linch", charger);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to register irq %d; error %d\n",
+ charger->irq_base + TPS80031_INT_LINCH_GATED, ret);
+ goto irq_linch_fail;
+ }
+
+ ret = request_threaded_irq(charger->irq_base + TPS80031_INT_FAULT_WDG,
+ NULL, watchdog_expire_isr, 0, "tps80031-wdg", charger);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to register irq %d; error %d\n",
+ charger->irq_base + TPS80031_INT_FAULT_WDG, ret);
+ goto irq_wdg_fail;
+ }
+
+ ret = configure_charging_parameter(charger);
+ if (ret)
+ goto config_fail;
+
+ dev_set_drvdata(&pdev->dev, charger);
+ return ret;
+
+config_fail:
+ free_irq(charger->irq_base + TPS80031_INT_FAULT_WDG, charger);
+irq_wdg_fail:
+ free_irq(charger->irq_base + TPS80031_INT_LINCH_GATED, charger);
+irq_linch_fail:
+ regulator_unregister(charger->rdev);
+regulator_fail:
+ kfree(charger);
+ return ret;
+}
+
+static int tps80031_charger_remove(struct platform_device *pdev)
+{
+ struct tps80031_charger *charger = dev_get_drvdata(&pdev->dev);
+
+ regulator_unregister(charger->rdev);
+ kfree(charger);
+ return 0;
+}
+
+static struct platform_driver tps80031_charger_driver = {
+ .driver = {
+ .name = "tps80031-charger",
+ .owner = THIS_MODULE,
+ },
+ .probe = tps80031_charger_probe,
+ .remove = tps80031_charger_remove,
+};
+
+static int __init tps80031_charger_init(void)
+{
+ return platform_driver_register(&tps80031_charger_driver);
+}
+
+static void __exit tps80031_charger_exit(void)
+{
+ platform_driver_unregister(&tps80031_charger_driver);
+}
+
+subsys_initcall(tps80031_charger_init);
+module_exit(tps80031_charger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("tps80031 battery charger driver");