Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

350
drivers/battery/Kconfig Normal file
View file

@ -0,0 +1,350 @@
config BATTERY_SAMSUNG
tristate "samsung battery driver"
help
Say Y to include support for samsung battery driver
This battery driver integrated all battery-related functions
To see battery-related functions,
refer to sec_charging_common.h
config BATTERY_SWELLING
bool "prevent battery swelling"
help
Say Y to include support for prevent battery swelling
config BATTERY_SWELLING_SELF_DISCHARGING
bool "prevent battery swelling with self discharging"
help
Say Y to include support for prevent battery swelling with self discharging
config INBATTERY
bool "prevent inbattery"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support for prevent inbattery
config BATTERY_AGE_FORECAST
tristate "battery age forecast"
default n
depends on BATTERY_SWELLING
help
Say Y to use calc time to full function.
config SW_SELF_DISCHARGING
bool "enable sw_self_discharging"
default n
help
Say Y to enable CONFIG_SW_SELF_DISCHARGING
# Fuel Gauge
config FUELGAUGE_DUMMY
tristate "dummy fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for dummy fuel gauge driver.
This driver source code implemented
skeleton source code for fuel gauge functions.
config FUELGAUGE_MAX17042
tristate "MAX17042 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX17042 fuel gauge driver.
This fuel-gauge can be used in voltage-tracking mode
or coulomb-counting mode.
config FUELGAUGE_MAX17042_VOLTAGE_TRACKING
tristate "use MAX17042 fuel gauge only as voltage tracking"
default n
depends on FUELGAUGE_MAX17042
help
Say Y to use MAX17042 fuel gauge
only as voltage tracking.
This mode is for target that consumes low current
like smart-phone.
config FUELGAUGE_MAX17042_COULOMB_COUNTING
tristate "use MAX17042 fuel gauge as coulomb counting (including voltage tracking)"
default n
depends on FUELGAUGE_MAX17042
help
Say Y to use MAX17042 fuel gauge
as coulomb counting (including voltage tracking).
This mode is for target that consumes high current
like tablet.
config FUELGAUGE_MAX17048
tristate "MAX17048 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX17048 fuel gauge driver.
This fuel-gauge can be used
only in voltage-tracking mode.
config FUELGAUGE_MAX17050
tristate "MAX17050 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX17047 or MAX17050 fuel gauge driver.
This fuel-gauge can be used in voltage-tracking mode
or coulomb-counting mode.
config FUELGAUGE_MAX17050_VOLTAGE_TRACKING
tristate "use MAX17050 fuel gauge only as voltage tracking"
default n
depends on FUELGAUGE_MAX17050
help
Say Y to use MAX17050 fuel gauge
only as voltage tracking.
This mode is for target that consumes low current
like smart-phone.
config FUELGAUGE_MAX17050_COULOMB_COUNTING
tristate "use MAX17050 fuel gauge as coulomb counting (including voltage tracking)"
default n
depends on FUELGAUGE_MAX17050
help
Say Y to use MAX17050 fuel gauge
as coulomb counting (including voltage tracking).
This mode is for target that consumes high current
like tablet.
config FUELGAUGE_MAX77823
tristate "MAX77823 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX17047 or MAX17050 fuel gauge driver.
This fuel-gauge can be used in voltage-tracking mode
or coulomb-counting mode.
config FUELGAUGE_MAX77843
tristate "MAX77843 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX17047 or MAX17050 fuel gauge driver.
This fuel-gauge can be used in voltage-tracking mode
or coulomb-counting mode.
config FUELGAUGE_MAX77833
tristate "MAX77833 fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX77833 fuel gauge driver.
This fuel-gauge can be used in coulomb-counting mode.
config FUELGAUGE_MAX77823_VOLTAGE_TRACKING
tristate "use MAX77823 fuel gauge only as voltage tracking"
default n
depends on FUELGAUGE_MAX77823
help
Say Y to use MAX17050 fuel gauge
only as voltage tracking.
This mode is for target that consumes low current
like smart-phone.
config FUELGAUGE_MAX77823_COULOMB_COUNTING
tristate "use MAX77823 fuel gauge as coulomb counting (including voltage tracking)"
default n
depends on FUELGAUGE_MAX77823
help
Say Y to use MAX77823 fuel gauge
as coulomb counting (including voltage tracking).
This mode is for target that consumes high current
like tablet.
config FUELGAUGE_S2MPU06
tristate "S2MPU06 fuel gauge driver"
default n
depends on MFD_S2MPU06 && I2C
help
Say Y to include support
for S2MPU06 fuel gauge driver.
This fuel-gauge can be used in voltage mode.
# Charger
config CHARGER_DUMMY
tristate "dummy charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for dummy charger driver.
This driver source code implemented
skeleton source code for charger functions.
config CHARGER_MAX8903
tristate "MAX8903 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for MAXIM MAX8903 charger driver.
This driver source code implemented
all functions for MAX8903 charger.
config CHARGER_SMB328
tristate "SMB328 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for Summit SMB328 charger driver.
This driver source code implemented
all functions for SMB328 charger.
config CHARGER_SMB347
tristate "SMB347 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for Summit SMB347 charger driver.
This driver source code implemented
all functions for SMB347 charger.
config CHARGER_BQ24157
tristate "BQ24157 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for TI BQ24157 charger driver.
This driver source code implemented
all functions for BQ24157 charger.
config CHARGER_BQ24190
tristate "BQ24190 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for TI BQ24190 charger driver.
This driver source code implemented
all functions for BQ24190 charger.
config CHARGER_BQ24191
tristate "BQ24191 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for TI BQ24191 charger driver.
This driver source code implemented
all functions for BQ24191 charger.
config CHARGER_BQ24260
tristate "BQ24260 charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for TI BQ24260 charger driver.
This driver source code implemented
all functions for BQ24260 charger.
config CHARGER_MAX77693
tristate "MAX77693 battery charger support"
depends on MFD_MAX77693 && I2C
help
Say Y here to enable support for the MAX77693 charger
MAX77693 incluse muic, pmic, haptic, led,
flash driver.
You have to define MFD_MAX77693
config CHARGER_MAX77823
tristate "MAX77823 battery charger support"
depends on BATTERY_SAMSUNG
help
Say Y here to enable support for the MAX77823 charger
config CHARGER_MAX77843
tristate "MAX77843 battery charger support"
depends on BATTERY_SAMSUNG
help
Say Y here to enable support for the MAX77843 charger
config CHARGER_MAX77833
tristate "MAX77833 battery charger support"
depends on BATTERY_SAMSUNG
help
Say Y here to enable support for the MAX77833 charger
config CHARGER_MAX77804
tristate "MAX77804 battery charger support"
depends on (MFD_MAX77804 || MFD_MAX77804K) && I2C
help
Say Y here to enable support for the MAX77804 charger
MAX77804 incluse muic, pmic, haptic, led,
flash driver.
You have to define MFD_MAX77804
config CHARGER_MAX77888
tristate "MAX77888 battery charger support"
depends on (MFD_MAX77888) && I2C
help
Say Y here to enable support for the MAX77888 charger
MAX77888 incluse muic, pmic, haptic, led,
flash driver.
You have to define MFD_MAX77888
config CHARGER_S2MU003
tristate "S2MU003 charger support"
depends on (MFD_S2MU003) && I2C
help
Say Y here to enable support for the S2MU003 charger
S2MU003 incluse pmic, led driver.
You have to define MFD_S2MU003
config CHARGER_S2MPU06
tristate "S2MPU06 charger support"
depends on MFD_S2MPU06 && I2C
help
Say Y here to enable support for the S2MU003 charger
S2MU003 incluse pmic, led driver.
You have to define MFD_S2MU003
config WIRELESS_CHARGER_BQ51221
tristate "bq51221 battery charger support"
depends on BATTERY_SAMSUNG && I2C
help
Say Y here to enable support for the bq51221 charger
bq51221 wireless charger driver.
config AFC_CHARGER_MODE
bool "afc charging support in sec battery driver"
default n
depends on CHARGER_MAX77843
help
Say Y to include support for sec afc charging support
config SAMSUNG_LPM_MODE
bool "Off charging mode support in sec battery driver"
default n
help
Say Y to include support for sec off charging support
This value defined at bootloader.
Before enable this feature,
implemet power off charging in the bootloader.
config EN_OOPS
bool "enable oops filter"
default n
help
Say Y to enable oops filter

26
drivers/battery/Makefile Normal file
View file

@ -0,0 +1,26 @@
obj-$(CONFIG_BATTERY_SAMSUNG) += sec_battery.o
obj-$(CONFIG_OF) += sec_adc.o
obj-$(CONFIG_FUELGAUGE_MAX17042) += max17042_fuelgauge.o sec_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_MAX17048) += max17048_fuelgauge.o sec_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_MAX17050) += max17050_fuelgauge.o sec_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_MAX77823) += max77823_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_MAX77843) += max77843_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_MAX77833) += max77833_fuelgauge.o
obj-$(CONFIG_FUELGAUGE_S2MPU06) += s2mpu06_fuelgauge.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_SMB328) += smb328_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347_charger.o sec_charger.o
obj-$(CONFIG_CHARGER_BQ24157) += bq24157_charger.o sec_charger.o
obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24191) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24260) += bq24260_charger.o sec_charger.o
obj-$(CONFIG_CHARGER_MAX77804) += max77804_charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX77823) += max77823_charger.o
obj-$(CONFIG_CHARGER_MAX77843) += max77843_charger.o
obj-$(CONFIG_CHARGER_MAX77888) += max77888_charger.o
obj-$(CONFIG_CHARGER_MAX77833) += max77833_charger.o
obj-$(CONFIG_CHARGER_S2MU003) += s2mu003_charger.o
obj-$(CONFIG_WIRELESS_CHARGER_BQ51221) += bq51221_charger.o
obj-$(CONFIG_CHARGER_S2MPU06) += s2mpu06_charger.o

