summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorSyed Rafiuddin <srafiuddin@nvidia.com>2012-09-05 11:09:37 +0530
committerVarun Colbert <vcolbert@nvidia.com>2012-09-11 13:00:00 -0700
commit19b640b4d551b07787b86a3a6ffe71508b319870 (patch)
tree1158d0ccfca02f667b83fdfc633bc59852d85aad /drivers/power
parent0de78c019ac105d6a48c9ac5e3c6a8aa98b8d048 (diff)
power: max77665: add battery charger support
MAX77665 supports battery charging. Add driver for supporting this feature Bug 1011298 Change-Id: I34a1b95836c1fce24548592dd073fdfedcc49669 Signed-off-by: Syed Rafiuddin <srafiuddin@nvidia.com> Reviewed-on: http://git-master/r/130619 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com>
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig9
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/max77665-charger.c477
3 files changed, 487 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 5dbe917cb8ca..f677451d6aaf 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -29,6 +29,15 @@ config APM_POWER
Say Y here to enable support APM status emulation using
battery class devices.
+config CHARGER_MAX77665
+ tristate "MAX77665 battery charger support"
+ depends on MFD_MAX77665 && EXTCON
+ help
+ MAX77665 is a companion pmic for smartphones and tablets
+ which supports battery charging feature.
+ Say Y here to enable battery charging driver support for
+ maxim MAX77665 chipset.
+
config MAX8925_POWER
tristate "MAX8925 battery charger support"
depends on MFD_MAX8925
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 9af5acc30478..9df971ba0d22 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_CHARGER_TPS8003X) += tps80031-charger.o
obj-$(CONFIG_BATTERY_GAUGE_TPS8003X) += tps80031_battery_gauge.o
obj-$(CONFIG_CHARGER_SMB349) += smb349-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
+obj-$(CONFIG_CHARGER_MAX77665) += max77665-charger.o
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o
obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
diff --git a/drivers/power/max77665-charger.c b/drivers/power/max77665-charger.c
new file mode 100644
index 000000000000..b20e21e22271
--- /dev/null
+++ b/drivers/power/max77665-charger.c
@@ -0,0 +1,477 @@
+/*
+ * max77665-charger.c - Battery charger driver
+ *
+ * Copyright (C) 2012 nVIDIA corporation
+ * 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; 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max77665.h>
+#include <linux/max77665-charger.h>
+
+/* fast charge current in mA */
+static const uint32_t chg_cc[] = {
+ 0, 33, 66, 99, 133, 166, 199, 233, 266, 299,
+ 333, 366, 399, 432, 466, 499, 532, 566, 599, 632,
+ 666, 699, 732, 765, 799, 832, 865, 899, 932, 965,
+ 999, 1032, 1065, 1098, 1132, 1165, 1198, 1232, 1265,
+ 1298, 1332, 1365, 1398, 1421, 1465, 1498, 1531, 1565,
+ 1598, 1631, 1665, 1698, 1731, 1764, 1798, 1831, 1864,
+ 1898, 1931, 1964, 1998, 2031, 2064, 2097,
+};
+
+/* primary charge termination voltage in mV */
+static const uint32_t chg_cv_prm[] = {
+ 3650, 3675, 3700, 3725, 3750,
+ 3775, 3800, 3825, 3850, 3875,
+ 3900, 3925, 3950, 3975, 4000,
+ 4025, 4050, 4075, 4100, 4125,
+ 4150, 4175, 4200, 4225, 4250,
+ 4275, 4300, 4325, 4340, 4350,
+ 4375, 4400,
+};
+
+/* maxim input current limit in mA*/
+static const uint32_t chgin_ilim[] = {
+ 0, 100, 200, 300, 400, 500, 600, 700,
+ 800, 900, 1000, 1100, 1200, 1300, 1400,
+ 1500, 1600, 1700, 1800, 1900, 2000, 2100,
+ 2200, 2300, 2400, 2500,
+};
+
+struct max77665_charger {
+ struct device *dev;
+ struct power_supply ac;
+ struct power_supply usb;
+ struct max77665_charger_plat_data *plat_data;
+ uint8_t ac_online;
+ uint8_t usb_online;
+ uint8_t num_cables;
+ struct extcon_dev *edev;
+};
+
+struct max77665_charger_cable {
+ const char *extcon_name;
+ const char *name;
+ struct notifier_block nb;
+ struct max77665_charger *charger;
+ struct extcon_specific_cable_nb *extcon_dev;
+};
+
+
+static enum power_supply_property max77665_ac_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property max77665_usb_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int max77665_write_reg(struct max77665_charger *charger,
+ uint8_t reg, uint8_t value)
+{
+ int ret = 0;
+ struct device *dev = charger->dev;
+
+ ret = max77665_write(dev->parent, MAX77665_I2C_SLAVE_PMIC, reg, value);
+ if (ret < 0)
+ return ret;
+ return ret;
+}
+
+static int max77665_read_reg(struct max77665_charger *charger,
+ uint8_t reg, uint32_t *value)
+{
+ int ret = 0;
+ uint8_t read_val;
+
+ struct device *dev = charger->dev;
+
+ ret = max77665_read(dev->parent, MAX77665_I2C_SLAVE_PMIC,
+ reg, &read_val);
+ if (!ret)
+ *value = read_val;
+
+ return ret;
+}
+
+static int max77665_update_reg(struct max77665_charger *charger,
+ uint8_t reg, uint8_t value)
+{
+ int ret = 0;
+ uint8_t read_val;
+
+ struct device *dev = charger->dev;
+
+ ret = max77665_read(dev->parent, MAX77665_I2C_SLAVE_PMIC,
+ reg, &read_val);
+ if (ret < 0)
+ return ret;
+
+ ret = max77665_write(dev->parent, MAX77665_I2C_SLAVE_PMIC, reg,
+ read_val | value);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+/* Convert current to register value using lookup table */
+static int convert_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 max77665_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77665_charger *chip = container_of(psy,
+ struct max77665_charger, ac);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE)
+ val->intval = chip->ac_online;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int max77665_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77665_charger *chip = container_of(psy,
+ struct max77665_charger, usb);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE)
+ val->intval = chip->usb_online;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int max77665_enable_write(struct max77665_charger *charger, int access)
+{
+ int ret = 0;
+
+ if (access) {
+ /* enable write acces to registers */
+ ret = max77665_write_reg(charger, MAX77665_CHG_CNFG_06, 0x0c);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed to write to reg 0x%x\n",
+ MAX77665_CHG_CNFG_06);
+ return ret;
+ }
+ } else {
+ /* Disable write acces to registers */
+ ret = max77665_write_reg(charger, MAX77665_CHG_CNFG_06, 0x00);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed to write to reg 0x%x\n",
+ MAX77665_CHG_CNFG_06);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int max77665_charger_enable(struct max77665_charger *charger,
+ enum max77665_mode mode)
+{
+ int ret;
+
+ ret = max77665_enable_write(charger, true);
+ if (ret < 0) {
+ dev_err(charger->dev, "failed to enable write acess\n");
+ return ret;
+ }
+
+ if (mode == CHARGER) {
+ /* enable charging */
+ ret = max77665_write_reg(charger, MAX77665_CHG_CNFG_00, 0x05);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed in wirting to register 0x%x:\n",
+ MAX77665_CHG_CNFG_00);
+ return ret;
+ }
+ } else if (mode == OTG) {
+ /* enable OTG mode */
+ ret = max77665_write_reg(charger, MAX77665_CHG_CNFG_00, 0x2A);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed in writing to register 0x%x:\n",
+ MAX77665_CHG_CNFG_00);
+ return ret;
+ }
+ }
+
+ ret = max77665_enable_write(charger, false);
+ if (ret < 0) {
+ dev_err(charger->dev, "failed to disable write acess\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int max77665_charger_init(struct max77665_charger *charger)
+{
+ int ret = 0;
+
+ ret = max77665_enable_write(charger, true);
+ if (ret < 0) {
+ dev_err(charger->dev, "failed to enable write acess\n");
+ goto error;
+ }
+
+ if (charger->plat_data->fast_chg_cc) {
+ ret = convert_to_reg(chg_cc, ARRAY_SIZE(chg_cc),
+ charger->plat_data->fast_chg_cc);
+ if (ret < 0)
+ goto error;
+
+ ret = max77665_update_reg(charger, MAX77665_CHG_CNFG_02, ret);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed in writing to register 0x%x\n",
+ MAX77665_CHG_CNFG_02);
+ goto error;
+ }
+ }
+
+ if (charger->plat_data->term_volt) {
+ ret = convert_to_reg(chg_cv_prm, ARRAY_SIZE(chg_cv_prm),
+ charger->plat_data->term_volt);
+ if (ret < 0)
+ goto error;
+
+ ret = max77665_update_reg(charger, MAX77665_CHG_CNFG_04, ret+1);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed in writing to register 0x%x\n",
+ MAX77665_CHG_CNFG_04);
+ goto error;
+ }
+ }
+
+ if (charger->plat_data->curr_lim) {
+ ret = convert_to_reg(chgin_ilim, ARRAY_SIZE(chgin_ilim),
+ charger->plat_data->curr_lim);
+ if (ret < 0)
+ goto error;
+
+ ret = max77665_update_reg(charger, MAX77665_CHG_CNFG_09, ret+5);
+ if (ret < 0) {
+ dev_err(charger->dev, "Failed in writing to register 0x%x\n",
+ MAX77665_CHG_CNFG_09);
+ goto error;
+ }
+ }
+error:
+ ret = max77665_enable_write(charger, false);
+ if (ret < 0) {
+ dev_err(charger->dev, "failed to enable write acess\n");
+ return ret;
+ }
+ return ret;
+}
+
+static int charger_extcon_notifier(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ int ret;
+ struct max77665_charger_cable *cable =
+ container_of(self, struct max77665_charger_cable, nb);
+
+ cable->charger->ac_online = 0;
+ cable->charger->usb_online = 0;
+
+ if (extcon_get_cable_state(cable->charger->edev, "0")) {
+ ret = max77665_charger_enable(cable->charger, CHARGER);
+ if (ret < 0)
+ goto error;
+
+ cable->charger->usb_online = 1;
+ power_supply_changed(&cable->charger->usb);
+ }
+
+ if (extcon_get_cable_state(cable->charger->edev, "1")) {
+ ret = max77665_charger_enable(cable->charger, OTG);
+ if (ret < 0)
+ goto error;
+ }
+
+ if (extcon_get_cable_state(cable->charger->edev, "2")) {
+ ret = max77665_charger_enable(cable->charger, CHARGER);
+ if (ret < 0)
+ goto error;
+
+ cable->charger->ac_online = 1;
+ power_supply_changed(&cable->charger->ac);
+ }
+
+error:
+ return NOTIFY_DONE;
+}
+
+static int max77665_extcon_init(struct max77665_charger *charger,
+ struct max77665_charger_cable *cable)
+{
+ int ret = 0;
+
+ cable->nb.notifier_call = charger_extcon_notifier;
+
+ ret = extcon_register_interest(cable->extcon_dev,
+ cable->extcon_name, cable->name, &cable->nb);
+ if (ret < 0) {
+ dev_err(charger->dev, "Cannot register for %s(cable: %s).\n",
+ cable->extcon_name, cable->name);
+
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static __devinit int max77665_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ uint8_t j;
+ uint32_t read_val;
+ struct max77665_charger *charger;
+ struct max77665_charger_plat_data *pdata;
+
+ pdata = dev_get_platdata(pdev->dev.parent);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data available\n");
+ return -ENODEV;
+ }
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
+ if (!charger) {
+ dev_err(&pdev->dev, "failed to allocate memory status\n");
+ return -ENOMEM;
+ }
+
+ dev_set_drvdata(&pdev->dev, charger);
+
+ charger->dev = &pdev->dev;
+
+ /* check for battery presence */
+ ret = max77665_read_reg(charger, MAX77665_CHG_DTLS_01, &read_val);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error in reading register 0x%x\n",
+ MAX77665_CHG_DTLS_01);
+ return -ENODEV;
+ } else if (!(read_val & 0xe0)) {
+ dev_err(&pdev->dev, "Battery not detected exiting driver..\n");
+ return -ENODEV;
+ }
+
+ charger->plat_data->fast_chg_cc = pdata->fast_chg_cc;
+ charger->plat_data->term_volt = pdata->term_volt;
+ charger->plat_data->curr_lim = pdata->curr_lim;
+
+ charger->ac.name = "ac";
+ charger->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ charger->ac.get_property = max77665_ac_get_property;
+ charger->ac.properties = max77665_ac_props;
+ charger->ac.num_properties = ARRAY_SIZE(max77665_ac_props);
+
+ ret = power_supply_register(charger->dev, &charger->ac);
+ if (ret) {
+ dev_err(charger->dev, "failed: power supply register\n");
+ goto error;
+ }
+
+ charger->usb.name = "usb";
+ charger->usb.type = POWER_SUPPLY_TYPE_USB;
+ charger->usb.get_property = max77665_usb_get_property;
+ charger->usb.properties = max77665_usb_props;
+ charger->usb.num_properties = ARRAY_SIZE(max77665_usb_props);
+
+ ret = power_supply_register(charger->dev, &charger->usb);
+ if (ret) {
+ dev_err(charger->dev, "failed: power supply register\n");
+ goto pwr_sply_error;
+ }
+
+ for (j = 0 ; j < charger->num_cables ; j++) {
+ struct max77665_charger_cable *cable =
+ &charger->plat_data->cables[j];
+
+ ret = max77665_extcon_init(charger, cable);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot initialize extcon");
+ goto chrg_error;
+ }
+ }
+
+ ret = max77665_charger_init(charger);
+ if (ret < 0) {
+ dev_err(charger->dev, "failed to initialize charger\n");
+ goto chrg_error;
+ }
+
+ return 0;
+
+chrg_error:
+ power_supply_unregister(&charger->usb);
+pwr_sply_error:
+ power_supply_unregister(&charger->ac);
+error:
+ return ret;
+}
+
+static int __devexit max77665_battery_remove(struct platform_device *pdev)
+{
+ struct max77665_charger *charger = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&charger->ac);
+ power_supply_unregister(&charger->usb);
+
+ return 0;
+}
+
+static const struct platform_device_id max77665_battery_id[] = {
+ { "max77665-battery", 0 },
+};
+
+static struct platform_driver max77665_battery_driver = {
+ .driver = {
+ .name = "max77665-battery",
+ .owner = THIS_MODULE,
+ },
+ .probe = max77665_battery_probe,
+ .remove = __devexit_p(max77665_battery_remove),
+ .id_table = max77665_battery_id,
+};
+
+module_platform_driver(max77665_battery_driver);
+
+MODULE_DESCRIPTION("MAXIM MAX77665 battery charging driver");
+MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>");
+MODULE_LICENSE("GPL v2");