1021 lines
23 KiB
C
Executable File
1021 lines
23 KiB
C
Executable File
/*******************************************************************************
|
|
Copyright (C), 2020, TP-LINK TECHNOLOGIES CO., LTD.
|
|
File name : bq25601-charger.c
|
|
Description : Driver for charge ic bq25601.
|
|
Author : wuchao(w8189)
|
|
|
|
History:
|
|
------------------------------
|
|
V1.0, 2020-07-01, wuchao create file.
|
|
*******************************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/qpnp/qpnp-adc.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/i2c.h>
|
|
|
|
#undef BQ25601_DEBUG
|
|
|
|
#undef dev_dbg
|
|
#ifdef BQ25601_DEBUG
|
|
#define dev_dbg(dev, format, arg...) dev_printk(KERN_INFO, dev, format, ##arg)
|
|
#else
|
|
#define dev_dbg(dev, format, arg...)
|
|
#endif
|
|
|
|
//define addrflag
|
|
int addrflag = 1;
|
|
int part_id;
|
|
|
|
// current is defined as a macro in current.h
|
|
|
|
/* Correspondence table of voltage and temperature */
|
|
static const int adc_map_temp[][2] =
|
|
{
|
|
{1800, -100},
|
|
{1712, -40},
|
|
{1654, -30},
|
|
{1570, -20},
|
|
{1517, -15},
|
|
{1457, -10},
|
|
{1390, -5},
|
|
{1316, 0},
|
|
{1238, 5},
|
|
{1155, 10},
|
|
{1070, 15},
|
|
{985, 20},
|
|
{900, 25},
|
|
{817, 30},
|
|
{738, 35},
|
|
{663, 40},
|
|
{593, 45},
|
|
{529, 50},
|
|
{470, 55},
|
|
{417, 60},
|
|
{370, 65},
|
|
{291, 75},
|
|
{160, 100},
|
|
{91, 125},
|
|
{0, 1000},
|
|
};
|
|
|
|
struct bq25601_charger {
|
|
struct i2c_client *client;
|
|
struct device *dev;
|
|
|
|
struct power_supply *usb_psy;
|
|
struct power_supply batt_psy;
|
|
|
|
struct qpnp_vadc_chip *vadc_dev;
|
|
|
|
struct work_struct usb_detect_work;
|
|
|
|
struct mutex lock;
|
|
struct mutex irq_lock;
|
|
struct mutex read_write_lock;
|
|
|
|
u32 irq_gpio;
|
|
u32 charge_gpio;
|
|
|
|
int usb_status;
|
|
int vbat_div;
|
|
};
|
|
|
|
static enum power_supply_property bq25601_power_properties[] = {
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_TEMP,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
};
|
|
|
|
/* i2c read & write */
|
|
|
|
static int __bq25601_read_reg(struct bq25601_charger *chip, u8 reg, u8 *val)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_read_byte_data(chip->client, reg);
|
|
if (ret < 0)
|
|
{
|
|
dev_err(chip->dev, "i2c read fail: can't read from %02x: %d\n", reg, ret);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
*val = ret;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: get reg(0x%02x) value 0x%02x\n", __func__, reg, *val);
|
|
return 0;
|
|
}
|
|
|
|
static int __bq25601_write_reg(struct bq25601_charger *chip, int reg, u8 val)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_write_byte_data(chip->client, reg, val);
|
|
if (ret < 0)
|
|
{
|
|
dev_err(chip->dev, "i2c write fail: can't write %02x to %02x: %d\n", val, reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: set reg(0x%02x) value 0x%02x\n", __func__, reg, val);
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_read_reg(struct bq25601_charger *chip, int reg, u8 *val)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&chip->read_write_lock);
|
|
rc = __bq25601_read_reg(chip, reg, val);
|
|
mutex_unlock(&chip->read_write_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int bq25601_masked_write(struct bq25601_charger *chip, int reg, u8 mask, u8 val, u8 shift)
|
|
{
|
|
s32 rc;
|
|
u8 temp;
|
|
|
|
mutex_lock(&chip->read_write_lock);
|
|
rc = __bq25601_read_reg(chip, reg, &temp);
|
|
if (rc)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
mask <<= shift;
|
|
val <<= shift;
|
|
|
|
temp &= ~mask;
|
|
temp |= val & mask;
|
|
rc = __bq25601_write_reg(chip, reg, temp);
|
|
|
|
out:
|
|
mutex_unlock(&chip->read_write_lock);
|
|
return rc;
|
|
}
|
|
|
|
/* set usb charging current */
|
|
|
|
#define USB_CHARGE_CURRENT_CTRL_REG 0x0
|
|
#define USB_CHARGE_CURRENT_MASK 0x1F
|
|
#define USB_CHARGE_CURRENT_SHIFT 0
|
|
|
|
// to do... we can use a better way
|
|
#define USB_CHARGE_CURRENT_VAL1000 0x9
|
|
|
|
static int bq25601_set_usb_charge_current(struct bq25601_charger *chip, unsigned int charge_current)
|
|
{
|
|
u8 val = USB_CHARGE_CURRENT_VAL1000;
|
|
|
|
dev_dbg(chip->dev, "%s: set usb charge current %dmA\n", __func__, charge_current);
|
|
|
|
switch (charge_current)
|
|
{
|
|
case 1000:
|
|
val = USB_CHARGE_CURRENT_VAL1000;
|
|
break;
|
|
default:
|
|
val = USB_CHARGE_CURRENT_VAL1000;
|
|
break;
|
|
}
|
|
|
|
return bq25601_masked_write(chip, USB_CHARGE_CURRENT_CTRL_REG,
|
|
USB_CHARGE_CURRENT_MASK, val, USB_CHARGE_CURRENT_SHIFT);
|
|
}
|
|
|
|
/* set & get battery charging current */
|
|
|
|
#define BATT_CHARGE_CURRENT_CTRL_REG 0x2
|
|
#define BATT_CHARGE_CURRENT_MASK 0x3F
|
|
#define BATT_CHARGE_CURRENT_SHIFT 0
|
|
|
|
// to do... we can use a better way
|
|
#define BATT_CHARGE_CURRENT_VAL0 0x0
|
|
#define BATT_CHARGE_CURRENT_VAL420 0x7
|
|
#define BATT_CHARGE_CURRENT_VAL960 0x10
|
|
#define BATT_CHARGE_CURRENT_VAL420_NEW 0x1C
|
|
#define BATT_CHARGE_CURRENT_VAL960_NEW 0x27
|
|
|
|
static int bq25601_set_batt_charge_current(struct bq25601_charger *chip, unsigned int charge_current, int addrflag)
|
|
{
|
|
u8 val = BATT_CHARGE_CURRENT_VAL0;
|
|
|
|
dev_dbg(chip->dev, "%s: set battery charge current %dmA\n", __func__, charge_current);
|
|
|
|
if ((addrflag == 2) && (part_id >= 0))
|
|
{
|
|
switch (charge_current)
|
|
{
|
|
case 0:
|
|
val = BATT_CHARGE_CURRENT_VAL0;
|
|
break;
|
|
case 400:
|
|
val = BATT_CHARGE_CURRENT_VAL420_NEW;
|
|
break;
|
|
case 1000:
|
|
val = BATT_CHARGE_CURRENT_VAL960_NEW;
|
|
break;
|
|
default:
|
|
val = BATT_CHARGE_CURRENT_VAL420_NEW;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (charge_current)
|
|
{
|
|
case 0:
|
|
val = BATT_CHARGE_CURRENT_VAL0;
|
|
break;
|
|
case 400:
|
|
val = BATT_CHARGE_CURRENT_VAL420;
|
|
break;
|
|
case 1000:
|
|
val = BATT_CHARGE_CURRENT_VAL960;
|
|
break;
|
|
default:
|
|
val = BATT_CHARGE_CURRENT_VAL420;
|
|
break;
|
|
}
|
|
}
|
|
return bq25601_masked_write(chip, BATT_CHARGE_CURRENT_CTRL_REG,
|
|
BATT_CHARGE_CURRENT_MASK, val, BATT_CHARGE_CURRENT_SHIFT);
|
|
}
|
|
|
|
static int bq25601_get_batt_charge_current(struct bq25601_charger *chip, unsigned int *charge_current, int addrflag)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = bq25601_read_reg(chip, BATT_CHARGE_CURRENT_CTRL_REG, &val);
|
|
if (rc < 0)
|
|
return -EAGAIN;
|
|
|
|
if ((addrflag == 2) && (part_id >= 0))
|
|
{
|
|
switch ((val >> BATT_CHARGE_CURRENT_SHIFT) & BATT_CHARGE_CURRENT_MASK)
|
|
{
|
|
case BATT_CHARGE_CURRENT_VAL0:
|
|
*charge_current = 0;
|
|
break;
|
|
case BATT_CHARGE_CURRENT_VAL420_NEW:
|
|
*charge_current = 400;
|
|
break;
|
|
case BATT_CHARGE_CURRENT_VAL960_NEW:
|
|
*charge_current = 1000;
|
|
break;
|
|
default:
|
|
*charge_current = 400;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ((val >> BATT_CHARGE_CURRENT_SHIFT) & BATT_CHARGE_CURRENT_MASK)
|
|
{
|
|
case BATT_CHARGE_CURRENT_VAL0:
|
|
*charge_current = 0;
|
|
break;
|
|
case BATT_CHARGE_CURRENT_VAL420:
|
|
*charge_current = 400;
|
|
break;
|
|
case BATT_CHARGE_CURRENT_VAL960:
|
|
*charge_current = 1000;
|
|
break;
|
|
default:
|
|
*charge_current = 400;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: get battery charge current(0x%02x) %dmA\n", __func__, val, *charge_current);
|
|
return 0;
|
|
}
|
|
|
|
/*set & get pre charge current */
|
|
#define BATT_CHARGE_PRE_CURRENT_REG 0x3
|
|
#define BATT_CHARGE_PRE_CURRENT_VAL180 0xDD
|
|
#define BATT_CHARGE_PRE_CURRENT_VAL40 0xD5
|
|
|
|
static int bq25601_set_batt_pre_charge_current(struct bq25601_charger *chip, unsigned int pre_current)
|
|
{
|
|
u8 val = BATT_CHARGE_PRE_CURRENT_VAL180;
|
|
|
|
dev_dbg(chip->dev, "%s: set batt pre charge current %dmA\n", __func__, val);
|
|
|
|
s32 ret;
|
|
|
|
switch (pre_current)
|
|
{
|
|
case 180:
|
|
val = BATT_CHARGE_PRE_CURRENT_VAL180;
|
|
break;
|
|
case 40:
|
|
val = BATT_CHARGE_PRE_CURRENT_VAL40;
|
|
break;
|
|
default:
|
|
val = BATT_CHARGE_PRE_CURRENT_VAL180;
|
|
break;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(chip->client, BATT_CHARGE_PRE_CURRENT_REG, val);
|
|
if (ret < 0)
|
|
{
|
|
dev_err(chip->dev, "i2c write fail: can't write %02x to %02x: %d\n", val, BATT_CHARGE_PRE_CURRENT_REG, ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_get_batt_pre_charge_current(struct bq25601_charger *chip, unsigned int *pre_current)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = bq25601_read_reg(chip, BATT_CHARGE_PRE_CURRENT_REG, &val);
|
|
if (rc < 0)
|
|
return -EAGAIN;
|
|
|
|
switch (val)
|
|
{
|
|
case BATT_CHARGE_PRE_CURRENT_VAL180:
|
|
*pre_current = 180;
|
|
break;
|
|
case BATT_CHARGE_PRE_CURRENT_VAL40:
|
|
*pre_current = 40;
|
|
break;
|
|
default:
|
|
*pre_current = 180;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* set & get battery charging voltage */
|
|
|
|
#define BATT_CHARGE_VOLTAGE_CTRL_REG 0x4
|
|
#define BATT_CHARGE_VOLTAGE_MASK 0x1F
|
|
#define BATT_CHARGE_VOLTAGE_SHIFT 3
|
|
|
|
// to do... we can use a better way
|
|
#define BATT_CHARGE_VOLTAGE_VAL4080 0x7
|
|
#define BATT_CHARGE_VOLTAGE_VAL4208 0xB
|
|
|
|
static int bq25601_set_batt_charge_voltage(struct bq25601_charger *chip, unsigned int voltage)
|
|
{
|
|
u8 val = BATT_CHARGE_VOLTAGE_VAL4208;
|
|
|
|
dev_dbg(chip->dev, "%s: set battery charge voltage %dmV\n", __func__, voltage);
|
|
|
|
switch (voltage)
|
|
{
|
|
case 4100:
|
|
val = BATT_CHARGE_VOLTAGE_VAL4080;
|
|
break;
|
|
case 4200:
|
|
val = BATT_CHARGE_VOLTAGE_VAL4208;
|
|
break;
|
|
default:
|
|
val = BATT_CHARGE_VOLTAGE_VAL4080;
|
|
break;
|
|
}
|
|
|
|
return bq25601_masked_write(chip, BATT_CHARGE_VOLTAGE_CTRL_REG,
|
|
BATT_CHARGE_VOLTAGE_MASK, val, BATT_CHARGE_VOLTAGE_SHIFT);
|
|
}
|
|
|
|
static int bq25601_get_batt_charge_voltage(struct bq25601_charger *chip, unsigned int *voltage)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = bq25601_read_reg(chip, BATT_CHARGE_VOLTAGE_CTRL_REG, &val);
|
|
if (rc < 0)
|
|
return -EAGAIN;
|
|
|
|
switch ((val >> BATT_CHARGE_VOLTAGE_SHIFT) & BATT_CHARGE_VOLTAGE_MASK)
|
|
{
|
|
case BATT_CHARGE_VOLTAGE_VAL4080:
|
|
*voltage = 4100;
|
|
break;
|
|
case BATT_CHARGE_VOLTAGE_VAL4208:
|
|
*voltage = 4200;
|
|
break;
|
|
default:
|
|
*voltage = 4100;
|
|
break;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: get battery charge voltage(0x%02x) %dmV\n", __func__, val, *voltage);
|
|
return 0;
|
|
}
|
|
|
|
/* disable watchdog */
|
|
|
|
#define WATCHDOG_CTRL_REG 0x5
|
|
#define WATCHDOG_MASK 0x3
|
|
#define WATCHDOG_SHIFT 4
|
|
|
|
// to do... we can use a better way
|
|
#define WATCHDOG_DISABLED 0x0
|
|
|
|
static int bq25601_disable_watchdog(struct bq25601_charger *chip)
|
|
{
|
|
u8 val = WATCHDOG_DISABLED;
|
|
|
|
dev_dbg(chip->dev, "%s: disable watchdog\n", __func__);
|
|
|
|
return bq25601_masked_write(chip, WATCHDOG_CTRL_REG, WATCHDOG_MASK, val, WATCHDOG_SHIFT);
|
|
}
|
|
|
|
/* set usb charging min voltage */
|
|
|
|
#define USB_CHARGE_VOLTAGE_CTRL_REG 0x6
|
|
#define USB_CHARGE_VOLTAGE_MASK 0xF
|
|
#define USB_CHARGE_VOLTAGE_SHIFT 0
|
|
|
|
// to do... we can use a better way
|
|
#define USB_CHARGE_VOLTAGE_VAL4600 0x7
|
|
|
|
static int bq25601_set_usb_charge_voltage(struct bq25601_charger *chip, unsigned int voltage)
|
|
{
|
|
u8 val = USB_CHARGE_VOLTAGE_VAL4600;
|
|
|
|
dev_dbg(chip->dev, "%s: set usb charge voltage %dmV\n", __func__, voltage);
|
|
|
|
switch (voltage)
|
|
{
|
|
case 4600:
|
|
val = USB_CHARGE_VOLTAGE_VAL4600;
|
|
break;
|
|
default:
|
|
val = USB_CHARGE_VOLTAGE_VAL4600;
|
|
break;
|
|
}
|
|
|
|
return bq25601_masked_write(chip, USB_CHARGE_VOLTAGE_CTRL_REG,
|
|
USB_CHARGE_VOLTAGE_MASK, val, USB_CHARGE_VOLTAGE_SHIFT);
|
|
}
|
|
|
|
/* get usb status */
|
|
|
|
#define USB_STATUS_CTRL_REG 0x8
|
|
#define USB_POWER_STATUS_MASK 0x1
|
|
#define USB_POWER_STATUS_SHIFT 2
|
|
|
|
static int bq25601_get_usb_status(struct bq25601_charger *chip, unsigned int *status)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = bq25601_read_reg(chip, USB_STATUS_CTRL_REG, &val);
|
|
if (rc < 0)
|
|
return -EAGAIN;
|
|
|
|
// we can just use PG_STST
|
|
if ((val >> USB_POWER_STATUS_SHIFT) & USB_POWER_STATUS_MASK)
|
|
{
|
|
*status = 1;
|
|
}
|
|
else
|
|
{
|
|
*status = 0;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: get usb status(0x%02x) %d\n", __func__, val, *status);
|
|
return 0;
|
|
}
|
|
|
|
/* get battery voltage */
|
|
|
|
static int bq25601_get_property_battery_voltage(struct bq25601_charger *chip)
|
|
{
|
|
int rc = 0;
|
|
struct qpnp_vadc_result results;
|
|
int voltage = 0;
|
|
|
|
if (IS_ERR_OR_NULL(chip->vadc_dev))
|
|
{
|
|
chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
|
|
if (IS_ERR(chip->vadc_dev))
|
|
{
|
|
pr_err("%s, Failed to get vadc\n", __func__);
|
|
return PTR_ERR(chip->vadc_dev);
|
|
}
|
|
}
|
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, P_MUX2_1_1, &results);
|
|
if (rc)
|
|
{
|
|
pr_err("Unable to read battery voltage, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
voltage = (int)(results.physical * chip->vbat_div) / 1000;
|
|
dev_dbg(chip->dev, "%s: get battery voltage %d\n", __func__, voltage);
|
|
return voltage;
|
|
}
|
|
|
|
/* get part id */
|
|
#define BATT_PART_ID_REG 0xB
|
|
static int bq25601_get_batt_part_id(struct bq25601_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = bq25601_read_reg(chip, BATT_PART_ID_REG, &val);
|
|
dev_dbg("get reg(0x%02x) value 0x%02x\n", BATT_PART_ID_REG, val);
|
|
if (rc < 0)
|
|
return -1;
|
|
return val;
|
|
}
|
|
|
|
/* get battery temperature */
|
|
|
|
static int em_batt_map_linear(const int (*table)[2], const u32 table_size, int in_num, int *out_num)
|
|
{
|
|
u32 i = 0;
|
|
|
|
if ((NULL == table) || (NULL == out_num))
|
|
{
|
|
pr_err("Invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
while (i < table_size)
|
|
{
|
|
if (table[i][0] < in_num)
|
|
{
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
*out_num = table[0][1];
|
|
}
|
|
else if (i == table_size)
|
|
{
|
|
*out_num = table[table_size-1][1];
|
|
}
|
|
else
|
|
{
|
|
/* result is between search_index and search_index-1, interpolate linearly */
|
|
*out_num = (((table[i][1] - table[i-1][1]) * (in_num - table[i-1][0]))
|
|
/ (table[i][0] - table[i-1][0])) + table[i-1][1];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_get_property_battery_id_therm(struct bq25601_charger *chip)
|
|
{
|
|
int rc = 0;
|
|
int temp_value = 0;
|
|
struct qpnp_vadc_result results;
|
|
|
|
if (IS_ERR_OR_NULL(chip->vadc_dev))
|
|
{
|
|
chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
|
|
if (IS_ERR(chip->vadc_dev))
|
|
{
|
|
pr_err("%s, Failed to get vadc\n", __func__);
|
|
return PTR_ERR(chip->vadc_dev);
|
|
}
|
|
}
|
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, LR_MUX2_BAT_ID, &results);
|
|
if (rc)
|
|
{
|
|
pr_err("Unable to read battery temperature, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
temp_value = ((int)results.physical) / 1000; /* convert to mV */
|
|
|
|
rc = em_batt_map_linear(adc_map_temp, sizeof(adc_map_temp) / sizeof(adc_map_temp[0]),
|
|
temp_value, &temp_value);
|
|
if (rc)
|
|
{
|
|
pr_err("Unable to calculate battery temperature, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return temp_value;
|
|
}
|
|
|
|
/* sysfs, write & read */
|
|
|
|
static int bq25601_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
|
|
{
|
|
switch (psp)
|
|
{
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int bq25601_power_set_property(struct power_supply *psy, enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct bq25601_charger *chip = container_of(psy, struct bq25601_charger, batt_psy);
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
switch (psp)
|
|
{
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
bq25601_set_batt_charge_current(chip, val->intval, addrflag);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
bq25601_set_batt_charge_voltage(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
bq25601_set_batt_pre_charge_current(chip, val->intval);
|
|
break;
|
|
default:
|
|
mutex_unlock(&chip->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
power_supply_changed(&chip->batt_psy);
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_power_get_property(struct power_supply *psy, enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct bq25601_charger *chip = container_of(psy, struct bq25601_charger, batt_psy);
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
switch (psp)
|
|
{
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
val->intval = bq25601_get_property_battery_voltage(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
val->intval = bq25601_get_property_battery_id_therm(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
bq25601_get_batt_charge_current(chip, &(val->intval), addrflag);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
bq25601_get_batt_charge_voltage(chip, &(val->intval));
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
bq25601_get_batt_pre_charge_current(chip, &(val->intval));
|
|
break;
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = "BQ25601";
|
|
break;
|
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
val->strval = "TI";
|
|
break;
|
|
default:
|
|
mutex_unlock(&chip->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
return 0;
|
|
}
|
|
|
|
/* irq handler & usb detect */
|
|
|
|
static int bq25601_set_usb_default_status(struct bq25601_charger *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
// set usb charge limit voltage 4.6V
|
|
rc = bq25601_set_usb_charge_voltage(chip, 4600);
|
|
if (rc)
|
|
{
|
|
dev_err(chip->dev, "set usb charge voltage failed\n");
|
|
return rc;
|
|
}
|
|
|
|
// set usb charge current 1A
|
|
rc = bq25601_set_usb_charge_current(chip, 1000);
|
|
if (rc)
|
|
{
|
|
dev_err(chip->dev, "set usb charge current failed\n");
|
|
return rc;
|
|
}
|
|
|
|
// enable usb charging
|
|
rc = gpio_direction_output(chip->charge_gpio, 0);
|
|
if (rc)
|
|
{
|
|
dev_err(chip->dev, "set direction for charge gpio(%d) failed\n", chip->charge_gpio);
|
|
return rc;
|
|
}
|
|
|
|
// disable watchdog
|
|
rc = bq25601_disable_watchdog(chip);
|
|
if (rc)
|
|
{
|
|
dev_err(chip->dev, "disable watchdog failed\n");
|
|
return rc;
|
|
}
|
|
|
|
//set batt pre charge
|
|
if ((addrflag == 2) && (part_id >= 0))
|
|
{
|
|
rc = bq25601_set_batt_pre_charge_current(chip, 180);
|
|
}
|
|
if (rc)
|
|
{
|
|
dev_err(chip->dev, "set batt pre charge failed\n");
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void bq25601_usb_detect_work_func(struct work_struct *work)
|
|
{
|
|
struct bq25601_charger *chip = container_of(work, struct bq25601_charger, usb_detect_work);
|
|
int status = 0;
|
|
|
|
bq25601_get_usb_status(chip, &status);
|
|
if ((chip->usb_status != status) && (chip->usb_psy))
|
|
{
|
|
if (status)
|
|
{
|
|
dev_info(chip->dev, "usb insert, reset usb charge status\n");
|
|
bq25601_set_usb_default_status(chip);
|
|
}
|
|
power_supply_set_present(chip->usb_psy, status);
|
|
chip->usb_status = status;
|
|
}
|
|
else
|
|
{
|
|
dev_dbg(chip->dev, "usb status not changed\n");
|
|
}
|
|
}
|
|
|
|
static irqreturn_t bq25601_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct bq25601_charger *chip = dev_id;
|
|
|
|
mutex_lock(&chip->irq_lock);
|
|
dev_dbg(chip->dev, "IRQ triggered\n");
|
|
schedule_work(&chip->usb_detect_work);
|
|
mutex_unlock(&chip->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* module init & probe & remove */
|
|
|
|
static int bq25601_charger_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
{
|
|
int rc, irq, value;
|
|
struct bq25601_charger *chip;
|
|
struct power_supply *usb_psy;
|
|
|
|
dev_info(&client->dev, "BQ25601 charger probe start...\n");
|
|
|
|
usb_psy = power_supply_get_by_name("usb");
|
|
if (!usb_psy)
|
|
{
|
|
dev_dbg(&client->dev, "USB psy not found, deferring probe\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
// same init value as chip->usb_status
|
|
power_supply_set_present(usb_psy, 0);
|
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
{
|
|
dev_err(&client->dev, "Cannot allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->client = client;
|
|
if (client->addr == 0x6b)
|
|
{
|
|
addrflag = 1;
|
|
}
|
|
else
|
|
{
|
|
addrflag = 2;
|
|
}
|
|
chip->dev = &client->dev;
|
|
chip->usb_psy = usb_psy;
|
|
chip->usb_status = 0;
|
|
|
|
mutex_init(&chip->lock);
|
|
mutex_init(&chip->irq_lock);
|
|
mutex_init(&chip->read_write_lock);
|
|
part_id = bq25601_get_batt_part_id(chip);
|
|
if (part_id < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
if (of_find_property(chip->dev->of_node, "qcom,chg-vadc", NULL))
|
|
{
|
|
/* early for VADC get, defer probe if needed */
|
|
chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
|
|
if (IS_ERR(chip->vadc_dev))
|
|
{
|
|
rc = PTR_ERR(chip->vadc_dev);
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("vadc property configured incorrectly\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
chip->batt_psy.name = "battery";
|
|
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
chip->batt_psy.get_property = bq25601_power_get_property;
|
|
chip->batt_psy.set_property = bq25601_power_set_property;
|
|
chip->batt_psy.property_is_writeable = bq25601_property_is_writeable;
|
|
chip->batt_psy.properties = bq25601_power_properties;
|
|
chip->batt_psy.num_properties = ARRAY_SIZE(bq25601_power_properties);
|
|
|
|
rc = power_supply_register(chip->dev, &chip->batt_psy);
|
|
if (rc < 0)
|
|
{
|
|
dev_err(&client->dev, "Cannot register batt_psy, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
INIT_WORK(&chip->usb_detect_work, bq25601_usb_detect_work_func);
|
|
|
|
rc = of_property_read_u32(chip->dev->of_node, "ti,chg-vbat-div", &value);
|
|
if (rc == 0)
|
|
{
|
|
chip->vbat_div = value;
|
|
}
|
|
else
|
|
{
|
|
chip->vbat_div = 1;
|
|
dev_err(&client->dev, "Failed to get battery voltage division, rc = %d\n", rc);
|
|
}
|
|
|
|
chip->charge_gpio = of_get_named_gpio(chip->dev->of_node, "ti,chg-en-gpio", 0);
|
|
dev_info(&client->dev, "charge gpio: %d\n", chip->charge_gpio);
|
|
if (gpio_is_valid(chip->charge_gpio))
|
|
{
|
|
rc = gpio_request(chip->charge_gpio, "ti,chg-en-gpio");
|
|
if (rc)
|
|
{
|
|
pr_err("charge enable gpio request failed, rc = %d\n", rc);
|
|
goto fail_charge_gpio_init;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto fail_charge_gpio_init;
|
|
}
|
|
|
|
chip->irq_gpio = of_get_named_gpio_flags(chip->dev->of_node, "ti,irq-gpio", 0, NULL);
|
|
dev_info(&client->dev, "irq gpio: %d\n", chip->irq_gpio);
|
|
if (gpio_is_valid(chip->irq_gpio))
|
|
{
|
|
rc = gpio_request(chip->irq_gpio, "bq25601_irq");
|
|
if (rc)
|
|
{
|
|
dev_err(&client->dev, "irq gpio request failed, rc = %d", rc);
|
|
goto fail_irq_gpio_init;
|
|
}
|
|
rc = gpio_direction_input(chip->irq_gpio);
|
|
if (rc)
|
|
{
|
|
dev_err(&client->dev, "set direction for irq gpio(%d) failed\n", chip->irq_gpio);
|
|
goto fail_irq_gpio_init;
|
|
}
|
|
irq = gpio_to_irq(chip->irq_gpio);
|
|
if (irq < 0)
|
|
{
|
|
dev_err(&client->dev, "Invalid irq_gpio irq = %d\n", irq);
|
|
goto fail_irq_gpio_init;
|
|
}
|
|
rc = devm_request_threaded_irq(&client->dev, irq, NULL, bq25601_irq_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "bq25601_irq", chip);
|
|
if (rc)
|
|
{
|
|
dev_err(&client->dev, "Failed allocate irq = %d request, rc = %d\n", irq, rc);
|
|
goto fail_irq_gpio_init;
|
|
}
|
|
enable_irq_wake(irq);
|
|
dev_info(&client->dev, "set irq success\n");
|
|
}
|
|
else
|
|
{
|
|
goto fail_irq_gpio_init;
|
|
}
|
|
|
|
rc = bq25601_set_usb_default_status(chip);
|
|
if (rc)
|
|
{
|
|
goto fail_enable_charge;
|
|
}
|
|
|
|
// init usb status
|
|
schedule_work(&chip->usb_detect_work);
|
|
|
|
dev_info(&client->dev, "BQ25601 charger successfully probed\n");
|
|
return 0;
|
|
|
|
fail_enable_charge:
|
|
fail_irq_gpio_init:
|
|
if (gpio_is_valid(chip->irq_gpio))
|
|
gpio_free(chip->irq_gpio);
|
|
fail_charge_gpio_init:
|
|
if (gpio_is_valid(chip->charge_gpio))
|
|
gpio_free(chip->charge_gpio);
|
|
power_supply_unregister(&chip->batt_psy);
|
|
return rc;
|
|
}
|
|
|
|
static int bq25601_charger_remove(struct i2c_client *client)
|
|
{
|
|
struct bq25601_charger *chip = i2c_get_clientdata(client);
|
|
|
|
power_supply_unregister(&chip->batt_psy);
|
|
if (gpio_is_valid(chip->charge_gpio))
|
|
gpio_free(chip->charge_gpio);
|
|
if (gpio_is_valid(chip->irq_gpio))
|
|
gpio_free(chip->irq_gpio);
|
|
mutex_destroy(&chip->irq_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
dev_info(&client->dev, "BQ25601 charger suspend\n");
|
|
return 0;
|
|
}
|
|
|
|
static int bq25601_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
dev_info(&client->dev, "BQ25601 charger resume\n");
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops bq25601_pm_ops = {
|
|
.suspend = bq25601_suspend,
|
|
.resume = bq25601_resume,
|
|
};
|
|
|
|
static struct of_device_id bq25601_match_table[] = {
|
|
{ .compatible = "ti,sgm41513-charger",},
|
|
{ .compatible = "ti,bq25601-charger",},
|
|
};
|
|
|
|
static const struct i2c_device_id bq25601_charger_id[] = {
|
|
{"sgm41513-charger", 0},
|
|
{"bq25601-charger", 1},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, bq25601_charger_id);
|
|
|
|
static struct i2c_driver bq25601_charger_driver = {
|
|
.driver = {
|
|
.name = "bq25601-charger",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = bq25601_match_table,
|
|
.pm = &bq25601_pm_ops,
|
|
},
|
|
.probe = bq25601_charger_probe,
|
|
.remove = bq25601_charger_remove,
|
|
.id_table = bq25601_charger_id,
|
|
};
|
|
|
|
module_i2c_driver(bq25601_charger_driver);
|
|
|
|
MODULE_DESCRIPTION("BQ25601 Charger");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("i2c:bq25601-charger");
|