Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755012AbaAVJ2Q (ORCPT ); Wed, 22 Jan 2014 04:28:16 -0500 Received: from mga11.intel.com ([192.55.52.93]:13967 "EHLO mga11.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754866AbaAVJ2E (ORCPT ); Wed, 22 Jan 2014 04:28:04 -0500 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.95,698,1384329600"; d="scan'208";a="468912176" From: Jenny TC To: linux-kernel@vger.kernel.org, Dmitry Eremin-Solenikov Cc: Anton Vorontsov , Anton Vorontsov , Jenny TC , Kim Milo , Lee Jones , Jingoo Han , Chanwoo Choi , Sachin Kamat , Rupesh Kumar , Lars-Peter Clausen , =?UTF-8?q?Pali=20Roh=C3=A1r?= , Mark Brown , Rhyland Klein , Pavel Machek , David Woodhouse , Tony Lindgren , Russell King , Sebastian Reichel , aaro.koskinen@iki.fi, freemangordon@abv.bg, linux-omap@vger.kernel.org Subject: [PATCH 4/4] power_supply: bq24261 charger driver Date: Wed, 22 Jan 2014 22:49:54 +0530 Message-Id: <1390411194-21410-5-git-send-email-jenny.tc@intel.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1390411194-21410-1-git-send-email-jenny.tc@intel.com> References: <1390411194-21410-1-git-send-email-jenny.tc@intel.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- 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 + */ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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, ®_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, ®_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, ®_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, ®_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, ®_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 "); +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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/