On Wednesday 07 December 2011 23:25:12 Vladimir Zapolskiy wrote: > Hi Pali, > > On 07.12.2011 23:03, Pali Rohár wrote: > > Hello, > > > > I'm sending preview of my implementation of bq2415x charger driver. Now > > all code is in one kernel module bq2415x_charger which has more > > interfaces (power_supply, sysfs, regulator). It is still unfinished, > > missing more bq2415x configuration like sysfs entires for boost, > > voltages or platform hooks (e.g. rx51 isp1704). This driver will have > > similar sysfs interface as in Joerg Reisenweber spec > > http://maemo.cloud-7.de/bq24150-sysnode.spec.txt . Driver should be > > replacemnt for Nokia N900 proprietary charging program BME. I was > > inspirated by driver bq27x00_battery and other bq2415x implementaton by > > Aliaksei Katovich and Felipe Contreras. > > > > Please comment implementation and write what should be added or modified > > in driver (it is still not complete). > > There are well defined policies regarding patch review in the Linux > kernel mailing lists. I encourage you to take a look at > http://felipec.wordpress.com/2009/10/25/git-send-email-tricks/ Ok, I do not know where to put this driver, so I only send files. Here is diff diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c new file mode 100644 index 0000000..eb9957a --- /dev/null +++ b/drivers/power/bq2415x_charger.c @@ -0,0 +1,1115 @@ +/* + bq2415x_charger.c - bq2415x charger driver + Copyright (C) 2011 Pali Rohár + + 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; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* + Datasheets: + http://www.ti.com/product/bq24150 + http://www.ti.com/product/bq24150a + http://www.ti.com/product/bq24152 + http://www.ti.com/product/bq24153 + http://www.ti.com/product/bq24153a + http://www.ti.com/product/bq24155 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bq2415x_charger.h" + +#define BQ2415X_WATCHDOG_TIMEOUT 10 + +#define BQ2415X_REG_STATUS 0x00 +#define BQ2415X_REG_CONTROL 0x01 +#define BQ2415X_REG_VOLTAGE 0x02 +#define BQ2415X_REG_VENDER 0x03 +#define BQ2415X_REG_CURRENT 0x04 + +/* reset state for all registers */ +#define BQ2415X_RESET_STATUS BIT(6) +#define BQ2415X_RESET_CONTROL (BIT(4)|BIT(5)) +#define BQ2415X_RESET_VOLTAGE (BIT(1)|BIT(3)) +#define BQ2415X_RESET_CURRENT (BIT(0)|BIT(3)|BIT(7)) + +/* status register */ +#define BQ2415X_BIT_TMR_RST 7 +#define BQ2415X_BIT_OTG 7 +#define BQ2415X_BIT_EN_STAT 6 +#define BQ2415X_MASK_STAT (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_STAT 4 +#define BQ2415X_BIT_BOOST 3 +#define BQ2415X_MASK_FAULT (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_FAULT 0 + +/* control register */ +#define BQ2415X_MASK_LIMIT (BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_LIMIT 6 +#define BQ2415X_MASK_VLOWV (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_VLOWV 4 +#define BQ2415X_BIT_TE 3 +#define BQ2415X_BIT_CE 2 +#define BQ2415X_BIT_HZ_MODE 1 +#define BQ2415X_BIT_OPA_MODE 0 + +/* voltage register */ +#define BQ2415X_MASK_VO (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VO 2 +#define BQ2415X_BIT_OTG_PL 1 +#define BQ2415X_BIT_OTG_EN 0 + +/* vender register */ +#define BQ2415X_MASK_VENDER (BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VENDER 5 +#define BQ2415X_MASK_PN (BIT(3)|BIT(4)) +#define BQ2415X_SHIFT_PN 4 +#define BQ2415X_MASK_REVISION (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_REVISION 0 + +/* current register */ +/* RESET BIT(7) */ +#define BQ2415X_MASK_VI_CHRG (BIT(4)|BIT(5)|BIT(6)) +#define BQ2415X_SHIFT_VI_CHRG 4 +/* N/A BIT(3) */ +#define BQ2415X_MASK_VI_TERM (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_VI_TERM 0 + +struct bq2415x_regulator { + struct regulator_ops ops; + struct regulator_desc desc; + struct regulator_dev *dev; +}; + +struct bq2415x_device { + struct device *dev; + struct bq2415x_platform_data *platform_data; + struct bq2415x_regulator current_limit; + struct bq2415x_regulator weak_battery_voltage; + struct bq2415x_regulator battery_regulation_voltage; + struct bq2415x_regulator charge_current_sense_voltage; + struct bq2415x_regulator termination_current_sense_voltage; + struct led_classdev led; + struct power_supply charger; + struct delayed_work work; + int watchdog; + enum bq2415x_chip chip; + char *name; + int id; +}; + +static DEFINE_IDR(bq2415x_id); +static DEFINE_MUTEX(bq2415x_mutex); + +/* i2c read functions */ + +static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + struct i2c_msg msg[2]; + u8 val; + int ret; + + dev_info(bq->dev, "bq2415x_i2c_read reg=%#x\n", reg); + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = ® + msg[0].len = sizeof(reg); + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &val; + msg[1].len = sizeof(val); + + mutex_lock(&bq2415x_mutex); + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + mutex_unlock(&bq2415x_mutex); + + if (ret < 0) + return ret; + + return val; +} + +static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg, u8 mask, u8 shift) +{ + int ret; + + dev_info(bq->dev, "bq2415x_i2c_read_mask reg=%#x mask=%#x shift=%#x\n", reg, mask, shift); + + if (shift > 8) + return -EINVAL; + + ret = bq2415x_i2c_read(bq, reg); + if (ret < 0) + return ret; + else + return (ret & mask) >> shift; +} + +static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit) +{ + if (bit > 8) + return -EINVAL; + else + return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit); +} + +/* i2c write functions */ + +static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + struct i2c_msg msg[1]; + u8 data[2]; + int ret; + + dev_info(bq->dev, "bq2415x_i2c_write reg=%#x val=%#x\n", reg, val); + + data[0] = reg; + data[1] = val; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = data; + msg[0].len = ARRAY_SIZE(data); + + mutex_lock(&bq2415x_mutex); + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + mutex_unlock(&bq2415x_mutex); + + /* i2c_transfer returns number of messages transferred */ + if (ret < 0) + return ret; + else if (ret != 1) + return -EIO; + + return 0; +} + +static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val, u8 mask, u8 shift) +{ + int ret; + + dev_info(bq->dev, "bq2415x_i2c_write_mask reg=%#x val=%#x mask=%#x shift=%#x\n", reg, val, mask, shift); + + if (shift > 8) + return -EINVAL; + + ret = bq2415x_i2c_read(bq, reg); + if (ret < 0) + return ret; + + ret &= ~mask; + ret |= val << shift; + + return bq2415x_i2c_write(bq, reg, ret); +} + +static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, bool val, u8 bit) +{ + if (bit > 8) + return -EINVAL; + else + return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit); +} + +/* global detect chip */ + +enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq) +{ + struct i2c_client *client = to_i2c_client(bq->dev); + int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER); + if (ret < 0) + return ret; + if (client->addr == 0x6b) { + switch (ret) { + case 0: + if (bq->chip == BQ24151A) + return bq->chip; + else + return BQ24151; + case 1: + if (bq->chip == BQ24150A || bq->chip == BQ24152 || bq->chip == BQ24155) + return bq->chip; + else + return BQ24150; + case 2: + if (bq->chip == BQ24153A) + return bq->chip; + else + return BQ24153; + default: + return BQUNKNOWN; + } + } else if (client->addr == 0x6a) { + switch (ret) { + case 0: + if (bq->chip == BQ24156A) + return bq->chip; + else + return BQ24156; + case 2: + return BQ24158; + default: + return BQUNKNOWN; + } + } + return BQUNKNOWN; +} +EXPORT_SYMBOL_GPL(bq2415x_detect_chip); + +/* global exec command function */ + +int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command) +{ + switch(command) + { + case BQ2415X_WATCHDOG_RESET: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_TMR_RST); + case BQ2415X_OTG_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_OTG); + case BQ2415X_STAT_PIN_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_EN_STAT); + case BQ2415X_STAT_PIN_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_EN_STAT); + case BQ2415X_STAT_PIN_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0, BQ2415X_BIT_EN_STAT); + case BQ2415X_CHARGE_STATUS: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT); + case BQ2415X_BOOST_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS, BQ2415X_BIT_BOOST); + case BQ2415X_FAULT_STATUS: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS, BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT); + + case BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_TE); + case BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_TE); + case BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_TE); + case BQ2415X_CHARGER_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_CE); + case BQ2415X_CHARGER_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_CE); + case BQ2415X_CHARGER_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_CE); + case BQ2415X_HIGH_IMPEDANCE_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_HZ_MODE); + case BQ2415X_HIGH_IMPEDANCE_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_HZ_MODE); + case BQ2415X_HIGH_IMPEDANCE_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_HZ_MODE); + case BQ2415X_MODE: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL, BQ2415X_BIT_OPA_MODE); + case BQ2415X_SET_BOOST_MODE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE); + case BQ2415X_SET_CHARGER_MODE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_OPA_MODE); + + case BQ2415X_OTG_HIGH_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_HIGH_ACTIVATE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_LOW_ACTIVATE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_PL); + case BQ2415X_OTG_PIN_STATUS: + return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE, BQ2415X_BIT_OTG_EN); + case BQ2415X_OTG_PIN_ENABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_EN); + case BQ2415X_OTG_PIN_DISABLE: + return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_EN); + + case BQ2415X_VENDER_CODE: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER); + case BQ2415X_PART_NUMBER: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_PN, BQ2415X_SHIFT_PN); + case BQ2415X_REVISION: + return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER, BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION); + + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(bq2415x_exec_command); + +/* global other functions */ + +#define bq2415x_set_default_value(bq, value) \ + do { \ + int ret = 0; \ + if (bq->platform_data->value != -1) \ + ret = bq2415x_set_##value(bq, bq->platform_data->value); \ + if (ret < 0) \ + return ret; \ + } while (0) + +int bq2415x_set_defaults(struct bq2415x_device *bq) +{ + bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE); + bq2415x_set_default_value(bq, current_limit); + bq2415x_set_default_value(bq, weak_battery_voltage); + bq2415x_set_default_value(bq, battery_regulation_voltage); + bq2415x_set_default_value(bq, charge_current_sense_voltage); + bq2415x_set_default_value(bq, termination_current_sense_voltage); + bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); + return 0; +} +EXPORT_SYMBOL_GPL(bq2415x_set_defaults); + +#undef bq2415x_set_default_value + +void bq2415x_reset_chip(struct bq2415x_device *bq) +{ + bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT); + bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE); + bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL); + bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS); +} +EXPORT_SYMBOL_GPL(bq2415x_reset_chip); + +int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA) +{ + int val = (mA/100 + (mA%100 > 0 ? 1 : 0) - 1) / 4; + + if (val < 0) + val = 0; + + if (val > 3) + val = 3; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); +} +EXPORT_SYMBOL_GPL(bq2415x_set_current_limit); + +int bq2415x_get_current_limit(struct bq2415x_device *bq) +{ + int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT); + if (ret < 0) + return ret; + else + return 100 * (1 + 4*ret); +} +EXPORT_SYMBOL_GPL(bq2415x_get_current_limit); + +int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV) +{ + int val = mV/100 + (mV%100 > 0 ? 1 : 0) - 34; + + if (val < 0) + val = 0; + + if (val > 3) + return -EINVAL; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); +} +EXPORT_SYMBOL_GPL(bq2415x_set_weak_battery_voltage); + +int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq) +{ + int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL, BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); + if (ret < 0) + return ret; + else + return 100 * (34 + ret); +} +EXPORT_SYMBOL_GPL(bq2415x_get_weak_battery_voltage); + +int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV) +{ + int val = mV/10 + (mV%10 > 0 ? 1 : 0) - 350; + + if (val < 0) + val = 0; + + if (val > 94) /* FIXME: Max is 94 or 122 ? */ + return -EINVAL; + + return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); +} +EXPORT_SYMBOL_GPL(bq2415x_set_battery_regulation_voltage); + +int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq) +{ + int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE, BQ2415X_MASK_VO, BQ2415X_SHIFT_VO); + if (ret < 0) + return ret; + else + return 10 * (350 + ret); +} +EXPORT_SYMBOL_GPL(bq2415x_get_battery_regulation_voltage); + +int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV) +{ + /* TODO */ + return -1; +} +EXPORT_SYMBOL_GPL(bq2415x_set_charge_current_sense_voltage); + +int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq) +{ + /* TODO */ + return -1; +} +EXPORT_SYMBOL_GPL(bq2415x_get_charge_current_sense_voltage); + +int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV) +{ + /* TODO */ + return -1; +} +EXPORT_SYMBOL_GPL(bq2415x_set_termination_current_sense_voltage); + +int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq) +{ + /* TODO */ + return -1; +} +EXPORT_SYMBOL_GPL(bq2415x_get_termination_current_sense_voltage); + +/* current limit regulator */ + +static int bq2415x_regulator_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_set_current_limit(bq, min_uA / 1000); +} + +static int bq2415x_regulator_get_current_limit(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_get_current_limit(bq) * 1000; +} + +struct regulator_ops bq2415x_regulator_current_limit = { + .set_current_limit = bq2415x_regulator_set_current_limit, + .get_current_limit = bq2415x_regulator_get_current_limit, +}; + +/* weak battery voltage regulator */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static int bq2415x_regulator_list_weak_battery_voltage(struct regulator_dev *rdev, unsigned selector) +{ + if (selector > 3) + return 0; + else + return 100000 * (34 + selector); +} +#endif + +static int bq2415x_regulator_set_weak_battery_voltage(struct regulator_dev *rdev, int min_uV, int max_uV +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + , unsigned *selector +#endif + ) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_set_weak_battery_voltage(bq, min_uV / 1000); +} + +static int bq2415x_regulator_get_weak_battery_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_get_weak_battery_voltage(bq) * 1000; +} + +static struct regulator_ops bq2415x_regulator_weak_battery_voltage = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + .list_voltage = bq2415x_regulator_list_weak_battery_voltage, +#endif + .set_voltage = bq2415x_regulator_set_weak_battery_voltage, + .get_voltage = bq2415x_regulator_get_weak_battery_voltage, +}; + +/* battery regulation voltage regulator */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static int bq2415x_regulator_list_battery_regulation_voltage(struct regulator_dev *rdev, unsigned selector) +{ + if (selector > 94) /* FIXME: Max is 94 or 122 ? */ + return 0; + else + return 10000 * (350 + selector); +} +#endif + +static int bq2415x_regulator_set_battery_regulation_voltage(struct regulator_dev *rdev, int min_uV, int max_uV +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + , unsigned *selector +#endif + ) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_set_battery_regulation_voltage(bq, min_uV / 1000); +} + +static int bq2415x_regulator_get_battery_regulation_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_get_battery_regulation_voltage(bq) * 1000; +} + +static struct regulator_ops bq2415x_regulator_battery_regulation_voltage = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + .list_voltage = bq2415x_regulator_list_battery_regulation_voltage, +#endif + .set_voltage = bq2415x_regulator_set_battery_regulation_voltage, + .get_voltage = bq2415x_regulator_get_battery_regulation_voltage, +}; + +/* charge current sense voltage regulator */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static int bq2415x_regulator_list_charge_current_sense_voltage(struct regulator_dev *rdev, unsigned selector) +{ + /* TODO */ + return 0; +} +#endif + +static int bq2415x_regulator_set_charge_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + , unsigned *selector +#endif + ) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_set_charge_current_sense_voltage(bq, min_uV / 1000); +} + +static int bq2415x_regulator_get_charge_current_sense_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_get_charge_current_sense_voltage(bq) * 1000; +} + +static struct regulator_ops bq2415x_regulator_charge_current_sense_voltage = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + .list_voltage = bq2415x_regulator_list_charge_current_sense_voltage, +#endif + .set_voltage = bq2415x_regulator_set_charge_current_sense_voltage, + .get_voltage = bq2415x_regulator_get_charge_current_sense_voltage, +}; + +/* termination current sense voltage regulator */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static int bq2415x_regulator_list_termination_current_sense_voltage(struct regulator_dev *rdev, unsigned selector) +{ + /* TODO */ + return 0; +} +#endif + +static int bq2415x_regulator_set_termination_current_sense_voltage(struct regulator_dev *rdev, int min_uV, int max_uV +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + , unsigned *selector +#endif + ) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_set_termination_current_sense_voltage(bq, min_uV / 1000); +} + +static int bq2415x_regulator_get_termination_current_sense_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_get_termination_current_sense_voltage(bq) * 1000; +} + +static int bq2415x_regulator_enable_termination_current_sense_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE); +} + +static int bq2415x_regulator_disable_termination_current_sense_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE); +} + +static int bq2415x_regulator_is_enabled_termination_current_sense_voltage(struct regulator_dev *rdev) +{ + struct bq2415x_device *bq = rdev_get_drvdata(rdev); + return bq2415x_exec_command(bq, BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS); +} + +static struct regulator_ops bq2415x_regulator_termination_current_sense_voltage = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + .list_voltage = bq2415x_regulator_list_termination_current_sense_voltage, +#endif + .set_voltage = bq2415x_regulator_set_termination_current_sense_voltage, + .get_voltage = bq2415x_regulator_get_termination_current_sense_voltage, + .enable = bq2415x_regulator_enable_termination_current_sense_voltage, + .disable = bq2415x_regulator_disable_termination_current_sense_voltage, + .is_enabled = bq2415x_regulator_is_enabled_termination_current_sense_voltage, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, init, data) +#else +#define regulator_register2(desc, dev, init, data) regulator_register(desc, dev, data) +#endif + +#define bq2415x_regulator_register(bq, rname, rid, rtype) \ + do { \ + bq->rname.desc.name = __stringify(rname); \ + bq->rname.desc.id = rid; \ + bq->rname.desc.type = rtype; \ + bq->rname.desc.owner = THIS_MODULE; \ + bq->rname.desc.ops = &bq2415x_regulator_##rname; \ + bq->rname.dev = regulator_register2(&bq->rname.desc, bq->dev, NULL, bq); \ + if (IS_ERR(bq->rname.dev)) \ + return PTR_ERR(bq->rname.dev); \ + } while (0) + +#define bq2415x_regulator_unregister(bq, rname) regulator_unregister(bq->rname.dev) + +static int bq2415x_regulator_init(struct bq2415x_device *bq) +{ +/* bq2415x_regulator_register(bq, current_limit, 0, REGULATOR_CURRENT); + bq2415x_regulator_register(bq, weak_battery_voltage, 1, REGULATOR_VOLTAGE); + bq2415x_regulator_register(bq, battery_regulation_voltage, 2, REGULATOR_VOLTAGE); + bq2415x_regulator_register(bq, charge_current_sense_voltage, 3, REGULATOR_VOLTAGE); + bq2415x_regulator_register(bq, ermination_current_sense_voltage, 4, REGULATOR_VOLTAGE);*/ + return 0; +} + +static void bq2415x_regulator_exit(struct bq2415x_device *bq) +{ +/* bq2415x_regulator_unregister(bq, current_limit); + bq2415x_regulator_unregister(bq, weak_battery_voltage); + bq2415x_regulator_unregister(bq, battery_regulation_voltage); + bq2415x_regulator_unregister(bq, charge_current_sense_voltage); + bq2415x_regulator_unregister(bq, termination_current_sense_voltage);*/ +} + +#undef bq2415x_regulator_register +#undef bq2415x_regulator_unregister + +/* power supply */ + +static enum power_supply_property bq2415x_power_supply_props[] = { + /* TODO */ + POWER_SUPPLY_PROP_STATUS, +}; + +static void bq2415x_power_supply_work(struct work_struct *work) +{ + struct bq2415x_device *bq = container_of(work, struct bq2415x_device, work.work); + int ret; + + dev_info(bq->dev, "bq2415x_power_supply_work\n"); + + if (bq->watchdog != 1) + return; + + ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET); + if (ret < 0) + bq->watchdog = ret; + + if (bq->watchdog != 1) + return; + + /* TODO charge */ + + schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ); +} + +static void bq2415x_power_supply_update_watchdog(struct bq2415x_device *bq, int auto_enable) +{ + if (auto_enable && bq->watchdog == 1) + return; + + if (!auto_enable && bq->watchdog == 0) + return; + + if (auto_enable) { + bq->watchdog = 1; + schedule_delayed_work(&bq->work, BQ2415X_WATCHDOG_TIMEOUT * HZ); + bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET); + } else { + bq->watchdog = 0; + cancel_delayed_work_sync(&bq->work); + } +} + +static int bq2415x_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) +{ + struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger); + /* TODO */ + return -EINVAL; +} + +static int bq2415x_power_supply_init(struct bq2415x_device *bq) +{ + int ret; + + bq->charger.name = bq->name; + bq->charger.type = POWER_SUPPLY_TYPE_USB; + bq->charger.properties = bq2415x_power_supply_props; + bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props); + bq->charger.get_property = bq2415x_power_supply_get_property; + + ret = power_supply_register(bq->dev, &bq->charger); + if (ret) + return ret; + + INIT_DELAYED_WORK(&bq->work, bq2415x_power_supply_work); + + bq2415x_power_supply_update_watchdog(bq, 1); + return ret; +} + +static void bq2415x_power_supply_exit(struct bq2415x_device *bq) +{ + bq->watchdog = 0; + cancel_delayed_work_sync(&bq->work); + power_supply_unregister(&bq->charger); +} + +/* sysfs files */ + +static ssize_t bq2415x_sysfs_show_status(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger); + enum bq2415x_command command; + int ret; + + if (strcmp(attr->attr.name, "otg_status") == 0) + command = BQ2415X_OTG_STATUS; + else if (strcmp(attr->attr.name, "charge_status") == 0) + command = BQ2415X_CHARGE_STATUS; + else if (strcmp(attr->attr.name, "boost_status") == 0) + command = BQ2415X_BOOST_STATUS; + else if (strcmp(attr->attr.name, "fault_status") == 0) + command = BQ2415X_FAULT_STATUS; + else + return -EINVAL; + + ret = bq2415x_exec_command(bq, command); + if (ret < 0) + return ret; + else + return sprintf(buf, "%d\n", ret); +} + +static ssize_t bq2415x_sysfs_set_watchdog(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger); + int ret = 0; + + if (strncmp(buf, "auto", 4) == 0) + bq2415x_power_supply_update_watchdog(bq, 1); + else if (strncmp(buf, "off", 3) == 0) + bq2415x_power_supply_update_watchdog(bq, 0); + else + ret = bq2415x_exec_command(bq, BQ2415X_WATCHDOG_RESET); + + if (ret < 0) + return ret; + else + return count; +} + +static ssize_t bq2415x_sysfs_show_watchdog(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq2415x_device *bq = container_of(psy, struct bq2415x_device, charger); + + switch (bq->watchdog) { + case 1: return sprintf(buf, "auto\n"); + case 0: return sprintf(buf, "off\n"); + default: return sprintf(buf, "error %d\n", bq->watchdog); + } +} + +/* TODO: Add other sysfs entries */ + +static DEVICE_ATTR(watchdog, S_IWUSR | S_IRUGO, bq2415x_sysfs_show_watchdog, bq2415x_sysfs_set_watchdog); +static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); +static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL); + +static struct attribute *bq2415x_sysfs_attributes[] = { + &dev_attr_watchdog.attr, + &dev_attr_otg_status.attr, + &dev_attr_charge_status.attr, + &dev_attr_boost_status.attr, + &dev_attr_fault_status.attr, + NULL, +}; + +static const struct attribute_group bq2415x_sysfs_attr_group = { + .attrs = bq2415x_sysfs_attributes, +}; + +static int bq2415x_sysfs_init(struct bq2415x_device *bq) +{ + return sysfs_create_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group); +} + +static void bq2415x_sysfs_exit(struct bq2415x_device *bq) +{ + sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group); +} + +/* charger led device on STAT_PIN */ + +static void bq2415x_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + struct bq2415x_device *bq = i2c_get_clientdata(client); + + if (value) + bq2415x_exec_command(bq, BQ2415X_STAT_PIN_ENABLE); + else + bq2415x_exec_command(bq, BQ2415X_STAT_PIN_DISABLE); +} + +static enum led_brightness bq2415x_led_get(struct led_classdev *led_cdev) +{ + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + struct bq2415x_device *bq = i2c_get_clientdata(client); + + if (bq2415x_exec_command(bq, BQ2415X_STAT_PIN_STATUS) > 0) + return LED_FULL; + else + return LED_OFF; +} + +static int bq2415x_led_init(struct bq2415x_device *bq) +{ + bq->led.name = bq->name; + bq->led.brightness_set = bq2415x_led_set; + bq->led.brightness_get = bq2415x_led_get; + return led_classdev_register(bq->dev, &bq->led); +} + +static void bq2415x_led_exit(struct bq2415x_device *bq) +{ + led_classdev_unregister(&bq->led); +} + +/* bq2415x register */ + +static int bq2415x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret; + int num; + char *name; + struct bq2415x_device *bq; + + if (!client->dev.platform_data) { + dev_err(&client->dev, "platform data not set\n"); + return -ENODEV; + } + + /* Get new ID for the new device */ + ret = idr_pre_get(&bq2415x_id, GFP_KERNEL); + if (ret == 0) + return -ENOMEM; + + mutex_lock(&bq2415x_mutex); + ret = idr_get_new(&bq2415x_id, client, &num); + mutex_unlock(&bq2415x_mutex); + + if (ret < 0) + return ret; + + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + if (!name) { + dev_err(&client->dev, "failed to allocate device name\n"); + ret = -ENOMEM; + goto error_1; + } + + bq = kzalloc(sizeof(*bq), GFP_KERNEL); + if (!bq) { + dev_err(&client->dev, "failed to allocate device data\n"); + ret = -ENOMEM; + goto error_2; + } + + i2c_set_clientdata(client, bq); + + bq->id = num; + bq->dev = &client->dev; + bq->chip = id->driver_data; + bq->name = name; + bq->platform_data = client->dev.platform_data; + bq->watchdog = 0; + + bq2415x_reset_chip(bq); + + ret = bq2415x_regulator_init(bq); + if (ret) { + dev_err(bq->dev, "failed to register regulators: %d\n", ret); + goto error_3; + } + + ret = bq2415x_led_init(bq); + if (ret) { + dev_err(bq->dev, "failed to register led device: %d\n", ret); + goto error_4; + } + + ret = bq2415x_power_supply_init(bq); + if (ret) { + dev_err(bq->dev, "failed to register power supply: %d\n", ret); + goto error_5; + } + + ret = bq2415x_sysfs_init(bq); + if (ret) { + dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret); + goto error_6; + } + + bq2415x_set_defaults(bq); + + dev_info(bq->dev, "driver registred\n"); + return 0; + +/*error_7:*/ + bq2415x_sysfs_exit(bq); +error_6: + bq2415x_power_supply_exit(bq); +error_5: + bq2415x_led_exit(bq); +error_4: + bq2415x_regulator_exit(bq); +error_3: + kfree(bq); +error_2: + kfree(name); +error_1: + mutex_lock(&bq2415x_mutex); + idr_remove(&bq2415x_id, num); + mutex_unlock(&bq2415x_mutex); + + return ret; +} + +/* bq2415x unregister */ + +static int bq2415x_remove(struct i2c_client *client) +{ + struct bq2415x_device *bq = i2c_get_clientdata(client); + + bq2415x_sysfs_exit(bq); + bq2415x_power_supply_exit(bq); + bq2415x_led_exit(bq); + bq2415x_regulator_exit(bq); + + bq2415x_reset_chip(bq); + + mutex_lock(&bq2415x_mutex); + idr_remove(&bq2415x_id, bq->id); + mutex_unlock(&bq2415x_mutex); + + dev_info(bq->dev, "driver unregistred\n"); + + kfree(bq->name); + kfree(bq); + + return 0; +} + +static const struct i2c_device_id bq2415x_i2c_id_table[] = { + { "bq2415x", BQUNKNOWN }, + { "bq24150", BQ24150 }, + { "bq24150a", BQ24150A }, + { "bq24151", BQ24151 }, + { "bq24151a", BQ24151A }, + { "bq24152", BQ24152 }, + { "bq24153", BQ24153 }, + { "bq24153a", BQ24153A }, + { "bq24155", BQ24155 }, + { "bq24156", BQ24156 }, + { "bq24156a", BQ24156A }, + { "bq24158", BQ24158 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table); + +static struct i2c_driver bq2415x_driver = { + .driver = { + .name = "bq2415x-charger", + }, + .probe = bq2415x_probe, + .remove = bq2415x_remove, + .id_table = bq2415x_i2c_id_table, +}; + +/* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */ +static struct bq2415x_platform_data rx51_platform_data = { + .current_limit = 100, /* mA */ + .weak_battery_voltage = 3400, /* mV */ + .battery_regulation_voltage = 4200, /* mV */ + .charge_current_sense_voltage = -1, /* TODO */ + .termination_current_sense_voltage = -1, /* TODO */ +}; +static struct i2c_board_info rx51_board_info = { + I2C_BOARD_INFO("bq24150", 0x6b), + .platform_data = &rx51_platform_data, +}; +static struct i2c_client *client; +/* END */ + +static int __init bq2415x_init(void) +{ + /* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */ + client = i2c_new_device(i2c_get_adapter(2), &rx51_board_info); + /* END */ + + return i2c_add_driver(&bq2415x_driver); +} +module_init(bq2415x_init); + +static void __exit bq2415x_exit(void) +{ + /* BEGIN: Temporary RX51 hack TODO: move to board-rx51.c */ + i2c_unregister_device(client); + /* END */ + + i2c_del_driver(&bq2415x_driver); +} +module_exit(bq2415x_exit); + +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("bq2415x charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq2415x_charger.h b/drivers/power/bq2415x_charger.h new file mode 100644 index 0000000..de0ec02 --- /dev/null +++ b/drivers/power/bq2415x_charger.h @@ -0,0 +1,97 @@ +/* + bq2415x_charger.h - bq2415x charger driver + Copyright (C) 2011 Pali Rohár + + 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; either version 2 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef BQ2415X_CHARGER_H +#define BQ2415X_CHARGER_H + +struct bq2415x_device; + +struct bq2415x_platform_data { + int current_limit; + int weak_battery_voltage; + int battery_regulation_voltage; + int charge_current_sense_voltage; + int termination_current_sense_voltage; +}; + +enum bq2415x_command { + BQ2415X_WATCHDOG_RESET, + BQ2415X_OTG_STATUS, + BQ2415X_STAT_PIN_STATUS, + BQ2415X_STAT_PIN_ENABLE, + BQ2415X_STAT_PIN_DISABLE, + BQ2415X_CHARGE_STATUS, + BQ2415X_BOOST_STATUS, + BQ2415X_FAULT_STATUS, + + BQ2415X_CHARGE_CURRENT_TERMINATION_STATUS, + BQ2415X_CHARGE_CURRENT_TERMINATION_ENABLE, + BQ2415X_CHARGE_CURRENT_TERMINATION_DISABLE, + BQ2415X_CHARGER_STATUS, + BQ2415X_CHARGER_ENABLE, + BQ2415X_CHARGER_DISABLE, + BQ2415X_HIGH_IMPEDANCE_STATUS, + BQ2415X_HIGH_IMPEDANCE_ENABLE, + BQ2415X_HIGH_IMPEDANCE_DISABLE, + BQ2415X_MODE, + BQ2415X_SET_BOOST_MODE, + BQ2415X_SET_CHARGER_MODE, + + BQ2415X_OTG_HIGH_STATUS, + BQ2415X_OTG_HIGH_ACTIVATE, + BQ2415X_OTG_LOW_ACTIVATE, + BQ2415X_OTG_PIN_STATUS, + BQ2415X_OTG_PIN_ENABLE, + BQ2415X_OTG_PIN_DISABLE, + + BQ2415X_VENDER_CODE, + BQ2415X_PART_NUMBER, + BQ2415X_REVISION, +}; + +enum bq2415x_chip { + BQUNKNOWN, + BQ24150, + BQ24150A, + BQ24151, + BQ24151A, + BQ24152, + BQ24153, + BQ24153A, + BQ24155, + BQ24156, + BQ24156A, + BQ24158, +}; + +enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq); +int bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command); +int bq2415x_set_defaults(struct bq2415x_device *bq); +int bq2415x_set_current_limit(struct bq2415x_device *bq, int uA); +int bq2415x_get_current_limit(struct bq2415x_device *bq); +int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV); +int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq); +int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV); +int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq); +int bq2415x_set_charge_current_sense_voltage(struct bq2415x_device *bq, int mV); +int bq2415x_get_charge_current_sense_voltage(struct bq2415x_device *bq); +int bq2415x_set_termination_current_sense_voltage(struct bq2415x_device *bq, int mV); +int bq2415x_get_termination_current_sense_voltage(struct bq2415x_device *bq); + +#endif -- Pali Rohár pali.rohar@gmail.com