Other Parts Discussed in Thread: BQ25120
Hi Expert,
Do you have bq25120 sample code? RTOS should be better. Thanks!
This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
Please find attached.
/*
* BQ25120 Battery Charger Driver
*
* Copyright (C) 2015, Texas Instruments Corporation
*
* License TBD from SRAS
*/
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
/* Registers */
#define BQ25120_STATUS_SHIPMODE_REG 0x00
#define BQ25120_FAULTS_FAULTMASKS_REG 0x01
#define BQ25120_TSCONTROL_STATUS_REG 0x02
#define BQ25120_FASTCHARGE_CTL_REG 0x03
#define BQ25120_CHARGETERM_I2CADDR_REG 0x04
#define BQ25120_BATT_VOLTAGE_CTL_REG 0x05
#define BQ25120_SYSTEM_VOUT_CTL_REG 0x06
#define BQ25120_LOADSW_LDO_CTL_REG 0x07
#define BQ25120_PUSH_BTN_CTL_REG 0x08
#define BQ25120_ILIMIT_UVLO_CTL_REG 0x09
#define BQ25120_BATT_VOLT_MONITOR_REG 0x0A
#define BQ25120_VIN_DPM_TIMER_REG 0x0B
/* Status and ShipMode Control Register (0x0) */
#define BQ25120_STAT (BIT(7) | BIT(6))
#define BQ25120_STAT_SHIFT 0x06
#define BQ25120_STAT_MASK 0x03
#define BQ25120_EN_SHIPMODE BIT(5)
#define BQ25120_RESET BIT(4)
#define BQ25120_TIMER BIT(3)
#define BQ25120_VINDPM_STAT BIT(2)
#define BQ25120_NOT_CD_STAT BIT(1)
#define BQ25120_SYS_EN_STAT BIT(0)
/* Faults and Fault Masks Register (Ox1) */
#define BQ25120_VIN_OV BIT(7)
#define BQ25120_VIN_UV BIT(6)
#define BQ25120_BAT_UVLO BIT(5)
#define BQ25120_BAT_OCP BIT(4)
#define BQ25120_FAULTS (BIT(7) | BIT(6) | BIT(5) | BIT(4))
#define BQ25120_FAULTS_SHIFT 0x04
#define BQ25120_FAULTS_MASK 0x0F
#define BQ25120_VIN_OV_M BIT(3)
#define BQ25120_VIN_OV_M_SHIFT 0x03
#define BQ25120_VIN_OV_M_MASK 0x01
#define BQ25120_VIN_UV_M BIT(2)
#define BQ25120_VIN_UV_M_SHIFT 0x02
#define BQ25120_VIN_UV_M_MASK 0x01
#define BQ25120_BAT_UVLO_M BIT(1)
#define BQ25120_BAT_UVLO_M_SHIFT 0x01
#define BQ25120_BAT_UVLO_M_MASK 0x01
#define BQ25120_BAT_OCP_M BIT(0)
#define BQ25120_BAT_OCP_M_SHIFT 0x0
#define BQ25120_BAT_OCP_M_MASK 0x01
/* TS Control and Status Mask Register (0x2) */
#define BQ25120_TS_EN BIT(7)
#define BQ25120_TS_FAULT (BIT(6) | BIT(5))
#define BQ25120_TS_FAULT_SHIFT 0x05
#define BQ25120_TS_FAULT_MASK 0X03
#define BQ25120_TS_FAULT_OPEN BIT(4)
#define BQ25120_EN_INT BIT(3)
#define BQ25120_WAKE_M BIT(2)
#define BQ25120_WAKE_M_SHIFT 0x02
#define BQ25120_WAKE_M_MASK 0x01
#define BQ25120_RESET_M BIT(1)
#define BQ25120_RESET_SHIFT 0x01
#define BQ25120_RESET_MASK 0x01
#define BQ25120_TIMER_M BIT(0)
#define BQ25120_TIMER_M_SHIFT 0
#define BQ25120_TIMER_M_MASK 0x01
/* Fast Charge Control Register (0x03) */
#define BQ25120_ICHRG_RANGE BIT(7)
#define BQ25120_ICHARG (BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2))
#define BQ25120_ICHARG_SHIFT 0x02
#define BQ25120_ICHARG_MASK 0x1F
#define BQ25120_CE BIT(1)
#define BQ25120_HZ_MODE BIT(0)
/* Termination/Pre-Charge and I2C Address Register (0x4) */
#define BQ25120_IPRETERM_RANGE BIT(7)
#define BQ25120_IPRETERM (BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2))
#define BQ25120_IPRETERM_SHIFT 0x02
#define BQ25120_IPRETERM_MASK 0x1F
#define BQ25120_TE BIT(1)
// Bit 0 Reserved
/* Battery Voltage Control Register (0x05) */
#define BQ25120_VBREG (BIT(7) | BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1))
#define BQ25120_VBREG_SHIFT 0x1
#define BQ25120_VBREG_MASK 0x7F
// Bit 0 Reserved
/* SYS VOUT Control Register (0x06) */
#define BQ25120_EN_SYS_OUT BIT(7)
#define BQ25120_SYS_SEL (BIT(6) | BIT(5))
#define BQ25120_SYS_SEL_SHIFT 0x4
#define BQ25120_SYS_MASK 0x3
#define BQ25120_SYS_VOUT (BIT(4) | BIT(3) | BIT(2) | BIT(1))
#define BQ25120_SYS_VOUT_SHIFT 0x01
#define BQ25120_SYS_VOUT_MASK 0x0F
//Bit 0 Reserved
/* Load Switch and LDO Control Register (0x07) */
#define BQ25120_EN_LS_LDO BIT(7)
#define BQ25120_LS_LDO (BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2))
#define BQ25120_LS_LDO_SHIFT 0x2
#define BQ25120_LS_LDO_MASK 0x1F
//Bit 1 Reserved
#define BQ25120_MRRESET_VIN BIT(0)
/* Pushbutton Control Register (0x08) */
#define BQ25120_MRWAKE1 BIT(7)
#define BQ25120_MRWAKE2 BIT(6)
#define BQ25120_MRREC BIT(5)
#define BQ25120_MRRESET (BIT(4) | BIT(3))
#define BQ25120_MRRESET_SHIFT 0x03
#define BQ25120_MRRESET_MASK 0x03
#define BQ25120_PGB_MRS BIT(2)
#define BQ25120_WAKE1 BIT(1)
#define BQ25120_WAKE2 BIT(0)
/* ILIM and Battery UVLO Control Register (0x09) */
#define BQ25120_RESET_REG BIT(7)
//Bit 6 Reserved
#define BQ25120_INLIM (BIT(5) | BIT(4) | BIT(3))
#define BQ25120_INLIM_SHIFT 0x03
#define BQ25120_INLIM_MASK 0x07
#define BQ25120_BUVLO (BIT(2) | BIT(1) | BIT(0))
#define BQ25120_BUVLO_SHIFT 0x0
#define BQ25120_BUVLO_MASK 0x7
/* Voltage Based Battery Monitor Register (0x0A) */
#define BQ25120_VBMON_READ BIT(7)
#define BQ25120_VBMON_RANGE (BIT(6) | BIT(5))
#define BQ25120_VBMON_RANGE_SHIFT 0x05
#define BQ25120_VBMON_RANGE_MASK 0x03
#define BQ25120_VBMON_TH (BIT(4) | BIT(3) | BIT(2))
#define BQ25120_VBMON_TH_SHIFT 0x02
#define BQ25120_VBMON_TH_MASK 0x07
//Bit 1 and 0 Reserved
/* VIN_DPM and Timers Register (0x0B) */
#define BQ25120_VINDPM_OFF BIT(7)
#define BQ25120_VINDPM (BIT(6) | BIT(5) | BIT(4))
#define BQ25120_VINDPM_SHIFT 0x04
#define BQ25120_VINDPM_MASK 0x07
#define BQ25120_2XTMR_EN BIT(3)
#define BQ25120_TMR (BIT(2) | BIT(1))
#define BQ25120_TMR_SHIFT 0x01
#define BQ25120_TMR_MASK 0x03
#define BQ25120_TMR_DISABLE 0x03
//Bit 0 Reserved
#define BQ25120_MAX_CHG_FAULT 8
static struct reg_default BQ25120_reg_defs[] = {
{BQ25120_STATUS_SHIPMODE_REG, 0x01},
{BQ25120_FAULTS_FAULTMASKS_REG, 0x00},
{BQ25120_TSCONTROL_STATUS_REG, 0x88},
{BQ25120_FASTCHARGE_CTL_REG, 0x14},
{BQ25120_CHARGETERM_I2CADDR_REG, 0x0E},
{BQ25120_BATT_VOLTAGE_CTL_REG, 0x78},
{BQ25120_SYSTEM_VOUT_CTL_REG, 0xAA},
{BQ25120_LOADSW_LDO_CTL_REG, 0x7C},
{BQ25120_PUSH_BTN_CTL_REG, 0x68},
{BQ25120_ILIMIT_UVLO_CTL_REG, 0x0A},
{BQ25120_BATT_VOLT_MONITOR_REG, 0x00},
{BQ25120_VIN_DPM_TIMER_REG, 0x4A},
};
enum BQ25120_charge_fault {
BQ25120_NORMAL = 0,
BQ25120_CH_VIN_OV,
BQ25120_CH_VIN_UV,
BQ25120_BATTERY_UVLO,
BQ25120_BATTERY_OCP,
};
enum BQ25120_temp_status {
BQ25120_TEMP_NORMAL = 0,
BQ25120_TEMP_TNTC,
BQ25120_TEMP_TCOOL,
BQ25120_TEMP_TWARM,
};
//Not used.
static const char *fault_desc[BQ25120_MAX_CHG_FAULT] = {
[BQ25120_NORMAL] = "Normal",
[BQ25120_VIN] = "Vin > VovpINPUT",
[BQ25120_LOW_SUPPLY_CONNECTED] =
"Low Power Supply Connected or Boost Mode Overcurrent",
[BQ25120_THERMAL_SHUTDOWN] = "Thermal Shutdown",
[BQ25120_BATTERY_TS_FAULT] = "Battery Temperature Fault",
[BQ25120_BATTERY_OVP] = "Battery OVP",
[BQ25120_NO_BATTERY_CONNECTED] = "No Battery Connected",
};
static const char * temp_status_desc[BQ25120_MAX_TEMP_STATUS] = {
[BQ25120_NORMAL] = "Normal",
[BQ25120_TEMP_TNTC] = "TS temp < Tcold or TS temp > Thot",
[BQ25120_TEMP_TCOOL] = "Tcool > TS temp or TS temp > Tcold",
[BQ25120_TEMP_TWARM] = "Twarm < TS temp or TS temp < Tcold",
};
/**
* struct bq25120_charger - bq25120 charger instance
* @dev: pointer to device
* @regmap: pointer to driver regmap
* @mains: power_supply instance for AC/DC power
* @battery: power_supply instance for battery
* @mains_online: is AC/DC input connected
* @charging_enabled: is charging enabled
* @pdata: pointer to platform data
* @irq: fault irq number
*/
struct bq25120_charger {
struct device *dev;
struct regmap *regmap;
struct power_supply mains;
struct power_supply battery;
bool mains_online;
bool usb_online;
bool charging_enabled;
bool vsysoff_enabled;
int irq; /* IRQ for INT pin */
const struct bq25120_charger_platform_data *pdata;
};
/* Charge current in mA */
static const unsigned int cc_tbl[] = {
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
40,
50,
60,
70,
80,
90,
100,
110,
120,
130,
140,
150,
160,
170,
180,
190,
200,
210,
220,
230,
240,
250,
260,
270,
280,
290,
300
};
/* Termination current in mA */
static const unsigned int tc_tbl[] = {
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37
};
/* Input current limit in mA */
static const unsigned int icl_tbl[] = {
50,
100,
150,
200,
250,
300,
350,
400
};
/* Safety timer time limit in minutes */
static const unsigned int stl_tbl[] = {
30,
180,
540,
0, /* Disable */
};
/* Convert current to register value using lookup table */
static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
{
size_t i;
for (i = 0; i < size; i++)
if (val < tbl[i])
break;
return i > 0 ? i - 1 : -EINVAL;
}
/*
* bq25120_is_ps_online - returns whether input power source is connected
* @bq25120: pointer to bq25120 charger instance
*
* Returns %true if input power source is connected. Note that this is
* dependent on what platform has configured for usable power sources.
*/
static bool bq25120_is_ps_online(struct bq25120_charger *bq25120)
{
return (bq25120->mains_online);
}
/**
* bq25120_update_ps_status - refreshes the power source status
* @bq25120: pointer to bq25120 charger instance
*
* Function checks whether any power source is connected to the charger and
* updates internal state accordingly. If there is a change to previous state
* function returns %1, otherwise %0 and negative errno in case of errror.
*/
static int bq25120_update_ps_status(struct bq25120_charger *bq25120)
{
bool usb = false;
bool dc = false;
unsigned int val;
int ret;
int vinDpm;
ret = regmap_read(bq25120->regmap, BQ25120_BATT_VOLT_MONITOR_REG , &val);
if (ret < 0)
return ret;
/*
* platform data _and_ whether corresponding undervoltage is set.
*/
if (bq25120->pdata->use_mains)
dc = !( (val >> 7) BQ25120_VINDPM_OFF) );
ret = bq25120->mains_online != dc;
bq25120->mains_online = dc;
return ret;
}
enum power_supply_property bq25120_charger_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
};
/**
* bq25120_charging_status - returns status of charging
* @bq25120: pointer to bq25120 charger instance
*
* Function returns charging status.
* %0 means ready, %1 charging is in progress,
* %2charging done and %3 fault.
*/
static int bq25120_charging_status(struct bq25120_charger *bq25120)
{
unsigned int val;
int ret;
if (!bq25120_is_ps_online(bq25120))
return 0;
ret = regmap_read(bq25120->regmap, BQ25120_STATUS_SHIPMODE_REG, &val);
if (ret < 0)
return 0;
return ((val & BQ25120_STAT_MASK) >> BQ25120_STAT_SHIFT);
}
static int bq25120_charging_type(struct bq25120_charger *bq25120)
{
unsigned int val;
int ret;
if (!bq25120_is_ps_online(bq25120))
return 0;
ret = regmap_read(bq25120->regmap, BQ25120_STATUS_SHIPMODE_REG, &val);
if (ret < 0)
return 0;
return ((val >> BQ25120_STAT_SHIFT) & BQ25120_STAT_MASK);
}
static int bq25120_timer_suspend(struct bq25120_charger *bq25120)
{
int ret = 0;
if (bq25120->pdata->enable_control != BQ25120_CE) {
dev_dbg(bq25120->dev, "vsys enable/disable in SW disabled\n");
return 0;
}
ret = regmap_update_bits(bq25120->regmap, BQ25120_VIN_DPM_TIMER_REG,
BQ25120_TMR, BQ25120_TMR_DISABLE);
if (!ret)
dev_err(bq25120->dev, "failed to suspend timer\n");
return ret;
}
static int bq25120_timer_reset(struct bq25120_charger *bq25120)
{
int ret;
unsigned int tmr;
/* Reset with previous time value, when fault occur */
ret = regmap_read(bq25120->regmap, BQ25120_VIN_DPM_TIMER_REG, &tmr);
tmr = (tmr & BQ25120_TMR) >> BQ25120_TMR_SHIFT;
ret = regmap_update_bits(bq25120->regmap, BQ25120_VIN_DPM_TIMER_REG,
BQ25120_TMR, tmr);
if (!ret)
dev_err(bq25120->dev, "failed to reset timer\n");
return ret;
}
static int bq25120_charging_set(struct bq25120_charger *bq25120, bool enable)
{
int ret = 0;
if (bq25120->charging_enabled != enable) {
ret = regmap_update_bits(bq25120->regmap, BQ25120_FASTCHARGE_CTL_REG,
BQ25120_CE, enable ? 0 : 1);
if (!ret)
bq25120->charging_enabled = enable;
}
return ret;
}
static inline int bq25120_charging_enable(struct bq25120_charger *bq25120)
{
return bq25120_charging_set(bq25120, true);
}
static inline int bq25120_charging_disable(struct bq25120_charger *bq25120)
{
return bq25120_charging_set(bq25120, false);
}
static int bq25120_start_stop_charging(struct bq25120_charger *bq25120)
{
int ret;
/*
* Depending on whether valid power source is connected or not, we
* disable or enable the charging. We do it manually because it
* depends on how the platform has configured the valid inputs.
*/
if (bq25120_is_ps_online(bq25120)) {
ret = bq25120_charging_enable(bq25120);
if (ret < 0)
dev_err(bq25120->dev, "failed to enable charging\n");
} else {
ret = bq25120_charging_disable(bq25120);
if (ret < 0)
dev_err(bq25120->dev, "failed to disable charging\n");
}
return ret;
}
static int bq25120_set_charge_current(struct bq25120_charger *bq25120)
{
int ret;
unsigned int max_chrg_curr = bq25120->pdata->max_charge_current;
unsigned int term_curr = bq25120->pdata->termination_current;
if (max_chrg_curr) {
if (max_chrg_curr > 300) {
dev_warn(bq25120->dev, "Platform max charge current %d. \
Set to 300mA for bq25120\n", max_chrg_curr);
}
ret = current_to_hw(cc_tbl, ARRAY_SIZE(cc_tbl), max_chrg_curr);
if (ret < 0)
return ret;
ret = regmap_update_bits(bq25120->regmap,
BQ25120_CHARGETERM_I2CADDR_REG,
BQ25120_IPRETERM, ret);
if (ret < 0)
return ret;
}
if (term_curr) {
if (term_curr < 5) {
dev_warn(bq25120->dev, "Platform termination current %d. \
Set to 5mA for bq25120\n", term_curr);
}
ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), term_curr);
if (ret < 0)
return ret;
ret = regmap_update_bits(bq25120->regmap,
BQ25120_CHARGETERM_I2CADDR_REG,
BQ25120_IPRETERM, ret);
if (ret < 0)
return ret;
}
return 0;
}
static int bq25120_set_current_limits(struct bq25120_charger *bq25120)
{
int ret;
if (bq25120->pdata->mains_current_limit) {
ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
bq25120->pdata->mains_current_limit);
if (ret < 0)
return ret;
if (ret > 400)
dev_warn(bq25120->dev, "Invalid mains input current limit\n");
else {
ret = regmap_update_bits(bq25120->regmap,
BQ25120_ILIMIT_UVLO_CTL_REG,
BQ25120_INLIM, ret);
if (ret < 0)
return ret;
}
}
return 0;
}
static int bq25120_set_voltage_limits(struct bq25120_charger *bq25120)
{
int ret;
if (bq25120->pdata->max_charge_voltage) {
ret = bq25120->pdata->max_charge_voltage;
if (ret < 0)
return ret;
ret = regmap_update_bits(bq25120->regmap,
BQ25120_BATT_VOLTAGE_CTL_REG,
BQ25120_VBREG, ret);
if (ret < 0)
return ret;
}
return 0;
}
static int bq25120_hw_init(struct bq25120_charger *bq25120)
{
int ret;
/*
* Program the platform specific configuration values to the device
* first.
*/
ret = bq25120_set_charge_current(bq25120);
if (ret < 0)
goto fail;
ret = bq25120_set_current_limits(bq25120);
if (ret < 0)
goto fail;
ret = bq25120_set_voltage_limits(bq25120);
if (ret < 0)
goto fail;
ret = bq25120_update_ps_status(bq25120);
if (ret < 0)
goto fail;
ret = bq25120_start_stop_charging(bq25120);
fail:
return ret;
}
static irqreturn_t bq25120_interrupt(int irq, void *data)
{
struct bq25120_charger *bq25120 = data;
unsigned int irqstat;
bool handled = false;
int ret;
ret = regmap_read(bq25120->regmap, BQ25120_FAULTS_FAULTMASKS_REG,
&irqstat);
if (ret < 0) {
dev_warn(bq25120->dev, "reading BQ25120_FAULTS_FAULTMASKS_REG failed\n");
return IRQ_NONE;
}
irqstat &= 0xF0; //Mask mask bits
/*
* If we get a fault report the error.
*/
switch (irqstat) {
case BQ25120_NORMAL: /* Normal - No fault */
handled = true;
break;
case BQ25120_VIN_OV:
dev_info(bq25120->dev,"Charger Fault: Vin Over Voltage fault.\n");
handled = false;
break;
case BQ25120_VIN_UV:
dev_info(bq25120->dev,"Charger Fault: VIN Under Voltage fault.\n");
handled = false;
break;
case BQ25120_BAT_UVLO:
dev_info(bq25120->dev,
"Battery Fault: BAT_UVLO fault.\n");
handled = false;
break;
case BQ25120_BAT_OCP:
dev_info(bq25120->dev,
"Battery Fault: BAT_OCP fault.\n");
handled = false;
break;
default:
dev_err(bq25120->dev,
"Fault: unknown fault# %d - not handled. \n",
irqstat);
handled = false;
break;
}
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static int bq25120_irq_set(struct bq25120_charger *bq25120, bool enable)
{
int ret;
/*
* Enable/disable interrupts for:
*/
ret = regmap_update_bits(bq25120->regmap, BQ25120_TSCONTROL_STATUS_REG,
BQ25120_EN_INT, enable ? 1 : 0);
if (ret < 0)
return ret;
return 0;
}
static inline int bq25120_irq_enable(struct bq25120_charger *bq25120)
{
return bq25120_irq_set(bq25120, true);
}
static inline int bq25120_irq_disable(struct bq25120_charger *bq25120)
{
return bq25120_irq_set(bq25120, false);
}
static int bq25120_irq_init(struct bq25120_charger *bq25120, int irq)
{
int ret;
if (irq <= 0) {
dev_info(bq25120->dev, "invalid irq number: %d\n", irq);
goto out;
}
regmap_update_bits(bq25120->regmap, BQ25120_TSCONTROL_STATUS_REG,
BQ25120_EN_INT, 1);
ret = request_threaded_irq(irq, NULL, bq25120_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bq25120_irq", bq25120);
if (ret)
return ret;
bq25120->irq = irq;
bq25120_irq_enable(bq25120);
out:
return 0;
}
static ssize_t bq25120_show_charger_fault(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned int val;
int ret;
struct bq25120_charger *bq25120 = dev_get_drvdata(dev);
ret = regmap_read(bq25120->regmap, BQ25120_FAULTS_FAULTMASKS_REG, &val);
if (ret < 0)
return 0;
val &= 0xC0;
switch (val)
{
case BQ25120_VIN_OV:
return scnprintf(buf, PAGE_SIZE, "%s\n", "Charge VIN overvoltage fault.");
break;
case BQ25120_VIN_UV:
return scnprintf(buf, PAGE_SIZE, "%s\n", "Charge VIN undervoltage fault.");
break;
}
return 0;
}
static ssize_t bq25120_show_temp_fault(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int val;
int ret;
struct bq25120_charger *bq25120 = dev_get_drvdata(dev);
ret = regmap_read(bq25120->regmap, BQ25120_TSCONTROL_STATUS_REG, &val);
if (ret < 0)
return 0;
val = (val >> BQ25120_TS_FAULT_SHIFT) & BQ25120_TS_FAULT_MASK;
return scnprintf(buf, PAGE_SIZE, "%s\n", temp_status_desc[val]);
}
static DEVICE_ATTR(charger_fault, S_IRUSR, bq25120_show_charger_fault, NULL);
static DEVICE_ATTR(temp_fault, S_IRUSR, bq25120_show_temp_fault, NULL);
static struct attribute *bq25120_charger_attr[] = {
&dev_attr_charger_fault.attr,
&dev_attr_temp_fault.attr,
NULL,
};
static const struct attribute_group bq25120_attr_group = {
.attrs = bq25120_charger_attr,
};
/*
* Returns the constant charge current programmed
* into the charger in mA.
*/
static int get_const_charge_current(struct bq25120_charger *bq25120)
{
int ret;
unsigned int val, intval;
if (!bq25120_is_ps_online(bq25120))
return -ENODATA;
ret = regmap_read(bq25120->regmap, bq25120_CHARGE_CURR_TERM_REG, &val);
if (ret < 0)
return ret;
val = (val & bq25120_CHARGE_CURR) >> bq25120_CHARGE_CURR_SHIFT;
intval = 500 + val * 100;
if (intval >= 3000)
intval = 3000; /* set Max as 3A */
dev_info(bq25120->dev, "const charge current is %d mA\n", intval);
return intval;
}
/*
* Returns the constant charge voltage programmed
* into the charger in mV.
*/
static int get_const_charge_voltage(struct bq25120_charger *bq25120)
{
int ret;
unsigned int val;
int intval;
if (!bq25120_is_ps_online(bq25120))
return -ENODATA;
ret = regmap_read(bq25120->regmap, bq25120_BATTERY_VOLTAGE_REG, &val);
if (ret < 0)
return ret;
val = (val & bq25120_BATTERY_VOLTAGE) >> bq25120_BATTERY_VOLTAGE_SHIFT;
intval = 3500 + val * 20;
if (intval >= 4440) /* set MAX as 4.4V */
intval = 4440;
dev_info(bq25120->dev, "const charge voltage is=%d mV\n", intval);
return intval;
}
static int bq25120_mains_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct bq25120_charger *bq25120 =
container_of(psy, struct bq25120_charger, mains);
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = bq25120->mains_online;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = get_const_charge_voltage(bq25120);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = get_const_charge_current(bq25120);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
default:
return -EINVAL;
}
return 0;
}
static int bq25120_battery_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct bq25120_charger *bq25120 =
container_of(psy, struct bq25120_charger, battery);
const struct bq25120_charger_platform_data *pdata = bq25120->pdata;
int ret;
ret = bq25120_update_ps_status(bq25120);
if (ret < 0)
return ret;
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
if (!bq25120_is_ps_online(bq25120)) {
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
}
switch (bq25120_charging_status(bq25120)) {
case 0:
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case 1:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
break;
case 2:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
case 3:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; /* FAULT */
break;
}
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (!bq25120_is_ps_online(bq25120))
return -ENODATA;
/* We handle charger mode and boost mode. */
switch (bq25120_charging_type(bq25120)) {
case 0:
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case 1:
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
break;
default:
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
}
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = pdata->battery_info.technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = pdata->battery_info.voltage_min_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = pdata->battery_info.voltage_max_design;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = pdata->battery_info.charge_full_design;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = pdata->battery_info.name;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property bq25120_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static int bq25120_power_supply_register(struct bq25120_charger *bq25120)
{
static char *battery[] = { "bq25120-battery" };
int ret;
if (bq25120->pdata->use_mains) {
bq25120->mains.name = "bq25120-mains";
bq25120->mains.type = POWER_SUPPLY_TYPE_MAINS;
bq25120->mains.get_property = bq25120_mains_get_property;
bq25120->mains.properties = bq25120_charger_properties;
bq25120->mains.num_properties =
ARRAY_SIZE(bq25120_charger_properties);
bq25120->mains.supplied_to = battery;
bq25120->mains.num_supplicants = ARRAY_SIZE(battery);
ret = power_supply_register(bq25120->dev, &bq25120->mains);
if (ret < 0)
return ret;
}
bq25120->battery.name = "bq25120-battery";
bq25120->battery.type = POWER_SUPPLY_TYPE_BATTERY;
bq25120->battery.get_property = bq25120_battery_get_property;
bq25120->battery.properties = bq25120_battery_properties;
bq25120->battery.num_properties =
ARRAY_SIZE(bq25120_battery_properties);
ret = power_supply_register(bq25120->dev, &bq25120->battery);
if (ret < 0) {
if (bq25120->pdata->use_mains)
power_supply_unregister(&bq25120->mains);
return ret;
}
return 0;
}
static void bq25120_power_supply_unregister(struct bq25120_charger *bq25120)
{
power_supply_unregister(&bq25120->battery);
if (bq25120->pdata->use_usb)
power_supply_unregister(&bq25120->usb);
if (bq25120->pdata->use_mains)
power_supply_unregister(&bq25120->mains);
}
static const struct regmap_config bq25120_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = bq25120_MAX_REGISTER,
.reg_defaults = bq25120_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25120_reg_defs),
.cache_type = REGCACHE_RBTREE,
};
static int bq25120_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct bq25120_charger_platform_data *pdata;
struct device *dev = &client->dev;
struct bq25120_charger *bq25120;
int ret;
pdata = dev->platform_data;
if (!pdata) {
printk(KERN_NOTICE "In probe - failed with NO PDATA\n");
return -EINVAL;
}
bq25120 = devm_kzalloc(dev, sizeof(*bq25120), GFP_KERNEL);
if (!bq25120)
return -ENOMEM;
bq25120->dev = &client->dev;
bq25120->pdata = client->dev.platform_data;
i2c_set_clientdata(client, bq25120);
bq25120->regmap = devm_regmap_init_i2c(client, &bq25120_regmap);
if (IS_ERR(bq25120->regmap))
return PTR_ERR(bq25120->regmap);
ret = bq25120_hw_init(bq25120);
if (ret < 0) {
dev_err(dev, "failed to initialize bq25120 device: %d\n", ret);
goto err_dev;
}
ret = bq25120_power_supply_register(bq25120);
if (ret < 0) {
dev_err(dev, "failed to register power supply: %d\n", ret);
goto err_dev;
}
ret = sysfs_create_group(&dev->kobj, &bq25120_attr_group);
if (ret < 0) {
dev_err(dev, "failed to add charge sysfs: %d\n", ret);
goto err_sysfs;
}
/*
* Interrupt pin is optional. If it is connected, we setup the
* interrupt support here.
*/
ret = bq25120_irq_init(bq25120, client->irq);
if (ret < 0) {
dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
dev_warn(dev, "disabling IRQ support\n");
}
return 0;
err_sysfs:
bq25120_power_supply_unregister(bq25120);
err_dev:
return ret;
}
static int bq25120_remove(struct i2c_client *client)
{
struct bq25120_charger *bq25120 = i2c_get_clientdata(client);
if (bq25120->irq) {
bq25120_irq_disable(bq25120);
free_irq(bq25120->irq, bq25120);
}
sysfs_remove_group(&bq25120->dev->kobj, &bq25120_attr_group);
bq25120_power_supply_unregister(bq25120);
return 0;
}
static const struct i2c_device_id bq25120_id[] = {
{ "bq25120", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, bq25120_id);
static struct i2c_driver bq25120_driver = {
.driver = {
.name = "bq25120",
.owner = THIS_MODULE,
},
.probe = bq25120_probe,
.remove = bq25120_remove,
.id_table = bq25120_id,
};
module_i2c_driver(bq25120_driver);
MODULE_DESCRIPTION("bq25120 battery charger driver");
MODULE_AUTHOR("TI BMS software tools team");
MODULE_LICENSE("TI BSD");