blob: 0058fe999578b752707378808768da1a5d32831e [file] [log] [blame]
/*
* 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>
#include <linux/power/max17042_battery.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;
};
static enum power_supply_property max77665_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property max77665_usb_props[] = {
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;
uint8_t read_val;
ret = max77665_enable_write(charger, true);
if (ret < 0) {
dev_err(charger->dev, "failed to enable write acess\n");
goto error;
}
ret = max77665_update_reg(charger, MAX77665_CHG_CNFG_01, 0xa4);
if (ret < 0) {
dev_err(charger->dev, "Failed in writing to register 0x%x\n",
MAX77665_CHG_CNFG_01);
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 writing to reg: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_write_reg(charger,
MAX77665_CHG_CNFG_09, ret*5);
if (ret < 0) {
dev_err(charger->dev, "Failed writing to reg: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 max77665_enable_charger(struct max77665_charger *charger)
{
int ret = 0;
if (extcon_get_cable_state(charger->edev, "USB")) {
ret = max77665_charger_enable(charger, CHARGER);
if (ret < 0)
goto error;
charger->usb_online = 1;
power_supply_changed(&charger->usb);
charger->plat_data->curr_lim = 500;
}
if (extcon_get_cable_state(charger->edev, "USB-Host")) {
ret = max77665_charger_enable(charger, OTG);
if (ret < 0)
goto error;
}
if (extcon_get_cable_state(charger->edev, "TA")) {
ret = max77665_charger_enable(charger, CHARGER);
if (ret < 0)
goto error;
charger->ac_online = 1;
power_supply_changed(&charger->ac);
}
ret = max77665_charger_init(charger);
if (ret < 0) {
dev_err(charger->dev, "failed to initialize charger\n");
goto error;
}
return 0;
error:
return ret;
}
static int charger_extcon_notifier(struct notifier_block *self,
unsigned long event, void *ptr)
{
return NOTIFY_DONE;
}
static __devinit int max77665_battery_probe(struct platform_device *pdev)
{
int ret = 0;
uint8_t j;
uint32_t read_val;
struct max77665_charger *charger;
charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
if (!charger) {
dev_err(&pdev->dev, "failed to allocate memory status\n");
return -ENOMEM;
}
charger->dev = &pdev->dev;
charger->plat_data = pdev->dev.platform_data;
dev_set_drvdata(&pdev->dev, charger);
/* modify OTP setting of input current limit to 100ma */
ret = max77665_write_reg(charger, MAX77665_CHG_CNFG_09, 0x05);
if (ret < 0) {
dev_err(charger->dev, "failed to write to reg: 0x%x\n",
MAX77665_CHG_CNFG_09);
goto error;
}
/* 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;
}
/* differentiate between E1236 and E1587*/
ret = maxim_get_temp();
if (ret == 0xff) {
dev_err(&pdev->dev, "failed in reading temperaure\n");
return -ENODEV;
} else if ((ret < MIN_TEMP) || (ret > MAX_TEMP)) {
dev_err(&pdev->dev, "E1236 detected exiting driver....\n");
return -ENODEV;
}
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->plat_data->num_cables; j++) {
struct max77665_charger_cable *cable =
&charger->plat_data->cables[j];
cable->nb.notifier_call = charger_extcon_notifier;
ret = extcon_register_interest(&cable->extcon_dev,
"max77665-muic", cable->name, &cable->nb);
if (ret < 0) {
dev_err(charger->dev, "Cannot register for cable: %s\n",
cable->name);
ret = -EINVAL;
}
}
charger->edev = extcon_get_extcon_dev("max77665-muic");
if (!charger->edev)
return -ENODEV;
ret = max77665_enable_charger(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 struct platform_driver max77665_battery_driver = {
.driver = {
.name = "max77665-charger",
.owner = THIS_MODULE,
},
.probe = max77665_battery_probe,
.remove = __devexit_p(max77665_battery_remove),
};
static int __init max77665_battery_init(void)
{
return platform_driver_register(&max77665_battery_driver);
}
static void __exit max77665_battery_exit(void)
{
platform_driver_unregister(&max77665_battery_driver);
}
late_initcall(max77665_battery_init);
module_exit(max77665_battery_exit);
MODULE_DESCRIPTION("MAXIM MAX77665 battery charging driver");
MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>");
MODULE_LICENSE("GPL v2");