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.

BQ25125: Sloppy Documentation

Part Number: BQ25125

Power Management Folks,

I'm doing a design with the BQ25125 and I'm finding the documentation and support software for this device a little sloppy.  

Let's start with the datasheet.  There are several bit fields in the register map that are blank.  These need to be marked reserved as to avoid customer confusion.

Second, the excel tool is a good start and definitely will help customers get started with the part, but the code it generates is lacking.  Why is the code spit out into a text file?  Seems like it would make sense to generate a C file.  Also where are BQ25125.h and StdPollI2C.h?  This could lead to customer confusion.

Why aren't you providing a header file for this device?  I know there are internal tools in the MCU groups that are used to generate header files from design information.  Leverage them and supply your customers with a header file for these devices that follow conventions used on TI microcontrollers.  I shouldn't have to create my own, but in any case here's the one I came up with.

bq25125.h

Trey

  • Hello Trey,

    Thank you for the feedback on the datasheet. We're currently in the process of updating the document.

    I've attached a C file which can help give a starting point on software for the BQ25125.

    /*
     * 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");