diff options
author | Syed Rafiuddin <srafiuddin@nvidia.com> | 2012-09-05 11:09:37 +0530 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2012-09-11 13:00:00 -0700 |
commit | 19b640b4d551b07787b86a3a6ffe71508b319870 (patch) | |
tree | 1158d0ccfca02f667b83fdfc633bc59852d85aad /drivers/power | |
parent | 0de78c019ac105d6a48c9ac5e3c6a8aa98b8d048 (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/Kconfig | 9 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/max77665-charger.c | 477 |
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"); |