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