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