mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
350
drivers/battery/Kconfig
Normal file
350
drivers/battery/Kconfig
Normal 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
26
drivers/battery/Makefile
Normal 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
|
516
drivers/battery/bq24260_charger.c
Normal file
516
drivers/battery/bq24260_charger.c
Normal 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;
|
||||
}
|
759
drivers/battery/bq51221_charger.c
Normal file
759
drivers/battery/bq51221_charger.c
Normal 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");
|
671
drivers/battery/max17048_fuelgauge.c
Normal file
671
drivers/battery/max17048_fuelgauge.c
Normal 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;
|
||||
}
|
2451
drivers/battery/max17050_fuelgauge.c
Normal file
2451
drivers/battery/max17050_fuelgauge.c
Normal file
File diff suppressed because it is too large
Load diff
1302
drivers/battery/max77693_charger.c
Normal file
1302
drivers/battery/max77693_charger.c
Normal file
File diff suppressed because it is too large
Load diff
1876
drivers/battery/max77804_charger.c
Normal file
1876
drivers/battery/max77804_charger.c
Normal file
File diff suppressed because it is too large
Load diff
1535
drivers/battery/max77823_charger.c
Normal file
1535
drivers/battery/max77823_charger.c
Normal file
File diff suppressed because it is too large
Load diff
2580
drivers/battery/max77823_fuelgauge.c
Normal file
2580
drivers/battery/max77823_fuelgauge.c
Normal file
File diff suppressed because it is too large
Load diff
2032
drivers/battery/max77833_charger.c
Normal file
2032
drivers/battery/max77833_charger.c
Normal file
File diff suppressed because it is too large
Load diff
2288
drivers/battery/max77833_fuelgauge.c
Normal file
2288
drivers/battery/max77833_fuelgauge.c
Normal file
File diff suppressed because it is too large
Load diff
1844
drivers/battery/max77843_charger.c
Normal file
1844
drivers/battery/max77843_charger.c
Normal file
File diff suppressed because it is too large
Load diff
2096
drivers/battery/max77843_fuelgauge.c
Normal file
2096
drivers/battery/max77843_fuelgauge.c
Normal file
File diff suppressed because it is too large
Load diff
1029
drivers/battery/s2mpu06_charger.c
Executable file
1029
drivers/battery/s2mpu06_charger.c
Executable file
File diff suppressed because it is too large
Load diff
1061
drivers/battery/s2mpu06_fuelgauge.c
Executable file
1061
drivers/battery/s2mpu06_fuelgauge.c
Executable file
File diff suppressed because it is too large
Load diff
1134
drivers/battery/s2mu003_charger.c
Normal file
1134
drivers/battery/s2mu003_charger.c
Normal file
File diff suppressed because it is too large
Load diff
185
drivers/battery/sec_adc.c
Normal file
185
drivers/battery/sec_adc.c
Normal 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);
|
||||
}
|
||||
|
8210
drivers/battery/sec_battery.c
Normal file
8210
drivers/battery/sec_battery.c
Normal file
File diff suppressed because it is too large
Load diff
409
drivers/battery/sec_charger.c
Normal file
409
drivers/battery/sec_charger.c
Normal 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");
|
660
drivers/battery/sec_fuelgauge.c
Normal file
660
drivers/battery/sec_fuelgauge.c
Normal 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");
|
740
drivers/battery/smb347_charger.c
Normal file
740
drivers/battery/smb347_charger.c
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue