Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power: supply: Add driver for Pegatron Chagall battery

The Pegatron Chagall is an Android tablet utilizing a customized Cypress
CG7153AM microcontroller (MCU) as its battery fuel gauge. It supports a
single-cell battery and features a dual-color charging LED.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Link: https://lore.kernel.org/r/20250429061803.9581-4-clamor95@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Svyatoslav Ryhel and committed by
Sebastian Reichel
fbc1d056 615a8d9d

+304
+12
drivers/power/supply/Kconfig
··· 107 107 Say Y here to enable support for power supply provided by 108 108 Active-semi ActivePath ACT8945A charger. 109 109 110 + config BATTERY_CHAGALL 111 + tristate "Pegatron Chagall battery driver" 112 + depends on I2C 113 + depends on LEDS_CLASS 114 + help 115 + Say Y to include support for Cypress CG7153AM IC based battery 116 + fuel gauge with custom firmware found in Pegatron Chagall based 117 + tablet line. 118 + 119 + This driver can also be built as a module. If so, the module will be 120 + called chagall-battery. 121 + 110 122 config BATTERY_CPCAP 111 123 tristate "Motorola CPCAP PMIC battery driver" 112 124 depends on MFD_CPCAP && IIO
+1
drivers/power/supply/Makefile
··· 23 23 obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o 24 24 obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o 25 25 obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o 26 + obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o 26 27 obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o 27 28 obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o 28 29 obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
+291
drivers/power/supply/chagall-battery.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + #include <linux/array_size.h> 4 + #include <linux/delay.h> 5 + #include <linux/devm-helpers.h> 6 + #include <linux/err.h> 7 + #include <linux/i2c.h> 8 + #include <linux/leds.h> 9 + #include <linux/mod_devicetable.h> 10 + #include <linux/module.h> 11 + #include <linux/power_supply.h> 12 + #include <linux/regmap.h> 13 + 14 + #define CHAGALL_REG_LED_AMBER 0x60 15 + #define CHAGALL_REG_LED_WHITE 0x70 16 + #define CHAGALL_REG_BATTERY_TEMPERATURE 0xa2 17 + #define CHAGALL_REG_BATTERY_VOLTAGE 0xa4 18 + #define CHAGALL_REG_BATTERY_CURRENT 0xa6 19 + #define CHAGALL_REG_BATTERY_CAPACITY 0xa8 20 + #define CHAGALL_REG_BATTERY_CHARGING_CURRENT 0xaa 21 + #define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE 0xac 22 + #define CHAGALL_REG_BATTERY_STATUS 0xae 23 + #define BATTERY_DISCHARGING BIT(6) 24 + #define BATTERY_FULL_CHARGED BIT(5) 25 + #define BATTERY_FULL_DISCHARGED BIT(4) 26 + #define CHAGALL_REG_BATTERY_REMAIN_CAPACITY 0xb0 27 + #define CHAGALL_REG_BATTERY_FULL_CAPACITY 0xb2 28 + #define CHAGALL_REG_MAX_COUNT 0xb4 29 + 30 + #define CHAGALL_BATTERY_DATA_REFRESH 5000 31 + #define TEMP_CELSIUS_OFFSET 2731 32 + 33 + static const struct regmap_config chagall_battery_regmap_config = { 34 + .reg_bits = 8, 35 + .val_bits = 8, 36 + .max_register = CHAGALL_REG_MAX_COUNT, 37 + .reg_format_endian = REGMAP_ENDIAN_LITTLE, 38 + .val_format_endian = REGMAP_ENDIAN_LITTLE, 39 + }; 40 + 41 + struct chagall_battery_data { 42 + struct regmap *regmap; 43 + struct led_classdev amber_led; 44 + struct led_classdev white_led; 45 + struct power_supply *battery; 46 + struct delayed_work poll_work; 47 + u16 last_state; 48 + }; 49 + 50 + static void chagall_led_set_brightness_amber(struct led_classdev *led, 51 + enum led_brightness brightness) 52 + { 53 + struct chagall_battery_data *cg = 54 + container_of(led, struct chagall_battery_data, amber_led); 55 + 56 + regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness); 57 + } 58 + 59 + static void chagall_led_set_brightness_white(struct led_classdev *led, 60 + enum led_brightness brightness) 61 + { 62 + struct chagall_battery_data *cg = 63 + container_of(led, struct chagall_battery_data, white_led); 64 + 65 + regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness); 66 + } 67 + 68 + static const enum power_supply_property chagall_battery_properties[] = { 69 + POWER_SUPPLY_PROP_STATUS, 70 + POWER_SUPPLY_PROP_PRESENT, 71 + POWER_SUPPLY_PROP_VOLTAGE_NOW, 72 + POWER_SUPPLY_PROP_VOLTAGE_MAX, 73 + POWER_SUPPLY_PROP_CURRENT_NOW, 74 + POWER_SUPPLY_PROP_CURRENT_MAX, 75 + POWER_SUPPLY_PROP_CAPACITY, 76 + POWER_SUPPLY_PROP_TEMP, 77 + POWER_SUPPLY_PROP_CHARGE_FULL, 78 + POWER_SUPPLY_PROP_CHARGE_NOW, 79 + }; 80 + 81 + static const unsigned int chagall_battery_prop_offs[] = { 82 + [POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS, 83 + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE, 84 + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE, 85 + [POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT, 86 + [POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT, 87 + [POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY, 88 + [POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE, 89 + [POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY, 90 + [POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY, 91 + }; 92 + 93 + static int chagall_battery_get_value(struct chagall_battery_data *cg, 94 + enum power_supply_property psp, u32 *val) 95 + { 96 + if (psp >= ARRAY_SIZE(chagall_battery_prop_offs)) 97 + return -EINVAL; 98 + if (!chagall_battery_prop_offs[psp]) 99 + return -EINVAL; 100 + 101 + /* Battery data is stored in 2 consecutive registers with little-endian */ 102 + return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2); 103 + } 104 + 105 + static int chagall_battery_get_status(u32 status_reg) 106 + { 107 + if (status_reg & BATTERY_FULL_CHARGED) 108 + return POWER_SUPPLY_STATUS_FULL; 109 + else if (status_reg & BATTERY_DISCHARGING) 110 + return POWER_SUPPLY_STATUS_DISCHARGING; 111 + else 112 + return POWER_SUPPLY_STATUS_CHARGING; 113 + } 114 + 115 + static int chagall_battery_get_property(struct power_supply *psy, 116 + enum power_supply_property psp, 117 + union power_supply_propval *val) 118 + { 119 + struct chagall_battery_data *cg = power_supply_get_drvdata(psy); 120 + int ret; 121 + 122 + switch (psp) { 123 + case POWER_SUPPLY_PROP_PRESENT: 124 + val->intval = 1; 125 + break; 126 + 127 + default: 128 + ret = chagall_battery_get_value(cg, psp, &val->intval); 129 + if (ret) 130 + return ret; 131 + 132 + switch (psp) { 133 + case POWER_SUPPLY_PROP_TEMP: 134 + val->intval -= TEMP_CELSIUS_OFFSET; 135 + break; 136 + 137 + case POWER_SUPPLY_PROP_VOLTAGE_MAX: 138 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 139 + case POWER_SUPPLY_PROP_CURRENT_MAX: 140 + case POWER_SUPPLY_PROP_CURRENT_NOW: 141 + case POWER_SUPPLY_PROP_CHARGE_FULL: 142 + case POWER_SUPPLY_PROP_CHARGE_NOW: 143 + val->intval *= 1000; 144 + break; 145 + 146 + case POWER_SUPPLY_PROP_STATUS: 147 + val->intval = chagall_battery_get_status(val->intval); 148 + break; 149 + 150 + default: 151 + break; 152 + } 153 + 154 + break; 155 + } 156 + 157 + return 0; 158 + } 159 + 160 + static void chagall_battery_poll_work(struct work_struct *work) 161 + { 162 + struct chagall_battery_data *cg = 163 + container_of(work, struct chagall_battery_data, poll_work.work); 164 + u32 state; 165 + int ret; 166 + 167 + ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state); 168 + if (ret) 169 + return; 170 + 171 + state = chagall_battery_get_status(state); 172 + 173 + if (cg->last_state != state) { 174 + cg->last_state = state; 175 + power_supply_changed(cg->battery); 176 + } 177 + 178 + /* continuously send uevent notification */ 179 + schedule_delayed_work(&cg->poll_work, 180 + msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); 181 + } 182 + 183 + static const struct power_supply_desc chagall_battery_desc = { 184 + .name = "chagall-battery", 185 + .type = POWER_SUPPLY_TYPE_BATTERY, 186 + .properties = chagall_battery_properties, 187 + .num_properties = ARRAY_SIZE(chagall_battery_properties), 188 + .get_property = chagall_battery_get_property, 189 + .external_power_changed = power_supply_changed, 190 + }; 191 + 192 + static int chagall_battery_probe(struct i2c_client *client) 193 + { 194 + struct chagall_battery_data *cg; 195 + struct device *dev = &client->dev; 196 + struct power_supply_config cfg = { }; 197 + int ret; 198 + 199 + cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL); 200 + if (!cg) 201 + return -ENOMEM; 202 + 203 + cfg.drv_data = cg; 204 + cfg.fwnode = dev_fwnode(dev); 205 + 206 + i2c_set_clientdata(client, cg); 207 + 208 + cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config); 209 + if (IS_ERR(cg->regmap)) 210 + return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n"); 211 + 212 + cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN; 213 + cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg); 214 + if (IS_ERR(cg->battery)) 215 + return dev_err_probe(dev, PTR_ERR(cg->battery), 216 + "failed to register power supply\n"); 217 + 218 + cg->amber_led.name = "power::amber"; 219 + cg->amber_led.max_brightness = 1; 220 + cg->amber_led.flags = LED_CORE_SUSPENDRESUME; 221 + cg->amber_led.brightness_set = chagall_led_set_brightness_amber; 222 + cg->amber_led.default_trigger = "chagall-battery-charging"; 223 + 224 + ret = devm_led_classdev_register(dev, &cg->amber_led); 225 + if (ret) 226 + return dev_err_probe(dev, ret, "failed to register amber LED\n"); 227 + 228 + cg->white_led.name = "power::white"; 229 + cg->white_led.max_brightness = 1; 230 + cg->white_led.flags = LED_CORE_SUSPENDRESUME; 231 + cg->white_led.brightness_set = chagall_led_set_brightness_white; 232 + cg->white_led.default_trigger = "chagall-battery-full"; 233 + 234 + ret = devm_led_classdev_register(dev, &cg->white_led); 235 + if (ret) 236 + return dev_err_probe(dev, ret, "failed to register white LED\n"); 237 + 238 + led_set_brightness(&cg->amber_led, LED_OFF); 239 + led_set_brightness(&cg->white_led, LED_OFF); 240 + 241 + ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work); 242 + if (ret) 243 + return ret; 244 + 245 + schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); 246 + 247 + return 0; 248 + } 249 + 250 + static int __maybe_unused chagall_battery_suspend(struct device *dev) 251 + { 252 + struct i2c_client *client = to_i2c_client(dev); 253 + struct chagall_battery_data *cg = i2c_get_clientdata(client); 254 + 255 + cancel_delayed_work_sync(&cg->poll_work); 256 + 257 + return 0; 258 + } 259 + 260 + static int __maybe_unused chagall_battery_resume(struct device *dev) 261 + { 262 + struct i2c_client *client = to_i2c_client(dev); 263 + struct chagall_battery_data *cg = i2c_get_clientdata(client); 264 + 265 + schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH)); 266 + 267 + return 0; 268 + } 269 + 270 + static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops, 271 + chagall_battery_suspend, chagall_battery_resume); 272 + 273 + static const struct of_device_id chagall_of_match[] = { 274 + { .compatible = "pegatron,chagall-ec" }, 275 + { } 276 + }; 277 + MODULE_DEVICE_TABLE(of, chagall_of_match); 278 + 279 + static struct i2c_driver chagall_battery_driver = { 280 + .driver = { 281 + .name = "chagall-battery", 282 + .pm = &chagall_battery_pm_ops, 283 + .of_match_table = chagall_of_match, 284 + }, 285 + .probe = chagall_battery_probe, 286 + }; 287 + module_i2c_driver(chagall_battery_driver); 288 + 289 + MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); 290 + MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver"); 291 + MODULE_LICENSE("GPL");