1209 lines
35 KiB
C
Executable file
1209 lines
35 KiB
C
Executable file
/*
|
|
* s2asl01_switching.c
|
|
* Samsung S2ASL01 Switching IC driver
|
|
*
|
|
* Copyright (C) 2018 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/power/s2asl01_switching.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
|
|
static enum power_supply_property s2asl01_main_props[] = {
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW
|
|
};
|
|
|
|
static enum power_supply_property s2asl01_sub_props[] = {
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW
|
|
};
|
|
|
|
static struct device_attribute s2asl01_limiter_attrs[] = {
|
|
S2ASL01_LIMITER_ATTR(limiter_data),
|
|
};
|
|
|
|
static int s2asl01_write_reg(struct i2c_client *client, int reg, u8 data)
|
|
{
|
|
struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&switching->i2c_lock);
|
|
ret = i2c_smbus_write_byte_data(client, reg, data);
|
|
mutex_unlock(&switching->i2c_lock);
|
|
if (ret < 0) {
|
|
pr_info("%s [%s]: reg(0x%x), ret(%d)\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], reg, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int s2asl01_read_reg(struct i2c_client *client, int reg, void *data)
|
|
{
|
|
struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&switching->i2c_lock);
|
|
ret = i2c_smbus_read_byte_data(client, reg);
|
|
mutex_unlock(&switching->i2c_lock);
|
|
if (ret < 0) {
|
|
pr_info("%s [%s]: reg(0x%x), ret(%d)\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], reg, ret);
|
|
return ret;
|
|
}
|
|
ret &= 0xff;
|
|
*(u8 *)data = (u8)ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int s2asl01_update_reg(struct i2c_client *client, u8 reg, u8 val, u8 mask)
|
|
{
|
|
struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
u8 old_val = 0, new_val = 0;
|
|
|
|
mutex_lock(&switching->i2c_lock);
|
|
ret = i2c_smbus_read_byte_data(client, reg);
|
|
if (ret >= 0) {
|
|
old_val = ret & 0xff;
|
|
new_val = (val & mask) | (old_val & (~mask));
|
|
ret = i2c_smbus_write_byte_data(client, reg, new_val);
|
|
}
|
|
mutex_unlock(&switching->i2c_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void s2asl01_test_read(struct i2c_client *client)
|
|
{
|
|
struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
|
|
u8 data = 0;
|
|
char str[1016] = {0,};
|
|
int i = 0;
|
|
|
|
/* address 0x00 ~ 0x1f */
|
|
for (i = 0x0; i <= 0x1F; i++) {
|
|
s2asl01_read_reg(switching->client, i, &data);
|
|
sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
|
|
}
|
|
|
|
pr_info("%s [%s]: %s\n", __func__, current_limiter_type_str[switching->pdata->bat_type], str);
|
|
}
|
|
|
|
static void s2asl01_set_in_ok(struct s2asl01_switching_data *switching, bool onoff)
|
|
{
|
|
pr_info("%s[%s]: INOK = %d\n", __func__, current_limiter_type_str[switching->pdata->bat_type], onoff);
|
|
if (onoff)
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
1 << S2ASL01_CTRL3_INOK_SHIFT, S2ASL01_CTRL3_INOK_MASK);
|
|
else
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
0 << S2ASL01_CTRL3_INOK_SHIFT, S2ASL01_CTRL3_INOK_MASK);
|
|
}
|
|
|
|
static void s2asl01_set_supllement_mode(struct s2asl01_switching_data *switching, bool onoff)
|
|
{
|
|
pr_info("%s[%s]: SUPLLEMENT MODE = %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], onoff);
|
|
|
|
if (onoff) {
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
1 << S2ASL01_CTRL3_SUPLLEMENTMODE_SHIFT, S2ASL01_CTRL3_SUPLLEMENTMODE_MASK);
|
|
switching->supllement_mode = true;
|
|
} else {
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
0 << S2ASL01_CTRL3_SUPLLEMENTMODE_SHIFT, S2ASL01_CTRL3_SUPLLEMENTMODE_MASK);
|
|
switching->supllement_mode = false;
|
|
}
|
|
}
|
|
|
|
static void s2asl01_set_recharging_start(struct s2asl01_switching_data *switching)
|
|
{
|
|
pr_info("%s: INOK = %d, SUPLLEMENT MODE = %d\n",
|
|
__func__, switching->in_ok, switching->supllement_mode);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL1,
|
|
1 << S2ASL01_CTRL1_RESTART_SHIFT, S2ASL01_CTRL1_RESTART_MASK);
|
|
}
|
|
|
|
static void s2asl01_set_eoc_on(struct s2asl01_switching_data *switching)
|
|
{
|
|
pr_info("%s[%s]: INOK = %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], switching->in_ok);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL1,
|
|
1 << S2ASL01_CTRL1_EOC_SHIFT, S2ASL01_CTRL1_EOC_MASK);
|
|
}
|
|
|
|
static void s2asl01_set_dischg_mode(struct s2asl01_switching_data *switching, int chg_mode)
|
|
{
|
|
u8 val = 0;
|
|
int ret = 0;
|
|
|
|
switch (chg_mode) {
|
|
case CURRENT_REGULATION:
|
|
case CURRENT_VOLTAGE_REGULATION:
|
|
case NO_REGULATION_FULLY_ON:
|
|
val = chg_mode;
|
|
break;
|
|
case CURRENT_SMARTER_VOLTAGE_REGULATION:
|
|
pr_info("%s: chg_mode(%d) changes to CURRENT_VOLTAGE_REGULATION\n",
|
|
__func__, chg_mode);
|
|
val = CURRENT_VOLTAGE_REGULATION;
|
|
break;
|
|
default:
|
|
pr_info("%s: wrong input(%d)\n", __func__, chg_mode);
|
|
break;
|
|
}
|
|
|
|
ret = s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
val << S2ASL01_CTRL3_DIS_MODE_SHIFT, S2ASL01_CTRL3_DIS_MODE_MASK);
|
|
if (ret < 0)
|
|
pr_err("%s, i2c read fail\n", __func__);
|
|
|
|
pr_info("%s: set discharge mode (%d)\n", __func__, chg_mode);
|
|
}
|
|
|
|
static int s2asl01_get_dischg_mode(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 val = 0, chg_mode = 0;
|
|
int ret = 0;
|
|
|
|
ret = s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val);
|
|
chg_mode = (val & S2ASL01_CTRL3_DIS_MODE_MASK) >> S2ASL01_CTRL3_DIS_MODE_SHIFT;
|
|
|
|
switch (chg_mode) {
|
|
case CURRENT_REGULATION:
|
|
case CURRENT_VOLTAGE_REGULATION:
|
|
case NO_REGULATION_FULLY_ON:
|
|
return chg_mode;
|
|
case CURRENT_SMARTER_VOLTAGE_REGULATION:
|
|
pr_info("%s: chg_mode(%d) CURRENT_VOLTAGE_REGULATION\n",
|
|
__func__, chg_mode);
|
|
return CURRENT_VOLTAGE_REGULATION;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static void s2asl01_set_chg_mode(struct s2asl01_switching_data *switching, int chg_mode)
|
|
{
|
|
u8 val = 0;
|
|
int ret = 0;
|
|
|
|
switch (chg_mode) {
|
|
case CURRENT_REGULATION:
|
|
case CURRENT_VOLTAGE_REGULATION:
|
|
case CURRENT_SMARTER_VOLTAGE_REGULATION:
|
|
case NO_REGULATION_FULLY_ON:
|
|
val = chg_mode;
|
|
break;
|
|
default:
|
|
pr_info("%s: wrong input(%d)\n", __func__, chg_mode);
|
|
break;
|
|
}
|
|
|
|
ret = s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
|
|
val << S2ASL01_CTRL3_CHG_MODE_SHIFT, S2ASL01_CTRL3_CHG_MODE_MASK);
|
|
if (ret < 0)
|
|
pr_err("%s, i2c read fail\n", __func__);
|
|
|
|
pr_info("%s: set charge mode (%d)\n", __func__, chg_mode);
|
|
}
|
|
|
|
static int s2asl01_get_chg_mode(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 val = 0, chg_mode = 0;
|
|
int ret = 0;
|
|
|
|
ret = s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val);
|
|
chg_mode = (val & S2ASL01_CTRL3_CHG_MODE_MASK) >> S2ASL01_CTRL3_CHG_MODE_SHIFT;
|
|
|
|
switch (chg_mode) {
|
|
case CURRENT_REGULATION:
|
|
case CURRENT_VOLTAGE_REGULATION:
|
|
case CURRENT_SMARTER_VOLTAGE_REGULATION:
|
|
case NO_REGULATION_FULLY_ON:
|
|
return chg_mode;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int s2asl01_get_vchg(struct s2asl01_switching_data *switching, int mode)
|
|
{
|
|
u8 data[2] = {0, };
|
|
u16 temp = 0;
|
|
u32 vchg = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_VCHG, &data[0]);
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_VCHG, &data[1]);
|
|
|
|
//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
|
|
// __func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);
|
|
|
|
temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
|
|
temp &= 0x0FFF;
|
|
vchg = temp * 1250;
|
|
|
|
if (mode == S2M_BATTERY_VOLTAGE_MV)
|
|
vchg = vchg / 1000;
|
|
|
|
pr_info("%s [%s]: vchg = %d %s\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], vchg,
|
|
(mode == S2M_BATTERY_VOLTAGE_UV) ? "uV" : "mV");
|
|
|
|
return vchg;
|
|
}
|
|
|
|
static int s2asl01_get_vbat(struct s2asl01_switching_data *switching, int mode)
|
|
{
|
|
u8 data[2] = {0, };
|
|
u16 temp = 0;
|
|
u32 vbat = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_VBAT, &data[0]);
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_VBAT, &data[1]);
|
|
|
|
//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
|
|
// __func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);
|
|
|
|
temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
|
|
temp &= 0x0FFF;
|
|
vbat = temp * 1250;
|
|
|
|
if (mode == S2M_BATTERY_VOLTAGE_MV)
|
|
vbat = vbat / 1000;
|
|
|
|
pr_info("%s [%s]: vbat = %d %s\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], vbat,
|
|
(mode == S2M_BATTERY_VOLTAGE_UV) ? "uV" : "mV");
|
|
|
|
return vbat;
|
|
}
|
|
|
|
static int s2asl01_get_ichg(struct s2asl01_switching_data *switching, int mode)
|
|
{
|
|
u8 data[2] = {0, };
|
|
u16 temp = 0;
|
|
u32 ichg = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_ICHG, &data[0]);
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_ICHG, &data[1]);
|
|
|
|
//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
|
|
// __func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);
|
|
|
|
temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
|
|
temp &= 0x0FFF;
|
|
ichg = (temp * 15625) / 10;
|
|
|
|
if (mode == S2M_BATTERY_CURRENT_MA)
|
|
ichg = ichg / 1000;
|
|
|
|
pr_info("%s [%s]: Ichg = %d %s\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], ichg,
|
|
(mode == S2M_BATTERY_CURRENT_UA) ? "uA" : "mA");
|
|
|
|
return ichg;
|
|
}
|
|
|
|
static int s2asl01_get_idischg(struct s2asl01_switching_data *switching, int mode)
|
|
{
|
|
u8 data[2] = {0, };
|
|
u16 temp = 0;
|
|
u32 idischg = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_IDISCHG, &data[0]);
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_IDISCHG, &data[1]);
|
|
|
|
//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
|
|
// __func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);
|
|
|
|
temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
|
|
temp &= 0x0FFF;
|
|
idischg = (temp * 15625) / 10;
|
|
|
|
if (mode == S2M_BATTERY_CURRENT_MA)
|
|
idischg = idischg / 1000;
|
|
|
|
pr_info("%s [%s]: IDISCHG = %d %s\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], idischg,
|
|
(mode == S2M_BATTERY_CURRENT_UA) ? "uA" : "mA");
|
|
|
|
return idischg;
|
|
}
|
|
|
|
static void s2asl01_set_fast_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (switching->rev_id == 0) {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 3200)
|
|
data = (charging_current / 50) - 1;
|
|
else
|
|
data = 0x3F;
|
|
} else {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 350)
|
|
data = (charging_current - 50) / 75;
|
|
else if (charging_current > 350 && charging_current <= 3300)
|
|
data = ((charging_current - 350) / 50) + 4;
|
|
else
|
|
data = 0x3F;
|
|
}
|
|
|
|
pr_info("%s [%s]: current %d, 0x%02x\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL4, data, FCC_CHG_CURRENTLIMIT_MASK);
|
|
}
|
|
|
|
static int s2asl01_get_fast_charging_current_limit(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int charging_current = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL4, &data);
|
|
|
|
data = data & FCC_CHG_CURRENTLIMIT_MASK;
|
|
|
|
if (data > 0x3F) {
|
|
pr_err("%s: Invalid fast charging current limit value\n", __func__);
|
|
data = 0x3F;
|
|
}
|
|
|
|
if (switching->rev_id == 0)
|
|
charging_current = (data + 1) * 50;
|
|
else {
|
|
if ((data >= 0x00) && (data <= 0x04))
|
|
charging_current = (data * 75) + 50;
|
|
else
|
|
charging_current = ((data - 4) * 50) + 350;
|
|
}
|
|
|
|
return charging_current;
|
|
}
|
|
|
|
static void s2asl01_set_trickle_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (switching->rev_id == 0) {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 500)
|
|
data = (charging_current / 50) - 1;
|
|
else
|
|
data = 0x09;
|
|
} else {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 350)
|
|
data = (charging_current - 50) / 75;
|
|
else if (charging_current > 350 && charging_current <= 550)
|
|
data = ((charging_current - 350) / 50) + 4;
|
|
else
|
|
data = 0x08;
|
|
}
|
|
|
|
pr_info("%s [%s]: current %d, 0x%02x\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL5, data, TRICKLE_CHG_CURRENT_LIMIT_MASK);
|
|
}
|
|
|
|
static int s2asl01_get_trickle_charging_current_limit(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int charging_current = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL5, &data);
|
|
|
|
data = data & TRICKLE_CHG_CURRENT_LIMIT_MASK;
|
|
|
|
if (switching->rev_id == 0) {
|
|
if (data > 0x09) {
|
|
pr_err("%s: Invalid trickle charging current limit value\n", __func__);
|
|
data = 0x09;
|
|
}
|
|
charging_current = (data + 1) * 50;
|
|
} else {
|
|
if (data > 0x08) {
|
|
pr_err("%s: Invalid trickle charging current limit value\n", __func__);
|
|
data = 0x08;
|
|
}
|
|
if ((data >= 0x00) && (data <= 0x04))
|
|
charging_current = (data * 75) + 50;
|
|
else
|
|
charging_current = ((data - 4) * 50) + 350;
|
|
}
|
|
|
|
return charging_current;
|
|
}
|
|
|
|
static void s2asl01_set_dischg_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (switching->rev_id == 0) {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 6400)
|
|
data = (charging_current / 50) - 1;
|
|
else
|
|
data = 0x7F;
|
|
} else {
|
|
if (charging_current <= 50)
|
|
data = 0x00;
|
|
else if (charging_current > 50 && charging_current <= 350)
|
|
data = (charging_current - 50) / 75;
|
|
else if (charging_current > 350 && charging_current <= 6500)
|
|
data = ((charging_current - 350) / 50) + 4;
|
|
else
|
|
data = 0x7F;
|
|
}
|
|
pr_info("%s [%s]: current %d, 0x%02x\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL6, data, DISCHG_CURRENT_LIMIT_MASK);
|
|
}
|
|
|
|
static int s2asl01_get_dischg_charging_current_limit(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int dischg_current = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL6, &data);
|
|
|
|
data = data & DISCHG_CURRENT_LIMIT_MASK;
|
|
|
|
if (data > 0x7F) {
|
|
pr_err("%s: Invalid discharging current limit value\n", __func__);
|
|
data = 0x7F;
|
|
}
|
|
|
|
if (switching->rev_id == 0)
|
|
dischg_current = (data + 1) * 50;
|
|
else {
|
|
if ((data >= 0x00) && (data <= 0x04))
|
|
dischg_current = (data * 75) + 50;
|
|
else
|
|
dischg_current = ((data - 4) * 50) + 350;
|
|
}
|
|
|
|
return dischg_current;
|
|
}
|
|
|
|
static void s2asl01_set_recharging_voltage(struct s2asl01_switching_data *switching, int charging_voltage)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (charging_voltage < 4000)
|
|
data = 0x90;
|
|
else if (charging_voltage >= 4000 && charging_voltage < 4400)
|
|
data = (charging_voltage - 2560) / 10;
|
|
else
|
|
data = 0xB8;
|
|
|
|
pr_info("%s: voltage %d, 0x%02x\n", __func__, charging_voltage, data);
|
|
|
|
s2asl01_write_reg(switching->client, S2ASL01_SWITCHING_TOP_RECHG_CTRL1, data);
|
|
}
|
|
|
|
static int s2asl01_get_recharging_voltage(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int charging_voltage = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_RECHG_CTRL1, &data);
|
|
charging_voltage = (data * 10) + 2560;
|
|
|
|
return charging_voltage;
|
|
}
|
|
|
|
static void s2asl01_set_eoc_voltage(struct s2asl01_switching_data *switching, int charging_voltage)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (charging_voltage < 4000)
|
|
data = 0x90;
|
|
else if (charging_voltage >= 4000 && charging_voltage < 4400)
|
|
data = (charging_voltage - 2560) / 10;
|
|
else
|
|
data = 0xB8;
|
|
|
|
pr_info("%s [%s]: voltage %d, 0x%02x\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], charging_voltage, data);
|
|
|
|
s2asl01_write_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL1, data);
|
|
}
|
|
|
|
static int s2asl01_get_eoc_voltage(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int charging_voltage = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL1, &data);
|
|
charging_voltage = (data * 10) + 2560;
|
|
|
|
return charging_voltage;
|
|
}
|
|
|
|
static void s2asl01_set_eoc_current(struct s2asl01_switching_data *switching, int charging_current)
|
|
{
|
|
u8 data = 0;
|
|
|
|
if (charging_current < 100)
|
|
data = 0x04;
|
|
else if (charging_current >= 100 && charging_current < 775)
|
|
data = charging_current / 25;
|
|
else
|
|
data = 0x1F;
|
|
|
|
pr_info("%s [%s]: current %d, 0x%02x\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);
|
|
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL2, data, 0x1F);
|
|
}
|
|
|
|
static int s2asl01_get_eoc_current(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 data = 0;
|
|
int charging_current = 0;
|
|
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL2, &data);
|
|
data &= 0x1F;
|
|
charging_current = data * 25;
|
|
|
|
return charging_current;
|
|
}
|
|
|
|
static void s2asl01_powermeter_onoff(struct s2asl01_switching_data *switching, bool onoff)
|
|
{
|
|
pr_info("%s [%s]: (%d)\n", __func__, current_limiter_type_str[switching->pdata->bat_type], onoff);
|
|
|
|
/* Power Meter Continuous Operation Mode
|
|
* [7]: VCHG
|
|
* [6]: VBAT
|
|
* [5]: ICHG
|
|
* [4]: IDISCHG
|
|
*/
|
|
if (onoff) {
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_PM_ENABLE, 0xF0, 0xF0);
|
|
switching->power_meter = true;
|
|
} else {
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_PM_ENABLE, 0x00, 0xF0);
|
|
switching->power_meter = false;
|
|
}
|
|
}
|
|
|
|
static int s2asl01_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct s2asl01_switching_data *switching = power_supply_get_drvdata(psy);
|
|
enum s2m_power_supply_property s2m_psp = (enum s2m_power_supply_property)psp;
|
|
int vchg, vbat, ichg, idischg;
|
|
|
|
//pr_info("%s [%s]\n", __func__, current_limiter_type_str[switching->pdata->bat_type]);
|
|
|
|
switch ((int)psp) {
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
vchg = s2asl01_get_vchg(switching, S2M_BATTERY_VOLTAGE_MV);
|
|
vbat = s2asl01_get_vbat(switching, S2M_BATTERY_VOLTAGE_MV);
|
|
ichg = s2asl01_get_ichg(switching, S2M_BATTERY_CURRENT_MA);
|
|
idischg = s2asl01_get_idischg(switching, S2M_BATTERY_CURRENT_MA);
|
|
pr_info("%s [%s]: vchg=%dmV, vbat=%dmV, ichg=%dmA, idischg=%dmA\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type],
|
|
vchg, vbat, ichg, idischg);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
val->intval = s2asl01_get_vbat(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = s2asl01_get_ichg(switching, S2M_BATTERY_CURRENT_UA);
|
|
if (val->intval == 0)
|
|
val->intval = s2asl01_get_idischg(switching, S2M_BATTERY_CURRENT_UA) * (-1);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_MIN ... POWER_SUPPLY_S2M_PROP_MAX:
|
|
switch (s2m_psp) {
|
|
case POWER_SUPPLY_S2M_PROP_CHGIN_OK:
|
|
val->intval = switching->in_ok ? 1 : 0;
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_SUPLLEMENT_MODE:
|
|
val->intval = switching->supllement_mode ? 1 : 0;
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_DISCHG_MODE:
|
|
val->intval = s2asl01_get_dischg_mode(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_CHG_MODE:
|
|
val->intval = s2asl01_get_chg_mode(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_CHG_VOLTAGE:
|
|
val->intval = s2asl01_get_vchg(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_BAT_VOLTAGE:
|
|
val->intval = s2asl01_get_vbat(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_CHG_CURRENT:
|
|
val->intval = s2asl01_get_ichg(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_DISCHG_CURRENT:
|
|
val->intval = s2asl01_get_idischg(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_FASTCHG_LIMIT_CURRENT:
|
|
val->intval = s2asl01_get_fast_charging_current_limit(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_TRICKLECHG_LIMIT_CURRENT:
|
|
val->intval = s2asl01_get_trickle_charging_current_limit(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_DISCHG_LIMIT_CURRENT:
|
|
val->intval = s2asl01_get_dischg_charging_current_limit(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_RECHG_VOLTAGE:
|
|
val->intval = s2asl01_get_recharging_voltage(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_EOC_VOLTAGE:
|
|
val->intval = s2asl01_get_eoc_voltage(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_EOC_CURRENT:
|
|
val->intval = s2asl01_get_eoc_current(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_POWERMETER_ENABLE:
|
|
val->intval = switching->power_meter;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
//s2asl01_test_read(switching->client);
|
|
return 0;
|
|
}
|
|
|
|
static int s2asl01_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct s2asl01_switching_data *switching = power_supply_get_drvdata(psy);
|
|
enum s2m_power_supply_property s2m_psp = (enum s2m_power_supply_property)psp;
|
|
|
|
//pr_info("%s [%s]\n", __func__, current_limiter_type_str[switching->pdata->bat_type]);
|
|
|
|
switch ((int)psp) {
|
|
case POWER_SUPPLY_PROP_CHARGE_FULL:
|
|
pr_info("%s [%s]: is it full? %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
|
|
s2asl01_set_supllement_mode(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENERGY_NOW:
|
|
pr_info("%s [%s]: pwr off mode 2 %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
|
|
if (val->intval)
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL2,
|
|
S2ASL01_SUB_PWR_OFF_MODE2, S2ASL01_SUB_PWR_OFF_MODE_MASK);
|
|
else
|
|
s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL2,
|
|
S2ASL01_SUB_PWR_OFF_MODE1, S2ASL01_SUB_PWR_OFF_MODE_MASK);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_MIN ... POWER_SUPPLY_S2M_PROP_MAX:
|
|
switch (s2m_psp) {
|
|
case POWER_SUPPLY_S2M_PROP_CHARGING_ENABLED:
|
|
pr_info("%s [%s]: chg en ? %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
|
|
//switching->in_ok = val->intval;
|
|
//s2asl01_set_in_ok(switching, switching->in_ok);
|
|
//s2asl01_set_eoc_on(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_CHGIN_OK:
|
|
switching->in_ok = val->intval;
|
|
s2asl01_set_in_ok(switching, switching->in_ok);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_SUPLLEMENT_MODE:
|
|
s2asl01_set_supllement_mode(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_RECHG_ON:
|
|
s2asl01_set_recharging_start(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_EOC_ON:
|
|
s2asl01_set_eoc_on(switching);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_DISCHG_MODE:
|
|
s2asl01_set_dischg_mode(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_CHG_MODE:
|
|
s2asl01_set_chg_mode(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_FASTCHG_LIMIT_CURRENT:
|
|
s2asl01_set_fast_charging_current_limit(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_TRICKLECHG_LIMIT_CURRENT:
|
|
s2asl01_set_trickle_charging_current_limit(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_DISCHG_LIMIT_CURRENT:
|
|
s2asl01_set_dischg_charging_current_limit(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_RECHG_VOLTAGE:
|
|
s2asl01_set_recharging_voltage(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_EOC_VOLTAGE:
|
|
s2asl01_set_eoc_voltage(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_EOC_CURRENT:
|
|
s2asl01_set_eoc_current(switching, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_S2M_PROP_POWERMETER_ENABLE:
|
|
s2asl01_powermeter_onoff(switching, val->intval);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
s2asl01_test_read(switching->client);
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
static int s2asl01_switching_parse_dt(struct device *dev, struct s2asl01_platform_data *pdata)
|
|
{
|
|
//struct device_node *np = of_find_node_by_name(NULL, "s2asl01-switching");
|
|
struct device_node *np = dev->of_node;
|
|
int ret = 0;
|
|
//enum of_gpio_flags irq_gpio_flags;
|
|
|
|
pr_info("%s parsing start\n", __func__);
|
|
|
|
if (np == NULL) {
|
|
pr_err("%s np is NULL\n", __func__);
|
|
} else {
|
|
ret = of_property_read_u32(np, "limiter,bat_type", &pdata->bat_type);
|
|
if (ret < 0)
|
|
pr_info("%s: bat_type is empty\n", __func__);
|
|
|
|
if (pdata->bat_type & LIMITER_MAIN) {
|
|
pr_info("%s: It is MAIN battery dt\n", __func__);
|
|
#if 0
|
|
ret = pdata->bat_enb = of_get_named_gpio_flags(np, "limiter,main_bat_enb_gpio",
|
|
0, &irq_gpio_flags);
|
|
if (ret < 0)
|
|
dev_err(dev, "%s: can't main_bat_enb_gpio\r\n", __func__);
|
|
#endif
|
|
np = of_find_node_by_name(NULL, "sec-dual-battery");
|
|
if (!np)
|
|
pr_info("%s: np NULL\n", __func__);
|
|
else {
|
|
ret = of_property_read_u32(np, "battery,main_charging_rate", &pdata->charging_rate);
|
|
if (ret)
|
|
pdata->charging_rate = 60;
|
|
pr_info("%s: pdata->charging_rate(%d)\n", __func__, pdata->charging_rate);
|
|
}
|
|
np = of_find_node_by_name(NULL, "s2asl01-switching-main");
|
|
} else if (pdata->bat_type & LIMITER_SUB) {
|
|
pr_info("%s: It is SUB battery dt\n", __func__);
|
|
#if 0
|
|
ret = pdata->bat_enb = of_get_named_gpio_flags(np, "limiter,sub_bat_enb_gpio",
|
|
0, &irq_gpio_flags);
|
|
if (ret < 0)
|
|
dev_err(dev, "%s: can't sub_bat_enb_gpio\r\n", __func__);
|
|
#endif
|
|
np = of_find_node_by_name(NULL, "sec-dual-battery");
|
|
if (!np)
|
|
pr_info("%s: np NULL\n", __func__);
|
|
else {
|
|
ret = of_property_read_u32(np, "battery,sub_charging_rate", &pdata->charging_rate);
|
|
if (ret)
|
|
pdata->charging_rate = 50;
|
|
pr_info("%s: charging_rate(%d)\n", __func__, pdata->charging_rate);
|
|
}
|
|
np = of_find_node_by_name(NULL, "s2asl01-switching-sub");
|
|
}
|
|
|
|
ret = of_property_read_string(np, "limiter,switching_name", (char const **)&pdata->switching_name);
|
|
if (ret < 0) {
|
|
pr_info("%s: Switching IC name is empty\n", __func__);
|
|
pdata->switching_name = "s2asl01-switching";
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,chg_current_limit", &pdata->chg_current_limit);
|
|
if (ret < 0) {
|
|
ret = of_property_read_u32(np, "limiter,chg_current_max", &pdata->chg_current_max);
|
|
if (ret < 0)
|
|
pdata->chg_current_limit = 1650;
|
|
else
|
|
pdata->chg_current_limit = pdata->chg_current_max * pdata->charging_rate / 100;
|
|
}
|
|
pr_info("%s: Chg current limit is (%d)\n", __func__, pdata->chg_current_limit);
|
|
|
|
ret = of_property_read_u32(np, "limiter,eoc", &pdata->eoc);
|
|
if (ret < 0) {
|
|
pr_info("%s: eoc is empty\n", __func__);
|
|
pdata->eoc = 200; /* for interrupt setting, not used */
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,float_voltage", &pdata->float_voltage);
|
|
if (ret < 0) {
|
|
pr_info("%s: float voltage is empty\n", __func__);
|
|
pdata->float_voltage = 4350; /* for interrupt setting, not used */
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,hys_vchg_level", &pdata->hys_vchg_level);
|
|
if (ret < 0) {
|
|
pr_info("%s: Hysteresis level is empty(vchg)\n", __func__);
|
|
pdata->hys_vchg_level = 4; /* 250mV(default) */
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,hys_vbat_level", &pdata->hys_vbat_level);
|
|
if (ret < 0) {
|
|
pr_info("%s: Hysteresis level is empty(vbat)\n", __func__);
|
|
pdata->hys_vbat_level = 4; /* 250mV(default) */
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,hys_ichg_level", &pdata->hys_ichg_level);
|
|
if (ret < 0) {
|
|
pr_info("%s: Hysteresis level is empty(ichg)\n", __func__);
|
|
pdata->hys_ichg_level = 4; /* 500mA(default) */
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "limiter,hys_idischg_level", &pdata->hys_idischg_level);
|
|
if (ret < 0) {
|
|
pr_info("%s: Hysteresis level is empty(idischg)\n", __func__);
|
|
pdata->hys_idischg_level = 4; /* 500mA(default) */
|
|
}
|
|
|
|
pdata->tsd_en = (of_find_property(np, "limiter,tsd-en", NULL)) ? true : false;
|
|
}
|
|
|
|
pr_info("%s parsing end\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id s2asl01_switching_match_table[] = {
|
|
{ .compatible = "samsung,s2asl01-switching",},
|
|
{},
|
|
};
|
|
#else
|
|
static int s2asl01_switching_parse_dt(struct device *dev, struct s2asl01_switching_data *switching)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#define s2asl01_switching_match_table NULL
|
|
#endif /* CONFIG_OF */
|
|
|
|
static int s2asl01_limiter_create_attrs(struct device *dev)
|
|
{
|
|
int i, rc;
|
|
|
|
for (i = 0; i < (int)ARRAY_SIZE(s2asl01_limiter_attrs); i++) {
|
|
rc = device_create_file(dev, &s2asl01_limiter_attrs[i]);
|
|
if (rc)
|
|
goto create_attrs_failed;
|
|
}
|
|
return rc;
|
|
|
|
create_attrs_failed:
|
|
dev_err(dev, "%s: failed (%d)\n", __func__, rc);
|
|
while (i--)
|
|
device_remove_file(dev, &s2asl01_limiter_attrs[i]);
|
|
return rc;
|
|
}
|
|
|
|
ssize_t s2asl01_limiter_show_attrs(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct s2asl01_switching_data *limiter = power_supply_get_drvdata(psy);
|
|
const ptrdiff_t offset = attr - s2asl01_limiter_attrs;
|
|
int i = 0, j = 0;
|
|
u8 data = 0;
|
|
|
|
dev_info(limiter->dev, "%s: (%ld)\n", __func__, offset);
|
|
|
|
switch (offset) {
|
|
case LIMITER_DATA:
|
|
for (j = 0; j <= S2ASL01_SWITCHING_PM_I_OPTION; j++) {
|
|
s2asl01_read_reg(limiter->client, j, &data);
|
|
i += scnprintf(buf + i, PAGE_SIZE - i,
|
|
"0x%02x:\t0x%02x\n", j, data);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
ssize_t s2asl01_limiter_store_attrs(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct s2asl01_switching_data *limiter = power_supply_get_drvdata(psy);
|
|
const ptrdiff_t offset = attr - s2asl01_limiter_attrs;
|
|
int ret = 0;
|
|
int x, y;
|
|
|
|
dev_info(limiter->dev, "%s: (%ld)\n", __func__, offset);
|
|
|
|
switch (offset) {
|
|
case LIMITER_DATA:
|
|
if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) {
|
|
u8 addr = x;
|
|
u8 data = y;
|
|
|
|
if (s2asl01_write_reg(limiter->client, addr, data) < 0)
|
|
dev_info(limiter->dev, "%s: addr: 0x%x write fail\n", __func__, addr);
|
|
}
|
|
ret = count;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void s2asl01_get_rev_id(struct s2asl01_switching_data *switching)
|
|
{
|
|
u8 val1 = 0, val2 = 0;
|
|
|
|
/* rev ID */
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_ID, &val1);
|
|
if ((val1 & 0xF0) == 0) {
|
|
s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val2);
|
|
if (val2 != 0x0C) {
|
|
/* EVT0 0x11 address default value: 0x02 */
|
|
switching->es_num = 0;
|
|
switching->rev_id = 0;
|
|
} else {
|
|
/* EVT1 0x11 address default value: 0x0C */
|
|
switching->es_num = 1;
|
|
switching->rev_id = 1;
|
|
}
|
|
} else {
|
|
switching->es_num = (val1 & 0xC0) >> 6;
|
|
switching->rev_id = (val1 & 0x30) >> 4;
|
|
}
|
|
|
|
pr_info("%s [%s]: rev id: %d, es_num = %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type],
|
|
switching->rev_id, switching->es_num);
|
|
}
|
|
|
|
static void s2asl01_init_regs(struct s2asl01_switching_data *switching)
|
|
{
|
|
pr_info("%s: s2asl01 switching initialize\n", __func__);
|
|
|
|
/* Set supllement mode */
|
|
s2asl01_set_supllement_mode(switching, true);
|
|
|
|
s2asl01_test_read(switching->client);
|
|
}
|
|
|
|
static const struct power_supply_desc s2asl01_main_power_supply_desc = {
|
|
.name = "s2asl01-switching-main",
|
|
.type = POWER_SUPPLY_TYPE_UNKNOWN,
|
|
.properties = s2asl01_main_props,
|
|
.num_properties = ARRAY_SIZE(s2asl01_main_props),
|
|
.get_property = s2asl01_get_property,
|
|
.set_property = s2asl01_set_property,
|
|
.no_thermal = true,
|
|
};
|
|
|
|
static const struct power_supply_desc s2asl01_sub_power_supply_desc = {
|
|
.name = "s2asl01-switching-sub",
|
|
.type = POWER_SUPPLY_TYPE_UNKNOWN,
|
|
.properties = s2asl01_sub_props,
|
|
.num_properties = ARRAY_SIZE(s2asl01_sub_props),
|
|
.get_property = s2asl01_get_property,
|
|
.set_property = s2asl01_set_property,
|
|
.no_thermal = true,
|
|
};
|
|
|
|
static int s2asl01_switching_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device_node *of_node = client->dev.of_node;
|
|
struct s2asl01_switching_data *switching;
|
|
struct s2asl01_platform_data *pdata = client->dev.platform_data;
|
|
struct power_supply_config psy_cfg = {};
|
|
int ret = 0;
|
|
|
|
dev_info(&client->dev, "%s: S2ASL01 Switching Driver Loading\n", __func__);
|
|
|
|
if (of_node) {
|
|
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
ret = s2asl01_switching_parse_dt(&client->dev, pdata);
|
|
if (ret < 0)
|
|
goto err_parse_dt;
|
|
} else {
|
|
pdata = client->dev.platform_data;
|
|
}
|
|
|
|
switching = kzalloc(sizeof(*switching), GFP_KERNEL);
|
|
if (switching == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_limiter_nomem;
|
|
}
|
|
switching->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(switching->dev, "I2C functionality is not supported.\n");
|
|
ret = -EINVAL;
|
|
goto err_i2cfunc_not_support;
|
|
}
|
|
switching->client = client;
|
|
switching->pdata = pdata;
|
|
|
|
i2c_set_clientdata(client, switching);
|
|
mutex_init(&switching->i2c_lock);
|
|
|
|
if (switching->pdata->bat_type & LIMITER_MAIN) {
|
|
psy_cfg.drv_data = switching;
|
|
switching->psy_sw = power_supply_register(switching->dev, &s2asl01_main_power_supply_desc, &psy_cfg);
|
|
if ((void *)switching->psy_sw < 0) {
|
|
pr_err("%s: Failed to Register psy_sw\n", __func__);
|
|
goto err_supply_unreg;
|
|
}
|
|
} else if (switching->pdata->bat_type & LIMITER_SUB) {
|
|
psy_cfg.drv_data = switching;
|
|
switching->psy_sw = power_supply_register(switching->dev, &s2asl01_sub_power_supply_desc, &psy_cfg);
|
|
if ((void *)switching->psy_sw < 0) {
|
|
pr_err("%s: Failed to Register psy_sw\n", __func__);
|
|
goto err_supply_unreg;
|
|
}
|
|
}
|
|
|
|
switching->wqueue = create_singlethread_workqueue("limiter_workqueue");
|
|
if (!switching->wqueue) {
|
|
pr_err("%s: Fail to Create Workqueue\n", __func__);
|
|
goto err_pdata_free;
|
|
}
|
|
|
|
switching->in_ok = false;
|
|
switching->supllement_mode = false;
|
|
switching->power_meter = false;
|
|
#if 0
|
|
pr_info("%s [%s]: enb = %d\n",
|
|
__func__, current_limiter_type_str[switching->pdata->bat_type],
|
|
gpio_get_value(switching->pdata->bat_enb));
|
|
#endif
|
|
s2asl01_get_rev_id(switching);
|
|
s2asl01_init_regs(switching);
|
|
|
|
/* sysfs create */
|
|
ret = s2asl01_limiter_create_attrs(&switching->psy_sw->dev);
|
|
if (ret) {
|
|
dev_err(switching->dev,
|
|
"%s: Failed to create_attrs\n", __func__);
|
|
goto err_irq;
|
|
}
|
|
|
|
dev_info(&client->dev, "%s: S2ASL01 Switching Driver Loaded\n", __func__);
|
|
return 0;
|
|
|
|
err_irq:
|
|
err_pdata_free:
|
|
power_supply_unregister(switching->psy_sw);
|
|
err_supply_unreg:
|
|
mutex_destroy(&switching->i2c_lock);
|
|
err_i2cfunc_not_support:
|
|
kfree(switching);
|
|
err_parse_dt:
|
|
err_limiter_nomem:
|
|
devm_kfree(&client->dev, pdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct i2c_device_id s2asl01_switching_id[] = {
|
|
{"s2asl01-switching", 0},
|
|
{}
|
|
};
|
|
|
|
static void s2asl01_switching_shutdown(struct i2c_client *client)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
}
|
|
|
|
static int s2asl01_switching_remove(struct i2c_client *client)
|
|
{
|
|
struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
|
|
|
|
power_supply_unregister(switching->psy_sw);
|
|
mutex_destroy(&switching->i2c_lock);
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
static int s2asl01_switching_suspend(struct device *dev)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int s2asl01_switching_resume(struct device *dev)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define s2asl01_switching_suspend NULL
|
|
#define s2asl01_switching_resume NULL
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(s2asl01_switching_pm_ops, s2asl01_switching_suspend, s2asl01_switching_resume);
|
|
|
|
static struct i2c_driver s2asl01_switching_driver = {
|
|
.driver = {
|
|
.name = "s2asl01-switching",
|
|
.owner = THIS_MODULE,
|
|
.pm = &s2asl01_switching_pm_ops,
|
|
.of_match_table = s2asl01_switching_match_table,
|
|
},
|
|
.probe = s2asl01_switching_probe,
|
|
.remove = s2asl01_switching_remove,
|
|
.shutdown = s2asl01_switching_shutdown,
|
|
.id_table = s2asl01_switching_id,
|
|
};
|
|
|
|
static int __init s2asl01_switching_init(void)
|
|
{
|
|
pr_info("%s: S2ASL01 Switching Init\n", __func__);
|
|
return i2c_add_driver(&s2asl01_switching_driver);
|
|
}
|
|
|
|
static void __exit s2asl01_switching_exit(void)
|
|
{
|
|
i2c_del_driver(&s2asl01_switching_driver);
|
|
}
|
|
module_init(s2asl01_switching_init);
|
|
module_exit(s2asl01_switching_exit);
|
|
|
|
MODULE_DESCRIPTION("Samsung S2ASL01 Switching IC Driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|