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