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
chagall_led_set_brightness_amber(struct led_classdev * led,enum led_brightness brightness)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
chagall_led_set_brightness_white(struct led_classdev * led,enum led_brightness brightness)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
chagall_battery_get_value(struct chagall_battery_data * cg,enum power_supply_property psp,u32 * val)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
chagall_battery_get_status(u32 status_reg)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
chagall_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
chagall_battery_poll_work(struct work_struct * work)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
chagall_battery_probe(struct i2c_client * client)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
chagall_battery_suspend(struct device * dev)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
chagall_battery_resume(struct device * dev)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");
292