xref: /linux/drivers/power/supply/chagall-battery.c (revision cbae17630954cb89f2bdf5bbadfd3aa81ea67283)
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