devicetree battery support and client bq27xxx_battery
Subsystems (4)
v9 cover devicetree battery support and client bq27xxx_battery
Overview:
* new devicetree battery node specifies static battery data
* fuel gauge and charger nodes shall use monitored-battery=<&battery_node>
* new power_supply_get_battery_info() reads battery data from devicetree
* new struct power_supply_battery_info provides battery data to drivers
* drivers surface battery data in sysfs via related power_supply_prop_* fields
* bq27xxx driver calls the above and writes battery data to RAM/NVM for params essential to correct operation: energy-full-design-microwatt-hours, charge-full-design-microamp-hours, voltage-min-design-microvolt
Changes in v9:
bq27xxx_battery:
* fix set_cfgupdate() * support bq27500, 545, 421; defer others * bq27441, 621 use enum BQ27425 instead of BQ27421 * drop print_dm_blocks() patch * minor polishing
Documentation/devicetree/.../battery.txt:
* describe rationale for enum power_supply_property names
Changes in v8:
bq27xxx_battery:
* wait on flag after set_cfgupdate & soft_reset * drop print_config(), report status in update_dm_block() * clarify error messages * cleanup from Andrew's feedback; minor polishing
Changes in v7:
bq27xxx_battery:
* support chips where terminate_voltage & design_* live in separate blocks * draft support for 421, 441, 621 chips * new patch to log chip memory fields * report bus I/O errors; return error code in bq27xxx_battery_i2c * verify checksum in read_dm_block() * use set_cfgupdate only if chip provides it, soft_reset on I/O error * block_data_control=0 only in write_dm_block() * note toxic code from TI bqtool in write_dm_block() * lots of functionally neutral polishing
Documentation/devicetree/.../battery.txt:
* mention power_supply_get_battery_info()
Changes in v6:
* Documentation/devictree/... fixes
* bq27xxx_battery: clarify names
* bq27xxx_battery: verify that selected registers are supported
* bq27xxx_battery: allocate NVM buffer on stack
* bq27xxx_battery_i2c: fix return code of bulk_read
Changes in v5:
* incorporate feedback into Documentation/devicetree/.../battery.txt
* use power_supply_prop_* names in devicetree and power_supply_battery_info
* default fields to -EINVAL in power_supply_battery_info
* power_supply_get_battery_info() always looks for "monitored-battery"
* power_supply_get_battery_info() emits a warning if !psy->of_node
* squash patches for power_supply_battery_info
* bq27xxx_battery: check power_supply_battery_info values
* bq27xxx_battery: note missing power_supply_prop_* features
* bq27xxx_battery: new patch for access methods
Changes in v4:
* add "fixed-battery" compatible field to be be more consistant with devicetree
Changes in v3:
* split i2c changes into respective patches
* add documentation for battery information for fuel gauge
* rebased documentation patches on change on the list
* abstracted the battery configuration for the state machine to an generic struct and platform data access function
Changes in v2:
* add documentation for uWh and uAh property units
* change devicetree entries to match new property units
Liam (4):
devicetree: power: Add battery.txt
devicetree: power: bq27xxx: Add monitored-battery documentation
power: power_supply: Add power_supply_battery_info and API
power: bq27xxx_battery: Add power_supply_battery_info support
Matt Ranostay (4):
devicetree: property-units: Add uWh and uAh units
power: bq27xxx_battery: Define access methods to write chip registers
power: bq27xxx_battery: Move BQ27421 chip id to its own group
power: bq27xxx_battery_i2c: Add I2C bulk read/write functions
.../devicetree/bindings/power/supply/battery.txt | 45 ++ .../devicetree/bindings/power/supply/bq27xxx.txt | 11 +- .../devicetree/bindings/property-units.txt | 2 + drivers/power/supply/bq27xxx_battery.c | 506 ++++++++++++++++++++- drivers/power/supply/bq27xxx_battery_i2c.c | 88 +++- drivers/power/supply/power_supply_core.c | 40 ++ include/linux/power/bq27xxx_battery.h | 6 +- include/linux/power_supply.h | 18 + 8 files changed, 707 insertions(+), 9 deletions(-) create mode 100644 Documentation/devicetree/bindings/power/supply/battery.txt
Signature · 1 line
2.9.3
A message that is not in the archive (sent off-list or not yet received): 20170306072539.980-1-liam-RYWXG+zxWwBdeoIcmNTgJF6hYfS7NtTn@public.gmane.org
PATCH v9 1/8 devicetree: power: Add battery.txt
From: Liam Breck <[email protected]>
Documentation of static battery characteristics that can be defined for batteries which cannot self-identify. This information is required by fuel-gauge and charger chips for proper handling of the battery.
--- .../devicetree/bindings/power/supply/battery.txt | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/battery.txt
Documentation/devicetree/bindings/power/supply/battery.txt new file +45 -0
@@ -0,0 +1,45 @@ +Battery Characteristics + +Required Properties: + - compatible: Must be "fixed-battery" + +Optional Properties: + - voltage-min-design-microvolt: drained battery voltage + - energy-full-design-microwatt-hours: battery design energy + - charge-full-design-microamp-hours: battery design capacity + +Because drivers surface properties in sysfs using names derived +from enum power_supply_property, e.g. +/sys/class/power_supply/<device>/charge_full_design, our +battery properties must be named for the corresponding elements in +enum power_supply_property, defined in include/linux/power_supply.h. + +Batteries must be referenced by chargers and/or fuel-gauges +using a phandle. The phandle's property should be named +"monitored-battery". + +Driver code should call power_supply_get_battery_info() to obtain +battery properties via monitored-battery. For details see: + drivers/power/supply/power_supply_core.c + drivers/power/supply/bq27xxx_battery.c + +Example: + + bat: battery { + compatible = "fixed-battery"; + voltage-min-design-microvolt = <3200000>; + energy-full-design-microwatt-hours = <5290000>; + charge-full-design-microamp-hours = <1430000>; + }; + + charger: charger@11 { + .... + monitored-battery = <&bat>; + ... + }; + + fuel_gauge: fuel-gauge@22 { + .... + monitored-battery = <&bat>; + ... + };
Signature · 6 lines
2.9.3 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
PATCH v9 3/8 devicetree: power: bq27xxx: Add monitored-battery documentation
In reply to a message not in the archiveFrom: Liam Breck <[email protected]>
Document monitored-battery = <&battery_node>
--- Documentation/devicetree/bindings/power/supply/bq27xxx.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
Documentation/devicetree/bindings/power/supply/bq27xxx.txt +10 -1
@@ -28,9 +28,18 @@ Required properties: * "ti,bq27621" - BQ27621 - reg: integer, i2c address of the device. +Optional properties: +- monitored-battery: phandle of battery information devicetree node + + See Documentation/devicetree/bindings/power/supply/battery.txt + If either of the referenced battery's *-full-design-*-hours properties are set, + then both must be. + Example: -bq27510g3 { +bq27510g3 : fuel-gauge@55 { compatible = "ti,bq27510g3"; reg = <0x55>; + + monitored-battery = <&bat>; };
Signature · 6 lines
2.9.3 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
PATCH v9 2/8 devicetree: property-units: Add uWh and uAh units
In reply to Liam BreckFrom: Matt Ranostay <[email protected]>
Add entries for microwatt-hours and microamp-hours.
--- Documentation/devicetree/bindings/property-units.txt | 2 ++ 1 file changed, 2 insertions(+)
Documentation/devicetree/bindings/property-units.txt +2 -0
@@ -25,8 +25,10 @@ Distance Electricity ---------------------------------------- -microamp : micro amps +-microamp-hours : micro amp-hours -ohms : Ohms -micro-ohms : micro Ohms +-microwatt-hours: micro Watt-hours -microvolt : micro volts Temperature
Signature · 1 line
2.9.3
PATCH v9 4/8 power: power_supply: Add power_supply_battery_info and API
In reply to Liam BreckFrom: Liam Breck <[email protected]>
power_supply_get_battery_info() reads battery data from devicetree. struct power_supply_battery_info provides battery data to drivers. Drivers may surface battery data in sysfs via corresponding POWER_SUPPLY_PROP_* fields.
--- drivers/power/supply/power_supply_core.c | 40 ++++++++++++++++++++++++++++++++ include/linux/power_supply.h | 18 ++++++++++++++ 2 files changed, 58 insertions(+)
drivers/power/supply/power_supply_core.c +40 -0
@@ -17,6 +17,7 @@#include <linux/device.h> #include <linux/notifier.h> #include <linux/err.h> +#include <linux/of.h> #include <linux/power_supply.h> #include <linux/thermal.h> #include "power_supply.h"
@@ -487,6 +488,45 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,EXPORT_SYMBOL_GPL (devm_power_supply_get_by_phandle);#endif /* CONFIG_OF */ +int power_supply_get_battery_info (struct power_supply *psy, +struct power_supply_battery_info *info) +{ +struct device_node *battery_np; +const char *value; +int err; + + info->energy_full_design_uwh = -EINVAL ; + info->charge_full_design_uah = -EINVAL ; + info->voltage_min_design_uv = -EINVAL ; + +if (!psy->of_node) { +dev_warn (&psy->dev,"%s currently only supports devicetree\n" , + __func__); +return -ENXIO ; + } + + battery_np =of_parse_phandle (psy->of_node,"monitored-battery" ,0 ); +if (!battery_np) +return -ENODEV ; + + err =of_property_read_string (battery_np,"compatible" , &value); +if (err) +return err; + +if (strcmp ("fixed-battery" , value)) +return -ENODEV ; + +of_property_read_u32 (battery_np,"energy-full-design-microwatt-hours" , + &info->energy_full_design_uwh); +of_property_read_u32 (battery_np,"charge-full-design-microamp-hours" , + &info->charge_full_design_uah); +of_property_read_u32 (battery_np,"voltage-min-design-microvolt" , + &info->voltage_min_design_uv); + +return 0 ; +} +EXPORT_SYMBOL_GPL (power_supply_get_battery_info); +int power_supply_get_property (struct power_supply *psy,enum power_supply_property psp,union power_supply_propval *val)
include/linux/power_supply.h +18 -0
@@ -288,6 +288,21 @@ struct power_supply_info {int use_for_apm; }; +/* +* This is the recommended struct to manage static battery parameters, +* populated by power_supply_get_battery_info(). Most platform drivers should +* use these for consistency. +* Its field names must correspond to elements in enum power_supply_property. +* The default field value is -EINVAL. +* Power supply class itself doesn't use this. +*/ + +struct power_supply_battery_info { +int energy_full_design_uwh;/* microWatt-hours */ +int charge_full_design_uah;/* microAmp-hours */ +int voltage_min_design_uv;/* microVolts */ +}; +extern struct atomic_notifier_head power_supply_notifier;extern int power_supply_reg_notifier (struct notifier_block *nb);extern void power_supply_unreg_notifier (struct notifier_block *nb);
@@ -306,6 +321,9 @@ static inline struct power_supply * devm_power_supply_get_by_phandle(struct device *dev, const char *property) {return NULL ; }#endif /* CONFIG_OF */ + +extern int power_supply_get_battery_info (struct power_supply *psy, +struct power_supply_battery_info *info);extern void power_supply_changed (struct power_supply *psy);extern int power_supply_am_i_supplied (struct power_supply *psy);extern int power_supply_set_battery_charged (struct power_supply *psy);
Signature · 1 line
2.9.3
PATCH v9 5/8 power: bq27xxx_battery: Define access methods to write chip registers
In reply to Liam BreckFrom: Matt Ranostay <[email protected]>
write(), read_bulk(), write_bulk() support setting chip registers, e.g. with data obtained from power_supply_battery_info.
--- include/linux/power/bq27xxx_battery.h | 3 +++ 1 file changed, 3 insertions(+)
include/linux/power/bq27xxx_battery.h +3 -0
@@ -32,6 +32,9 @@ struct bq27xxx_platform_data {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,int value,bool single); +int (*read_bulk)(struct bq27xxx_device_info *di,u8 reg,u8 *data,int len); +int (*write_bulk)(struct bq27xxx_device_info *di,u8 reg,u8 *data,int len); };struct bq27xxx_reg_cache {
Signature · 1 line
2.9.3
PATCH v9 6/8 power: bq27xxx_battery: Move BQ27421 chip id to its own group
In reply to Liam BreckFrom: Matt Ranostay <[email protected]>
Add BQ27425 id to cover 425, 441, 621 chips. This allows support for 421 by the devicetree code.
--- drivers/power/supply/bq27xxx_battery.c | 23 ++++++++++++++++++++++- drivers/power/supply/bq27xxx_battery_i2c.c | 6 +++--- include/linux/power/bq27xxx_battery.h | 3 ++- 3 files changed, 27 insertions(+), 5 deletions(-)
drivers/power/supply/bq27xxx_battery.c +22 -1
@@ -259,6 +259,25 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x18, }, + [BQ27425] = { + [BQ27XXX_REG_CTRL] = 0x00, + [BQ27XXX_REG_TEMP] = 0x02, + [BQ27XXX_REG_INT_TEMP] = 0x1e, + [BQ27XXX_REG_VOLT] = 0x04, + [BQ27XXX_REG_AI] = 0x10, + [BQ27XXX_REG_FLAGS] = 0x06, + [BQ27XXX_REG_TTE] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTF] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTES] = INVALID_REG_ADDR, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = 0x08, + [BQ27XXX_REG_FCC] = 0x0e, + [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR, + [BQ27XXX_REG_AE] = INVALID_REG_ADDR, + [BQ27XXX_REG_SOC] = 0x1c, + [BQ27XXX_REG_DCAP] = 0x3c, + [BQ27XXX_REG_AP] = 0x18, + }, };static enum power_supply_property bq27000_battery_props[] = {
@@ -427,6 +446,7 @@ static struct {BQ27XXX_PROP (BQ27541 , bq27541_battery_props),BQ27XXX_PROP (BQ27545 , bq27545_battery_props),BQ27XXX_PROP (BQ27421 , bq27421_battery_props), + BQ27XXX_PROP(BQ27425, bq27421_battery_props), };static DEFINE_MUTEX (bq27xxx_list_lock);
@@ -682,6 +702,7 @@ static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags)return flags & (BQ27XXX_FLAG_OTC |BQ27XXX_FLAG_OTD );case BQ27530 :case BQ27421 : +case BQ27425 :return flags &BQ27XXX_FLAG_OT ;default :return false;
@@ -693,7 +714,7 @@ static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags)*/ static bool bq27xxx_battery_undertemp (struct bq27xxx_device_info *di,u16 flags) { -if (di->chip ==BQ27530 || di->chip ==BQ27421 ) +if (di->chip ==BQ27530 || di->chip ==BQ27421 || di->chip ==BQ27425 )return flags &BQ27XXX_FLAG_UT ;return false;
drivers/power/supply/bq27xxx_battery_i2c.c +3 -3
@@ -159,9 +159,9 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = { { "bq27742", BQ27541 }, { "bq27545", BQ27545 }, { "bq27421", BQ27421 }, - { "bq27425", BQ27421 }, - { "bq27441", BQ27421 }, - { "bq27621", BQ27421 }, + { "bq27425", BQ27425 }, + { "bq27441", BQ27425 }, + { "bq27621", BQ27425 }, {}, };MODULE_DEVICE_TABLE (i2c, bq27xxx_i2c_id_table);
include/linux/power/bq27xxx_battery.h +2 -1
@@ -9,7 +9,8 @@ enum bq27xxx_chip {BQ27530 ,/* bq27530, bq27531 */ BQ27541 ,/* bq27541, bq27542, bq27546, bq27742 */ BQ27545 ,/* bq27545 */ - BQ27421, /* bq27421, bq27425, bq27441, bq27621 */ +BQ27421 ,/* bq27421 */ + BQ27425, /* bq27425, bq27441, bq27621 */ };/**
Signature · 1 line
2.9.3
PATCH v9 7/8 power: bq27xxx_battery: Add power_supply_battery_info support
In reply to Liam BreckFrom: Liam Breck <[email protected]>
Previously there was no way to configure chip registers in the event that the defaults didn't match the battery in question.
BQ27xxx driver now calls power_supply_get_battery_info, checks the inputs, and writes battery data to chip RAM or non-volatile memory.
--- drivers/power/supply/bq27xxx_battery.c | 483 ++++++++++++++++++++++++++++++++- 1 file changed, 481 insertions(+), 2 deletions(-)
drivers/power/supply/bq27xxx_battery.c +481 -2
@@ -51,7 +51,7 @@#include <linux/power/bq27xxx_battery.h> -#define DRIVER_VERSION "1.2.0" +#define DRIVER_VERSION "1.3.0"#define BQ27XXX_MANUFACTURER "Texas Instruments"
@@ -59,6 +59,7 @@#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_CFGUP BIT(4)#define BQ27XXX_FLAG_FC BIT(9)#define BQ27XXX_FLAG_OTD BIT(14)#define BQ27XXX_FLAG_OTC BIT(15)
@@ -72,6 +73,11 @@#define BQ27000_FLAG_FC BIT(5)#define BQ27000_FLAG_CHGS BIT(7)/* Charge state flag */ +/* control register params */ +#define BQ27XXX_SEALED 0x20 +#define BQ27XXX_SET_CFGUPDATE 0x13 +#define BQ27XXX_SOFT_RESET 0x42 +#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 */
@@ -102,6 +108,11 @@ enum bq27xxx_reg_index {BQ27XXX_REG_SOC ,/* State-of-Charge */ BQ27XXX_REG_DCAP ,/* Design Capacity */ BQ27XXX_REG_AP ,/* Average Power */ +BQ27XXX_DM_CTRL ,/* BlockDataControl() */ +BQ27XXX_DM_CLASS ,/* DataClass() */ +BQ27XXX_DM_BLOCK ,/* DataBlock() */ +BQ27XXX_DM_DATA ,/* BlockData() */ +BQ27XXX_DM_CKSUM ,/* BlockDataChecksum() */ BQ27XXX_REG_MAX, /* sentinel */ };
@@ -125,6 +136,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x0b, [BQ27XXX_REG_DCAP] = 0x76, [BQ27XXX_REG_AP] = 0x24, + [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR, + [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR, + [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, + [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, + [BQ27XXX_DM_CKSUM ] =INVALID_REG_ADDR , }, [BQ27010] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -144,6 +160,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x0b, [BQ27XXX_REG_DCAP] = 0x76, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR, + [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR, + [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, + [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, + [BQ27XXX_DM_CKSUM ] =INVALID_REG_ADDR , }, [BQ27500] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -163,6 +184,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27510] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -182,6 +208,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x20, [BQ27XXX_REG_DCAP] = 0x2e, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27530] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -201,6 +232,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, [BQ27XXX_REG_AP] = 0x24, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27541] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -220,6 +256,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27545] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -239,6 +280,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, [BQ27XXX_REG_AP] = 0x24, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27421] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -258,6 +304,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x1c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x18, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM ] =0x60 , }, [BQ27425] = { [BQ27XXX_REG_CTRL ] =0x00 ,
@@ -277,6 +328,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x1c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x18, + [BQ27XXX_DM_CTRL] = 0x61, + [BQ27XXX_DM_CLASS] = 0x3e, + [BQ27XXX_DM_BLOCK] = 0x3f, + [BQ27XXX_DM_DATA] = 0x40, + [BQ27XXX_DM_CKSUM] = 0x60, }, };
@@ -452,6 +508,106 @@ static struct {static DEFINE_MUTEX (bq27xxx_list_lock);static LIST_HEAD (bq27xxx_battery_devices); +#define BQ27XXX_DM_SZ 32 + +#define BQ27XXX_MSLEEP (i) usleep_range((i)*1000, (i)*1000+500) + +struct bq27xxx_dm_reg { +u8 subclass_id; +u8 offset; +u8 bytes; +u16 min, max; +}; + +struct bq27xxx_dm_buf { +u8 class; +u8 block; +u8 a[BQ27XXX_DM_SZ ]; +bool full, updt; +}; + +#define BQ27XXX_DM_BUF (di, i) { \ + .class = bq27xxx_dm_regs[(di)->chip][i].subclass_id, \ + .block = bq27xxx_dm_regs[(di)->chip][i].offset / BQ27XXX_DM_SZ, \ +} + +static inline u16 *bq27xxx_dm_reg_ptr (struct bq27xxx_dm_buf *buf, +struct bq27xxx_dm_reg *reg) +{ +if (buf->class == reg->subclass_id + && buf->block == reg->offset /BQ27XXX_DM_SZ ) +return (u16 *) (buf->a + reg->offset %BQ27XXX_DM_SZ ); + +return NULL ; +} + +enum bq27xxx_dm_reg_id { +BQ27XXX_DM_DESIGN_CAPACITY =0 , +BQ27XXX_DM_DESIGN_ENERGY , +BQ27XXX_DM_TERMINATE_VOLTAGE , +}; + +static const char * bq27xxx_dm_reg_name[] = { + [BQ27XXX_DM_DESIGN_CAPACITY ] ="design-capacity" , + [BQ27XXX_DM_DESIGN_ENERGY ] ="design-energy" , + [BQ27XXX_DM_TERMINATE_VOLTAGE ] ="terminate-voltage" , +}; + +static struct bq27xxx_dm_reg bq27500_dm_regs[] = { + [BQ27XXX_DM_DESIGN_CAPACITY ] = {48 ,10 ,2 ,0 ,65535 }, + [BQ27XXX_DM_DESIGN_ENERGY ] = { },/* missing on chip */ + [BQ27XXX_DM_TERMINATE_VOLTAGE ] = {80 ,48 ,2 ,1000 ,32767 }, +}; + +static struct bq27xxx_dm_reg bq27545_dm_regs[] = { + [BQ27XXX_DM_DESIGN_CAPACITY ] = {48 ,23 ,2 ,0 ,32767 }, + [BQ27XXX_DM_DESIGN_ENERGY ] = {48 ,25 ,2 ,0 ,32767 }, + [BQ27XXX_DM_TERMINATE_VOLTAGE ] = {80 ,67 ,2 ,2800 ,3700 }, +}; + +static struct bq27xxx_dm_reg bq27421_dm_regs[] = { + [BQ27XXX_DM_DESIGN_CAPACITY ] = {82 ,10 ,2 ,0 ,8000 }, + [BQ27XXX_DM_DESIGN_ENERGY ] = {82 ,12 ,2 ,0 ,32767 }, + [BQ27XXX_DM_TERMINATE_VOLTAGE ] = {82 ,16 ,2 ,2500 ,3700 }, +}; + +/* requires di->chip/group distinction +*static struct bq27xxx_dm_reg bq27425_dm_regs[] = { +* [BQ27XXX_DM_DESIGN_CAPACITY] = { 82, 12, 2, 0, 32767 }, +* [BQ27XXX_DM_DESIGN_ENERGY] = { 82, 14, 2, 0, 32767 }, +* [BQ27XXX_DM_TERMINATE_VOLTAGE] = { 82, 18, 2, 2800, 3700 }, +*}; +* +*static struct bq27xxx_dm_reg bq27621_dm_regs[] = { +* [BQ27XXX_DM_DESIGN_CAPACITY] = { 82, 3, 2, 0, 8000 }, +* [BQ27XXX_DM_DESIGN_ENERGY] = { 82, 5, 2, 0, 32767 }, +* [BQ27XXX_DM_TERMINATE_VOLTAGE] = { 82, 9, 2, 2500, 3700 }, +*}; +*/ + +static struct bq27xxx_dm_reg *bq27xxx_dm_regs[] = { + [BQ27500 ] = bq27500_dm_regs, + [BQ27545 ] = bq27545_dm_regs, + [BQ27421 ] = bq27421_dm_regs, +/* requires di->chip/group distinction +* [BQ27425] = bq27425_dm_regs, +* [BQ27441] = bq27421_dm_regs, +* [BQ27621] = bq27621_dm_regs, +*/ +}; + +static u32 bq27xxx_unseal_keys[] = { + [BQ27500 ] =0x04143672 , + [BQ27545 ] =0x04143672 , + [BQ27421 ] =0x80008000 , +/* requires di->chip/group distinction +* [BQ27425] = 0x04143672, +* [BQ27441] = 0x80008000, +* [BQ27621] = 0x80008000, +*/ +}; + +static int poll_interval_param_set (const char *val,const struct kernel_param *kp) {struct bq27xxx_device_info *di;
@@ -496,6 +652,318 @@ static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,return di->bus.read (di, di->regs[reg_index], single); } +static int bq27xxx_battery_set_seal_state (struct bq27xxx_device_info *di, +bool state) +{ +int ret; + +if (state) { + ret = di->bus.write (di, di->regs[BQ27XXX_REG_CTRL ],BQ27XXX_SEALED , false); +if (ret <0 ) + goto out; + }else { +u32 key = bq27xxx_unseal_keys[di->chip]; + + ret = di->bus.write (di, di->regs[BQ27XXX_REG_CTRL ], (u16)(key >>16 ), false); +if (ret <0 ) + goto out; + + ret = di->bus.write (di, di->regs[BQ27XXX_REG_CTRL ], (u16 )key, false); +if (ret <0 ) + goto out; + } +return 0 ; + +out: +dev_err (di->dev,"bus error on %s: %d\n" , state ?"seal" :"unseal" , ret); +return ret; +} + +static u8 bq27xxx_battery_checksum_dm_block (struct bq27xxx_dm_buf *buf) +{ +u16 sum =0 ; +int i; + +for (i =0 ; i <BQ27XXX_DM_SZ ; i++) + sum += buf->a[i]; + sum &=0xff ; + +return 0xff - sum; +} + +static int bq27xxx_battery_read_dm_block (struct bq27xxx_device_info *di, +struct bq27xxx_dm_buf *buf) +{ +int ret; + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_CLASS ], buf->class, true); +if (ret <0 ) + goto out; + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_BLOCK ], buf->block, true); +if (ret <0 ) + goto out; + +BQ27XXX_MSLEEP (1 ); + + ret = di->bus.read_bulk (di, di->regs[BQ27XXX_DM_DATA ], buf->a,BQ27XXX_DM_SZ ); +if (ret <0 ) + goto out; + + ret = di->bus.read (di, di->regs[BQ27XXX_DM_CKSUM ], true); +if (ret <0 ) + goto out; + +if ((u8 )ret !=bq27xxx_battery_checksum_dm_block (buf)) { + ret = -EINVAL ; + goto out; + } + + buf->full = true; + buf->updt = false; +return 0 ; + +out: +dev_err (di->dev,"bus error reading chip memory: %d\n" , ret); +return ret; +} + +static void bq27xxx_battery_update_dm_block (struct bq27xxx_device_info *di, +struct bq27xxx_dm_buf *buf, +enum bq27xxx_dm_reg_id reg_id, +unsigned int val) +{ +struct bq27xxx_dm_reg *reg = &bq27xxx_dm_regs[di->chip][reg_id]; +const char * str = bq27xxx_dm_reg_name[reg_id]; +u16 *prev =bq27xxx_dm_reg_ptr (buf, reg); + +if (prev ==NULL ) { +dev_warn (di->dev,"buffer does not match %s dm spec\n" , str); +return ; + } + +if (reg->bytes !=2 ) { +dev_warn (di->dev,"%s dm spec has unsupported byte size\n" , str); +return ; + } + +if (!buf->full) +return ; + +if (be16_to_cpup (prev) == val) { +dev_info (di->dev,"%s has %u\n" , str, val); +return ; + } + +dev_info (di->dev,"update %s to %u\n" , str, val); + + *prev =cpu_to_be16 (val); + buf->updt = true; +} + +static int bq27xxx_battery_set_cfgupdate (struct bq27xxx_device_info *di, +bool state) +{ +int ret, try=100 ; + + ret = di->bus.write (di, di->regs[BQ27XXX_REG_CTRL ], + state ?BQ27XXX_SET_CFGUPDATE :BQ27XXX_SOFT_RESET , + false); +if (ret <0 ) + goto out; + +do { +BQ27XXX_MSLEEP (25 ); + ret = di->bus.read (di, di->regs[BQ27XXX_REG_FLAGS ], false); +if (ret <0 ) + goto out; + }while (!(ret &BQ27XXX_FLAG_CFGUP ) == state && --try); + +if (100 -try)/* remove after other cfgupdate parts are tested */ +dev_info (di->dev,"cfgupdate %d, retries %d\n" , state,100 -try); + +if (try) +return 0 ; + +dev_err (di->dev,"timed out waiting for cfgupdate flag %d\n" , state); +return -EINVAL ; + +out: +dev_err (di->dev,"bus error on %s: %d\n" , state ?"set_cfgupdate" :"soft_reset" , ret); +return ret; +} + +static int bq27xxx_battery_write_dm_block (struct bq27xxx_device_info *di, +struct bq27xxx_dm_buf *buf) +{ +bool cfgup = di->chip ==BQ27425 || di->chip ==BQ27421 ;/* || BQ27441 || BQ27621 */ +int ret; + +if (!buf->updt) +return 0 ; + +if (cfgup) { + ret =bq27xxx_battery_set_cfgupdate (di, true); +if (ret <0 ) +return ret; + } + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_CTRL ],0 , true); +if (ret <0 ) + goto out; + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_CLASS ], buf->class, true); +if (ret <0 ) + goto out; + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_BLOCK ], buf->block, true); +if (ret <0 ) + goto out; + +BQ27XXX_MSLEEP (1 ); + + ret = di->bus.write_bulk (di, di->regs[BQ27XXX_DM_DATA ], buf->a,BQ27XXX_DM_SZ ); +if (ret <0 ) + goto out; + + ret = di->bus.write (di, di->regs[BQ27XXX_DM_CKSUM ], +bq27xxx_battery_checksum_dm_block (buf), true); +if (ret <0 ) + goto out; + +/* THE FOLLOWING SEQUENCE IS TOXIC. DO NOT USE! +* If the 'time' delay is insufficient, NVM corruption results on +* the '425 chip (and perhaps others), which could damage the chip. +* It was suggested in this TI tool: +* http://git.ti.com/bms-linux/bqtool/blobs/master/gauge.c#line328 +* +* 1. MSLEEP(time) after above write(BQ27XXX_DM_CKSUM, ...) +* 2. write(BQ27XXX_DM_BLOCK, buf->block) +* 3. sum = read(BQ27XXX_DM_CKSUM) +* 4. if (sum != bq27xxx_battery_checksum_dm_block(buf)) +* report error +*/ + +if (cfgup) { +BQ27XXX_MSLEEP (1 ); + ret =bq27xxx_battery_set_cfgupdate (di, false); +if (ret <0 ) +return ret; + }else { +/* flash DM updates in <100ms, but reset time isn't documented */ +BQ27XXX_MSLEEP (400 ); + } + + buf->updt = false; +return 0 ; + +out: +if (cfgup) +bq27xxx_battery_set_cfgupdate (di, false); + +dev_err (di->dev,"bus error writing chip memory: %d\n" , ret); +return ret; +} + +static void bq27xxx_battery_set_config (struct bq27xxx_device_info *di, +struct power_supply_battery_info *info) +{ +struct bq27xxx_dm_buf bd =BQ27XXX_DM_BUF (di,BQ27XXX_DM_DESIGN_CAPACITY ); +struct bq27xxx_dm_buf bt =BQ27XXX_DM_BUF (di,BQ27XXX_DM_TERMINATE_VOLTAGE ); + +if (info->charge_full_design_uah != -EINVAL + && info->energy_full_design_uwh != -EINVAL ) { +bq27xxx_battery_read_dm_block (di, &bd); +/* assume design energy & capacity are in same block */ +bq27xxx_battery_update_dm_block (di, &bd, +BQ27XXX_DM_DESIGN_CAPACITY , + info->charge_full_design_uah /1000 ); +bq27xxx_battery_update_dm_block (di, &bd, +BQ27XXX_DM_DESIGN_ENERGY , + info->energy_full_design_uwh /1000 ); + } + +if (info->voltage_min_design_uv != -EINVAL ) { +bool same = bd.class == bt.class && bd.block == bt.block; +if (!same) +bq27xxx_battery_read_dm_block (di, &bt); +bq27xxx_battery_update_dm_block (di, same ? &bd : &bt, +BQ27XXX_DM_TERMINATE_VOLTAGE , + info->voltage_min_design_uv /1000 ); + } + +bq27xxx_battery_write_dm_block (di, &bd); +bq27xxx_battery_write_dm_block (di, &bt); +} + +void bq27xxx_battery_settings (struct bq27xxx_device_info *di) +{ +struct power_supply_battery_info info = {}; +unsigned int min, max; + +/* functions don't exist for writing data so abort */ +if (!di->bus.write || !di->bus.write_bulk) +return ; + +/* no settings to be set for this chipset so abort */ +if (!bq27xxx_dm_regs[di->chip]) +return ; + +if (bq27xxx_battery_set_seal_state (di, false) <0 ) +return ; + +if (power_supply_get_battery_info (di->bat, &info) <0 ) + goto out; + +if (info.energy_full_design_uwh != info.charge_full_design_uah) { +if (info.energy_full_design_uwh == -EINVAL ) +dev_warn (di->dev, +"missing battery:energy-full-design-microwatt-hours\n" ); +else if (info.charge_full_design_uah == -EINVAL ) +dev_warn (di->dev, +"missing battery:charge-full-design-microamp-hours\n" ); + } + +/* assume min == 0 */ + max = bq27xxx_dm_regs[di->chip][BQ27XXX_DM_DESIGN_ENERGY ].max; +if (info.energy_full_design_uwh > max *1000 ) { +dev_err (di->dev, +"invalid battery:energy-full-design-microwatt-hours %d\n" , + info.energy_full_design_uwh); + info.energy_full_design_uwh = -EINVAL ; + } + +/* assume min == 0 */ + max = bq27xxx_dm_regs[di->chip][BQ27XXX_DM_DESIGN_CAPACITY ].max; +if (info.charge_full_design_uah > max *1000 ) { +dev_err (di->dev, +"invalid battery:charge-full-design-microamp-hours %d\n" , + info.charge_full_design_uah); + info.charge_full_design_uah = -EINVAL ; + } + + min = bq27xxx_dm_regs[di->chip][BQ27XXX_DM_TERMINATE_VOLTAGE ].min; + max = bq27xxx_dm_regs[di->chip][BQ27XXX_DM_TERMINATE_VOLTAGE ].max; +if ((info.voltage_min_design_uv < min *1000 + || info.voltage_min_design_uv > max *1000 ) + && info.voltage_min_design_uv != -EINVAL ) { +dev_err (di->dev, +"invalid battery:voltage-min-design-microvolt %d\n" , + info.voltage_min_design_uv); + info.voltage_min_design_uv = -EINVAL ; + } + +if ((info.energy_full_design_uwh == -EINVAL + || info.charge_full_design_uah == -EINVAL ) + && info.voltage_min_design_uv == -EINVAL ) + goto out; + +bq27xxx_battery_set_config (di, &info); + +out: +bq27xxx_battery_set_seal_state (di, true); +} +/* * Return the battery State-of-Charge * Or < 0 if something fails.
@@ -1006,6 +1474,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN : ret =bq27xxx_simple_value (di->charge_design_full, val);break ; +/* +* TODO: Implement these to make registers set from +* power_supply_battery_info visible in sysfs. +*/ +case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN : +case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN : +return -EINVAL ;case POWER_SUPPLY_PROP_CYCLE_COUNT : ret =bq27xxx_simple_value (di->cache.cycle_count, val);break ;
@@ -1039,7 +1514,10 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)int bq27xxx_battery_setup (struct bq27xxx_device_info *di) {struct power_supply_desc *psy_desc; -struct power_supply_config psy_cfg = { .drv_data = di, }; +struct power_supply_config psy_cfg = { + .of_node = di->dev->of_node, + .drv_data = di, + };INIT_DELAYED_WORK (&di->work, bq27xxx_battery_poll);mutex_init (&di->lock);
@@ -1064,6 +1542,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)dev_info (di->dev,"support ver. %s enabled\n" ,DRIVER_VERSION ); +bq27xxx_battery_settings (di);bq27xxx_battery_update (di);mutex_lock (&bq27xxx_list_lock);
Signature · 1 line
2.9.3
PATCH v9 8/8 power: bq27xxx_battery_i2c: Add I2C bulk read/write functions
In reply to Liam BreckFrom: Matt Ranostay <[email protected]>
write(), read_bulk(), write_bulk() are required by bq27xxx_battery devicetree code.
--- drivers/power/supply/bq27xxx_battery_i2c.c | 82 +++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-)
drivers/power/supply/bq27xxx_battery_i2c.c +81 -1
@@ -38,7 +38,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, {struct i2c_client *client =to_i2c_client (di->dev);struct i2c_msg msg[2 ]; -unsigned char data[2 ]; +u8 data[2 ];int ret;if (!client->adapter)
@@ -68,6 +68,82 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,return ret; } +static int bq27xxx_battery_i2c_write (struct bq27xxx_device_info *di,u8 reg, +int value,bool single) +{ +struct i2c_client *client =to_i2c_client (di->dev); +struct i2c_msg msg; +u8 data[4 ]; +int ret; + +if (!client->adapter) +return -ENODEV ; + + data[0 ] = reg; +if (single) { + data[1 ] = (u8 ) value; + msg.len =2 ; + }else { +put_unaligned_le16 (value, &data[1 ]); + msg.len =3 ; + } + + msg.buf = data; + msg.addr = client->addr; + msg.flags =0 ; + + ret =i2c_transfer (client->adapter, &msg,1 ); +if (ret <0 ) +return ret; +if (ret !=1 ) +return -EINVAL ; +return 0 ; +} + +static int bq27xxx_battery_i2c_bulk_read (struct bq27xxx_device_info *di,u8 reg, +u8 *data,int len) +{ +struct i2c_client *client =to_i2c_client (di->dev); +int ret; + +if (!client->adapter) +return -ENODEV ; + + ret =i2c_smbus_read_i2c_block_data (client, reg, len, data); +if (ret <0 ) +return ret; +if (ret != len) +return -EINVAL ; +return 0 ; +} + +static int bq27xxx_battery_i2c_bulk_write (struct bq27xxx_device_info *di, +u8 reg,u8 *data,int len) +{ +struct i2c_client *client =to_i2c_client (di->dev); +struct i2c_msg msg; +u8 buf[33 ]; +int ret; + +if (!client->adapter) +return -ENODEV ; + + buf[0 ] = reg; +memcpy (&buf[1 ], data, len); + + msg.buf = buf; + msg.addr = client->addr; + msg.flags =0 ; + msg.len = len +1 ; + + ret =i2c_transfer (client->adapter, &msg,1 ); +if (ret <0 ) +return ret; +if (ret !=1 ) +return -EINVAL ; +return 0 ; +} +static int bq27xxx_battery_i2c_probe (struct i2c_client *client,const struct i2c_device_id *id) {
@@ -95,7 +171,11 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client, di->dev = &client->dev; di->chip = id->driver_data; di->name = name; + di->bus.read = bq27xxx_battery_i2c_read; + di->bus.write = bq27xxx_battery_i2c_write; + di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read; + di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write; ret =bq27xxx_battery_setup (di);if (ret)
Signature · 1 line
2.9.3