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