tegrakernel/kernel/kernel-4.9/drivers/power/supply/isl9238_charger.c

2063 lines
56 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* isl9238_charger.c -- ISL9238 Narrow o/p voltage DC charger driver
*
* Copyright (C) 2017-2018 NVIDIA CORPORATION. All rights reserved.
*
* Author: Venkat Reddy Talla <vreddytalla@nvidia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
* whether express or implied; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/power/battery-charger-gauge-comm.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#define MAX_STR_PRINT 50
/* Register definitions */
#define ISL9238_CHG_CURR_LIMIT 0x14
#define ISL9238_MAX_SYS_VOLTAGE 0x15
#define ISL9238_T1_T2_TWO_LEVEL 0x38
#define ISL9238_CONTROL0_OPTIONS 0x39
#define ISL9238_INFO1_CHG_STATUS 0x3A
#define ISL9238_ADPTR_CURR_LIMIT2 0x3B
#define ISL9238_CONTROL1_OPTIONS 0x3C
#define ISL9238_CONTROL2_OPTIONS 0x3D
#define ISL9238_MIN_SYS_VOLTAGE 0x3E
#define ISL9238_ADPTR_CURR_LIMIT1 0x3F
#define ISL9238_AC_PROCHOT 0x47
#define ISL9238_DC_PROCHOT 0x48
#define ISL9238_OTG_VOLTAGE 0x49
#define ISL9238_OTG_CURRENT 0x4A
#define ISL9238_INPUT_VOLTAGE 0x4B
#define ISL9238_CONTROL3_OPTIONS 0x4C
#define ISL9238_INFO2_CHG_STATUS 0x4D
#define ISL9238_CONTROL4_OPTIONS 0x4E
#define ISL9238_MANUFACTURER_ID 0xFE
#define ISL9238_DEVICE_ID 0xFF
#define ISL9238_CHG_CURR_LIMIT_MASK 0x1FFC
#define ISL9238_MAX_SYS_VOLTAGE_MASK 0x7FF8
#define ISL9238_MIN_SYS_VOLTAGE_MASK 0x3F00
#define ISL9238_INPUT_VOLTAGE_MASK 0x3F00
#define ISL9238_ADPTR_CURR_LIMIT1_MASK 0x1FFC
#define ISL9238_ADPTR_CURR_LIMIT2_MASK 0x1FFC
#define ISL9238_AC_PROCHOT_MASK 0x1F80
#define ISL9238_DC_PROCHOT_MASK 0x3F00
#define ISL9238_OTG_MASK 0x800
#define ISL9238_OTG_ENABLE 0x800
#define ISL9238_OTG_DISABLE 0x0
#define ISL9238_REGULATION_MASK 0x4
#define ISL9238_BAT_LEARN_MASK 0x1000
#define ISL9238_TURBO_MODE_MASK 0x40
#define ISL9238_TURBO_MODE_DISABLE 0x40
#define ISL9238_AUTO_CHARGE_MODE_MASK 0x80
#define ISL9238_SMBUS_TIMER_MASK 0x80
#define ISL9238_BATTERY_OVP_MASK 0x80
#define ISL9238_TRICKLE_CHG_CURR_MASK 0xC000
#define ISL9238_OTG_CURRENT_MASK 0x1F80
#define ISL9238_RELOAD_ACLIM_MASK BIT(14)
#define ISL9238_REREAD_PROG_MASK BIT(15)
#define ISL9238_TRICKLE_CHG_MODE BIT(4)
#define ISL9238_LOW_VSYS_PROCHOT BIT(10)
#define ISL9238_DC_PROCHOT_TRIP BIT(11)
#define ISL9238_AC_OTG_CURR_PROCHOT BIT(12)
#define ISL9238_LOW_POWER_MODE BIT(15)
#define ISL9238_BATGON_PIN_STATE BIT(12)
#define ISL9238_COMPARATOR_OUTPUT BIT(13)
#define ISL9238_ACOK_PIN_STATE BIT(14)
#define ISL9238_ACTIVE_CNTRL_LOOP_MASK 0x6000
#define ISL9238_CHG_CURRENT_LOOP 0x2000
#define ISL9238_ADAPTER_CURRENT_LOOP 0x4000
#define ISL9238_INPUT_VOLTAGE_LOOP 0x6000
#define ISL9238_CHARGER_MODE_MASK 0xE0
#define ISL9238_BOOST_MODE 0x20
#define ISL9238_BUCK_MODE 0x40
#define ISL9238_BUCK_BOOST_MODE 0x60
#define ISL9238_OTG_BOOST_MODE 0xA0
#define ISL9238_OTG_BUCK_MODE 0xC0
#define ISL9238_OTG_BUCK_BOOST_MODE 0xE0
#define ISL9238_BATTERY_STATE BIT(12)
#define ISL9238_ADAPTER_STATE BIT(14)
#define ISL9238_SMBUS_TIMER_ENABLE BIT(7)
#define ISL9238_BATTERY_OVP_ENABLE BIT(1)
#define ISL9238_TRIGGER_PROCHOT_ACOK BIT(5)
#define ISL9238_TRIGGER_OTG_CURR BIT(7)
#define ISL9238_PSYS_POWER_MONITOR BIT(3)
#define ISL9238_ADPTR_CHG_CURR_MIN 0x8
#define ISL9238_ADPTR_CHG_CURR_MAX 0x2F80
#define ISL9238_BAT_CHG_CURRENT_SMBUSK BIT(7)
#define ISL9238_DISABLE_RELOAD_ACLIM BIT(14)
#define ISL9238_DISABLE_REREAD_PROG BIT(15)
#define ISL9238_CURR_SENSE_RES_OTP 10
#define ISL9238_ADPTR_SENSE_RES_OTP 20
#define ISL9238_TEMP_H_CHG_DISABLE 60
#define ISL9238_TEMP_L_CHG_DISABLE 0
static const struct regmap_range isl9238_readable_ranges[] = {
regmap_reg_range(ISL9238_CHG_CURR_LIMIT, ISL9238_CONTROL4_OPTIONS),
regmap_reg_range(ISL9238_MANUFACTURER_ID, ISL9238_DEVICE_ID),
};
static const struct regmap_access_table isl9238_readable_table = {
.yes_ranges = isl9238_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(isl9238_readable_ranges),
};
static const struct regmap_config isl9238_rmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = ISL9238_DEVICE_ID,
.cache_type = REGCACHE_NONE,
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.rd_table = &isl9238_readable_table,
};
struct isl9238_vbus_pdata {
struct regulator_init_data *ridata;
};
struct isl9238_chg_pdata {
struct isl9238_vbus_pdata *vbus_pdata;
int num_consumer_supplies;
struct regulator_consumer_supply *consumer_supplies;
struct regulator_init_data *chg_ridata;
const char *tz_name;
int temp_poll_time;
int temp_range_len;
u32 *temp_range;
int *temp_chg_curr_lim;
u32 *max_battery_voltage;
u32 *voltage_curr_lim;
u32 charge_current_lim;
u32 max_sys_voltage;
u32 max_init_voltage;
u32 min_sys_voltage;
u32 input_voltage_limit;
u32 adapter_current_lim1;
u32 adapter_current_lim2;
u32 acprochot_threshold;
u32 dcprochot_threshold;
u32 adapter_duration_t1;
u32 adapter_duration_t2;
u32 trickle_chg_current;
u32 curr_sense_resistor;
u32 adapter_sense_resistor;
u32 otg_voltage;
u32 otg_current;
u32 terminate_chg_current;
bool disable_input_regulation;
bool enable_bat_learn_mode;
bool disable_turbo_mode;
bool enable_auto_charging;
bool disable_safety_timer;
bool enable_battery_ovp; /* over voltage protection */
bool enable_prochot_acok; /*trigger prochot with acook */
bool enable_prochot_otg_curr; /* trigger prochot with otgcurrent */
bool enable_psys_monitor; /* system power monitor PSYS */
};
struct isl9238_charger {
struct device *dev;
struct i2c_client *client;
struct regmap *rmap;
int irq;
struct mutex mutex; /* mutex for charger */
struct mutex otg_mutex; /* mutex for otg */
struct isl9238_chg_pdata *chg_pdata;
struct regulator_dev *vbus_rdev;
struct regulator_desc vbus_reg_desc;
struct regulator_init_data *vbus_ridata;
struct device_node *vbus_np;
struct regulator_init_data *chg_ridata;
struct regulator_dev *chg_rdev;
struct regulator_desc chg_reg_desc;
struct device_node *chg_np;
struct battery_charger_dev *bc_dev;
bool is_otg_connected;
bool shutdown_complete;
bool disable_sc7_during_charging;
bool wake_lock_released;
bool cable_connected;
bool thermal_chg_disable;
bool terminate_charging;
bool emulate_input_disconnected;
u32 chg_status;
u32 in_current_limit;
u32 last_adapter_current;
u32 in_voltage_limit;
u32 last_adapter_voltage;
u32 last_otg_voltage;
u32 otg_voltage_limit;
u32 curnt_sense_res;
u32 adptr_sense_res;
u32 charging_current_lim;
u32 adapter_curr_lim1;
u32 max_init_voltage;
u32 max_sys_voltage;
u32 terminate_chg_current;
int last_temp;
};
struct isl9238_charger *isl9238_bc;
static inline int isl9238_write(struct isl9238_charger *isl9238,
u8 reg, unsigned int reg_val)
{
return regmap_write(isl9238->rmap, reg, reg_val);
}
static inline int isl9238_read(struct isl9238_charger *isl9238, u8 reg)
{
unsigned int reg_val;
int ret;
ret = regmap_read(isl9238->rmap, reg, &reg_val);
if (ret < 0)
return ret;
return reg_val;
}
static int isl9238_update_bits(struct isl9238_charger *isl9238, u8 reg,
unsigned int mask, unsigned int reg_val)
{
return regmap_update_bits(isl9238->rmap, reg, mask, reg_val);
}
static int isl9238_val_to_reg(int val, int offset, int div, int nbits,
bool roundup)
{
int max_val = offset + (BIT(nbits) - 1) * div;
if (val <= offset)
return 0;
if (val >= max_val)
return BIT(nbits) - 1;
if (roundup)
return DIV_ROUND_UP(val - offset, div);
else
return (val - offset) / div;
}
#if defined(CONFIG_SYSFS)
static ssize_t charging_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
ret = isl9238_read(isl9238_bc, ISL9238_CHG_CURR_LIMIT);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(dev, "register read fail: %d\n", ret);
return ret;
}
if (ret)
return snprintf(buf, MAX_STR_PRINT, "enabled\n");
else
return snprintf(buf, MAX_STR_PRINT, "disabled\n");
}
static ssize_t charging_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int rval;
int ret;
bool enabled;
if ((*buf == 'E') || (*buf == 'e'))
enabled = true;
else if ((*buf == 'D') || (*buf == 'd'))
enabled = false;
else
return -EINVAL;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
if (enabled) {
rval = isl9238_bc->charging_current_lim *
isl9238_bc->curnt_sense_res /
ISL9238_CURR_SENSE_RES_OTP;
rval = rval & ISL9238_CHG_CURR_LIMIT_MASK;
isl9238_bc->last_temp = 0;
battery_charger_thermal_start_monitoring(isl9238_bc->bc_dev);
} else {
rval = 0;
isl9238_bc->last_temp = 0;
battery_charger_thermal_stop_monitoring(isl9238_bc->bc_dev);
}
ret = isl9238_write(isl9238_bc, ISL9238_CHG_CURR_LIMIT, rval);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "register write fail: %d\n", ret);
return ret;
}
return count;
}
static ssize_t input_cable_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
mutex_unlock(&isl9238_bc->mutex);
if (isl9238_bc->emulate_input_disconnected)
return snprintf(buf, MAX_STR_PRINT, "Disconnected\n");
else
return snprintf(buf, MAX_STR_PRINT, "Connected\n");
}
static ssize_t input_cable_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int rval;
int ret;
bool connect;
if ((*buf == 'C') || (*buf == 'c'))
connect = true;
else if ((*buf == 'D') || (*buf == 'd'))
connect = false;
else
return -EINVAL;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
if (connect) {
isl9238_bc->emulate_input_disconnected = false;
rval = isl9238_bc->adapter_curr_lim1 *
isl9238_bc->adptr_sense_res /
ISL9238_ADPTR_SENSE_RES_OTP;
rval = rval & ISL9238_ADPTR_CURR_LIMIT1_MASK;
isl9238_bc->last_temp = 0;
battery_charger_thermal_start_monitoring(isl9238_bc->bc_dev);
} else {
isl9238_bc->emulate_input_disconnected = true;
rval = 0;
isl9238_bc->last_temp = 0;
battery_charger_thermal_stop_monitoring(isl9238_bc->bc_dev);
}
ret = isl9238_write(isl9238_bc, ISL9238_ADPTR_CURR_LIMIT1, rval);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "register write fail: %d\n", ret);
return ret;
}
if (connect)
dev_info(isl9238_bc->dev,
"Emulation of charger cable disconnect disabled\n");
else
dev_info(isl9238_bc->dev,
"Emulated as charger cable disconnected\n");
return count;
}
static ssize_t charge_current_limit_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
ret = isl9238_read(isl9238_bc, ISL9238_CHG_CURR_LIMIT);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "charge curnt read fail:%d", ret);
return ret;
}
if (isl9238_bc->curnt_sense_res)
ret = ret * ISL9238_CURR_SENSE_RES_OTP /
isl9238_bc->curnt_sense_res;
return snprintf(buf, MAX_STR_PRINT, "%u mA\n", ret);
}
static ssize_t charge_current_limit_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int curr_val, rval, ret;
if (kstrtouint(buf, 0, &curr_val)) {
dev_err(isl9238_bc->dev, "current limit read fail\n");
return -EINVAL;
}
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
if (isl9238_bc->curnt_sense_res)
curr_val = curr_val * isl9238_bc->curnt_sense_res /
ISL9238_CURR_SENSE_RES_OTP;
rval = curr_val & ISL9238_CHG_CURR_LIMIT_MASK;
ret = isl9238_write(isl9238_bc, ISL9238_CHG_CURR_LIMIT, rval);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "charge curnt write fail:%d\n", ret);
return ret;
}
return count;
}
static ssize_t charge_current_range_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret = 0;
ret = snprintf(buf + strlen(buf), MAX_STR_PRINT, "%d mA to %d mA\n",
ISL9238_ADPTR_CHG_CURR_MIN, ISL9238_ADPTR_CHG_CURR_MAX);
return ret;
}
static ssize_t adapter_current_limit_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
ret = isl9238_read(isl9238_bc, ISL9238_ADPTR_CURR_LIMIT1);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "adptr curnt1 read fail:%d\n", ret);
return ret;
}
if (isl9238_bc->adptr_sense_res)
ret = ret * ISL9238_ADPTR_SENSE_RES_OTP /
isl9238_bc->adptr_sense_res;
return snprintf(buf, MAX_STR_PRINT, "%d mA\n", ret);
}
static ssize_t adapter_current_limit_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int curnt_val, rval, ret;
if (kstrtouint(buf, 0, &curnt_val)) {
dev_err(isl9238_bc->dev, "current limit read fail\n");
return -EINVAL;
}
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
if (isl9238_bc->adptr_sense_res)
curnt_val = curnt_val * isl9238_bc->adptr_sense_res /
ISL9238_ADPTR_SENSE_RES_OTP;
rval = curnt_val & ISL9238_ADPTR_CURR_LIMIT1_MASK;
ret = isl9238_write(isl9238_bc, ISL9238_ADPTR_CURR_LIMIT1, rval);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "adptr curnt1 write fail:%d\n", ret);
return ret;
}
return count;
}
static ssize_t max_system_voltage_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
ret = isl9238_read(isl9238_bc, ISL9238_MAX_SYS_VOLTAGE);
mutex_unlock(&isl9238_bc->mutex);
if (ret < 0) {
dev_err(isl9238_bc->dev, "system voltage read fail:%d\n", ret);
return ret;
}
ret = ret & ISL9238_MAX_SYS_VOLTAGE_MASK;
return snprintf(buf, MAX_STR_PRINT, "%d mV\n", ret);
}
static ssize_t adapter_current_range_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret = 0;
ret = snprintf(buf + strlen(buf), MAX_STR_PRINT, "%d mA to %d mA\n",
ISL9238_ADPTR_CHG_CURR_MIN,
ISL9238_ADPTR_CHG_CURR_MAX);
return ret;
}
static ssize_t operating_modes_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int rval;
int ret = 0;
mutex_lock(&isl9238_bc->mutex);
if (isl9238_bc->shutdown_complete) {
mutex_unlock(&isl9238_bc->mutex);
return -EIO;
}
rval = isl9238_read(isl9238_bc, ISL9238_INFO1_CHG_STATUS);
if (rval < 0) {
dev_err(isl9238_bc->dev, "info1 reg read fail: %d\n", rval);
mutex_unlock(&isl9238_bc->mutex);
return rval;
}
if (rval & ISL9238_TRICKLE_CHG_MODE)
ret = snprintf(buf, MAX_STR_PRINT,
"trickle charging mode is active\n");
else
ret = snprintf(buf, MAX_STR_PRINT,
"trickle charging mode is not active\n");
if (rval & ISL9238_LOW_VSYS_PROCHOT)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"low_vsys prochot is tripped\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"low_vsys prochot is not tripped\n");
if (rval & ISL9238_DC_PROCHOT_TRIP)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"dcprochot is tripped\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"dcprochot is not tripped\n");
if (rval & ISL9238_AC_OTG_CURR_PROCHOT)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"acprochot/otgcurrentprochot is tripped\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"acprochot/otgcurrentprochot is not tripped\n");
switch (rval & ISL9238_ACTIVE_CNTRL_LOOP_MASK) {
case ISL9238_CHG_CURRENT_LOOP:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"charging current loop is active\n");
break;
case ISL9238_ADAPTER_CURRENT_LOOP:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"adapter current loop is active\n");
break;
case ISL9238_INPUT_VOLTAGE_LOOP:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"input voltage loop is active\n");
break;
default:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"max system voltage control loop is active\n");
break;
}
if (rval & ISL9238_LOW_POWER_MODE)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"internal reference circuit is active\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"internal reference circuit is not active\n");
rval = isl9238_read(isl9238_bc, ISL9238_INFO2_CHG_STATUS);
if (rval < 0) {
dev_err(isl9238_bc->dev, "info2 reg read fail: %d\n", rval);
mutex_unlock(&isl9238_bc->mutex);
return rval;
}
switch (rval & ISL9238_CHARGER_MODE_MASK) {
case ISL9238_BOOST_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"boost mode is active\n");
break;
case ISL9238_BUCK_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"buck mode is active\n");
break;
case ISL9238_BUCK_BOOST_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"buck boost mode is active\n");
break;
case ISL9238_OTG_BOOST_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"otg boost mode is active\n");
break;
case ISL9238_OTG_BUCK_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"otg buck mode is active\n");
break;
case ISL9238_OTG_BUCK_BOOST_MODE:
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"otg buck boost mode is active\n");
break;
default:
break;
}
if (rval & ISL9238_BATGON_PIN_STATE)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"no battery\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"battery is present\n");
if (rval & ISL9238_COMPARATOR_OUTPUT)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"comparator output is high\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"comparator output is low\n");
if (rval & ISL9238_ACOK_PIN_STATE)
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"adapter is present\n");
else
ret += snprintf(buf + strlen(buf), MAX_STR_PRINT,
"no adapter\n");
mutex_unlock(&isl9238_bc->mutex);
return ret;
}
static DEVICE_ATTR_RW(charging_state);
static DEVICE_ATTR_RW(input_cable_state);
static DEVICE_ATTR_RW(charge_current_limit);
static DEVICE_ATTR_RO(charge_current_range);
static DEVICE_ATTR_RW(adapter_current_limit);
static DEVICE_ATTR_RO(adapter_current_range);
static DEVICE_ATTR_RO(max_system_voltage);
static DEVICE_ATTR_RO(operating_modes);
static struct attribute *isl9238_attributes[] = {
&dev_attr_charging_state.attr,
&dev_attr_input_cable_state.attr,
&dev_attr_charge_current_limit.attr,
&dev_attr_charge_current_range.attr,
&dev_attr_adapter_current_limit.attr,
&dev_attr_adapter_current_range.attr,
&dev_attr_max_system_voltage.attr,
&dev_attr_operating_modes.attr,
NULL,
};
static const struct attribute_group isl9238_attr_group = {
.attrs = isl9238_attributes,
};
static void isl9238_sysfs_init(struct device *dev)
{
int err;
static struct kobject *bc_kobj;
bc_kobj = kobject_create_and_add("battery_charger", kernel_kobj);
if (!bc_kobj) {
dev_err(dev, "/sys/kernel/battery_charger sysfs create failed\n");
return;
}
err = sysfs_create_group(bc_kobj, &isl9238_attr_group);
if (err)
dev_err(dev, "failed to create isl9238 sysfs entries\n");
}
#else
static void tisl9238_sysfs_init(struct device *dev)
{
}
#endif
static int isl9238_otg_enable(struct regulator_dev *rdev)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int ret;
mutex_lock(&isl9238->otg_mutex);
isl9238->is_otg_connected = true;
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL1_OPTIONS,
ISL9238_OTG_MASK, ISL9238_OTG_ENABLE);
if (ret < 0) {
dev_err(isl9238->dev, "OTG enable failed %d", ret);
mutex_unlock(&isl9238->otg_mutex);
return ret;
}
mutex_unlock(&isl9238->otg_mutex);
dev_info(isl9238->dev, "OTG enabled\n");
return ret;
}
static int isl9238_otg_disable(struct regulator_dev *rdev)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int ret;
mutex_lock(&isl9238->otg_mutex);
isl9238->is_otg_connected = false;
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL1_OPTIONS,
ISL9238_OTG_MASK, ISL9238_OTG_DISABLE);
if (ret < 0) {
dev_err(isl9238->dev, "OTG disable failed %d", ret);
mutex_unlock(&isl9238->otg_mutex);
return ret;
}
mutex_unlock(&isl9238->otg_mutex);
dev_info(isl9238->dev, "OTG disabled\n");
return ret;
}
static int isl9238_otg_is_enabled(struct regulator_dev *rdev)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int ret;
ret = isl9238_read(isl9238, ISL9238_CONTROL1_OPTIONS);
if (ret < 0) {
dev_err(isl9238->dev, "CONTROL1 read failed %d", ret);
return ret;
}
return (ret & ISL9238_OTG_MASK) == ISL9238_OTG_ENABLE;
}
static int isl9238_otg_set_voltage(struct regulator_dev *rdev,
int min_uv, int max_uv,
unsigned int *selector)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int otg_voltage_limit;
int reg_val;
int ret = -EINVAL;
if (!isl9238->is_otg_connected) {
dev_info(isl9238->dev, "otg not enabled\n");
return ret;
}
if (max_uv == 0)
return 0;
dev_info(isl9238->dev, "setting otg voltage %d\n", max_uv / 1000);
isl9238->last_otg_voltage = max_uv;
otg_voltage_limit = max_uv / 1000;
reg_val = isl9238_val_to_reg(otg_voltage_limit, 0, 12, 12, 0);
ret = isl9238_write(isl9238, ISL9238_OTG_VOLTAGE, reg_val << 3);
if (ret < 0) {
dev_err(isl9238->dev, "otg voltage update failed %d\n", ret);
return ret;
}
isl9238->otg_voltage_limit = otg_voltage_limit;
return ret;
}
static int isl9238_otg_set_curr_limit(struct regulator_dev *rdev,
int min_ua, int max_ua)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int ret = 0;
int otg_current_lim, val;
if (!isl9238->is_otg_connected) {
dev_info(isl9238->dev, "otg not enabled\n");
return -EINVAL;
}
if (max_ua == 0)
return 0;
dev_info(isl9238->dev, "setting otg current %d\n", max_ua / 1000);
otg_current_lim = max_ua / 1000;
if (isl9238->curnt_sense_res)
otg_current_lim = otg_current_lim * isl9238->curnt_sense_res /
ISL9238_CURR_SENSE_RES_OTP;
val = otg_current_lim & ISL9238_OTG_CURRENT_MASK;
ret = isl9238_write(isl9238, ISL9238_OTG_CURRENT, val);
if (ret < 0)
dev_err(isl9238->dev, "otg current write fail:%d\n", ret);
return ret;
}
static struct regulator_ops isl9238_otg_ops = {
.enable = isl9238_otg_enable,
.disable = isl9238_otg_disable,
.is_enabled = isl9238_otg_is_enabled,
.set_voltage = isl9238_otg_set_voltage,
.set_current_limit = isl9238_otg_set_curr_limit,
};
static struct regulator_desc isl9238_otg_reg_desc = {
.name = "isl9238-vbus",
.ops = &isl9238_otg_ops,
.type = REGULATOR_VOLTAGE,
.enable_time = 220000,
.owner = THIS_MODULE,
};
static int isl9238_init_vbus_regulator(struct isl9238_charger *isl9238,
struct isl9238_chg_pdata *pdata)
{
int ret = 0;
struct regulator_config rconfig = { };
if (!pdata->vbus_pdata) {
dev_err(isl9238->dev, "No vbus platform data\n");
return 0;
}
isl9238->vbus_reg_desc = isl9238_otg_reg_desc;
isl9238->vbus_ridata = pdata->vbus_pdata->ridata;
isl9238->vbus_ridata->constraints.valid_modes_mask =
REGULATOR_MODE_NORMAL |
REGULATOR_MODE_STANDBY;
isl9238->vbus_ridata->constraints.valid_ops_mask =
REGULATOR_CHANGE_MODE |
REGULATOR_CHANGE_STATUS |
REGULATOR_CHANGE_VOLTAGE |
REGULATOR_CHANGE_CURRENT;
/* Register vbus regulator */
rconfig.dev = isl9238->dev;
rconfig.of_node = isl9238->vbus_np;
rconfig.init_data = isl9238->vbus_ridata;
rconfig.driver_data = isl9238;
isl9238->vbus_rdev = devm_regulator_register(isl9238->dev,
&isl9238->vbus_reg_desc, &rconfig);
if (IS_ERR(isl9238->vbus_rdev)) {
ret = PTR_ERR(isl9238->vbus_rdev);
dev_err(isl9238->dev,
"VBUS regulator register failed %d\n", ret);
return ret;
}
return 0;
}
static int isl9238_set_charging_voltage(struct regulator_dev *rdev,
int min_uv, int max_uv,
unsigned int *selector)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
int in_voltage_limit;
int reg_val;
int ret = 0;
if (isl9238->is_otg_connected)
return -EINVAL;
dev_info(isl9238->dev, "setting adapter voltage %d\n", max_uv / 1000);
reg_val = isl9238->max_init_voltage & ISL9238_MAX_SYS_VOLTAGE_MASK;
ret = isl9238_write(isl9238, ISL9238_MAX_SYS_VOLTAGE, reg_val);
if (ret < 0) {
dev_err(isl9238->dev, "max sys voltage write fail:%d\n", ret);
return ret;
}
if (max_uv == 0)
return 0;
isl9238->last_adapter_voltage = max_uv;
in_voltage_limit = max_uv / 1000;
if (isl9238->wake_lock_released)
in_voltage_limit = 5000;
reg_val = isl9238_val_to_reg(in_voltage_limit, 0, 341, 6, 0);
ret = isl9238_write(isl9238, ISL9238_INPUT_VOLTAGE, reg_val << 8);
if (ret < 0) {
dev_err(isl9238->dev, "set input voltage failed %d\n", ret);
return ret;
}
isl9238->in_voltage_limit = in_voltage_limit;
return 0;
}
static int isl9238_set_charging_current(struct regulator_dev *rdev,
int min_ua, int max_ua)
{
struct isl9238_charger *isl9238 = rdev_get_drvdata(rdev);
unsigned int chg_current_limit, adapter_curr_limit = 0;
int in_current_limit;
int old_current_limit;
int ret = 0;
int val;
if (isl9238->is_otg_connected)
return -EINVAL;
if (max_ua == 0) {
/* disable charging by setting charging current to 0mA */
ret = isl9238_write(isl9238, ISL9238_CHG_CURR_LIMIT, 0);
if (ret < 0) {
dev_err(isl9238->dev,
"charge current update failed %d\n", ret);
return ret;
}
isl9238->wake_lock_released = false;
isl9238->chg_status = BATTERY_DISCHARGING;
isl9238->cable_connected = 0;
dev_info(isl9238->dev, "stop thermal monitor\n");
isl9238->last_temp = 0;
battery_charger_thermal_stop_monitoring(isl9238->bc_dev);
dev_info(isl9238->dev, "charging disabled\n");
battery_charging_status_update(isl9238->bc_dev,
isl9238->chg_status);
battery_charger_release_wake_lock(isl9238->bc_dev);
/* set default adapter current limit */
adapter_curr_limit = isl9238->adapter_curr_lim1;
dev_info(isl9238->dev, "set default adapter current:%d\n",
adapter_curr_limit);
if (isl9238->adptr_sense_res)
adapter_curr_limit = adapter_curr_limit *
isl9238->adptr_sense_res / ISL9238_ADPTR_SENSE_RES_OTP;
val = adapter_curr_limit & ISL9238_ADPTR_CURR_LIMIT1_MASK;
ret = isl9238_write(isl9238, ISL9238_ADPTR_CURR_LIMIT1, val);
if (ret < 0)
dev_err(isl9238->dev, "set adptr curr fail:%d\n", ret);
return ret;
}
dev_info(isl9238->dev, "setting adapter current:%d\n", max_ua / 1000);
/* enable charging by setting default charging current */
if (isl9238->charging_current_lim) {
chg_current_limit = isl9238->charging_current_lim;
if (isl9238->curnt_sense_res)
chg_current_limit = chg_current_limit *
isl9238->curnt_sense_res /
ISL9238_CURR_SENSE_RES_OTP;
val = chg_current_limit & ISL9238_CHG_CURR_LIMIT_MASK;
ret = isl9238_write(isl9238, ISL9238_CHG_CURR_LIMIT, val);
if (ret < 0) {
dev_err(isl9238->dev,
"charging current write fail:%d\n", ret);
return ret;
}
dev_info(isl9238->dev, "charging enabled\n");
}
old_current_limit = isl9238->in_current_limit;
isl9238->last_adapter_current = max_ua;
in_current_limit = max_ua / 1000;
isl9238->cable_connected = 1;
isl9238->chg_status = BATTERY_CHARGING;
if (isl9238->wake_lock_released)
in_current_limit = 500;
/*
* disable auto charge to set adapter current through smbus
* disable reload adapter current limit on usb plugin
* disable reread prog pin resistor
*/
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL3_OPTIONS,
ISL9238_AUTO_CHARGE_MODE_MASK |
ISL9238_RELOAD_ACLIM_MASK |
ISL9238_REREAD_PROG_MASK,
ISL9238_BAT_CHG_CURRENT_SMBUSK |
ISL9238_DISABLE_RELOAD_ACLIM |
ISL9238_DISABLE_REREAD_PROG);
if (ret < 0) {
dev_err(isl9238->dev, "control3 reg write fail:%d\n", ret);
return ret;
}
if (isl9238->adptr_sense_res)
in_current_limit = in_current_limit *
isl9238->adptr_sense_res / ISL9238_ADPTR_SENSE_RES_OTP;
val = in_current_limit & ISL9238_ADPTR_CURR_LIMIT1_MASK;
ret = isl9238_write(isl9238, ISL9238_ADPTR_CURR_LIMIT1, val);
if (ret < 0) {
dev_err(isl9238->dev, "set adapter current failed %d\n", ret);
return ret;
}
isl9238->in_current_limit = in_current_limit;
battery_charging_status_update(isl9238->bc_dev, isl9238->chg_status);
dev_info(isl9238->dev, "start thermal monitor\n");
isl9238->last_temp = 0;
battery_charger_thermal_start_monitoring(isl9238->bc_dev);
battery_charger_acquire_wake_lock(isl9238->bc_dev);
return 0;
}
static struct regulator_ops isl9238_regulator_ops = {
.set_current_limit = isl9238_set_charging_current,
.set_voltage = isl9238_set_charging_voltage,
};
static struct regulator_desc isl9238_chg_reg_desc = {
.name = "isl9238-charger",
.ops = &isl9238_regulator_ops,
.type = REGULATOR_CURRENT,
.owner = THIS_MODULE,
};
static int isl9238_init_charger_regulator(struct isl9238_charger *isl9238,
struct isl9238_chg_pdata *pdata)
{
struct regulator_config rconfig = { };
int ret = 0;
isl9238->chg_reg_desc = isl9238_chg_reg_desc;
isl9238->chg_ridata = pdata->chg_ridata;
isl9238->chg_ridata->driver_data = isl9238;
isl9238->chg_ridata->constraints.valid_modes_mask =
REGULATOR_MODE_NORMAL |
REGULATOR_MODE_STANDBY;
isl9238->chg_ridata->constraints.valid_ops_mask =
REGULATOR_CHANGE_MODE |
REGULATOR_CHANGE_STATUS |
REGULATOR_CHANGE_CURRENT |
REGULATOR_CHANGE_VOLTAGE;
rconfig.dev = isl9238->dev;
rconfig.of_node = isl9238->chg_np;
rconfig.init_data = isl9238->chg_ridata;
rconfig.driver_data = isl9238;
isl9238->chg_rdev = devm_regulator_register(isl9238->dev,
&isl9238->chg_reg_desc, &rconfig);
if (IS_ERR(isl9238->chg_rdev)) {
ret = PTR_ERR(isl9238->chg_rdev);
dev_err(isl9238->dev, "charger reg register fail %d\n", ret);
}
return ret;
}
static int isl9238_show_chip_version(struct isl9238_charger *isl9238)
{
int ret;
ret = isl9238_read(isl9238, ISL9238_MANUFACTURER_ID);
if (ret < 0) {
dev_err(isl9238->dev, "MFR id read failed: %d\n", ret);
return ret;
}
dev_info(isl9238->dev, "ISL9238 Manufacture OTP id:0x%02X\n", ret);
ret = isl9238_read(isl9238, ISL9238_DEVICE_ID);
if (ret < 0) {
dev_err(isl9238->dev, "device id read failed: %d\n", ret);
return ret;
}
dev_info(isl9238->dev, "ISL9238 Device OTP id:0x%02X\n", ret);
return 0;
}
static void isl9238_charger_get_op_modes(struct isl9238_charger *isl9238)
{
int ret;
ret = isl9238_read(isl9238, ISL9238_INFO1_CHG_STATUS);
if (ret < 0) {
dev_err(isl9238->dev, "info1 reg read failed: %d\n", ret);
return;
}
dev_info(isl9238->dev, "info1:charger operating modes 0x%02x\n", ret);
if (ret & ISL9238_TRICKLE_CHG_MODE)
dev_info(isl9238->dev, "trickle charging mode is active\n");
switch (ret & ISL9238_ACTIVE_CNTRL_LOOP_MASK) {
case ISL9238_CHG_CURRENT_LOOP:
dev_info(isl9238->dev, "charging current loop is active\n");
break;
case ISL9238_ADAPTER_CURRENT_LOOP:
dev_info(isl9238->dev, "adapter current loop is active\n");
break;
case ISL9238_INPUT_VOLTAGE_LOOP:
dev_info(isl9238->dev, "input voltage loop is active\n");
break;
default:
dev_info(isl9238->dev,
"max system voltage control loop is active\n");
break;
}
ret = isl9238_read(isl9238, ISL9238_INFO2_CHG_STATUS);
if (ret < 0) {
dev_err(isl9238->dev, "info2 reg read failed: %d\n", ret);
return;
}
dev_info(isl9238->dev, "info2:charger operating modes 0x%02x\n", ret);
switch (ret & ISL9238_CHARGER_MODE_MASK) {
case ISL9238_BOOST_MODE:
dev_info(isl9238->dev, "boost mode is active\n");
break;
case ISL9238_BUCK_MODE:
dev_info(isl9238->dev, "buck mode is active\n");
break;
case ISL9238_BUCK_BOOST_MODE:
dev_info(isl9238->dev, "buck boost mode is active\n");
break;
case ISL9238_OTG_BOOST_MODE:
dev_info(isl9238->dev, "otg boost mode is active\n");
break;
case ISL9238_OTG_BUCK_MODE:
dev_info(isl9238->dev, "otg buck mode is active\n");
break;
case ISL9238_OTG_BUCK_BOOST_MODE:
dev_info(isl9238->dev, "otg buck boost mode is active\n");
break;
default:
break;
}
if (ret & ISL9238_ADAPTER_STATE)
dev_info(isl9238->dev, "adapter is connected\n");
if (ret & ISL9238_BATTERY_STATE)
dev_info(isl9238->dev, "no battery present\n");
}
static int isl9238_safetytimer_disable(struct isl9238_charger *isl9238)
{
int ret;
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL0_OPTIONS,
ISL9238_SMBUS_TIMER_MASK,
ISL9238_SMBUS_TIMER_ENABLE);
if (ret < 0)
dev_err(isl9238->dev, "disable safety timer fail:%d\n", ret);
else
dev_info(isl9238->dev, "Charging SAFETY timer disabled\n");
return ret;
}
static irqreturn_t isl9238_irq(int id, void *dev)
{
struct isl9238_charger *isl9238 = dev;
mutex_lock(&isl9238->mutex);
if (isl9238->shutdown_complete)
goto out;
isl9238_charger_get_op_modes(isl9238);
out:
mutex_unlock(&isl9238->mutex);
return IRQ_HANDLED;
}
static int isl9238_charger_thermal_configure(
struct battery_charger_dev *bc_dev,
int temp, bool enable_charger, bool enable_charg_half_current,
int battery_voltage)
{
struct isl9238_charger *isl9238 = battery_charger_get_drvdata(bc_dev);
struct isl9238_chg_pdata *chg_pdata;
unsigned int val;
int temp_chg_curr_lim = 0, volt_chg_curr_lim = 0;
int bat_voltage, bat_current, max_system_voltage = 0;
int charging_state = 0;
int ret, i;
if (isl9238->shutdown_complete)
return 0;
chg_pdata = isl9238->chg_pdata;
if (!isl9238->cable_connected || !chg_pdata->temp_range_len)
return 0;
dev_info(isl9238->dev, "battery temp %d\n", temp);
charging_state = battery_gauge_get_charging_status(isl9238->bc_dev);
if (charging_state == POWER_SUPPLY_STATUS_FULL) {
dev_info(isl9238->dev, "charging done:stop thermal monitor\n");
battery_charger_thermal_stop_monitoring(isl9238->bc_dev);
battery_charger_release_wake_lock(isl9238->bc_dev);
return 0;
}
if ((isl9238->last_temp == temp) && !isl9238->terminate_charging)
return 0;
isl9238->last_temp = temp;
isl9238->terminate_charging = false;
bat_current = battery_gauge_get_charging_current(isl9238->bc_dev);
if (bat_current <= isl9238->terminate_chg_current)
isl9238->terminate_charging = true;
if ((temp >= ISL9238_TEMP_H_CHG_DISABLE ||
temp <= ISL9238_TEMP_L_CHG_DISABLE) ||
isl9238->terminate_charging) {
if (isl9238->thermal_chg_disable)
return 0;
/* set chg current to 0 to disable charging */
ret = isl9238_write(isl9238, ISL9238_CHG_CURR_LIMIT, 0x0);
if (ret < 0) {
dev_err(isl9238->dev,
"charge current write fail:%d\n", ret);
return ret;
}
if (isl9238->terminate_charging)
dev_info(isl9238->dev,
"bat current:%d below terminate current:%d\n",
bat_current, isl9238->terminate_chg_current);
dev_info(isl9238->dev, "thermal: charging disabled\n");
isl9238->thermal_chg_disable = true;
return 0;
}
for (i = 0; i < chg_pdata->temp_range_len; ++i) {
if (temp <= chg_pdata->temp_range[i]) {
temp_chg_curr_lim = chg_pdata->temp_chg_curr_lim[i];
volt_chg_curr_lim = chg_pdata->voltage_curr_lim[i];
max_system_voltage = chg_pdata->max_battery_voltage[i];
if (isl9238->thermal_chg_disable) {
dev_info(isl9238->dev,
"thermal: charging enabled\n");
isl9238->thermal_chg_disable = false;
}
break;
}
}
if (temp_chg_curr_lim != volt_chg_curr_lim) {
bat_voltage =
battery_gauge_get_battery_voltage(isl9238->bc_dev);
dev_info(isl9238->dev, "battery voltage:%d mV\n", bat_voltage);
dev_info(isl9238->dev, "battery current:%d mA\n", bat_current);
if (bat_current <= volt_chg_curr_lim ||
bat_voltage >= max_system_voltage) {
temp_chg_curr_lim = volt_chg_curr_lim;
max_system_voltage = isl9238->max_sys_voltage;
} else {
max_system_voltage = isl9238->max_init_voltage;
}
}
dev_info(isl9238->dev, "thermal:set max system voltage:%d mV\n",
max_system_voltage);
val = max_system_voltage & ISL9238_MAX_SYS_VOLTAGE_MASK;
ret = isl9238_write(isl9238, ISL9238_MAX_SYS_VOLTAGE, val);
if (ret < 0) {
dev_err(isl9238->dev, "reg write fail:%d\n", ret);
return ret;
}
dev_info(isl9238->dev, "thermal:set charging current:%d mA\n",
temp_chg_curr_lim);
if (isl9238->curnt_sense_res)
temp_chg_curr_lim = temp_chg_curr_lim *
isl9238->curnt_sense_res / ISL9238_CURR_SENSE_RES_OTP;
val = temp_chg_curr_lim & ISL9238_CHG_CURR_LIMIT_MASK;
ret = isl9238_write(isl9238, ISL9238_CHG_CURR_LIMIT, val);
if (ret < 0)
dev_err(isl9238->dev, "charge current write fail:%d\n", ret);
return ret;
}
static int isl9238_report_charging_state(struct battery_charger_dev *bc_dev)
{
struct isl9238_charger *isl9238 = battery_charger_get_drvdata(bc_dev);
return isl9238->chg_status;
}
static struct battery_charging_ops isl9238_charger_bci_ops = {
.get_charging_status = isl9238_report_charging_state,
.thermal_configure = isl9238_charger_thermal_configure,
};
static struct battery_charger_info isl9238_charger_bci = {
.cell_id = 0,
.bc_ops = &isl9238_charger_bci_ops,
};
static int isl9238_charger_init(struct isl9238_charger *isl9238)
{
unsigned int val;
unsigned int chg_curr_lim;
unsigned int curr_sense_res;
unsigned int adptr_sense_res;
unsigned int trickle_chg_curr;
unsigned int adptr_curr_lim1;
unsigned int adptr_curr_lim2;
unsigned int otg_current_lim;
int ret;
chg_curr_lim = isl9238->chg_pdata->charge_current_lim;
curr_sense_res = isl9238->chg_pdata->curr_sense_resistor;
adptr_sense_res = isl9238->chg_pdata->adapter_sense_resistor;
adptr_curr_lim1 = isl9238->chg_pdata->adapter_current_lim1;
adptr_curr_lim2 = isl9238->chg_pdata->adapter_current_lim2;
trickle_chg_curr = isl9238->chg_pdata->trickle_chg_current;
otg_current_lim = isl9238->chg_pdata->otg_current;
if (isl9238->chg_pdata->enable_auto_charging) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL3_OPTIONS,
ISL9238_AUTO_CHARGE_MODE_MASK, 0x0);
if (ret < 0) {
dev_err(isl9238->dev,
"auto charge mode enable failed %d\n", ret);
return ret;
}
} else {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL3_OPTIONS,
ISL9238_AUTO_CHARGE_MODE_MASK |
ISL9238_RELOAD_ACLIM_MASK |
ISL9238_REREAD_PROG_MASK,
ISL9238_BAT_CHG_CURRENT_SMBUSK |
ISL9238_DISABLE_RELOAD_ACLIM |
ISL9238_DISABLE_REREAD_PROG);
if (ret < 0) {
dev_err(isl9238->dev,
"control3 reg write fail:%d\n", ret);
return ret;
}
}
if (chg_curr_lim) {
if (curr_sense_res)
chg_curr_lim = chg_curr_lim *
curr_sense_res / ISL9238_CURR_SENSE_RES_OTP;
val = chg_curr_lim & ISL9238_CHG_CURR_LIMIT_MASK;
ret = isl9238_write(isl9238, ISL9238_CHG_CURR_LIMIT, val);
if (ret < 0) {
dev_err(isl9238->dev,
"charge current update failed %d\n", ret);
return ret;
}
}
if (isl9238->max_init_voltage) {
val = isl9238->max_init_voltage & ISL9238_MAX_SYS_VOLTAGE_MASK;
ret = isl9238_write(isl9238, ISL9238_MAX_SYS_VOLTAGE, val);
if (ret < 0) {
dev_err(isl9238->dev,
"max system voltage update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->min_sys_voltage) {
val = isl9238->chg_pdata->min_sys_voltage &
ISL9238_MIN_SYS_VOLTAGE_MASK;
ret = isl9238_write(isl9238, ISL9238_MIN_SYS_VOLTAGE, val);
if (ret < 0) {
dev_err(isl9238->dev,
"min system voltage update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->input_voltage_limit) {
val = isl9238_val_to_reg(
isl9238->chg_pdata->input_voltage_limit, 0, 341, 6, 0);
ret = isl9238_write(isl9238, ISL9238_INPUT_VOLTAGE, val << 8);
if (ret < 0) {
dev_err(isl9238->dev,
"input voltage update failed %d\n", ret);
return ret;
}
}
if (adptr_curr_lim1) {
if (adptr_sense_res)
adptr_curr_lim1 = adptr_curr_lim1 *
adptr_sense_res / ISL9238_ADPTR_SENSE_RES_OTP;
val = adptr_curr_lim1 & ISL9238_ADPTR_CURR_LIMIT1_MASK;
ret = isl9238_write(isl9238, ISL9238_ADPTR_CURR_LIMIT1, val);
if (ret < 0) {
dev_err(isl9238->dev,
"adapter current_1 update failed %d\n", ret);
return ret;
}
}
if (adptr_curr_lim2) {
if (adptr_sense_res)
adptr_curr_lim2 = adptr_curr_lim2 *
adptr_sense_res / ISL9238_ADPTR_SENSE_RES_OTP;
val = adptr_curr_lim2 & ISL9238_ADPTR_CURR_LIMIT2_MASK;
ret = isl9238_write(isl9238, ISL9238_ADPTR_CURR_LIMIT2, val);
if (ret < 0) {
dev_err(isl9238->dev,
"adapter current_2 update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->acprochot_threshold) {
val = isl9238->chg_pdata->acprochot_threshold &
ISL9238_AC_PROCHOT_MASK;
ret = isl9238_write(isl9238, ISL9238_AC_PROCHOT, val);
if (ret < 0) {
dev_err(isl9238->dev,
"ac Prochot update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->dcprochot_threshold) {
val = isl9238->chg_pdata->dcprochot_threshold &
ISL9238_DC_PROCHOT_MASK;
ret = isl9238_write(isl9238, ISL9238_DC_PROCHOT, val);
if (ret < 0) {
dev_err(isl9238->dev,
"dc Prochot update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->disable_input_regulation) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL0_OPTIONS,
ISL9238_REGULATION_MASK, 0x0);
if (ret < 0) {
dev_err(isl9238->dev,
"regulation disable failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->otg_voltage) {
val = isl9238_val_to_reg(isl9238->chg_pdata->otg_voltage,
0, 12, 12, 0);
ret = isl9238_write(isl9238, ISL9238_OTG_VOLTAGE, val << 3);
if (ret < 0) {
dev_err(isl9238->dev,
"otg voltage update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->otg_current) {
if (curr_sense_res)
otg_current_lim = otg_current_lim * curr_sense_res /
ISL9238_CURR_SENSE_RES_OTP;
val = otg_current_lim & ISL9238_OTG_CURRENT_MASK;
ret = isl9238_write(isl9238, ISL9238_OTG_CURRENT, val);
if (ret < 0) {
dev_err(isl9238->dev,
"otg current update failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->disable_safety_timer) {
ret = isl9238_safetytimer_disable(isl9238);
if (ret < 0)
return ret;
}
if (isl9238->chg_pdata->enable_bat_learn_mode) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL1_OPTIONS,
ISL9238_BAT_LEARN_MASK,
ISL9238_BAT_LEARN_MASK);
if (ret < 0) {
dev_err(isl9238->dev,
"bat learn mode enable failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->disable_turbo_mode) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL1_OPTIONS,
ISL9238_TURBO_MODE_MASK,
ISL9238_TURBO_MODE_DISABLE);
if (ret < 0) {
dev_err(isl9238->dev,
"Turbo mode disable failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->enable_battery_ovp) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL2_OPTIONS,
ISL9238_BATTERY_OVP_MASK,
ISL9238_BATTERY_OVP_ENABLE);
if (ret < 0) {
dev_err(isl9238->dev,
"battery ovp enable failed %d\n", ret);
return ret;
}
}
if (trickle_chg_curr) {
if (trickle_chg_curr == 128)
val = 0x4000;
else if (trickle_chg_curr == 64)
val = 0x8000;
else if (trickle_chg_curr == 512)
val = 0xC000;
else
val = 0x0;
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL2_OPTIONS,
ISL9238_TRICKLE_CHG_CURR_MASK, val);
if (ret < 0) {
dev_err(isl9238->dev,
"trickle chg current config failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->enable_prochot_acok) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL4_OPTIONS,
ISL9238_TRIGGER_PROCHOT_ACOK,
ISL9238_TRIGGER_PROCHOT_ACOK);
if (ret < 0) {
dev_err(isl9238->dev,
"acok trigger enable failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->enable_prochot_otg_curr) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL4_OPTIONS,
ISL9238_TRIGGER_OTG_CURR,
ISL9238_TRIGGER_OTG_CURR);
if (ret < 0) {
dev_err(isl9238->dev,
"otg prochot trigger failed %d\n", ret);
return ret;
}
}
if (isl9238->chg_pdata->enable_psys_monitor) {
ret = isl9238_update_bits(isl9238, ISL9238_CONTROL1_OPTIONS,
ISL9238_PSYS_POWER_MONITOR,
ISL9238_PSYS_POWER_MONITOR);
if (ret < 0) {
dev_err(isl9238->dev,
"psys monitor enable failed %d\n", ret);
return ret;
}
}
return 0;
}
static struct isl9238_chg_pdata *isl9238_parse_dt_data(
struct i2c_client *client, struct device_node **vbus_np,
struct device_node **chg_np)
{
struct device_node *np = client->dev.of_node;
struct device_node *vbus_reg_node = NULL;
struct device_node *chg_reg_node = NULL;
struct isl9238_chg_pdata *pdata;
u32 pval;
int ret;
int count, temp_range_len = 0, curr_volt_table_len = 0;
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
chg_reg_node = of_find_node_by_name(np, "charger");
if (!chg_reg_node || !of_device_is_available(chg_reg_node)) {
dev_info(&client->dev, "charger dt status disabled\n");
return ERR_PTR(-EINVAL);
}
pdata->chg_ridata = of_get_regulator_init_data(&client->dev,
chg_reg_node, &isl9238_chg_reg_desc);
if (!pdata->chg_ridata)
return ERR_PTR(-EINVAL);
ret = of_property_read_u32(np, "isl,charge-current-limit-ma", &pval);
if (!ret)
pdata->charge_current_lim = pval;
ret = of_property_read_u32(np, "isl,max-system-voltage-mv", &pval);
if (!ret)
pdata->max_sys_voltage = pval;
ret = of_property_read_u32(np, "isl,max-init-voltage-mv", &pval);
if (!ret)
pdata->max_init_voltage = pval;
ret = of_property_read_u32(np, "isl,min-system-voltage-mv", &pval);
if (!ret)
pdata->min_sys_voltage = pval;
ret = of_property_read_u32(np, "isl,input-voltage-limit-mv", &pval);
if (!ret)
pdata->input_voltage_limit = pval;
ret = of_property_read_u32(np, "isl,adapter-current-limit1-ma", &pval);
if (!ret)
pdata->adapter_current_lim1 = pval;
ret = of_property_read_u32(np, "isl,adapter-current-limit2-ma", &pval);
if (!ret)
pdata->adapter_current_lim2 = pval;
ret = of_property_read_u32(np, "isl,acprochot-threshold", &pval);
if (!ret)
pdata->acprochot_threshold = pval;
ret = of_property_read_u32(np, "isl,dcprochot-threshold", &pval);
if (!ret)
pdata->dcprochot_threshold = pval;
ret = of_property_read_u32(np, "isl,trickle-chg-current-ma", &pval);
if (!ret)
pdata->trickle_chg_current = pval;
ret = of_property_read_u32(np, "isl,otg-voltage-mv", &pval);
if (!ret)
pdata->otg_voltage = pval;
ret = of_property_read_u32(np, "isl,otg-current-ma", &pval);
if (!ret)
pdata->otg_current = pval;
ret = of_property_read_u32(np, "isl,current-sense-res", &pval);
if (!ret)
pdata->curr_sense_resistor = pval;
ret = of_property_read_u32(np, "isl,adapter-sense-res", &pval);
if (!ret)
pdata->adapter_sense_resistor = pval;
ret = of_property_read_u32(np, "isl,terminate-chg-current", &pval);
if (!ret)
pdata->terminate_chg_current = pval / 1000;
pdata->disable_input_regulation = of_property_read_bool(np,
"isl,disable-regulation");
pdata->enable_bat_learn_mode = of_property_read_bool(np,
"isl,enable-bat-learn-mode");
pdata->disable_turbo_mode = of_property_read_bool(np,
"isl,disable-turbo-mode");
pdata->enable_auto_charging = of_property_read_bool(np,
"isl,enable-auto-charging");
pdata->enable_battery_ovp = of_property_read_bool(np,
"isl,enable-battery-ovp");
pdata->enable_prochot_acok = of_property_read_bool(np,
"isl,enable-trigger-acok");
pdata->enable_prochot_otg_curr = of_property_read_bool(np,
"isl,enable-prochot-otg-current");
pdata->disable_safety_timer = of_property_read_bool(np,
"isl,disable-smbus-timer");
pdata->enable_psys_monitor = of_property_read_bool(np,
"isl,enable-psys-monitor");
pdata->tz_name = of_get_property(np, "isl,thermal-zone", NULL);
ret = of_property_read_u32(np, "isl,temp-polling-time-sec", &pval);
if (!ret)
pdata->temp_poll_time = pval;
temp_range_len = of_property_count_u32_elems(np, "isl,temp-range");
pdata->temp_range_len = temp_range_len;
if (temp_range_len <= 0)
goto vbus_node;
curr_volt_table_len =
of_property_count_u32_elems(np, "isl,current-voltage-table");
curr_volt_table_len = curr_volt_table_len / 3;
if (temp_range_len != curr_volt_table_len) {
dev_info(&client->dev, "current-thermal profile invalid\n");
goto vbus_node;
}
pdata->temp_range = devm_kzalloc(&client->dev,
sizeof(u32) * temp_range_len,
GFP_KERNEL);
if (!pdata->temp_range)
return ERR_PTR(-ENOMEM);
ret = of_property_read_u32_array(np, "isl,temp-range",
pdata->temp_range, temp_range_len);
if (ret < 0)
return ERR_PTR(ret);
pdata->temp_chg_curr_lim = devm_kzalloc(&client->dev,
sizeof(u32) * curr_volt_table_len,
GFP_KERNEL);
if (!pdata->temp_chg_curr_lim)
return ERR_PTR(-ENOMEM);
pdata->voltage_curr_lim = devm_kzalloc(&client->dev,
sizeof(u32) * curr_volt_table_len,
GFP_KERNEL);
if (!pdata->voltage_curr_lim)
return ERR_PTR(-ENOMEM);
pdata->max_battery_voltage = devm_kzalloc(&client->dev,
sizeof(u32) * curr_volt_table_len,
GFP_KERNEL);
if (!pdata->max_battery_voltage)
return ERR_PTR(-ENOMEM);
for (count = 0; count < curr_volt_table_len; ++count) {
ret = of_property_read_u32_index(np,
"isl,current-voltage-table",
count, &pval);
if (!ret)
pdata->temp_chg_curr_lim[count] = pval;
ret = of_property_read_u32_index(np,
"isl,current-voltage-table",
count + 5, &pval);
if (!ret)
pdata->voltage_curr_lim[count] = pval;
ret = of_property_read_u32_index(np,
"isl,current-voltage-table",
count + 10, &pval);
if (!ret)
pdata->max_battery_voltage[count] = pval;
}
vbus_node:
vbus_reg_node = of_find_node_by_name(np, "vbus");
if (vbus_reg_node) {
pdata->vbus_pdata = devm_kzalloc(&client->dev,
sizeof(*pdata->vbus_pdata), GFP_KERNEL);
if (!pdata->vbus_pdata)
return ERR_PTR(-ENOMEM);
pdata->vbus_pdata->ridata = of_get_regulator_init_data(
&client->dev, vbus_reg_node,
&isl9238_otg_reg_desc);
if (!pdata->vbus_pdata->ridata)
return ERR_PTR(-EINVAL);
}
*vbus_np = vbus_reg_node;
*chg_np = chg_reg_node;
return pdata;
}
static int isl9238_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct isl9238_charger *isl9238;
struct isl9238_chg_pdata *pdata = NULL;
struct device_node *vbus_np = NULL;
struct device_node *chg_np = NULL;
int ret = 0;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
return -EIO;
if (client->dev.of_node) {
pdata = isl9238_parse_dt_data(client, &vbus_np, &chg_np);
if (IS_ERR(pdata)) {
ret = PTR_ERR(pdata);
dev_err(&client->dev, "Dts Parsing failed, %d\n", ret);
return ret;
}
}
isl9238 = devm_kzalloc(&client->dev, sizeof(*isl9238), GFP_KERNEL);
if (!isl9238)
return -ENOMEM;
isl9238->rmap = devm_regmap_init_i2c(client, &isl9238_rmap_config);
if (IS_ERR(isl9238->rmap)) {
ret = PTR_ERR(isl9238->rmap);
dev_err(&client->dev, "regmap init failed: %d\n", ret);
return ret;
}
isl9238->dev = &client->dev;
i2c_set_clientdata(client, isl9238);
isl9238->client = client;
isl9238->chg_pdata = pdata;
isl9238->vbus_np = vbus_np;
isl9238->chg_np = chg_np;
isl9238->curnt_sense_res = isl9238->chg_pdata->curr_sense_resistor;
isl9238->adptr_sense_res = isl9238->chg_pdata->adapter_sense_resistor;
isl9238->charging_current_lim = isl9238->chg_pdata->charge_current_lim;
isl9238->adapter_curr_lim1 = isl9238->chg_pdata->adapter_current_lim1;
isl9238->max_init_voltage = isl9238->chg_pdata->max_init_voltage;
isl9238->max_sys_voltage = isl9238->chg_pdata->max_sys_voltage;
isl9238->terminate_chg_current =
isl9238->chg_pdata->terminate_chg_current;
isl9238_charger_bci.polling_time_sec =
isl9238->chg_pdata->temp_poll_time;
isl9238_charger_bci.tz_name = isl9238->chg_pdata->tz_name;
isl9238->chg_status = BATTERY_DISCHARGING;
isl9238_bc = isl9238;
mutex_init(&isl9238->mutex);
mutex_init(&isl9238->otg_mutex);
ret = isl9238_show_chip_version(isl9238);
if (ret < 0) {
dev_err(&client->dev, "version read failed %d\n", ret);
goto scrub_mutex;
}
isl9238_sysfs_init(&client->dev);
ret = isl9238_charger_init(isl9238);
if (ret < 0) {
dev_err(&client->dev, "Charger init failed: %d\n", ret);
goto scrub_mutex;
}
ret = isl9238_init_charger_regulator(isl9238, pdata);
if (ret < 0) {
dev_err(&client->dev, "Charger reg init failed %d\n", ret);
goto scrub_mutex;
}
if (client->irq) {
ret = devm_request_threaded_irq(isl9238->dev, client->irq, NULL,
isl9238_irq, IRQF_ONESHOT |
IRQF_TRIGGER_FALLING,
dev_name(isl9238->dev),
isl9238);
if (ret < 0) {
dev_warn(isl9238->dev,
"IRQ request IRQ fail:%d\n", ret);
dev_info(isl9238->dev,
"isl bc driver without irq enabled\n");
ret = 0;
}
}
device_set_wakeup_capable(isl9238->dev, true);
device_wakeup_enable(isl9238->dev);
isl9238->bc_dev = battery_charger_register(isl9238->dev,
&isl9238_charger_bci, isl9238);
if (IS_ERR(isl9238->bc_dev)) {
ret = PTR_ERR(isl9238->bc_dev);
dev_err(isl9238->dev, "batt-chg register fail:%d\n", ret);
goto scrub_mutex;
}
ret = isl9238_init_vbus_regulator(isl9238, pdata);
if (ret < 0) {
dev_err(&client->dev, "VBUS regulator init failed %d\n", ret);
goto scrub_mutex;
}
/* get charger operating modes */
isl9238_charger_get_op_modes(isl9238);
return 0;
scrub_mutex:
mutex_destroy(&isl9238->mutex);
mutex_destroy(&isl9238->otg_mutex);
return ret;
}
static void isl9238_shutdown(struct i2c_client *client)
{
struct isl9238_charger *isl9238 = i2c_get_clientdata(client);
mutex_lock(&isl9238->mutex);
isl9238->shutdown_complete = 1;
mutex_unlock(&isl9238->mutex);
if (isl9238->is_otg_connected)
isl9238_otg_disable(isl9238->vbus_rdev);
if (!isl9238->cable_connected)
return;
dev_info(isl9238->dev, "shutdown:stop thermal monitor\n");
battery_charger_thermal_stop_monitoring(isl9238->bc_dev);
}
#ifdef CONFIG_PM_SLEEP
static int isl9238_suspend(struct device *dev)
{
struct isl9238_charger *isl9238 = dev_get_drvdata(dev);
int charging_state = 0;
int ret = 0;
charging_state = battery_gauge_get_charging_status(isl9238->bc_dev);
if (charging_state != POWER_SUPPLY_STATUS_FULL) {
dev_err(isl9238->dev, "charging in progress: block suspend\n");
ret = -EINVAL;
}
dev_info(isl9238->dev, "charging status:%d\n", charging_state);
return ret;
}
static int isl9238_resume(struct device *dev)
{
struct isl9238_charger *isl9238 = dev_get_drvdata(dev);
isl9238_charger_get_op_modes(isl9238);
return 0;
};
#endif
static const struct dev_pm_ops isl9238_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(isl9238_suspend, isl9238_resume)
};
static const struct of_device_id isl9238_of_match[] = {
{ .compatible = "isil,isl9238", },
{},
};
MODULE_DEVICE_TABLE(of, isl9238_of_match);
static const struct i2c_device_id isl9238_id[] = {
{.name = "isl9238",},
{},
};
static struct i2c_driver isl9238_i2c_driver = {
.driver = {
.name = "isl9238",
.owner = THIS_MODULE,
.of_match_table = isl9238_of_match,
.pm = &isl9238_pm_ops,
},
.probe = isl9238_probe,
.shutdown = isl9238_shutdown,
.id_table = isl9238_id,
};
static int __init isl9238_module_init(void)
{
return i2c_add_driver(&isl9238_i2c_driver);
}
subsys_initcall(isl9238_module_init);
static void __exit isl9238_cleanup(void)
{
i2c_del_driver(&isl9238_i2c_driver);
}
module_exit(isl9238_cleanup);
MODULE_DESCRIPTION("ISL9238 battery charger driver");
MODULE_AUTHOR("Venkat Reddy Talla <vreddytalla@nvidia.com>");
MODULE_LICENSE("GPL v2");