blob: df426654652705f669fbc37133f30a95b34aa205 [file] [log] [blame]
/*
* smb347_charger.c
* Samsung SMB347 Charger Driver
*
* Copyright (C) 2012 Samsung Electronics
*
*
* 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.
*/
#define DEBUG
#include <linux/battery/sec_charger.h>
static int smb347_i2c_write(struct i2c_client *client,
int reg, u8 *buf)
{
int ret;
ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf);
if (ret < 0)
dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret);
return ret;
}
static int smb347_i2c_read(struct i2c_client *client,
int reg, u8 *buf)
{
int ret;
ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf);
if (ret < 0)
dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret);
return ret;
}
static void smb347_i2c_write_array(struct i2c_client *client,
u8 *buf, int size)
{
int i;
for (i = 0; i < size; i += 3)
smb347_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1);
}
static void smb347_set_command(struct i2c_client *client,
int reg, int datum)
{
int val;
u8 data = 0;
val = smb347_i2c_read(client, reg, &data);
if (val >= 0) {
dev_dbg(&client->dev, "%s : reg(0x%02x): 0x%02x",
__func__, reg, data);
if (data != datum) {
data = datum;
if (smb347_i2c_write(client, reg, &data) < 0)
dev_err(&client->dev,
"%s : error!\n", __func__);
val = smb347_i2c_read(client, reg, &data);
if (val >= 0)
dev_dbg(&client->dev, " => 0x%02x\n", data);
}
}
}
static void smb347_test_read(struct i2c_client *client)
{
u8 data = 0;
u32 addr = 0;
for (addr = 0; addr <= 0x0f; addr++) {
smb347_i2c_read(client, addr, &data);
dev_dbg(&client->dev,
"smb347 addr : 0x%02x data : 0x%02x\n", addr, data);
}
for (addr = 0x30; addr <= 0x3f; addr++) {
smb347_i2c_read(client, addr, &data);
dev_dbg(&client->dev,
"smb347 addr : 0x%02x data : 0x%02x\n", addr, data);
}
}
static void smb347_read_regs(struct i2c_client *client, char *str)
{
u8 data = 0;
u32 addr = 0;
for (addr = 0; addr <= 0x0f; addr++) {
smb347_i2c_read(client, addr, &data);
sprintf(str+strlen(str), "0x%x, ", data);
}
/* "#" considered as new line in application */
sprintf(str+strlen(str), "#");
for (addr = 0x30; addr <= 0x3f; addr++) {
smb347_i2c_read(client, addr, &data);
sprintf(str+strlen(str), "0x%x, ", data);
}
}
static int smb347_read_reg(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
pr_err("%s: err %d, try again!\n", __func__, ret);
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
pr_err("%s: err %d\n", __func__, ret);
}
return ret;
}
static int smb347_get_charging_status(struct i2c_client *client)
{
int status = POWER_SUPPLY_STATUS_UNKNOWN;
u8 data_a = 0;
u8 data_b = 0;
u8 data_c = 0;
u8 data_d = 0;
u8 data_e = 0;
smb347_i2c_read(client, SMB347_STATUS_A, &data_a);
dev_info(&client->dev,
"%s : charger status A(0x%02x)\n", __func__, data_a);
smb347_i2c_read(client, SMB347_STATUS_B, &data_b);
dev_info(&client->dev,
"%s : charger status B(0x%02x)\n", __func__, data_b);
smb347_i2c_read(client, SMB347_STATUS_C, &data_c);
dev_info(&client->dev,
"%s : charger status C(0x%02x)\n", __func__, data_c);
smb347_i2c_read(client, SMB347_STATUS_D, &data_d);
dev_info(&client->dev,
"%s : charger status D(0x%02x)\n", __func__, data_d);
smb347_i2c_read(client, SMB347_STATUS_E, &data_e);
dev_info(&client->dev,
"%s : charger status E(0x%02x)\n", __func__, data_e);
/* At least one charge cycle terminated,
* Charge current < Termination Current
*/
if ((data_c & 0x20) == 0x20) {
/* top-off by full charging */
status = POWER_SUPPLY_STATUS_FULL;
goto charging_status_end;
}
/* Is enabled ? */
if (data_c & 0x01) {
/* check for 0x06 : no charging (0b00) */
/* not charging */
if (!(data_c & 0x06)) {
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
goto charging_status_end;
} else {
status = POWER_SUPPLY_STATUS_CHARGING;
goto charging_status_end;
}
} else
status = POWER_SUPPLY_STATUS_DISCHARGING;
charging_status_end:
return (int)status;
}
static int smb347_get_charging_health(struct i2c_client *client)
{
int health = POWER_SUPPLY_HEALTH_GOOD;
u8 data_a = 0;
u8 data_b = 0;
u8 data_c = 0;
u8 data_d = 0;
u8 data_e = 0;
smb347_i2c_read(client, SMB347_STATUS_A, &data_a);
dev_info(&client->dev,
"%s : charger status A(0x%02x)\n", __func__, data_a);
smb347_i2c_read(client, SMB347_STATUS_B, &data_b);
dev_info(&client->dev,
"%s : charger status B(0x%02x)\n", __func__, data_b);
smb347_i2c_read(client, SMB347_STATUS_C, &data_c);
dev_info(&client->dev,
"%s : charger status C(0x%02x)\n", __func__, data_c);
smb347_i2c_read(client, SMB347_STATUS_D, &data_d);
dev_info(&client->dev,
"%s : charger status D(0x%02x)\n", __func__, data_d);
smb347_i2c_read(client, SMB347_STATUS_E, &data_e);
dev_info(&client->dev,
"%s : charger status E(0x%02x)\n", __func__, data_e);
/* Is enabled ? */
if (data_c & 0x01) {
if (!(data_a & 0x02)) /* Input current is NOT OK */
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
}
return (int)health;
}
static void smb347_allow_volatile_writes(struct i2c_client *client)
{
int val, reg;
u8 data;
reg = SMB347_COMMAND_A;
val = smb347_i2c_read(client, reg, &data);
if ((val >= 0) && !(data & 0x80)) {
dev_dbg(&client->dev,
"%s : reg(0x%02x): 0x%02x", __func__, reg, data);
data |= (0x1 << 7);
if (smb347_i2c_write(client, reg, &data) < 0)
dev_err(&client->dev, "%s : error!\n", __func__);
val = smb347_i2c_read(client, reg, &data);
if (val >= 0) {
data = (u8) data;
dev_dbg(&client->dev, " => 0x%02x\n", data);
}
}
}
static u8 smb347_get_float_voltage_data(
int float_voltage)
{
u8 data;
if (float_voltage < 3500)
float_voltage = 3500;
data = (float_voltage - 3500) / 20;
return data;
}
static u8 smb347_get_input_current_limit_data(
struct sec_charger_info *charger, int input_current)
{
u8 data;
if (input_current <= 300)
data = 0x0;
else if (input_current <= 500)
data = 0x1;
else if (input_current <= 700)
data = 0x2;
else if (input_current <= 900)
data = 0x3;
else if (input_current <= 1200)
data = 0x4;
else if (input_current <= 1500)
data = 0x5;
else if (input_current <= 1800)
data = 0x6;
else if (input_current <= 2000)
data = 0x7;
else if (input_current <= 2200)
data = 0x8;
else if (input_current <= 2500)
data = 0x9;
else
data = 0;
return data;
}
static u8 smb347_get_termination_current_limit_data(
int termination_current)
{
u8 data;
if (termination_current <= 37)
data = 0x0;
else if (termination_current <= 50)
data = 0x1;
else if (termination_current <= 100)
data = 0x2;
else if (termination_current <= 150)
data = 0x3;
else if (termination_current <= 200)
data = 0x4;
else if (termination_current <= 250)
data = 0x5;
else if (termination_current <= 500)
data = 0x6;
else if (termination_current <= 600)
data = 0x7;
else
data = 0;
return data;
}
static u8 smb347_get_fast_charging_current_data(
int fast_charging_current)
{
u8 data;
if (fast_charging_current <= 700)
data = 0x0;
else if (fast_charging_current <= 900)
data = 0x1;
else if (fast_charging_current <= 1200)
data = 0x2;
else if (fast_charging_current <= 1500)
data = 0x3;
else if (fast_charging_current <= 1800)
data = 0x4;
else if (fast_charging_current <= 2000)
data = 0x5;
else if (fast_charging_current <= 2200)
data = 0x6;
else if (fast_charging_current <= 2500)
data = 0x7;
else
data = 0;
return data << 5;
}
static void smb347_charger_function_conrol(
struct i2c_client *client)
{
struct sec_charger_info *charger = i2c_get_clientdata(client);
u8 data;
if (charger->charging_current < 0) {
dev_dbg(&client->dev,
"%s : OTG is activated. Ignore command!\n", __func__);
return;
}
smb347_allow_volatile_writes(client);
if (charger->cable_type ==
POWER_SUPPLY_TYPE_BATTERY) {
/* turn off charger */
smb347_set_command(client,
SMB347_COMMAND_A, 0x80);
/* high current mode for system current */
smb347_set_command(client,
SMB347_COMMAND_B, 0x01);
} else {
/* Pre-charge curr 250mA */
dev_dbg(&client->dev,
"%s : fast charging current (%dmA)\n",
__func__, charger->charging_current);
dev_dbg(&client->dev,
"%s : termination current (%dmA)\n",
__func__, charger->pdata->charging_current[
charger->cable_type].full_check_current_1st);
data = 0x1c;
data |= smb347_get_fast_charging_current_data(
charger->charging_current);
data |= smb347_get_termination_current_limit_data(
charger->pdata->charging_current[
charger->cable_type].full_check_current_1st);
smb347_set_command(client,
SMB347_CHARGE_CURRENT, data);
/* Pin enable control */
/* DCIN Input Pre-bias Enable */
data = 0x01;
if (charger->pdata->chg_gpio_en)
data |= 0x40;
if (charger->pdata->chg_polarity_en)
data |= 0x20;
smb347_set_command(client,
SMB347_PIN_ENABLE_CONTROL, data);
/* Input current limit */
dev_dbg(&client->dev, "%s : input current (%dmA)\n",
__func__, charger->pdata->charging_current
[charger->cable_type].input_current_limit);
data = 0;
data = smb347_get_input_current_limit_data(
charger,
charger->pdata->charging_current
[charger->cable_type].input_current_limit);
smb347_set_command(client,
SMB347_INPUT_CURRENTLIMIT, data);
/*
* Input to System FET by Register
* Enable AICL, VCHG
* Max System voltage =Vflt + 0.1v
* Input Source Priority : USBIN
*/
if (charger->pdata->chg_functions_setting &
SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT)
/* disable AICL */
smb347_set_command(client,
SMB347_VARIOUS_FUNCTIONS, 0x85);
else
/* enable AICL */
smb347_set_command(client,
SMB347_VARIOUS_FUNCTIONS, 0x95);
/* Float voltage, Vprechg : 2.4V */
dev_dbg(&client->dev, "%s : float voltage (%dmV)\n",
__func__, charger->pdata->chg_float_voltage);
data = 0;
data |= smb347_get_float_voltage_data(
charger->pdata->chg_float_voltage);
smb347_set_command(client,
SMB347_FLOAT_VOLTAGE, data);
/* Charge control
* Automatic Recharge disable,
* Current Termination disable,
* BMD disable, Recharge Threshold =50mV,
* APSD disable */
data = 0xC0;
switch (charger->pdata->full_check_type) {
case SEC_BATTERY_FULLCHARGED_CHGGPIO:
case SEC_BATTERY_FULLCHARGED_CHGINT:
case SEC_BATTERY_FULLCHARGED_CHGPSY:
/* Enable Current Termination */
data &= 0xBF;
break;
default:
break;
}
smb347_set_command(client,
SMB347_CHARGE_CONTROL, data);
/* STAT, Timer control : STAT active low,
* Complete time out 1527min.
*/
smb347_set_command(client,
SMB347_STAT_TIMERS_CONTROL, 0x1A);
/* Pin/Enable
* USB 5/1/HC Dual state
* DCIN pre-bias Enable
*/
smb347_set_command(client,
SMB347_PIN_ENABLE_CONTROL, 0x09);
/* Therm control :
* Therm monitor disable,
* Minimum System Voltage 3.60V
*/
smb347_set_command(client,
SMB347_THERM_CONTROL_A, 0x7F);
/* USB selection : USB2.0(100mA/500mA),
* INOK polarity Active low
*/
smb347_set_command(client,
SMB347_SYSOK_USB30_SELECTION, 0x08);
/* Other control
* Low batt detection disable
* Minimum System Voltage 3.60V
*/
smb347_set_command(client,
SMB347_OTHER_CONTROL_A, 0x00);
/* OTG tlim therm control */
smb347_set_command(client,
SMB347_OTG_TLIM_THERM_CONTROL, 0x3F);
/* Limit cell temperature */
smb347_set_command(client,
SMB347_LIMIT_CELL_TEMPERATURE_MONITOR, 0x01);
/* Fault interrupt : Clear */
smb347_set_command(client,
SMB347_FAULT_INTERRUPT, 0x00);
/* STATUS ingerrupt : Clear */
smb347_set_command(client,
SMB347_STATUS_INTERRUPT, 0x00);
/* turn on charger */
smb347_set_command(client,
SMB347_COMMAND_A, 0xC2);
/* HC or USB5 mode */
switch (charger->cable_type) {
case POWER_SUPPLY_TYPE_MAINS:
case POWER_SUPPLY_TYPE_MISC:
/* High-current mode */
data = 0x01;
break;
case POWER_SUPPLY_TYPE_USB:
case POWER_SUPPLY_TYPE_USB_DCP:
case POWER_SUPPLY_TYPE_USB_CDP:
case POWER_SUPPLY_TYPE_USB_ACA:
/* USB5 */
data = 0x02;
break;
default:
/* USB1 */
data = 0x00;
break;
}
smb347_set_command(client,
SMB347_COMMAND_B, data);
}
}
static void smb347_charger_otg_conrol(
struct i2c_client *client)
{
struct sec_charger_info *charger = i2c_get_clientdata(client);
smb347_allow_volatile_writes(client);
if (charger->cable_type ==
POWER_SUPPLY_TYPE_BATTERY) {
/* turn off charger */
smb347_set_command(client,
SMB347_COMMAND_A, 0x80);
} else {
/* turn on OTG */
smb347_set_command(client,
SMB347_COMMAND_A, (0x1 << 4));
}
}
static int smb347_check_charging_status(struct i2c_client *client)
{
int val, reg;
u8 data = 0;
int ret = -1;
reg = SMB347_STATUS_C; /* SMB328A_BATTERY_CHARGING_STATUS_C */
val = smb347_read_reg(client, reg);
if (val >= 0) {
data = (u8) val;
pr_debug("%s : reg (0x%x) = 0x%x\n", __func__, reg, data);
ret = (data & (0x3 << 1)) >> 1;
pr_debug("%s : status = 0x%x\n", __func__, data);
}
return ret;
}
bool sec_hal_chg_init(struct i2c_client *client)
{
smb347_test_read(client);
return true;
}
bool sec_hal_chg_suspend(struct i2c_client *client)
{
return true;
}
bool sec_hal_chg_resume(struct i2c_client *client)
{
return true;
}
bool sec_hal_chg_get_property(struct i2c_client *client,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sec_charger_info *charger = i2c_get_clientdata(client);
u8 data;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = smb347_get_charging_status(client);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
switch (smb347_check_charging_status(client)) {
case 0:
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
case 1:
val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
break;
case 2:
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
break;
case 3:
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
default:
pr_err("%s : get charge type error!\n", __func__);
return -EINVAL;
}
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = smb347_get_charging_health(client);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (charger->charging_current) {
smb347_i2c_read(client, SMB347_STATUS_B, &data);
if (data & 0x20)
switch (data & 0x18) {
case 0:
val->intval = 100;
break;
case 1:
val->intval = 150;
break;
case 2:
val->intval = 200;
break;
case 3:
val->intval = 250;
break;
}
else
switch (data & 0x07) {
case 0:
val->intval = 700;
break;
case 1:
val->intval = 900;
break;
case 2:
val->intval = 1200;
break;
case 3:
val->intval = 1500;
break;
case 4:
val->intval = 1800;
break;
case 5:
val->intval = 2000;
break;
case 6:
val->intval = 2200;
break;
case 7:
val->intval = 2500;
break;
}
} else
val->intval = 0;
dev_dbg(&client->dev,
"%s : set-current(%dmA), current now(%dmA)\n",
__func__, charger->charging_current, val->intval);
break;
default:
return false;
}
return true;
}
bool sec_hal_chg_set_property(struct i2c_client *client,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct sec_charger_info *charger = i2c_get_clientdata(client);
switch (psp) {
/* val->intval : type */
case POWER_SUPPLY_PROP_ONLINE:
/* val->intval : charging current */
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (charger->charging_current < 0)
smb347_charger_otg_conrol(client);
else if (charger->charging_current > 0)
smb347_charger_function_conrol(client);
else {
smb347_charger_function_conrol(client);
smb347_charger_otg_conrol(client);
}
smb347_test_read(client);
break;
default:
return false;
}
return true;
}
ssize_t sec_hal_chg_show_attrs(struct device *dev,
const ptrdiff_t offset, char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct sec_charger_info *chg =
container_of(psy, struct sec_charger_info, psy_chg);
int i = 0;
char *str = NULL;
switch (offset) {
case CHG_DATA:
i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n",
chg->reg_data);
break;
case CHG_REGS:
str = kzalloc(sizeof(char)*1024, GFP_KERNEL);
if (!str)
return -ENOMEM;
smb347_read_regs(chg->client, str);
i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n",
str);
kfree(str);
break;
default:
i = -EINVAL;
break;
}
return i;
}
ssize_t sec_hal_chg_store_attrs(struct device *dev,
const ptrdiff_t offset,
const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct sec_charger_info *chg =
container_of(psy, struct sec_charger_info, psy_chg);
int ret = 0;
int x = 0;
u8 data = 0;
switch (offset) {
case CHG_REG:
if (sscanf(buf, "%x\n", &x) == 1) {
chg->reg_addr = x;
smb347_i2c_read(chg->client,
chg->reg_addr, &data);
chg->reg_data = data;
dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n",
__func__, chg->reg_addr, chg->reg_data);
ret = count;
}
break;
case CHG_DATA:
if (sscanf(buf, "%x\n", &x) == 1) {
data = (u8)x;
dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n",
__func__, chg->reg_addr, data);
smb347_i2c_write(chg->client,
chg->reg_addr, &data);
ret = count;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}