View file

@ -0,0 +1,516 @@
/*
* 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;
}

View file

@ -0,0 +1,759 @@
/*
* bq51221_charger.c
* Samsung bq51221 Charger Driver
*
* Copyright (C) 2014 Samsung Electronics
* Yeongmi Ha <yeongmi86.ha@samsung.com>
*
* 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.
*
*/
#include <linux/battery/charger/bq51221_charger.h>
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/kernel.h>
#define ENABLE 1
#define DISABLE 0
static enum power_supply_property sec_charger_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL,
};
static int bq51221_read_device(struct i2c_client *client,
u8 reg, u8 bytes, void *dest)
{
int ret;
if (bytes > 1) {
ret = i2c_smbus_read_i2c_block_data(client, reg, bytes, dest);
} else {
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
return ret;
*(unsigned char *)dest = (unsigned char)ret;
}
return ret;
}
static int bq51221_reg_read(struct i2c_client *client, u8 reg)
{
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
u8 data;
int ret = 0;
mutex_lock(&charger->io_lock);
ret = bq51221_read_device(client, reg, 1, &data);
mutex_unlock(&charger->io_lock);
if (ret < 0) {
pr_err("%s: can't read reg(0x%x), ret(%d)\n", __func__, reg, ret);
return ret;
} else {
pr_err("%s: can read reg(0x%x), ret(%d)\n", __func__, reg, ret);
return (int)data;
}
}
static int bq51221_reg_write(struct i2c_client *client, u8 reg, u8 data)
{
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
int ret = 0;
mutex_lock(&charger->io_lock);
ret = i2c_smbus_write_byte_data(client, reg, data);
mutex_unlock(&charger->io_lock);
if (ret < 0)
pr_err("%s: can't write reg(0x%x), ret(%d)\n", __func__, reg, ret);
return ret;
}
static int bq51221_get_pad_mode(struct i2c_client *client)
{
int ret = 0;
int retry_cnt =0;
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
if(charger->pdata->pad_mode != BQ51221_PAD_MODE_NONE) {
/* read pad mode PMA = 1, WPC = 0 (Status bit)*/
ret = bq51221_reg_read(client, BQ51221_REG_INDICATOR);
if(ret < 0) {
while(retry_cnt++ < 3) {
msleep(50);
pr_info("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret);
/* read pad mode PMA = 1, WPC = 0 (Status bit)*/
ret = bq51221_reg_read(client, BQ51221_REG_INDICATOR);
if(ret >= 0)
break;
}
}
pr_info("%s pad_mode = %d \n", __func__,ret);
if(ret >= 0) {
ret &= BQ51221_POWER_MODE_MASK;
if(ret == 0)
charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC;
else if (ret == 1)
charger->pdata->pad_mode = BQ51221_PAD_MODE_PMA;
else
charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC;
}
else
ret = 0;
}
return ret;
}
int bq51221_set_full_charge_info(struct i2c_client *client)
{
int data = 0;
int ret = 0, i = 0;
int retry_cnt =0;
pr_info("%s\n", __func__);
for(i=0; i< 3; i++) {
/* send cs100 */
ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_CS100);
ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_CS100_VALUE);
if(ret < 0) {
while(retry_cnt++ < 3) {
msleep(50);
pr_info("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret);
/* send cs100 */
ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_CS100);
ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_CS100_VALUE);
if(ret >= 0)
break;
}
return ret;
}
/* send end packet */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= !BQ51221_SEND_USER_PKT_DONE_MASK;
ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data);
/* check packet error */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= BQ51221_SEND_USER_PKT_ERR_MASK;
data = data >> 5;
pr_info("%s error pkt = 0x%x \n",__func__, data);
if(data == BQ51221_PTK_ERR_NO_ERR) {
pr_err("%s sent CS100!\n",__func__);
ret = 1;
} else {
pr_err("%s can not send CS100! err pkt = 0x%x\n",__func__, data);
ret = -1;
}
msleep(300);
}
return ret;
}
int bq51221_set_voreg(struct i2c_client *client, int default_value)
{
u8 data = 0;
int ret = 0;
int retry_cnt =0;
union power_supply_propval value;
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
#if defined(CONFIG_WIRELESS_CHARGER_INBATTERY_5V_FIX)
return 0;
#endif
if (charger->pdata->pad_mode == BQ51221_PAD_MODE_PMA) {
pr_info("%s PMA MODE, do not set Voreg \n", __func__);
return 0;
}
psy_do_property("battery", get,
POWER_SUPPLY_PROP_CAPACITY, value);
if ((value.intval >= charger->pdata->wireless_cc_cv) && !default_value)
default_value = 1;
if (default_value) {
/* init VOREG with default value */
ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01);
if(ret < 0) {
while(retry_cnt++ < 3) {
msleep(50);
pr_debug("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret);
/* init VOREG with default value */
ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01);
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER);
if(ret >= 0) {
pr_debug("%s VOREG = 0x%x \n", __func__, data);
break;
}
}
}
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER);
pr_info("%s VOREG = 0x%x 5.0V, cnt(%d)\n", __func__, data, retry_cnt);
} else {
ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x02);
if(ret < 0) {
while(retry_cnt++ < 3) {
msleep(50);
pr_debug("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret);
/* init VOREG with default value */
ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x02);
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER);
if(ret >= 0) {
pr_debug("%s VOREG = 0x%x \n", __func__, data);
break;
}
}
}
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER);
pr_info("%s VOREG = 0x%x 5.5V, cnt(%d)\n", __func__, data, retry_cnt);
}
return ret;
}
int bq51221_set_end_power_transfer(struct i2c_client *client, int ept_mode)
{
int pad_mode = 0;
int data = 0;
int ret = 0;
pr_info("%s\n", __func__);
switch(ept_mode)
{
case END_POWER_TRANSFER_CODE_OVER_TEMPERATURE:
/* read pad mode PMA = 1, WPC = 0 (Status bit)*/
pad_mode = bq51221_reg_read(client, BQ51221_REG_INDICATOR);
pr_info("%s pad_mode = %d \n", __func__,pad_mode);
if(pad_mode > 0)
pad_mode &= BQ51221_POWER_MODE_MASK;
if(pad_mode) {
pr_info("%s PMA MODE, send EOC \n", __func__);
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data |= BQ51221_SEND_EOC_MASK;
ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data);
} else {
pr_info("%s WPC MODE, send EPT-OT \n", __func__);
ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_EPT);
ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_EPT_CODE_OVER_TEMPERATURE);
/* send end packet */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= !BQ51221_SEND_USER_PKT_DONE_MASK;
ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data);
/* check packet error */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= BQ51221_SEND_USER_PKT_ERR_MASK;
data = data >> 5;
pr_info("%s error pkt = 0x%x \n",__func__, data);
if(data != BQ51221_PTK_ERR_NO_ERR) {
pr_err("%s can not send ept! err pkt = 0x%x\n",__func__, data);
ret = -1;
}
}
break;
case END_POWER_TRANSFER_CODE_RECONFIGURE:
pr_info("%s send EPT-Reconfigure \n", __func__);
ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_EPT);
ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_EPT_CODE_RECONFIGURE);
/* send end packet */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= !BQ51221_SEND_USER_PKT_DONE_MASK;
ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data);
/* check packet error */
data = bq51221_reg_read(client, BQ51221_REG_MAILBOX);
data &= BQ51221_SEND_USER_PKT_ERR_MASK;
data = data >> 5;
pr_info("%s error pkt = 0x%x \n",__func__, data);
if(data != BQ51221_PTK_ERR_NO_ERR) {
pr_err("%s can not send ept! err pkt = 0x%x\n",__func__, data);
ret = -1;
}
break;
default:
pr_info("%s this ept mode is not reserved \n",__func__);
ret = -1;
break;
}
return ret;
}
void bq51221_wireless_chg_init(struct i2c_client *client)
{
int data = 0;
union power_supply_propval value;
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
pr_info("%s\n", __func__);
psy_do_property("battery", get,
POWER_SUPPLY_PROP_CAPACITY, value);
/* init I limit(IOREG) */
bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER2, BQ51221_IOREG_100_VALUE);
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER2);
pr_info("%s IOREG = 0x%x \n", __func__, data);
/* init CEP timing */
/* init RCVD PWR */
/* read pad mode */
bq51221_get_pad_mode(client);
pr_info("%s siop = %d \n" ,__func__, charger->pdata->siop_level );
if ((value.intval < charger->pdata->wireless_cc_cv) &&
(charger->pdata->pad_mode == BQ51221_PAD_MODE_WPC) &&
(charger->pdata->siop_level == 100) &&
!charger->pdata->default_voreg) {
/* set VOREG set 5.5V*/
bq51221_set_voreg(charger->client, 0);
} else {
/* init VOREG with default value */
bq51221_set_voreg(charger->client, 1);
}
}
static void bq51221_detect_work(
struct work_struct *work)
{
struct bq51221_charger_data *charger =
container_of(work, struct bq51221_charger_data, wpc_work.work);
pr_info("%s\n", __func__);
bq51221_wireless_chg_init(charger->client);
}
static int bq51221_chg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq51221_charger_data *charger =
container_of(psy, struct bq51221_charger_data, psy_chg);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
pr_info("%s charger->pdata->cs100_status %d \n",__func__,charger->pdata->cs100_status);
val->intval = charger->pdata->cs100_status;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = bq51221_get_pad_mode(charger->client);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = charger->pdata->siop_level;
break;
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
break;
default:
return -EINVAL;
}
return 0;
}
static int bq51221_chg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq51221_charger_data *charger =
container_of(psy, struct bq51221_charger_data, psy_chg);
union power_supply_propval value;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if(val->intval == POWER_SUPPLY_STATUS_FULL) {
charger->pdata->cs100_status = bq51221_set_full_charge_info(charger->client);
pr_info("%s charger->pdata->cs100_status %d \n",__func__,charger->pdata->cs100_status);
}
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (!charger->pdata->default_voreg &&
!delayed_work_pending(&charger->wpc_work))
bq51221_set_voreg(charger->client, val->intval);
break;
case POWER_SUPPLY_PROP_HEALTH:
if(val->intval == POWER_SUPPLY_HEALTH_OVERHEAT ||
val->intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT ||
val->intval == POWER_SUPPLY_HEALTH_COLD)
bq51221_set_end_power_transfer(charger->client, END_POWER_TRANSFER_CODE_OVER_TEMPERATURE);
else if(val->intval == POWER_SUPPLY_HEALTH_UNDERVOLTAGE)
bq51221_set_end_power_transfer(charger->client, END_POWER_TRANSFER_CODE_RECONFIGURE);
break;
case POWER_SUPPLY_PROP_ONLINE:
if(val->intval == POWER_SUPPLY_TYPE_WIRELESS) {
charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC;
queue_delayed_work(charger->wqueue, &charger->wpc_work,
msecs_to_jiffies(5000));
wake_lock_timeout(&charger->wpc_wake_lock, HZ * 6);
} else if(val->intval == POWER_SUPPLY_TYPE_BATTERY) {
bq51221_set_voreg(charger->client, 1);
charger->pdata->pad_mode = BQ51221_PAD_MODE_NONE;
cancel_delayed_work(&charger->wpc_work);
}
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
charger->pdata->siop_level = val->intval;
pr_info("%s siop = %d \n",__func__, charger->pdata->siop_level);
break;
case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
if (val->intval) {
charger->pdata->default_voreg = true;
bq51221_set_voreg(charger->client, val->intval);
} else {
charger->pdata->default_voreg = false;
psy_do_property("battery", get,
POWER_SUPPLY_PROP_STATUS, value);
if ((value.intval == POWER_SUPPLY_STATUS_CHARGING) &&
(charger->pdata->pad_mode == BQ51221_PAD_MODE_WPC)) {
queue_delayed_work(charger->wqueue, &charger->wpc_work,
msecs_to_jiffies(5000));
wake_lock_timeout(&charger->wpc_wake_lock, HZ * 6);
}
}
break;
default:
return -EINVAL;
}
return 0;
}
#if 0 /* this part is for bq51221s */
static void bq51221_chg_isr_work(struct work_struct *work)
{
//struct bq51221_charger_data *charger =
// container_of(work, struct bq51221_charger_data, isr_work.work);
pr_info("%s \n",__func__);
}
static irqreturn_t bq51221_chg_irq_thread(int irq, void *irq_data)
{
struct bq51221_charger_data *charger = irq_data;
pr_info("%s \n",__func__);
schedule_delayed_work(&charger->isr_work, 0);
return IRQ_HANDLED;
}
#endif
static int bq51221_chg_parse_dt(struct device *dev,
bq51221_charger_platform_data_t *pdata)
{
int ret = 0;
struct device_node *np = dev->of_node;
if (!np) {
pr_info("%s: np NULL\n", __func__);
return 1;
}
np = of_find_node_by_name(NULL, "battery");
if (!np) {
pr_err("%s np NULL\n", __func__);
} else {
ret = of_property_read_u32(np,
"battery,wireless_cc_cv", &pdata->wireless_cc_cv);
ret = of_property_read_string(np,
"battery,wirelss_charger_name", (char const **)&pdata->wireless_charger_name);
if (ret)
pr_info("%s: Vendor is Empty\n", __func__);
}
return ret;
#if 0 /* this part is for bq51221s */
ret = pdata->irq_gpio = of_get_named_gpio_flags(np, "bq51221-charger,irq-gpio",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't get irq-gpio\r\n", __FUNCTION__);
return ret;
}
pr_info("%s irq_gpio = %d \n",__func__, pdata->irq_gpio);
pdata->irq_base = gpio_to_irq(pdata->irq_gpio);
return 0;
#endif
}
static int bq51221_charger_probe(
struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *of_node = client->dev.of_node;
struct bq51221_charger_data *charger;
bq51221_charger_platform_data_t *pdata = client->dev.platform_data;
int ret = 0;
dev_info(&client->dev,
"%s: bq51221 Charger Driver Loading\n", __func__);
if (of_node) {
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(&client->dev, "Failed to allocate memory\n");
return -ENOMEM;
}
ret = bq51221_chg_parse_dt(&client->dev, pdata);
if (ret < 0)
goto err_parse_dt;
} else {
pdata = client->dev.platform_data;
}
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (charger == NULL) {
dev_err(&client->dev, "Memory is not enough.\n");
ret = -ENOMEM;
goto err_wpc_nomem;
}
charger->dev = &client->dev;
ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK);
if (!ret) {
ret = i2c_get_functionality(client->adapter);
dev_err(charger->dev, "I2C functionality is not supported.\n");
ret = -ENOSYS;
goto err_i2cfunc_not_support;
}
charger->client = client;
charger->pdata = pdata;
pr_info("%s: %s\n", __func__, charger->pdata->wireless_charger_name );
/* if board-init had already assigned irq_base (>=0) ,
no need to allocate it;
assign -1 to let this driver allocate resource by itself*/
#if 0 /* this part is for bq51221s */
if (pdata->irq_base < 0)
pdata->irq_base = irq_alloc_descs(-1, 0, BQ51221_EVENT_IRQ, 0);
if (pdata->irq_base < 0) {
pr_err("%s: irq_alloc_descs Fail! ret(%d)\n",
__func__, pdata->irq_base);
ret = -EINVAL;
goto irq_base_err;
} else {
charger->irq_base = pdata->irq_base;
pr_info("%s: irq_base = %d\n",
__func__, charger->irq_base);
#if (LINUX_VERSION_CODE>=KERNEL_VERSION(3,4,0))
irq_domain_add_legacy(of_node, BQ51221_EVENT_IRQ, charger->irq_base, 0,
&irq_domain_simple_ops, NULL);
#endif /*(LINUX_VERSION_CODE>=KERNEL_VERSION(3,4,0))*/
}
#endif
i2c_set_clientdata(client, charger);
charger->psy_chg.name = pdata->wireless_charger_name;
charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN;
charger->psy_chg.get_property = bq51221_chg_get_property;
charger->psy_chg.set_property = bq51221_chg_set_property;
charger->psy_chg.properties = sec_charger_props;
charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props);
mutex_init(&charger->io_lock);
#if 0 /* this part is for bq51221s */
if (charger->chg_irq) {
INIT_DELAYED_WORK(
&charger->isr_work, bq51221_chg_isr_work);
ret = request_threaded_irq(charger->chg_irq,
NULL, bq51221_chg_irq_thread,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"charger-irq", charger);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Reqeust IRQ\n", __func__);
goto err_supply_unreg;
}
ret = enable_irq_wake(charger->chg_irq);
if (ret < 0)
dev_err(&client->dev,
"%s: Failed to Enable Wakeup Source(%d)\n",
__func__, ret);
}
#endif
charger->pdata->cs100_status = 0;
charger->pdata->pad_mode = BQ51221_PAD_MODE_NONE;
charger->pdata->siop_level = 100;
charger->pdata->default_voreg = false;
ret = power_supply_register(&client->dev, &charger->psy_chg);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Register psy_chg\n", __func__);
goto err_supply_unreg;
}
charger->wqueue = create_workqueue("bq51221_workqueue");
if (!charger->wqueue) {
pr_err("%s: Fail to Create Workqueue\n", __func__);
goto err_pdata_free;
}
wake_lock_init(&(charger->wpc_wake_lock), WAKE_LOCK_SUSPEND,
"wpc_wakelock");
INIT_DELAYED_WORK(&charger->wpc_work, bq51221_detect_work);
dev_info(&client->dev,
"%s: bq51221 Charger Driver Loaded\n", __func__);
return 0;
err_pdata_free:
power_supply_unregister(&charger->psy_chg);
err_supply_unreg:
mutex_destroy(&charger->io_lock);
err_i2cfunc_not_support:
kfree(charger);
err_wpc_nomem:
err_parse_dt:
kfree(pdata);
return ret;
}
static int bq51221_charger_remove(struct i2c_client *client)
{
return 0;
}
#if defined CONFIG_PM
static int bq51221_charger_suspend(struct i2c_client *client,
pm_message_t state)
{
return 0;
}
static int bq51221_charger_resume(struct i2c_client *client)
{
return 0;
}
#else
#define p9015_charger_suspend NULL
#define p9015_charger_resume NULL
#endif
static void bq51221_charger_shutdown(struct i2c_client *client)
{
struct bq51221_charger_data *charger = i2c_get_clientdata(client);
int data = 0;
if(charger->pdata->pad_mode != BQ51221_PAD_MODE_NONE) {
/* init VOREG set 5.0V*/
bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01);
data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER);
pr_info("%s VOREG = 0x%x \n", __func__, data);
}
}
static const struct i2c_device_id bq51221_charger_id_table[] = {
{ "bq51221-charger", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, bq51221_id_table);
#ifdef CONFIG_OF
static struct of_device_id bq51221_charger_match_table[] = {
{ .compatible = "ti,bq51221-charger",},
{},
};
#else
#define bq51221_charger_match_table NULL
#endif
static struct i2c_driver bq51221_charger_driver = {
.driver = {
.name = "bq51221-charger",
.owner = THIS_MODULE,
.of_match_table = bq51221_charger_match_table,
},
.shutdown = bq51221_charger_shutdown,
.suspend = bq51221_charger_suspend,
.resume = bq51221_charger_resume,
.probe = bq51221_charger_probe,
.remove = bq51221_charger_remove,
.id_table = bq51221_charger_id_table,
};
static int __init bq51221_charger_init(void)
{
pr_info("%s \n",__func__);
return i2c_add_driver(&bq51221_charger_driver);
}
static void __exit bq51221_charger_exit(void)
{
pr_info("%s \n",__func__);
i2c_del_driver(&bq51221_charger_driver);
}
module_init(bq51221_charger_init);
module_exit(bq51221_charger_exit);
MODULE_DESCRIPTION("Samsung bq51221 Charger Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,671 @@
/*
* max17048_fuelgauge.c
* Samsung MAX17048 Fuel Gauge 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.
*
*/
#include <linux/battery/sec_fuelgauge.h>
#include <linux/sec_batt.h>
#if 0
static int max17048_write_reg(struct i2c_client *client, int reg, u8 value)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
#endif
static int max17048_read_reg(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int max17048_read_word(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int max17048_write_word(struct i2c_client *client, int reg, u16 buf)
{
int ret;
ret = i2c_smbus_write_word_data(client, reg, buf);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static void max17048_reset(struct i2c_client *client)
{
u16 mode, reset_cmd;
mode = max17048_read_word(client, MAX17048_MODE_MSB);
mode = swab16(mode);
reset_cmd = swab16(mode | 0x4000);
i2c_smbus_write_word_data(client, MAX17048_MODE_MSB, reset_cmd);
msleep(300);
}
static int max17048_get_vcell(struct i2c_client *client)
{
u32 vcell;
u16 w_data;
u32 temp;
temp = max17048_read_word(client, MAX17048_VCELL_MSB);
w_data = swab16(temp);
temp = ((w_data & 0xFFF0) >> 4) * 1250;
vcell = temp / 1000;
dev_dbg(&client->dev,
"%s : vcell (%d)\n", __func__, vcell);
return vcell;
}
static int max17048_get_avg_vcell(struct i2c_client *client)
{
u32 vcell_data = 0;
u32 vcell_max = 0;
u32 vcell_min = 0;
u32 vcell_total = 0;
u32 i;
for (i = 0; i < AVER_SAMPLE_CNT; i++) {
vcell_data = max17048_get_vcell(client);
if (i != 0) {
if (vcell_data > vcell_max)
vcell_max = vcell_data;
else if (vcell_data < vcell_min)
vcell_min = vcell_data;
} else {
vcell_max = vcell_data;
vcell_min = vcell_data;
}
vcell_total += vcell_data;
}
return (vcell_total - vcell_max - vcell_min) / (AVER_SAMPLE_CNT-2);
}
static int max17048_get_ocv(struct i2c_client *client)
{
u32 ocv;
u16 w_data;
u32 temp;
u16 cmd;
cmd = swab16(0x4A57);
max17048_write_word(client, 0x3E, cmd);
temp = max17048_read_word(client, MAX17048_OCV_MSB);
w_data = swab16(temp);
temp = ((w_data & 0xFFF0) >> 4) * 1250;
ocv = temp / 1000;
cmd = swab16(0x0000);
max17048_write_word(client, 0x3E, cmd);
dev_dbg(&client->dev,
"%s : ocv (%d)\n", __func__, ocv);
return ocv;
}
/* soc should be 0.01% unit */
static int max17048_get_soc(struct i2c_client *client)
{
struct sec_fuelgauge_info *fuelgauge =
i2c_get_clientdata(client);
u8 data[2] = {0, 0};
int temp, soc;
u64 psoc64 = 0;
u64 temp64;
u32 divisor = 10000000;
temp = max17048_read_word(client, MAX17048_SOC_MSB);
if (get_battery_data(fuelgauge).is_using_model_data) {
/* [ TempSOC = ((SOC1 * 256) + SOC2) * 0.001953125 ] */
temp64 = swab16(temp);
psoc64 = temp64 * 1953125;
psoc64 = div_u64(psoc64, divisor);
soc = psoc64 & 0xffff;
} else {
data[0] = temp & 0xff;
data[1] = (temp & 0xff00) >> 8;
soc = (data[0] * 100) + (data[1] * 100 / 256);
}
dev_dbg(&client->dev,
"%s : raw capacity (%d), data(0x%04x)\n",
__func__, soc, (data[0]<<8) | data[1]);
return soc;
}
static int max17048_get_current(struct i2c_client *client)
{
union power_supply_propval value;
psy_do_property("sec-charger", get,
POWER_SUPPLY_PROP_CURRENT_NOW, value);
return value.intval;
}
#define DISCHARGE_SAMPLE_CNT 5
static int discharge_cnt=0;
static int all_vcell[5] = {0,};
/* if ret < 0, discharge */
static int sec_bat_check_discharge(int vcell)
{
int i, cnt, ret = 0;
all_vcell[discharge_cnt++] = vcell;
if (discharge_cnt >= DISCHARGE_SAMPLE_CNT)
discharge_cnt = 0;
cnt = discharge_cnt;
/* check after last value is set */
if (all_vcell[cnt] == 0)
return 0;
for (i = 0; i < DISCHARGE_SAMPLE_CNT; i++) {
if (cnt == i)
continue;
if (all_vcell[cnt] > all_vcell[i])
ret--;
else
ret++;
}
return ret;
}
/* judge power off or not by current_avg */
static int max17048_get_current_average(struct i2c_client *client)
{
union power_supply_propval value_bat;
union power_supply_propval value_chg;
int vcell, soc, curr_avg;
int check_discharge;
psy_do_property("sec-charger", get,
POWER_SUPPLY_PROP_CURRENT_NOW, value_chg);
psy_do_property("battery", get,
POWER_SUPPLY_PROP_HEALTH, value_bat);
vcell = max17048_get_vcell(client);
soc = max17048_get_soc(client) / 100;
check_discharge = sec_bat_check_discharge(vcell);
/* if 0% && under 3.4v && low power charging(1000mA), power off */
if (!lpcharge && (soc <= 0) && (vcell < 3400) &&
(check_discharge < 0) &&
(((value_bat.intval == POWER_SUPPLY_HEALTH_OVERHEAT) ||
(value_bat.intval == POWER_SUPPLY_HEALTH_COLD)))) {
pr_info("%s: SOC(%d), Vnow(%d), Inow(%d)\n",
__func__, soc, vcell, value_chg.intval);
curr_avg = -1;
} else {
curr_avg = value_chg.intval;
}
return curr_avg;
}
void sec_bat_reset_discharge(struct i2c_client *client)
{
int i;
for (i = 0; i < DISCHARGE_SAMPLE_CNT ; i++)
all_vcell[i] = 0;
discharge_cnt = 0;
}
static void max17048_get_version(struct i2c_client *client)
{
u16 w_data;
int temp;
temp = max17048_read_word(client, MAX17048_VER_MSB);
w_data = swab16(temp);
dev_info(&client->dev,
"MAX17048 Fuel-Gauge Ver 0x%04x\n", w_data);
}
static u16 max17048_get_rcomp(struct i2c_client *client)
{
u16 w_data;
int temp;
temp = max17048_read_word(client, MAX17048_RCOMP_MSB);
w_data = swab16(temp);
dev_dbg(&client->dev,
"%s : current rcomp = 0x%04x\n",
__func__, w_data);
return w_data;
}
static void max17048_set_rcomp(struct i2c_client *client, u16 new_rcomp)
{
i2c_smbus_write_word_data(client,
MAX17048_RCOMP_MSB, swab16(new_rcomp));
}
static void max17048_rcomp_update(struct i2c_client *client, int temp)
{
struct sec_fuelgauge_info *fuelgauge =
i2c_get_clientdata(client);
union power_supply_propval value;
int starting_rcomp = 0;
int new_rcomp = 0;
int rcomp_current = 0;
rcomp_current = max17048_get_rcomp(client);
psy_do_property("battery", get,
POWER_SUPPLY_PROP_STATUS, value);
if (value.intval == POWER_SUPPLY_STATUS_CHARGING) /* in charging */
starting_rcomp = get_battery_data(fuelgauge).RCOMP_charging;
else
starting_rcomp = get_battery_data(fuelgauge).RCOMP0;
if (temp > RCOMP0_TEMP)
new_rcomp = starting_rcomp + ((temp - RCOMP0_TEMP) *
get_battery_data(fuelgauge).temp_cohot / 1000);
else if (temp < RCOMP0_TEMP)
new_rcomp = starting_rcomp + ((temp - RCOMP0_TEMP) *
get_battery_data(fuelgauge).temp_cocold / 1000);
else
new_rcomp = starting_rcomp;
if (new_rcomp > 255)
new_rcomp = 255;
else if (new_rcomp < 0)
new_rcomp = 0;
new_rcomp <<= 8;
new_rcomp &= 0xff00;
/* not related to RCOMP */
new_rcomp |= (rcomp_current & 0xff);
if (rcomp_current != new_rcomp) {
dev_dbg(&client->dev,
"%s : RCOMP 0x%04x -> 0x%04x (0x%02x)\n",
__func__, rcomp_current, new_rcomp,
new_rcomp >> 8);
max17048_set_rcomp(client, new_rcomp);
}
}
#ifdef CONFIG_OF
static int max17048_parse_dt(struct device *dev,
struct sec_fuelgauge_info *fuelgauge)
{
struct device_node *np = dev->of_node;
int ret;
int value;
if (np == NULL) {
pr_err("%s np NULL\n", __func__);
} else {
ret = of_property_read_u32(np, "fuelgauge,rcomp0",
&value);
pr_err("%s value %d\n",
__func__, value);
get_battery_data(fuelgauge).RCOMP0 = (u8)value;
if (ret < 0)
pr_err("%s error reading rcomp0 %d\n",
__func__, ret);
ret = of_property_read_u32(np, "fuelgauge,rcomp_charging",
&value);
pr_err("%s value %d\n",
__func__, value);
get_battery_data(fuelgauge).RCOMP_charging = (u8)value;
if (ret < 0)
pr_err("%s error reading rcomp_charging %d\n",
__func__, ret);
ret = of_property_read_u32(np, "fuelgauge,temp_cohot",
&get_battery_data(fuelgauge).temp_cohot);
if (ret < 0)
pr_err("%s error reading temp_cohot %d\n",
__func__, ret);
ret = of_property_read_u32(np, "fuelgauge,temp_cocold",
&get_battery_data(fuelgauge).temp_cocold);
if (ret < 0)
pr_err("%s error reading temp_cocold %d\n",
__func__, ret);
get_battery_data(fuelgauge).is_using_model_data = of_property_read_bool(np,
"fuelgauge,is_using_model_data");
ret = of_property_read_string(np, "fuelgauge,type_str",
(const char **)&get_battery_data(fuelgauge).type_str);
if (ret < 0)
pr_err("%s error reading temp_cocold %d\n",
__func__, ret);
pr_info("%s RCOMP0: 0x%x, RCOMP_charging: 0x%x, temp_cohot: %d,"
"temp_cocold: %d, is_using_model_data: %d, "
"type_str: %s,\n", __func__,
get_battery_data(fuelgauge).RCOMP0,
get_battery_data(fuelgauge).RCOMP_charging,
get_battery_data(fuelgauge).temp_cohot,
get_battery_data(fuelgauge).temp_cocold,
get_battery_data(fuelgauge).is_using_model_data,
get_battery_data(fuelgauge).type_str
);
}
return 0;
}
#endif
static void fg_read_regs(struct i2c_client *client, char *str)
{
int data = 0;
u32 addr = 0;
for (addr = 0x02; addr <= 0x04; addr += 2) {
data = max17048_read_word(client, addr);
sprintf(str + strlen(str), "0x%04x, ", data);
}
/* "#" considered as new line in application */
sprintf(str+strlen(str), "#");
for (addr = 0x08; addr <= 0x1a; addr += 2) {
data = max17048_read_word(client, addr);
sprintf(str + strlen(str), "0x%04x, ", data);
}
}
bool sec_hal_fg_init(struct i2c_client *client)
{
#ifdef CONFIG_OF
struct sec_fuelgauge_info *fuelgauge =
i2c_get_clientdata(client);
int error;
error = max17048_parse_dt(&client->dev, fuelgauge);
if (error) {
dev_err(&client->dev,
"%s : Failed to get max17048 fuel_init\n", __func__);
return false;
}
#endif
pr_info("%s\n", __func__);
max17048_get_version(client);
return true;
}
bool sec_hal_fg_suspend(struct i2c_client *client)
{
return true;
}
bool sec_hal_fg_resume(struct i2c_client *client)
{
return true;
}
bool sec_hal_fg_fuelalert_init(struct i2c_client *client, int soc)
{
u16 temp;
u8 data;
temp = max17048_get_rcomp(client);
data = 32 - soc; /* set soc for fuel alert */
temp &= 0xff00;
temp += data;
dev_dbg(&client->dev,
"%s : new rcomp = 0x%04x\n",
__func__, temp);
max17048_set_rcomp(client, temp);
return true;
}
bool sec_hal_fg_is_fuelalerted(struct i2c_client *client)
{
u16 temp;
temp = max17048_get_rcomp(client);
if (temp & 0x20) /* ALRT is asserted */
return true;
return false;
}
bool sec_hal_fg_fuelalert_process(void *irq_data, bool is_fuel_alerted)
{
return true;
}
bool sec_hal_fg_full_charged(struct i2c_client *client)
{
return true;
}
bool sec_hal_fg_reset(struct i2c_client *client)
{
max17048_reset(client);
return true;
}
bool sec_hal_fg_get_property(struct i2c_client *client,
enum power_supply_property psp,
union power_supply_propval *val)
{
int i, pr_cnt = 1;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = 0;
break;
/* Cell voltage (VCELL, mV) */
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = max17048_get_vcell(client);
break;
/* Additional Voltage Information (mV) */
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
switch (val->intval) {
case SEC_BATTEY_VOLTAGE_AVERAGE:
val->intval = max17048_get_avg_vcell(client);
break;
case SEC_BATTEY_VOLTAGE_OCV:
val->intval = max17048_get_ocv(client);
break;
}
break;
/* Current (mA) */
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = max17048_get_current(client);
break;
/* Average Current (mA) */
case POWER_SUPPLY_PROP_CURRENT_AVG:
val->intval = max17048_get_current_average(client);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
break;
case POWER_SUPPLY_PROP_ENERGY_NOW:
break;
/* SOC (%) */
case POWER_SUPPLY_PROP_CAPACITY:
if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) {
val->intval = max17048_get_soc(client);
} else {
val->intval = max17048_get_soc(client) / 10;
if (!(pr_cnt++ % 10)) {
pr_cnt = 1;
for (i = 0x02; i < 0x1C; i++)
printk("0x%02x(0x%02x), ",
i, max17048_read_reg(client, i));
printk("\n");
}
}
break;
/* Battery Temperature */
case POWER_SUPPLY_PROP_TEMP:
/* Target Temperature */
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
break;
default:
return false;
}
return true;
}
bool sec_hal_fg_set_property(struct i2c_client *client,
enum power_supply_property psp,
const union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
sec_bat_reset_discharge(client);
break;
/* Battery Temperature */
case POWER_SUPPLY_PROP_TEMP:
/* Target Temperature */
/* temperature is 0.1 degree, should be divide by 10 */
max17048_rcomp_update(client, val->intval / 10);
break;
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
break;
default:
return false;
}
return true;
}
ssize_t sec_hal_fg_show_attrs(struct device *dev,
const ptrdiff_t offset, char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct sec_fuelgauge_info *fg =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
int i = 0;
char *str = NULL;
switch (offset) {
case FG_DATA:
i += scnprintf(buf + i, PAGE_SIZE - i, "%02x%02x\n",
fg->reg_data[1], fg->reg_data[0]);
break;
case FG_REGS:
str = kzalloc(sizeof(char)*1024, GFP_KERNEL);
if (!str)
return -ENOMEM;
fg_read_regs(fg->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_fg_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_fuelgauge_info *fg =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
int ret = 0;
int x = 0;
u16 data;
switch (offset) {
case FG_REG:
if (sscanf(buf, "%x\n", &x) == 1) {
fg->reg_addr = x;
data = max17048_read_word(
fg->client, fg->reg_addr);
fg->reg_data[0] = (data & 0xff00) >> 8;
fg->reg_data[1] = (data & 0x00ff);
dev_dbg(&fg->client->dev,
"%s: (read) addr = 0x%x, data = 0x%02x%02x\n",
__func__, fg->reg_addr,
fg->reg_data[1], fg->reg_data[0]);
ret = count;
}
break;
case FG_DATA:
if (sscanf(buf, "%x\n", &x) == 1) {
dev_dbg(&fg->client->dev,
"%s: (write) addr = 0x%x, data = 0x%04x\n",
__func__, fg->reg_addr, x);
i2c_smbus_write_word_data(fg->client,
fg->reg_addr, swab16(x));
ret = count;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1029
drivers/battery/s2mpu06_charger.c Executable file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

185
drivers/battery/sec_adc.c Normal file
View file

@ -0,0 +1,185 @@
/*
* sec_adc.c
* Samsung Mobile Battery 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.
*/
#include <linux/battery/sec_adc.h>
struct adc_list {
const char* name;
struct iio_channel *channel;
bool is_used;
};
static struct adc_list batt_adc_list[] = {
{.name = "adc-cable"},
{.name = "adc-bat"},
{.name = "adc-temp"},
{.name = "adc-temp"},
{.name = "adc-full"},
{.name = "adc-volt"},
{.name = "adc-chg-temp"},
{.name = "adc-in-bat"},
{.name = "adc-dischg"},
{.name = "adc-dischg-ntc"},
{.name = "adc-wpc-temp"},
{.name = "adc-slave-chg-temp"},
};
static void sec_bat_adc_ap_init(struct platform_device *pdev)
{
int i = 0;
struct iio_channel *temp_adc;
for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) {
temp_adc = iio_channel_get(&pdev->dev, batt_adc_list[i].name);
batt_adc_list[i].channel = temp_adc;
batt_adc_list[i].is_used = !IS_ERR_OR_NULL(temp_adc);
}
}
static int sec_bat_adc_ap_read(int channel)
{
int data = -1;
int ret = 0;
ret = (batt_adc_list[channel].is_used) ?
iio_read_channel_raw(batt_adc_list[channel].channel, &data) : 0;
return data;
}
static void sec_bat_adc_ap_exit(void)
{
int i = 0;
for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) {
if (batt_adc_list[i].is_used) {
iio_channel_release(batt_adc_list[i].channel);
}
}
}
static void sec_bat_adc_none_init(struct platform_device *pdev)
{
}
static int sec_bat_adc_none_read(int channel)
{
return 0;
}
static void sec_bat_adc_none_exit(void)
{
}
static void sec_bat_adc_ic_init(struct platform_device *pdev)
{
}
static int sec_bat_adc_ic_read(int channel)
{
return 0;
}
static void sec_bat_adc_ic_exit(void)
{
}
static int adc_read_type(struct sec_battery_info *battery, int channel)
{
int adc = 0;
if ((!battery->pdata->self_discharging_en) &&
((channel == SEC_BAT_ADC_CHANNEL_DISCHARGING_CHECK) ||
(channel == SEC_BAT_ADC_CHANNEL_DISCHARGING_NTC))) {
pr_info("%s : Doesn't enable Self Discharging Algorithm\n", __func__);
return 0;
}
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
adc = sec_bat_adc_none_read(channel);
break;
case SEC_BATTERY_ADC_TYPE_AP :
adc = sec_bat_adc_ap_read(channel);
break;
case SEC_BATTERY_ADC_TYPE_IC :
adc = sec_bat_adc_ic_read(channel);
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
return adc;
}
static void adc_init_type(struct platform_device *pdev,
struct sec_battery_info *battery)
{
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
sec_bat_adc_none_init(pdev);
break;
case SEC_BATTERY_ADC_TYPE_AP :
sec_bat_adc_ap_init(pdev);
break;
case SEC_BATTERY_ADC_TYPE_IC :
sec_bat_adc_ic_init(pdev);
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
}
static void adc_exit_type(struct sec_battery_info *battery)
{
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
sec_bat_adc_none_exit();
break;
case SEC_BATTERY_ADC_TYPE_AP :
sec_bat_adc_ap_exit();
break;
case SEC_BATTERY_ADC_TYPE_IC :
sec_bat_adc_ic_exit();
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
}
int adc_read(struct sec_battery_info *battery, int channel)
{
int adc = 0;
adc = adc_read_type(battery, channel);
dev_dbg(battery->dev, "[%s]adc = %d\n", __func__, adc);
return adc;
}
void adc_init(struct platform_device *pdev, struct sec_battery_info *battery)
{
adc_init_type(pdev, battery);
}
void adc_exit(struct sec_battery_info *battery)
{
adc_exit_type(battery);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,409 @@
/*
* sec_charger.c
* Samsung Mobile 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.
*/
#include <linux/battery/sec_charger.h>
static enum power_supply_property sec_charger_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static int sec_chg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sec_charger_info *charger =
container_of(psy, struct sec_charger_info, psy_chg);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = charger->charging_current ? 1 : 0;
break;
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_HEALTH:
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (!sec_hal_chg_get_property(charger->client, psp, val))
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static int sec_chg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct sec_charger_info *charger =
container_of(psy, struct sec_charger_info, psy_chg);
switch (psp) {
/* val->intval : type */
case POWER_SUPPLY_PROP_STATUS:
charger->status = val->intval;
break;
case POWER_SUPPLY_PROP_ONLINE:
charger->cable_type = val->intval;
if (val->intval == POWER_SUPPLY_TYPE_BATTERY)
charger->is_charging = false;
else
charger->is_charging = true;
/* current setting */
charger->charging_current =
charger->pdata->charging_current[
val->intval].fast_charging_current;
if (!sec_hal_chg_set_property(charger->client, psp, val))
return -EINVAL;
break;
/* val->intval : charging current */
case POWER_SUPPLY_PROP_CURRENT_NOW:
charger->charging_current = val->intval;
if (!sec_hal_chg_set_property(charger->client, psp, val))
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static void sec_chg_isr_work(struct work_struct *work)
{
struct sec_charger_info *charger =
container_of(work, struct sec_charger_info, isr_work.work);
union power_supply_propval val;
int full_check_type;
dev_info(&charger->client->dev,
"%s: Charger Interrupt\n", __func__);
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;
if (full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) {
if (!sec_hal_chg_get_property(charger->client,
POWER_SUPPLY_PROP_STATUS, &val))
return;
switch (val.intval) {
case POWER_SUPPLY_STATUS_DISCHARGING:
dev_err(&charger->client->dev,
"%s: Interrupted but Discharging\n", __func__);
break;
case POWER_SUPPLY_STATUS_NOT_CHARGING:
dev_err(&charger->client->dev,
"%s: Interrupted but NOT Charging\n", __func__);
break;
case POWER_SUPPLY_STATUS_FULL:
dev_info(&charger->client->dev,
"%s: Interrupted by Full\n", __func__);
psy_do_property("battery", set,
POWER_SUPPLY_PROP_STATUS, val);
break;
case POWER_SUPPLY_STATUS_CHARGING:
dev_err(&charger->client->dev,
"%s: Interrupted but Charging\n", __func__);
break;
case POWER_SUPPLY_STATUS_UNKNOWN:
default:
dev_err(&charger->client->dev,
"%s: Invalid Charger Status\n", __func__);
break;
}
}
if (charger->pdata->ovp_uvlo_check_type ==
SEC_BATTERY_OVP_UVLO_CHGINT) {
if (!sec_hal_chg_get_property(charger->client,
POWER_SUPPLY_PROP_HEALTH, &val))
return;
switch (val.intval) {
case POWER_SUPPLY_HEALTH_OVERHEAT:
case POWER_SUPPLY_HEALTH_COLD:
dev_err(&charger->client->dev,
"%s: Interrupted but Hot/Cold\n", __func__);
break;
case POWER_SUPPLY_HEALTH_DEAD:
dev_err(&charger->client->dev,
"%s: Interrupted but Dead\n", __func__);
break;
case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
case POWER_SUPPLY_HEALTH_UNDERVOLTAGE:
dev_info(&charger->client->dev,
"%s: Interrupted by OVP/UVLO\n", __func__);
psy_do_property("battery", set,
POWER_SUPPLY_PROP_HEALTH, val);
break;
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
dev_err(&charger->client->dev,
"%s: Interrupted but Unspec\n", __func__);
break;
case POWER_SUPPLY_HEALTH_GOOD:
dev_err(&charger->client->dev,
"%s: Interrupted but Good\n", __func__);
break;
case POWER_SUPPLY_HEALTH_UNKNOWN:
default:
dev_err(&charger->client->dev,
"%s: Invalid Charger Health\n", __func__);
break;
}
}
}
static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data)
{
struct sec_charger_info *charger = irq_data;
schedule_delayed_work(&charger->isr_work, 0);
return IRQ_HANDLED;
}
static int sec_chg_create_attrs(struct device *dev)
{
int i, rc;
for (i = 0; i < ARRAY_SIZE(sec_charger_attrs); i++) {
rc = device_create_file(dev, &sec_charger_attrs[i]);
if (rc)
goto create_attrs_failed;
}
goto create_attrs_succeed;
create_attrs_failed:
dev_err(dev, "%s: failed (%d)\n", __func__, rc);
while (i--)
device_remove_file(dev, &sec_charger_attrs[i]);
create_attrs_succeed:
return rc;
}
ssize_t sec_chg_show_attrs(struct device *dev,
struct device_attribute *attr, char *buf)
{
const ptrdiff_t offset = attr - sec_charger_attrs;
int i = 0;
switch (offset) {
case CHG_REG:
case CHG_DATA:
case CHG_REGS:
i = sec_hal_chg_show_attrs(dev, offset, buf);
break;
default:
i = -EINVAL;
break;
}
return i;
}
ssize_t sec_chg_store_attrs(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
const ptrdiff_t offset = attr - sec_charger_attrs;
int ret = 0;
switch (offset) {
case CHG_REG:
case CHG_DATA:
ret = sec_hal_chg_store_attrs(dev, offset, buf, count);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int __devinit sec_charger_probe(
struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter =
to_i2c_adapter(client->dev.parent);
struct sec_charger_info *charger;
int ret = 0;
dev_dbg(&client->dev,
"%s: SEC Charger Driver Loading\n", __func__);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
charger->client = client;
charger->pdata = client->dev.platform_data;
i2c_set_clientdata(client, charger);
charger->psy_chg.name = "sec-charger";
charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN;
charger->psy_chg.get_property = sec_chg_get_property;
charger->psy_chg.set_property = sec_chg_set_property;
charger->psy_chg.properties = sec_charger_props;
charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props);
if (!charger->pdata->chg_gpio_init()) {
dev_err(&client->dev,
"%s: Failed to Initialize GPIO\n", __func__);
goto err_free;
}
if (!sec_hal_chg_init(charger->client)) {
dev_err(&client->dev,
"%s: Failed to Initialize Charger\n", __func__);
goto err_free;
}
ret = power_supply_register(&client->dev, &charger->psy_chg);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Register psy_chg\n", __func__);
goto err_free;
}
if (charger->pdata->chg_irq) {
INIT_DELAYED_WORK_DEFERRABLE(
&charger->isr_work, sec_chg_isr_work);
ret = request_threaded_irq(charger->pdata->chg_irq,
NULL, sec_chg_irq_thread,
charger->pdata->chg_irq_attr,
"charger-irq", charger);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Reqeust IRQ\n", __func__);
goto err_supply_unreg;
}
ret = enable_irq_wake(charger->pdata->chg_irq);
if (ret < 0)
dev_err(&client->dev,
"%s: Failed to Enable Wakeup Source(%d)\n",
__func__, ret);
}
ret = sec_chg_create_attrs(charger->psy_chg.dev);
if (ret) {
dev_err(&client->dev,
"%s : Failed to create_attrs\n", __func__);
goto err_req_irq;
}
dev_dbg(&client->dev,
"%s: SEC Charger Driver Loaded\n", __func__);
return 0;
err_req_irq:
if (charger->pdata->chg_irq)
free_irq(charger->pdata->chg_irq, charger);
err_supply_unreg:
power_supply_unregister(&charger->psy_chg);
err_free:
kfree(charger);
return ret;
}
static int __devexit sec_charger_remove(
struct i2c_client *client)
{
return 0;
}
static int sec_charger_suspend(struct i2c_client *client,
pm_message_t state)
{
if (!sec_hal_chg_suspend(client))
dev_err(&client->dev,
"%s: Failed to Suspend Charger\n", __func__);
return 0;
}
static int sec_charger_resume(struct i2c_client *client)
{
if (!sec_hal_chg_resume(client))
dev_err(&client->dev,
"%s: Failed to Resume Charger\n", __func__);
return 0;
}
static void sec_charger_shutdown(struct i2c_client *client)
{
}
static const struct i2c_device_id sec_charger_id[] = {
{"sec-charger", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, sec_charger_id);
static struct i2c_driver sec_charger_driver = {
.driver = {
.name = "sec-charger",
},
.probe = sec_charger_probe,
.remove = __devexit_p(sec_charger_remove),
.suspend = sec_charger_suspend,
.resume = sec_charger_resume,
.shutdown = sec_charger_shutdown,
.id_table = sec_charger_id,
};
static int __init sec_charger_init(void)
{
return i2c_add_driver(&sec_charger_driver);
}
static void __exit sec_charger_exit(void)
{
i2c_del_driver(&sec_charger_driver);
}
module_init(sec_charger_init);
module_exit(sec_charger_exit);
MODULE_DESCRIPTION("Samsung Charger Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,660 @@
/*
* sec_fuelgauge.c
* Samsung Mobile Fuel Gauge 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_fuelgauge.h>
#include <linux/of_gpio.h>
static struct device_attribute sec_fg_attrs[] = {
SEC_FG_ATTR(reg),
SEC_FG_ATTR(data),
SEC_FG_ATTR(regs),
};
static enum power_supply_property sec_fuelgauge_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
};
/* capacity is 0.1% unit */
static void sec_fg_get_scaled_capacity(
struct sec_fuelgauge_info *fuelgauge,
union power_supply_propval *val)
{
val->intval = (val->intval < fuelgauge->pdata->capacity_min) ?
0 : ((val->intval - fuelgauge->pdata->capacity_min) * 1000 /
(fuelgauge->capacity_max - fuelgauge->pdata->capacity_min));
dev_dbg(&fuelgauge->client->dev,
"%s: scaled capacity (%d.%d)\n",
__func__, val->intval/10, val->intval%10);
}
/* capacity is integer */
static void sec_fg_get_atomic_capacity(
struct sec_fuelgauge_info *fuelgauge,
union power_supply_propval *val)
{
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) {
if (fuelgauge->capacity_old < val->intval)
val->intval = fuelgauge->capacity_old + 1;
else if (fuelgauge->capacity_old > val->intval)
val->intval = fuelgauge->capacity_old - 1;
}
/* keep SOC stable in abnormal status */
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) {
if (!fuelgauge->is_charging &&
fuelgauge->capacity_old < val->intval) {
dev_err(&fuelgauge->client->dev,
"%s: capacity (old %d : new %d)\n",
__func__, fuelgauge->capacity_old, val->intval);
val->intval = fuelgauge->capacity_old;
}
}
/* updated old capacity */
fuelgauge->capacity_old = val->intval;
}
static int sec_fg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
int soc_type = val->intval;
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_ENERGY_NOW:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
if (!sec_hal_fg_get_property(fuelgauge->client, psp, val))
return -EINVAL;
if (psp == POWER_SUPPLY_PROP_CAPACITY) {
if (soc_type == SEC_FUELGAUGE_CAPACITY_TYPE_RAW)
break;
if (fuelgauge->pdata->capacity_calculation_type &
(SEC_FUELGAUGE_CAPACITY_TYPE_SCALE |
SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE))
sec_fg_get_scaled_capacity(fuelgauge, val);
/* capacity should be between 0% and 100%
* (0.1% degree)
*/
if (val->intval > 1000)
val->intval = 1000;
if (val->intval < 0)
val->intval = 0;
/* get only integer part */
val->intval /= 10;
/* check whether doing the wake_unlock */
if ((val->intval > fuelgauge->pdata->fuel_alert_soc) &&
fuelgauge->is_fuel_alerted) {
wake_unlock(&fuelgauge->fuel_alert_wake_lock);
sec_hal_fg_fuelalert_init(fuelgauge->client,
fuelgauge->pdata->fuel_alert_soc);
}
/* (Only for atomic capacity)
* In initial time, capacity_old is 0.
* and in resume from sleep,
* capacity_old is too different from actual soc.
* should update capacity_old
* by val->intval in booting or resume.
*/
if (fuelgauge->initial_update_of_soc) {
/* updated old capacity */
fuelgauge->capacity_old = val->intval;
fuelgauge->initial_update_of_soc = false;
break;
}
if (fuelgauge->pdata->capacity_calculation_type &
(SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC |
SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL))
sec_fg_get_atomic_capacity(fuelgauge, val);
}
break;
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CHARGE_FULL:
return -ENODATA;
default:
return -EINVAL;
}
return 0;
}
static int sec_fg_calculate_dynamic_scale(
struct sec_fuelgauge_info *fuelgauge)
{
union power_supply_propval raw_soc_val;
raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW;
if (!sec_hal_fg_get_property(fuelgauge->client,
POWER_SUPPLY_PROP_CAPACITY,
&raw_soc_val))
return -EINVAL;
raw_soc_val.intval /= 10;
if (raw_soc_val.intval <
fuelgauge->pdata->capacity_max -
fuelgauge->pdata->capacity_max_margin) {
fuelgauge->capacity_max =
fuelgauge->pdata->capacity_max -
fuelgauge->pdata->capacity_max_margin;
dev_dbg(&fuelgauge->client->dev, "%s: capacity_max (%d)",
__func__, fuelgauge->capacity_max);
} else {
fuelgauge->capacity_max =
(raw_soc_val.intval >
fuelgauge->pdata->capacity_max +
fuelgauge->pdata->capacity_max_margin) ?
(fuelgauge->pdata->capacity_max +
fuelgauge->pdata->capacity_max_margin) :
raw_soc_val.intval;
dev_dbg(&fuelgauge->client->dev, "%s: raw soc (%d)",
__func__, fuelgauge->capacity_max);
}
fuelgauge->capacity_max =
(fuelgauge->capacity_max * 99 / 100);
/* update capacity_old for sec_fg_get_atomic_capacity algorithm */
fuelgauge->capacity_old = 100;
dev_info(&fuelgauge->client->dev, "%s: %d is used for capacity_max\n",
__func__, fuelgauge->capacity_max);
return fuelgauge->capacity_max;
}
static int sec_fg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(psy, struct sec_fuelgauge_info, psy_fg);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (val->intval == POWER_SUPPLY_STATUS_FULL)
sec_hal_fg_full_charged(fuelgauge->client);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (val->intval == POWER_SUPPLY_TYPE_BATTERY) {
if (fuelgauge->pdata->capacity_calculation_type &
SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)
sec_fg_calculate_dynamic_scale(fuelgauge);
}
break;
case POWER_SUPPLY_PROP_ONLINE:
fuelgauge->cable_type = val->intval;
if (val->intval == POWER_SUPPLY_TYPE_BATTERY)
fuelgauge->is_charging = false;
else
fuelgauge->is_charging = true;
case POWER_SUPPLY_PROP_CAPACITY:
if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) {
fuelgauge->initial_update_of_soc = true;
if (!sec_hal_fg_reset(fuelgauge->client))
return -EINVAL;
else
break;
}
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
if (!sec_hal_fg_set_property(fuelgauge->client, psp, val))
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static void sec_fg_isr_work(struct work_struct *work)
{
struct sec_fuelgauge_info *fuelgauge =
container_of(work, struct sec_fuelgauge_info, isr_work.work);
/* process for fuel gauge chip */
sec_hal_fg_fuelalert_process(fuelgauge, fuelgauge->is_fuel_alerted);
/* process for others */
if (fuelgauge->pdata->fuelalert_process != NULL)
fuelgauge->pdata->fuelalert_process(fuelgauge->is_fuel_alerted);
}
static irqreturn_t sec_fg_irq_thread(int irq, void *irq_data)
{
struct sec_fuelgauge_info *fuelgauge = irq_data;
bool fuel_alerted;
if (fuelgauge->pdata->fuel_alert_soc >= 0) {
fuel_alerted =
sec_hal_fg_is_fuelalerted(fuelgauge->client);
dev_info(&fuelgauge->client->dev,
"%s: Fuel-alert %salerted!\n",
__func__, fuel_alerted ? "" : "NOT ");
if (fuel_alerted == fuelgauge->is_fuel_alerted) {
if (!fuelgauge->pdata->repeated_fuelalert) {
dev_dbg(&fuelgauge->client->dev,
"%s: Fuel-alert Repeated (%d)\n",
__func__, fuelgauge->is_fuel_alerted);
return IRQ_HANDLED;
}
}
if (fuel_alerted)
wake_lock(&fuelgauge->fuel_alert_wake_lock);
else
wake_unlock(&fuelgauge->fuel_alert_wake_lock);
schedule_delayed_work(&fuelgauge->isr_work, 0);
fuelgauge->is_fuel_alerted = fuel_alerted;
}
return IRQ_HANDLED;
}
static int sec_fg_create_attrs(struct device *dev)
{
int i, rc;
for (i = 0; i < ARRAY_SIZE(sec_fg_attrs); i++) {
rc = device_create_file(dev, &sec_fg_attrs[i]);
if (rc)
goto create_attrs_failed;
}
goto create_attrs_succeed;
create_attrs_failed:
dev_err(dev, "%s: failed (%d)\n", __func__, rc);
while (i--)
device_remove_file(dev, &sec_fg_attrs[i]);
create_attrs_succeed:
return rc;
}
ssize_t sec_fg_show_attrs(struct device *dev,
struct device_attribute *attr, char *buf)
{
const ptrdiff_t offset = attr - sec_fg_attrs;
int i = 0;
switch (offset) {
case FG_REG:
case FG_DATA:
case FG_REGS:
i = sec_hal_fg_show_attrs(dev, offset, buf);
break;
default:
i = -EINVAL;
break;
}
return i;
}
ssize_t sec_fg_store_attrs(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
const ptrdiff_t offset = attr - sec_fg_attrs;
int ret = 0;
switch (offset) {
case FG_REG:
case FG_DATA:
ret = sec_hal_fg_store_attrs(dev, offset, buf, count);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
#ifdef CONFIG_OF
static int fuelgauge_parse_dt(struct device *dev,
struct sec_fuelgauge_info *fuelgauge)
{
struct device_node *np = dev->of_node;
sec_battery_platform_data_t *pdata = fuelgauge->pdata;
int ret;
#if 0
int ta_int_gpio;
sec_battery_platform_data_t sec_battery_pdata;
#endif
/* reset, irq gpio info */
if (np == NULL) {
pr_err("%s np NULL\n", __func__);
} else {
pdata->fg_irq = of_get_named_gpio(np, "fuelgauge,fuel_int", 0);
if (pdata->fg_irq < 0) {
pr_err("%s error reading fg_irq = %d\n", __func__, pdata->fg_irq);
pdata->fg_irq = 0;
}
ret = of_property_read_u32(np, "fuelgauge,capacity_max",
&pdata->capacity_max);
if (ret < 0)
pr_err("%s error reading capacity_max %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin",
&pdata->capacity_max_margin);
if (ret < 0)
pr_err("%s error reading capacity_max_margin %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_min",
&pdata->capacity_min);
if (ret < 0)
pr_err("%s error reading capacity_min %d\n", __func__, ret);
ret = of_property_read_u32(np, "fuelgauge,capacity_calculation_type",
&pdata->capacity_calculation_type);
if (ret < 0)
pr_err("%s error reading capacity_calculation_type %d\n",
__func__, ret);
ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc",
&pdata->fuel_alert_soc);
if (ret < 0)
pr_err("%s error reading pdata->fuel_alert_soc %d\n",
__func__, ret);
pdata->repeated_fuelalert = of_property_read_bool(np,
"fuelgaguge,repeated_fuelalert");
pr_info("%s fg_irq: %d, capacity_max: %d\n"
"cpacity_max_margin: %d, capacity_min: %d\n"
"calculation_type: 0x%x, fuel_alert_soc: %d,\n"
"repeated_fuelalert: %d\n",
__func__, pdata->fg_irq,
pdata->capacity_max, pdata->capacity_max_margin,
pdata->capacity_min, pdata->capacity_calculation_type,
pdata->fuel_alert_soc, pdata->repeated_fuelalert);
}
return 0;
}
#endif
static int __devinit sec_fuelgauge_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct sec_fuelgauge_info *fuelgauge;
sec_battery_platform_data_t *pdata = NULL;
struct battery_data_t *battery_data = NULL;
int ret = 0;
union power_supply_propval raw_soc_val;
dev_info(&client->dev,
"%s: SEC Fuelgauge Driver Loading\n", __func__);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL);
if (!fuelgauge)
return -ENOMEM;
mutex_init(&fuelgauge->fg_lock);
fuelgauge->client = client;
if (client->dev.of_node) {
int error;
pdata = devm_kzalloc(&client->dev,
sizeof(sec_battery_platform_data_t),
GFP_KERNEL);
if (!pdata) {
dev_err(&client->dev, "Failed to allocate memory\n");
ret = -ENOMEM;
goto err_free;
}
battery_data = devm_kzalloc(&client->dev,
sizeof(struct battery_data_t),
GFP_KERNEL);
if (!battery_data) {
dev_err(&client->dev, "Failed to allocate memory\n");
devm_kfree(&client->dev, pdata);
ret = -ENOMEM;
goto err_free;
}
pdata->battery_data = (void *)battery_data;
fuelgauge->pdata = pdata;
error = fuelgauge_parse_dt(&client->dev, fuelgauge);
if (error) {
dev_err(&client->dev,
"%s: Failed to get fuel_int\n", __func__);
}
} else {
dev_err(&client->dev,
"%s: Failed to get of_node\n", __func__);
fuelgauge->pdata = client->dev.platform_data;
}
i2c_set_clientdata(client, fuelgauge);
if (!sec_hal_fg_init(fuelgauge->client)) {
dev_err(&client->dev,
"%s: Failed to Initialize Fuelgauge\n", __func__);
goto err_free;
}
fuelgauge->psy_fg.name = "sec-fuelgauge";
fuelgauge->psy_fg.type = POWER_SUPPLY_TYPE_UNKNOWN;
fuelgauge->psy_fg.get_property = sec_fg_get_property;
fuelgauge->psy_fg.set_property = sec_fg_set_property;
fuelgauge->psy_fg.properties = sec_fuelgauge_props;
fuelgauge->psy_fg.num_properties =
ARRAY_SIZE(sec_fuelgauge_props);
fuelgauge->capacity_max = fuelgauge->pdata->capacity_max;
raw_soc_val.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RAW;
sec_hal_fg_get_property(fuelgauge->client,
POWER_SUPPLY_PROP_CAPACITY, &raw_soc_val);
raw_soc_val.intval /= 10;
if(raw_soc_val.intval > fuelgauge->pdata->capacity_max)
sec_fg_calculate_dynamic_scale(fuelgauge);
/*
if (!fuelgauge->pdata->fg_gpio_init()) {
dev_err(&client->dev,
"%s: Failed to Initialize GPIO\n", __func__);
goto err_free;
}
*/
ret = power_supply_register(&client->dev, &fuelgauge->psy_fg);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Register psy_fg\n", __func__);
goto err_free;
}
if (fuelgauge->pdata->fg_irq) {
fuelgauge->fg_irq = gpio_to_irq(fuelgauge->pdata->fg_irq);
INIT_DELAYED_WORK(
&fuelgauge->isr_work, sec_fg_isr_work);
ret = request_threaded_irq(fuelgauge->fg_irq,
NULL, sec_fg_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"fuelgauge-irq", fuelgauge);
if (ret) {
dev_err(&client->dev,
"%s: Failed to Reqeust IRQ\n", __func__);
goto err_supply_unreg;
}
ret = enable_irq_wake(fuelgauge->fg_irq);
if (ret < 0)
dev_err(&client->dev,
"%s: Failed to Enable Wakeup Source(%d)\n",
__func__, ret);
}
fuelgauge->is_fuel_alerted = false;
if (fuelgauge->pdata->fuel_alert_soc >= 0) {
if (sec_hal_fg_fuelalert_init(fuelgauge->client,
fuelgauge->pdata->fuel_alert_soc))
wake_lock_init(&fuelgauge->fuel_alert_wake_lock,
WAKE_LOCK_SUSPEND, "fuel_alerted");
else {
dev_err(&client->dev,
"%s: Failed to Initialize Fuel-alert\n",
__func__);
goto err_irq;
}
}
fuelgauge->initial_update_of_soc = true;
ret = sec_fg_create_attrs(fuelgauge->psy_fg.dev);
if (ret) {
dev_err(&client->dev,
"%s : Failed to create_attrs\n", __func__);
goto err_irq;
}
dev_info(&client->dev,
"%s: SEC Fuelgauge Driver Loaded\n", __func__);
return 0;
err_irq:
if (fuelgauge->fg_irq)
free_irq(fuelgauge->fg_irq, fuelgauge);
wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock);
err_supply_unreg:
power_supply_unregister(&fuelgauge->psy_fg);
err_free:
mutex_destroy(&fuelgauge->fg_lock);
kfree(fuelgauge);
return ret;
}
static int __devexit sec_fuelgauge_remove(
struct i2c_client *client)
{
struct sec_fuelgauge_info *fuelgauge = i2c_get_clientdata(client);
if (fuelgauge->pdata->fuel_alert_soc >= 0)
wake_lock_destroy(&fuelgauge->fuel_alert_wake_lock);
return 0;
}
static int sec_fuelgauge_suspend(struct device *dev)
{
struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev);
if (!sec_hal_fg_suspend(fuelgauge->client))
dev_err(&fuelgauge->client->dev,
"%s: Failed to Suspend Fuelgauge\n", __func__);
return 0;
}
static int sec_fuelgauge_resume(struct device *dev)
{
struct sec_fuelgauge_info *fuelgauge = dev_get_drvdata(dev);
if (!sec_hal_fg_resume(fuelgauge->client))
dev_err(&fuelgauge->client->dev,
"%s: Failed to Resume Fuelgauge\n", __func__);
fuelgauge->initial_update_of_soc = true;
return 0;
}
static void sec_fuelgauge_shutdown(struct i2c_client *client)
{
}
static const struct i2c_device_id sec_fuelgauge_id[] = {
{"sec-fuelgauge", 0},
{}
};
static const struct dev_pm_ops sec_fuelgauge_pm_ops = {
.suspend = sec_fuelgauge_suspend,
.resume = sec_fuelgauge_resume,
};
MODULE_DEVICE_TABLE(i2c, sec_fuelgauge_id);
#if defined(CONFIG_OF)
static struct of_device_id sec_fuelgauge_i2c_dt_ids[] = {
{ .compatible = "sec-fuelgauge,i2c" },
{ }
};
MODULE_DEVICE_TABLE(of, sec_fuelgauge_i2c_dt_ids);
#endif /* CONFIG_OF */
static struct i2c_driver sec_fuelgauge_driver = {
.driver = {
.name = "sec-fuelgauge",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &sec_fuelgauge_pm_ops,
#endif
#if defined(CONFIG_OF)
.of_match_table = sec_fuelgauge_i2c_dt_ids,
#endif /* CONFIG_OF */
},
.probe = sec_fuelgauge_probe,
.remove = __devexit_p(sec_fuelgauge_remove),
.shutdown = sec_fuelgauge_shutdown,
.id_table = sec_fuelgauge_id,
};
static int __init sec_fuelgauge_init(void)
{
dev_info(0, "%s: \n", __func__);
return i2c_add_driver(&sec_fuelgauge_driver);
}
static void __exit sec_fuelgauge_exit(void)
{
i2c_del_driver(&sec_fuelgauge_driver);
}
module_init(sec_fuelgauge_init);
module_exit(sec_fuelgauge_exit);
MODULE_DESCRIPTION("Samsung Fuel Gauge Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,740 @@
/*
* 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;
}