517 lines
12 KiB
C
517 lines
12 KiB
C
/*
|
|
* bq24260_charger.c
|
|
* Samsung bq24260 Charger Driver
|
|
*
|
|
* Copyright (C) 2012 Samsung Electronics
|
|
*
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
#define DEBUG
|
|
|
|
#include <linux/battery/sec_charger.h>
|
|
|
|
static int bq24260_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 bq24260_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 bq24260_i2c_write_array(struct i2c_client *client,
|
|
u8 *buf, int size)
|
|
{
|
|
int i;
|
|
for (i = 0; i < size; i += 3)
|
|
bq24260_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1);
|
|
}
|
|
|
|
static void bq24260_set_command(struct i2c_client *client,
|
|
int reg, int datum)
|
|
{
|
|
int val;
|
|
u8 data = 0;
|
|
val = bq24260_i2c_read(client, reg, &data);
|
|
if (val >= 0) {
|
|
dev_dbg(&client->dev, "%s : reg(0x%02x): 0x%02x(0x%02x)",
|
|
__func__, reg, data, datum);
|
|
if (data != datum) {
|
|
data = datum;
|
|
if (bq24260_i2c_write(client, reg, &data) < 0)
|
|
dev_err(&client->dev,
|
|
"%s : error!\n", __func__);
|
|
val = bq24260_i2c_read(client, reg, &data);
|
|
if (val >= 0)
|
|
dev_dbg(&client->dev, " => 0x%02x\n", data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void bq24260_test_read(struct i2c_client *client)
|
|
{
|
|
u8 data = 0;
|
|
u32 addr = 0;
|
|
for (addr = 0; addr <= 0x06; addr++) {
|
|
bq24260_i2c_read(client, addr, &data);
|
|
dev_dbg(&client->dev,
|
|
"bq24260 addr : 0x%02x data : 0x%02x\n", addr, data);
|
|
}
|
|
}
|
|
|
|
static void bq24260_read_regs(struct i2c_client *client, char *str)
|
|
{
|
|
u8 data = 0;
|
|
u32 addr = 0;
|
|
|
|
for (addr = 0; addr <= 0x06; addr++) {
|
|
bq24260_i2c_read(client, addr, &data);
|
|
sprintf(str+strlen(str), "0x%x, ", data);
|
|
}
|
|
}
|
|
|
|
|
|
static int bq24260_get_charging_status(struct i2c_client *client)
|
|
{
|
|
int status = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
u8 data = 0;
|
|
|
|
bq24260_i2c_read(client, BQ24260_STATUS, &data);
|
|
dev_info(&client->dev,
|
|
"%s : charger status(0x%02x)\n", __func__, data);
|
|
|
|
data = (data & 0x30);
|
|
|
|
switch (data) {
|
|
case 0x00:
|
|
status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
case 0x10:
|
|
status = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case 0x20:
|
|
status = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
case 0x30:
|
|
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
break;
|
|
}
|
|
|
|
return (int)status;
|
|
}
|
|
|
|
static int bq24260_get_charging_health(struct i2c_client *client)
|
|
{
|
|
int health = POWER_SUPPLY_HEALTH_GOOD;
|
|
u8 data = 0;
|
|
|
|
bq24260_i2c_read(client, BQ24260_STATUS, &data);
|
|
dev_info(&client->dev,
|
|
"%s : charger status(0x%02x)\n", __func__, data);
|
|
|
|
if ((data & 0x30) == 0x30) { /* check for fault */
|
|
data = (data & 0x07);
|
|
|
|
switch (data) {
|
|
case 0x01:
|
|
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
break;
|
|
case 0x02:
|
|
health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (int)health;
|
|
}
|
|
|
|
static u8 bq24260_get_float_voltage_data(
|
|
int float_voltage)
|
|
{
|
|
u8 data;
|
|
|
|
if (float_voltage < 3500)
|
|
float_voltage = 3500;
|
|
|
|
data = (float_voltage - 3500) / 20;
|
|
|
|
return data << 2;
|
|
}
|
|
|
|
static u8 bq24260_get_input_current_limit_data(
|
|
int input_current)
|
|
{
|
|
u8 data = 0x00;
|
|
|
|
if (input_current <= 100)
|
|
data = 0x00;
|
|
else if (input_current <= 150)
|
|
data = 0x01;
|
|
else if (input_current <= 500)
|
|
data = 0x02;
|
|
else if (input_current <= 900)
|
|
data = 0x03;
|
|
else if (input_current <= 1000)
|
|
data = 0x04;
|
|
else if (input_current <= 2000)/* will be set as 1950mA */
|
|
data = 0x06;
|
|
else /* No limit */
|
|
data = 0x07;
|
|
|
|
return data << 4;
|
|
}
|
|
|
|
static u8 bq24260_get_termination_current_limit_data(
|
|
int termination_current)
|
|
{
|
|
u8 data;
|
|
|
|
/* default offset 50mA, max 300mA */
|
|
data = (termination_current - 50) / 50;
|
|
|
|
return data;
|
|
}
|
|
|
|
static u8 bq24260_get_fast_charging_current_data(
|
|
int fast_charging_current)
|
|
{
|
|
u8 data;
|
|
|
|
/* default offset 500mA */
|
|
if (fast_charging_current < 500)
|
|
fast_charging_current = 500;
|
|
|
|
data = (fast_charging_current - 500) / 100;
|
|
|
|
return data << 3;
|
|
}
|
|
|
|
static void bq24260_charger_function_conrol(
|
|
struct i2c_client *client)
|
|
{
|
|
struct sec_charger_info *charger = i2c_get_clientdata(client);
|
|
union power_supply_propval val;
|
|
int full_check_type;
|
|
u8 data;
|
|
if (charger->charging_current < 0) {
|
|
dev_dbg(&client->dev,
|
|
"%s : OTG is activated. Ignore command!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (charger->cable_type ==
|
|
POWER_SUPPLY_TYPE_BATTERY) {
|
|
data = 0x00;
|
|
bq24260_i2c_read(client, BQ24260_CONTROL, &data);
|
|
data |= 0x2;
|
|
data &= 0x7f; /* Prevent register reset */
|
|
bq24260_set_command(client,
|
|
BQ24260_CONTROL, data);
|
|
} else {
|
|
data = 0x00;
|
|
bq24260_i2c_read(client, BQ24260_CONTROL, &data);
|
|
/* Enable charging */
|
|
data &= 0x7d; /*default enabled*/
|
|
psy_do_property("battery", get,
|
|
POWER_SUPPLY_PROP_CHARGE_NOW, val);
|
|
if (val.intval == SEC_BATTERY_CHARGING_1ST)
|
|
full_check_type = charger->pdata->full_check_type;
|
|
else
|
|
full_check_type = charger->pdata->full_check_type_2nd;
|
|
/* Termination setting */
|
|
switch (full_check_type) {
|
|
case SEC_BATTERY_FULLCHARGED_CHGGPIO:
|
|
case SEC_BATTERY_FULLCHARGED_CHGINT:
|
|
case SEC_BATTERY_FULLCHARGED_CHGPSY:
|
|
/* Enable Current Termination */
|
|
data |= 0x04;
|
|
break;
|
|
default:
|
|
data &= 0x7b;
|
|
break;
|
|
}
|
|
/* Input current limit */
|
|
dev_dbg(&client->dev, "%s : input current (%dmA)\n",
|
|
__func__, charger->pdata->charging_current
|
|
[charger->cable_type].input_current_limit);
|
|
data &= 0x0F;
|
|
data |= bq24260_get_input_current_limit_data(
|
|
charger->pdata->charging_current
|
|
[charger->cable_type].input_current_limit);
|
|
bq24260_set_command(client,
|
|
BQ24260_CONTROL, data);
|
|
|
|
data = 0x00;
|
|
/* Float voltage */
|
|
dev_dbg(&client->dev, "%s : float voltage (%dmV)\n",
|
|
__func__, charger->pdata->chg_float_voltage);
|
|
data |= bq24260_get_float_voltage_data(
|
|
charger->pdata->chg_float_voltage);
|
|
bq24260_set_command(client,
|
|
BQ24260_VOLTAGE, data);
|
|
|
|
data = 0x00;
|
|
/* Fast charge and Termination current */
|
|
dev_dbg(&client->dev, "%s : fast charging current (%dmA)\n",
|
|
__func__, charger->charging_current);
|
|
data |= bq24260_get_fast_charging_current_data(
|
|
charger->charging_current);
|
|
dev_dbg(&client->dev, "%s : termination current (%dmA)\n",
|
|
__func__, charger->pdata->charging_current[
|
|
charger->cable_type].full_check_current_1st >= 300 ?
|
|
300 : charger->pdata->charging_current[
|
|
charger->cable_type].full_check_current_1st);
|
|
data |= bq24260_get_termination_current_limit_data(
|
|
charger->pdata->charging_current[
|
|
charger->cable_type].full_check_current_1st);
|
|
bq24260_set_command(client,
|
|
BQ24260_CURRENT, data);
|
|
|
|
/* Special Charger Voltage
|
|
* Normal charge current
|
|
*/
|
|
bq24260_i2c_read(client, BQ24260_SPECIAL, &data);
|
|
data &= 0xdf;
|
|
bq24260_set_command(client,
|
|
BQ24260_SPECIAL, data);
|
|
}
|
|
}
|
|
|
|
static void bq24260_charger_otg_conrol(
|
|
struct i2c_client *client)
|
|
{
|
|
struct sec_charger_info *charger = i2c_get_clientdata(client);
|
|
u8 data;
|
|
if (charger->cable_type ==
|
|
POWER_SUPPLY_TYPE_BATTERY) {
|
|
dev_info(&client->dev, "%s : turn off OTG\n", __func__);
|
|
/* turn off OTG */
|
|
bq24260_i2c_read(client, BQ24260_STATUS, &data);
|
|
data &= 0xbf;
|
|
bq24260_set_command(client,
|
|
BQ24260_STATUS, data);
|
|
} else {
|
|
dev_info(&client->dev, "%s : turn on OTG\n", __func__);
|
|
/* turn on OTG */
|
|
bq24260_i2c_read(client, BQ24260_STATUS, &data);
|
|
data |= 0x40;
|
|
bq24260_set_command(client,
|
|
BQ24260_STATUS, data);
|
|
}
|
|
}
|
|
|
|
static int bq24260_get_charge_type(struct i2c_client *client)
|
|
{
|
|
int ret;
|
|
u8 data;
|
|
|
|
bq24260_i2c_read(client, BQ24260_STATUS, &data);
|
|
data = (data & 0x30)>>4;
|
|
|
|
switch (data) {
|
|
case 0x01:
|
|
ret = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
break;
|
|
default:
|
|
ret = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool sec_hal_chg_init(struct i2c_client *client)
|
|
{
|
|
bq24260_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 = bq24260_get_charging_status(client);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
val->intval = bq24260_get_charge_type(client);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = bq24260_get_charging_health(client);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
if (charger->charging_current) {
|
|
/* Rsns 0.068 Ohm */
|
|
bq24260_i2c_read(client, BQ24260_CURRENT, &data);
|
|
val->intval = (data >> 3) * 100 + 500;
|
|
} 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:
|
|
if (charger->pdata->chg_gpio_en) {
|
|
if (gpio_request(charger->pdata->chg_gpio_en,
|
|
"CHG_EN") < 0) {
|
|
dev_err(&client->dev,
|
|
"failed to request vbus_in gpio\n");
|
|
break;
|
|
}
|
|
if (charger->cable_type ==
|
|
POWER_SUPPLY_TYPE_BATTERY)
|
|
gpio_set_value_cansleep(
|
|
charger->pdata->chg_gpio_en,
|
|
charger->pdata->chg_polarity_en ?
|
|
0 : 1);
|
|
else
|
|
gpio_set_value_cansleep(
|
|
charger->pdata->chg_gpio_en,
|
|
charger->pdata->chg_polarity_en ?
|
|
1 : 0);
|
|
gpio_free(charger->pdata->chg_gpio_en);
|
|
}
|
|
/* val->intval : charging current */
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
if (charger->charging_current < 0)
|
|
bq24260_charger_otg_conrol(client);
|
|
else if (charger->charging_current > 0)
|
|
bq24260_charger_function_conrol(client);
|
|
else {
|
|
bq24260_charger_function_conrol(client);
|
|
bq24260_charger_otg_conrol(client);
|
|
}
|
|
bq24260_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_REG:
|
|
i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n",
|
|
chg->reg_addr);
|
|
break;
|
|
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;
|
|
|
|
bq24260_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;
|
|
bq24260_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);
|
|
bq24260_i2c_write(chg->client,
|
|
chg->reg_addr, &data);
|
|
ret = count;
|
|
}
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|