* [PATCH 0/3] Add Cellwise CW2217
@ 2026-10-27 13:17 Chris Morgan
2026-10-27 13:17 ` [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 Chris Morgan
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Chris Morgan @ 2026-10-27 13:17 UTC (permalink / raw)
To: linux-pm
Cc: devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre,
t.schramm, Chris Morgan
From: Chris Morgan <macromorgan@hotmail.com>
Add the Cellwise CW2217 battery gauge driver. This driver is based on
the sources used for Rockchip's Board Support package [1] but has
been updated to use the regmap API and devm helpers.
[1] https://github.com/rockchip-linux/kernel/blob/develop-6.12/drivers/power/supply/cw221x_battery.c
Chris Morgan (3):
dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218
power: supply: cw2217: Add support for Cellwise cw2217
arm64: dts: rockchip: Add Battery Gauge to Anbernic Vita-Pro
.../bindings/power/supply/cw2015_battery.yaml | 42 +-
.../rockchip/rk3576-anbernic-rg-vita-pro.dts | 21 +-
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/cw2217_battery.c | 818 ++++++++++++++++++
5 files changed, 885 insertions(+), 8 deletions(-)
create mode 100644 drivers/power/supply/cw2217_battery.c
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 2026-10-27 13:17 [PATCH 0/3] Add Cellwise CW2217 Chris Morgan @ 2026-10-27 13:17 ` Chris Morgan 2026-10-27 15:57 ` Krzysztof Kozlowski 2026-10-27 13:17 ` [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 Chris Morgan 2026-10-27 13:17 ` [PATCH 3/3] arm64: dts: rockchip: Add Battery Gauge to Anbernic Vita-Pro Chris Morgan 2 siblings, 1 reply; 7+ messages in thread From: Chris Morgan @ 2026-10-27 13:17 UTC (permalink / raw) To: linux-pm Cc: devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Chris Morgan From: Chris Morgan <macromorgan@hotmail.com> Add bindings for the Cellwise cw2215/cw2217/cw2218 i2C interface battery gauge. It is similar in function to the existing cw2015 but with a different register layout. Each of the cw2215, cw2217 and cw2218 only really differ in the minor specs and the hardware ID they display when queried. shunt-resistor-micro-ohms is a property in use for multiple devices so not appending a vendor prefix to it. Signed-off-by: Chris Morgan <macromorgan@hotmail.com> --- .../bindings/power/supply/cw2015_battery.yaml | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml b/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml index f7bde324153d..12d2d9a9cf6f 100644 --- a/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml +++ b/Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml @@ -14,12 +14,13 @@ description: | phandle in monitored-battery. If specified the driver uses the charge-full-design-microamp-hours property of the battery. -allOf: - - $ref: power-supply.yaml# - properties: compatible: - const: cellwise,cw2015 + enum: + - cellwise,cw2015 + - cellwise,cw2215 + - cellwise,cw2217 + - cellwise,cw2218 reg: maxItems: 1 @@ -31,8 +32,6 @@ properties: it is to mail two batteries to a test facility of CellWise and receive back a test report with the binary blob. $ref: /schemas/types.yaml#/definitions/uint8-array - minItems: 64 - maxItems: 64 cellwise,monitor-interval-ms: description: @@ -41,7 +40,14 @@ properties: power-supplies: minItems: 1 - maxItems: 8 # Should be enough + maxItems: 8 # Should be enough + + shunt-resistor-micro-ohms: + description: | + The value, in micro-ohms, of the shunt resistor used for sampling + the battery current. + minimum: 1000000 + maximum: 50000000 monitored-battery: true @@ -49,6 +55,28 @@ required: - compatible - reg +allOf: + - $ref: power-supply.yaml# + - if: + properties: + compatible: + contains: + enum: + - cellwise,cw2015 + then: + properties: + shunt-resistor-micro-ohms: false + + cellwise,battery-profile: + minItems: 64 + maxItems: 64 + + else: + properties: + cellwise,battery-profile: + minItems: 80 + maxItems: 80 + additionalProperties: false examples: -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 2026-10-27 13:17 ` [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 Chris Morgan @ 2026-10-27 15:57 ` Krzysztof Kozlowski 0 siblings, 0 replies; 7+ messages in thread From: Krzysztof Kozlowski @ 2026-10-27 15:57 UTC (permalink / raw) To: Chris Morgan, linux-pm Cc: devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Chris Morgan On 27/10/2026 14:17, Chris Morgan wrote: > > -allOf: > - - $ref: power-supply.yaml# > - > properties: > compatible: > - const: cellwise,cw2015 > + enum: > + - cellwise,cw2015 > + - cellwise,cw2215 > + - cellwise,cw2217 > + - cellwise,cw2218 > > reg: > maxItems: 1 > @@ -31,8 +32,6 @@ properties: > it is to mail two batteries to a test facility of CellWise and receive > back a test report with the binary blob. > $ref: /schemas/types.yaml#/definitions/uint8-array > - minItems: 64 > - maxItems: 64 This needs min/maxItems constraints. > > cellwise,monitor-interval-ms: > description: > @@ -41,7 +40,14 @@ properties: > > power-supplies: > minItems: 1 > - maxItems: 8 # Should be enough > + maxItems: 8 # Should be enough Why changing? yamllint complained? > + > + shunt-resistor-micro-ohms: > + description: | Do not need '|' unless you need to preserve formatting. > + The value, in micro-ohms, of the shunt resistor used for sampling > + the battery current. > + minimum: 1000000 > + maximum: 50000000 > > monitored-battery: true > > @@ -49,6 +55,28 @@ required: > - compatible > - reg > > +allOf: > + - $ref: power-supply.yaml# > + - if: > + properties: > + compatible: > + contains: > + enum: > + - cellwise,cw2015 > + then: > + properties: > + shunt-resistor-micro-ohms: false > + > + cellwise,battery-profile: > + minItems: 64 > + maxItems: 64 > + > + else: > + properties: > + cellwise,battery-profile: > + minItems: 80 > + maxItems: 80 default for shunt-resistor-micro-ohms? or is it required for new devices? Best regards, Krzysztof ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 2026-10-27 13:17 [PATCH 0/3] Add Cellwise CW2217 Chris Morgan 2026-10-27 13:17 ` [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 Chris Morgan @ 2026-10-27 13:17 ` Chris Morgan 2026-10-27 13:34 ` Shawn Lin 2026-10-27 13:17 ` [PATCH 3/3] arm64: dts: rockchip: Add Battery Gauge to Anbernic Vita-Pro Chris Morgan 2 siblings, 1 reply; 7+ messages in thread From: Chris Morgan @ 2026-10-27 13:17 UTC (permalink / raw) To: linux-pm Cc: devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Chris Morgan From: Chris Morgan <macromorgan@hotmail.com> Add support for the CW2217 i2c battery gauge. The gauge driver was adapted from the Rockchip BSP driver [1]. [1] https://github.com/rockchip-linux/kernel/blob/develop-6.12/drivers/power/supply/cw221x_battery.c Signed-off-by: Chris Morgan <macromorgan@hotmail.com> --- drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + drivers/power/supply/cw2217_battery.c | 818 ++++++++++++++++++++++++++ 3 files changed, 830 insertions(+) create mode 100644 drivers/power/supply/cw2217_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b89ef40df7f8..6a5235ec16c1 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -141,6 +141,17 @@ config BATTERY_CW2015 This driver can also be built as a module. If so, the module will be called cw2015_battery. +config BATTERY_CW2217 + tristate "CW2217 Battery driver" + depends on I2C + select REGMAP_I2C + help + Say Y here to enable support for the cellwise cw2217 + battery fuel gauge (used in the Anbernic RG Vita Pro & others) + + This driver can also be built as a module. If so, the module will be + called cw2217_battery. + config BATTERY_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" depends on W1 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index f3c0d1006c1a..66e6303888c7 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o +obj-$(CONFIG_BATTERY_CW2217) += cw2217_battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o diff --git a/drivers/power/supply/cw2217_battery.c b/drivers/power/supply/cw2217_battery.c new file mode 100644 index 000000000000..8b18db3d54db --- /dev/null +++ b/drivers/power/supply/cw2217_battery.c @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Chrager driver for cw2217 + * + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei <xsf@rock-chips.com> + */ +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/devm-helpers.h> +#include <linux/i2c.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> + +#include <asm/byteorder.h> + +#define CW2217_VERSION 0x00 +#define CW2217_CHIP_ID 0xA0 + +#define CW2217_VCELL_H 0x02 +#define CW2217_VCELL_L 0x03 + +#define CW2217_SOC_H 0x04 +#define CW2217_SOC_L 0x05 + +#define CW2217_TEMP 0x06 + +#define CW2217_CONFIG 0x08 +#define CW2217_CONFIG_ACT_MSK GENMASK(7, 4) +#define CW2217_CONFIG_SLEEP (0x03 << 6) +#define CW2217_CONFIG_RESTART (0x03 << 4) +#define CW2217_CONFIG_ACTIVE (0x00) + +#define CW2217_INT_CONF 0x0A +#define CW2217_EN_SOC_INT BIT(6) +#define CW2217_EN_TMX_INT BIT(5) +#define CW2217_EN_TMN_INT BIT(4) + +#define CW2217_SOC_ALERT 0x0B +#define CW2217_SOC_UPDATE_FLG BIT(7) +#define CW2217_SOC_ALERT_MSK GENMASK(6, 0) +#define CW2217_SOC_ALERT_OFF 0x0 + +#define CW2217_TEMP_MAX 0x0C + +#define CW2217_TEMP_MIN 0x0D + +#define CW2217_CURRENT_H 0x0E +#define CW2217_CURRENT_L 0x0F + +#define CW2217_BAT_PROFILE 0x10 +#define CW2217_PROFILE_SZ 80 + +#define CW2217_T_HOST_H 0xA0 +#define CW2217_T_HOST_L 0xA1 + +#define CW2217_USER_CONF 0xA2 + +#define CW2217_CYCLECNT_H 0xA4 +#define CW2217_CYCLECNT_L 0xA5 + +#define CW2217_SOH 0xA6 + +/* This register not documented in datasheet. */ +#define CW2217_IC_STATE 0xA7 +#define CW2217_IC_READY_MASK 0x0C +#define CW2217_IC_READY 3 + +#define CW2217_STB_CUR_H 0xA8 +#define CW2217_STB_CUR_L 0xA9 + +#define CW2217_FW_VERSION 0xAB +#define CW2217_ID_MASK GENMASK(7, 6) +#define CW2215_ID 2 +#define CW2217_ID 1 +#define CW2218_ID 0 + +/* Default values from BSP driver */ +#define CW2217_USER_RSENSE 1500 +#define CW2217_DEFAULT_POLL_INTERVAL_MS 5000 + +struct cw2217_battery { + struct device *dev; + struct regmap *regmap; + struct power_supply *cw_bat; + u8 *bat_profile; + struct workqueue_struct *battery_workqueue; + struct delayed_work battery_delay_work; + int user_rsense; + int chip_id; + int voltage; + int soc; + int temp; + long cw_current; + int cycle; + int soh; + int fw_version; + u32 poll_interval_ms; + bool update_profile; +}; + +/* + * The CONFIG register is used for the host MCU to configure the fuel gauge IC. + * The default value is 0xF0, SLEEP and RESTART bits are set. To power up the + * IC, the host MCU needs to write 0x30 to exit shutdown mode, and then write + * 0x00 to restart the gauge to enter active mode. To reset the IC, the host + * MCU needs to write 0xF0, 0x30 and 0x00 in sequence to this register to + * complete the restart procedure. The CW2217 will reload relevant parameters + * and settings and restart SOC calculation. Note that the SOC may be a + * different value after reset operation since it is a brand-new calculation + * based on the latest battery status. The BSP driver slept 20ms after restart + * and 10ms after setting active, though no delays are documented in the + * datasheet. + */ +static int cw2217_active(struct cw2217_battery *cw_bat, bool active) +{ + int ret; + + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, + CW2217_CONFIG_RESTART); + if (ret) + return ret; + + fsleep(20000); + + if (active) { + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, + CW2217_CONFIG_ACTIVE); + if (ret) + return ret; + } else { + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, + CW2217_CONFIG_SLEEP | CW2217_CONFIG_RESTART); + if (ret) + return ret; + } + + fsleep(10000); + + return 0; +} + +/* + * The 0x00 register is an UNSIGNED 8bit read-only register. Its value is + * fixed to 0xA0 in shutdown mode and active mode. + */ +static int cw2217_get_chip_id(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + ret = regmap_read(cw_bat->regmap, CW2217_VERSION, ®); + if (ret) + return ret; + + cw_bat->chip_id = reg; + + return 0; +} + +/* + * The VCELL registers (0x02 0x03) are an UNSIGNED 14bit read-only register + * that updates the battery voltage continuously. Battery voltage is measured + * between the VCELL pin and VSS pin, which is the ground reference. A 14bit + * sigma-delta A/D converter is used and the voltage resolution is 312.5uV + * (0.3125mV is * 5/16). + */ +static int cw2217_get_voltage(struct cw2217_battery *cw_bat) +{ + __be16 reg; + int ret; + + ret = regmap_bulk_read(cw_bat->regmap, CW2217_VCELL_H, + ®, sizeof(reg)); + if (ret) + return ret; + + cw_bat->voltage = ((be16_to_cpu(reg) * 5) / 16) * 1000; + + return 0; +} + +/* + * The SOC registers (0x04 0x05) are an UNSIGNED 16bit read-only register that + * indicates the SOC of the battery. The SOC shows in % format, which means + * how much percent of the battery's total available capacity is remaining in + * the battery now. The SOC can intrinsically adjust itself to cater to the + * change of battery status; including load, temperature, and aging. + * The high byte(0x04) contains the SOC in 1% unit which can be directly used + * if this resolution is good enough for the application. The low byte (0x05) + * provides more accurate fractional part of the SOC and its LSB is (1/256) %. + * For now, we are only interested in the high byte. + */ +static int cw2217_get_capacity(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + ret = regmap_read(cw_bat->regmap, CW2217_SOC_H, ®); + if (ret) + return ret; + + cw_bat->soc = clamp(reg, 0, 100); + + return 0; +} + +/* + * The TEMP register is an UNSIGNED 8bit read only register. + * It reports the real-time battery temperature + * measured at TS pin. The scope is from -40 to 87.5 degrees Celsius, + * LSB is 0.5 degree Celsius. TEMP(C) = - 40 + Value(0x06 Reg) / 2 + */ +static int cw2217_get_temp(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + ret = regmap_read(cw_bat->regmap, CW2217_TEMP, ®); + if (ret) + return ret; + + cw_bat->temp = ((int)reg / 2) - 40; + + return 0; +} + +/* + * Current is a signed 16bit register (0x0E 0x0F) that reports current A/D + * converter result of the voltage across the current sense resistor, + * 10mohm typical. The register value should be divided by the sense + * resistance to convert to amperes. The value of the sense resistor + * determines the resolution and the full-scale range of the current readings. + * The LSB of 0x0F is (52.4/32768)uV for CW2215 and CW2217. The LSB of 0x0F is + * (125/32768)uV for CW2218. + */ +static int cw2217_get_current(struct cw2217_battery *cw_bat) +{ + s32 cw_current; + __be16 reg; + u8 fw_ver; + int ret; + + if (cw_bat->fw_version == 0) { + cw_bat->cw_current = 0; + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); + return -EIO; + } + + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CURRENT_H, + ®, sizeof(reg)); + if (ret) + return ret; + + cw_current = (s16)be16_to_cpu(reg); + + fw_ver = FIELD_GET(CW2217_ID_MASK, cw_bat->fw_version); + switch (fw_ver) { + case CW2215_ID: + case CW2217_ID: + cw_current = (cw_current * 1600) / (cw_bat->user_rsense / 1000); + cw_bat->cw_current = cw_current * 1000; + break; + case CW2218_ID: + cw_current = (cw_current * 3815) / (cw_bat->user_rsense / 1000); + cw_bat->cw_current = cw_current * 1000; + break; + default: + cw_bat->cw_current = 0; + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); + return -EIO; + } + + return 0; +} + +/* + * CYCLECNT is an UNSIGNED 16bit register (0xA4 0xA5) that counts cycle life + * of the battery. The LSB of 0xA5 stands for 1/16 cycle. This register will + * be cleared after entering shutdown mode. + */ +static int cw2217_get_cycle_count(struct cw2217_battery *cw_bat) +{ + __be16 reg; + int ret; + + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CYCLECNT_H, + ®, sizeof(reg)); + if (ret) + return ret; + + cw_bat->cycle = be16_to_cpu(reg) / 16; + + return 0; +} + +/* + * SOH (State of Health) is an UNSIGNED 8bit register(0xA6) that represents the level of + * battery aging by tracking battery internal impedance increment. When the device enters + * active mode, this register refresh to 0x64 by default. Its range is 0x00 to 0x64, + * indicating 0 to 100%. This register will be clear after enters shutdown mode. + */ +static int cw2217_get_soh(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + ret = regmap_read(cw_bat->regmap, CW2217_SOH, ®); + if (ret) + return ret; + + cw_bat->soh = clamp(reg, 0, 100); + + return 0; +} + +/* + * FW_VERSION register reports the firmware (FW) running in the chip. It is + * fixed to 0x00 when the chip is in shutdown mode. When in active mode, + * Bit [7:6] = '01' stand for the CW2217, Bit [7:6] = '00' stand for the + * CW2218 and Bit [7:6] = '10' stand for CW2215. Bit[5:0] stand for the FW + * version running in the chip. Note that the FW version is subject to update. + */ +static int cw2217_get_fw_version(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + ret = regmap_read(cw_bat->regmap, CW2217_FW_VERSION, ®); + if (ret) + return ret; + + cw_bat->fw_version = reg; + + return 0; +} + +static int cw2217_update_data(struct cw2217_battery *cw_bat) +{ + int ret; + + ret = cw2217_get_voltage(cw_bat); + if (ret) + return ret; + + ret = cw2217_get_capacity(cw_bat); + if (ret) + return ret; + + ret = cw2217_get_temp(cw_bat); + if (ret) + return ret; + + ret = cw2217_get_current(cw_bat); + if (ret) + return ret; + + ret = cw2217_get_cycle_count(cw_bat); + if (ret) + return ret; + + return cw2217_get_soh(cw_bat); +} + +static int cw2217_parse_properties(struct cw2217_battery *cw_bat) +{ + struct device *dev = cw_bat->dev; + int length; + int ret; + + ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms", + &cw_bat->poll_interval_ms); + if (ret) { + dev_dbg(cw_bat->dev, "Using default poll interval\n"); + cw_bat->poll_interval_ms = CW2217_DEFAULT_POLL_INTERVAL_MS; + } + + /* + * Datasheet only lists options of between 1M and 50M for resistor + * values, so assuming that is all and clamping if out of range. + */ + ret = device_property_read_u32(dev, + "shunt-resistor-micro-ohms", + &cw_bat->user_rsense); + if (ret) { + cw_bat->user_rsense = CW2217_USER_RSENSE; + dev_warn(cw_bat->dev, + "Using default resistor value, reported mA may be wrong\n"); + } + + /* Clamp, but also warn we're clamping due to out of range. */ + if ((cw_bat->user_rsense < 1000000) || (cw_bat->user_rsense > 50000000)) { + cw_bat->user_rsense = clamp(cw_bat->user_rsense, 1000000, 50000000); + dev_warn(cw_bat->dev, "Shunt resistor value out of range, setting to %d", + cw_bat->user_rsense); + } + + length = device_property_count_u8(dev, "cellwise,battery-profile"); + if (length < 0) { + dev_warn(cw_bat->dev, + "No battery-profile found, using current flash contents\n"); + } else if (length != CW2217_PROFILE_SZ) { + dev_err(cw_bat->dev, "battery-profile must be %d bytes\n", + CW2217_PROFILE_SZ); + return -EINVAL; + } else { + cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL); + if (!cw_bat->bat_profile) + return -ENOMEM; + + ret = device_property_read_u8_array(dev, + "cellwise,battery-profile", + cw_bat->bat_profile, + length); + if (ret) + return ret; + } + + return 0; +} + +/* Load a new battery profile provided from the device tree. */ +static int cw2217_update_config(struct cw2217_battery *cw_bat) +{ + u32 reg; + int ret; + + /* Set the battery monitor state to shutdown. */ + ret = cw2217_active(cw_bat, false); + if (ret) + return ret; + + /* update new battery info */ + ret = regmap_raw_write(cw_bat->regmap, CW2217_BAT_PROFILE, + cw_bat->bat_profile, CW2217_PROFILE_SZ); + if (ret) + return ret; + + /* Set update flag for new profile and disable SOC alert. */ + ret = regmap_write(cw_bat->regmap, CW2217_SOC_ALERT, + CW2217_SOC_UPDATE_FLG | CW2217_SOC_ALERT_OFF); + if (ret) + return ret; + + /* mask all interrupts */ + ret = regmap_clear_bits(cw_bat->regmap, CW2217_INT_CONF, + CW2217_EN_SOC_INT | CW2217_EN_TMX_INT | CW2217_EN_TMN_INT); + if (ret) + return ret; + + ret = cw2217_active(cw_bat, true); + if (ret) + return ret; + + /* + * Check the undocumented register for bits 2 and 3 to be 1. Note that + * the BSP driver would sleep 100ms after every iteration and retry up + * to 50 times, this felt excessive so reducing to 10. + */ + ret = regmap_read_poll_timeout(cw_bat->regmap, CW2217_IC_STATE, reg, + FIELD_GET(CW2217_IC_READY_MASK, reg) == CW2217_IC_READY, + 100 * USEC_PER_MSEC, 10 * USEC_PER_SEC); + if (ret) { + dev_err(cw_bat->dev, + "Gauge did not become ready after profile upload\n"); + cw2217_active(cw_bat, false); + return ret; + } + + return 0; +} + +/* Determine whether the battery profile needs to be updated. */ +static int cw2217_check_profile(struct cw2217_battery *cw_bat) +{ + u8 bat_info[CW2217_PROFILE_SZ]; + int ret; + u32 reg; + + cw_bat->update_profile = false; + + ret = regmap_read(cw_bat->regmap, CW2217_CONFIG, ®); + if (ret) + return ret; + + /* Active is 0x00, anything else is inactive */ + if (FIELD_GET(CW2217_CONFIG_ACT_MSK, reg)) { + cw_bat->update_profile = true; + return 0; + } + + ret = regmap_read(cw_bat->regmap, CW2217_SOC_ALERT, ®); + if (ret) + return ret; + + if (FIELD_GET(CW2217_SOC_UPDATE_FLG, reg) == 0) { + cw_bat->update_profile = true; + return 0; + } + + ret = regmap_raw_read(cw_bat->regmap, CW2217_BAT_PROFILE, bat_info, CW2217_PROFILE_SZ); + if (ret) { + dev_err(cw_bat->dev, "Failed to read existing battery profile\n"); + return ret; + } + + if (memcmp(bat_info, cw_bat->bat_profile, CW2217_PROFILE_SZ)) { + dev_warn(cw_bat->dev, "Replacing stored battery profile\n"); + cw_bat->update_profile = true; + } + + return 0; +} + +static int cw2217_init(struct cw2217_battery *cw_bat) +{ + int ret; + + ret = cw2217_get_chip_id(cw_bat); + if (ret) + return dev_err_probe(cw_bat->dev, ret, "I2C read write error"); + + /* + * All of the known Cellwise 221x chips set the ID bits to 0xa0. If it + * doesn't match, we're trying to probe the wrong hardware. + */ + if (cw_bat->chip_id != CW2217_CHIP_ID) + return dev_err_probe(cw_bat->dev, -ENODEV, "Not a cw221x\n"); + + ret = cw2217_check_profile(cw_bat); + if (ret) { + dev_err(cw_bat->dev, "i2c read write error"); + return ret; + } + + if (cw_bat->update_profile) { + ret = cw2217_update_config(cw_bat); + if (ret) + return ret; + } + + return 0; +} + +static void cw2217_bat_work(struct work_struct *work) +{ + struct delayed_work *delay_work; + struct cw2217_battery *cw_bat; + int ret; + + delay_work = container_of(work, + struct delayed_work, + work); + + cw_bat = container_of(delay_work, + struct cw2217_battery, + battery_delay_work); + + ret = cw2217_update_data(cw_bat); + if (ret) + dev_err(cw_bat->dev, "i2c read error when update data"); + + queue_delayed_work(cw_bat->battery_workqueue, + &cw_bat->battery_delay_work, + msecs_to_jiffies(cw_bat->poll_interval_ms)); +} + +static int cw2217_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cw2217_battery *cw_bat = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = cw_bat->cycle; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = cw_bat->soc; + break; + case POWER_SUPPLY_PROP_STATUS: + /* + * Observed when battery was full that it would report + * approximately -12mv at between 97-99 SoC. Assume that if the + * battery current is above 0 it's charging, it's below + * 0 but with an SoC 95% or above it's not charging because + * it's full, otherwise it's simply not charging. If it's not + * supplied it is discharging. + */ + if (power_supply_is_system_supplied()) { + if (cw_bat->cw_current > 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (cw_bat->soc >= 95) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = (cw_bat->voltage <= 0) ? 0 : 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = cw_bat->voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = cw_bat->cw_current; + break; + case POWER_SUPPLY_PROP_STATE_OF_HEALTH: + val->intval = cw_bat->soh; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = cw_bat->temp; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property cw2217_battery_properties[] = { + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_STATE_OF_HEALTH, + POWER_SUPPLY_PROP_TEMP, +}; + +static const struct power_supply_desc cw2217_bat_desc = { + .name = "cw2217-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = cw2217_battery_properties, + .num_properties = ARRAY_SIZE(cw2217_battery_properties), + .get_property = cw2217_battery_get_property, +}; + +static const struct regmap_range cw2217_ranges_rd_yes[] = { + regmap_reg_range(CW2217_VERSION, CW2217_VERSION), + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), + regmap_reg_range(CW2217_BAT_PROFILE, + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), + regmap_reg_range(CW2217_FW_VERSION, CW2217_FW_VERSION), +}; + +static const struct regmap_access_table cw2217_rd_table = { + .yes_ranges = cw2217_ranges_rd_yes, + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_rd_yes), +}; + +static const struct regmap_range cw2217_ranges_wr_yes[] = { + regmap_reg_range(CW2217_TEMP, CW2217_TEMP), + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), + regmap_reg_range(CW2217_BAT_PROFILE, + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), +}; + +static const struct regmap_access_table cw2217_wr_table = { + .yes_ranges = cw2217_ranges_wr_yes, + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_wr_yes), +}; + +static const struct regmap_range cw2217_ranges_vol_yes[] = { + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), + regmap_reg_range(CW2217_INT_CONF, CW2217_SOC_ALERT), + regmap_reg_range(CW2217_CURRENT_H, CW2217_CURRENT_L), + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), +}; + +static const struct regmap_access_table cw2217_vol_table = { + .yes_ranges = cw2217_ranges_vol_yes, + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_vol_yes), +}; + +static const struct regmap_config cw2217_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &cw2217_rd_table, + .wr_table = &cw2217_wr_table, + .volatile_table = &cw2217_vol_table, + .cache_type = REGCACHE_MAPLE, + .max_register = CW2217_FW_VERSION, +}; + +static int cw2217_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = { 0 }; + struct cw2217_battery *cw_bat; + int ret; + + cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); + if (!cw_bat) + return -ENOMEM; + + i2c_set_clientdata(client, cw_bat); + cw_bat->dev = &client->dev; + + cw_bat->regmap = devm_regmap_init_i2c(client, &cw2217_regmap_config); + if (IS_ERR(cw_bat->regmap)) + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->regmap), + "Failed to allocate regmap: %ld\n", + PTR_ERR(cw_bat->regmap)); + + ret = cw2217_parse_properties(cw_bat); + if (ret) + return ret; + + ret = cw2217_init(cw_bat); + if (ret) + return dev_err_probe(cw_bat->dev, ret, "cw2217 init fail\n"); + + ret = cw2217_get_fw_version(cw_bat); + if (ret) + return dev_err_probe(cw_bat->dev, ret, + "Unable to read fw version\n"); + + ret = cw2217_update_data(cw_bat); + if (ret) + return dev_err_probe(cw_bat->dev, ret, + "Unable to read initial data\n"); + + psy_cfg.drv_data = cw_bat; + cw_bat->cw_bat = devm_power_supply_register(&client->dev, + &cw2217_bat_desc, &psy_cfg); + if (IS_ERR(cw_bat->cw_bat)) + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->cw_bat), + "Failed to register battery: %ld\n", + PTR_ERR(cw_bat->cw_bat)); + + cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev, + "cw2217_gauge", 0); + if (!cw_bat->battery_workqueue) + return -ENOMEM; + + ret = devm_delayed_work_autocancel(&client->dev, + &cw_bat->battery_delay_work, + cw2217_bat_work); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register delayed work\n"); + + queue_delayed_work(cw_bat->battery_workqueue, + &cw_bat->battery_delay_work, + msecs_to_jiffies(50)); + + return 0; +} + +static int __maybe_unused cw2217_bat_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); + + cancel_delayed_work(&cw_bat->battery_delay_work); + return 0; +} + +static int __maybe_unused cw2217_bat_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); + + queue_delayed_work(cw_bat->battery_workqueue, + &cw_bat->battery_delay_work, + msecs_to_jiffies(20)); + return 0; +} + +static SIMPLE_DEV_PM_OPS(cw2217_bat_pm_ops, cw2217_bat_suspend, + cw2217_bat_resume); + +static const struct i2c_device_id cw2217_id_table[] = { + { .name = "cw2215" }, + { .name = "cw2217" }, + { .name = "cw2218" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cw2217_id_table); + +static const struct of_device_id cw2217_match_table[] = { + { .compatible = "cellwise,cw2215", }, + { .compatible = "cellwise,cw2217", }, + { .compatible = "cellwise,cw2218", }, + { }, +}; +MODULE_DEVICE_TABLE(of, cw2217_match_table); + +static struct i2c_driver cw2217_driver = { + .driver = { + .name = "cw2217", + .pm = &cw2217_bat_pm_ops, + .of_match_table = of_match_ptr(cw2217_match_table), + }, + .probe = cw2217_probe, + .id_table = cw2217_id_table, +}; + +module_i2c_driver(cw2217_driver); + +MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>"); +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); +MODULE_DESCRIPTION("cw2217 battery driver"); +MODULE_LICENSE("GPL"); -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 2026-10-27 13:17 ` [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 Chris Morgan @ 2026-10-27 13:34 ` Shawn Lin 2026-10-27 15:46 ` Chris Morgan 0 siblings, 1 reply; 7+ messages in thread From: Shawn Lin @ 2026-10-27 13:34 UTC (permalink / raw) To: Chris Morgan, linux-pm Cc: shawn.lin, devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Chris Morgan, Xu Shengfei Cc: Shengfei 在 2026/10/27 星期二 21:17, Chris Morgan 写道: > From: Chris Morgan <macromorgan@hotmail.com> > > Add support for the CW2217 i2c battery gauge. The gauge driver was > adapted from the Rockchip BSP driver [1]. > I don't look into it to evaluate how much it differ from Shengfei's driver. If not too much, he deserves a credit here. I CCed him to look more into your submission version. > [1] https://github.com/rockchip-linux/kernel/blob/develop-6.12/drivers/power/supply/cw221x_battery.c > > Signed-off-by: Chris Morgan <macromorgan@hotmail.com> > --- > drivers/power/supply/Kconfig | 11 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/cw2217_battery.c | 818 ++++++++++++++++++++++++++ > 3 files changed, 830 insertions(+) > create mode 100644 drivers/power/supply/cw2217_battery.c > > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > index b89ef40df7f8..6a5235ec16c1 100644 > --- a/drivers/power/supply/Kconfig > +++ b/drivers/power/supply/Kconfig > @@ -141,6 +141,17 @@ config BATTERY_CW2015 > This driver can also be built as a module. If so, the module will be > called cw2015_battery. > > +config BATTERY_CW2217 > + tristate "CW2217 Battery driver" > + depends on I2C > + select REGMAP_I2C > + help > + Say Y here to enable support for the cellwise cw2217 > + battery fuel gauge (used in the Anbernic RG Vita Pro & others) > + > + This driver can also be built as a module. If so, the module will be > + called cw2217_battery. > + > config BATTERY_DS2760 > tristate "DS2760 battery driver (HP iPAQ & others)" > depends on W1 > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile > index f3c0d1006c1a..66e6303888c7 100644 > --- a/drivers/power/supply/Makefile > +++ b/drivers/power/supply/Makefile > @@ -27,6 +27,7 @@ obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o > obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o > obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o > obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o > +obj-$(CONFIG_BATTERY_CW2217) += cw2217_battery.o > obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o > obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o > obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o > diff --git a/drivers/power/supply/cw2217_battery.c b/drivers/power/supply/cw2217_battery.c > new file mode 100644 > index 000000000000..8b18db3d54db > --- /dev/null > +++ b/drivers/power/supply/cw2217_battery.c > @@ -0,0 +1,818 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Chrager driver for cw2217 > + * > + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. > + * > + * Author: Xu Shengfei <xsf@rock-chips.com> > + */ > +#include <linux/bits.h> > +#include <linux/delay.h> > +#include <linux/devm-helpers.h> > +#include <linux/i2c.h> > +#include <linux/minmax.h> > +#include <linux/module.h> > +#include <linux/power_supply.h> > +#include <linux/regmap.h> > +#include <linux/workqueue.h> > + > +#include <asm/byteorder.h> > + > +#define CW2217_VERSION 0x00 > +#define CW2217_CHIP_ID 0xA0 > + > +#define CW2217_VCELL_H 0x02 > +#define CW2217_VCELL_L 0x03 > + > +#define CW2217_SOC_H 0x04 > +#define CW2217_SOC_L 0x05 > + > +#define CW2217_TEMP 0x06 > + > +#define CW2217_CONFIG 0x08 > +#define CW2217_CONFIG_ACT_MSK GENMASK(7, 4) > +#define CW2217_CONFIG_SLEEP (0x03 << 6) > +#define CW2217_CONFIG_RESTART (0x03 << 4) > +#define CW2217_CONFIG_ACTIVE (0x00) > + > +#define CW2217_INT_CONF 0x0A > +#define CW2217_EN_SOC_INT BIT(6) > +#define CW2217_EN_TMX_INT BIT(5) > +#define CW2217_EN_TMN_INT BIT(4) > + > +#define CW2217_SOC_ALERT 0x0B > +#define CW2217_SOC_UPDATE_FLG BIT(7) > +#define CW2217_SOC_ALERT_MSK GENMASK(6, 0) > +#define CW2217_SOC_ALERT_OFF 0x0 > + > +#define CW2217_TEMP_MAX 0x0C > + > +#define CW2217_TEMP_MIN 0x0D > + > +#define CW2217_CURRENT_H 0x0E > +#define CW2217_CURRENT_L 0x0F > + > +#define CW2217_BAT_PROFILE 0x10 > +#define CW2217_PROFILE_SZ 80 > + > +#define CW2217_T_HOST_H 0xA0 > +#define CW2217_T_HOST_L 0xA1 > + > +#define CW2217_USER_CONF 0xA2 > + > +#define CW2217_CYCLECNT_H 0xA4 > +#define CW2217_CYCLECNT_L 0xA5 > + > +#define CW2217_SOH 0xA6 > + > +/* This register not documented in datasheet. */ > +#define CW2217_IC_STATE 0xA7 > +#define CW2217_IC_READY_MASK 0x0C > +#define CW2217_IC_READY 3 > + > +#define CW2217_STB_CUR_H 0xA8 > +#define CW2217_STB_CUR_L 0xA9 > + > +#define CW2217_FW_VERSION 0xAB > +#define CW2217_ID_MASK GENMASK(7, 6) > +#define CW2215_ID 2 > +#define CW2217_ID 1 > +#define CW2218_ID 0 > + > +/* Default values from BSP driver */ > +#define CW2217_USER_RSENSE 1500 > +#define CW2217_DEFAULT_POLL_INTERVAL_MS 5000 > + > +struct cw2217_battery { > + struct device *dev; > + struct regmap *regmap; > + struct power_supply *cw_bat; > + u8 *bat_profile; > + struct workqueue_struct *battery_workqueue; > + struct delayed_work battery_delay_work; > + int user_rsense; > + int chip_id; > + int voltage; > + int soc; > + int temp; > + long cw_current; > + int cycle; > + int soh; > + int fw_version; > + u32 poll_interval_ms; > + bool update_profile; > +}; > + > +/* > + * The CONFIG register is used for the host MCU to configure the fuel gauge IC. > + * The default value is 0xF0, SLEEP and RESTART bits are set. To power up the > + * IC, the host MCU needs to write 0x30 to exit shutdown mode, and then write > + * 0x00 to restart the gauge to enter active mode. To reset the IC, the host > + * MCU needs to write 0xF0, 0x30 and 0x00 in sequence to this register to > + * complete the restart procedure. The CW2217 will reload relevant parameters > + * and settings and restart SOC calculation. Note that the SOC may be a > + * different value after reset operation since it is a brand-new calculation > + * based on the latest battery status. The BSP driver slept 20ms after restart > + * and 10ms after setting active, though no delays are documented in the > + * datasheet. > + */ > +static int cw2217_active(struct cw2217_battery *cw_bat, bool active) > +{ > + int ret; > + > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > + CW2217_CONFIG_RESTART); > + if (ret) > + return ret; > + > + fsleep(20000); > + > + if (active) { > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > + CW2217_CONFIG_ACTIVE); > + if (ret) > + return ret; > + } else { > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > + CW2217_CONFIG_SLEEP | CW2217_CONFIG_RESTART); > + if (ret) > + return ret; > + } > + > + fsleep(10000); > + > + return 0; > +} > + > +/* > + * The 0x00 register is an UNSIGNED 8bit read-only register. Its value is > + * fixed to 0xA0 in shutdown mode and active mode. > + */ > +static int cw2217_get_chip_id(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + ret = regmap_read(cw_bat->regmap, CW2217_VERSION, ®); > + if (ret) > + return ret; > + > + cw_bat->chip_id = reg; > + > + return 0; > +} > + > +/* > + * The VCELL registers (0x02 0x03) are an UNSIGNED 14bit read-only register > + * that updates the battery voltage continuously. Battery voltage is measured > + * between the VCELL pin and VSS pin, which is the ground reference. A 14bit > + * sigma-delta A/D converter is used and the voltage resolution is 312.5uV > + * (0.3125mV is * 5/16). > + */ > +static int cw2217_get_voltage(struct cw2217_battery *cw_bat) > +{ > + __be16 reg; > + int ret; > + > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_VCELL_H, > + ®, sizeof(reg)); > + if (ret) > + return ret; > + > + cw_bat->voltage = ((be16_to_cpu(reg) * 5) / 16) * 1000; > + > + return 0; > +} > + > +/* > + * The SOC registers (0x04 0x05) are an UNSIGNED 16bit read-only register that > + * indicates the SOC of the battery. The SOC shows in % format, which means > + * how much percent of the battery's total available capacity is remaining in > + * the battery now. The SOC can intrinsically adjust itself to cater to the > + * change of battery status; including load, temperature, and aging. > + * The high byte(0x04) contains the SOC in 1% unit which can be directly used > + * if this resolution is good enough for the application. The low byte (0x05) > + * provides more accurate fractional part of the SOC and its LSB is (1/256) %. > + * For now, we are only interested in the high byte. > + */ > +static int cw2217_get_capacity(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + ret = regmap_read(cw_bat->regmap, CW2217_SOC_H, ®); > + if (ret) > + return ret; > + > + cw_bat->soc = clamp(reg, 0, 100); > + > + return 0; > +} > + > +/* > + * The TEMP register is an UNSIGNED 8bit read only register. > + * It reports the real-time battery temperature > + * measured at TS pin. The scope is from -40 to 87.5 degrees Celsius, > + * LSB is 0.5 degree Celsius. TEMP(C) = - 40 + Value(0x06 Reg) / 2 > + */ > +static int cw2217_get_temp(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + ret = regmap_read(cw_bat->regmap, CW2217_TEMP, ®); > + if (ret) > + return ret; > + > + cw_bat->temp = ((int)reg / 2) - 40; > + > + return 0; > +} > + > +/* > + * Current is a signed 16bit register (0x0E 0x0F) that reports current A/D > + * converter result of the voltage across the current sense resistor, > + * 10mohm typical. The register value should be divided by the sense > + * resistance to convert to amperes. The value of the sense resistor > + * determines the resolution and the full-scale range of the current readings. > + * The LSB of 0x0F is (52.4/32768)uV for CW2215 and CW2217. The LSB of 0x0F is > + * (125/32768)uV for CW2218. > + */ > +static int cw2217_get_current(struct cw2217_battery *cw_bat) > +{ > + s32 cw_current; > + __be16 reg; > + u8 fw_ver; > + int ret; > + > + if (cw_bat->fw_version == 0) { > + cw_bat->cw_current = 0; > + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); > + return -EIO; > + } > + > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CURRENT_H, > + ®, sizeof(reg)); > + if (ret) > + return ret; > + > + cw_current = (s16)be16_to_cpu(reg); > + > + fw_ver = FIELD_GET(CW2217_ID_MASK, cw_bat->fw_version); > + switch (fw_ver) { > + case CW2215_ID: > + case CW2217_ID: > + cw_current = (cw_current * 1600) / (cw_bat->user_rsense / 1000); > + cw_bat->cw_current = cw_current * 1000; > + break; > + case CW2218_ID: > + cw_current = (cw_current * 3815) / (cw_bat->user_rsense / 1000); > + cw_bat->cw_current = cw_current * 1000; > + break; > + default: > + cw_bat->cw_current = 0; > + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); > + return -EIO; > + } > + > + return 0; > +} > + > +/* > + * CYCLECNT is an UNSIGNED 16bit register (0xA4 0xA5) that counts cycle life > + * of the battery. The LSB of 0xA5 stands for 1/16 cycle. This register will > + * be cleared after entering shutdown mode. > + */ > +static int cw2217_get_cycle_count(struct cw2217_battery *cw_bat) > +{ > + __be16 reg; > + int ret; > + > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CYCLECNT_H, > + ®, sizeof(reg)); > + if (ret) > + return ret; > + > + cw_bat->cycle = be16_to_cpu(reg) / 16; > + > + return 0; > +} > + > +/* > + * SOH (State of Health) is an UNSIGNED 8bit register(0xA6) that represents the level of > + * battery aging by tracking battery internal impedance increment. When the device enters > + * active mode, this register refresh to 0x64 by default. Its range is 0x00 to 0x64, > + * indicating 0 to 100%. This register will be clear after enters shutdown mode. > + */ > +static int cw2217_get_soh(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + ret = regmap_read(cw_bat->regmap, CW2217_SOH, ®); > + if (ret) > + return ret; > + > + cw_bat->soh = clamp(reg, 0, 100); > + > + return 0; > +} > + > +/* > + * FW_VERSION register reports the firmware (FW) running in the chip. It is > + * fixed to 0x00 when the chip is in shutdown mode. When in active mode, > + * Bit [7:6] = '01' stand for the CW2217, Bit [7:6] = '00' stand for the > + * CW2218 and Bit [7:6] = '10' stand for CW2215. Bit[5:0] stand for the FW > + * version running in the chip. Note that the FW version is subject to update. > + */ > +static int cw2217_get_fw_version(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + ret = regmap_read(cw_bat->regmap, CW2217_FW_VERSION, ®); > + if (ret) > + return ret; > + > + cw_bat->fw_version = reg; > + > + return 0; > +} > + > +static int cw2217_update_data(struct cw2217_battery *cw_bat) > +{ > + int ret; > + > + ret = cw2217_get_voltage(cw_bat); > + if (ret) > + return ret; > + > + ret = cw2217_get_capacity(cw_bat); > + if (ret) > + return ret; > + > + ret = cw2217_get_temp(cw_bat); > + if (ret) > + return ret; > + > + ret = cw2217_get_current(cw_bat); > + if (ret) > + return ret; > + > + ret = cw2217_get_cycle_count(cw_bat); > + if (ret) > + return ret; > + > + return cw2217_get_soh(cw_bat); > +} > + > +static int cw2217_parse_properties(struct cw2217_battery *cw_bat) > +{ > + struct device *dev = cw_bat->dev; > + int length; > + int ret; > + > + ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms", > + &cw_bat->poll_interval_ms); > + if (ret) { > + dev_dbg(cw_bat->dev, "Using default poll interval\n"); > + cw_bat->poll_interval_ms = CW2217_DEFAULT_POLL_INTERVAL_MS; > + } > + > + /* > + * Datasheet only lists options of between 1M and 50M for resistor > + * values, so assuming that is all and clamping if out of range. > + */ > + ret = device_property_read_u32(dev, > + "shunt-resistor-micro-ohms", > + &cw_bat->user_rsense); > + if (ret) { > + cw_bat->user_rsense = CW2217_USER_RSENSE; > + dev_warn(cw_bat->dev, > + "Using default resistor value, reported mA may be wrong\n"); > + } > + > + /* Clamp, but also warn we're clamping due to out of range. */ > + if ((cw_bat->user_rsense < 1000000) || (cw_bat->user_rsense > 50000000)) { > + cw_bat->user_rsense = clamp(cw_bat->user_rsense, 1000000, 50000000); > + dev_warn(cw_bat->dev, "Shunt resistor value out of range, setting to %d", > + cw_bat->user_rsense); > + } > + > + length = device_property_count_u8(dev, "cellwise,battery-profile"); > + if (length < 0) { > + dev_warn(cw_bat->dev, > + "No battery-profile found, using current flash contents\n"); > + } else if (length != CW2217_PROFILE_SZ) { > + dev_err(cw_bat->dev, "battery-profile must be %d bytes\n", > + CW2217_PROFILE_SZ); > + return -EINVAL; > + } else { > + cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL); > + if (!cw_bat->bat_profile) > + return -ENOMEM; > + > + ret = device_property_read_u8_array(dev, > + "cellwise,battery-profile", > + cw_bat->bat_profile, > + length); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +/* Load a new battery profile provided from the device tree. */ > +static int cw2217_update_config(struct cw2217_battery *cw_bat) > +{ > + u32 reg; > + int ret; > + > + /* Set the battery monitor state to shutdown. */ > + ret = cw2217_active(cw_bat, false); > + if (ret) > + return ret; > + > + /* update new battery info */ > + ret = regmap_raw_write(cw_bat->regmap, CW2217_BAT_PROFILE, > + cw_bat->bat_profile, CW2217_PROFILE_SZ); > + if (ret) > + return ret; > + > + /* Set update flag for new profile and disable SOC alert. */ > + ret = regmap_write(cw_bat->regmap, CW2217_SOC_ALERT, > + CW2217_SOC_UPDATE_FLG | CW2217_SOC_ALERT_OFF); > + if (ret) > + return ret; > + > + /* mask all interrupts */ > + ret = regmap_clear_bits(cw_bat->regmap, CW2217_INT_CONF, > + CW2217_EN_SOC_INT | CW2217_EN_TMX_INT | CW2217_EN_TMN_INT); > + if (ret) > + return ret; > + > + ret = cw2217_active(cw_bat, true); > + if (ret) > + return ret; > + > + /* > + * Check the undocumented register for bits 2 and 3 to be 1. Note that > + * the BSP driver would sleep 100ms after every iteration and retry up > + * to 50 times, this felt excessive so reducing to 10. > + */ > + ret = regmap_read_poll_timeout(cw_bat->regmap, CW2217_IC_STATE, reg, > + FIELD_GET(CW2217_IC_READY_MASK, reg) == CW2217_IC_READY, > + 100 * USEC_PER_MSEC, 10 * USEC_PER_SEC); > + if (ret) { > + dev_err(cw_bat->dev, > + "Gauge did not become ready after profile upload\n"); > + cw2217_active(cw_bat, false); > + return ret; > + } > + > + return 0; > +} > + > +/* Determine whether the battery profile needs to be updated. */ > +static int cw2217_check_profile(struct cw2217_battery *cw_bat) > +{ > + u8 bat_info[CW2217_PROFILE_SZ]; > + int ret; > + u32 reg; > + > + cw_bat->update_profile = false; > + > + ret = regmap_read(cw_bat->regmap, CW2217_CONFIG, ®); > + if (ret) > + return ret; > + > + /* Active is 0x00, anything else is inactive */ > + if (FIELD_GET(CW2217_CONFIG_ACT_MSK, reg)) { > + cw_bat->update_profile = true; > + return 0; > + } > + > + ret = regmap_read(cw_bat->regmap, CW2217_SOC_ALERT, ®); > + if (ret) > + return ret; > + > + if (FIELD_GET(CW2217_SOC_UPDATE_FLG, reg) == 0) { > + cw_bat->update_profile = true; > + return 0; > + } > + > + ret = regmap_raw_read(cw_bat->regmap, CW2217_BAT_PROFILE, bat_info, CW2217_PROFILE_SZ); > + if (ret) { > + dev_err(cw_bat->dev, "Failed to read existing battery profile\n"); > + return ret; > + } > + > + if (memcmp(bat_info, cw_bat->bat_profile, CW2217_PROFILE_SZ)) { > + dev_warn(cw_bat->dev, "Replacing stored battery profile\n"); > + cw_bat->update_profile = true; > + } > + > + return 0; > +} > + > +static int cw2217_init(struct cw2217_battery *cw_bat) > +{ > + int ret; > + > + ret = cw2217_get_chip_id(cw_bat); > + if (ret) > + return dev_err_probe(cw_bat->dev, ret, "I2C read write error"); > + > + /* > + * All of the known Cellwise 221x chips set the ID bits to 0xa0. If it > + * doesn't match, we're trying to probe the wrong hardware. > + */ > + if (cw_bat->chip_id != CW2217_CHIP_ID) > + return dev_err_probe(cw_bat->dev, -ENODEV, "Not a cw221x\n"); > + > + ret = cw2217_check_profile(cw_bat); > + if (ret) { > + dev_err(cw_bat->dev, "i2c read write error"); > + return ret; > + } > + > + if (cw_bat->update_profile) { > + ret = cw2217_update_config(cw_bat); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static void cw2217_bat_work(struct work_struct *work) > +{ > + struct delayed_work *delay_work; > + struct cw2217_battery *cw_bat; > + int ret; > + > + delay_work = container_of(work, > + struct delayed_work, > + work); > + > + cw_bat = container_of(delay_work, > + struct cw2217_battery, > + battery_delay_work); > + > + ret = cw2217_update_data(cw_bat); > + if (ret) > + dev_err(cw_bat->dev, "i2c read error when update data"); > + > + queue_delayed_work(cw_bat->battery_workqueue, > + &cw_bat->battery_delay_work, > + msecs_to_jiffies(cw_bat->poll_interval_ms)); > +} > + > +static int cw2217_battery_get_property(struct power_supply *psy, > + enum power_supply_property psp, > + union power_supply_propval *val) > +{ > + struct cw2217_battery *cw_bat = power_supply_get_drvdata(psy); > + int ret = 0; > + > + switch (psp) { > + case POWER_SUPPLY_PROP_CYCLE_COUNT: > + val->intval = cw_bat->cycle; > + break; > + case POWER_SUPPLY_PROP_CAPACITY: > + val->intval = cw_bat->soc; > + break; > + case POWER_SUPPLY_PROP_STATUS: > + /* > + * Observed when battery was full that it would report > + * approximately -12mv at between 97-99 SoC. Assume that if the > + * battery current is above 0 it's charging, it's below > + * 0 but with an SoC 95% or above it's not charging because > + * it's full, otherwise it's simply not charging. If it's not > + * supplied it is discharging. > + */ > + if (power_supply_is_system_supplied()) { > + if (cw_bat->cw_current > 0) > + val->intval = POWER_SUPPLY_STATUS_CHARGING; > + else if (cw_bat->soc >= 95) > + val->intval = POWER_SUPPLY_STATUS_FULL; > + else > + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; > + } else > + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; > + break; > + case POWER_SUPPLY_PROP_PRESENT: > + val->intval = (cw_bat->voltage <= 0) ? 0 : 1; > + break; > + case POWER_SUPPLY_PROP_VOLTAGE_NOW: > + val->intval = cw_bat->voltage; > + break; > + case POWER_SUPPLY_PROP_CURRENT_NOW: > + val->intval = cw_bat->cw_current; > + break; > + case POWER_SUPPLY_PROP_STATE_OF_HEALTH: > + val->intval = cw_bat->soh; > + break; > + case POWER_SUPPLY_PROP_TEMP: > + val->intval = cw_bat->temp; > + break; > + default: > + ret = -EINVAL; > + break; > + } > + > + return ret; > +} > + > +static enum power_supply_property cw2217_battery_properties[] = { > + POWER_SUPPLY_PROP_CYCLE_COUNT, > + POWER_SUPPLY_PROP_CAPACITY, > + POWER_SUPPLY_PROP_PRESENT, > + POWER_SUPPLY_PROP_VOLTAGE_NOW, > + POWER_SUPPLY_PROP_CURRENT_NOW, > + POWER_SUPPLY_PROP_STATUS, > + POWER_SUPPLY_PROP_STATE_OF_HEALTH, > + POWER_SUPPLY_PROP_TEMP, > +}; > + > +static const struct power_supply_desc cw2217_bat_desc = { > + .name = "cw2217-battery", > + .type = POWER_SUPPLY_TYPE_BATTERY, > + .properties = cw2217_battery_properties, > + .num_properties = ARRAY_SIZE(cw2217_battery_properties), > + .get_property = cw2217_battery_get_property, > +}; > + > +static const struct regmap_range cw2217_ranges_rd_yes[] = { > + regmap_reg_range(CW2217_VERSION, CW2217_VERSION), > + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), > + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), > + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), > + regmap_reg_range(CW2217_BAT_PROFILE, > + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), > + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), > + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), > + regmap_reg_range(CW2217_FW_VERSION, CW2217_FW_VERSION), > +}; > + > +static const struct regmap_access_table cw2217_rd_table = { > + .yes_ranges = cw2217_ranges_rd_yes, > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_rd_yes), > +}; > + > +static const struct regmap_range cw2217_ranges_wr_yes[] = { > + regmap_reg_range(CW2217_TEMP, CW2217_TEMP), > + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), > + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), > + regmap_reg_range(CW2217_BAT_PROFILE, > + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), > + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), > +}; > + > +static const struct regmap_access_table cw2217_wr_table = { > + .yes_ranges = cw2217_ranges_wr_yes, > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_wr_yes), > +}; > + > +static const struct regmap_range cw2217_ranges_vol_yes[] = { > + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), > + regmap_reg_range(CW2217_INT_CONF, CW2217_SOC_ALERT), > + regmap_reg_range(CW2217_CURRENT_H, CW2217_CURRENT_L), > + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), > +}; > + > +static const struct regmap_access_table cw2217_vol_table = { > + .yes_ranges = cw2217_ranges_vol_yes, > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_vol_yes), > +}; > + > +static const struct regmap_config cw2217_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .rd_table = &cw2217_rd_table, > + .wr_table = &cw2217_wr_table, > + .volatile_table = &cw2217_vol_table, > + .cache_type = REGCACHE_MAPLE, > + .max_register = CW2217_FW_VERSION, > +}; > + > +static int cw2217_probe(struct i2c_client *client) > +{ > + struct power_supply_config psy_cfg = { 0 }; > + struct cw2217_battery *cw_bat; > + int ret; > + > + cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); > + if (!cw_bat) > + return -ENOMEM; > + > + i2c_set_clientdata(client, cw_bat); > + cw_bat->dev = &client->dev; > + > + cw_bat->regmap = devm_regmap_init_i2c(client, &cw2217_regmap_config); > + if (IS_ERR(cw_bat->regmap)) > + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->regmap), > + "Failed to allocate regmap: %ld\n", > + PTR_ERR(cw_bat->regmap)); > + > + ret = cw2217_parse_properties(cw_bat); > + if (ret) > + return ret; > + > + ret = cw2217_init(cw_bat); > + if (ret) > + return dev_err_probe(cw_bat->dev, ret, "cw2217 init fail\n"); > + > + ret = cw2217_get_fw_version(cw_bat); > + if (ret) > + return dev_err_probe(cw_bat->dev, ret, > + "Unable to read fw version\n"); > + > + ret = cw2217_update_data(cw_bat); > + if (ret) > + return dev_err_probe(cw_bat->dev, ret, > + "Unable to read initial data\n"); > + > + psy_cfg.drv_data = cw_bat; > + cw_bat->cw_bat = devm_power_supply_register(&client->dev, > + &cw2217_bat_desc, &psy_cfg); > + if (IS_ERR(cw_bat->cw_bat)) > + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->cw_bat), > + "Failed to register battery: %ld\n", > + PTR_ERR(cw_bat->cw_bat)); > + > + cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev, > + "cw2217_gauge", 0); > + if (!cw_bat->battery_workqueue) > + return -ENOMEM; > + > + ret = devm_delayed_work_autocancel(&client->dev, > + &cw_bat->battery_delay_work, > + cw2217_bat_work); > + if (ret) > + return dev_err_probe(&client->dev, ret, > + "Failed to register delayed work\n"); > + > + queue_delayed_work(cw_bat->battery_workqueue, > + &cw_bat->battery_delay_work, > + msecs_to_jiffies(50)); > + > + return 0; > +} > + > +static int __maybe_unused cw2217_bat_suspend(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); > + > + cancel_delayed_work(&cw_bat->battery_delay_work); > + return 0; > +} > + > +static int __maybe_unused cw2217_bat_resume(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); > + > + queue_delayed_work(cw_bat->battery_workqueue, > + &cw_bat->battery_delay_work, > + msecs_to_jiffies(20)); > + return 0; > +} > + > +static SIMPLE_DEV_PM_OPS(cw2217_bat_pm_ops, cw2217_bat_suspend, > + cw2217_bat_resume); > + > +static const struct i2c_device_id cw2217_id_table[] = { > + { .name = "cw2215" }, > + { .name = "cw2217" }, > + { .name = "cw2218" }, > + { } > +}; > +MODULE_DEVICE_TABLE(i2c, cw2217_id_table); > + > +static const struct of_device_id cw2217_match_table[] = { > + { .compatible = "cellwise,cw2215", }, > + { .compatible = "cellwise,cw2217", }, > + { .compatible = "cellwise,cw2218", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, cw2217_match_table); > + > +static struct i2c_driver cw2217_driver = { > + .driver = { > + .name = "cw2217", > + .pm = &cw2217_bat_pm_ops, > + .of_match_table = of_match_ptr(cw2217_match_table), > + }, > + .probe = cw2217_probe, > + .id_table = cw2217_id_table, > +}; > + > +module_i2c_driver(cw2217_driver); > + > +MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>"); > +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); > +MODULE_DESCRIPTION("cw2217 battery driver"); > +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 2026-10-27 13:34 ` Shawn Lin @ 2026-10-27 15:46 ` Chris Morgan 0 siblings, 0 replies; 7+ messages in thread From: Chris Morgan @ 2026-10-27 15:46 UTC (permalink / raw) To: Shawn Lin Cc: Chris Morgan, linux-pm, devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Xu Shengfei On Tue, Oct 27, 2026 at 09:34:53PM +0800, Shawn Lin wrote: > Cc: Shengfei > > 在 2026/10/27 星期二 21:17, Chris Morgan 写道: > > From: Chris Morgan <macromorgan@hotmail.com> > > > > Add support for the CW2217 i2c battery gauge. The gauge driver was > > adapted from the Rockchip BSP driver [1]. > > > > I don't look into it to evaluate how much it differ from Shengfei's > driver. If not too much, he deserves a credit here. I CCed him to > look more into your submission version. It shouldn't differ a lot, and I left his name in the file itself. I'll include it on the next revision in the Signed-off/Co-developed line if that works? Sorry for the oversight. Thank you. > > > [1] https://github.com/rockchip-linux/kernel/blob/develop-6.12/drivers/power/supply/cw221x_battery.c > > > > Signed-off-by: Chris Morgan <macromorgan@hotmail.com> > > --- > > drivers/power/supply/Kconfig | 11 + > > drivers/power/supply/Makefile | 1 + > > drivers/power/supply/cw2217_battery.c | 818 ++++++++++++++++++++++++++ > > 3 files changed, 830 insertions(+) > > create mode 100644 drivers/power/supply/cw2217_battery.c > > > > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > > index b89ef40df7f8..6a5235ec16c1 100644 > > --- a/drivers/power/supply/Kconfig > > +++ b/drivers/power/supply/Kconfig > > @@ -141,6 +141,17 @@ config BATTERY_CW2015 > > This driver can also be built as a module. If so, the module will be > > called cw2015_battery. > > +config BATTERY_CW2217 > > + tristate "CW2217 Battery driver" > > + depends on I2C > > + select REGMAP_I2C > > + help > > + Say Y here to enable support for the cellwise cw2217 > > + battery fuel gauge (used in the Anbernic RG Vita Pro & others) > > + > > + This driver can also be built as a module. If so, the module will be > > + called cw2217_battery. > > + > > config BATTERY_DS2760 > > tristate "DS2760 battery driver (HP iPAQ & others)" > > depends on W1 > > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile > > index f3c0d1006c1a..66e6303888c7 100644 > > --- a/drivers/power/supply/Makefile > > +++ b/drivers/power/supply/Makefile > > @@ -27,6 +27,7 @@ obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o > > obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o > > obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o > > obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o > > +obj-$(CONFIG_BATTERY_CW2217) += cw2217_battery.o > > obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o > > obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o > > obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o > > diff --git a/drivers/power/supply/cw2217_battery.c b/drivers/power/supply/cw2217_battery.c > > new file mode 100644 > > index 000000000000..8b18db3d54db > > --- /dev/null > > +++ b/drivers/power/supply/cw2217_battery.c > > @@ -0,0 +1,818 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Chrager driver for cw2217 > > + * > > + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. > > + * > > + * Author: Xu Shengfei <xsf@rock-chips.com> > > + */ > > +#include <linux/bits.h> > > +#include <linux/delay.h> > > +#include <linux/devm-helpers.h> > > +#include <linux/i2c.h> > > +#include <linux/minmax.h> > > +#include <linux/module.h> > > +#include <linux/power_supply.h> > > +#include <linux/regmap.h> > > +#include <linux/workqueue.h> > > + > > +#include <asm/byteorder.h> > > + > > +#define CW2217_VERSION 0x00 > > +#define CW2217_CHIP_ID 0xA0 > > + > > +#define CW2217_VCELL_H 0x02 > > +#define CW2217_VCELL_L 0x03 > > + > > +#define CW2217_SOC_H 0x04 > > +#define CW2217_SOC_L 0x05 > > + > > +#define CW2217_TEMP 0x06 > > + > > +#define CW2217_CONFIG 0x08 > > +#define CW2217_CONFIG_ACT_MSK GENMASK(7, 4) > > +#define CW2217_CONFIG_SLEEP (0x03 << 6) > > +#define CW2217_CONFIG_RESTART (0x03 << 4) > > +#define CW2217_CONFIG_ACTIVE (0x00) > > + > > +#define CW2217_INT_CONF 0x0A > > +#define CW2217_EN_SOC_INT BIT(6) > > +#define CW2217_EN_TMX_INT BIT(5) > > +#define CW2217_EN_TMN_INT BIT(4) > > + > > +#define CW2217_SOC_ALERT 0x0B > > +#define CW2217_SOC_UPDATE_FLG BIT(7) > > +#define CW2217_SOC_ALERT_MSK GENMASK(6, 0) > > +#define CW2217_SOC_ALERT_OFF 0x0 > > + > > +#define CW2217_TEMP_MAX 0x0C > > + > > +#define CW2217_TEMP_MIN 0x0D > > + > > +#define CW2217_CURRENT_H 0x0E > > +#define CW2217_CURRENT_L 0x0F > > + > > +#define CW2217_BAT_PROFILE 0x10 > > +#define CW2217_PROFILE_SZ 80 > > + > > +#define CW2217_T_HOST_H 0xA0 > > +#define CW2217_T_HOST_L 0xA1 > > + > > +#define CW2217_USER_CONF 0xA2 > > + > > +#define CW2217_CYCLECNT_H 0xA4 > > +#define CW2217_CYCLECNT_L 0xA5 > > + > > +#define CW2217_SOH 0xA6 > > + > > +/* This register not documented in datasheet. */ > > +#define CW2217_IC_STATE 0xA7 > > +#define CW2217_IC_READY_MASK 0x0C > > +#define CW2217_IC_READY 3 > > + > > +#define CW2217_STB_CUR_H 0xA8 > > +#define CW2217_STB_CUR_L 0xA9 > > + > > +#define CW2217_FW_VERSION 0xAB > > +#define CW2217_ID_MASK GENMASK(7, 6) > > +#define CW2215_ID 2 > > +#define CW2217_ID 1 > > +#define CW2218_ID 0 > > + > > +/* Default values from BSP driver */ > > +#define CW2217_USER_RSENSE 1500 > > +#define CW2217_DEFAULT_POLL_INTERVAL_MS 5000 > > + > > +struct cw2217_battery { > > + struct device *dev; > > + struct regmap *regmap; > > + struct power_supply *cw_bat; > > + u8 *bat_profile; > > + struct workqueue_struct *battery_workqueue; > > + struct delayed_work battery_delay_work; > > + int user_rsense; > > + int chip_id; > > + int voltage; > > + int soc; > > + int temp; > > + long cw_current; > > + int cycle; > > + int soh; > > + int fw_version; > > + u32 poll_interval_ms; > > + bool update_profile; > > +}; > > + > > +/* > > + * The CONFIG register is used for the host MCU to configure the fuel gauge IC. > > + * The default value is 0xF0, SLEEP and RESTART bits are set. To power up the > > + * IC, the host MCU needs to write 0x30 to exit shutdown mode, and then write > > + * 0x00 to restart the gauge to enter active mode. To reset the IC, the host > > + * MCU needs to write 0xF0, 0x30 and 0x00 in sequence to this register to > > + * complete the restart procedure. The CW2217 will reload relevant parameters > > + * and settings and restart SOC calculation. Note that the SOC may be a > > + * different value after reset operation since it is a brand-new calculation > > + * based on the latest battery status. The BSP driver slept 20ms after restart > > + * and 10ms after setting active, though no delays are documented in the > > + * datasheet. > > + */ > > +static int cw2217_active(struct cw2217_battery *cw_bat, bool active) > > +{ > > + int ret; > > + > > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > > + CW2217_CONFIG_RESTART); > > + if (ret) > > + return ret; > > + > > + fsleep(20000); > > + > > + if (active) { > > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > > + CW2217_CONFIG_ACTIVE); > > + if (ret) > > + return ret; > > + } else { > > + ret = regmap_write(cw_bat->regmap, CW2217_CONFIG, > > + CW2217_CONFIG_SLEEP | CW2217_CONFIG_RESTART); > > + if (ret) > > + return ret; > > + } > > + > > + fsleep(10000); > > + > > + return 0; > > +} > > + > > +/* > > + * The 0x00 register is an UNSIGNED 8bit read-only register. Its value is > > + * fixed to 0xA0 in shutdown mode and active mode. > > + */ > > +static int cw2217_get_chip_id(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_VERSION, ®); > > + if (ret) > > + return ret; > > + > > + cw_bat->chip_id = reg; > > + > > + return 0; > > +} > > + > > +/* > > + * The VCELL registers (0x02 0x03) are an UNSIGNED 14bit read-only register > > + * that updates the battery voltage continuously. Battery voltage is measured > > + * between the VCELL pin and VSS pin, which is the ground reference. A 14bit > > + * sigma-delta A/D converter is used and the voltage resolution is 312.5uV > > + * (0.3125mV is * 5/16). > > + */ > > +static int cw2217_get_voltage(struct cw2217_battery *cw_bat) > > +{ > > + __be16 reg; > > + int ret; > > + > > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_VCELL_H, > > + ®, sizeof(reg)); > > + if (ret) > > + return ret; > > + > > + cw_bat->voltage = ((be16_to_cpu(reg) * 5) / 16) * 1000; > > + > > + return 0; > > +} > > + > > +/* > > + * The SOC registers (0x04 0x05) are an UNSIGNED 16bit read-only register that > > + * indicates the SOC of the battery. The SOC shows in % format, which means > > + * how much percent of the battery's total available capacity is remaining in > > + * the battery now. The SOC can intrinsically adjust itself to cater to the > > + * change of battery status; including load, temperature, and aging. > > + * The high byte(0x04) contains the SOC in 1% unit which can be directly used > > + * if this resolution is good enough for the application. The low byte (0x05) > > + * provides more accurate fractional part of the SOC and its LSB is (1/256) %. > > + * For now, we are only interested in the high byte. > > + */ > > +static int cw2217_get_capacity(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_SOC_H, ®); > > + if (ret) > > + return ret; > > + > > + cw_bat->soc = clamp(reg, 0, 100); > > + > > + return 0; > > +} > > + > > +/* > > + * The TEMP register is an UNSIGNED 8bit read only register. > > + * It reports the real-time battery temperature > > + * measured at TS pin. The scope is from -40 to 87.5 degrees Celsius, > > + * LSB is 0.5 degree Celsius. TEMP(C) = - 40 + Value(0x06 Reg) / 2 > > + */ > > +static int cw2217_get_temp(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_TEMP, ®); > > + if (ret) > > + return ret; > > + > > + cw_bat->temp = ((int)reg / 2) - 40; > > + > > + return 0; > > +} > > + > > +/* > > + * Current is a signed 16bit register (0x0E 0x0F) that reports current A/D > > + * converter result of the voltage across the current sense resistor, > > + * 10mohm typical. The register value should be divided by the sense > > + * resistance to convert to amperes. The value of the sense resistor > > + * determines the resolution and the full-scale range of the current readings. > > + * The LSB of 0x0F is (52.4/32768)uV for CW2215 and CW2217. The LSB of 0x0F is > > + * (125/32768)uV for CW2218. > > + */ > > +static int cw2217_get_current(struct cw2217_battery *cw_bat) > > +{ > > + s32 cw_current; > > + __be16 reg; > > + u8 fw_ver; > > + int ret; > > + > > + if (cw_bat->fw_version == 0) { > > + cw_bat->cw_current = 0; > > + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); > > + return -EIO; > > + } > > + > > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CURRENT_H, > > + ®, sizeof(reg)); > > + if (ret) > > + return ret; > > + > > + cw_current = (s16)be16_to_cpu(reg); > > + > > + fw_ver = FIELD_GET(CW2217_ID_MASK, cw_bat->fw_version); > > + switch (fw_ver) { > > + case CW2215_ID: > > + case CW2217_ID: > > + cw_current = (cw_current * 1600) / (cw_bat->user_rsense / 1000); > > + cw_bat->cw_current = cw_current * 1000; > > + break; > > + case CW2218_ID: > > + cw_current = (cw_current * 3815) / (cw_bat->user_rsense / 1000); > > + cw_bat->cw_current = cw_current * 1000; > > + break; > > + default: > > + cw_bat->cw_current = 0; > > + dev_err(cw_bat->dev, "Error! CW2217 firmware read error!\n"); > > + return -EIO; > > + } > > + > > + return 0; > > +} > > + > > +/* > > + * CYCLECNT is an UNSIGNED 16bit register (0xA4 0xA5) that counts cycle life > > + * of the battery. The LSB of 0xA5 stands for 1/16 cycle. This register will > > + * be cleared after entering shutdown mode. > > + */ > > +static int cw2217_get_cycle_count(struct cw2217_battery *cw_bat) > > +{ > > + __be16 reg; > > + int ret; > > + > > + ret = regmap_bulk_read(cw_bat->regmap, CW2217_CYCLECNT_H, > > + ®, sizeof(reg)); > > + if (ret) > > + return ret; > > + > > + cw_bat->cycle = be16_to_cpu(reg) / 16; > > + > > + return 0; > > +} > > + > > +/* > > + * SOH (State of Health) is an UNSIGNED 8bit register(0xA6) that represents the level of > > + * battery aging by tracking battery internal impedance increment. When the device enters > > + * active mode, this register refresh to 0x64 by default. Its range is 0x00 to 0x64, > > + * indicating 0 to 100%. This register will be clear after enters shutdown mode. > > + */ > > +static int cw2217_get_soh(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_SOH, ®); > > + if (ret) > > + return ret; > > + > > + cw_bat->soh = clamp(reg, 0, 100); > > + > > + return 0; > > +} > > + > > +/* > > + * FW_VERSION register reports the firmware (FW) running in the chip. It is > > + * fixed to 0x00 when the chip is in shutdown mode. When in active mode, > > + * Bit [7:6] = '01' stand for the CW2217, Bit [7:6] = '00' stand for the > > + * CW2218 and Bit [7:6] = '10' stand for CW2215. Bit[5:0] stand for the FW > > + * version running in the chip. Note that the FW version is subject to update. > > + */ > > +static int cw2217_get_fw_version(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_FW_VERSION, ®); > > + if (ret) > > + return ret; > > + > > + cw_bat->fw_version = reg; > > + > > + return 0; > > +} > > + > > +static int cw2217_update_data(struct cw2217_battery *cw_bat) > > +{ > > + int ret; > > + > > + ret = cw2217_get_voltage(cw_bat); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_get_capacity(cw_bat); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_get_temp(cw_bat); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_get_current(cw_bat); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_get_cycle_count(cw_bat); > > + if (ret) > > + return ret; > > + > > + return cw2217_get_soh(cw_bat); > > +} > > + > > +static int cw2217_parse_properties(struct cw2217_battery *cw_bat) > > +{ > > + struct device *dev = cw_bat->dev; > > + int length; > > + int ret; > > + > > + ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms", > > + &cw_bat->poll_interval_ms); > > + if (ret) { > > + dev_dbg(cw_bat->dev, "Using default poll interval\n"); > > + cw_bat->poll_interval_ms = CW2217_DEFAULT_POLL_INTERVAL_MS; > > + } > > + > > + /* > > + * Datasheet only lists options of between 1M and 50M for resistor > > + * values, so assuming that is all and clamping if out of range. > > + */ > > + ret = device_property_read_u32(dev, > > + "shunt-resistor-micro-ohms", > > + &cw_bat->user_rsense); > > + if (ret) { > > + cw_bat->user_rsense = CW2217_USER_RSENSE; > > + dev_warn(cw_bat->dev, > > + "Using default resistor value, reported mA may be wrong\n"); > > + } > > + > > + /* Clamp, but also warn we're clamping due to out of range. */ > > + if ((cw_bat->user_rsense < 1000000) || (cw_bat->user_rsense > 50000000)) { > > + cw_bat->user_rsense = clamp(cw_bat->user_rsense, 1000000, 50000000); > > + dev_warn(cw_bat->dev, "Shunt resistor value out of range, setting to %d", > > + cw_bat->user_rsense); > > + } > > + > > + length = device_property_count_u8(dev, "cellwise,battery-profile"); > > + if (length < 0) { > > + dev_warn(cw_bat->dev, > > + "No battery-profile found, using current flash contents\n"); > > + } else if (length != CW2217_PROFILE_SZ) { > > + dev_err(cw_bat->dev, "battery-profile must be %d bytes\n", > > + CW2217_PROFILE_SZ); > > + return -EINVAL; > > + } else { > > + cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL); > > + if (!cw_bat->bat_profile) > > + return -ENOMEM; > > + > > + ret = device_property_read_u8_array(dev, > > + "cellwise,battery-profile", > > + cw_bat->bat_profile, > > + length); > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +/* Load a new battery profile provided from the device tree. */ > > +static int cw2217_update_config(struct cw2217_battery *cw_bat) > > +{ > > + u32 reg; > > + int ret; > > + > > + /* Set the battery monitor state to shutdown. */ > > + ret = cw2217_active(cw_bat, false); > > + if (ret) > > + return ret; > > + > > + /* update new battery info */ > > + ret = regmap_raw_write(cw_bat->regmap, CW2217_BAT_PROFILE, > > + cw_bat->bat_profile, CW2217_PROFILE_SZ); > > + if (ret) > > + return ret; > > + > > + /* Set update flag for new profile and disable SOC alert. */ > > + ret = regmap_write(cw_bat->regmap, CW2217_SOC_ALERT, > > + CW2217_SOC_UPDATE_FLG | CW2217_SOC_ALERT_OFF); > > + if (ret) > > + return ret; > > + > > + /* mask all interrupts */ > > + ret = regmap_clear_bits(cw_bat->regmap, CW2217_INT_CONF, > > + CW2217_EN_SOC_INT | CW2217_EN_TMX_INT | CW2217_EN_TMN_INT); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_active(cw_bat, true); > > + if (ret) > > + return ret; > > + > > + /* > > + * Check the undocumented register for bits 2 and 3 to be 1. Note that > > + * the BSP driver would sleep 100ms after every iteration and retry up > > + * to 50 times, this felt excessive so reducing to 10. > > + */ > > + ret = regmap_read_poll_timeout(cw_bat->regmap, CW2217_IC_STATE, reg, > > + FIELD_GET(CW2217_IC_READY_MASK, reg) == CW2217_IC_READY, > > + 100 * USEC_PER_MSEC, 10 * USEC_PER_SEC); > > + if (ret) { > > + dev_err(cw_bat->dev, > > + "Gauge did not become ready after profile upload\n"); > > + cw2217_active(cw_bat, false); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +/* Determine whether the battery profile needs to be updated. */ > > +static int cw2217_check_profile(struct cw2217_battery *cw_bat) > > +{ > > + u8 bat_info[CW2217_PROFILE_SZ]; > > + int ret; > > + u32 reg; > > + > > + cw_bat->update_profile = false; > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_CONFIG, ®); > > + if (ret) > > + return ret; > > + > > + /* Active is 0x00, anything else is inactive */ > > + if (FIELD_GET(CW2217_CONFIG_ACT_MSK, reg)) { > > + cw_bat->update_profile = true; > > + return 0; > > + } > > + > > + ret = regmap_read(cw_bat->regmap, CW2217_SOC_ALERT, ®); > > + if (ret) > > + return ret; > > + > > + if (FIELD_GET(CW2217_SOC_UPDATE_FLG, reg) == 0) { > > + cw_bat->update_profile = true; > > + return 0; > > + } > > + > > + ret = regmap_raw_read(cw_bat->regmap, CW2217_BAT_PROFILE, bat_info, CW2217_PROFILE_SZ); > > + if (ret) { > > + dev_err(cw_bat->dev, "Failed to read existing battery profile\n"); > > + return ret; > > + } > > + > > + if (memcmp(bat_info, cw_bat->bat_profile, CW2217_PROFILE_SZ)) { > > + dev_warn(cw_bat->dev, "Replacing stored battery profile\n"); > > + cw_bat->update_profile = true; > > + } > > + > > + return 0; > > +} > > + > > +static int cw2217_init(struct cw2217_battery *cw_bat) > > +{ > > + int ret; > > + > > + ret = cw2217_get_chip_id(cw_bat); > > + if (ret) > > + return dev_err_probe(cw_bat->dev, ret, "I2C read write error"); > > + > > + /* > > + * All of the known Cellwise 221x chips set the ID bits to 0xa0. If it > > + * doesn't match, we're trying to probe the wrong hardware. > > + */ > > + if (cw_bat->chip_id != CW2217_CHIP_ID) > > + return dev_err_probe(cw_bat->dev, -ENODEV, "Not a cw221x\n"); > > + > > + ret = cw2217_check_profile(cw_bat); > > + if (ret) { > > + dev_err(cw_bat->dev, "i2c read write error"); > > + return ret; > > + } > > + > > + if (cw_bat->update_profile) { > > + ret = cw2217_update_config(cw_bat); > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static void cw2217_bat_work(struct work_struct *work) > > +{ > > + struct delayed_work *delay_work; > > + struct cw2217_battery *cw_bat; > > + int ret; > > + > > + delay_work = container_of(work, > > + struct delayed_work, > > + work); > > + > > + cw_bat = container_of(delay_work, > > + struct cw2217_battery, > > + battery_delay_work); > > + > > + ret = cw2217_update_data(cw_bat); > > + if (ret) > > + dev_err(cw_bat->dev, "i2c read error when update data"); > > + > > + queue_delayed_work(cw_bat->battery_workqueue, > > + &cw_bat->battery_delay_work, > > + msecs_to_jiffies(cw_bat->poll_interval_ms)); > > +} > > + > > +static int cw2217_battery_get_property(struct power_supply *psy, > > + enum power_supply_property psp, > > + union power_supply_propval *val) > > +{ > > + struct cw2217_battery *cw_bat = power_supply_get_drvdata(psy); > > + int ret = 0; > > + > > + switch (psp) { > > + case POWER_SUPPLY_PROP_CYCLE_COUNT: > > + val->intval = cw_bat->cycle; > > + break; > > + case POWER_SUPPLY_PROP_CAPACITY: > > + val->intval = cw_bat->soc; > > + break; > > + case POWER_SUPPLY_PROP_STATUS: > > + /* > > + * Observed when battery was full that it would report > > + * approximately -12mv at between 97-99 SoC. Assume that if the > > + * battery current is above 0 it's charging, it's below > > + * 0 but with an SoC 95% or above it's not charging because > > + * it's full, otherwise it's simply not charging. If it's not > > + * supplied it is discharging. > > + */ > > + if (power_supply_is_system_supplied()) { > > + if (cw_bat->cw_current > 0) > > + val->intval = POWER_SUPPLY_STATUS_CHARGING; > > + else if (cw_bat->soc >= 95) > > + val->intval = POWER_SUPPLY_STATUS_FULL; > > + else > > + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; > > + } else > > + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; > > + break; > > + case POWER_SUPPLY_PROP_PRESENT: > > + val->intval = (cw_bat->voltage <= 0) ? 0 : 1; > > + break; > > + case POWER_SUPPLY_PROP_VOLTAGE_NOW: > > + val->intval = cw_bat->voltage; > > + break; > > + case POWER_SUPPLY_PROP_CURRENT_NOW: > > + val->intval = cw_bat->cw_current; > > + break; > > + case POWER_SUPPLY_PROP_STATE_OF_HEALTH: > > + val->intval = cw_bat->soh; > > + break; > > + case POWER_SUPPLY_PROP_TEMP: > > + val->intval = cw_bat->temp; > > + break; > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + > > + return ret; > > +} > > + > > +static enum power_supply_property cw2217_battery_properties[] = { > > + POWER_SUPPLY_PROP_CYCLE_COUNT, > > + POWER_SUPPLY_PROP_CAPACITY, > > + POWER_SUPPLY_PROP_PRESENT, > > + POWER_SUPPLY_PROP_VOLTAGE_NOW, > > + POWER_SUPPLY_PROP_CURRENT_NOW, > > + POWER_SUPPLY_PROP_STATUS, > > + POWER_SUPPLY_PROP_STATE_OF_HEALTH, > > + POWER_SUPPLY_PROP_TEMP, > > +}; > > + > > +static const struct power_supply_desc cw2217_bat_desc = { > > + .name = "cw2217-battery", > > + .type = POWER_SUPPLY_TYPE_BATTERY, > > + .properties = cw2217_battery_properties, > > + .num_properties = ARRAY_SIZE(cw2217_battery_properties), > > + .get_property = cw2217_battery_get_property, > > +}; > > + > > +static const struct regmap_range cw2217_ranges_rd_yes[] = { > > + regmap_reg_range(CW2217_VERSION, CW2217_VERSION), > > + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), > > + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), > > + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), > > + regmap_reg_range(CW2217_BAT_PROFILE, > > + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), > > + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), > > + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), > > + regmap_reg_range(CW2217_FW_VERSION, CW2217_FW_VERSION), > > +}; > > + > > +static const struct regmap_access_table cw2217_rd_table = { > > + .yes_ranges = cw2217_ranges_rd_yes, > > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_rd_yes), > > +}; > > + > > +static const struct regmap_range cw2217_ranges_wr_yes[] = { > > + regmap_reg_range(CW2217_TEMP, CW2217_TEMP), > > + regmap_reg_range(CW2217_CONFIG, CW2217_CONFIG), > > + regmap_reg_range(CW2217_INT_CONF, CW2217_CURRENT_L), > > + regmap_reg_range(CW2217_BAT_PROFILE, > > + CW2217_BAT_PROFILE + CW2217_PROFILE_SZ - 1), > > + regmap_reg_range(CW2217_T_HOST_H, CW2217_USER_CONF), > > +}; > > + > > +static const struct regmap_access_table cw2217_wr_table = { > > + .yes_ranges = cw2217_ranges_wr_yes, > > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_wr_yes), > > +}; > > + > > +static const struct regmap_range cw2217_ranges_vol_yes[] = { > > + regmap_reg_range(CW2217_VCELL_H, CW2217_TEMP), > > + regmap_reg_range(CW2217_INT_CONF, CW2217_SOC_ALERT), > > + regmap_reg_range(CW2217_CURRENT_H, CW2217_CURRENT_L), > > + regmap_reg_range(CW2217_CYCLECNT_H, CW2217_STB_CUR_L), > > +}; > > + > > +static const struct regmap_access_table cw2217_vol_table = { > > + .yes_ranges = cw2217_ranges_vol_yes, > > + .n_yes_ranges = ARRAY_SIZE(cw2217_ranges_vol_yes), > > +}; > > + > > +static const struct regmap_config cw2217_regmap_config = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + .rd_table = &cw2217_rd_table, > > + .wr_table = &cw2217_wr_table, > > + .volatile_table = &cw2217_vol_table, > > + .cache_type = REGCACHE_MAPLE, > > + .max_register = CW2217_FW_VERSION, > > +}; > > + > > +static int cw2217_probe(struct i2c_client *client) > > +{ > > + struct power_supply_config psy_cfg = { 0 }; > > + struct cw2217_battery *cw_bat; > > + int ret; > > + > > + cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); > > + if (!cw_bat) > > + return -ENOMEM; > > + > > + i2c_set_clientdata(client, cw_bat); > > + cw_bat->dev = &client->dev; > > + > > + cw_bat->regmap = devm_regmap_init_i2c(client, &cw2217_regmap_config); > > + if (IS_ERR(cw_bat->regmap)) > > + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->regmap), > > + "Failed to allocate regmap: %ld\n", > > + PTR_ERR(cw_bat->regmap)); > > + > > + ret = cw2217_parse_properties(cw_bat); > > + if (ret) > > + return ret; > > + > > + ret = cw2217_init(cw_bat); > > + if (ret) > > + return dev_err_probe(cw_bat->dev, ret, "cw2217 init fail\n"); > > + > > + ret = cw2217_get_fw_version(cw_bat); > > + if (ret) > > + return dev_err_probe(cw_bat->dev, ret, > > + "Unable to read fw version\n"); > > + > > + ret = cw2217_update_data(cw_bat); > > + if (ret) > > + return dev_err_probe(cw_bat->dev, ret, > > + "Unable to read initial data\n"); > > + > > + psy_cfg.drv_data = cw_bat; > > + cw_bat->cw_bat = devm_power_supply_register(&client->dev, > > + &cw2217_bat_desc, &psy_cfg); > > + if (IS_ERR(cw_bat->cw_bat)) > > + return dev_err_probe(cw_bat->dev, PTR_ERR(cw_bat->cw_bat), > > + "Failed to register battery: %ld\n", > > + PTR_ERR(cw_bat->cw_bat)); > > + > > + cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev, > > + "cw2217_gauge", 0); > > + if (!cw_bat->battery_workqueue) > > + return -ENOMEM; > > + > > + ret = devm_delayed_work_autocancel(&client->dev, > > + &cw_bat->battery_delay_work, > > + cw2217_bat_work); > > + if (ret) > > + return dev_err_probe(&client->dev, ret, > > + "Failed to register delayed work\n"); > > + > > + queue_delayed_work(cw_bat->battery_workqueue, > > + &cw_bat->battery_delay_work, > > + msecs_to_jiffies(50)); > > + > > + return 0; > > +} > > + > > +static int __maybe_unused cw2217_bat_suspend(struct device *dev) > > +{ > > + struct i2c_client *client = to_i2c_client(dev); > > + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); > > + > > + cancel_delayed_work(&cw_bat->battery_delay_work); > > + return 0; > > +} > > + > > +static int __maybe_unused cw2217_bat_resume(struct device *dev) > > +{ > > + struct i2c_client *client = to_i2c_client(dev); > > + struct cw2217_battery *cw_bat = i2c_get_clientdata(client); > > + > > + queue_delayed_work(cw_bat->battery_workqueue, > > + &cw_bat->battery_delay_work, > > + msecs_to_jiffies(20)); > > + return 0; > > +} > > + > > +static SIMPLE_DEV_PM_OPS(cw2217_bat_pm_ops, cw2217_bat_suspend, > > + cw2217_bat_resume); > > + > > +static const struct i2c_device_id cw2217_id_table[] = { > > + { .name = "cw2215" }, > > + { .name = "cw2217" }, > > + { .name = "cw2218" }, > > + { } > > +}; > > +MODULE_DEVICE_TABLE(i2c, cw2217_id_table); > > + > > +static const struct of_device_id cw2217_match_table[] = { > > + { .compatible = "cellwise,cw2215", }, > > + { .compatible = "cellwise,cw2217", }, > > + { .compatible = "cellwise,cw2218", }, > > + { }, > > +}; > > +MODULE_DEVICE_TABLE(of, cw2217_match_table); > > + > > +static struct i2c_driver cw2217_driver = { > > + .driver = { > > + .name = "cw2217", > > + .pm = &cw2217_bat_pm_ops, > > + .of_match_table = of_match_ptr(cw2217_match_table), > > + }, > > + .probe = cw2217_probe, > > + .id_table = cw2217_id_table, > > +}; > > + > > +module_i2c_driver(cw2217_driver); > > + > > +MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>"); > > +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); > > +MODULE_DESCRIPTION("cw2217 battery driver"); > > +MODULE_LICENSE("GPL"); > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 3/3] arm64: dts: rockchip: Add Battery Gauge to Anbernic Vita-Pro 2026-10-27 13:17 [PATCH 0/3] Add Cellwise CW2217 Chris Morgan 2026-10-27 13:17 ` [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 Chris Morgan 2026-10-27 13:17 ` [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 Chris Morgan @ 2026-10-27 13:17 ` Chris Morgan 2 siblings, 0 replies; 7+ messages in thread From: Chris Morgan @ 2026-10-27 13:17 UTC (permalink / raw) To: linux-pm Cc: devicetree, linux-rockchip, heiko, conor+dt, krzk+dt, robh, sre, t.schramm, Chris Morgan From: Chris Morgan <macromorgan@hotmail.com> Add support for the Cellwise cw2217 battery gauge to the Anbernic Vita-Pro. Signed-off-by: Chris Morgan <macromorgan@hotmail.com> --- .../rockchip/rk3576-anbernic-rg-vita-pro.dts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts b/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts index e66803b5399c..5c61467eaa03 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts @@ -954,7 +954,26 @@ hym8563: rtc@51 { wakeup-source; }; - /* cellwise,cw221X battery manager at 0x64 */ + cw2217: charger@64 { + compatible = "cellwise,cw2217"; + reg = <0x64>; + cellwise,battery-profile = /bits/ 8 < + 0x5a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 + 0xab 0xb5 0xae 0xc0 0xa9 0xa4 0xcf 0xaf + 0x96 0xff 0xff 0xd7 0x9a 0x83 0x70 0x69 + 0x65 0x65 0x65 0xb8 0x81 0xd9 0xaf 0xd9 + 0xd2 0xce 0xcd 0xcb 0xca 0xc7 0xc8 0xc5 + 0xc2 0xba 0xa8 0x96 0x8a 0x85 0x7d 0x7a + 0x7f 0x88 0x92 0x93 0x94 0xd3 0xff 0x5f + 0x20 0x00 0xab 0x10 0x00 0x91 0x68 0x00 + 0x00 0x00 0x64 0x3c 0xa0 0x89 0x00 0x00 + 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xb2 + >; + monitored-battery = <&battery>; + power-supplies = <&sgm41542>; + shunt-resistor-micro-ohms = <2000000>; + status = "okay"; + }; }; &mipidcphy { -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-27 15:57 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-27 13:17 [PATCH 0/3] Add Cellwise CW2217 Chris Morgan 2026-10-27 13:17 ` [PATCH 1/3] dt-bindings: power: cw2015: add cw2215, cw2217, and cw2218 Chris Morgan 2026-10-27 15:57 ` Krzysztof Kozlowski 2026-10-27 13:17 ` [PATCH 2/3] power: supply: cw2217: Add support for Cellwise cw2217 Chris Morgan 2026-10-27 13:34 ` Shawn Lin 2026-10-27 15:46 ` Chris Morgan 2026-10-27 13:17 ` [PATCH 3/3] arm64: dts: rockchip: Add Battery Gauge to Anbernic Vita-Pro Chris Morgan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox