diff --git a/arch/arm/configs/zero-gravitas_defconfig b/arch/arm/configs/zero-gravitas_defconfig index c793525b9e03..64fdb3c007b3 100644 --- a/arch/arm/configs/zero-gravitas_defconfig +++ b/arch/arm/configs/zero-gravitas_defconfig @@ -140,7 +140,7 @@ CONFIG_I2C_IMX=y CONFIG_SPI=y CONFIG_GPIO_SYSFS=y CONFIG_POWER_SUPPLY=y -CONFIG_BATTERY_BQ27XXX=y +CONFIG_BATTERY_BQ27441=y CONFIG_CHARGER_GPIO=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_SYSCON=y diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 09da31383f7b..fa533ae4d314 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -157,6 +157,12 @@ config BATTERY_SBS Say Y to include support for SBS battery driver for SBS-compliant gas gauges. +config BATTERY_BQ27441 + tristate "BQ27441 quote unqote advanced battery driver" + depends on I2C || I2C=n + help + Say Y here to use a special driver for the BQ27441. + config BATTERY_BQ27XXX tristate "BQ27xxx battery driver" depends on I2C || I2C=n diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 4fff4d647f4c..d33f37356422 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o +obj-$(CONFIG_BATTERY_BQ27441) += bq27441/bq27441_battery.o bq27441/bq27xxx_battery.o bq27441/bq27xxx_battery_i2c.o obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o diff --git a/drivers/power/bq27441/bq27441_battery.c b/drivers/power/bq27441/bq27441_battery.c new file mode 100644 index 000000000000..5047cc9bc504 --- /dev/null +++ b/drivers/power/bq27441/bq27441_battery.c @@ -0,0 +1,1259 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bq27xxx_battery.h" + +#define CONFIG_VERSION 7 +#define CONFIG_VERSION_FACTORY_RESET 0xFF + +#define BQ27441_CONTROL_STATUS 0x0000 +#define BQ27441_DEVICE_TYPE 0x0001 +#define BQ27441_FW_VERSION 0x0002 +#define BQ27441_DM_CODE 0x0004 +#define BQ27441_PREV_MACWRITE 0x0007 +#define BQ27441_CHEM_ID 0x0008 +#define BQ27441_BAT_INSERT 0x000C +#define BQ27441_BAT_REMOVE 0x000D +#define BQ27441_SET_HIBERNATE 0x0011 +#define BQ27441_CLEAR_HIBERNATE 0x0012 +#define BQ27441_SET_CFGUPDATE 0x0013 +#define BQ27441_SHUTDOWN_ENABLE 0x001B +#define BQ27441_SHUTDOWN 0x001C +#define BQ27441_SEALED 0x0020 +#define BQ27441_PULSE_SOC_INT 0x0023 +#define BQ27441_RESET 0x0041 +#define BQ27441_SOFT_RESET 0x0042 + +#define BQ27441_UNSEAL 0x8000 + +#define BQ27441_CONTROL_1 0x00 +#define BQ27441_CONTROL_2 0x01 +#define BQ27441_TEMPERATURE 0x02 +#define BQ27441_VOLTAGE 0x04 +#define BQ27441_FLAGS 0x06 +#define BQ27441_FLAGS_CFGUPMODE (1 << 4) +#define BQ27441_FLAGS_ITPOR (1 << 5) +#define BQ27441_NOMINAL_AVAIL_CAPACITY 0x08 +#define BQ27441_FULL_AVAIL_CAPACITY 0x0a +#define BQ27441_REMAINING_CAPACITY 0x0c +#define BQ27441_FULL_CHG_CAPACITY 0x0e +#define BQ27441_AVG_CURRENT 0x10 +#define BQ27441_STANDBY_CURRENT 0x12 +#define BQ27441_MAXLOAD_CURRENT 0x14 +#define BQ27441_AVERAGE_POWER 0x18 +#define BQ27441_STATE_OF_CHARGE 0x1c +#define BQ27441_INT_TEMPERATURE 0x1e +#define BQ27441_STATE_OF_HEALTH 0x20 + +#define BQ27441_OPCONF_BATLOWEN (1 << 3) +#define BQ27441_OPCONF_GPIOPOL (1 << 3) + +#define BQ27441_BLOCK_DATA_CHECKSUM 0x60 +#define BQ27441_BLOCK_DATA_CONTROL 0x61 +#define BQ27441_DATA_BLOCK_CLASS 0x3E +#define BQ27441_DATA_BLOCK 0x3F + +#define BQ27441_OPCONFIG_1 0x40 +#define BQ27441_OPCONFIG_2 0x41 + +#define BQ27441_DESIGN_CAPACITY_1 0x4A +#define BQ27441_DESIGN_CAPACITY_2 0x4B +#define BQ27441_DESIGN_ENERGY_1 0x4C +#define BQ27441_DESIGN_ENERGY_2 0x4D +#define BQ27441_TAPER_RATE_1 0x5B +#define BQ27441_TAPER_RATE_2 0x5C +#define BQ27441_TERMINATE_VOLTAGE_1 0x50 +#define BQ27441_TERMINATE_VOLTAGE_2 0x51 +#define BQ27441_V_CHG_TERM_1 0x41 +#define BQ27441_V_CHG_TERM_2 0x42 + +#define BQ27441_BATTERY_LOW 15 +#define BQ27441_BATTERY_FULL 100 + +#define BQ27441_MAX_REGS 0x7F + +struct bq27441_extended_cmd { + u8 datablock[2]; + u8 command[32]; + u8 checksum; + u16 wait_time; +}; + +static const struct bq27441_extended_cmd zerogravitas_golden_file[] = { + { + .datablock = {0x02, 0x00}, + .command = { + 0x02, 0x26, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0xA5, + .wait_time = 10, + }, + { + .datablock = {0x24, 0x00}, + .command = { + 0x00, 0x19, 0x28, 0x63, 0x5F, 0xFF, 0x62, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x69, + .wait_time = 10, + }, + { + .datablock = {0x30, 0x00}, + .command = { + 0x0E, 0x74, 0xFD, 0xFF, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x49, + .wait_time = 10, + }, + { + .datablock = {0x31, 0x00}, + .command = { + 0x0A, 0x0F, 0x02, 0x05, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0xAD, + .wait_time = 10, + }, + { + .datablock = {0x40, 0x00}, + .command = { + 0x25, 0xFC, 0x0F, 0x48, 0x00, 0x14, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x66, + .wait_time = 10, + }, + { + .datablock = {0x44, 0x00}, + .command = { + 0x05, 0x00, 0x32, 0x01, 0xC2, 0x14, 0x14, 0x00, + 0x03, 0x08, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x39, + .wait_time = 10, + }, + { + .datablock = {0x50, 0x00}, + .command = { + 0x02, 0xBC, 0x01, 0x2C, 0x00, 0x1E, 0x00, 0xC8, + 0xC8, 0x14, 0x08, 0x00, 0x3C, 0x0E, 0x10, 0x00, + 0x0A, 0x46, 0x05, 0x14, 0x05, 0x0F, 0x03, 0x20, + 0x00, 0x64, 0x46, 0x50, 0x0A, 0x01, 0x90, 0x00}, + .checksum = 0xBB, + .wait_time = 10, + }, + { + .datablock = {0x50, 0x01}, + .command = { + 0x64, 0x19, 0xDC, 0x5C, 0x60, 0x00, 0x7D, 0x00, + 0x04, 0x03, 0x19, 0x25, 0x0F, 0x14, 0x0A, 0x78, + 0x60, 0x28, 0x01, 0xF4, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x43, 0x80, 0x04, 0x01, 0x14, 0x00}, + .checksum = 0x2A, + .wait_time = 10, + }, + { + .datablock = {0x50, 0x02}, + .command = { + 0x0B, 0x0B, 0xB8, 0x01, 0x2C, 0x0A, 0x01, 0x0A, + 0x00, 0x00, 0x00, 0xC8, 0x00, 0x64, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0xC1, + .wait_time = 10, + }, + { + .datablock = {0x51, 0x00}, + .command = { + 0x00, 0xA7, 0x00, 0x64, 0x00, 0xFA, 0x00, 0x3C, + 0x3C, 0x01, 0xB3, 0xB3, 0x01, 0x90, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x8A, + .wait_time = 10, + }, + { + .datablock = {0x52, 0x00}, + .command = { + 0x41, 0x8F, 0x01, 0x00, 0x00, 0x81, 0x0E, 0xDB, + 0x0E, 0xA8, 0x0B, 0xB8, 0x2B, 0x5C, 0x05, 0x3C, + 0x0D, 0x16, 0x00, 0xC8, 0x00, 0x32, 0x00, 0x14, + 0x03, 0xE8, 0x01, 0x01, 0x2C, 0x10, 0x04, 0x00}, + .checksum = 0x25, + .wait_time = 10, + }, + { + .datablock = {0x52, 0x01}, + .command = { + 0x0A, 0x10, 0x5E, 0xFF, 0xCE, 0xFF, 0xCE, 0x00, + 0x02, 0x02, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0x2D, + .wait_time = 10, + }, + { + .datablock = {0x59, 0x00}, + .command = { + 0x00, 0x80, 0x00, 0x80, 0x00, 0x83, 0x00, 0x90, + 0x00, 0x74, 0x00, 0x70, 0x00, 0x7D, 0x00, 0x8C, + 0x00, 0x88, 0x00, 0x8B, 0x00, 0xB4, 0x00, 0xD8, + 0x01, 0x90, 0x04, 0x17, 0x06, 0x82, 0x00, 0x00}, + .checksum = 0x2C, + .wait_time = 10, + }, + { + .datablock = {0x70, 0x00}, + .command = { + 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .checksum = 0xFF, + .wait_time = 10, + }, +}; + +static inline bool is_reg_valid(int reg) +{ + return (reg >= 0x00 && reg <= BQ27441_MAX_REGS); +} + +static inline int read_byte(struct bq27xxx_device_info *di, int reg) +{ + if (!di || !is_reg_valid(reg)) + return -EINVAL; + + return di->bus.read(di, reg, true); +} + +static inline int read_word(struct bq27xxx_device_info *di, int reg) +{ + if (!di || !is_reg_valid(reg)) + return -EINVAL; + + return di->bus.read(di, reg, false); +} + +static inline int write_byte(struct bq27xxx_device_info *di, int reg, + u8 data) +{ + int ret; + if (!di || !is_reg_valid(reg)) + return -EINVAL; + + ret = di->bus.write(di, reg, &data, sizeof(data)); + usleep_range(100, 200); + + return ret; +} + +static inline int write_word(struct bq27xxx_device_info *di, int reg, + u16 data) +{ + int ret; + unsigned char buf[2] = {(data & 0xff), (data >> 8)}; + + if (!di || !is_reg_valid(reg)) + return -EINVAL; + + ret = di->bus.write(di, reg, buf, sizeof(buf)); + usleep_range(100, 200); + + return ret; +} + +static inline int write_array(struct bq27xxx_device_info *di, int reg, + const u8 *data, size_t len) +{ + int ret; + + if (!di || !is_reg_valid(reg)) + return -EINVAL; + + ret = di->bus.write(di, reg, data, len);; + usleep_range(100, 200); /* This chip is slooooooow */ + + return ret; +} + +static inline int control_read(struct bq27xxx_device_info *di, const u16 addr) +{ + int ret; + ret = write_word(di, BQ27441_CONTROL_1, addr); + if (ret < 0) + return ret; + + usleep_range(100, 200); + + return read_word(di, BQ27441_CONTROL_1); +} + +static inline int control_write(struct bq27xxx_device_info *di, const u16 cmd) +{ + return write_word(di, BQ27441_CONTROL_1, cmd); +} + +static inline int write_extended_cmd(struct bq27xxx_device_info *di, + const struct bq27441_extended_cmd *cmd) +{ + int ret; + u8 read_checksum; + + ret = write_array(di, BQ27441_DATA_BLOCK_CLASS, cmd->datablock, + sizeof(cmd->datablock)); + if (ret < 0 || ret != sizeof(cmd->datablock) + 1) { + dev_warn(di->dev, + "Failed to write datablock to %02X-%02X (id %u), ret %d\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], ret); + return ret; + } + + ret = write_array(di, 0x40, cmd->command, sizeof(cmd->command)); + if (ret < 0 || ret != sizeof(cmd->command) + 1) { + dev_warn(di->dev, + "Failed to write command to %02X-%02X (id %u), ret %d\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], ret); + return ret; + } + + ret = write_array(di, BQ27441_BLOCK_DATA_CHECKSUM, &cmd->checksum, + sizeof(cmd->checksum)); + if (ret < 0 || ret != sizeof(cmd->checksum) + 1) { + dev_warn(di->dev, + "Failed to write checksum to %02X-%02X (id: %u), ret %d\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], ret); + return ret; + } + + usleep_range(cmd->wait_time * 1000, cmd->wait_time * 1100); + + ret = write_array(di, BQ27441_DATA_BLOCK_CLASS, cmd->datablock, + sizeof(cmd->datablock)); + if (ret < 0 || ret != sizeof(cmd->datablock) + 1) { + dev_warn(di->dev, + "Failed to write datablock second time to %02X-%02X (id: %u), ret %d\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], ret); + return ret; + } + + ret = read_byte(di, BQ27441_BLOCK_DATA_CHECKSUM); + if (ret < 0) { + dev_warn(di->dev, + "Failed to read checksum for %02X-%02X (id: %u), ret %d\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], ret); + return ret; + } + + read_checksum = ret & 0xFF; + if (read_checksum != cmd->checksum) { + dev_warn(di->dev, + "Failed to write to %02X-%02X (id: %u), checksum %02x read back %02x\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0], + cmd->checksum, read_checksum); + return -EINVAL; + } + + dev_info(di->dev, + "Happily wrote to %02X-%02X (id: %u)\n", + cmd->datablock[0], cmd->datablock[1], cmd->datablock[0]); + + return 0; +} + +static inline int read_extended_byteorword(struct bq27xxx_device_info *di, + u8 dataclass, u8 offset, bool single) +{ + int ret; + u8 datablock = offset / 32; + u8 dataclassblock[] = {dataclass, datablock}; + + ret = write_array(di, BQ27441_DATA_BLOCK_CLASS, dataclassblock, + sizeof(dataclassblock)); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + ret = di->bus.read(di, 0x40 + offset % 32, single); + if (ret < 0) + return ret; + + if (single) + return (ret & 0xff); + else + return ( ((ret & 0xff) << 8) | ((ret & 0xff00) >> 8) ); +} + +static inline int write_extended_byteorword(struct bq27xxx_device_info *di, + u8 dataclass, u8 offset, const u8 *data, bool single) +{ + int ret; + u8 old_checksum; + u8 read_checksum; + u8 old_data[2]; + u8 new_data[2] = {data[0], single ? 0 : data[1]}; + int new_checksum; + int temp_checksum; + u8 datablock = offset / 32; + u8 dataclassblock[] = {dataclass, datablock}; + + ret = write_array(di, BQ27441_DATA_BLOCK_CLASS, dataclassblock, + sizeof(dataclassblock)); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + ret = read_byte(di, BQ27441_BLOCK_DATA_CHECKSUM); + if (ret < 0) + return ret; + + old_checksum = ret & 0xff; + + ret = di->bus.read(di, 0x40 + offset % 32, single); + if (ret < 0) + return ret; + + old_data[0] = ret & 0xff; + old_data[1] = single ? 0 : ((ret & 0xff00) >> 8); + + temp_checksum = (255 - old_checksum - old_data[0] - old_data[1]) % 256; + new_checksum = 255 - ((temp_checksum + new_data[0] + new_data[1]) % 256); + new_checksum &= 0xFF; + + ret = write_array(di, 0x40 + offset % 32, new_data, single ? 1 : 2); + if (ret < 0) + return ret; + + ret = write_byte(di, BQ27441_BLOCK_DATA_CHECKSUM, new_checksum); + if (ret < 0) + return ret; + + usleep_range(10000, 11000); + + ret = write_array(di, BQ27441_DATA_BLOCK_CLASS, dataclassblock, + sizeof(dataclassblock)); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + ret = read_byte(di, BQ27441_BLOCK_DATA_CHECKSUM); + if (ret < 0) + return ret; + + read_checksum = ret & 0xFF; + if (read_checksum != new_checksum) { + dev_warn(di->dev, + "Failed to write to %02X-%02X (id: %u), checksum %02x read back %02x\n", + dataclass, datablock, dataclass, + new_checksum, read_checksum); + return -EINVAL; + } + + dev_info(di->dev, + "Happily wrote to %02X-%02X (id: %u)\n", + dataclass, datablock, dataclass); + + return 0; +} + +static inline int config_mode_start(struct bq27xxx_device_info *di) +{ + int ret; + int flags_lsb; + int control_status; + unsigned long timeout; + + ret = read_word(di, BQ27441_FLAGS); + if (ret < 0) + return ret; + + dev_info(di->dev, "Flags: 0x%04x\n", ret); + flags_lsb = (ret & 0xff); + + if (flags_lsb & BQ27441_FLAGS_CFGUPMODE) { + dev_info(di->dev, "Device already in config mode\n"); + return 0; + } + + /* unseal the fuel gauge for data access if needed */ + + ret = control_read(di, BQ27441_CONTROL_STATUS); + if (ret < 0) + return ret; + + dev_info(di->dev, "Control status before unseal: 0x%04x\n", ret); + control_status = ret; + + if (control_status & 0x2000) { + ret = control_write(di, BQ27441_UNSEAL); + if (ret < 0) + return ret; + + ret = control_write(di, BQ27441_UNSEAL); + if (ret < 0) + return ret; + } + else + dev_info(di->dev, "Device already unsealed\n"); + + usleep_range(1000, 2000); + + ret = control_read(di, BQ27441_CONTROL_STATUS); + if (ret < 0) + return ret; + + dev_info(di->dev, "Control status after unseal: 0x%04x\n", ret); + + /* Set fuel gauge in config mode */ + ret = control_write(di, BQ27441_SET_CFGUPDATE); + if (ret < 0) + return ret; + + /* Wait for config mode */ + timeout = jiffies + HZ; + flags_lsb = 0; + while (!flags_lsb) { + ret = read_byte(di, BQ27441_FLAGS); + if (ret < 0) + return ret; + + flags_lsb = (ret & BQ27441_FLAGS_CFGUPMODE); + dev_info(di->dev, "flags_lsb %02x ret %02x\n", flags_lsb, ret); + + if (time_after(jiffies, timeout)) { + dev_warn(di->dev, "Timeout waiting for cfg update\n"); + return -EIO; + } + usleep_range(1000, 2000); + } + + /* Enable block mode */ + ret = write_byte(di, BQ27441_BLOCK_DATA_CONTROL, 0x00); + if (ret < 0) { + dev_warn(di->dev, "Unable to enable block mode, ret %d\n", ret); + return ret; + } + + return 0; +} + +static inline int config_mode_stop(struct bq27xxx_device_info *di) +{ + int ret; + int flags_lsb; + unsigned long timeout = jiffies + HZ; + + ret = read_byte(di, BQ27441_FLAGS); + if (ret < 0) + return ret; + + if (ret & BQ27441_FLAGS_CFGUPMODE) { + dev_info(di->dev, "Exiting config mode by soft reset\n"); + + ret = control_write(di, BQ27441_SOFT_RESET); + if (ret < 0) + return ret; + + /* Wait for config mode to exit */ + timeout = jiffies + HZ; + flags_lsb = BQ27441_FLAGS_CFGUPMODE; + while (flags_lsb) { + ret = read_byte(di, BQ27441_FLAGS); + if (ret < 0) + return ret; + + flags_lsb = (ret & BQ27441_FLAGS_CFGUPMODE); + + if (time_after(jiffies, timeout)) { + dev_warn(di->dev, "Timeout waiting for cfg update stop\n"); + return -EIO; + } + usleep_range(1000, 2000); + } + } + + /* seal the fuel gauge */ +#if 0 /* fixme LIM: Re-enable in production code */ + ret = control_write(di, BQ27441_SEALED); + if (ret < 0) + return ret; +#endif + + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +struct fsfile { + const char *name; + unsigned char reg; + unsigned char dataclass; + struct file_operations fops; + mode_t mode; +}; + +#define FSFOPS_R(readfunc) \ + .fops = {.open = simple_open, .write = NULL, .read = readfunc, .owner = THIS_MODULE}, .mode = S_IRUGO + +#define FSFOPS_RW(readfunc, writefunc) \ + .fops = {.open = simple_open, .write = writefunc, .read = readfunc, .owner = THIS_MODULE}, .mode = (S_IRUGO | S_IWUGO) + +static ssize_t debugfs_show_ext_u16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_store_ext_u16(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_show_ext_u8(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_store_ext_u8(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_polarity_show(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_polarity_store(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_factoryforce_show(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_factoryforce_store(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset); + +static ssize_t debugfs_show_u16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_show_s16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_show_u8(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); +static ssize_t debugfs_show_u8hex(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset); + +static const struct fsfile fsfiles[] = { + {.name = "FullAvailableCap", .reg = 0x0A, FSFOPS_R(debugfs_show_u16)}, + {.name = "RemainingCapacity", .reg = 0x0C, FSFOPS_R(debugfs_show_u16)}, + {.name = "StandbyCurrent", .reg = 0x12, FSFOPS_R(debugfs_show_s16)}, + {.name = "MaxLoadCurrent", .reg = 0x14, FSFOPS_R(debugfs_show_s16)}, + {.name = "AveragePower", .reg = 0x18, FSFOPS_R(debugfs_show_s16)}, + {.name = "InternalTemperature", .reg = 0x1E, FSFOPS_R(debugfs_show_u16)}, + {.name = "StateOfHealth", .reg = 0x20, FSFOPS_R(debugfs_show_u8)}, + {.name = "StateOfHealthStatus", .reg = 0x21, FSFOPS_R(debugfs_show_u8hex)}, + {.name = "RemainingCapUnfiltered", .reg = 0x28, FSFOPS_R(debugfs_show_u16)}, + {.name = "RemainingCapFiltered", .reg = 0x2A, FSFOPS_R(debugfs_show_u16)}, + {.name = "FullChargeCapUnfiltered", .reg = 0x2C, FSFOPS_R(debugfs_show_u16)}, + {.name = "FullChargeCapFiltered", .reg = 0x2E, FSFOPS_R(debugfs_show_u16)}, + {.name = "StateOfChargeUnfiltered", .reg = 0x30, FSFOPS_R(debugfs_show_u16)}, + {.name = "OpConfig_0", .reg = 0x3A, FSFOPS_R(debugfs_show_u8hex)}, + {.name = "OpConfig_1", .reg = 0x3B, FSFOPS_R(debugfs_show_u8hex)}, + + /* Extended */ + {.name = "SOC1SetThreshold", .reg = 0, .dataclass = 49, FSFOPS_RW(debugfs_show_ext_u8, debugfs_store_ext_u8)}, + {.name = "SOC1ClearThreshold", .reg = 1, .dataclass = 49, FSFOPS_RW(debugfs_show_ext_u8, debugfs_store_ext_u8)}, + {.name = "DMCode", .reg = 3, .dataclass = 64, FSFOPS_RW(debugfs_show_ext_u8, debugfs_store_ext_u8)}, + {.name = "MinDeltaVoltage", .reg = 72, .dataclass = 80, FSFOPS_RW(debugfs_show_ext_u16, debugfs_store_ext_u16)}, + {.name = "MaxDeltaVoltage", .reg = 74, .dataclass = 80, FSFOPS_RW(debugfs_show_ext_u16, debugfs_store_ext_u16)}, + {.name = "QMaxCell0", .reg = 0, .dataclass = 82, FSFOPS_R(debugfs_show_ext_u16)}, + {.name = "TerminateVoltage", .reg = 16, .dataclass = 82, FSFOPS_RW(debugfs_show_ext_u16, debugfs_store_ext_u16)}, + {.name = "VatChgTerm", .reg = 33, .dataclass = 82, FSFOPS_RW(debugfs_show_ext_u16, debugfs_store_ext_u16)}, + {.name = "DeltaVoltage", .reg = 39, .dataclass = 82, FSFOPS_R(debugfs_show_ext_u16)}, + {.name = "ForceFactoryConfig", .reg = 0, .dataclass = 0, FSFOPS_RW(debugfs_factoryforce_show, debugfs_factoryforce_store)}, + {.name = "lowBat_polarity", .reg = 0, .dataclass = 0, FSFOPS_RW(debugfs_polarity_show, debugfs_polarity_store)}, +}; + +inline static int get_fsfile_match(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fsfiles); i++) { + if (!strcmp(fsfiles[i].name, name)) { + return i; + } + } + return -EINVAL; +} + +static ssize_t debugfs_show_ext_byteorword(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset, bool single) +{ + int ret; + struct bq27xxx_device_info *di = fp->private_data; + char buf[8] = {0}; + int index; + u16 mask; + + if (!di) + return -EIO; + + index = get_fsfile_match(fp->f_path.dentry->d_iname); + if (index < 0) + return index; + + mutex_lock(&di->lock); + ret = read_extended_byteorword(di, fsfiles[index].dataclass, + fsfiles[index].reg, single); + mutex_unlock(&di->lock); + + if (ret < 0) + return ret; + + mask = (single ? 0xFF : 0xFFFF); + + ret = scnprintf(buf, sizeof(buf) - 1, "%u\n", (ret & mask)); + if (ret < 0) + return ret; + + return simple_read_from_buffer(userbuf, count, offset, buf, ret); +} + +static ssize_t debugfs_show_ext_u8(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_show_ext_byteorword(fp, userbuf, count, offset, true); +} + +static ssize_t debugfs_show_ext_u16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_show_ext_byteorword(fp, userbuf, count, offset, false); +} + +static ssize_t debugfs_store_ext_byteorword(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset, bool single) +{ + int ret; + struct bq27xxx_device_info *di = fp->private_data; + u8 data[2] = {0, 0}; + int index; + u32 dataout; + + if (!di) + return -EIO; + + ret = sscanf(userbuf, "%u", &dataout); + if (ret != 1 || dataout > 65535) + return -EINVAL; + + if (single) + data[0] = dataout & 0xFF; + else { + data[0] = (dataout >> 8) & 0xFF; + data[1] = dataout & 0xFF; + } + + index = get_fsfile_match(fp->f_path.dentry->d_iname); + if (index < 0) + return index; + + mutex_lock(&di->lock); + ret = config_mode_start(di); + if (ret < 0) { + mutex_unlock(&di->lock); + return ret; + } + ret = write_extended_byteorword(di, fsfiles[index].dataclass, + fsfiles[index].reg, data, single); + mutex_unlock(&di->lock); + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t debugfs_store_ext_u8(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_store_ext_byteorword(fp, userbuf, count, offset, true); +} + +static ssize_t debugfs_store_ext_u16(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_store_ext_byteorword(fp, userbuf, count, offset, false); +} + +static ssize_t debugfs_show_s16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + int ret; + struct bq27xxx_device_info *di = fp->private_data; + char buf[8] = {0}; + int index; + s16 theword; + + if (!di) + return -EIO; + + index = get_fsfile_match(fp->f_path.dentry->d_iname); + if (index < 0) + return index; + + ret = read_word(di, fsfiles[index].reg); + if (ret < 0) + return ret; + + theword = (s16)(ret & 0xFFFF); + + ret = scnprintf(buf, sizeof(buf) - 1, "%d\n", theword); + if (ret < 0) + return ret; + + return simple_read_from_buffer(userbuf, count, offset, buf, ret); +} + +static ssize_t debugfs_show_byteword(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset, bool hex, bool single) +{ + int ret; + struct bq27xxx_device_info *di = fp->private_data; + char buf[8] = {0}; + int index; + u16 theword; + u16 mask = (single ? 0xFF : 0xFFFF); + + if (!di) + return -EIO; + + index = get_fsfile_match(fp->f_path.dentry->d_iname); + if (index < 0) + return index; + + ret = di->bus.read(di, fsfiles[index].reg, single); + if (ret < 0) + return ret; + + theword = (ret & mask); + + ret = scnprintf(buf, sizeof(buf) - 1, hex ? "0x%02X\n" : "%u\n", theword); + if (ret < 0) + return ret; + + return simple_read_from_buffer(userbuf, count, offset, buf, ret); +} + +static ssize_t debugfs_show_u16(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_show_byteword(fp, userbuf, count, offset, false, false); +} + +static ssize_t debugfs_show_u8(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_show_byteword(fp, userbuf, count, offset, false, true); +} + +static ssize_t debugfs_show_u8hex(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + return debugfs_show_byteword(fp, userbuf, count, offset, true, true); +} + +static int configure(struct bq27xxx_device_info *di); + +static int factory_reset(struct bq27xxx_device_info *di) +{ + int ret; + const u8 data = CONFIG_VERSION_FACTORY_RESET; + + ret = control_write(di, BQ27441_RESET); + if (ret < 0) { + dev_warn(di->dev, "Unable to hard reset, ret %d\n", ret); + return ret; + } + usleep_range(10000, 20000); + + ret = config_mode_start(di); + if (ret < 0) { + dev_warn(di->dev, "Unable to start config mode, ret %d\n", ret); + return ret; + } + + ret = write_extended_byteorword(di, 64, 3, &data, true); + if (ret < 0) + return ret; + + ret = config_mode_stop(di); + if (ret < 0) + return ret; + + return 0; +} + +static ssize_t debugfs_factoryforce_store(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset) +{ + int ret; + bool factoryforce; + struct bq27xxx_device_info *di = fp->private_data; + + if (!di) + return -EIO; + + if (count < 1) + return -EINVAL; + + if (userbuf[0] == '1') + factoryforce = true; + else if (userbuf[0] == '0') + factoryforce = false; + else + return -EINVAL; + + mutex_lock(&di->lock); + if (factoryforce) + ret = factory_reset(di); + else + ret = configure(di); + mutex_unlock(&di->lock); + + if (ret >= 0) + return count; + else + return ret; +} + +static ssize_t debugfs_factoryforce_show(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + int ret; + u8 dmcode; + struct bq27xxx_device_info *di = fp->private_data; + char buf[4] = {0}; + + if (!di) + return -EIO; + + mutex_lock(&di->lock); + ret = control_read(di, BQ27441_DM_CODE); + mutex_unlock(&di->lock); + + if (ret < 0) + return ret; + + dmcode = (ret & 0xff); + + ret = scnprintf(buf, sizeof(buf) - 1, "%c\n", + (dmcode == CONFIG_VERSION_FACTORY_RESET) ? '1' : '0'); + if (ret < 0) + return ret; + + return simple_read_from_buffer(userbuf, count, offset, buf, ret); +} + +static inline int get_gpiopol(struct bq27xxx_device_info *di) +{ + int ret; + + ret = read_byte(di, 0x3b); + if (ret < 0) + return ret; + + dev_info(di->dev, "%s triggered, opconfig_h(0x3b): 0x%02x\n", __func__, ret); + + return (ret & BQ27441_OPCONF_GPIOPOL); +} + +static inline int set_gpiopol(struct bq27xxx_device_info *di, bool status) +{ + int ret; + u8 opconfig1; + u8 old_opconfig1; + + dev_info(di->dev, "set_gpiopol triggered\n"); + + ret = config_mode_start(di); + if (ret < 0) { + dev_warn(di->dev, "Unable to start config mode, ret %d\n", ret); + return ret; + } + + ret = read_extended_byteorword(di, 0x40, 0, true); + if (ret < 0) + return ret; + + old_opconfig1 = (ret & 0xff); + + if (status) + opconfig1 = old_opconfig1 | BQ27441_OPCONF_GPIOPOL; + else + opconfig1 = (old_opconfig1 & ~BQ27441_OPCONF_GPIOPOL); + + ret = write_extended_byteorword(di, 0x40, 0, &opconfig1, true); + if (ret < 0) + return ret; + + ret = config_mode_stop(di); + if (ret < 0) + return ret; + + return 0; +} + +static ssize_t debugfs_polarity_store(struct file *fp, const char __user *userbuf, + size_t count, loff_t *offset) +{ + int ret; + bool status; + struct bq27xxx_device_info *di = fp->private_data; + + if (!di) + return -EIO; + + if (count < 1) + return -EINVAL; + + if (userbuf[0] == '1') + status = true; + else if (userbuf[0] == '0') + status = false; + else + return -EINVAL; + + mutex_lock(&di->lock); + ret = set_gpiopol(di, status); + mutex_unlock(&di->lock); + + if (ret >= 0) + return count; + else + return ret; +} + +static ssize_t debugfs_polarity_show(struct file *fp, char __user *userbuf, + size_t count, loff_t *offset) +{ + int ret; + int polarity; + struct bq27xxx_device_info *di = fp->private_data; + char buf[4] = {0}; + + dev_info(di->dev, "polarity_debugfs_show count %zu\n", count); + + if (!di) + return -EIO; + + mutex_lock(&di->lock); + ret = get_gpiopol(di); + mutex_unlock(&di->lock); + + if (ret < 0) + return ret; + + polarity = ret; + + ret = scnprintf(buf, sizeof(buf) - 1, "%c\n", polarity ? '1' : '0'); + if (ret < 0) + return ret; + + return simple_read_from_buffer(userbuf, count, offset, buf, ret); +} + +static int bq27441_create_debugfs(struct bq27xxx_device_info *di) +{ + int i; + + di->dfs_dir = debugfs_create_dir("bq27441", NULL); + + for (i = 0; i < ARRAY_SIZE(fsfiles); i++) { + debugfs_create_file(fsfiles[i].name, fsfiles[i].mode, di->dfs_dir, + di, &fsfiles[i].fops); + } + + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int check_fw_version(struct bq27xxx_device_info *di) +{ + /* Check firmware version + W: AA 00 01 00 + C: AA 00 21 04 + W: AA 00 02 00 + C: AA 00 09 01 + */ + + int ret; + int device_type; + int fw_version; + + device_type = control_read(di, BQ27441_DEVICE_TYPE); + if (device_type < 0) + return device_type; + + fw_version = control_read(di, BQ27441_FW_VERSION); + if (fw_version < 0) + return fw_version; + + dev_info(di->dev, "Device type %04X, Firmware version %04X\n", + device_type, fw_version); + + ret = 0; + if (device_type != 0x0421) { + dev_warn(di->dev, "Unsupported device type detected\n"); + ret = -EFAULT; + } + + if (fw_version != 0x0109) { + dev_warn(di->dev, "Unsupported firmware version detected\n"); + ret = -EFAULT; + } + + return ret; +} + +static int configure(struct bq27xxx_device_info *di) +{ + int ret; + int checksum; + int design_capacity; + int design_energy; + int taper_rate; + int terminate_voltage; + int flags_lsb; + int i; + u8 version = CONFIG_VERSION; + + flags_lsb = read_byte(di, BQ27441_FLAGS); + + ret = config_mode_start(di); + if (ret < 0) { + dev_warn(di->dev, "Failed to enter config mode\n"); + return ret; + } + + /* Dump configuration */ + for (i = 0; i < ARRAY_SIZE(zerogravitas_golden_file); i++) { + ret = write_extended_cmd(di, &zerogravitas_golden_file[i]); + if (ret < 0) + return ret; + } + + /* Read back stuff */ + ret = write_word(di, BQ27441_DATA_BLOCK_CLASS, 0x0052); + if (ret < 0) { + dev_warn(di->dev, "Failed to read back data\n"); + return ret; + } + + usleep_range(1000, 2000); + checksum = read_byte(di, BQ27441_BLOCK_DATA_CHECKSUM); + if (checksum < 0) { + dev_warn(di->dev, "Failed to read back data\n"); + return ret; + } + + /* read all the old values that we want to update */ + + design_capacity = read_word(di, BQ27441_DESIGN_CAPACITY_1); + design_energy = read_word(di, BQ27441_DESIGN_ENERGY_1); + taper_rate = read_word(di, BQ27441_TAPER_RATE_1); + terminate_voltage = read_word(di, BQ27441_TERMINATE_VOLTAGE_1); + + dev_info(di->dev, + "Read current values on device:\n" + " capacity: %d mAh\n" + " energy: %d mW\n" + " taper_rate: %d\n" + " terminate_voltage: %d mV\n" + " itpor flag: %d\n" + " checksum: %x\n", + be16_to_cpu(design_capacity), + be16_to_cpu(design_energy), + be16_to_cpu(taper_rate), + be16_to_cpu(terminate_voltage), + (flags_lsb & BQ27441_FLAGS_ITPOR), + checksum); + + ret = write_extended_byteorword(di, 0x40, 3, &version, true); + if (ret < 0) { + dev_warn(di->dev, "Unable to write BQ27441_DM_CODE, ret %d\n", ret); + return ret; + } + + usleep_range(1000, 2000); + ret = control_read(di, BQ27441_DM_CODE); + if (ret < 0) { + dev_warn(di->dev, "Unable to read back BQ27441_DM_CODE, ret %d\n", ret); + return ret; + } + dev_info(di->dev, "BQ27441_DM_CODE read back %04X\n", ret); + + ret = config_mode_stop(di); + return ret; +} + +int bq27441_init(struct bq27xxx_device_info *di) +{ + int ret; + bool itpor; + u8 dmcode; + + mutex_lock(&di->lock); + + ret = check_fw_version(di); + if (ret < 0) + goto done; + +#ifdef CONFIG_DEBUG_FS + if (bq27441_create_debugfs(di) < 0) + dev_warn(di->dev, "Failed to create debugfs\n"); +#endif /* CONFIG_DEBUG_FS */ + + ret = read_byte(di, BQ27441_FLAGS); + if (ret < 0) { + dev_warn(di->dev, "Unable to read BQ27441_FLAGS, ret %d\n", ret); + goto done; + } + itpor = (ret & BQ27441_FLAGS_ITPOR); + dev_info(di->dev, "ITPOR bit: %c", itpor ? '1' : '0'); + + ret = control_read(di, BQ27441_DM_CODE); + if (ret < 0) { + dev_warn(di->dev, "Unable to read BQ27441_DM_CODE, ret %d\n", ret); + goto done; + } + dmcode = (ret & 0xff); + dev_info(di->dev, "Configuration version %u\n", dmcode); + + if (dmcode == CONFIG_VERSION_FACTORY_RESET) + goto done; + else if (dmcode != CONFIG_VERSION || itpor) + ret = configure(di); + +done: + mutex_unlock(&di->lock); + + if (ret < 0) + dev_warn(di->dev, "Failed to initialize\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(bq27441_init); + +void bq27441_exit(struct bq27xxx_device_info *di) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(di->dfs_dir); +#endif /* CONFIG_DEBUG_FS */ +} +EXPORT_SYMBOL_GPL(bq27441_exit); + +MODULE_AUTHOR("Lars "); +MODULE_DESCRIPTION("BQ27441 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27441/bq27441_battery.h b/drivers/power/bq27441/bq27441_battery.h new file mode 100644 index 000000000000..9841bceb1745 --- /dev/null +++ b/drivers/power/bq27441/bq27441_battery.h @@ -0,0 +1,7 @@ +#ifndef _BQ27441_BATTERY_H +#define _BQ27441_BATTERY_H + +int bq27441_init(struct bq27xxx_device_info *di); +int bq27441_exit(struct bq27xxx_device_info *di); + +#endif /* _BQ27441_BATTERY_H */ diff --git a/drivers/power/bq27441/bq27xxx_battery.c b/drivers/power/bq27441/bq27xxx_battery.c new file mode 100644 index 000000000000..6575361f5c23 --- /dev/null +++ b/drivers/power/bq27441/bq27xxx_battery.c @@ -0,0 +1,1134 @@ +/* + * BQ27xxx battery driver + * + * Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Copyright (C) 2010-2011 Lars-Peter Clausen + * Copyright (C) 2011 Pali Rohár + * + * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Datasheets: + * http://www.ti.com/product/bq27000 + * http://www.ti.com/product/bq27200 + * http://www.ti.com/product/bq27010 + * http://www.ti.com/product/bq27210 + * http://www.ti.com/product/bq27500 + * http://www.ti.com/product/bq27510-g3 + * http://www.ti.com/product/bq27520-g4 + * http://www.ti.com/product/bq27530-g1 + * http://www.ti.com/product/bq27531-g1 + * http://www.ti.com/product/bq27541-g1 + * http://www.ti.com/product/bq27542-g1 + * http://www.ti.com/product/bq27546-g1 + * http://www.ti.com/product/bq27742-g1 + * http://www.ti.com/product/bq27545-g1 + * http://www.ti.com/product/bq27421-g1 + * http://www.ti.com/product/bq27425-g1 + * http://www.ti.com/product/bq27411-g1 + * http://www.ti.com/product/bq27621-g1 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bq27xxx_battery.h" +#include "bq27441_battery.h" + +#define DRIVER_VERSION "1.2.0" + +#define BQ27XXX_MANUFACTURER "Texas Instruments" + +/* BQ27XXX Flags */ +#define BQ27XXX_FLAG_DSC BIT(0) +#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ +#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27XXX_FLAG_FC BIT(9) +#define BQ27XXX_FLAG_OTD BIT(14) +#define BQ27XXX_FLAG_OTC BIT(15) +#define BQ27XXX_FLAG_UT BIT(14) +#define BQ27XXX_FLAG_OT BIT(15) + +/* BQ27000 has different layout for Flags register */ +#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */ +#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */ +#define BQ27000_FLAG_FC BIT(5) +#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ + +#define BQ27XXX_RS (20) /* Resistor sense mOhm */ +#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */ +#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */ + +#define INVALID_REG_ADDR 0xff + + +/* + * bq27xxx_reg_index - Register names + * + * These are indexes into a device's register mapping array. + */ +enum bq27xxx_reg_index { + BQ27XXX_REG_CTRL = 0, /* Control */ + BQ27XXX_REG_TEMP, /* Temperature */ + BQ27XXX_REG_INT_TEMP, /* Internal Temperature */ + BQ27XXX_REG_VOLT, /* Voltage */ + BQ27XXX_REG_AI, /* Average Current */ + BQ27XXX_REG_FLAGS, /* Flags */ + BQ27XXX_REG_TTE, /* Time-to-Empty */ + BQ27XXX_REG_TTF, /* Time-to-Full */ + BQ27XXX_REG_TTES, /* Time-to-Empty Standby */ + BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */ + BQ27XXX_REG_NAC, /* Nominal Available Capacity */ + BQ27XXX_REG_FCC, /* Full Charge Capacity */ + BQ27XXX_REG_CYCT, /* Cycle Count */ + BQ27XXX_REG_AE, /* Available Energy */ + BQ27XXX_REG_SOC, /* State-of-Charge */ + BQ27XXX_REG_DCAP, /* Design Capacity */ + BQ27XXX_REG_AP, /* Average Power */ +}; + +/* Register mappings */ +static u8 bq27000_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + INVALID_REG_ADDR, /* INT TEMP - NA*/ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + 0x18, /* TTF */ + 0x1c, /* TTES */ + 0x26, /* TTECP */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + 0x22, /* AE */ + 0x0b, /* SOC(RSOC) */ + 0x76, /* DCAP(ILMD) */ + 0x24, /* AP */ +}; + +static u8 bq27010_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + INVALID_REG_ADDR, /* INT TEMP - NA*/ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + 0x18, /* TTF */ + 0x1c, /* TTES */ + 0x26, /* TTECP */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x0b, /* SOC(RSOC) */ + 0x76, /* DCAP(ILMD) */ + INVALID_REG_ADDR, /* AP - NA */ +}; + +static u8 bq27500_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + 0x1a, /* TTES */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + 0x3c, /* DCAP(ILMD) */ + INVALID_REG_ADDR, /* AP - NA */ +}; + +static u8 bq27530_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x32, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + INVALID_REG_ADDR, /* DCAP - NA */ + 0x24, /* AP */ +}; + +static u8 bq27541_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + 0x3c, /* DCAP */ + 0x24, /* AP */ +}; + +static u8 bq27545_regs[] = { + 0x00, /* CONTROL */ + 0x06, /* TEMP */ + 0x28, /* INT TEMP */ + 0x08, /* VOLT */ + 0x14, /* AVG CURR */ + 0x0a, /* FLAGS */ + 0x16, /* TTE */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x0c, /* NAC */ + 0x12, /* LMD(FCC) */ + 0x2a, /* CYCT */ + INVALID_REG_ADDR, /* AE - NA */ + 0x2c, /* SOC(RSOC) */ + INVALID_REG_ADDR, /* DCAP - NA */ + 0x24, /* AP */ +}; + +static u8 bq27421_regs[] = { + 0x00, /* CONTROL */ + 0x02, /* TEMP */ + 0x1e, /* INT TEMP */ + 0x04, /* VOLT */ + 0x10, /* AVG CURR */ + 0x06, /* FLAGS */ + INVALID_REG_ADDR, /* TTE - NA */ + INVALID_REG_ADDR, /* TTF - NA */ + INVALID_REG_ADDR, /* TTES - NA */ + INVALID_REG_ADDR, /* TTECP - NA */ + 0x08, /* NAC */ + 0x0e, /* FCC */ + INVALID_REG_ADDR, /* CYCT - NA */ + INVALID_REG_ADDR, /* AE - NA */ + 0x1c, /* SOC */ + 0x3c, /* DCAP */ + 0x18, /* AP */ +}; + +static u8 *bq27xxx_regs[] = { + [BQ27000] = bq27000_regs, + [BQ27010] = bq27010_regs, + [BQ27500] = bq27500_regs, + [BQ27530] = bq27530_regs, + [BQ27541] = bq27541_regs, + [BQ27545] = bq27545_regs, + [BQ27421] = bq27421_regs, +}; + +static enum power_supply_property bq27000_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27010_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27500_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27530_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27541_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27545_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property bq27421_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +#define BQ27XXX_PROP(_id, _prop) \ + [_id] = { \ + .props = _prop, \ + .size = ARRAY_SIZE(_prop), \ + } + +static struct { + enum power_supply_property *props; + size_t size; +} bq27xxx_battery_props[] = { + BQ27XXX_PROP(BQ27000, bq27000_battery_props), + BQ27XXX_PROP(BQ27010, bq27010_battery_props), + BQ27XXX_PROP(BQ27500, bq27500_battery_props), + BQ27XXX_PROP(BQ27530, bq27530_battery_props), + BQ27XXX_PROP(BQ27541, bq27541_battery_props), + BQ27XXX_PROP(BQ27545, bq27545_battery_props), + BQ27XXX_PROP(BQ27421, bq27421_battery_props), +}; + +static unsigned int poll_interval = 360; +module_param(poll_interval, uint, 0644); +MODULE_PARM_DESC(poll_interval, + "battery poll interval in seconds - 0 disables polling"); + +/* + * Common code for BQ27xxx devices + */ + +static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index, + bool single) +{ + /* Reports EINVAL for invalid/missing registers */ + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; + + return di->bus.read(di, di->regs[reg_index], single); +} + +/* + * Return the battery State-of-Charge + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) +{ + int soc; + + if (di->chip == BQ27000 || di->chip == BQ27010) + soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true); + else + soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false); + + if (soc < 0) + dev_dbg(di->dev, "error reading State-of-Charge\n"); + + return soc; +} + +/* + * Return a battery charge value in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg) +{ + int charge; + + charge = bq27xxx_read(di, reg, false); + if (charge < 0) { + dev_dbg(di->dev, "error reading charge register %02x: %d\n", + reg, charge); + return charge; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + charge *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + else + charge *= 1000; + + return charge; +} + +/* + * Return the battery Nominal available capacity in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di) +{ + int flags; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); + if (flags >= 0 && (flags & BQ27000_FLAG_CI)) + return -ENODATA; + } + + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC); +} + +/* + * Return the battery Full Charge Capacity in µAh + * Or < 0 if something fails. + */ +static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di) +{ + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC); +} + +/* + * Return the Design Capacity in µAh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di) +{ + int dcap; + + if (di->chip == BQ27000 || di->chip == BQ27010) + dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, true); + else + dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false); + + if (dcap < 0) { + dev_dbg(di->dev, "error reading initial last measured discharge\n"); + return dcap; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + dcap = (dcap << 8) * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + else + dcap *= 1000; + + return dcap; +} + +/* + * Return the battery Available energy in µWh + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di) +{ + int ae; + + ae = bq27xxx_read(di, BQ27XXX_REG_AE, false); + if (ae < 0) { + dev_dbg(di->dev, "error reading available energy\n"); + return ae; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + ae *= BQ27XXX_POWER_CONSTANT / BQ27XXX_RS; + else + ae *= 1000; + + return ae; +} + +/* + * Return the battery temperature in tenths of degree Kelvin + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di) +{ + int temp; + + temp = bq27xxx_read(di, BQ27XXX_REG_TEMP, false); + if (temp < 0) { + dev_err(di->dev, "error reading temperature\n"); + return temp; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + temp = 5 * temp / 2; + + return temp; +} + +/* + * Return the battery Cycle count total + * Or < 0 if something fails. + */ +static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di) +{ + int cyct; + + cyct = bq27xxx_read(di, BQ27XXX_REG_CYCT, false); + if (cyct < 0) + dev_err(di->dev, "error reading cycle count total\n"); + + return cyct; +} + +/* + * Read a time register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg) +{ + int tval; + + tval = bq27xxx_read(di, reg, false); + if (tval < 0) { + dev_dbg(di->dev, "error reading time register %02x: %d\n", + reg, tval); + return tval; + } + + if (tval == 65535) + return -ENODATA; + + return tval * 60; +} + +/* + * Read an average power register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di) +{ + int tval; + + tval = bq27xxx_read(di, BQ27XXX_REG_AP, false); + if (tval < 0) { + dev_err(di->dev, "error reading average power register %02x: %d\n", + BQ27XXX_REG_AP, tval); + return tval; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) + return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS; + else + return tval; +} + +/* + * Returns true if a battery over temperature condition is detected + */ +static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27500 || di->chip == BQ27541 || di->chip == BQ27545) + return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_OT; + + return false; +} + +/* + * Returns true if a battery under temperature condition is detected + */ +static bool bq27xxx_battery_undertemp(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27530 || di->chip == BQ27421) + return flags & BQ27XXX_FLAG_UT; + + return false; +} + +/* + * Returns true if a low state of charge condition is detected + */ +static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) +{ + if (di->chip == BQ27000 || di->chip == BQ27010) + return flags & (BQ27000_FLAG_EDV1 | BQ27000_FLAG_EDVF); + else + return flags & (BQ27XXX_FLAG_SOC1 | BQ27XXX_FLAG_SOCF); +} + +/* + * Read flag register. + * Return < 0 if something fails. + */ +static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) +{ + int flags; + + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (flags < 0) { + dev_err(di->dev, "error reading flag register:%d\n", flags); + return flags; + } + + /* Unlikely but important to return first */ + if (unlikely(bq27xxx_battery_overtemp(di, flags))) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (unlikely(bq27xxx_battery_undertemp(di, flags))) + return POWER_SUPPLY_HEALTH_COLD; + if (unlikely(bq27xxx_battery_dead(di, flags))) + return POWER_SUPPLY_HEALTH_DEAD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +void bq27xxx_battery_update(struct bq27xxx_device_info *di) +{ + struct bq27xxx_reg_cache cache = {0, }; + bool has_ci_flag = di->chip == BQ27000 || di->chip == BQ27010; + bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010; + + cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); + if ((cache.flags & 0xff) == 0xff) + cache.flags = -1; /* read error */ + if (cache.flags >= 0) { + cache.temperature = bq27xxx_battery_read_temperature(di); + if (has_ci_flag && (cache.flags & BQ27000_FLAG_CI)) { + dev_info_once(di->dev, "battery is not calibrated! ignoring capacity values\n"); + cache.capacity = -ENODATA; + cache.energy = -ENODATA; + cache.time_to_empty = -ENODATA; + cache.time_to_empty_avg = -ENODATA; + cache.time_to_full = -ENODATA; + cache.charge_full = -ENODATA; + cache.health = -ENODATA; + } else { + if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR) + cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE); + if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR) + cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP); + if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR) + cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF); + cache.charge_full = bq27xxx_battery_read_fcc(di); + cache.capacity = bq27xxx_battery_read_soc(di); + if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR) + cache.energy = bq27xxx_battery_read_energy(di); + cache.health = bq27xxx_battery_read_health(di); + } + if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR) + cache.cycle_count = bq27xxx_battery_read_cyct(di); + if (di->regs[BQ27XXX_REG_AP] != INVALID_REG_ADDR) + cache.power_avg = bq27xxx_battery_read_pwr_avg(di); + + /* We only have to read charge design full once */ + if (di->charge_design_full <= 0) + di->charge_design_full = bq27xxx_battery_read_dcap(di); + } + + if (di->cache.capacity != cache.capacity) + power_supply_changed(di->bat); + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) + di->cache = cache; + + di->last_update = jiffies; +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_update); + +static void bq27xxx_battery_poll(struct work_struct *work) +{ + struct bq27xxx_device_info *di = + container_of(work, struct bq27xxx_device_info, + work.work); + + bq27xxx_battery_update(di); + + if (poll_interval > 0) { + /* The timer does not have to be accurate. */ + set_timer_slack(&di->work.timer, poll_interval * HZ / 4); + schedule_delayed_work(&di->work, poll_interval * HZ); + } +} + +/* + * Return the battery average current in µA + * Note that current can be negative signed as well + * Or 0 if something fails. + */ +static int bq27xxx_battery_current(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int curr; + int flags; + + curr = bq27xxx_read(di, BQ27XXX_REG_AI, false); + if (curr < 0) { + dev_err(di->dev, "error reading current\n"); + return curr; + } + + if (di->chip == BQ27000 || di->chip == BQ27010) { + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (flags & BQ27000_FLAG_CHGS) { + dev_dbg(di->dev, "negative current!\n"); + curr = -curr; + } + + val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS; + } else { + /* Other gauges return signed value */ + val->intval = (int)((s16)curr) * 1000; + } + + return 0; +} + +static int bq27xxx_battery_status(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int status; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (di->cache.flags & BQ27000_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27000_FLAG_CHGS) + status = POWER_SUPPLY_STATUS_CHARGING; + else if (power_supply_am_i_supplied(di->bat)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (di->cache.flags & BQ27XXX_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (di->cache.flags & BQ27XXX_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } + + val->intval = status; + + return 0; +} + +static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int level; + + if (di->chip == BQ27000 || di->chip == BQ27010) { + if (di->cache.flags & BQ27000_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27000_FLAG_EDV1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27000_FLAG_EDVF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } else { + if (di->cache.flags & BQ27XXX_FLAG_FC) + level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (di->cache.flags & BQ27XXX_FLAG_SOC1) + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (di->cache.flags & BQ27XXX_FLAG_SOCF) + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + } + + val->intval = level; + + return 0; +} + +/* + * Return the battery Voltage in millivolts + * Or < 0 if something fails. + */ +static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, + union power_supply_propval *val) +{ + int volt; + + volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false); + if (volt < 0) { + dev_err(di->dev, "error reading voltage\n"); + return volt; + } + + val->intval = volt * 1000; + + return 0; +} + +static int bq27xxx_simple_value(int value, + union power_supply_propval *val) +{ + if (value < 0) + return value; + + val->intval = value; + + return 0; +} + +static int bq27xxx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + mutex_lock(&di->lock); + if (time_is_before_jiffies(di->last_update + 5 * HZ)) { + cancel_delayed_work_sync(&di->work); + bq27xxx_battery_poll(&di->work.work); + } + mutex_unlock(&di->lock); + + if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq27xxx_battery_status(di, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq27xxx_battery_voltage(di, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->cache.flags < 0 ? 0 : 1; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq27xxx_battery_current(di, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq27xxx_simple_value(di->cache.capacity, val); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = bq27xxx_battery_capacity_level(di, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = bq27xxx_simple_value(di->cache.temperature, val); + if (ret == 0) + val->intval -= 2731; /* convert decidegree k to c */ + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_empty, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = bq27xxx_simple_value(di->cache.time_to_full, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = bq27xxx_simple_value(di->cache.charge_full, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = bq27xxx_simple_value(di->charge_design_full, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = bq27xxx_simple_value(di->cache.cycle_count, val); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = bq27xxx_simple_value(di->cache.energy, val); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + ret = bq27xxx_simple_value(di->cache.power_avg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq27xxx_simple_value(di->cache.health, val); + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ27XXX_MANUFACTURER; + break; + default: + return -EINVAL; + } + + return ret; +} + +static void bq27xxx_external_power_changed(struct power_supply *psy) +{ + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + + cancel_delayed_work_sync(&di->work); + schedule_delayed_work(&di->work, 0); +} + +int bq27xxx_battery_setup(struct bq27xxx_device_info *di) +{ + struct power_supply_desc *psy_desc; + struct power_supply_config psy_cfg = { .drv_data = di, }; + int volt; + + INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); + mutex_init(&di->lock); + di->regs = bq27xxx_regs[di->chip]; + + psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = di->name; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; + psy_desc->properties = bq27xxx_battery_props[di->chip].props; + psy_desc->num_properties = bq27xxx_battery_props[di->chip].size; + psy_desc->get_property = bq27xxx_battery_get_property; + psy_desc->external_power_changed = bq27xxx_external_power_changed; + + di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + dev_err(di->dev, "Failed to register battery\n"); + return PTR_ERR(di->bat); + } + + dev_info(di->dev, "Support ver. %s enabled\n", DRIVER_VERSION); + + volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false); + if (volt < 0) + dev_err(di->dev, "Error reading voltage\n"); + else + dev_info(di->dev, "Measured voltage: %dmV\n", volt); + + psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + bq27441_init(di); + + bq27xxx_battery_update(di); + + return 0; +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_setup); + +void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) +{ + /* + * power_supply_unregister call bq27xxx_battery_get_property which + * call bq27xxx_battery_poll. + * Make sure that bq27xxx_battery_poll will not call + * schedule_delayed_work again after unregister (which cause OOPS). + */ + poll_interval = 0; + + cancel_delayed_work_sync(&di->work); + + power_supply_unregister(di->bat); + + mutex_destroy(&di->lock); +} +EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown); + +static int bq27xxx_battery_platform_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct device *dev = di->dev; + struct bq27xxx_platform_data *pdata = dev->platform_data; + unsigned int timeout = 3; + int upper, lower; + int temp; + + if (!single) { + /* Make sure the value has not changed in between reading the + * lower and the upper part */ + upper = pdata->read(dev, reg + 1); + do { + temp = upper; + if (upper < 0) + return upper; + + lower = pdata->read(dev, reg); + if (lower < 0) + return lower; + + upper = pdata->read(dev, reg + 1); + } while (temp != upper && --timeout); + + if (timeout == 0) + return -EIO; + + return (upper << 8) | lower; + } + + return pdata->read(dev, reg); +} + +static int bq27xxx_battery_platform_probe(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di; + struct bq27xxx_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "no platform_data supplied\n"); + return -EINVAL; + } + + if (!pdata->read) { + dev_err(&pdev->dev, "no hdq read callback supplied\n"); + return -EINVAL; + } + + if (!pdata->chip) { + dev_err(&pdev->dev, "no device supplied\n"); + return -EINVAL; + } + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->chip = pdata->chip; + di->name = pdata->name ?: dev_name(&pdev->dev); + di->bus.read = bq27xxx_battery_platform_read; + + return bq27xxx_battery_setup(di); +} + +static int bq27xxx_battery_platform_remove(struct platform_device *pdev) +{ + struct bq27xxx_device_info *di = platform_get_drvdata(pdev); + + bq27441_exit(di); + + bq27xxx_battery_teardown(di); + + return 0; +} + +static const struct platform_device_id bq27xxx_battery_platform_id_table[] = { + { "bq27000-battery", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, bq27xxx_battery_platform_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id bq27xxx_battery_platform_of_match_table[] = { + { .compatible = "ti,bq27000" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bq27xxx_battery_platform_of_match_table); +#endif + +static struct platform_driver bq27xxx_battery_platform_driver = { + .probe = bq27xxx_battery_platform_probe, + .remove = bq27xxx_battery_platform_remove, + .driver = { + .name = "bq27000-battery", + .of_match_table = of_match_ptr(bq27xxx_battery_platform_of_match_table), + }, + .id_table = bq27xxx_battery_platform_id_table, +}; +module_platform_driver(bq27xxx_battery_platform_driver); + +MODULE_ALIAS("platform:bq27000-battery"); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27441/bq27xxx_battery.h b/drivers/power/bq27441/bq27xxx_battery.h new file mode 100644 index 000000000000..c41747c59507 --- /dev/null +++ b/drivers/power/bq27441/bq27xxx_battery.h @@ -0,0 +1,73 @@ +#ifndef __LINUX_BQ27X00_BATTERY_H__ +#define __LINUX_BQ27X00_BATTERY_H__ + +enum bq27xxx_chip { + BQ27000 = 1, /* bq27000, bq27200 */ + BQ27010, /* bq27010, bq27210 */ + BQ27500, /* bq27500, bq27510, bq27520 */ + BQ27530, /* bq27530, bq27531 */ + BQ27541, /* bq27541, bq27542, bq27546, bq27742 */ + BQ27545, /* bq27545 */ + BQ27421, /* bq27421, bq27425, bq27441, bq27621 */ +}; + +/** + * struct bq27xxx_plaform_data - Platform data for bq27xxx devices + * @name: Name of the battery. + * @chip: Chip class number of this device. + * @read: HDQ read callback. + * This function should provide access to the HDQ bus the battery is + * connected to. + * The first parameter is a pointer to the battery device, the second the + * register to be read. The return value should either be the content of + * the passed register or an error value. + */ +struct bq27xxx_platform_data { + const char *name; + enum bq27xxx_chip chip; + int (*read)(struct device *dev, unsigned int); +}; + +struct bq27xxx_device_info; +struct bq27xxx_access_methods { + int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single); + int (*write)(struct bq27xxx_device_info *di, u8 reg, const u8 *data, size_t len); +}; + +struct bq27xxx_reg_cache { + int temperature; + int time_to_empty; + int time_to_empty_avg; + int time_to_full; + int charge_full; + int cycle_count; + int capacity; + int energy; + int flags; + int power_avg; + int health; +}; + +struct dentry; + +struct bq27xxx_device_info { + struct device *dev; + enum bq27xxx_chip chip; + const char *name; + struct bq27xxx_access_methods bus; + struct bq27xxx_reg_cache cache; + int charge_design_full; + unsigned long last_update; + struct delayed_work work; + struct power_supply *bat; + struct mutex lock; + u8 *regs; + struct dentry *dfs_dir; + struct dentry *dfs_polarity_file; +}; + +void bq27xxx_battery_update(struct bq27xxx_device_info *di); +int bq27xxx_battery_setup(struct bq27xxx_device_info *di); +void bq27xxx_battery_teardown(struct bq27xxx_device_info *di); + +#endif diff --git a/drivers/power/bq27441/bq27xxx_battery_i2c.c b/drivers/power/bq27441/bq27xxx_battery_i2c.c new file mode 100644 index 000000000000..f9729eebdf8d --- /dev/null +++ b/drivers/power/bq27441/bq27xxx_battery_i2c.c @@ -0,0 +1,202 @@ +/* + * SCI Reset driver for Keystone based devices + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include + +#include "bq27xxx_battery.h" + +static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data) +{ + struct bq27xxx_device_info *di = data; + + bq27xxx_battery_update(di); + + return IRQ_HANDLED; +} + +static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, + bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg[2]; + unsigned char data[2]; + int ret; + + 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 = data; + if (single) + msg[1].len = 1; + else + msg[1].len = 2; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) + return ret; + + if (!single) + ret = get_unaligned_le16(data); + else + ret = data[0]; + + return ret; +} + +static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg, + const u8 *data, size_t len) +{ + struct i2c_client *client = to_i2c_client(di->dev); + int ret; + unsigned char *buf; + + if (!client->adapter) + return -ENODEV; + + buf = kzalloc(len + sizeof(reg), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = reg; + memcpy (&buf[1], data, len); + + ret = i2c_master_send(client, buf, len + sizeof(reg)); + + kfree(buf); + + return ret; +} + +static int bq27xxx_battery_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq27xxx_device_info *di; + int ret; + + di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->dev = &client->dev; + di->chip = id->driver_data; + di->name = id->name; + di->bus.read = bq27xxx_battery_i2c_read; + di->bus.write = bq27xxx_battery_i2c_write; + + ret = bq27xxx_battery_setup(di); + if (ret) + return ret; + + /* Schedule a polling after about 1 min */ + schedule_delayed_work(&di->work, 60 * HZ); + + i2c_set_clientdata(client, di); + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, bq27xxx_battery_irq_handler_thread, + IRQF_ONESHOT, + di->name, di); + if (ret) { + dev_err(&client->dev, + "Unable to register IRQ %d error %d\n", + client->irq, ret); + return ret; + } + } + + return 0; +} + +static int bq27xxx_battery_i2c_remove(struct i2c_client *client) +{ + struct bq27xxx_device_info *di = i2c_get_clientdata(client); + + bq27xxx_battery_teardown(di); + + return 0; +} + +static const struct i2c_device_id bq27xxx_i2c_id_table[] = { + { "bq27200", BQ27000 }, + { "bq27210", BQ27010 }, + { "bq27500", BQ27500 }, + { "bq27510", BQ27500 }, + { "bq27520", BQ27500 }, + { "bq27530", BQ27530 }, + { "bq27531", BQ27530 }, + { "bq27541", BQ27541 }, + { "bq27542", BQ27541 }, + { "bq27546", BQ27541 }, + { "bq27742", BQ27541 }, + { "bq27545", BQ27545 }, + { "bq27421", BQ27421 }, + { "bq27425", BQ27421 }, + { "bq27441", BQ27421 }, + { "bq27621", BQ27421 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { + { .compatible = "ti,bq27200" }, + { .compatible = "ti,bq27210" }, + { .compatible = "ti,bq27500" }, + { .compatible = "ti,bq27510" }, + { .compatible = "ti,bq27520" }, + { .compatible = "ti,bq27530" }, + { .compatible = "ti,bq27531" }, + { .compatible = "ti,bq27541" }, + { .compatible = "ti,bq27542" }, + { .compatible = "ti,bq27546" }, + { .compatible = "ti,bq27742" }, + { .compatible = "ti,bq27545" }, + { .compatible = "ti,bq27421" }, + { .compatible = "ti,bq27425" }, + { .compatible = "ti,bq27441" }, + { .compatible = "ti,bq27621" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); +#endif + +static struct i2c_driver bq27xxx_battery_i2c_driver = { + .driver = { + .name = "bq27xxx-battery", + .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table), + }, + .probe = bq27xxx_battery_i2c_probe, + .remove = bq27xxx_battery_i2c_remove, + .id_table = bq27xxx_i2c_id_table, +}; +module_i2c_driver(bq27xxx_battery_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); +MODULE_LICENSE("GPL");