BigW Consortium Gitlab

Commit 835aa641 by Ashish Syal

Updated BQ25601 and got source from…

parent 69c659ed
/*
* Platform data for the TI bq25601 battery charger driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BQ25601_PLATFORM_DATA_H_
#define BQ25601_PLATFORM_DATA_H_
struct bq25601_platform_data {
int gpio_int; /* GPIO pin that's connected to INT# */
};
#endif
/*
* Driver for the TI bq25601 battery charger.
*
* Author: Mark A. Greer <mgreer@animalcreek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include "bq25601-platform-data.h"
#define BQ25601_MANUFACTURER "Texas Instruments"
#define BQ25601_REG_ISC 0x00 /* Input Source Control */
#define BQ25601_REG_ISC_EN_HIZ_MASK BIT(7)
#define BQ25601_REG_ISC_EN_HIZ_SHIFT 7
#define BQ25601_REG_ISC_EN_ICHG_MON_MASK (BIT(6) | BIT(5))
#define BQ25601_REG_ISC_EN_ICHG_MON_SHIFT 5
#define BQ25601_REG_ISC_IINDPM_MASK (BIT(4) | BIT(3) | BIT(2) | \
BIT(1) | BIT(0))
#define BQ25601_REG_ISC_IINDPM_SHIFT 0
#define BQ25601_REG_POC 0x01 /* Power-On Configuration */
#define BQ25601_REG_POC_PFM_DIS_MASK BIT(7)
#define BQ25601_REG_POC_PFM_DIS_SHIFT 7
#define BQ25601_REG_POC_WD_RST_MASK BIT(6)
#define BQ25601_REG_POC_WD_RST_SHIFT 6
#define BQ25601_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4))
#define BQ25601_REG_POC_CHG_CONFIG_SHIFT 4
#define BQ25601_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1))
#define BQ25601_REG_POC_SYS_MIN_SHIFT 1
#define BQ25601_REG_POC_MIN_VBAT_SEL_MASK BIT(0)
#define BQ25601_REG_POC_MIN_VBAT_SEL_SHIFT 0
#define BQ25601_REG_CCC 0x02 /* Charge Current Control */
#define BQ25601_REG_CCC_BOOST_LIM_MASK BIT(7)
#define BQ25601_REG_CCC_BOOST_LIM_SHIFT 7
#define BQ25601_REG_CCC_Q1_FULLON_MASK BIT(6)
#define BQ25601_REG_CCC_Q1_FULLON_SHIFT 6
#define BQ25601_REG_CCC_ICHG_MASK (BIT(5) | BIT(4) | BIT(3) | \
BIT(2) | BIT(1) | BIT(0))
#define BQ25601_REG_CCC_ICHG_SHIFT 0
#define BQ25601_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */
#define BQ25601_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4))
#define BQ25601_REG_PCTCC_IPRECHG_SHIFT 4
#define BQ25601_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \
BIT(0))
#define BQ25601_REG_PCTCC_ITERM_SHIFT 0
#define BQ25601_REG_CVC 0x04 /* Charge Voltage Control */
#define BQ25601_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4) | BIT(3))
#define BQ25601_REG_CVC_VREG_SHIFT 3
#define BQ25601_REG_CVC_TOPOFF_TIMER_MASK (BIT(2) | BIT(1))
#define BQ25601_REG_CVC_TOPOFF_TIMER_SHIFT 1
#define BQ25601_REG_CVC_VRECHG_MASK BIT(0)
#define BQ25601_REG_CVC_VRECHG_SHIFT 0
#define BQ25601_REG_CTTC 0x05 /* Charge Term/Timer Control */
#define BQ25601_REG_CTTC_EN_TERM_MASK BIT(7)
#define BQ25601_REG_CTTC_EN_TERM_SHIFT 7
#define BQ25601_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4))
#define BQ25601_REG_CTTC_WATCHDOG_SHIFT 4
#define BQ25601_REG_CTTC_EN_TIMER_MASK BIT(3)
#define BQ25601_REG_CTTC_EN_TIMER_SHIFT 3
#define BQ25601_REG_CTTC_CHG_TIMER_MASK BIT(2)
#define BQ25601_REG_CTTC_CHG_TIMER_SHIFT 2
#define BQ25601_REG_CTTC_TREG_MASK BIT(1)
#define BQ25601_REG_CTTC_TREG_SHIFT 1
#define BQ25601_REG_CTTC_JEITA_ISET_MASK BIT(0)
#define BQ25601_REG_CTTC_JEITA_ISET_SHIFT 0
#define BQ25601_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */
#define BQ25601_REG_ICTRC_OVP_MASK (BIT(7) | BIT(6))
#define BQ25601_REG_ICTRC_OVP_SHIFT 6
#define BQ25601_REG_ICTRC_BOOSTV_MASK (BIT(5) | BIT(4))
#define BQ25601_REG_ICTRC_BOOSTV_SHIFT 4
#define BQ25601_REG_ICTRC_VINDPM_MASK (BIT(3) | BIT(2) | BIT(1) | \
BIT(0))
#define BQ25601_REG_ICTRC_VINDPM_SHIFT 0
#define BQ25601_REG_MOC 0x07 /* Misc. Operation Control */
#define BQ25601_REG_MOC_IINDET_EN_MASK BIT(7)
#define BQ25601_REG_MOC_IINDET_EN_SHIFT 7
#define BQ25601_REG_MOC_TMR2X_EN_MASK BIT(6)
#define BQ25601_REG_MOC_TMR2X_EN_SHIFT 6
#define BQ25601_REG_MOC_BATFET_DISABLE_MASK BIT(5)
#define BQ25601_REG_MOC_BATFET_DISABLE_SHIFT 5
#define BQ25601_REG_MOC_JEITA_VSET_MASK BIT(4)
#define BQ25601_REG_MOC_JEITA_VSET_SHIFT 4
#define BQ25601_REG_MOC_BATFET_DLY_MASK BIT(3)
#define BQ25601_REG_MOC_BATFET_DLY_SHIFT 3
#define BQ25601_REG_MOC_BATFET_RST_EN_MASK BIT(2)
#define BQ25601_REG_MOC_BATFET_RST_EN_SHIFT 2
#define BQ25601_REG_MOC_BAT_TRACK_MASK (BIT(1) | BIT(0))
#define BQ25601_REG_MOC_BAT_TRACK_SHIFT 0
#define BQ25601_REG_SS 0x08 /* System Status */
#define BQ25601_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6) | BIT(5))
#define BQ25601_REG_SS_VBUS_STAT_SHIFT 5
#define BQ25601_REG_SS_CHRG_STAT_MASK (BIT(4) | BIT(3))
#define BQ25601_REG_SS_CHRG_STAT_SHIFT 3
#define BQ25601_REG_SS_PG_STAT_MASK BIT(2)
#define BQ25601_REG_SS_PG_STAT_SHIFT 2
#define BQ25601_REG_SS_THERM_STAT_MASK BIT(1)
#define BQ25601_REG_SS_THERM_STAT_SHIFT 1
#define BQ25601_REG_SS_VSYS_STAT_MASK BIT(0)
#define BQ25601_REG_SS_VSYS_STAT_SHIFT 0
#define BQ25601_REG_F 0x09 /* Fault */
#define BQ25601_REG_F_WATCHDOG_FAULT_MASK BIT(7)
#define BQ25601_REG_F_WATCHDOG_FAULT_SHIFT 7
#define BQ25601_REG_F_BOOST_FAULT_MASK BIT(6)
#define BQ25601_REG_F_BOOST_FAULT_SHIFT 6
#define BQ25601_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4))
#define BQ25601_REG_F_CHRG_FAULT_SHIFT 4
#define BQ25601_REG_F_BAT_FAULT_MASK BIT(3)
#define BQ25601_REG_F_BAT_FAULT_SHIFT 3
#define BQ25601_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0))
#define BQ25601_REG_F_NTC_FAULT_SHIFT 0
#define BQ25601_REG_A 0x0A /* Misc */
#define BQ25601_REG_A_VBUS_GD_MASK BIT(7)
#define BQ25601_REG_A_VBUS_GD_SHIFT 7
#define BQ25601_REG_A_VINDPM_STAT_MASK BIT(6)
#define BQ25601_REG_A_VINDPM_STAT_SHIFT 6
#define BQ25601_REG_A_IINDPM_STAT_MASK BIT(5)
#define BQ25601_REG_A_IINDPM_STAT_SHIFT 5
#define BQ25601_REG_A_TOPOFF_ACTIVE_MASK BIT(3)
#define BQ25601_REG_A_TOPOFF_ACTIVE_SHIFT 3
#define BQ25601_REG_A_ACOV_STAT_MASK BIT(2)
#define BQ25601_REG_A_ACOV_STAT_SHIFT 2
#define BQ25601_REG_A_VINDPM_INT_MASK BIT(1)
#define BQ25601_REG_A_VINDPM_INT_SHIFT 1
#define BQ25601_REG_A_IINDPMINT_STAT_MASK BIT(0)
#define BQ25601_REG_A_IINDPMINT_STAT_SHIFT 0
#define BQ25601_REG_VPRS 0x0B /* Vendor/Part/Revision Status */
#define BQ25601_REG_VPRS_REG_RESET_MASK BIT(7)
#define BQ25601_REG_VPRS_REG_RESET_SHIFT 7
#define BQ25601_REG_VPRS_PN_MASK (BIT(6) | BIT(5) | BIT(4)| BIT(3))
#define BQ25601_REG_VPRS_PN_SHIFT 3
#define BQ25601_REG_VPRS_PN_25601 0x2
#define BQ25601_REG_VPRS_DEV_RES_MASK (BIT(1) | BIT(0))
#define BQ25601_REG_VPRS_DEV_RES_SHIFT 0
/*
* The FAULT register is latched by the bq25601 (except for NTC_FAULT)
* so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
*/
struct bq25601_dev_info {
struct i2c_client *client;
struct device *dev;
struct power_supply charger;
struct power_supply battery;
char model_name[I2C_NAME_SIZE];
kernel_ulong_t model;
unsigned int gpio_int;
unsigned int irq;
struct mutex f_reg_lock;
bool first_time;
bool charger_health_valid;
bool battery_health_valid;
bool battery_status_valid;
u8 f_reg;
u8 ss_reg;
u8 watchdog;
};
/*
* The tables below provide a 2-way mapping for the value that goes in
* the register field and the real-world value that it represents.
* The index of the array is the value that goes in the register; the
* number at that index in the array is the real-world value that it
* represents.
*/
/* REG02[7:2] (ICHG) in uAh */
static const int bq25601_ccc_ichg_values[] = {
512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000,
1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000,
1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000,
2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000,
2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000,
3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000,
3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000,
4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000
};
/* REG04[7:2] (VREG) in uV */
static const int bq25601_cvc_vreg_values[] = {
3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000,
3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000,
3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000,
3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000,
4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000,
4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000,
4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000,
4400000
};
/* REG06[1:0] (TREG) in tenths of degrees Celcius */
static const int bq25601_cttc_treg_values[] = {
600, 800, 1000, 1200
};
/*
* Return the index in 'tbl' of greatest value that is less than or equal to
* 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that
* the values in 'tbl' are sorted from smallest to largest and 'tbl_size'
* is less than 2^8.
*/
static u8 bq25601_find_idx(const int tbl[], int tbl_size, int v)
{
int i;
for (i = 1; i < tbl_size; i++)
if (v < tbl[i])
break;
return i - 1;
}
/* Basic driver I/O routines */
static int bq25601_read(struct bq25601_dev_info *bdi, u8 reg, u8 *data)
{
int ret;
ret = i2c_smbus_read_byte_data(bdi->client, reg);
if (ret < 0)
return ret;
*data = ret;
return 0;
}
static int bq25601_write(struct bq25601_dev_info *bdi, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(bdi->client, reg, data);
}
static int bq25601_read_mask(struct bq25601_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 *data)
{
u8 v;
int ret;
ret = bq25601_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= mask;
v >>= shift;
*data = v;
return 0;
}
static int bq25601_write_mask(struct bq25601_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 data)
{
u8 v;
int ret;
ret = bq25601_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= ~mask;
v |= ((data << shift) & mask);
return bq25601_write(bdi, reg, v);
}
static int bq25601_get_field_val(struct bq25601_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int *val)
{
u8 v;
int ret;
ret = bq25601_read_mask(bdi, reg, mask, shift, &v);
if (ret < 0)
return ret;
v = (v >= tbl_size) ? (tbl_size - 1) : v;
*val = tbl[v];
return 0;
}
static int bq25601_set_field_val(struct bq25601_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int val)
{
u8 idx;
idx = bq25601_find_idx(tbl, tbl_size, val);
return bq25601_write_mask(bdi, reg, mask, shift, idx);
}
#ifdef CONFIG_SYSFS
/*
* There are a numerous options that are configurable on the bq25601
* that go well beyond what the power_supply properties provide access to.
* Provide sysfs access to them so they can be examined and possibly modified
* on the fly. They will be provided for the charger power_supply object only
* and will be prefixed by 'f_' to make them easier to recognize.
*/
#define BQ25601_SYSFS_FIELD(_name, r, f, m, store) \
{ \
.attr = __ATTR(f_##_name, m, bq25601_sysfs_show, store), \
.reg = BQ25601_REG_##r, \
.mask = BQ25601_REG_##r##_##f##_MASK, \
.shift = BQ25601_REG_##r##_##f##_SHIFT, \
}
#define BQ25601_SYSFS_FIELD_RW(_name, r, f) \
BQ25601_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \
bq25601_sysfs_store)
#define BQ25601_SYSFS_FIELD_RO(_name, r, f) \
BQ25601_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL)
static ssize_t bq25601_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t bq25601_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
struct bq25601_sysfs_field_info {
struct device_attribute attr;
u8 reg;
u8 mask;
u8 shift;
};
/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */
#undef SS
static struct bq25601_sysfs_field_info bq25601_sysfs_field_tbl[] = {
/* sysfs name reg field in reg */
BQ25601_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ),
BQ25601_SYSFS_FIELD_RW(en_ichg_mon, ISC, EN_ICHG_MON),
BQ25601_SYSFS_FIELD_RW(iindpm, ISC, IINDPM),
BQ25601_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG),
BQ25601_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN),
BQ25601_SYSFS_FIELD_RW(min_vbat_sel, POC, MIN_VBAT_SEL),
BQ25601_SYSFS_FIELD_RW(boost_lim, CCC, BOOST_LIM),
BQ25601_SYSFS_FIELD_RW(q1_fullon, CCC, Q1_FULLON),
BQ25601_SYSFS_FIELD_RW(ichg, CCC, ICHG),
BQ25601_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG),
BQ25601_SYSFS_FIELD_RW(iterm, PCTCC, ITERM),
BQ25601_SYSFS_FIELD_RW(vreg, CVC, VREG),
BQ25601_SYSFS_FIELD_RW(topoff_timer, CVC, TOPOFF_TIMER),
BQ25601_SYSFS_FIELD_RW(vrechg, CVC, VRECHG),
BQ25601_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM),
BQ25601_SYSFS_FIELD_RW(watchdog, CTTC, WATCHDOG),
BQ25601_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER),
BQ25601_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER),
BQ25601_SYSFS_FIELD_RW(treg, CTTC, TREG),
BQ25601_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET),
BQ25601_SYSFS_FIELD_RW(ovp, ICTRC, OVP),
BQ25601_SYSFS_FIELD_RW(boostv, ICTRC, BOOSTV),
BQ25601_SYSFS_FIELD_RW(vindpm, ICTRC, VINDPM),
BQ25601_SYSFS_FIELD_RW(iindet_en, MOC, IINDET_EN),
BQ25601_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN),
BQ25601_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE),
BQ25601_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET),
BQ25601_SYSFS_FIELD_RW(batfet_dly, MOC, BATFET_DLY),
BQ25601_SYSFS_FIELD_RW(batfet_rst_en, MOC, BATFET_RST_EN),
BQ25601_SYSFS_FIELD_RW(bat_track, MOC, BAT_TRACK),
BQ25601_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT),
BQ25601_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT),
BQ25601_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT),
BQ25601_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT),
BQ25601_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT),
BQ25601_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT),
BQ25601_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT),
BQ25601_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT),
BQ25601_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT),
BQ25601_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT),
BQ25601_SYSFS_FIELD_RO(vbus_gd, A, VBUS_GD),
BQ25601_SYSFS_FIELD_RO(vindpm_stat, A, VINDPM_STAT),
BQ25601_SYSFS_FIELD_RO(iindpm_stat, A, IINDPM_STAT),
BQ25601_SYSFS_FIELD_RO(topoff_active, A, TOPOFF_ACTIVE),
BQ25601_SYSFS_FIELD_RO(acov_stat, A, ACOV_STAT),
BQ25601_SYSFS_FIELD_RO(vindpm_int, A, VINDPM_INT),
BQ25601_SYSFS_FIELD_RO(iindpmint_stat, A, IINDPMINT_STAT),
BQ25601_SYSFS_FIELD_RO(reg_reset, VPRS, REG_RESET),
BQ25601_SYSFS_FIELD_RO(pn, VPRS, PN),
BQ25601_SYSFS_FIELD_RO(dev_res, VPRS, DEV_RES),
};
static struct attribute *
bq25601_sysfs_attrs[ARRAY_SIZE(bq25601_sysfs_field_tbl) + 1];
static const struct attribute_group bq25601_sysfs_attr_group = {
.attrs = bq25601_sysfs_attrs,
};
static void bq25601_sysfs_init_attrs(void)
{
int i, limit = ARRAY_SIZE(bq25601_sysfs_field_tbl);
for (i = 0; i < limit; i++)
bq25601_sysfs_attrs[i] = &bq25601_sysfs_field_tbl[i].attr.attr;
bq25601_sysfs_attrs[limit] = NULL; /* Has additional entry for this */
}
static struct bq25601_sysfs_field_info *bq25601_sysfs_field_lookup(
const char *name)
{
int i, limit = ARRAY_SIZE(bq25601_sysfs_field_tbl);
for (i = 0; i < limit; i++)
if (!strcmp(name, bq25601_sysfs_field_tbl[i].attr.attr.name))
break;
if (i >= limit)
return NULL;
return &bq25601_sysfs_field_tbl[i];
}
static ssize_t bq25601_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, charger);
struct bq25601_sysfs_field_info *info;
int ret;
u8 v;
info = bq25601_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = bq25601_read_mask(bdi, info->reg, info->mask, info->shift, &v);
if (ret)
return ret;
return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
}
static ssize_t bq25601_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, charger);
struct bq25601_sysfs_field_info *info;
int ret;
u8 v;
info = bq25601_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = kstrtou8(buf, 0, &v);
if (ret < 0)
return ret;
ret = bq25601_write_mask(bdi, info->reg, info->mask, info->shift, v);
if (ret)
return ret;
return count;
}
static int bq25601_sysfs_create_group(struct bq25601_dev_info *bdi)
{
bq25601_sysfs_init_attrs();
return sysfs_create_group(&bdi->charger.dev->kobj,
&bq25601_sysfs_attr_group);
}
static void bq25601_sysfs_remove_group(struct bq25601_dev_info *bdi)
{
sysfs_remove_group(&bdi->charger.dev->kobj, &bq25601_sysfs_attr_group);
}
#else
static int bq25601_sysfs_create_group(struct bq25601_dev_info *bdi)
{
return 0;
}
static inline void bq25601_sysfs_remove_group(struct bq25601_dev_info *bdi) {}
#endif
/*
* According to the "Host Mode and default Mode" section of the
* manual, a write to any register causes the bq25601 to switch
* from default mode to host mode. It will switch back to default
* mode after a WDT timeout unless the WDT is turned off as well.
* So, by simply turning off the WDT, we accomplish both with the
* same write.
*/
static int bq25601_set_mode_host(struct bq25601_dev_info *bdi)
{
int ret;
u8 v;
ret = bq25601_read(bdi, BQ25601_REG_CTTC, &v);
if (ret < 0)
return ret;
bdi->watchdog = ((v & BQ25601_REG_CTTC_WATCHDOG_MASK) >>
BQ25601_REG_CTTC_WATCHDOG_SHIFT);
v &= ~BQ25601_REG_CTTC_WATCHDOG_MASK;
return bq25601_write(bdi, BQ25601_REG_CTTC, v);
}
static int bq25601_register_reset(struct bq25601_dev_info *bdi)
{
int ret, limit = 100;
u8 v;
/* Reset the registers */
ret = bq25601_write_mask(bdi, BQ25601_REG_VPRS,
BQ25601_REG_VPRS_REG_RESET_MASK,
BQ25601_REG_VPRS_REG_RESET_SHIFT,
0x1);
if (ret < 0)
return ret;
/* Reset bit will be cleared by hardware so poll until it is */
do {
ret = bq25601_read_mask(bdi, BQ25601_REG_VPRS,
BQ25601_REG_VPRS_REG_RESET_MASK,
BQ25601_REG_VPRS_REG_RESET_SHIFT,
&v);
if (ret < 0)
return ret;
if (!v)
break;
udelay(10);
} while (--limit);
if (!limit)
return -EIO;
return 0;
}
/* Charger power supply property routines */
static int bq25601_charger_get_charge_type(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int type, ret;
ret = bq25601_read_mask(bdi, BQ25601_REG_POC,
BQ25601_REG_POC_CHG_CONFIG_MASK,
BQ25601_REG_POC_CHG_CONFIG_SHIFT,
&v);
if (ret < 0)
return ret;
/* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */
if (!v) {
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
} else {
/* ret = bq25601_read_mask(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_FORCE_20PCT_MASK,
BQ25601_REG_CCC_FORCE_20PCT_SHIFT,
&v);
if (ret < 0)
return ret;*/
type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
POWER_SUPPLY_CHARGE_TYPE_FAST;
}
val->intval = type;
return 0;
}
static int bq25601_charger_set_charge_type(struct bq25601_dev_info *bdi,
const union power_supply_propval *val)
{
u8 chg_config, en_term;
int ret;
/*
* According to the "Termination when REG02[0] = 1" section of
* the bq25601 manual, the trickle charge could be less than the
* termination current so it recommends turning off the termination
* function.
*
* Note: AFAICT from the datasheet, the user will have to manually
* turn off the charging when in 20% mode. If its not turned off,
* there could be battery damage. So, use this mode at your own risk.
*/
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_NONE:
chg_config = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
chg_config = 0x1;
//force_20pct = 0x1;
en_term = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_FAST:
chg_config = 0x1;
//force_20pct = 0x0;
en_term = 0x1;
break;
default:
return -EINVAL;
}
if (chg_config) { /* Enabling the charger */
/* ret = bq25601_write_mask(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_FORCE_20PCT_MASK,
BQ25601_REG_CCC_FORCE_20PCT_SHIFT,
force_20pct);
if (ret < 0)
return ret;*/
ret = bq25601_write_mask(bdi, BQ25601_REG_CTTC,
BQ25601_REG_CTTC_EN_TERM_MASK,
BQ25601_REG_CTTC_EN_TERM_SHIFT,
en_term);
if (ret < 0)
return ret;
}
return bq25601_write_mask(bdi, BQ25601_REG_POC,
BQ25601_REG_POC_CHG_CONFIG_MASK,
BQ25601_REG_POC_CHG_CONFIG_SHIFT, chg_config);
}
static int bq25601_charger_get_health(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->charger_health_valid) {
v = bdi->f_reg;
bdi->charger_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq25601_read(bdi, BQ25601_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ25601_REG_F_BOOST_FAULT_MASK) {
/*
* This could be over-current or over-voltage but there's
* no way to tell which. Return 'OVERVOLTAGE' since there
* isn't an 'OVERCURRENT' value defined that we can return
* even if it was over-current.
*/
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ25601_REG_F_CHRG_FAULT_MASK;
v >>= BQ25601_REG_F_CHRG_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */
/*
* This could be over-voltage or under-voltage
* and there's no way to tell which. Instead
* of looking foolish and returning 'OVERVOLTAGE'
* when its really under-voltage, just return
* 'UNSPEC_FAILURE'.
*/
health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
case 0x2: /* Thermal Shutdown */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
case 0x3: /* Charge Safety Timer Expiration */
health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq25601_charger_get_online(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int ret;
ret = bq25601_read_mask(bdi, BQ25601_REG_SS,
BQ25601_REG_SS_PG_STAT_MASK,
BQ25601_REG_SS_PG_STAT_SHIFT, &v);
if (ret < 0)
return ret;
val->intval = v;
return 0;
}
static int bq25601_charger_get_current(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
//u8 v;
int curr, ret;
ret = bq25601_get_field_val(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_ICHG_MASK, BQ25601_REG_CCC_ICHG_SHIFT,
bq25601_ccc_ichg_values,
ARRAY_SIZE(bq25601_ccc_ichg_values), &curr);
if (ret < 0)
return ret;
/* ret = bq25601_read_mask(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_FORCE_20PCT_MASK,
BQ25601_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;*/
/* If FORCE_20PCT is enabled, then current is 20% of ICHG value */
/*if (v)
curr /= 5;*/
val->intval = curr;
return 0;
}
static int bq25601_charger_get_current_max(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq25601_ccc_ichg_values) - 1;
val->intval = bq25601_ccc_ichg_values[idx];
return 0;
}
static int bq25601_charger_set_current(struct bq25601_dev_info *bdi,
const union power_supply_propval *val)
{
// u8 v;
// int ret;
int curr = val->intval;
/* ret = bq25601_read_mask(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_FORCE_20PCT_MASK,
BQ25601_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;*/
/* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */
/*if (v)
curr *= 5;*/
return bq25601_set_field_val(bdi, BQ25601_REG_CCC,
BQ25601_REG_CCC_ICHG_MASK, BQ25601_REG_CCC_ICHG_SHIFT,
bq25601_ccc_ichg_values,
ARRAY_SIZE(bq25601_ccc_ichg_values), curr);
}
static int bq25601_charger_get_voltage(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
int voltage, ret;
ret = bq25601_get_field_val(bdi, BQ25601_REG_CVC,
BQ25601_REG_CVC_VREG_MASK, BQ25601_REG_CVC_VREG_SHIFT,
bq25601_cvc_vreg_values,
ARRAY_SIZE(bq25601_cvc_vreg_values), &voltage);
if (ret < 0)
return ret;
val->intval = voltage;
return 0;
}
static int bq25601_charger_get_voltage_max(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq25601_cvc_vreg_values) - 1;
val->intval = bq25601_cvc_vreg_values[idx];
return 0;
}
static int bq25601_charger_set_voltage(struct bq25601_dev_info *bdi,
const union power_supply_propval *val)
{
return bq25601_set_field_val(bdi, BQ25601_REG_CVC,
BQ25601_REG_CVC_VREG_MASK, BQ25601_REG_CVC_VREG_SHIFT,
bq25601_cvc_vreg_values,
ARRAY_SIZE(bq25601_cvc_vreg_values), val->intval);
}
static int bq25601_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq25601_charger_get_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq25601_charger_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq25601_charger_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25601_charger_get_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
ret = bq25601_charger_get_current_max(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq25601_charger_get_voltage(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
ret = bq25601_charger_get_voltage_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bdi->model_name;
ret = 0;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ25601_MANUFACTURER;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq25601_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq25601_charger_set_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25601_charger_set_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq25601_charger_set_voltage(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq25601_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property bq25601_charger_properties[] = {
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static char *bq25601_charger_supplied_to[] = {
"main-battery",
};
static void bq25601_charger_init(struct power_supply *charger)
{
charger->name = "bq25601-charger";
charger->type = POWER_SUPPLY_TYPE_USB;
charger->properties = bq25601_charger_properties;
charger->num_properties = ARRAY_SIZE(bq25601_charger_properties);
charger->supplied_to = bq25601_charger_supplied_to;
charger->num_supplies = ARRAY_SIZE(bq25601_charger_supplied_to);
charger->get_property = bq25601_charger_get_property;
charger->set_property = bq25601_charger_set_property;
charger->property_is_writeable = bq25601_charger_property_is_writeable;
}
/* Battery power supply property routines */
static int bq25601_battery_get_status(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 ss_reg, chrg_fault;
int status, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_status_valid) {
chrg_fault = bdi->f_reg;
bdi->battery_status_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq25601_read(bdi, BQ25601_REG_F, &chrg_fault);
if (ret < 0)
return ret;
}
chrg_fault &= BQ25601_REG_F_CHRG_FAULT_MASK;
chrg_fault >>= BQ25601_REG_F_CHRG_FAULT_SHIFT;
ret = bq25601_read(bdi, BQ25601_REG_SS, &ss_reg);
if (ret < 0)
return ret;
/*
* The battery must be discharging when any of these are true:
* - there is no good power source;
* - there is a charge fault.
* Could also be discharging when in "supplement mode" but
* there is no way to tell when its in that mode.
*/
if (!(ss_reg & BQ25601_REG_SS_PG_STAT_MASK) || chrg_fault) {
status = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
ss_reg &= BQ25601_REG_SS_CHRG_STAT_MASK;
ss_reg >>= BQ25601_REG_SS_CHRG_STAT_SHIFT;
switch (ss_reg) {
case 0x0: /* Not Charging */
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 0x1: /* Pre-charge */
case 0x2: /* Fast Charging */
status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 0x3: /* Charge Termination Done */
status = POWER_SUPPLY_STATUS_FULL;
break;
default:
ret = -EIO;
}
}
if (!ret)
val->intval = status;
return ret;
}
static int bq25601_battery_get_health(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_health_valid) {
v = bdi->f_reg;
bdi->battery_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq25601_read(bdi, BQ25601_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ25601_REG_F_BAT_FAULT_MASK) {
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ25601_REG_F_NTC_FAULT_MASK;
v >>= BQ25601_REG_F_NTC_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* TS1 Cold */
case 0x3: /* TS2 Cold */
case 0x5: /* Both Cold */
health = POWER_SUPPLY_HEALTH_COLD;
break;
case 0x2: /* TS1 Hot */
case 0x4: /* TS2 Hot */
case 0x6: /* Both Hot */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq25601_battery_get_online(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
u8 batfet_disable;
int ret;
ret = bq25601_read_mask(bdi, BQ25601_REG_MOC,
BQ25601_REG_MOC_BATFET_DISABLE_MASK,
BQ25601_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable);
if (ret < 0)
return ret;
val->intval = !batfet_disable;
return 0;
}
static int bq25601_battery_set_online(struct bq25601_dev_info *bdi,
const union power_supply_propval *val)
{
return bq25601_write_mask(bdi, BQ25601_REG_MOC,
BQ25601_REG_MOC_BATFET_DISABLE_MASK,
BQ25601_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval);
}
static int bq25601_battery_get_temp_alert_max(struct bq25601_dev_info *bdi,
union power_supply_propval *val)
{
int temp, ret;
ret = bq25601_get_field_val(bdi, BQ25601_REG_CTTC,
BQ25601_REG_CTTC_TREG_MASK,
BQ25601_REG_CTTC_TREG_SHIFT,
bq25601_cttc_treg_values,
ARRAY_SIZE(bq25601_cttc_treg_values), &temp);
if (ret < 0)
return ret;
val->intval = temp;
return 0;
}
static int bq25601_battery_set_temp_alert_max(struct bq25601_dev_info *bdi,
const union power_supply_propval *val)
{
return bq25601_set_field_val(bdi, BQ25601_REG_CTTC,
BQ25601_REG_CTTC_TREG_MASK,
BQ25601_REG_CTTC_TREG_SHIFT,
bq25601_cttc_treg_values,
ARRAY_SIZE(bq25601_cttc_treg_values), val->intval);
}
static int bq25601_battery_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = bq25601_battery_get_status(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq25601_battery_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq25601_battery_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
/* Could be Li-on or Li-polymer but no way to tell which */
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
ret = 0;
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq25601_battery_get_temp_alert_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq25601_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq25601_dev_info *bdi =
container_of(psy, struct bq25601_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_put_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = bq25601_battery_set_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq25601_battery_set_temp_alert_max(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq25601_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property bq25601_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_SCOPE,
};
static void bq25601_battery_init(struct power_supply *battery)
{
battery->name = "bq25601-battery";
battery->type = POWER_SUPPLY_TYPE_BATTERY;
battery->properties = bq25601_battery_properties;
battery->num_properties = ARRAY_SIZE(bq25601_battery_properties);
battery->get_property = bq25601_battery_get_property;
battery->set_property = bq25601_battery_set_property;
battery->property_is_writeable = bq25601_battery_property_is_writeable;
}
static irqreturn_t bq25601_irq_handler_thread(int irq, void *data)
{
struct bq25601_dev_info *bdi = data;
bool alert_userspace = false;
u8 ss_reg, f_reg;
int ret;
pm_runtime_get_sync(bdi->dev);
ret = bq25601_read(bdi, BQ25601_REG_SS, &ss_reg);
if (ret < 0) {
dev_err(bdi->dev, "Can't read SS reg: %d\n", ret);
goto out;
}
if (ss_reg != bdi->ss_reg) {
/*
* The device is in host mode so when PG_STAT goes from 1->0
* (i.e., power removed) HIZ needs to be disabled.
*/
if ((bdi->ss_reg & BQ25601_REG_SS_PG_STAT_MASK) &&
!(ss_reg & BQ25601_REG_SS_PG_STAT_MASK)) {
ret = bq25601_write_mask(bdi, BQ25601_REG_ISC,
BQ25601_REG_ISC_EN_HIZ_MASK,
BQ25601_REG_ISC_EN_HIZ_SHIFT,
0);
if (ret < 0)
dev_err(bdi->dev, "Can't access ISC reg: %d\n",
ret);
}
bdi->ss_reg = ss_reg;
alert_userspace = true;
}
mutex_lock(&bdi->f_reg_lock);
ret = bq25601_read(bdi, BQ25601_REG_F, &f_reg);
if (ret < 0) {
mutex_unlock(&bdi->f_reg_lock);
dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out;
}
if (f_reg != bdi->f_reg) {
bdi->f_reg = f_reg;
bdi->charger_health_valid = true;
bdi->battery_health_valid = true;
bdi->battery_status_valid = true;
alert_userspace = true;
}
mutex_unlock(&bdi->f_reg_lock);
/*
* Sometimes bq25601 gives a steady trickle of interrupts even
* though the watchdog timer is turned off and neither the STATUS
* nor FAULT registers have changed. Weed out these sprurious
* interrupts so userspace isn't alerted for no reason.
* In addition, the chip always generates an interrupt after
* register reset so we should ignore that one (the very first
* interrupt received).
*/
if (alert_userspace && !bdi->first_time) {
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
bdi->first_time = false;
}
out:
pm_runtime_put_sync(bdi->dev);
dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
return IRQ_HANDLED;
}
static int bq25601_hw_init(struct bq25601_dev_info *bdi)
{
u8 v;
int ret;
pm_runtime_get_sync(bdi->dev);
/* First check that the device really is what its supposed to be */
ret = bq25601_read_mask(bdi, BQ25601_REG_VPRS,
BQ25601_REG_VPRS_PN_MASK,
BQ25601_REG_VPRS_PN_SHIFT,
&v);
if (ret < 0)
goto out;
if (v != bdi->model) {
ret = -ENODEV;
goto out;
}
ret = bq25601_register_reset(bdi);
if (ret < 0)
goto out;
ret = bq25601_set_mode_host(bdi);
out:
pm_runtime_put_sync(bdi->dev);
return ret;
}
#ifdef CONFIG_OF
static int bq25601_setup_dt(struct bq25601_dev_info *bdi)
{
bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
if (bdi->irq <= 0)
return -1;
return 0;
}
#else
static int bq25601_setup_dt(struct bq25601_dev_info *bdi)
{
return -1;
}
#endif
static int bq25601_setup_pdata(struct bq25601_dev_info *bdi,
struct bq25601_platform_data *pdata)
{
int ret;
if (!gpio_is_valid(pdata->gpio_int))
return -1;
ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
if (ret < 0)
return -1;
ret = gpio_direction_input(pdata->gpio_int);
if (ret < 0)
goto out;
bdi->irq = gpio_to_irq(pdata->gpio_int);
if (!bdi->irq)
goto out;
bdi->gpio_int = pdata->gpio_int;
return 0;
out:
gpio_free(pdata->gpio_int);
return -1;
}
static int bq25601_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
struct bq25601_platform_data *pdata = client->dev.platform_data;
struct bq25601_dev_info *bdi;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
if (!bdi) {
dev_err(dev, "Can't alloc bdi struct\n");
return -ENOMEM;
}
bdi->client = client;
bdi->dev = dev;
bdi->model = id->driver_data;
strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
mutex_init(&bdi->f_reg_lock);
bdi->first_time = true;
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
i2c_set_clientdata(client, bdi);
if (dev->of_node)
ret = bq25601_setup_dt(bdi);
else if (pdata)
ret = bq25601_setup_pdata(bdi, pdata);
else
goto no_irq;
if (ret) {
dev_err(&client->dev, "Can't get irq info\n");
return -EINVAL;
}
ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
bq25601_irq_handler_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bq25601-charger", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
goto out1;
}
no_irq:
pm_runtime_enable(dev);
pm_runtime_resume(dev);
ret = bq25601_hw_init(bdi);
if (ret < 0) {
dev_err(dev, "Hardware init failed\n");
goto out2;
}
bq25601_charger_init(&bdi->charger);
ret = power_supply_register(dev, &bdi->charger);
if (ret) {
dev_err(dev, "Can't register charger\n");
goto out2;
}
bq25601_battery_init(&bdi->battery);
ret = power_supply_register(dev, &bdi->battery);
if (ret) {
dev_err(dev, "Can't register battery\n");
goto out3;
}
ret = bq25601_sysfs_create_group(bdi);
if (ret) {
dev_err(dev, "Can't create sysfs entries\n");
goto out4;
}
return 0;
out4:
power_supply_unregister(&bdi->battery);
out3:
power_supply_unregister(&bdi->charger);
out2:
pm_runtime_disable(dev);
out1:
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return ret;
}
static int bq25601_remove(struct i2c_client *client)
{
struct bq25601_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq25601_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
bq25601_sysfs_remove_group(bdi);
power_supply_unregister(&bdi->battery);
power_supply_unregister(&bdi->charger);
pm_runtime_disable(bdi->dev);
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int bq25601_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq25601_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq25601_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
return 0;
}
static int bq25601_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq25601_dev_info *bdi = i2c_get_clientdata(client);
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
pm_runtime_get_sync(bdi->dev);
bq25601_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
/* Things may have changed while suspended so alert upper layer */
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(bq25601_pm_ops, bq25601_pm_suspend, bq25601_pm_resume);
/*
* Only support the bq25601 right now. The bq24192, bq24192i, and bq24193
* are similar but not identical so the driver needs to be extended to
* support them.
*/
static const struct i2c_device_id bq25601_i2c_ids[] = {
{ "bq25601", BQ25601_REG_VPRS_PN_24190 },
{ },
};
#ifdef CONFIG_OF
static const struct of_device_id bq25601_of_match[] = {
{ .compatible = "ti,bq25601", },
{ },
};
MODULE_DEVICE_TABLE(of, bq25601_of_match);
#else
static const struct of_device_id bq25601_of_match[] = {
{ },
};
#endif
static struct i2c_driver bq25601_driver = {
.probe = bq25601_probe,
.remove = bq25601_remove,
.id_table = bq25601_i2c_ids,
.driver = {
.name = "bq25601-charger",
.owner = THIS_MODULE,
.pm = &bq25601_pm_ops,
.of_match_table = of_match_ptr(bq25601_of_match),
},
};
module_i2c_driver(bq25601_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
MODULE_ALIAS("i2c:bq25601-charger");
MODULE_DESCRIPTION("TI BQ25601 Charger Driver");
/*
* BQ2560x battery charging driver
*
* Copyright (C) 2013 Texas Instruments
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _LINUX_BQ2560X_I2C_H
#define _LINUX_BQ2560X_I2C_H
#include <linux/power_supply.h>
struct bq2560x_charge_param {
int vlim;
int ilim;
int ichg;
int vreg;
};
enum stat_ctrl {
STAT_CTRL_STAT,
STAT_CTRL_ICHG,
STAT_CTRL_INDPM,
STAT_CTRL_DISABLE,
};
enum vboost {
BOOSTV_4850 = 4850,
BOOSTV_5000 = 5000,
BOOSTV_5150 = 5150,
BOOSTV_5300 = 5300,
};
enum iboost {
BOOSTI_500 = 500,
BOOSTI_1200 = 1200,
};
enum vac_ovp {
VAC_OVP_5500 = 5500,
VAC_OVP_6200 = 6200,
VAC_OVP_10500 = 10500,
VAC_OVP_14300 = 14300,
};
struct bq2560x_platform_data {
struct bq2560x_charge_param usb;
struct bq2560x_charge_param ta;
int iprechg;
int iterm;
enum stat_ctrl statctrl;
enum vboost boostv; // options are 4850,
enum iboost boosti; // options are 500mA, 1200mA
enum vac_ovp vac_ovp;
};
#endif
\ No newline at end of file
/*
* BQ2560x battery charging driver
*
* Copyright (C) 2013 Texas Instruments
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#define pr_fmt(fmt) "bq2560x: %s: " fmt, __func__
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/err.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#include <linux/debugfs.h>
#include <linux/bitops.h>
#include <linux/math64.h>
#include <linux/alarmtimer.h>
#include "bq2560x_reg.h"
#include "bq2560x.h"
#if 1
#undef pr_debug
#define pr_debug pr_err
#undef pr_info
#define pr_info pr_err
#undef dev_dbg
#define dev_dbg dev_err
#else
#undef pr_info
#define pr_info pr_debug
#endif
enum bq2560x_vbus_type {
BQ2560X_VBUS_NONE = REG08_VBUS_TYPE_NONE,
BQ2560X_VBUS_USB = REG08_VBUS_TYPE_USB,
BQ2560X_VBUS_ADAPTER = REG08_VBUS_TYPE_ADAPTER,
BQ2560X_VBUS_OTG = REG08_VBUS_TYPE_OTG,
};
enum bq2560x_part_no {
BQ25600 = 0x00,
BQ25601 = 0x02,
};
enum {
USER = BIT(0),
JEITA = BIT(1),
BATT_FC = BIT(2),
BATT_PRES = BIT(3),
};
enum wakeup_src {
WAKEUP_SRC_MONITOR = 0,
WAKEUP_SRC_JEITA,
WAKEUP_SRC_MAX,
};
#define WAKEUP_SRC_MASK (~(~0 << WAKEUP_SRC_MAX))
struct bq2560x_wakeup_source {
struct wakeup_source source;
unsigned long enabled_bitmap;
spinlock_t ws_lock;
};
enum bq2560x_charge_state {
CHARGE_STATE_IDLE = REG08_CHRG_STAT_IDLE,
CHARGE_STATE_PRECHG = REG08_CHRG_STAT_PRECHG,
CHARGE_STATE_FASTCHG = REG08_CHRG_STAT_FASTCHG,
CHARGE_STATE_CHGDONE = REG08_CHRG_STAT_CHGDONE,
};
struct bq2560x_otg_regulator {
struct regulator_desc rdesc;
struct regulator_dev *rdev;
};
struct bq2560x {
struct device *dev;
struct i2c_client *client;
enum bq2560x_part_no part_no;
int revision;
int gpio_ce;
int vbus_type;
int status;
struct mutex data_lock;
struct mutex i2c_rw_lock;
struct mutex profile_change_lock;
struct mutex charging_disable_lock;
struct mutex irq_complete;
struct bq2560x_wakeup_source bq2560x_ws;
bool irq_waiting;
bool irq_disabled;
bool resume_completed;
bool batt_present;
bool usb_present;
bool batt_full;
bool charge_enabled;/* Register bit status */
bool otg_enabled;
bool batfet_enabled;
bool in_hiz;
bool dis_safety;
bool vindpm_triggered;
bool iindpm_triggered;
bool in_therm_regulation;
bool in_vsys_regulation;
bool power_good;
bool vbus_good;
bool topoff_active;
bool acov_triggered;
/* if use software jeita in case of NTC is connected to gauge */
bool software_jeita_supported;
bool jeita_active;
bool batt_hot;
bool batt_cold;
bool batt_warm;
bool batt_cool;
int batt_hot_degc;
int batt_warm_degc;
int batt_cool_degc;
int batt_cold_degc;
int hot_temp_hysteresis;
int cold_temp_hysteresis;
int batt_cool_ma;
int batt_warm_ma;
int batt_cool_mv;
int batt_warm_mv;
int batt_temp;
int jeita_ma;
int jeita_mv;
unsigned int thermal_levels;
unsigned int therm_lvl_sel;
unsigned int *thermal_mitigation;
int usb_psy_ma;
int charge_state;
int charging_disabled_status;
int fault_status;
int skip_writes;
int skip_reads;
struct bq2560x_platform_data* platform_data;
struct delayed_work discharge_jeita_work; /*normal no charge mode*/
struct delayed_work charge_jeita_work; /*charge mode jeita work*/
struct alarm jeita_alarm;
struct dentry *debug_root;
struct bq2560x_otg_regulator otg_vreg;
struct power_supply *usb_psy;
struct power_supply *bms_psy;
struct power_supply batt_psy;
};
static int BatteryTestStatus_enable = 0;
static int __bq2560x_read_reg(struct bq2560x* bq, u8 reg, u8 *data)
{
s32 ret;
ret = i2c_smbus_read_byte_data(bq->client, reg);
if (ret < 0) {
pr_err("i2c read fail: can't read from reg 0x%02X\n", reg);
return ret;
}
*data = (u8)ret;
return 0;
}
static int __bq2560x_write_reg(struct bq2560x* bq, int reg, u8 val)
{
s32 ret;
ret = i2c_smbus_write_byte_data(bq->client, reg, val);
if (ret < 0) {
pr_err("i2c write fail: can't write 0x%02X to reg 0x%02X: %d\n",
val, reg, ret);
return ret;
}
return 0;
}
static int bq2560x_read_byte(struct bq2560x *bq, u8 *data, u8 reg)
{
int ret;
if (bq->skip_reads) {
*data = 0;
return 0;
}
mutex_lock(&bq->i2c_rw_lock);
ret = __bq2560x_read_reg(bq, reg, data);
mutex_unlock(&bq->i2c_rw_lock);
return ret;
}
static int bq2560x_write_byte(struct bq2560x *bq, u8 reg, u8 data)
{
int ret;
if (bq->skip_writes) {
return 0;
}
mutex_lock(&bq->i2c_rw_lock);
ret = __bq2560x_write_reg(bq, reg, data);
mutex_unlock(&bq->i2c_rw_lock);
if (ret) {
pr_err("Failed: reg=%02X, ret=%d\n", reg, ret);
}
return ret;
}
static int bq2560x_update_bits(struct bq2560x *bq, u8 reg,
u8 mask, u8 data)
{
int ret;
u8 tmp;
if (bq->skip_reads || bq->skip_writes)
return 0;
mutex_lock(&bq->i2c_rw_lock);
ret = __bq2560x_read_reg(bq, reg, &tmp);
if (ret) {
pr_err("Failed: reg=%02X, ret=%d\n", reg, ret);
goto out;
}
tmp &= ~mask;
tmp |= data & mask;
ret = __bq2560x_write_reg(bq, reg, tmp);
if (ret) {
pr_err("Failed: reg=%02X, ret=%d\n", reg, ret);
}
out:
mutex_unlock(&bq->i2c_rw_lock);
return ret;
}
static void bq2560x_stay_awake(struct bq2560x_wakeup_source *source,
enum wakeup_src wk_src)
{
unsigned long flags;
spin_lock_irqsave(&source->ws_lock, flags);
if (!__test_and_set_bit(wk_src, &source->enabled_bitmap)) {
__pm_stay_awake(&source->source);
pr_debug("enabled source %s, wakeup_src %d\n",
source->source.name, wk_src);
}
spin_unlock_irqrestore(&source->ws_lock, flags);
}
static void bq2560x_relax(struct bq2560x_wakeup_source *source,
enum wakeup_src wk_src)
{
unsigned long flags;
spin_lock_irqsave(&source->ws_lock, flags);
if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) &&
!(source->enabled_bitmap & WAKEUP_SRC_MASK)) {
__pm_relax(&source->source);
pr_debug("disabled source %s\n", source->source.name);
}
spin_unlock_irqrestore(&source->ws_lock, flags);
pr_debug("relax source %s, wakeup_src %d\n",
source->source.name, wk_src);
}
static void bq2560x_wakeup_src_init(struct bq2560x *bq)
{
spin_lock_init(&bq->bq2560x_ws.ws_lock);
wakeup_source_init(&bq->bq2560x_ws.source, "bq2560x");
}
static int bq2560x_enable_otg(struct bq2560x *bq)
{
u8 val = REG01_OTG_ENABLE << REG01_OTG_CONFIG_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_01,
REG01_OTG_CONFIG_MASK, val);
}
static int bq2560x_disable_otg(struct bq2560x *bq)
{
u8 val = REG01_OTG_DISABLE << REG01_OTG_CONFIG_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_01,
REG01_OTG_CONFIG_MASK, val);
}
static int bq2560x_enable_charger(struct bq2560x *bq)
{
int ret;
u8 val = REG01_CHG_ENABLE << REG01_CHG_CONFIG_SHIFT;
ret = bq2560x_update_bits(bq, BQ2560X_REG_01, REG01_CHG_CONFIG_MASK, val);
return ret;
}
static int bq2560x_disable_charger(struct bq2560x *bq)
{
int ret;
u8 val = REG01_CHG_DISABLE << REG01_CHG_CONFIG_SHIFT;
ret = bq2560x_update_bits(bq, BQ2560X_REG_01, REG01_CHG_CONFIG_MASK, val);
return ret;
}
int bq2560x_set_chargecurrent(struct bq2560x *bq, int curr)
{
u8 ichg;
if (curr < REG02_ICHG_BASE)
curr = REG02_ICHG_BASE;
ichg = (curr - REG02_ICHG_BASE)/REG02_ICHG_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_02, REG02_ICHG_MASK,
ichg << REG02_ICHG_SHIFT);
}
int bq2560x_set_term_current(struct bq2560x *bq, int curr)
{
u8 iterm;
if (curr < REG03_ITERM_BASE)
curr = REG03_ITERM_BASE;
iterm = (curr - REG03_ITERM_BASE) / REG03_ITERM_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_03, REG03_ITERM_MASK,
iterm << REG03_ITERM_SHIFT);
}
int bq2560x_set_prechg_current(struct bq2560x *bq, int curr)
{
u8 iprechg;
if (curr < REG03_IPRECHG_BASE)
curr = REG03_IPRECHG_BASE;
iprechg = (curr - REG03_IPRECHG_BASE) / REG03_IPRECHG_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_03, REG03_IPRECHG_MASK,
iprechg << REG03_IPRECHG_SHIFT);
}
int bq2560x_set_chargevolt(struct bq2560x *bq, int volt)
{
u8 val;
if (volt < REG04_VREG_BASE)
volt = REG04_VREG_BASE;
val = (volt - REG04_VREG_BASE)/REG04_VREG_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_04, REG04_VREG_MASK,
val << REG04_VREG_SHIFT);
}
int bq2560x_set_input_volt_limit(struct bq2560x *bq, int volt)
{
u8 val;
if (volt < REG06_VINDPM_BASE)
volt = REG06_VINDPM_BASE;
val = (volt - REG06_VINDPM_BASE) / REG06_VINDPM_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_06, REG06_VINDPM_MASK,
val << REG06_VINDPM_SHIFT);
}
int bq2560x_set_input_current_limit(struct bq2560x *bq, int curr)
{
u8 val;
if (curr < REG00_IINLIM_BASE)
curr = REG00_IINLIM_BASE;
val = (curr - REG00_IINLIM_BASE) / REG00_IINLIM_LSB;
return bq2560x_update_bits(bq, BQ2560X_REG_00, REG00_IINLIM_MASK,
val << REG00_IINLIM_SHIFT);
}
int bq2560x_set_watchdog_timer(struct bq2560x *bq, u8 timeout)
{
u8 temp;
temp = (u8)(((timeout - REG05_WDT_BASE) / REG05_WDT_LSB) << REG05_WDT_SHIFT);
return bq2560x_update_bits(bq, BQ2560X_REG_05, REG05_WDT_MASK, temp);
}
EXPORT_SYMBOL_GPL(bq2560x_set_watchdog_timer);
int bq2560x_disable_watchdog_timer(struct bq2560x *bq)
{
u8 val = REG05_WDT_DISABLE << REG05_WDT_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_05, REG05_WDT_MASK, val);
}
EXPORT_SYMBOL_GPL(bq2560x_disable_watchdog_timer);
int bq2560x_reset_watchdog_timer(struct bq2560x *bq)
{
u8 val = REG01_WDT_RESET << REG01_WDT_RESET_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_01, REG01_WDT_RESET_MASK, val);
}
EXPORT_SYMBOL_GPL(bq2560x_reset_watchdog_timer);
int bq2560x_reset_chip(struct bq2560x *bq)
{
int ret;
u8 val = REG0B_REG_RESET << REG0B_REG_RESET_SHIFT;
ret = bq2560x_update_bits(bq, BQ2560X_REG_0B, REG0B_REG_RESET_MASK, val);
return ret;
}
EXPORT_SYMBOL_GPL(bq2560x_reset_chip);
int bq2560x_enter_hiz_mode(struct bq2560x *bq)
{
u8 val = REG00_HIZ_ENABLE << REG00_ENHIZ_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_00, REG00_ENHIZ_MASK, val);
}
EXPORT_SYMBOL_GPL(bq2560x_enter_hiz_mode);
int bq2560x_exit_hiz_mode(struct bq2560x *bq)
{
u8 val = REG00_HIZ_DISABLE << REG00_ENHIZ_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_00, REG00_ENHIZ_MASK, val);
}
EXPORT_SYMBOL_GPL(bq2560x_exit_hiz_mode);
int bq2560x_get_hiz_mode(struct bq2560x *bq, u8 *state)
{
u8 val;
int ret;
ret = bq2560x_read_byte(bq, &val, BQ2560X_REG_00);
if (ret)
return ret;
*state = (val & REG00_ENHIZ_MASK) >> REG00_ENHIZ_SHIFT;
return 0;
}
EXPORT_SYMBOL_GPL(bq2560x_get_hiz_mode);
static int bq2560x_enable_term(struct bq2560x* bq, bool enable)
{
u8 val;
int ret;
if (enable)
val = REG05_TERM_ENABLE << REG05_EN_TERM_SHIFT;
else
val = REG05_TERM_DISABLE << REG05_EN_TERM_SHIFT;
ret = bq2560x_update_bits(bq, BQ2560X_REG_05, REG05_EN_TERM_MASK, val);
return ret;
}
EXPORT_SYMBOL_GPL(bq2560x_enable_term);
int bq2560x_set_boost_current(struct bq2560x *bq, int curr)
{
u8 val;
val = REG02_BOOST_LIM_0P5A;
if (curr == BOOSTI_1200)
val = REG02_BOOST_LIM_1P2A;
return bq2560x_update_bits(bq, BQ2560X_REG_02, REG02_BOOST_LIM_MASK,
val << REG02_BOOST_LIM_SHIFT);
}
int bq2560x_set_boost_voltage(struct bq2560x *bq, int volt)
{
u8 val;
if (volt == BOOSTV_4850)
val = REG06_BOOSTV_4P85V;
else if (volt == BOOSTV_5150)
val = REG06_BOOSTV_5P15V;
else if (volt == BOOSTV_5300)
val = REG06_BOOSTV_5P3V;
else
val = REG06_BOOSTV_5V;
return bq2560x_update_bits(bq, BQ2560X_REG_06, REG06_BOOSTV_MASK,
val << REG06_BOOSTV_SHIFT);
}
static int bq2560x_set_acovp_threshold(struct bq2560x *bq, int volt)
{
u8 val;
if (volt == VAC_OVP_14300)
val = REG06_OVP_14P3V;
else if (volt == VAC_OVP_10500)
val = REG06_OVP_10P5V;
else if (volt == VAC_OVP_6200)
val = REG06_OVP_6P2V;
else
val = REG06_OVP_5P5V;
return bq2560x_update_bits(bq, BQ2560X_REG_06, REG06_OVP_MASK,
val << REG06_OVP_SHIFT);
}
static int bq2560x_set_stat_ctrl(struct bq2560x *bq, int ctrl)
{
u8 val;
val = ctrl;
return bq2560x_update_bits(bq, BQ2560X_REG_00, REG00_STAT_CTRL_MASK,
val << REG00_STAT_CTRL_SHIFT);
}
static int bq2560x_set_int_mask(struct bq2560x *bq, int mask)
{
u8 val;
val = mask;
return bq2560x_update_bits(bq, BQ2560X_REG_0A, REG0A_INT_MASK_MASK,
val << REG0A_INT_MASK_SHIFT);
}
static int bq2560x_enable_batfet(struct bq2560x *bq)
{
const u8 val = REG07_BATFET_ON << REG07_BATFET_DIS_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_07, REG07_BATFET_DIS_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_enable_batfet);
static int bq2560x_disable_batfet(struct bq2560x *bq)
{
const u8 val = REG07_BATFET_OFF << REG07_BATFET_DIS_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_07, REG07_BATFET_DIS_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_disable_batfet);
static int bq2560x_set_batfet_delay(struct bq2560x *bq, uint8_t delay)
{
u8 val;
if (delay == 0)
val = REG07_BATFET_DLY_0S;
else
val = REG07_BATFET_DLY_10S;
val <<= REG07_BATFET_DLY_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_07, REG07_BATFET_DLY_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_set_batfet_delay);
static int bq2560x_set_vdpm_bat_track(struct bq2560x *bq)
{
const u8 val = REG07_VDPM_BAT_TRACK_200MV << REG07_VDPM_BAT_TRACK_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_07, REG07_VDPM_BAT_TRACK_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_set_vdpm_bat_track);
static int bq2560x_enable_safety_timer(struct bq2560x *bq)
{
const u8 val = REG05_CHG_TIMER_ENABLE << REG05_EN_TIMER_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_05, REG05_EN_TIMER_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_enable_safety_timer);
static int bq2560x_disable_safety_timer(struct bq2560x *bq)
{
const u8 val = REG05_CHG_TIMER_DISABLE << REG05_EN_TIMER_SHIFT;
return bq2560x_update_bits(bq, BQ2560X_REG_05, REG05_EN_TIMER_MASK,
val);
}
EXPORT_SYMBOL_GPL(bq2560x_disable_safety_timer);
static int bq2560x_charging_disable(struct bq2560x *bq, int reason,
int disable)
{
int ret = 0;
int disabled;
mutex_lock(&bq->charging_disable_lock);
disabled = bq->charging_disabled_status;
pr_err("reason=%d requested_disable=%d disabled_status=%d\n",
reason, disable, disabled);
if (disable == true)
disabled |= reason;
else
disabled &= ~reason;
if (disabled && bq->charge_enabled)
ret = bq2560x_disable_charger(bq);
else if (!disabled && !bq->charge_enabled)
ret = bq2560x_enable_charger(bq);
if (ret) {
pr_err("Couldn't disable/enable charging for reason=%d ret=%d\n",
ret, reason);
} else {
bq->charging_disabled_status = disabled;
mutex_lock(&bq->data_lock);
bq->charge_enabled = !disabled;
mutex_unlock(&bq->data_lock);
}
mutex_unlock(&bq->charging_disable_lock);
return ret;
}
static struct power_supply *get_bms_psy(struct bq2560x *bq)
{
if (bq->bms_psy)
return bq->bms_psy;
bq->bms_psy = power_supply_get_by_name("bms");
if (!bq->bms_psy)
pr_debug("bms power supply not found\n");
return bq->bms_psy;
}
static int bq2560x_get_batt_property(struct bq2560x *bq,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct power_supply *bms_psy = get_bms_psy(bq);
int ret;
if (!bms_psy)
return -EINVAL;
ret = bms_psy->get_property(bms_psy, psp, val);
return ret;
}
static inline bool is_device_suspended(struct bq2560x *bq);
static int bq2560x_get_prop_charge_type(struct bq2560x *bq)
{
u8 val = 0;
if (is_device_suspended(bq))
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
bq2560x_read_byte(bq, &val, BQ2560X_REG_08);
val &= REG08_CHRG_STAT_MASK;
val >>= REG08_CHRG_STAT_SHIFT;
switch (val) {
case CHARGE_STATE_FASTCHG:
return POWER_SUPPLY_CHARGE_TYPE_FAST;
case CHARGE_STATE_PRECHG:
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
case CHARGE_STATE_CHGDONE:
case CHARGE_STATE_IDLE:
return POWER_SUPPLY_CHARGE_TYPE_NONE;
default:
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
}
}
static int bq2560x_get_prop_batt_present(struct bq2560x *bq)
{
union power_supply_propval batt_prop = {0,};
int ret;
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_PRESENT, &batt_prop);
if (!ret){
mutex_lock(&bq->data_lock);
bq->batt_present = batt_prop.intval;
mutex_unlock(&bq->data_lock);
}
return ret;
}
static int bq2560x_get_prop_batt_full(struct bq2560x *bq)
{
union power_supply_propval batt_prop = {0,};
int ret;
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_STATUS, &batt_prop);
if (!ret) {
mutex_lock(&bq->data_lock);
bq->batt_full = (batt_prop.intval == POWER_SUPPLY_STATUS_FULL);
mutex_unlock(&bq->data_lock);
}
return ret;
}
static int bq2560x_get_prop_charge_status(struct bq2560x *bq)
{
union power_supply_propval batt_prop = {0,};
int ret;
u8 status;
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_STATUS, &batt_prop);
if (!ret && batt_prop.intval == POWER_SUPPLY_STATUS_FULL)
return POWER_SUPPLY_STATUS_FULL;
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_08);
if (ret) {
return POWER_SUPPLY_STATUS_UNKNOWN;
}
mutex_lock(&bq->data_lock);
bq->charge_state = (status & REG08_CHRG_STAT_MASK) >> REG08_CHRG_STAT_SHIFT;
mutex_unlock(&bq->data_lock);
if (bq->usb_present && bq->jeita_active
&& (bq->batt_warm || bq->batt_cool)
&& bq->charge_state == CHARGE_STATE_CHGDONE)
return POWER_SUPPLY_STATUS_FULL;
switch(bq->charge_state) {
case CHARGE_STATE_FASTCHG:
case CHARGE_STATE_PRECHG:
return POWER_SUPPLY_STATUS_CHARGING;
case CHARGE_STATE_CHGDONE:
return POWER_SUPPLY_STATUS_NOT_CHARGING;
case CHARGE_STATE_IDLE:
return POWER_SUPPLY_STATUS_DISCHARGING;
default:
return POWER_SUPPLY_STATUS_UNKNOWN;
}
}
static int bq2560x_get_prop_health(struct bq2560x *bq)
{
int ret;
union power_supply_propval batt_prop = {0,};
if (bq->software_jeita_supported) {
if (bq->jeita_active) {
if (bq->batt_hot)
ret = POWER_SUPPLY_HEALTH_OVERHEAT;
/* else if (bq->batt_warm)
ret = POWER_SUPPLY_HEALTH_WARM;
else if (bq->batt_cool)
ret = POWER_SUPPLY_HEALTH_COOL;
else if (bq->batt_cold)
ret = POWER_SUPPLY_HEALTH_COLD;*/
} else {
ret = POWER_SUPPLY_HEALTH_GOOD;
}
} else {/* get health status from gauge */
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_HEALTH, &batt_prop);
if (!ret)
ret = batt_prop.intval;
else
ret = POWER_SUPPLY_HEALTH_UNKNOWN;
}
return ret;
}
static enum power_supply_property bq2560x_charger_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGING_ENABLED,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_FULL,
//POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
POWER_SUPPLY_PROP_TECHNOLOGY,
//POWER_SUPPLY_PROP_RESISTANCE_ID,
};
void static runin_work(struct bq2560x *bq, int batt_capacity)
{
int rc;
printk("%s:BatteryTestStatus_enable = %d bq->usb_present = %d \n",
__func__,BatteryTestStatus_enable,bq->usb_present);
if (/*!bq->usb_present || */!BatteryTestStatus_enable) {
if (bq->in_hiz) {
rc = bq2560x_exit_hiz_mode(bq);
if (rc) {
dev_err(bq->dev, "Couldn't enable charge rc=%d\n", rc);
} else {
pr_err("Exit Hiz Successfully\n");
bq->in_hiz = false;
}
}
return;
}
if (batt_capacity >= 80) {
pr_debug("bq2560x_get_prop_batt_capacity > 80\n");
//rc = bq2560x_charging_disable(bq, USER, true);
if (!bq->in_hiz) {
rc = bq2560x_enter_hiz_mode(bq);
if (rc) {
dev_err(bq->dev, "Couldn't disenable charge rc=%d\n", rc);
} else {
pr_err("Enter Hiz Successfully\n");
bq->in_hiz = true;
}
}
} else if (batt_capacity < 60) {
pr_debug("bq2560x_get_prop_batt_capacity < 60\n");
//rc = bq2560x_charging_disable(bq, USER, false);
if (bq->in_hiz) {
rc = bq2560x_exit_hiz_mode(bq);
if (rc) {
dev_err(bq->dev, "Couldn't enable charge rc=%d\n", rc);
} else {
pr_err("Exit Hiz Successfully\n");
bq->in_hiz = false;
}
}
}
}
static int bq2560x_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq2560x *bq = container_of(psy, struct bq2560x, batt_psy);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = bq2560x_get_prop_charge_type(bq);
pr_debug("POWER_SUPPLY_PROP_CHARGE_TYPE:%d\n", val->intval);
break;
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
val->intval = bq->charge_enabled;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = 3080;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = bq2560x_get_prop_charge_status(bq);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = bq2560x_get_prop_health(bq);
break;
case POWER_SUPPLY_PROP_CAPACITY:
bq2560x_get_batt_property(bq, psp, val);
runin_work(bq, val->intval);
break;
//case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
// val->intval = bq->therm_lvl_sel;
// break;
case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_TECHNOLOGY:
//case POWER_SUPPLY_PROP_RESISTANCE_ID:
// return bq2560x_get_batt_property(bq, psp, val);
default:
return -EINVAL;
}
return 0;
}
//static int bq2560x_system_temp_level_set(struct bq2560x *bq, int);
static int bq2560x_charger_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct bq2560x *bq = container_of(psy,
struct bq2560x, batt_psy);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
bq2560x_charging_disable(bq, USER, !val->intval);
power_supply_changed(&bq->batt_psy);
power_supply_changed(bq->usb_psy);
pr_info("POWER_SUPPLY_PROP_CHARGING_ENABLED: %s\n",
val->intval ? "enable" : "disable");
break;
//case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
// bq2560x_system_temp_level_set(bq, val->intval);
// break;
default:
return -EINVAL;
}
return 0;
}
static int bq2560x_charger_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
//case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
// ret = 1;
// break;
default:
ret = 0;
break;
}
return ret;
}
static int bq2560x_update_charging_profile(struct bq2560x *bq)
{
int ret;
int chg_ma;
int chg_mv;
int icl;
int therm_ma;
union power_supply_propval prop = {0,};
if (!bq->usb_present)
return 0;
ret = bq->usb_psy->get_property(bq->usb_psy,
POWER_SUPPLY_PROP_TYPE, &prop);
if (ret < 0) {
pr_err("couldn't read USB TYPE property, ret=%d\n", ret);
return ret;
}
pr_err("charge type = %d\n", prop.intval);
mutex_lock(&bq->profile_change_lock);
if (bq->jeita_active) {
chg_ma = bq->jeita_ma;
chg_mv = bq->jeita_mv;
} else {
if (prop.intval == POWER_SUPPLY_TYPE_USB_DCP
|| prop.intval == POWER_SUPPLY_TYPE_USB_CDP) {
chg_ma = bq->platform_data->ta.ichg;
chg_mv = bq->platform_data->ta.vreg;
} else {
chg_ma = bq->platform_data->usb.ichg;
chg_mv = bq->platform_data->usb.vreg;
}
}
icl = bq->usb_psy_ma;
if (bq->usb_psy_ma < chg_ma) {
chg_ma = bq->usb_psy_ma;
}
if (bq->therm_lvl_sel > 0
&& bq->therm_lvl_sel < (bq->thermal_levels - 1))
/*
* consider thermal limit only when it is active and not at
* the highest level
*/
therm_ma = bq->thermal_mitigation[bq->therm_lvl_sel];
else
therm_ma = chg_ma;
chg_ma = min(therm_ma, chg_ma);
pr_err("charge volt = %d, charge curr = %d, input curr limit = %d\n",
chg_mv, chg_ma, icl);
ret = bq2560x_set_input_current_limit(bq, icl);
if (ret < 0)
pr_err("couldn't set input current limit, ret=%d\n", ret);
ret = bq2560x_set_input_volt_limit(bq, bq->platform_data->ta.vlim);
if (ret < 0)
pr_err("couldn't set input voltage limit, ret=%d\n", ret);
ret = bq2560x_set_chargevolt(bq, chg_mv);
if (ret < 0)
pr_err("couldn't set charge voltage ret=%d\n", ret);
ret = bq2560x_set_chargecurrent(bq, chg_ma);
if (ret < 0)
pr_err("couldn't set charge current, ret=%d\n", ret);
mutex_unlock(&bq->profile_change_lock);
return 0;
}
/*static int bq2560x_system_temp_level_set(struct bq2560x *bq,
int lvl_sel)
{
int ret = 0;
int prev_therm_lvl;
pr_err("lvl_sel=%d, bq->therm_lvl_sel = %d\n", lvl_sel, bq->therm_lvl_sel);
if (BatteryTestStatus_enable)
return 0;
if (!bq->thermal_mitigation) {
pr_err("Thermal mitigation not supported\n");
return -EINVAL;
}
if (lvl_sel < 0) {
pr_err("Unsupported level selected %d\n", lvl_sel);
return -EINVAL;
}
if (lvl_sel >= bq->thermal_levels) {
pr_err("Unsupported level selected %d forcing %d\n", lvl_sel,
bq->thermal_levels - 1);
lvl_sel = bq->thermal_levels - 1;
}
if (lvl_sel == bq->therm_lvl_sel)
return 0;
prev_therm_lvl = bq->therm_lvl_sel;
bq->therm_lvl_sel = lvl_sel;
ret = bq2560x_update_charging_profile(bq);
if (ret)
pr_err("Couldn't set USB current ret = %d\n", ret);
return ret;
}
*/
static void bq2560x_external_power_changed(struct power_supply *psy)
{
struct bq2560x *bq = container_of(psy, struct bq2560x, batt_psy);
union power_supply_propval prop = {0,};
int ret, current_limit = 0;
ret = bq->usb_psy->get_property(bq->usb_psy,
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
if (ret < 0)
pr_err("could not read USB current_max property, ret=%d\n", ret);
else
current_limit = prop.intval / 1000;
pr_err("current_limit = %d\n", current_limit);
if (bq->usb_psy_ma != current_limit) {
bq->usb_psy_ma = current_limit;
bq2560x_update_charging_profile(bq);
}
ret = bq->usb_psy->get_property(bq->usb_psy,
POWER_SUPPLY_PROP_ONLINE, &prop);
if (ret < 0)
pr_err("could not read USB ONLINE property, ret=%d\n", ret);
else
pr_info("usb online status =%d\n", prop.intval);
ret = 0;
bq2560x_get_prop_charge_status(bq);
if (bq->usb_present /*&& bq->charge_state != CHARGE_STATE_IDLE*//* && bq->charge_enabled *//*!bq->charging_disabled_status*/
/*&& bq->usb_psy_ma != 0*/) {
if (prop.intval == 0){
pr_err("set usb online\n");
ret = power_supply_set_online(bq->usb_psy, true);
}
} else {
if (prop.intval == 1) {
pr_err("set usb offline\n");
ret = power_supply_set_online(bq->usb_psy, false);
}
}
if (ret < 0)
pr_info("could not set usb online state, ret=%d\n", ret);
}
static int bq2560x_psy_register(struct bq2560x *bq)
{
int ret;
bq->batt_psy.name = "battery";
bq->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
bq->batt_psy.properties = bq2560x_charger_props;
bq->batt_psy.num_properties = ARRAY_SIZE(bq2560x_charger_props);
bq->batt_psy.get_property = bq2560x_charger_get_property;
bq->batt_psy.set_property = bq2560x_charger_set_property;
bq->batt_psy.external_power_changed = bq2560x_external_power_changed;
bq->batt_psy.property_is_writeable = bq2560x_charger_is_writeable;
ret = power_supply_register(bq->dev, &bq->batt_psy);
if (ret < 0) {
pr_err("failed to register batt_psy:%d\n", ret);
return ret;
}
return 0;
}
static void bq2560x_psy_unregister(struct bq2560x *bq)
{
power_supply_unregister(&bq->batt_psy);
}
static int bq2560x_otg_regulator_enable(struct regulator_dev *rdev)
{
int ret;
struct bq2560x *bq = rdev_get_drvdata(rdev);
ret = bq2560x_enable_otg(bq);
if (ret) {
pr_err("Couldn't enable OTG mode ret=%d\n", ret);
} else {
bq->otg_enabled = true;
pr_info("bq2560x OTG mode Enabled!\n");
}
return ret;
}
static int bq2560x_otg_regulator_disable(struct regulator_dev *rdev)
{
int ret;
struct bq2560x *bq = rdev_get_drvdata(rdev);
ret = bq2560x_disable_otg(bq);
if (ret) {
pr_err("Couldn't disable OTG mode, ret=%d\n", ret);
} else {
bq->otg_enabled = false;
pr_info("bq2560x OTG mode Disabled\n");
}
return ret;
}
static int bq2560x_otg_regulator_is_enable(struct regulator_dev *rdev)
{
int ret;
u8 status;
u8 enabled;
struct bq2560x *bq = rdev_get_drvdata(rdev);
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_01);
if (ret)
return ret;
enabled = ((status & REG01_OTG_CONFIG_MASK) >> REG01_OTG_CONFIG_SHIFT);
return (enabled == REG01_OTG_ENABLE) ? 1 : 0;
}
struct regulator_ops bq2560x_otg_reg_ops = {
.enable = bq2560x_otg_regulator_enable,
.disable = bq2560x_otg_regulator_disable,
.is_enabled = bq2560x_otg_regulator_is_enable,
};
static int bq2560x_regulator_init(struct bq2560x *bq)
{
int ret = 0;
struct regulator_init_data *init_data;
struct regulator_config cfg = {};
init_data = of_get_regulator_init_data(bq->dev, bq->dev->of_node);
if (!init_data) {
dev_err(bq->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
if (init_data->constraints.name) {
bq->otg_vreg.rdesc.owner = THIS_MODULE;
bq->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
bq->otg_vreg.rdesc.ops = &bq2560x_otg_reg_ops;
bq->otg_vreg.rdesc.name = init_data->constraints.name;
pr_info("regualtor name = %s\n", bq->otg_vreg.rdesc.name);
cfg.dev = bq->dev;
cfg.init_data = init_data;
cfg.driver_data = bq;
cfg.of_node = bq->dev->of_node;
init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_STATUS;
bq->otg_vreg.rdev = regulator_register(
&bq->otg_vreg.rdesc, &cfg);
if (IS_ERR(bq->otg_vreg.rdev)) {
ret = PTR_ERR(bq->otg_vreg.rdev);
bq->otg_vreg.rdev = NULL;
if (ret != -EPROBE_DEFER)
dev_err(bq->dev,
"OTG reg failed, rc=%d\n", ret);
}
}
return ret;
}
static int bq2560x_parse_jeita_dt(struct device *dev, struct bq2560x* bq)
{
struct device_node *np = dev->of_node;
int ret;
ret = of_property_read_u32(np,"ti,bq2560x,jeita-hot-degc",
&bq->batt_hot_degc);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-hot-degc\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-warm-degc",
&bq->batt_warm_degc);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-warm-degc\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-cool-degc",
&bq->batt_cool_degc);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-cool-degc\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-cold-degc",
&bq->batt_cold_degc);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-cold-degc\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-hot-hysteresis",
&bq->hot_temp_hysteresis);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-hot-hysteresis\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-cold-hysteresis",
&bq->cold_temp_hysteresis);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-cold-hysteresis\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-cool-ma",
&bq->batt_cool_ma);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-cool-ma\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-cool-mv",
&bq->batt_cool_mv);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-cool-mv\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-warm-ma",
&bq->batt_warm_ma);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-warm-ma\n");
return ret;
}
ret = of_property_read_u32(np,"ti,bq2560x,jeita-warm-mv",
&bq->batt_warm_mv);
if(ret) {
pr_err("Failed to read ti,bq2560x,jeita-warm-mv\n");
return ret;
}
bq->software_jeita_supported =
of_property_read_bool(np,"ti,bq2560x,software-jeita-supported");
return 0;
}
static struct bq2560x_platform_data* bq2560x_parse_dt(struct device *dev,
struct bq2560x * bq)
{
int ret;
struct device_node *np = dev->of_node;
struct bq2560x_platform_data* pdata;
pdata = devm_kzalloc(dev, sizeof(struct bq2560x_platform_data),
GFP_KERNEL);
if (!pdata) {
pr_err("Out of memory\n");
return NULL;
}
ret = of_property_read_u32(np, "ti,bq2560x,chip-enable-gpio", &bq->gpio_ce);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,chip-enable-gpio\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,usb-vlim",&pdata->usb.vlim);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,usb-vlim\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,usb-ilim",&pdata->usb.ilim);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,usb-ilim\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,usb-vreg",&pdata->usb.vreg);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,usb-vreg\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,usb-ichg",&pdata->usb.ichg);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,usb-ichg\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,ta-vlim",&pdata->ta.vlim);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,ta-vlim\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,ta-ilim",&pdata->ta.ilim);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,ta-ilim\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,ta-vreg",&pdata->ta.vreg);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,ta-vreg\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,ta-ichg",&pdata->ta.ichg);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,ta-ichg\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,stat-pin-ctrl",&pdata->statctrl);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,stat-pin-ctrl\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,precharge-current",&pdata->iprechg);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,precharge-current\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,termination-current",&pdata->iterm);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,termination-current\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,boost-voltage",&pdata->boostv);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,boost-voltage\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,boost-current",&pdata->boosti);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,boost-current\n");
}
ret = of_property_read_u32(np,"ti,bq2560x,vac-ovp-threshold",&pdata->vac_ovp);
if(ret) {
pr_err("Failed to read node of ti,bq2560x,vac-ovp-threshold\n");
}
if (of_find_property(np, "ti,thermal-mitigation",
&bq->thermal_levels)) {
bq->thermal_mitigation = devm_kzalloc(bq->dev,
bq->thermal_levels,
GFP_KERNEL);
if (bq->thermal_mitigation == NULL) {
pr_err("thermal mitigation kzalloc() failed.\n");
}
bq->thermal_levels /= sizeof(int);
ret = of_property_read_u32_array(np,
"ti,thermal-mitigation",
bq->thermal_mitigation, bq->thermal_levels);
if (ret) {
pr_err("Couldn't read thermal limits ret = %d\n", ret);
}
}
return pdata;
}
static void bq2560x_init_jeita(struct bq2560x *bq)
{
bq->batt_temp = -EINVAL;
/* set default value in case of dts read fail */
bq->batt_hot_degc = 600;
bq->batt_warm_degc = 450;
bq->batt_cool_degc = 100;
bq->batt_cold_degc = 0;
bq->hot_temp_hysteresis = 50;
bq->cold_temp_hysteresis = 50;
bq->batt_cool_ma = 400;
bq->batt_cool_mv = 4100;
bq->batt_warm_ma = 400;
bq->batt_warm_mv = 4100;
bq->software_jeita_supported = true;
/* DTS setting will overwrite above default value */
bq2560x_parse_jeita_dt(&bq->client->dev, bq);
}
static int bq2560x_init_device(struct bq2560x *bq)
{
int ret;
bq2560x_disable_watchdog_timer(bq);
bq2560x_enable_batfet(bq);
bq2560x_set_vdpm_bat_track(bq);
ret = bq2560x_set_stat_ctrl(bq, bq->platform_data->statctrl);
if (ret)
pr_err("Failed to set stat pin control mode, ret = %d\n",ret);
ret = bq2560x_set_prechg_current(bq, bq->platform_data->iprechg);
if (ret)
pr_err("Failed to set prechg current, ret = %d\n",ret);
ret = bq2560x_set_term_current(bq, bq->platform_data->iterm);
if (ret)
pr_err("Failed to set termination current, ret = %d\n",ret);
ret = bq2560x_set_boost_voltage(bq, bq->platform_data->boostv);
if (ret)
pr_err("Failed to set boost voltage, ret = %d\n",ret);
ret = bq2560x_set_boost_current(bq, bq->platform_data->boosti);
if (ret)
pr_err("Failed to set boost current, ret = %d\n",ret);
ret = bq2560x_set_acovp_threshold(bq, bq->platform_data->vac_ovp);
if (ret)
pr_err("Failed to set acovp threshold, ret = %d\n",ret);
ret = bq2560x_set_int_mask(bq, REG0A_IINDPM_INT_MASK | REG0A_VINDPM_INT_MASK);
if (ret)
pr_err("Failed to set vindpm and iindpm int mask\n");
ret = bq2560x_enable_charger(bq);
if (ret) {
pr_err("Failed to enable charger, ret = %d\n",ret);
} else {
bq->charge_enabled = true;
pr_err("Charger Enabled Successfully!\n");
}
return 0;
}
static int bq2560x_detect_device(struct bq2560x* bq)
{
int ret;
u8 data;
ret = bq2560x_read_byte(bq, &data, BQ2560X_REG_0B);
if(ret == 0){
bq->part_no = (data & REG0B_PN_MASK) >> REG0B_PN_SHIFT;
bq->revision = (data & REG0B_DEV_REV_MASK) >> REG0B_DEV_REV_SHIFT;
}
return ret;
}
static void bq2560x_check_jeita(struct bq2560x *bq)
{
int ret;
bool last_hot, last_warm, last_cool, last_cold;
bool chg_disabled_jeita, jeita_hot_cold;
union power_supply_propval batt_prop = {0,};
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_TEMP, &batt_prop);
if (!ret)
bq->batt_temp = batt_prop.intval;
if (bq->batt_temp == -EINVAL)
return;
last_hot = bq->batt_hot;
last_warm = bq->batt_warm;
last_cool = bq->batt_cool;
last_cold = bq->batt_cold;
if (bq->batt_temp >= bq->batt_hot_degc) {/* HOT */
if (!bq->batt_hot) {
bq->batt_hot = true;
bq->batt_warm = false;
bq->batt_cool = false;
bq->batt_cold = false;
bq->jeita_ma = 0;
bq->jeita_mv = 0;
}
} else if (bq->batt_temp >= bq->batt_warm_degc) {/* WARM */
if (!bq->batt_hot
||(bq->batt_temp < bq->batt_hot_degc - bq->hot_temp_hysteresis)){
bq->batt_hot = false;
bq->batt_warm = true;
bq->batt_cool = false;
bq->batt_cold = false;
bq->jeita_mv = bq->batt_warm_mv;
bq->jeita_ma = bq->batt_warm_ma;
}
} else if (bq->batt_temp < bq->batt_cold_degc) {/* COLD */
if (!bq->batt_cold) {
bq->batt_hot = false;
bq->batt_warm = false;
bq->batt_cool = false;
bq->batt_cold = true;
bq->jeita_ma = 0;
bq->jeita_mv = 0;
}
} else if (bq->batt_temp < bq->batt_cool_degc) {/* COOL */
if (!bq->batt_cold ||
(bq->batt_temp > bq->batt_cold_degc + bq->cold_temp_hysteresis)) {
bq->batt_hot = false;
bq->batt_warm = false;
bq->batt_cool = true;
bq->batt_cold = false;
bq->jeita_mv = bq->batt_cool_mv;
bq->jeita_ma = bq->batt_cool_ma;
}
} else {/* NORMAL */
bq->batt_hot = false;
bq->batt_warm = false;
bq->batt_cool = false;
bq->batt_cold = false;
}
bq->jeita_active = bq->batt_cool || bq->batt_hot ||
bq->batt_cold || bq->batt_warm;
if ((last_cold != bq->batt_cold) || (last_warm != bq->batt_warm) ||
(last_cool != bq->batt_cool) || (last_hot != bq->batt_hot)) {
bq2560x_update_charging_profile(bq);
power_supply_changed(&bq->batt_psy);
power_supply_changed(bq->usb_psy);
} else if (bq->batt_hot || bq->batt_cold) { /*continuely update event */
power_supply_changed(&bq->batt_psy);
power_supply_changed(bq->usb_psy);
}
jeita_hot_cold = bq->jeita_active && (bq->batt_hot || bq->batt_cold);
chg_disabled_jeita = !!(bq->charging_disabled_status & JEITA);
if (jeita_hot_cold ^ chg_disabled_jeita)
bq2560x_charging_disable(bq, JEITA, jeita_hot_cold);
}
static void bq2560x_check_batt_pres(struct bq2560x *bq)
{
int ret = 0;
bool chg_disabled_pres;
ret = bq2560x_get_prop_batt_present(bq);
if (!ret) {
chg_disabled_pres = !!(bq->charging_disabled_status & BATT_PRES);
if (chg_disabled_pres ^ !bq->batt_present) {
ret = bq2560x_charging_disable(bq, BATT_PRES, !bq->batt_present);
if (ret) {
pr_err("failed to %s charging, ret = %d\n",
bq->batt_present ? "disable" : "enable",
ret);
}
power_supply_changed(&bq->batt_psy);
power_supply_changed(bq->usb_psy);
}
}
}
static void bq2560x_check_batt_full(struct bq2560x *bq)
{
int ret = 0;
bool chg_disabled_fc;
ret = bq2560x_get_prop_batt_full(bq);
if (!ret) {
chg_disabled_fc = !!(bq->charging_disabled_status & BATT_FC);
if (chg_disabled_fc ^ bq->batt_full) {
ret = bq2560x_charging_disable(bq, BATT_FC, bq->batt_full);
if (ret) {
pr_err("failed to %s charging, ret = %d\n",
bq->batt_full ? "disable" : "enable",
ret);
}
power_supply_changed(&bq->batt_psy);
power_supply_changed(bq->usb_psy);
}
}
}
static int calculate_jeita_poll_interval(struct bq2560x* bq)
{
int interval;
if (bq->batt_hot || bq->batt_cold)
interval = 5;
else if (bq->batt_warm || bq->batt_cool)
interval = 10;
else
interval = 15;
return interval;
}
#define FG_LOG_INTERVAL 120
static void bq2560x_dump_fg_reg(struct bq2560x *bq)
{
union power_supply_propval val = {0,};
static int dump_cnt;
if (++dump_cnt >= (FG_LOG_INTERVAL / calculate_jeita_poll_interval(bq))) {
dump_cnt = 0;
val.intval = 0;
// bq->bms_psy->set_property(bq->bms_psy,
// POWER_SUPPLY_PROP_UPDATE_NOW, &val);
}
}
static enum alarmtimer_restart bq2560x_jeita_alarm_cb(struct alarm *alarm,
ktime_t now)
{
struct bq2560x *bq = container_of(alarm,
struct bq2560x, jeita_alarm);
unsigned long ns;
bq2560x_stay_awake(&bq->bq2560x_ws, WAKEUP_SRC_JEITA);
schedule_delayed_work(&bq->charge_jeita_work, HZ/2);
ns = calculate_jeita_poll_interval(bq) * 1000000000LL;
alarm_forward_now(alarm, ns_to_ktime(ns));
return ALARMTIMER_RESTART;
}
static void bq2560x_dump_status(struct bq2560x* bq);
static void bq2560x_charge_jeita_workfunc(struct work_struct *work)
{
struct bq2560x *bq = container_of(work,
struct bq2560x, charge_jeita_work.work);
bq2560x_reset_watchdog_timer(bq);
bq2560x_check_batt_pres(bq);
bq2560x_check_batt_full(bq);
bq2560x_dump_fg_reg(bq);
bq2560x_check_jeita(bq);
bq2560x_dump_status(bq);
bq2560x_relax(&bq->bq2560x_ws, WAKEUP_SRC_JEITA);
}
static void bq2560x_discharge_jeita_workfunc(struct work_struct *work)
{
struct bq2560x *bq = container_of(work,
struct bq2560x, discharge_jeita_work.work);
bq2560x_check_batt_pres(bq);
bq2560x_check_batt_full(bq);
bq2560x_dump_fg_reg(bq);
bq2560x_check_jeita(bq);
schedule_delayed_work(&bq->discharge_jeita_work,
calculate_jeita_poll_interval(bq) * HZ);
}
static const unsigned char* charge_stat_str[] = {
"Not Charging",
"Precharging",
"Fast Charging",
"Charge Done",
};
static void bq2560x_dump_status(struct bq2560x* bq)
{
u8 status;
u8 addr;
int ret;
u8 val;
union power_supply_propval batt_prop = {0,};
ret = bq2560x_get_batt_property(bq,
POWER_SUPPLY_PROP_CURRENT_NOW, &batt_prop);
if (!ret)
pr_err("FG current:%d\n", batt_prop.intval);
for (addr = 0x0; addr <= 0x0B; addr++) {
if (addr == 0x09) {
pr_err("bq Reg[09] = 0x%02X\n", bq->fault_status);
continue;
}
ret = bq2560x_read_byte(bq, &val, addr);
if (!ret)
pr_err("bq Reg[%02X] = 0x%02X\n", addr, val);
else
pr_err("bq Reg red err\n");
}
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_0A);
if (ret) {
pr_err("failed to read reg0a\n");
return;
}
mutex_lock(&bq->data_lock);
bq->vbus_good = !!(status & REG0A_VBUS_GD_MASK);
bq->vindpm_triggered = !!(status & REG0A_VINDPM_STAT_MASK);
bq->iindpm_triggered = !!(status & REG0A_IINDPM_STAT_MASK);
bq->topoff_active = !!(status & REG0A_TOPOFF_ACTIVE_MASK);
bq->acov_triggered = !!(status & REG0A_ACOV_STAT_MASK);
mutex_unlock(&bq->data_lock);
if (!bq->power_good)
pr_info("Power Poor\n");
if (!bq->vbus_good)
pr_err("Vbus voltage not good!\n");
if (bq->vindpm_triggered)
pr_err("VINDPM triggered\n");
if (bq->iindpm_triggered)
pr_err("IINDPM triggered\n");
if (bq->acov_triggered)
pr_err("ACOV triggered\n");
if (bq->fault_status & REG09_FAULT_WDT_MASK)
pr_err("Watchdog timer expired!\n");
if (bq->fault_status & REG09_FAULT_BOOST_MASK)
pr_err("Boost fault occurred!\n");
status = (bq->fault_status & REG09_FAULT_CHRG_MASK) >> REG09_FAULT_CHRG_SHIFT;
if (status == REG09_FAULT_CHRG_INPUT)
pr_err("input fault!\n");
else if (status == REG09_FAULT_CHRG_THERMAL)
pr_err("charge thermal shutdown fault!\n");
else if (status == REG09_FAULT_CHRG_TIMER)
pr_err("charge timer expired fault!\n");
if (bq->fault_status & REG09_FAULT_BAT_MASK)
pr_err("battery ovp fault!\n");
if (!bq->software_jeita_supported) {
status = (bq->fault_status & REG09_FAULT_NTC_MASK) >> REG09_FAULT_NTC_SHIFT;
if (status == REG09_FAULT_NTC_WARM)
pr_debug("JEITA ACTIVE: WARM\n");
else if (status == REG09_FAULT_NTC_COOL)
pr_debug("JEITA ACTIVE: COOL\n");
else if (status == REG09_FAULT_NTC_COLD)
pr_debug("JEITA ACTIVE: COLD\n");
else if (status == REG09_FAULT_NTC_HOT)
pr_debug("JEITA ACTIVE: HOT!\n");
} else if (bq->jeita_active) {
if (bq->batt_hot)
pr_debug("JEITA ACTIVE: HOT\n");
else if (bq->batt_warm)
pr_debug("JEITA ACTIVE: WARM\n");
else if (bq->batt_cool)
pr_debug("JEITA ACTIVE: COOL\n");
else if (bq->batt_cold)
pr_debug("JEITA ACTIVE: COLD\n");
}
pr_err("%s\n",charge_stat_str[bq->charge_state]);
}
static void bq2560x_update_status(struct bq2560x *bq)
{
u8 status;
int ret;
/* Read twice to get present status */
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_09);
if (ret)
return;
pr_err("First read of REG[09] = 0x%02x\n", status);
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_09);
if (ret)
return;
pr_err("Second read of REG[09] = 0x%02x\n", status);
mutex_lock(&bq->data_lock);
bq->fault_status = status;
mutex_unlock(&bq->data_lock);
}
static irqreturn_t bq2560x_charger_interrupt(int irq, void *dev_id)
{
struct bq2560x *bq = dev_id;
u8 status;
int ret;
mutex_lock(&bq->irq_complete);
bq->irq_waiting = true;
if (!bq->resume_completed) {
dev_dbg(bq->dev, "IRQ triggered before device-resume\n");
if (!bq->irq_disabled) {
disable_irq_nosync(irq);
bq->irq_disabled = true;
}
mutex_unlock(&bq->irq_complete);
return IRQ_HANDLED;
}
bq->irq_waiting = false;
ret = bq2560x_read_byte(bq, &status, BQ2560X_REG_08);
if (ret) {
mutex_unlock(&bq->irq_complete);
return IRQ_HANDLED;
}
mutex_lock(&bq->data_lock);
bq->power_good = !!(status & REG08_PG_STAT_MASK);
mutex_unlock(&bq->data_lock);
if(!bq->power_good) {
if(bq->usb_present) {
bq->usb_present = false;
power_supply_set_present(bq->usb_psy, bq->usb_present);
}
if (bq->software_jeita_supported) {
alarm_try_to_cancel(&bq->jeita_alarm);
}
bq2560x_disable_watchdog_timer(bq);
schedule_delayed_work(&bq->discharge_jeita_work,
calculate_jeita_poll_interval(bq) * HZ);
pr_err("usb removed, set usb present = %d\n", bq->usb_present);
} else if (bq->power_good && !bq->usb_present) {
bq->usb_present = true;
msleep(10);/*for cdp detect*/
power_supply_set_present(bq->usb_psy, bq->usb_present);
cancel_delayed_work(&bq->discharge_jeita_work);
if (bq->software_jeita_supported) {
ret = alarm_start_relative(&bq->jeita_alarm,
ns_to_ktime(1 * 1000000000LL));
if (ret)
pr_err("start alarm for JEITA detection failed, ret=%d\n",
ret);
}
bq2560x_set_watchdog_timer(bq, 80);
pr_err("usb plugged in, set usb present = %d\n", bq->usb_present);
}
bq2560x_update_status(bq);
mutex_unlock(&bq->irq_complete);
power_supply_changed(&bq->batt_psy);
return IRQ_HANDLED;
}
static void determine_initial_status(struct bq2560x *bq)
{
int ret;
u8 status = 0;
ret = bq2560x_get_hiz_mode(bq, &status);
if (!ret)
bq->in_hiz = !!status;
bq2560x_charger_interrupt(bq->client->irq, bq);
}
static ssize_t bq2560x_show_registers(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct bq2560x *bq = dev_get_drvdata(dev);
u8 addr;
u8 val;
u8 tmpbuf[200];
int len;
int idx = 0;
int ret ;
idx = snprintf(buf, PAGE_SIZE, "%s:\n", "bq2560x Reg");
for (addr = 0x0; addr <= 0x0B; addr++) {
ret = bq2560x_read_byte(bq, &val, addr);
if (ret == 0) {
len = snprintf(tmpbuf, PAGE_SIZE - idx,"Reg[0x%.2x] = 0x%.2x\n", addr, val);
memcpy(&buf[idx], tmpbuf, len);
idx += len;
}
}
return idx;
}
static ssize_t bq2560x_store_registers(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct bq2560x *bq = dev_get_drvdata(dev);
int ret;
unsigned int reg;
unsigned int val;
ret = sscanf(buf, "%x %x", &reg, &val);
if (ret == 2 && reg < 0x0B) {
bq2560x_write_byte(bq, (unsigned char)reg, (unsigned char)val);
}
return count;
}
static ssize_t bq2560x_battery_test_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", BatteryTestStatus_enable);
}
static ssize_t bq2560x_battery_test_status_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
if (sscanf(buf, "%u", &input) != 1)
retval = -EINVAL;
else
BatteryTestStatus_enable = input;
pr_err("BatteryTestStatus_enable = %d\n", BatteryTestStatus_enable);
return retval;
}
static ssize_t bq2560x_show_hiz(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct bq2560x *bq = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", bq->in_hiz);
}
static ssize_t bq2560x_store_hiz(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct bq2560x *bq = dev_get_drvdata(dev);
int ret;
unsigned int val;
ret = sscanf(buf, "%d", &val);
if (ret == 1) {
if (val)
ret = bq2560x_enter_hiz_mode(bq);
else
ret = bq2560x_exit_hiz_mode(bq);
}
if (!ret)
bq->in_hiz = !!val;
return ret;
}
static ssize_t bq2560x_show_dis_safety(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct bq2560x *bq = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", bq->dis_safety);
}
static ssize_t bq2560x_store_dis_safety(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct bq2560x *bq = dev_get_drvdata(dev);
int ret;
unsigned int val;
ret = sscanf(buf, "%d", &val);
if (ret == 1) {
if (val)
ret = bq2560x_disable_safety_timer(bq);
else
ret = bq2560x_enable_safety_timer(bq);
}
if (!ret)
bq->dis_safety = !!val;
return count;
}
static DEVICE_ATTR(registers, S_IRUGO | S_IWUSR, bq2560x_show_registers, bq2560x_store_registers);
static DEVICE_ATTR(BatteryTestStatus, S_IRUGO | S_IWUSR, bq2560x_battery_test_status_show, bq2560x_battery_test_status_store);
static DEVICE_ATTR(hiz, S_IRUGO | S_IWUSR, bq2560x_show_hiz, bq2560x_store_hiz);
static DEVICE_ATTR(dissafety, S_IRUGO | S_IWUSR, bq2560x_show_dis_safety, bq2560x_store_dis_safety);
static struct attribute *bq2560x_attributes[] = {
&dev_attr_registers.attr,
&dev_attr_BatteryTestStatus.attr,
&dev_attr_hiz.attr,
&dev_attr_dissafety.attr,
NULL,
};
static const struct attribute_group bq2560x_attr_group = {
.attrs = bq2560x_attributes,
};
static int show_registers(struct seq_file *m, void *data)
{
struct bq2560x *bq = m->private;
u8 addr;
int ret;
u8 val;
for (addr = 0x0; addr <= 0x0B; addr++) {
ret = bq2560x_read_byte(bq, &val, addr);
if (!ret)
seq_printf(m, "Reg[%02X] = 0x%02X\n", addr, val);
}
return 0;
}
static int reg_debugfs_open(struct inode *inode, struct file *file)
{
struct bq2560x *bq = inode->i_private;
return single_open(file, show_registers, bq);
}
static const struct file_operations reg_debugfs_ops = {
.owner = THIS_MODULE,
.open = reg_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void create_debugfs_entry(struct bq2560x *bq)
{
bq->debug_root = debugfs_create_dir("bq2560x", NULL);
if (!bq->debug_root)
pr_err("Failed to create debug dir\n");
if (bq->debug_root) {
debugfs_create_file("registers", S_IFREG | S_IRUGO,
bq->debug_root, bq, &reg_debugfs_ops);
debugfs_create_x32("charging_disable_status", S_IFREG | S_IRUGO,
bq->debug_root, &(bq->charging_disabled_status));
debugfs_create_x32("fault_status", S_IFREG | S_IRUGO,
bq->debug_root, &(bq->fault_status));
debugfs_create_x32("vbus_type", S_IFREG | S_IRUGO,
bq->debug_root, &(bq->vbus_type));
debugfs_create_x32("charge_state", S_IFREG | S_IRUGO,
bq->debug_root, &(bq->charge_state));
debugfs_create_x32("skip_reads",
S_IFREG | S_IWUSR | S_IRUGO,
bq->debug_root,
&(bq->skip_reads));
debugfs_create_x32("skip_writes",
S_IFREG | S_IWUSR | S_IRUGO,
bq->debug_root,
&(bq->skip_writes));
}
}
static int bq2560x_charger_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct bq2560x *bq;
struct power_supply *usb_psy;
struct power_supply *bms_psy;
int ret;
usb_psy = power_supply_get_by_name("usb");
if (!usb_psy) {
dev_dbg(&client->dev, "USB supply not found, defer probe\n");
return -EPROBE_DEFER;
}
bms_psy = power_supply_get_by_name("bms");
if (!bms_psy) {
dev_dbg(&client->dev, "bms supply not found, defer probe\n");
return -EPROBE_DEFER;
}
bq = devm_kzalloc(&client->dev, sizeof(struct bq2560x), GFP_KERNEL);
if (!bq) {
pr_err("Out of memory\n");
return -ENOMEM;
}
bq->dev = &client->dev;
bq->usb_psy = usb_psy;
bq->bms_psy = bms_psy;
bq->client = client;
i2c_set_clientdata(client, bq);
mutex_init(&bq->i2c_rw_lock);
mutex_init(&bq->data_lock);
mutex_init(&bq->profile_change_lock);
mutex_init(&bq->charging_disable_lock);
mutex_init(&bq->irq_complete);
bq->resume_completed = true;
bq->irq_waiting = false;
ret = bq2560x_detect_device(bq);
if(ret) {
pr_err("No bq2560x device found!\n");
return -ENODEV;
}
bq2560x_init_jeita(bq);
if (client->dev.of_node)
bq->platform_data = bq2560x_parse_dt(&client->dev, bq);
else
bq->platform_data = client->dev.platform_data;
if (!bq->platform_data) {
pr_err("No platform data provided.\n");
return -EINVAL;
}
if (gpio_is_valid(bq->gpio_ce)) {
ret = devm_gpio_request(&client->dev, bq->gpio_ce, "bq2560x_ce");
if (ret) {
pr_err("Failed to request chip enable gpio %d:, err: %d\n", bq->gpio_ce, ret);
return ret;
}
gpio_direction_output(bq->gpio_ce, 0);
}
ret = bq2560x_init_device(bq);
if (ret) {
pr_err("Failed to init device\n");
return ret;
}
ret = bq2560x_psy_register(bq);
if (ret)
return ret;
ret = bq2560x_regulator_init(bq);
if (ret) {
pr_err("Couldn't initialize bq2560x regulator ret=%d\n", ret);
return ret;
}
INIT_DELAYED_WORK(&bq->charge_jeita_work, bq2560x_charge_jeita_workfunc);
INIT_DELAYED_WORK(&bq->discharge_jeita_work, bq2560x_discharge_jeita_workfunc);
alarm_init(&bq->jeita_alarm, ALARM_BOOTTIME, bq2560x_jeita_alarm_cb);
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
bq2560x_charger_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"bq2560x charger irq", bq);
if (ret < 0) {
pr_err("request irq for irq=%d failed, ret =%d\n", client->irq, ret);
goto err_1;
}
enable_irq_wake(client->irq);
}
bq2560x_wakeup_src_init(bq);
device_init_wakeup(bq->dev, 1);
create_debugfs_entry(bq);
ret = sysfs_create_group(&bq->dev->kobj, &bq2560x_attr_group);
if (ret) {
dev_err(bq->dev, "failed to register sysfs. err: %d\n", ret);
}
determine_initial_status(bq);
pr_err("bq2560x probe successfully, Part Num:%d, Revision:%d\n!",
bq->part_no, bq->revision);
return 0;
err_1:
bq2560x_psy_unregister(bq);
return ret;
}
static inline bool is_device_suspended(struct bq2560x *bq)
{
return !bq->resume_completed;
}
static int bq2560x_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq2560x *bq = i2c_get_clientdata(client);
mutex_lock(&bq->irq_complete);
bq->resume_completed = false;
mutex_unlock(&bq->irq_complete);
return 0;
}
static int bq2560x_suspend_noirq(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq2560x *bq = i2c_get_clientdata(client);
if (bq->irq_waiting) {
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
return -EBUSY;
}
return 0;
}
static int bq2560x_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq2560x *bq = i2c_get_clientdata(client);
mutex_lock(&bq->irq_complete);
bq->resume_completed = true;
if (bq->irq_waiting) {
bq->irq_disabled = false;
enable_irq(client->irq);
mutex_unlock(&bq->irq_complete);
bq2560x_charger_interrupt(client->irq, bq);
} else {
mutex_unlock(&bq->irq_complete);
}
power_supply_changed(&bq->batt_psy);
return 0;
}
static int bq2560x_charger_remove(struct i2c_client *client)
{
struct bq2560x *bq = i2c_get_clientdata(client);
alarm_try_to_cancel(&bq->jeita_alarm);
cancel_delayed_work_sync(&bq->charge_jeita_work);
cancel_delayed_work_sync(&bq->discharge_jeita_work);
regulator_unregister(bq->otg_vreg.rdev);
bq2560x_psy_unregister(bq);
mutex_destroy(&bq->charging_disable_lock);
mutex_destroy(&bq->profile_change_lock);
mutex_destroy(&bq->data_lock);
mutex_destroy(&bq->i2c_rw_lock);
mutex_destroy(&bq->irq_complete);
debugfs_remove_recursive(bq->debug_root);
sysfs_remove_group(&bq->dev->kobj, &bq2560x_attr_group);
return 0;
}
static void bq2560x_charger_shutdown(struct i2c_client *client)
{
}
static struct of_device_id bq2560x_charger_match_table[] = {
{.compatible = "ti,bq25600-charger",},
{.compatible = "ti,bq25601-charger",},
{},
};
MODULE_DEVICE_TABLE(of,bq2560x_charger_match_table);
static const struct i2c_device_id bq2560x_charger_id[] = {
{ "bq25600-charger", BQ25600 },
{ "bq25601-charger", BQ25601 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq2560x_charger_id);
static const struct dev_pm_ops bq2560x_pm_ops = {
.resume = bq2560x_resume,
.suspend_noirq = bq2560x_suspend_noirq,
.suspend = bq2560x_suspend,
};
static struct i2c_driver bq2560x_charger_driver = {
.driver = {
.name = "bq2560x-charger",
.owner = THIS_MODULE,
.of_match_table = bq2560x_charger_match_table,
.pm = &bq2560x_pm_ops,
},
.id_table = bq2560x_charger_id,
.probe = bq2560x_charger_probe,
.remove = bq2560x_charger_remove,
.shutdown = bq2560x_charger_shutdown,
};
module_i2c_driver(bq2560x_charger_driver);
MODULE_DESCRIPTION("TI BQ2560x Charger Driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Texas Instruments");
#ifndef __BQ2560X_HEADER__
#define __BQ2560X_HEADER__
/* Register 00h */
#define BQ2560X_REG_00 0x00
#define REG00_ENHIZ_MASK 0x80
#define REG00_ENHIZ_SHIFT 7
#define REG00_HIZ_ENABLE 1
#define REG00_HIZ_DISABLE 0
#define REG00_STAT_CTRL_MASK 0x60
#define REG00_STAT_CTRL_SHIFT 5
#define REG00_STAT_CTRL_STAT 0
#define REG00_STAT_CTRL_ICHG 1
#define REG00_STAT_CTRL_IINDPM 2
#define REG00_STAT_CTRL_DISABLE 3
#define REG00_IINLIM_MASK 0x1F
#define REG00_IINLIM_SHIFT 0
#define REG00_IINLIM_LSB 100
#define REG00_IINLIM_BASE 100
/* Register 01h */
#define BQ2560X_REG_01 0x01
#define REG01_PFM_DIS_MASK 0x80
#define REG01_PFM_DIS_SHIFT 7
#define REG01_PFM_ENABLE 0
#define REG01_PFM_DISABLE 1
#define REG01_WDT_RESET_MASK 0x40
#define REG01_WDT_RESET_SHIFT 6
#define REG01_WDT_RESET 1
#define REG01_OTG_CONFIG_MASK 0x20
#define REG01_OTG_CONFIG_SHIFT 5
#define REG01_OTG_ENABLE 1
#define REG01_OTG_DISABLE 0
#define REG01_CHG_CONFIG_MASK 0x10
#define REG01_CHG_CONFIG_SHIFT 4
#define REG01_CHG_DISABLE 0
#define REG01_CHG_ENABLE 1
#define REG01_SYS_MINV_MASK 0x0E
#define REG01_SYS_MINV_SHIFT 1
#define REG01_MIN_VBAT_SEL_MASK 0x01
#define REG01_MIN_VBAT_SEL_SHIFT 0
#define REG01_MIN_VBAT_2P8V 0
#define REG01_MIN_VBAT_2P5V 1
/* Register 0x02*/
#define BQ2560X_REG_02 0x02
#define REG02_BOOST_LIM_MASK 0x80
#define REG02_BOOST_LIM_SHIFT 7
#define REG02_BOOST_LIM_0P5A 0
#define REG02_BOOST_LIM_1P2A 1
#define REG02_Q1_FULLON_MASK 0x40
#define REG02_Q1_FULLON_SHIFT 6
#define REG02_Q1_FULLON_ENABLE 1
#define REG02_Q1_FULLON_DISABLE 0
#define REG02_ICHG_MASK 0x3F
#define REG02_ICHG_SHIFT 0
#define REG02_ICHG_BASE 0
#define REG02_ICHG_LSB 60
/* Register 0x03*/
#define BQ2560X_REG_03 0x03
#define REG03_IPRECHG_MASK 0xF0
#define REG03_IPRECHG_SHIFT 4
#define REG03_IPRECHG_BASE 60
#define REG03_IPRECHG_LSB 60
#define REG03_ITERM_MASK 0x0F
#define REG03_ITERM_SHIFT 0
#define REG03_ITERM_BASE 60
#define REG03_ITERM_LSB 60
/* Register 0x04*/
#define BQ2560X_REG_04 0x04
#define REG04_VREG_MASK 0xF8
#define REG04_VREG_SHIFT 3
#define REG04_VREG_BASE 3856
#define REG04_VREG_LSB 32
#define REG04_TOPOFF_TIMER_MASK 0x06
#define REG04_TOPOFF_TIMER_SHIFT 1
#define REG04_TOPOFF_TIMER_DISABLE 0
#define REG04_TOPOFF_TIMER_15M 1
#define REG04_TOPOFF_TIMER_30M 2
#define REG04_TOPOFF_TIMER_45M 3
#define REG04_VRECHG_MASK 0x01
#define REG04_VRECHG_SHIFT 0
#define REG04_VRECHG_100MV 0
#define REG04_VRECHG_200MV 1
/* Register 0x05*/
#define BQ2560X_REG_05 0x05
#define REG05_EN_TERM_MASK 0x80
#define REG05_EN_TERM_SHIFT 7
#define REG05_TERM_ENABLE 1
#define REG05_TERM_DISABLE 0
#define REG05_WDT_MASK 0x30
#define REG05_WDT_SHIFT 4
#define REG05_WDT_DISABLE 0
#define REG05_WDT_40S 1
#define REG05_WDT_80S 2
#define REG05_WDT_160S 3
#define REG05_WDT_BASE 0
#define REG05_WDT_LSB 40
#define REG05_EN_TIMER_MASK 0x08
#define REG05_EN_TIMER_SHIFT 3
#define REG05_CHG_TIMER_ENABLE 1
#define REG05_CHG_TIMER_DISABLE 0
#define REG05_CHG_TIMER_MASK 0x04
#define REG05_CHG_TIMER_SHIFT 2
#define REG05_CHG_TIMER_5HOURS 0
#define REG05_CHG_TIMER_10HOURS 1
#define REG05_TREG_MASK 0x02
#define REG05_TREG_SHIFT 1
#define REG05_TREG_90C 0
#define REG05_TREG_110C 1
#define REG05_JEITA_ISET_MASK 0x01
#define REG05_JEITA_ISET_SHIFT 0
#define REG05_JEITA_ISET_50PCT 0
#define REG05_JEITA_ISET_20PCT 1
/* Register 0x06*/
#define BQ2560X_REG_06 0x06
#define REG06_OVP_MASK 0xC0
#define REG06_OVP_SHIFT 0x6
#define REG06_OVP_5P5V 0
#define REG06_OVP_6P2V 1
#define REG06_OVP_10P5V 2
#define REG06_OVP_14P3V 3
#define REG06_BOOSTV_MASK 0x30
#define REG06_BOOSTV_SHIFT 4
#define REG06_BOOSTV_4P85V 0
#define REG06_BOOSTV_5V 1
#define REG06_BOOSTV_5P15V 2
#define REG06_BOOSTV_5P3V 3
#define REG06_VINDPM_MASK 0x0F
#define REG06_VINDPM_SHIFT 0
#define REG06_VINDPM_BASE 3900
#define REG06_VINDPM_LSB 100
/* Register 0x07*/
#define BQ2560X_REG_07 0x07
#define REG07_FORCE_DPDM_MASK 0x80
#define REG07_FORCE_DPDM_SHIFT 7
#define REG07_FORCE_DPDM 1
#define REG07_TMR2X_EN_MASK 0x40
#define REG07_TMR2X_EN_SHIFT 6
#define REG07_TMR2X_ENABLE 1
#define REG07_TMR2X_DISABLE 0
#define REG07_BATFET_DIS_MASK 0x20
#define REG07_BATFET_DIS_SHIFT 5
#define REG07_BATFET_OFF 1
#define REG07_BATFET_ON 0
#define REG07_JEITA_VSET_MASK 0x10
#define REG07_JEITA_VSET_SHIFT 4
#define REG07_JEITA_VSET_4100 0
#define REG07_JEITA_VSET_VREG 1
#define REG07_BATFET_DLY_MASK 0x08
#define REG07_BATFET_DLY_SHIFT 3
#define REG07_BATFET_DLY_0S 0
#define REG07_BATFET_DLY_10S 1
#define REG07_BATFET_RST_EN_MASK 0x04
#define REG07_BATFET_RST_EN_SHIFT 2
#define REG07_BATFET_RST_DISABLE 0
#define REG07_BATFET_RST_ENABLE 1
#define REG07_VDPM_BAT_TRACK_MASK 0x03
#define REG07_VDPM_BAT_TRACK_SHIFT 0
#define REG07_VDPM_BAT_TRACK_DISABLE 0
#define REG07_VDPM_BAT_TRACK_200MV 1
#define REG07_VDPM_BAT_TRACK_250MV 2
#define REG07_VDPM_BAT_TRACK_300MV 3
/* Register 0x08*/
#define BQ2560X_REG_08 0x08
#define REG08_VBUS_STAT_MASK 0xE0
#define REG08_VBUS_STAT_SHIFT 5
#define REG08_VBUS_TYPE_NONE 0
#define REG08_VBUS_TYPE_USB 1
#define REG08_VBUS_TYPE_ADAPTER 3
#define REG08_VBUS_TYPE_OTG 7
#define REG08_CHRG_STAT_MASK 0x18
#define REG08_CHRG_STAT_SHIFT 3
#define REG08_CHRG_STAT_IDLE 0
#define REG08_CHRG_STAT_PRECHG 1
#define REG08_CHRG_STAT_FASTCHG 2
#define REG08_CHRG_STAT_CHGDONE 3
#define REG08_PG_STAT_MASK 0x04
#define REG08_PG_STAT_SHIFT 2
#define REG08_POWER_GOOD 1
#define REG08_THERM_STAT_MASK 0x02
#define REG08_THERM_STAT_SHIFT 1
#define REG08_VSYS_STAT_MASK 0x01
#define REG08_VSYS_STAT_SHIFT 0
#define REG08_IN_VSYS_STAT 1
/* Register 0x09*/
#define BQ2560X_REG_09 0x09
#define REG09_FAULT_WDT_MASK 0x80
#define REG09_FAULT_WDT_SHIFT 7
#define REG09_FAULT_WDT 1
#define REG09_FAULT_BOOST_MASK 0x40
#define REG09_FAULT_BOOST_SHIFT 6
#define REG09_FAULT_CHRG_MASK 0x30
#define REG09_FAULT_CHRG_SHIFT 4
#define REG09_FAULT_CHRG_NORMAL 0
#define REG09_FAULT_CHRG_INPUT 1
#define REG09_FAULT_CHRG_THERMAL 2
#define REG09_FAULT_CHRG_TIMER 3
#define REG09_FAULT_BAT_MASK 0x08
#define REG09_FAULT_BAT_SHIFT 3
#define REG09_FAULT_BAT_OVP 1
#define REG09_FAULT_NTC_MASK 0x07
#define REG09_FAULT_NTC_SHIFT 0
#define REG09_FAULT_NTC_NORMAL 0
#define REG09_FAULT_NTC_WARM 2
#define REG09_FAULT_NTC_COOL 3
#define REG09_FAULT_NTC_COLD 5
#define REG09_FAULT_NTC_HOT 6
/* Register 0x0A */
#define BQ2560X_REG_0A 0x0A
#define REG0A_VBUS_GD_MASK 0x80
#define REG0A_VBUS_GD_SHIFT 7
#define REG0A_VBUS_GD 1
#define REG0A_VINDPM_STAT_MASK 0x40
#define REG0A_VINDPM_STAT_SHIFT 6
#define REG0A_VINDPM_ACTIVE 1
#define REG0A_IINDPM_STAT_MASK 0x20
#define REG0A_IINDPM_STAT_SHIFT 5
#define REG0A_IINDPM_ACTIVE 1
#define REG0A_TOPOFF_ACTIVE_MASK 0x08
#define REG0A_TOPOFF_ACTIVE_SHIFT 3
#define REG0A_TOPOFF_ACTIVE 1
#define REG0A_ACOV_STAT_MASK 0x04
#define REG0A_ACOV_STAT_SHIFT 2
#define REG0A_ACOV_ACTIVE 1
#define REG0A_VINDPM_INT_MASK 0x02
#define REG0A_VINDPM_INT_SHIFT 1
#define REG0A_VINDPM_INT_ENABLE 0
#define REG0A_VINDPM_INT_DISABLE 1
#define REG0A_IINDPM_INT_MASK 0x01
#define REG0A_IINDPM_INT_SHIFT 0
#define REG0A_IINDPM_INT_ENABLE 0
#define REG0A_IINDPM_INT_DISABLE 1
#define REG0A_INT_MASK_MASK 0x03
#define REG0A_INT_MASK_SHIFT 0
#define BQ2560X_REG_0B 0x0B
#define REG0B_REG_RESET_MASK 0x80
#define REG0B_REG_RESET_SHIFT 7
#define REG0B_REG_RESET 1
#define REG0B_PN_MASK 0x78
#define REG0B_PN_SHIFT 3
#define REG0B_DEV_REV_MASK 0x03
#define REG0B_DEV_REV_SHIFT 0
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment