[PATCH 4/4] power_supply: bq24261 charger driver

From: Jenny TC
Date: Wed Jan 22 2014 - 04:28:21 EST


This patch introduces BQ24261 charger driver. The driver makes use of power
supply charging driver to setup charging. So the driver does hardware
abstraction and handles h/w specific corner cases. The charging logic resides
with power supply charging driver

Signed-off-by: Jenny TC <jenny.tc@xxxxxxxxx>
---
drivers/power/Kconfig | 10 +
drivers/power/Makefile | 1 +
drivers/power/bq24261_charger.c | 1447 +++++++++++++++++++++++++++++++++
include/linux/power/bq24261_charger.h | 33 +
4 files changed, 1491 insertions(+)
create mode 100644 drivers/power/bq24261_charger.c
create mode 100644 include/linux/power/bq24261_charger.h

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 655457b..3f32f61 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -408,6 +408,16 @@ config BATTERY_GOLDFISH
Say Y to enable support for the battery and AC power in the
Goldfish emulator.

+config BQ24261_CHARGER
+ tristate "BQ24261 charger driver"
+ select POWER_SUPPLY_CHARGER
+ depends on I2C
+ help
+ Say Y to include support for BQ24261 Charger driver. This driver
+ makes use of power supply charging driver. So the driver gives
+ the charger hardware abstraction only. Charging logic is abstracted
+ in the power supply charging driver.
+
source "drivers/power/reset/Kconfig"

endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 77535fd..6d184c8 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -59,4 +59,5 @@ obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
+obj-$(CONFIG_BQ24261_CHARGER) += bq24261_charger.o
obj-$(CONFIG_POWER_RESET) += reset/
diff --git a/drivers/power/bq24261_charger.c b/drivers/power/bq24261_charger.c
new file mode 100644
index 0000000..6ac063b
--- /dev/null
+++ b/drivers/power/bq24261_charger.c
@@ -0,0 +1,1447 @@
+/*
+ * bq24261_charger.c - BQ24261 Charger I2C client driver
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Jenny TC <jenny.tc@xxxxxxxxx>
+ */
+#define DEBUG
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/usb/otg.h>
+#include <linux/power/power_supply_charger.h>
+#include <linux/power/bq24261_charger.h>
+#include <linux/seq_file.h>
+
+#include <asm/intel_scu_ipc.h>
+
+#define DEV_NAME "bq24261_charger"
+#define DEV_MANUFACTURER "TI"
+#define MODEL_NAME_SIZE 8
+#define DEV_MANUFACTURER_NAME_SIZE 4
+
+#define EXCEPTION_MONITOR_DELAY (60 * HZ)
+#define WDT_RESET_DELAY (15 * HZ)
+
+/* BQ24261 registers */
+#define BQ24261_STAT_CTRL0_ADDR 0x00
+#define BQ24261_CTRL_ADDR 0x01
+#define BQ24261_BATT_VOL_CTRL_ADDR 0x02
+#define BQ24261_VENDOR_REV_ADDR 0x03
+#define BQ24261_TERM_FCC_ADDR 0x04
+#define BQ24261_VINDPM_STAT_ADDR 0x05
+#define BQ24261_ST_NTC_MON_ADDR 0x06
+
+#define BQ24261_RESET_MASK (0x01 << 7)
+#define BQ24261_RESET_ENABLE (0x01 << 7)
+
+#define BQ24261_FAULT_MASK 0x07
+#define BQ24261_STAT_MASK (0x03 << 4)
+#define BQ24261_BOOST_MASK (0x01 << 6)
+#define BQ24261_TMR_RST_MASK (0x01 << 7)
+#define BQ24261_TMR_RST (0x01 << 7)
+
+#define BQ24261_ENABLE_BOOST (0x01 << 6)
+
+#define BQ24261_VOVP 0x01
+#define BQ24261_LOW_SUPPLY 0x02
+#define BQ24261_THERMAL_SHUTDOWN 0x03
+#define BQ24261_BATT_TEMP_FAULT 0x04
+#define BQ24261_TIMER_FAULT 0x05
+#define BQ24261_BATT_OVP 0x06
+#define BQ24261_NO_BATTERY 0x07
+#define BQ24261_STAT_READY 0x00
+
+#define BQ24261_STAT_CHRG_PRGRSS (0x01 << 4)
+#define BQ24261_STAT_CHRG_DONE (0x02 << 4)
+#define BQ24261_STAT_FAULT (0x03 << 4)
+
+#define BQ24261_CE_MASK (0x01 << 1)
+#define BQ24261_CE_DISABLE (0x01 << 1)
+
+#define BQ24261_HZ_MASK (0x01)
+#define BQ24261_HZ_ENABLE (0x01)
+
+#define BQ24261_ICHRG_MASK (0x1F << 3)
+#define BQ24261_ICHRG_100ma (0x01 << 3)
+#define BQ24261_ICHRG_200ma (0x01 << 4)
+#define BQ24261_ICHRG_400ma (0x01 << 5)
+#define BQ24261_ICHRG_800ma (0x01 << 6)
+#define BQ24261_ICHRG_1600ma (0x01 << 7)
+
+#define BQ24261_ITERM_MASK (0x03)
+#define BQ24261_ITERM_50ma (0x01 << 0)
+#define BQ24261_ITERM_100ma (0x01 << 1)
+#define BQ24261_ITERM_200ma (0x01 << 2)
+
+#define BQ24261_VBREG_MASK (0x3F << 2)
+
+#define BQ24261_INLMT_MASK (0x03 << 4)
+#define BQ24261_INLMT_100 0x00
+#define BQ24261_INLMT_150 (0x01 << 4)
+#define BQ24261_INLMT_500 (0x02 << 4)
+#define BQ24261_INLMT_900 (0x03 << 4)
+#define BQ24261_INLMT_1500 (0x04 << 4)
+#define BQ24261_INLMT_2500 (0x06 << 4)
+
+#define BQ24261_TE_MASK (0x01 << 2)
+#define BQ24261_TE_ENABLE (0x01 << 2)
+#define BQ24261_STAT_ENABLE_MASK (0x01 << 3)
+#define BQ24261_STAT_ENABLE (0x01 << 3)
+
+#define BQ24261_VENDOR_MASK (0x07 << 5)
+#define BQ24261_VENDOR (0x02 << 5)
+#define BQ24261_REV_MASK (0x07)
+#define BQ24261_REV (0x02)
+#define BQ24260_REV (0x01)
+
+#define BQ24261_TS_MASK (0x01 << 3)
+#define BQ24261_TS_ENABLED (0x01 << 3)
+#define BQ24261_BOOST_ILIM_MASK (0x01 << 4)
+#define BQ24261_BOOST_ILIM_500ma (0x0)
+#define BQ24261_BOOST_ILIM_1A (0x01 << 4)
+
+#define BQ24261_SAFETY_TIMER_MASK (0x03 << 5)
+#define BQ24261_SAFETY_TIMER_40MIN 0x00
+#define BQ24261_SAFETY_TIMER_6HR (0x01 << 5)
+#define BQ24261_SAFETY_TIMER_9HR (0x02 << 5)
+#define BQ24261_SAFETY_TIMER_DISABLED (0x03 << 5)
+
+/* 1% above voltage max design to report over voltage */
+#define BQ24261_OVP_MULTIPLIER 1010
+#define BQ24261_OVP_RECOVER_MULTIPLIER 990
+#define BQ24261_DEF_BAT_VOLT_MAX_DESIGN 4200000
+
+/* Settings for Voltage / DPPM Register (05) */
+#define BQ24261_VBATT_LEVEL1 3700000
+#define BQ24261_VBATT_LEVEL2 3960000
+#define BQ24261_VINDPM_MASK (0x07)
+#define BQ24261_VINDPM_320MV (0x01 << 2)
+#define BQ24261_VINDPM_160MV (0x01 << 1)
+#define BQ24261_VINDPM_80MV (0x01 << 0)
+#define BQ24261_CD_STATUS_MASK (0x01 << 3)
+#define BQ24261_DPM_EN_MASK (0x01 << 4)
+#define BQ24261_DPM_EN_FORCE (0x01 << 4)
+#define BQ24261_LOW_CHG_MASK (0x01 << 5)
+#define BQ24261_LOW_CHG_EN (0x01 << 5)
+#define BQ24261_LOW_CHG_DIS (~BQ24261_LOW_CHG_EN)
+#define BQ24261_DPM_STAT_MASK (0x01 << 6)
+#define BQ24261_MINSYS_STAT_MASK (0x01 << 7)
+
+#define BQ24261_MIN_CC 500
+
+u16 bq24261_sfty_tmr[][2] = {
+ {0, BQ24261_SAFETY_TIMER_DISABLED}
+ ,
+ {40, BQ24261_SAFETY_TIMER_40MIN}
+ ,
+ {360, BQ24261_SAFETY_TIMER_6HR}
+ ,
+ {540, BQ24261_SAFETY_TIMER_9HR}
+ ,
+};
+
+
+u16 bq24261_inlmt[][2] = {
+ {100, BQ24261_INLMT_100}
+ ,
+ {150, BQ24261_INLMT_150}
+ ,
+ {500, BQ24261_INLMT_500}
+ ,
+ {900, BQ24261_INLMT_900}
+ ,
+ {1500, BQ24261_INLMT_1500}
+ ,
+ {2500, BQ24261_INLMT_2500}
+ ,
+};
+
+u16 bq24261_iterm[][2] = {
+ {0, 0x00}
+ ,
+ {50, BQ24261_ITERM_50ma}
+ ,
+ {100, BQ24261_ITERM_100ma}
+ ,
+ {150, BQ24261_ITERM_100ma | BQ24261_ITERM_50ma}
+ ,
+ {200, BQ24261_ITERM_200ma}
+ ,
+ {250, BQ24261_ITERM_200ma | BQ24261_ITERM_50ma}
+ ,
+ {300, BQ24261_ITERM_200ma | BQ24261_ITERM_100ma}
+ ,
+ {350, BQ24261_ITERM_200ma | BQ24261_ITERM_100ma | BQ24261_ITERM_50ma}
+ ,
+};
+
+u16 bq24261_cc[][2] = {
+
+ {500, 0x00}
+ ,
+ {600, BQ24261_ICHRG_100ma}
+ ,
+ {700, BQ24261_ICHRG_200ma}
+ ,
+ {800, BQ24261_ICHRG_100ma | BQ24261_ICHRG_200ma}
+ ,
+ {900, BQ24261_ICHRG_400ma}
+ ,
+ {1000, BQ24261_ICHRG_400ma | BQ24261_ICHRG_100ma}
+ ,
+ {1100, BQ24261_ICHRG_400ma | BQ24261_ICHRG_200ma}
+ ,
+ {1200, BQ24261_ICHRG_400ma | BQ24261_ICHRG_200ma | BQ24261_ICHRG_100ma}
+ ,
+ {1300, BQ24261_ICHRG_800ma}
+ ,
+ {1400, BQ24261_ICHRG_800ma | BQ24261_ICHRG_100ma}
+ ,
+ {1500, BQ24261_ICHRG_800ma | BQ24261_ICHRG_200ma}
+ ,
+};
+
+#define BQ24261_MIN_CV 3500
+#define BQ24261_MAX_CV 4440
+#define BQ24261_CV_DIV 20
+#define BQ24261_CV_BIT_POS 2
+
+static enum power_supply_property bq24261_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_INLMT,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CUR,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_TEMP_MAX,
+ POWER_SUPPLY_PROP_TEMP_MIN,
+};
+
+enum bq24261_chrgr_stat {
+ BQ24261_CHRGR_STAT_UNKNOWN,
+ BQ24261_CHRGR_STAT_READY,
+ BQ24261_CHRGR_STAT_CHARGING,
+ BQ24261_CHRGR_STAT_BAT_FULL,
+ BQ24261_CHRGR_STAT_FAULT,
+};
+
+struct bq24261_charger {
+
+ struct mutex lock;
+ struct i2c_client *client;
+ struct bq24261_plat_data *pdata;
+ struct power_supply psy_usb;
+ struct power_supply_charger psyc_usb;
+ struct delayed_work notify_work;
+ struct delayed_work low_supply_fault_work;
+ struct delayed_work exception_mon_work;
+ struct list_head irq_queue;
+ wait_queue_head_t wait_ready;
+
+ int chrgr_health;
+ int bat_health;
+ int cc;
+ int cv;
+ int inlmt;
+ int max_cc;
+ int max_cv;
+ int iterm;
+ int cable_type;
+ int cntl_state;
+ int max_temp;
+ int min_temp;
+ enum bq24261_chrgr_stat chrgr_stat;
+ bool online;
+ bool present;
+ bool is_charging_enabled;
+ bool is_charger_enabled;
+ bool is_vsys_on;
+ bool is_hw_chrg_term;
+ char model_name[MODEL_NAME_SIZE];
+ char manufacturer[DEV_MANUFACTURER_NAME_SIZE];
+};
+
+enum bq2426x_model_num {
+ BQ24260 = 0,
+ BQ24261,
+};
+
+struct bq2426x_model {
+ char model_name[MODEL_NAME_SIZE];
+ enum bq2426x_model_num model;
+};
+
+static struct bq2426x_model bq24261_model_name[] = {
+ { "bq24260", BQ24260 },
+ { "bq24261", BQ24261 },
+};
+
+struct i2c_client *bq24261_client;
+static inline int get_battery_voltage(int *volt);
+static inline int get_battery_current(int *cur);
+static int bq24261_handle_irq(struct bq24261_charger *chip, u8 stat_reg);
+static inline int bq24261_set_iterm(struct bq24261_charger *chip, int iterm);
+static inline int bq24261_enable_hw_charge_term(struct bq24261_charger *chip,
+ bool val);
+
+enum power_supply_type get_power_supply_type(
+ enum psy_charger_cable_type cable)
+{
+
+ switch (cable) {
+
+ case PSY_CHARGER_CABLE_TYPE_USB_DCP:
+ return POWER_SUPPLY_TYPE_USB_DCP;
+ case PSY_CHARGER_CABLE_TYPE_USB_CDP:
+ return POWER_SUPPLY_TYPE_USB_CDP;
+ case PSY_CHARGER_CABLE_TYPE_USB_ACA:
+ case PSY_CHARGER_CABLE_TYPE_ACA_DOCK:
+ return POWER_SUPPLY_TYPE_USB_ACA;
+ case PSY_CHARGER_CABLE_TYPE_AC:
+ return POWER_SUPPLY_TYPE_MAINS;
+ case PSY_CHARGER_CABLE_TYPE_NONE:
+ case PSY_CHARGER_CABLE_TYPE_USB_SDP:
+ return POWER_SUPPLY_TYPE_USB;
+ default:
+ return POWER_SUPPLY_TYPE_UNKNOWN;
+ }
+
+ return POWER_SUPPLY_TYPE_USB;
+}
+
+static void lookup_regval(u16 tbl[][2], size_t size, u16 in_val, u8 *out_val)
+{
+ int i;
+ for (i = 1; i < size; ++i)
+ if (in_val < tbl[i][0])
+ break;
+
+ *out_val = (u8) tbl[i - 1][1];
+}
+
+void bq24261_cc_to_reg(int cc, u8 *reg_val)
+{
+ return lookup_regval(bq24261_cc, ARRAY_SIZE(bq24261_cc), cc, reg_val);
+
+}
+
+void bq24261_cv_to_reg(int cv, u8 *reg_val)
+{
+ int val;
+
+ val = clamp_t(int, cv, BQ24261_MIN_CV, BQ24261_MAX_CV);
+ *reg_val =
+ (((val - BQ24261_MIN_CV) / BQ24261_CV_DIV)
+ << BQ24261_CV_BIT_POS);
+}
+
+void bq24261_inlmt_to_reg(int inlmt, u8 *regval)
+{
+ return lookup_regval(bq24261_inlmt, ARRAY_SIZE(bq24261_inlmt),
+ inlmt, regval);
+}
+
+static inline void bq24261_iterm_to_reg(int iterm, u8 *regval)
+{
+ return lookup_regval(bq24261_iterm, ARRAY_SIZE(bq24261_iterm),
+ iterm, regval);
+}
+
+static inline void bq24261_sfty_tmr_to_reg(int tmr, u8 *regval)
+{
+ return lookup_regval(bq24261_sfty_tmr, ARRAY_SIZE(bq24261_sfty_tmr),
+ tmr, regval);
+}
+
+static inline int bq24261_read_reg(struct i2c_client *client, u8 reg)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0)
+ dev_err(&client->dev, "Error(%d) in reading reg %d\n", ret,
+ reg);
+
+ return ret;
+}
+
+static inline int bq24261_write_reg(struct i2c_client *client, u8 reg, u8 data)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, data);
+ if (ret < 0)
+ dev_err(&client->dev, "Error(%d) in writing %d to reg %d\n",
+ ret, data, reg);
+
+ return ret;
+}
+
+static inline int bq24261_read_modify_reg(struct i2c_client *client, u8 reg,
+ u8 mask, u8 val)
+{
+ int ret;
+
+ ret = bq24261_read_reg(client, reg);
+ if (ret < 0)
+ return ret;
+ ret = (ret & ~mask) | (mask & val);
+ return bq24261_write_reg(client, reg, ret);
+}
+
+static inline int bq24261_tmr_ntc_init(struct bq24261_charger *chip)
+{
+ u8 reg_val;
+ int ret;
+
+ bq24261_sfty_tmr_to_reg(chip->pdata->safety_timer, &reg_val);
+
+ if (chip->pdata->is_ts_enabled)
+ reg_val |= BQ24261_TS_ENABLED;
+
+ ret = bq24261_read_modify_reg(chip->client, BQ24261_ST_NTC_MON_ADDR,
+ BQ24261_TS_MASK|BQ24261_SAFETY_TIMER_MASK|
+ BQ24261_BOOST_ILIM_MASK, reg_val);
+
+ return ret;
+}
+
+static inline int bq24261_enable_charging(
+ struct bq24261_charger *chip, bool val)
+{
+ int ret;
+ u8 reg_val;
+ bool is_ready;
+
+ ret = bq24261_read_reg(chip->client,
+ BQ24261_STAT_CTRL0_ADDR);
+ if (ret < 0) {
+ dev_err(&chip->client->dev,
+ "Error(%d) in reading BQ24261_STAT_CTRL0_ADDR\n", ret);
+ }
+
+ is_ready = (ret & BQ24261_STAT_MASK) != BQ24261_STAT_FAULT;
+
+ /* If status is fault, wait for READY before enabling the charging */
+
+ if (!is_ready) {
+ ret = wait_event_timeout(chip->wait_ready,
+ (chip->chrgr_stat != BQ24261_CHRGR_STAT_READY),
+ HZ);
+ dev_info(&chip->client->dev,
+ "chrgr_stat=%x\n", chip->chrgr_stat);
+ if (ret == 0) {
+ dev_err(&chip->client->dev,
+ "Waiting for Charger Ready Failed.Enabling charging anyway\n");
+ }
+ }
+
+ if (val) {
+ reg_val = (~BQ24261_CE_DISABLE & BQ24261_CE_MASK);
+ if (chip->is_hw_chrg_term)
+ reg_val |= BQ24261_TE_ENABLE;
+ } else {
+ reg_val = BQ24261_CE_DISABLE;
+ }
+
+ reg_val |= BQ24261_STAT_ENABLE;
+
+ ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
+ BQ24261_STAT_ENABLE_MASK|BQ24261_RESET_MASK|
+ BQ24261_CE_MASK|BQ24261_TE_MASK,
+ reg_val);
+ if (ret || !val) {
+ cancel_delayed_work_sync(&chip->notify_work);
+ return ret;
+ }
+
+ bq24261_set_iterm(chip, chip->iterm);
+ schedule_delayed_work(&chip->notify_work, 0);
+ bq24261_enable_hw_charge_term(chip, true);
+ return bq24261_tmr_ntc_init(chip);
+}
+
+static inline int bq24261_reset_timer(struct bq24261_charger *chip)
+{
+ return bq24261_read_modify_reg(chip->client, BQ24261_STAT_CTRL0_ADDR,
+ BQ24261_TMR_RST_MASK, BQ24261_TMR_RST);
+}
+
+static inline int bq24261_enable_charger(
+ struct bq24261_charger *chip, int val)
+{
+
+ /* TODO: Implement enable/disable HiZ mode to enable/
+ * disable charger
+ */
+ u8 reg_val;
+ int ret;
+
+ reg_val = val ? (~BQ24261_HZ_ENABLE & BQ24261_HZ_MASK) :
+ BQ24261_HZ_ENABLE;
+
+ ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
+ BQ24261_HZ_MASK|BQ24261_RESET_MASK, reg_val);
+ if (ret)
+ return ret;
+
+ return bq24261_reset_timer(chip);
+}
+
+static inline int bq24261_set_cc(struct bq24261_charger *chip, int cc)
+{
+ u8 reg_val;
+ int ret;
+
+ dev_dbg(&chip->client->dev, "cc=%d\n", cc);
+
+ if (cc && (cc < BQ24261_MIN_CC)) {
+ dev_dbg(&chip->client->dev, "Set LOW_CHG bit\n");
+ reg_val = BQ24261_LOW_CHG_EN;
+ ret = bq24261_read_modify_reg(chip->client,
+ BQ24261_VINDPM_STAT_ADDR,
+ BQ24261_LOW_CHG_MASK, reg_val);
+ } else {
+ dev_dbg(&chip->client->dev, "Clear LOW_CHG bit\n");
+ reg_val = BQ24261_LOW_CHG_DIS;
+ ret = bq24261_read_modify_reg(chip->client,
+ BQ24261_VINDPM_STAT_ADDR,
+ BQ24261_LOW_CHG_MASK, reg_val);
+ }
+
+ bq24261_cc_to_reg(cc, &reg_val);
+
+ return bq24261_read_modify_reg(chip->client, BQ24261_TERM_FCC_ADDR,
+ BQ24261_ICHRG_MASK, reg_val);
+}
+
+static inline int bq24261_set_cv(struct bq24261_charger *chip, int cv)
+{
+ int bat_volt;
+ int ret;
+ u8 reg_val;
+ u8 vindpm_val = 0x0;
+
+ /*
+ * Setting VINDPM value as per the battery voltage
+ * VBatt Vindpm Register Setting
+ * < 3.7v 4.2v 0x0 (default)
+ * 3.71v - 3.96v 4.36v 0x2
+ * > 3.96v 4.6v 0x5
+ */
+ ret = get_battery_voltage(&bat_volt);
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Error getting battery voltage!!\n");
+ } else {
+ if (bat_volt > BQ24261_VBATT_LEVEL2)
+ vindpm_val =
+ (BQ24261_VINDPM_320MV | BQ24261_VINDPM_80MV);
+ else if (bat_volt > BQ24261_VBATT_LEVEL1)
+ vindpm_val = BQ24261_VINDPM_160MV;
+ }
+
+ ret = bq24261_read_modify_reg(chip->client,
+ BQ24261_VINDPM_STAT_ADDR,
+ BQ24261_VINDPM_MASK,
+ vindpm_val);
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Error setting VINDPM setting!!\n");
+ return ret;
+ }
+
+
+ bq24261_cv_to_reg(cv, &reg_val);
+
+ return bq24261_read_modify_reg(chip->client, BQ24261_BATT_VOL_CTRL_ADDR,
+ BQ24261_VBREG_MASK, reg_val);
+}
+
+static inline int bq24261_set_inlmt(struct bq24261_charger *chip, int inlmt)
+{
+ u8 reg_val;
+
+ bq24261_inlmt_to_reg(inlmt, &reg_val);
+
+ return bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
+ BQ24261_RESET_MASK|BQ24261_INLMT_MASK, reg_val);
+
+}
+
+static inline void resume_charging(struct bq24261_charger *chip)
+{
+
+ if (chip->is_charger_enabled)
+ bq24261_enable_charger(chip, true);
+ if (chip->inlmt)
+ bq24261_set_inlmt(chip, chip->inlmt);
+ if (chip->cc)
+ bq24261_set_cc(chip, chip->cc);
+ if (chip->cv)
+ bq24261_set_cv(chip, chip->cv);
+ if (chip->is_charging_enabled)
+ bq24261_enable_charging(chip, true);
+}
+
+static inline int bq24261_set_iterm(struct bq24261_charger *chip, int iterm)
+{
+ u8 reg_val;
+
+ bq24261_iterm_to_reg(iterm, &reg_val);
+
+ return bq24261_read_modify_reg(chip->client, BQ24261_TERM_FCC_ADDR,
+ BQ24261_ITERM_MASK, reg_val);
+}
+
+static inline int bq24261_enable_hw_charge_term(
+ struct bq24261_charger *chip, bool val)
+{
+ u8 data;
+ int ret;
+
+ data = val ? BQ24261_TE_ENABLE : (~BQ24261_TE_ENABLE & BQ24261_TE_MASK);
+
+
+ ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
+ BQ24261_RESET_MASK|BQ24261_TE_MASK, data);
+
+ if (ret)
+ return ret;
+
+ chip->is_hw_chrg_term = val ? true : false;
+
+ return ret;
+}
+
+
+static inline bool bq24261_is_vsys_on(struct bq24261_charger *chip)
+{
+ int ret;
+ struct i2c_client *client = chip->client;
+
+ ret = bq24261_read_reg(client, BQ24261_CTRL_ADDR);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Error(%d) in reading BQ24261_CTRL_ADDR\n", ret);
+ return false;
+ }
+
+ if (((ret & BQ24261_HZ_MASK) == BQ24261_HZ_ENABLE) &&
+ chip->is_charger_enabled) {
+ dev_err(&client->dev, "Charger in Hi Z Mode\n");
+ return false;
+ }
+
+ ret = bq24261_read_reg(client, BQ24261_VINDPM_STAT_ADDR);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Error(%d) in reading BQ24261_VINDPM_STAT_ADDR\n", ret);
+ return false;
+ }
+
+ if (ret & BQ24261_CD_STATUS_MASK) {
+ dev_err(&client->dev, "CD line asserted\n");
+ return false;
+ }
+
+ return true;
+}
+
+
+static inline bool is_bq24261_enabled(struct bq24261_charger *chip)
+{
+ if (chip->cable_type == PSY_CHARGER_CABLE_TYPE_NONE)
+ return false;
+ else if (!chip->is_charger_enabled)
+ return false;
+ /* BQ24261 gives interrupt only on stop/resume charging.
+ * If charging is already stopped, we need to query the hardware
+ * to see charger is still active and can supply vsys or not.
+ */
+ else if ((chip->chrgr_stat == BQ24261_CHRGR_STAT_FAULT) ||
+ (!chip->is_charging_enabled))
+ return bq24261_is_vsys_on(chip);
+ else
+ return chip->is_vsys_on;
+}
+
+static int bq24261_usb_psyc_set_property(struct power_supply_charger *psyc,
+ enum power_supply_charger_property pscp,
+ const union power_supply_propval *val)
+{
+ struct bq24261_charger *chip = container_of(psyc,
+ struct bq24261_charger,
+ psyc_usb);
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
+ dev_dbg(&chip->client->dev, "%s: prop=%d, val=%d\n",
+ __func__, pscp, val->intval);
+
+ switch (pscp) {
+
+ case POWER_SUPPLY_CHARGER_PROP_ENABLE_CHARGING:
+ ret = bq24261_enable_charging(chip, val->intval);
+
+ if (ret)
+ dev_err(&chip->client->dev,
+ "Error(%d) in %s charging", ret,
+ (val->intval ? "enable" : "disable"));
+ else
+ chip->is_charging_enabled = val->intval;
+
+ break;
+ case POWER_SUPPLY_CHARGER_PROP_ENABLE_CHARGER:
+ /* Don't enable the charger unless overvoltage is recovered */
+
+ if (chip->bat_health != POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
+ ret = bq24261_enable_charger(chip, val->intval);
+
+ if (ret)
+ dev_err(&chip->client->dev,
+ "Error(%d) in %s charger", ret,
+ (val->intval ? "enable" : "disable"));
+ else
+ chip->is_charger_enabled = val->intval;
+ } else {
+ dev_info(&chip->client->dev, "Battery Over Voltage. Charger will be disabled\n");
+ }
+ break;
+ case POWER_SUPPLY_CHARGER_PROP_CABLE_TYPE:
+ chip->cable_type = val->intval;
+ chip->psy_usb.type = get_power_supply_type(chip->cable_type);
+ if (chip->cable_type != PSY_CHARGER_CABLE_TYPE_NONE) {
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
+
+ /* Adding this processing in order to check
+ for any faults during connect */
+
+ ret = bq24261_read_reg(chip->client,
+ BQ24261_STAT_CTRL0_ADDR);
+ if (ret < 0)
+ dev_err(&chip->client->dev,
+ "Error (%d) in reading status register(0x00)\n",
+ ret);
+ else
+ bq24261_handle_irq(chip, ret);
+ } else {
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ cancel_delayed_work_sync(&chip->low_supply_fault_work);
+ }
+ break;
+ case POWER_SUPPLY_CHARGER_PROP_RESET_WDT:
+ bq24261_reset_timer(chip);
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static int bq24261_usb_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq24261_charger *chip = container_of(psy,
+ struct bq24261_charger,
+ psy_usb);
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
+
+ switch (psp) {
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ chip->present = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ chip->online = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq24261_set_cc(chip, val->intval);
+ if (!ret)
+ chip->cc = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = bq24261_set_cv(chip, val->intval);
+ if (!ret)
+ chip->cv = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ chip->max_cc = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ chip->max_cv = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
+ ret = bq24261_set_iterm(chip, val->intval);
+ if (!ret)
+ chip->iterm = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_INLMT:
+ ret = bq24261_set_inlmt(chip, val->intval);
+ if (!ret)
+ chip->inlmt = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ chip->cntl_state = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ chip->max_temp = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ chip->min_temp = val->intval;
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static int bq24261_usb_psyc_get_property(struct power_supply_charger *psyc,
+ enum power_supply_charger_property pscp,
+ union power_supply_propval *val)
+{
+ struct bq24261_charger *chip = container_of(psyc,
+ struct bq24261_charger,
+ psyc_usb);
+
+ mutex_lock(&chip->lock);
+
+ switch (pscp) {
+ case POWER_SUPPLY_CHARGER_PROP_CABLE_TYPE:
+ val->intval = chip->cable_type;
+ break;
+ case POWER_SUPPLY_CHARGER_PROP_ENABLE_CHARGING:
+ val->intval = (chip->is_charging_enabled &&
+ (chip->chrgr_stat == BQ24261_CHRGR_STAT_CHARGING));
+
+ break;
+ case POWER_SUPPLY_CHARGER_PROP_ENABLE_CHARGER:
+ val->intval = is_bq24261_enabled(chip);
+ break;
+ default:
+ mutex_unlock(&chip->lock);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&chip->lock);
+ return 0;
+}
+
+static int bq24261_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq24261_charger *chip = container_of(psy,
+ struct bq24261_charger,
+ psy_usb);
+
+ mutex_lock(&chip->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->online;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chip->chrgr_health;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = chip->max_cc;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ val->intval = chip->max_cv;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ val->intval = chip->cc;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ val->intval = chip->cv;
+ break;
+ case POWER_SUPPLY_PROP_INLMT:
+ val->intval = chip->inlmt;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
+ val->intval = chip->iterm;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ val->intval = chip->cntl_state;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ val->intval = chip->pdata->num_throttle_states;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = chip->model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = chip->manufacturer;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ val->intval = chip->max_temp;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ val->intval = chip->min_temp;
+ break;
+ default:
+ mutex_unlock(&chip->lock);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&chip->lock);
+ return 0;
+}
+
+static inline struct power_supply *get_psy_battery(void)
+{
+ struct class_dev_iter iter;
+ struct device *dev;
+ static struct power_supply *pst;
+
+ class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
+ while ((dev = class_dev_iter_next(&iter))) {
+ pst = (struct power_supply *)dev_get_drvdata(dev);
+ if (pst->type == POWER_SUPPLY_TYPE_BATTERY) {
+ class_dev_iter_exit(&iter);
+ return pst;
+ }
+ }
+ class_dev_iter_exit(&iter);
+
+ return NULL;
+}
+
+static inline int get_battery_voltage(int *volt)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = get_psy_battery();
+ if (!psy)
+ return -EINVAL;
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+ if (!ret)
+ *volt = (val.intval);
+
+ return ret;
+}
+
+static inline int get_battery_volt_max_design(int *volt)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = get_psy_battery();
+ if (!psy)
+ return -EINVAL;
+
+ ret = psy->get_property(psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &val);
+ if (!ret)
+ (*volt = val.intval);
+ return ret;
+}
+
+static inline int get_battery_current(int *cur)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = get_psy_battery();
+ if (!psy)
+ return -EINVAL;
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, &val);
+ if (!ret)
+ *cur = val.intval;
+
+ return ret;
+}
+
+static void notify_worker(struct work_struct *work)
+{
+
+ struct bq24261_charger *chip = container_of(work,
+ struct bq24261_charger,
+ notify_work.work);
+
+ atomic_notifier_call_chain(&power_supply_notifier,
+ PSY_EVENT_PROP_CHANGED, &chip->psy_usb);
+ schedule_delayed_work(&chip->notify_work, WDT_RESET_DELAY);
+}
+
+int bq24261_get_bat_health(void)
+{
+
+ struct bq24261_charger *chip;
+
+ if (!bq24261_client)
+ return -ENODEV;
+
+ chip = i2c_get_clientdata(bq24261_client);
+
+ return chip->bat_health;
+}
+
+
+static void bq24261_low_supply_fault_work(struct work_struct *work)
+{
+ struct bq24261_charger *chip = container_of(work,
+ struct bq24261_charger,
+ low_supply_fault_work.work);
+
+ if (chip->chrgr_stat == BQ24261_CHRGR_STAT_FAULT) {
+ dev_err(&chip->client->dev, "Low Supply Fault detected!!\n");
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_DEAD;
+ power_supply_changed(&chip->psy_usb);
+ }
+ return;
+}
+
+
+/* is_bat_over_voltage: check battery is over voltage or not
+* @chip: bq24261_charger context
+*
+* This function is used to verify the over voltage condition.
+* In some scenarios, HW generates Over Voltage exceptions when
+* battery voltage is normal. This function uses the over voltage
+* condition (voltage_max_design * 1.01) to verify battery is really
+* over charged or not.
+*/
+
+static bool is_bat_over_voltage(struct bq24261_charger *chip,
+ bool verify_recovery)
+{
+
+ int bat_volt, bat_volt_max_des, ret;
+
+ ret = get_battery_voltage(&bat_volt);
+ if (ret)
+ return verify_recovery ? false : true;
+
+ ret = get_battery_volt_max_design(&bat_volt_max_des);
+
+ if (ret)
+ bat_volt_max_des = BQ24261_DEF_BAT_VOLT_MAX_DESIGN;
+
+ dev_info(&chip->client->dev, "bat_volt=%d Voltage Max Design=%d OVP_VOLT=%d OVP recover volt=%d\n",
+ bat_volt, bat_volt_max_des,
+ (bat_volt_max_des/1000 * BQ24261_OVP_MULTIPLIER),
+ (bat_volt_max_des/1000 *
+ BQ24261_OVP_RECOVER_MULTIPLIER));
+ if (verify_recovery) {
+ if ((bat_volt) <= (bat_volt_max_des / 1000 *
+ BQ24261_OVP_RECOVER_MULTIPLIER))
+ return true;
+ else
+ return false;
+ } else {
+ if ((bat_volt) >= (bat_volt_max_des / 1000 *
+ BQ24261_OVP_MULTIPLIER))
+ return true;
+ else
+ return false;
+ }
+
+ return false;
+}
+
+#define IS_BATTERY_OVER_VOLTAGE(chip) \
+ is_bat_over_voltage(chip , false)
+
+#define IS_BATTERY_OVER_VOLTAGE_RECOVERED(chip) \
+ is_bat_over_voltage(chip , true)
+
+static void handle_battery_over_voltage(struct bq24261_charger *chip)
+{
+ /* Set Health to Over Voltage. Disable charger to discharge
+ * battery to reduce the battery voltage.
+ */
+ chip->bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ bq24261_enable_charger(chip, false);
+ chip->is_charger_enabled = false;
+ cancel_delayed_work_sync(&chip->exception_mon_work);
+ schedule_delayed_work(&chip->exception_mon_work,
+ EXCEPTION_MONITOR_DELAY);
+}
+
+static void bq24261_exception_mon_work(struct work_struct *work)
+{
+ struct bq24261_charger *chip = container_of(work,
+ struct bq24261_charger,
+ exception_mon_work.work);
+ /* Only overvoltage exception need to monitor.*/
+ if (IS_BATTERY_OVER_VOLTAGE_RECOVERED(chip)) {
+ dev_info(&chip->client->dev, "Over Voltage Exception Recovered\n");
+ chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
+ bq24261_enable_charger(chip, true);
+ chip->is_charger_enabled = true;
+ resume_charging(chip);
+ } else {
+ schedule_delayed_work(&chip->exception_mon_work,
+ EXCEPTION_MONITOR_DELAY);
+ }
+}
+
+static int bq24261_handle_irq(struct bq24261_charger *chip, u8 stat_reg)
+{
+ struct i2c_client *client = chip->client;
+ bool notify = true;
+
+ dev_info(&client->dev, "%s:%d stat=0x%x\n",
+ __func__, __LINE__, stat_reg);
+
+ switch (stat_reg & BQ24261_STAT_MASK) {
+ case BQ24261_STAT_READY:
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_READY;
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
+ chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
+ dev_info(&client->dev, "Charger Status: Ready\n");
+ notify = false;
+ break;
+ case BQ24261_STAT_CHRG_PRGRSS:
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_CHARGING;
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
+ chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
+ dev_info(&client->dev, "Charger Status: Charge Progress\n");
+ break;
+ case BQ24261_STAT_CHRG_DONE:
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
+ chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
+ dev_info(&client->dev, "Charger Status: Charge Done\n");
+
+ /* HW reports charge termination. Stop h/w charge termination
+ * and resume charging. Let power supply charging driver decide
+ * on charge termination
+ */
+
+ bq24261_enable_hw_charge_term(chip, false);
+ resume_charging(chip);
+ break;
+
+ case BQ24261_STAT_FAULT:
+ break;
+ }
+
+ if (stat_reg & BQ24261_BOOST_MASK)
+ dev_info(&client->dev, "Boost Mode\n");
+
+ if ((stat_reg & BQ24261_STAT_MASK) == BQ24261_STAT_FAULT) {
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_FAULT;
+
+ switch (stat_reg & BQ24261_FAULT_MASK) {
+ case BQ24261_VOVP:
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ dev_err(&client->dev, "Charger OVP Fault\n");
+ break;
+
+ case BQ24261_LOW_SUPPLY:
+ notify = false;
+
+ if (chip->cable_type !=
+ PSY_CHARGER_CABLE_TYPE_NONE) {
+ schedule_delayed_work
+ (&chip->low_supply_fault_work,
+ 5*HZ);
+ dev_dbg(&client->dev,
+ "Schedule Low Supply Fault work!!\n");
+ }
+ break;
+
+ case BQ24261_THERMAL_SHUTDOWN:
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ dev_err(&client->dev, "Charger Thermal Fault\n");
+ break;
+
+ case BQ24261_BATT_TEMP_FAULT:
+ chip->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ dev_err(&client->dev, "Battery Temperature Fault\n");
+ break;
+
+ case BQ24261_TIMER_FAULT:
+ chip->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ dev_err(&client->dev, "Charger Timer Fault\n");
+ break;
+
+ case BQ24261_BATT_OVP:
+ notify = false;
+ if (chip->bat_health !=
+ POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
+ if (!IS_BATTERY_OVER_VOLTAGE(chip)) {
+ chip->chrgr_stat =
+ BQ24261_CHRGR_STAT_UNKNOWN;
+ resume_charging(chip);
+ } else {
+ dev_err(&client->dev, "Battery Over Voltage Fault\n");
+ handle_battery_over_voltage(chip);
+ notify = true;
+ }
+ }
+ break;
+ case BQ24261_NO_BATTERY:
+ dev_err(&client->dev, "No Battery Connected\n");
+ break;
+
+ }
+
+ }
+
+ wake_up(&chip->wait_ready);
+
+ chip->is_vsys_on = bq24261_is_vsys_on(chip);
+ if (notify)
+ power_supply_changed(&chip->psy_usb);
+
+ return 0;
+}
+
+static irqreturn_t bq24261_thread_handler(int id, void *data)
+{
+ struct bq24261_charger *chip = (struct bq24261_charger *)data;
+ int ret;
+
+ mutex_lock(&chip->lock);
+
+ ret = bq24261_read_reg(chip->client, BQ24261_STAT_CTRL0_ADDR);
+ if (ret < 0)
+ dev_err(&chip->client->dev,
+ "Error (%d) in reading BQ24261_STAT_CTRL0_ADDR\n", ret);
+ else
+ bq24261_handle_irq(chip, ret);
+
+ mutex_unlock(&chip->lock);
+
+ return IRQ_HANDLED;
+}
+
+static enum bq2426x_model_num bq24261_get_model(int bq24261_rev_reg)
+{
+ switch (bq24261_rev_reg & BQ24261_REV_MASK) {
+ case BQ24260_REV:
+ return BQ24260;
+ case BQ24261_REV:
+ return BQ24261;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bq24261_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter;
+ struct bq24261_charger *chip;
+ int ret;
+ enum bq2426x_model_num bq24261_rev;
+
+ adapter = to_i2c_adapter(client->dev.parent);
+
+ if (!client->dev.platform_data) {
+ dev_err(&client->dev, "platform data is null");
+ return -EFAULT;
+ }
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(&client->dev,
+ "I2C adapter %s doesn'tsupport BYTE DATA transfer\n",
+ adapter->name);
+ return -EIO;
+ }
+
+ ret = bq24261_read_reg(client, BQ24261_VENDOR_REV_ADDR);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Error (%d) in reading BQ24261_VENDOR_REV_ADDR\n", ret);
+ return ret;
+ }
+
+ bq24261_rev = bq24261_get_model(ret);
+ if (((ret & BQ24261_VENDOR_MASK) != BQ24261_VENDOR) ||
+ (bq24261_rev < 0)) {
+ dev_err(&client->dev,
+ "Invalid Vendor/Revision number in BQ24261_VENDOR_REV_ADDR: %d",
+ ret);
+ return -ENODEV;
+ }
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(&client->dev, "mem alloc failed\n");
+ return -ENOMEM;
+ }
+
+ init_waitqueue_head(&chip->wait_ready);
+ i2c_set_clientdata(client, chip);
+ chip->pdata = client->dev.platform_data;
+
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ chip->psy_usb.name = DEV_NAME;
+ chip->psy_usb.type = POWER_SUPPLY_TYPE_USB;
+ chip->psy_usb.properties = bq24261_usb_props;
+ chip->psy_usb.num_properties = ARRAY_SIZE(bq24261_usb_props);
+ chip->psy_usb.get_property = bq24261_usb_get_property;
+ chip->psy_usb.set_property = bq24261_usb_set_property;
+ chip->psy_usb.supplied_to = chip->pdata->supplied_to;
+ chip->psy_usb.num_supplicants = chip->pdata->num_supplicants;
+ /* Limit charge current */
+ chip->max_cc = 1500;
+ chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
+ chip->chrgr_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+
+ chip->psyc_usb.get_property = bq24261_usb_psyc_get_property;
+ chip->psyc_usb.set_property = bq24261_usb_psyc_set_property;
+ chip->psyc_usb.throttle_states = chip->pdata->throttle_states;
+ chip->psyc_usb.num_throttle_states = chip->pdata->num_throttle_states;
+ chip->psyc_usb.supported_cables = PSY_CHARGER_CABLE_TYPE_USB;
+
+ strncpy(chip->model_name,
+ bq24261_model_name[bq24261_rev].model_name,
+ MODEL_NAME_SIZE);
+ strncpy(chip->manufacturer, DEV_MANUFACTURER,
+ DEV_MANUFACTURER_NAME_SIZE);
+
+ mutex_init(&chip->lock);
+ ret = power_supply_register(&client->dev, &chip->psy_usb);
+ if (ret) {
+ dev_err(&client->dev, "Failed: power supply register (%d)\n",
+ ret);
+ return ret;
+ }
+
+ chip->psyc_usb.psy = &chip->psy_usb;
+
+ ret = power_supply_register_charger(&chip->psyc_usb);
+ if (ret) {
+ dev_err(&client->dev, "Failed: power supply register (%d)\n",
+ ret);
+ power_supply_unregister(&chip->psy_usb);
+ return ret;
+ }
+
+ INIT_DELAYED_WORK(&chip->notify_work, notify_worker);
+ INIT_DELAYED_WORK(&chip->low_supply_fault_work,
+ bq24261_low_supply_fault_work);
+ INIT_DELAYED_WORK(&chip->exception_mon_work,
+ bq24261_exception_mon_work);
+
+ if (chip->client->irq) {
+ ret = request_threaded_irq(chip->client->irq,
+ NULL,
+ bq24261_thread_handler,
+ IRQF_SHARED|IRQF_NO_SUSPEND,
+ DEV_NAME, chip);
+ if (ret) {
+ dev_err(&client->dev, "Failed: request_irq (%d)\n",
+ ret);
+ power_supply_unregister(&chip->psy_usb);
+ power_supply_unregister_charger(&chip->psyc_usb);
+ return ret;
+ }
+ }
+
+ if (IS_BATTERY_OVER_VOLTAGE(chip))
+ handle_battery_over_voltage(chip);
+ else
+ chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ bq24261_client = client;
+
+ return 0;
+}
+
+static int bq24261_remove(struct i2c_client *client)
+{
+ struct bq24261_charger *chip = i2c_get_clientdata(client);
+
+ if (client->irq)
+ free_irq(client->irq, chip);
+
+ flush_scheduled_work();
+
+ power_supply_unregister(&chip->psy_usb);
+ return 0;
+}
+
+static const struct i2c_device_id bq24261_id[] = {
+ {DEV_NAME, 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, bq24261_id);
+
+static struct i2c_driver bq24261_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ },
+ .probe = bq24261_probe,
+ .remove = bq24261_remove,
+ .id_table = bq24261_id,
+};
+
+static int __init bq24261_init(void)
+{
+ return i2c_add_driver(&bq24261_driver);
+}
+
+module_init(bq24261_init);
+
+static void __exit bq24261_exit(void)
+{
+ i2c_del_driver(&bq24261_driver);
+}
+
+module_exit(bq24261_exit);
+
+MODULE_AUTHOR("Jenny TC <jenny.tc@xxxxxxxxx>");
+MODULE_DESCRIPTION("BQ24261 Charger Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/bq24261_charger.h b/include/linux/power/bq24261_charger.h
new file mode 100644
index 0000000..e6b399f
--- /dev/null
+++ b/include/linux/power/bq24261_charger.h
@@ -0,0 +1,33 @@
+/*
+ * bq24261_charger.h: platform data structure for bq24261 driver
+ *
+ * (C) Copyright 2012 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+
+#ifndef __BQ24261_CHARGER_H__
+#define __BQ24261_CHARGER_H__
+
+struct bq24261_plat_data {
+ char **supplied_to;
+ size_t num_supplicants;
+ struct psy_throttle_state *throttle_states;
+ size_t num_throttle_states;
+ int safety_timer;
+ bool is_ts_enabled;
+
+ int (*enable_charging) (bool val);
+ int (*enable_charger) (bool val);
+ int (*set_inlmt) (int val);
+ int (*set_cc) (int val);
+ int (*set_cv) (int val);
+ int (*set_iterm) (int val);
+ int (*enable_vbus) (int val);
+};
+
+
+#endif
--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/