diff options
author | Andreas Blaesius <skate4life@gmx.de> | 2016-05-05 13:38:49 +0200 |
---|---|---|
committer | Andreas Blaesius <skate4life@gmx.de> | 2016-05-05 13:38:49 +0200 |
commit | ca0128a03452200d50756147f725a9ad720a9460 (patch) | |
tree | 3ac9cd772c033b4e5ed51e4b8e5b9c06b9f008ca /drivers/power | |
parent | 7397e42ededab993958251a2f32598c7494a7140 (diff) | |
download | kernel_samsung_espresso10-ca0128a03452200d50756147f725a9ad720a9460.zip kernel_samsung_espresso10-ca0128a03452200d50756147f725a9ad720a9460.tar.gz kernel_samsung_espresso10-ca0128a03452200d50756147f725a9ad720a9460.tar.bz2 |
power: add SMB136 and SMB347 charger driver
Based on the sources from GT-P3110_JB_Opensource, with some minor cleanups
(mainly loglevel changes).
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 18 | ||||
-rw-r--r-- | drivers/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/smb136_charger.c | 302 | ||||
-rw-r--r-- | drivers/power/smb347_charger.c | 314 |
4 files changed, 636 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 67d1e81..efe7862 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -266,6 +266,24 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config CHARGER_SMB136 + tristate "SMB136 Charger IC" + depends on I2C + help + Say Y to enable support for the SMB136 charger and sysfs. + The driver supports controlling charger-enable and current-limit + pins based on the status of charger connections with interrupt + handlers. + +config CHARGER_SMB347 + tristate "SMB347 Charger IC" + depends on I2C + help + Say Y to enable support for the SMB347 charger and sysfs. + The driver supports controlling charger-enable and current-limit + pins based on the status of charger connections with interrupt + handlers. + config BATTERY_MANAGER tristate "Battery Manager" help diff --git a/drivers/power/Makefile b/drivers/power/Makefile index f900767..71df3b7 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -39,5 +39,7 @@ obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_BQ2415x) += bq2415x_charger.o obj-$(CONFIG_TWL6030_BCI_BATTERY) += twl6030_bci_battery.o +obj-$(CONFIG_CHARGER_SMB136) += smb136_charger.o +obj-$(CONFIG_CHARGER_SMB347) += smb347_charger.o obj-$(CONFIG_BATTERY_MANAGER) += bat_manager.o obj-$(CONFIG_FUEL_GAUGE) += fg/ diff --git a/drivers/power/smb136_charger.c b/drivers/power/smb136_charger.c new file mode 100644 index 0000000..31729fa --- /dev/null +++ b/drivers/power/smb136_charger.c @@ -0,0 +1,302 @@ +/* + * smb136_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * Ikkeun Kim <iks.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/smb_charger.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/battery.h> + +/* SMB136 Registers. */ +#define SMB136_CHARGE_CURRENT 0x00 +#define SMB136_INPUT_CURRENTLIMIT 0x01 +#define SMB136_FLOAT_VOLTAGE 0x02 +#define SMB136_CHARGE_CONTROL_A 0x03 +#define SMB136_CHARGE_CONTROL_B 0x04 +#define SMB136_PIN_ENABLE_CONTROL 0x05 +#define SMB136_OTG_CONTROL 0x06 +#define SMB136_SAFTY 0x09 + +#define SMB136_COMMAND_A 0x31 +#define SMB136_STATUS_D 0x35 +#define SMB136_STATUS_E 0x36 + +#define CHARGER_STATUS_FULL 0x1 +#define CHARGER_STATUS_CHARGERERR 0x2 +#define CHARGER_STATUS_USB_FAIL 0x3 +#define CHARGER_VBATT_UVLO 0x4 + +struct smb136_chg_data { + struct device *dev; + struct i2c_client *client; + struct smb_charger_data *pdata; + struct smb_charger_callbacks callbacks; +}; + +static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return -EIO; + } + + *data = ret & 0xff; + return 0; +} + +static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + int ret; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int smb136_read_status(struct smb_charger_callbacks *ptr) +{ + struct smb136_chg_data *chg = container_of(ptr, + struct smb136_chg_data, callbacks); + u8 reg_d; + u8 reg_e; + u8 res = 0; + int ret; + + ret = smb136_i2c_read(chg->client, SMB136_STATUS_D, ®_d); + if (ret < 0) { + dev_err(&chg->client->dev, "%s: I2C read fail addr: 0x%x\n", + __func__, SMB136_STATUS_D); + msleep(50); + smb136_i2c_read(chg->client, SMB136_STATUS_D, ®_d); + } + + ret = smb136_i2c_read(chg->client, SMB136_STATUS_E, ®_e); + if (ret < 0) { + dev_err(&chg->client->dev, "%s: I2C read fail addr: 0x%x\n", + __func__, SMB136_STATUS_E); + msleep(50); + smb136_i2c_read(chg->client, SMB136_STATUS_E, ®_e); + } + + dev_dbg(&chg->client->dev, + "addr: 0x%x, data: 0x%x, addr: 0x%x, data: 0x%x\n", + SMB136_STATUS_D, reg_d, SMB136_STATUS_E, reg_e); + + if (reg_e & 0x40) { + dev_dbg(&chg->client->dev, + "Charge current under termination current\n"); + res = CHARGER_STATUS_FULL; + } else if (reg_e & 0x8) { + dev_info(&chg->client->dev, + "Charger status charger err\n"); + res = CHARGER_STATUS_CHARGERERR; + } else if (reg_d & 0x80) { + dev_info(&chg->client->dev, + "USBIN<Vusb-fail OR USBIN>Vovlo\n"); + res = CHARGER_STATUS_USB_FAIL; + } else if (reg_d & 0x1) { + dev_info(&chg->client->dev, + "USBIN<Vusb-fail OR USBIN>Vovlo\n"); + res = CHARGER_VBATT_UVLO; + } + + return res; +} + +static void smb136_set_charging_state(struct smb_charger_callbacks *ptr, + int cable_status) +{ + struct smb136_chg_data *chg = container_of(ptr, + struct smb136_chg_data, callbacks); + u8 data; + + switch (cable_status) { + case CABLE_TYPE_AC: + dev_info(&chg->client->dev, + "charging current limit set to 1.3A/1.5A\n"); + /* HC mode */ + smb136_i2c_write(chg->client, SMB136_COMMAND_A, 0x8c); + + /* Set charge current */ + /* Over HW Rev 09 : 1.5A, else 1.3A */ + data = 0xF4; + if (chg->pdata->hw_revision < 0x9) + data = 0xD4; + smb136_i2c_write(chg->client, SMB136_CHARGE_CURRENT, data); + break; + + case CABLE_TYPE_USB: + /* Prevent in-rush current */ + dev_info(&chg->client->dev, + "charging current limit set to 0.5A\n"); + + /* USBIN 500mA mode */ + smb136_i2c_write(chg->client, SMB136_COMMAND_A, 0x88); + + /* Set charge current to 500mA */ + smb136_i2c_write(chg->client, SMB136_CHARGE_CURRENT, 0x14); + break; + + case CABLE_TYPE_NONE: + default: + dev_info(&chg->client->dev, + "charging current limit set to 0.1A\n"); + /* USB 100mA Mode, USB5/1 Current Levels */ + /* Prevent in-rush current */ + smb136_i2c_write(chg->client, SMB136_COMMAND_A, 0x80); + udelay(10); + + /* Set charge current to 100mA */ + /* Prevent in-rush current */ + smb136_i2c_write(chg->client, SMB136_CHARGE_CURRENT, 0x14); + udelay(10); + } + + /* 2. Change USB5/1/HC Control from Pin to I2C */ + smb136_i2c_write(chg->client, SMB136_PIN_ENABLE_CONTROL, 0x8); + + /* 4. Disable Automatic Input Current Limit */ + /* Over HW Rev 09 : 1.3A, else 1.0A */ + data = 0xE6; + if (chg->pdata->hw_revision < 0x09) + data = 0x66; + smb136_i2c_write(chg->client, SMB136_INPUT_CURRENTLIMIT, data); + + /* 4. Automatic Recharge Disabed */ + smb136_i2c_write(chg->client, SMB136_CHARGE_CONTROL_A, 0x8c); + + /* 5. Safty timer Disabled */ + smb136_i2c_write(chg->client, SMB136_CHARGE_CONTROL_B, 0x28); + + /* 6. Disable USB D+/D- Detection */ + smb136_i2c_write(chg->client, SMB136_OTG_CONTROL, 0x28); + + /* 7. Set Output Polarity for STAT */ + smb136_i2c_write(chg->client, SMB136_FLOAT_VOLTAGE, 0xCA); + + /* 9. Re-load Enable */ + smb136_i2c_write(chg->client, SMB136_SAFTY, 0x4b); +} + +static int smb136_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb136_chg_data *chg; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chg = kzalloc(sizeof(struct smb136_chg_data), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->client = client; + if (!chg->client) { + dev_err(&client->dev, "%s: no client\n", __func__); + ret = -EINVAL; + goto err_client; + } else { + chg->dev = &client->dev; + } + + chg->pdata = client->dev.platform_data; + if (!chg->pdata) { + dev_err(&client->dev, "%s: no platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + i2c_set_clientdata(client, chg); + + chg->callbacks.set_charging_state = smb136_set_charging_state; + chg->callbacks.get_status_reg = smb136_read_status; + if (chg->pdata->register_callbacks) + chg->pdata->register_callbacks(&chg->callbacks); + + dev_info(&client->dev, "probed\n"); + + return 0; + +err_pdata: +err_client: + dev_err(&client->dev, "%s: probe failed\n", __func__); + kfree(chg); + return ret; +} + +static int __devexit smb136_remove(struct i2c_client *client) +{ + struct smb136_chg_data *chg = i2c_get_clientdata(client); + + if (chg->pdata && chg->pdata->unregister_callbacks) + chg->pdata->unregister_callbacks(); + + kfree(chg); + return 0; +} + +static const struct i2c_device_id smb136_id[] = { + { "smb136-charger", 0 }, + { } +}; + + +static struct i2c_driver smb136_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb136-charger", + }, + .id_table = smb136_id, + .probe = smb136_i2c_probe, + .remove = __devexit_p(smb136_remove), + .command = NULL, +}; + + +MODULE_DEVICE_TABLE(i2c, smb136_id); + +static int __init smb136_init(void) +{ + return i2c_add_driver(&smb136_i2c_driver); +} + +static void __exit smb136_exit(void) +{ + i2c_del_driver(&smb136_i2c_driver); +} + +module_init(smb136_init); +module_exit(smb136_exit); + +MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>"); +MODULE_DESCRIPTION("smb136 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb347_charger.c b/drivers/power/smb347_charger.c new file mode 100644 index 0000000..779e32e --- /dev/null +++ b/drivers/power/smb347_charger.c @@ -0,0 +1,314 @@ +/* + * smb347_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/power/smb_charger.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/battery.h> + +/* SMB347 Registers. */ +#define SMB347_CHARGE_CURRENT 0x00 +#define SMB347_INPUT_CURRENTLIMIT 0x01 +#define SMB347_VARIOUS_FUNCTIONS 0x02 +#define SMB347_FLOAT_VOLTAGE 0x03 +#define SMB347_CHARGE_CONTROL 0x04 +#define SMB347_STAT_TIMERS_CONTROL 0x05 +#define SMB347_PIN_ENABLE_CONTROL 0x06 +#define SMB347_THERM_CONTROL_A 0x07 +#define SMB347_SYSOK_USB30_SELECTION 0x08 +#define SMB347_OTHER_CONTROL_A 0x09 +#define SMB347_OTG_TLIM_THERM_CONTROL 0x0A +#define SMB347_LIMIT_CELL_TEMPERATURE_MONITOR 0x0B +#define SMB347_FAULT_INTERRUPT 0x0C +#define SMB347_STATUS_INTERRUPT 0x0D + +#define SMB347_COMMAND_A 0x30 +#define SMB347_COMMAND_B 0x31 +#define SMB347_STATUS_C 0x3D + +/* Status register C */ +#define SMB347_CHARGING_STATUS (1 << 5) +#define SMB347_CHARGER_ERROR (1 << 6) + +#define CHARGER_STATUS_FULL 0x1 +#define CHARGER_STATUS_CHARGERERR 0x2 +#define CHARGER_STATUS_USB_FAIL 0x3 +#define CHARGER_VBATT_UVLO 0x4 + +struct smb347_chg_data { + struct device *dev; + struct i2c_client *client; + struct smb_charger_data *pdata; + struct smb_charger_callbacks callbacks; +}; + +static int smb347_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + return -EIO; + } + + *data = ret & 0xff; + return 0; +} + +static int smb347_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + int ret; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void smb347_charger_init(struct smb347_chg_data *chg) +{ + /* Allow volatile writes to CONFIG registers */ + smb347_i2c_write(chg->client, SMB347_COMMAND_A, 0x80); + + /* Command B : USB1 mode, USB mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00); + + /* Charge curr : Fast-chg 2200mA */ + /* Pre-charge curr 250mA, Term curr 250mA */ + smb347_i2c_write(chg->client, SMB347_CHARGE_CURRENT, 0xDD); + + /* Pin enable control : Charger enable control EN Pin - Active Low */ + /* : USB5/1/HC or USB9/1.5/HC Control - Register Control */ + /* : USB5/1/HC Input state - Tri-state Input */ + smb347_i2c_write(chg->client, SMB347_PIN_ENABLE_CONTROL, 0x60); + + /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */ + smb347_i2c_write(chg->client, SMB347_INPUT_CURRENTLIMIT, 0x66); + + /* Various func. : USBIN primary input, VCHG func. enable */ + smb347_i2c_write(chg->client, SMB347_VARIOUS_FUNCTIONS, 0x87); + + /* Float voltage : 4.2V */ + smb347_i2c_write(chg->client, SMB347_FLOAT_VOLTAGE, 0x63); + + /* Charge control : Auto recharge disable, APSD disable */ + smb347_i2c_write(chg->client, SMB347_CHARGE_CONTROL, 0x80); + + /* STAT, Timer control : STAT active low, Complete time out 1527min. */ + smb347_i2c_write(chg->client, SMB347_STAT_TIMERS_CONTROL, 0x1A); + + /* Therm control : Therm monitor disable */ + smb347_i2c_write(chg->client, SMB347_THERM_CONTROL_A, 0x7F); + + /* USB selection : USB2.0(100mA/500mA), INOK polarity */ + /* Active low */ + smb347_i2c_write(chg->client, SMB347_SYSOK_USB30_SELECTION, 0x08); + + /* Other control */ + smb347_i2c_write(chg->client, SMB347_OTHER_CONTROL_A, 0x1D); + + /* OTG tlim therm control */ + smb347_i2c_write(chg->client, SMB347_OTG_TLIM_THERM_CONTROL, 0x3F); + + /* Limit cell temperature */ + smb347_i2c_write(chg->client, + SMB347_LIMIT_CELL_TEMPERATURE_MONITOR, 0x01); + + /* Fault interrupt : Clear */ + smb347_i2c_write(chg->client, SMB347_FAULT_INTERRUPT, 0x00); + + /* STATUS ingerrupt : Clear */ + smb347_i2c_write(chg->client, SMB347_STATUS_INTERRUPT, 0x00); +} + +static int smb347_read_status(struct smb_charger_callbacks *ptr) +{ + struct smb347_chg_data *chg = container_of(ptr, + struct smb347_chg_data, callbacks); + + u8 res = 0; + u8 reg_c; + int ret; + + ret = smb347_i2c_read(chg->client, SMB347_STATUS_C, ®_c); + if (ret < 0) { + dev_err(&chg->client->dev, "%s: I2C Read fail addr: 0x%x\n", + __func__, SMB347_STATUS_C); + msleep(50); + smb347_i2c_read(chg->client, SMB347_STATUS_C, ®_c); + } + + dev_dbg(&chg->client->dev, + "addr: 0x%x, data: 0x%x\n", SMB347_STATUS_C, reg_c); + + if (reg_c & SMB347_CHARGER_ERROR) + res = CHARGER_STATUS_CHARGERERR; + else if (reg_c & SMB347_CHARGING_STATUS) + res = CHARGER_STATUS_FULL; + + return res; +} + +static void smb347_set_charging_state(struct smb_charger_callbacks *ptr, + int cable_status) +{ + struct smb347_chg_data *chg = container_of(ptr, + struct smb347_chg_data, callbacks); + + if (cable_status) { + /* Init smb347 charger */ + smb347_charger_init(chg); + + switch (cable_status) { + case CABLE_TYPE_AC: + /* Input current limit : DCIN 1800mA, USBIN HC 1800mA */ + smb347_i2c_write(chg->client, + SMB347_INPUT_CURRENTLIMIT, 0x66); + + /* CommandB : High-current mode */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x03); + + dev_info(&chg->client->dev, + "charging current limit set to 1.8A\n"); + break; + case CABLE_TYPE_USB: + /* CommandB : USB5 */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x02); + dev_info(&chg->client->dev, + "charging current limit set to 0.5A\n"); + break; + default: + /* CommandB : USB1 */ + smb347_i2c_write(chg->client, SMB347_COMMAND_B, 0x00); + dev_info(&chg->client->dev, + "charging current limit set to 0.1A\n"); + break; + } + } +} + +static int smb347_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb347_chg_data *chg; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chg = kzalloc(sizeof(struct smb347_chg_data), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + chg->client = client; + if (!chg->client) { + dev_err(&client->dev, "%s: no client\n", __func__); + ret = -EINVAL; + goto err_client; + } else { + chg->dev = &client->dev; + } + + chg->pdata = client->dev.platform_data; + if (!chg->pdata) { + dev_err(&client->dev, "%s: no platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + i2c_set_clientdata(client, chg); + + chg->callbacks.set_charging_state = smb347_set_charging_state; + chg->callbacks.get_status_reg = smb347_read_status; + if (chg->pdata->register_callbacks) + chg->pdata->register_callbacks(&chg->callbacks); + + dev_info(&client->dev, "probed\n"); + + return 0; + +err_pdata: +err_client: + dev_err(&client->dev, "%s: probe failed\n", __func__); + kfree(chg); + return ret; +} + +static int __devexit smb347_remove(struct i2c_client *client) +{ + struct smb347_chg_data *chg = i2c_get_clientdata(client); + + if (chg->pdata && chg->pdata->unregister_callbacks) + chg->pdata->unregister_callbacks(); + + kfree(chg); + return 0; +} + +static const struct i2c_device_id smb347_id[] = { + { "smb347-charger", 0 }, + { } +}; + + +static struct i2c_driver smb347_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb347-charger", + }, + .probe = smb347_i2c_probe, + .remove = smb347_remove, + .id_table = smb347_id, +}; + + +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static int __init smb347_init(void) +{ + return i2c_add_driver(&smb347_i2c_driver); +} + +static void __exit smb347_exit(void) +{ + i2c_del_driver(&smb347_i2c_driver); +} + +module_init(smb347_init); +module_exit(smb347_exit); + +MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>"); +MODULE_DESCRIPTION("smb347 charger driver"); +MODULE_LICENSE("GPL"); |