FILE
chagall-battery.c
File preview with syntax highlighting.
linux-6.19.14/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 33static 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 41struct 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 50static 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 59static 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 68static 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 81static 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 93static 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 105static 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 115static 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 160static 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 183static 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 192static 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 250static 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 260static 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 270static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops, 271 chagall_battery_suspend, chagall_battery_resume); 272 273static const struct of_device_id chagall_of_match[] = { 274 { .compatible = "pegatron,chagall-ec" }, 275 { } 276}; 277MODULE_DEVICE_TABLE(of, chagall_of_match); 278 279static 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}; 287module_i2c_driver(chagall_battery_driver); 288 289MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); 290MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver"); 291MODULE_LICENSE("GPL");
~/
Read-only file preview.