1fb24ccfbSArtur Rojek // SPDX-License-Identifier: GPL-2.0
2fb24ccfbSArtur Rojek /*
3fb24ccfbSArtur Rojek  * Battery driver for the Ingenic JZ47xx SoCs
4fb24ccfbSArtur Rojek  * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu>
5fb24ccfbSArtur Rojek  *
6fb24ccfbSArtur Rojek  * based on drivers/power/supply/jz4740-battery.c
7fb24ccfbSArtur Rojek  */
8fb24ccfbSArtur Rojek 
9fb24ccfbSArtur Rojek #include <linux/iio/consumer.h>
10fb24ccfbSArtur Rojek #include <linux/module.h>
11fb24ccfbSArtur Rojek #include <linux/of.h>
12fb24ccfbSArtur Rojek #include <linux/platform_device.h>
13fb24ccfbSArtur Rojek #include <linux/power_supply.h>
14fb24ccfbSArtur Rojek #include <linux/property.h>
15fb24ccfbSArtur Rojek 
16fb24ccfbSArtur Rojek struct ingenic_battery {
17fb24ccfbSArtur Rojek 	struct device *dev;
18fb24ccfbSArtur Rojek 	struct iio_channel *channel;
19fb24ccfbSArtur Rojek 	struct power_supply_desc desc;
20fb24ccfbSArtur Rojek 	struct power_supply *battery;
21*25fd3303SLinus Walleij 	struct power_supply_battery_info *info;
22fb24ccfbSArtur Rojek };
23fb24ccfbSArtur Rojek 
ingenic_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)24fb24ccfbSArtur Rojek static int ingenic_battery_get_property(struct power_supply *psy,
25fb24ccfbSArtur Rojek 					enum power_supply_property psp,
26fb24ccfbSArtur Rojek 					union power_supply_propval *val)
27fb24ccfbSArtur Rojek {
28fb24ccfbSArtur Rojek 	struct ingenic_battery *bat = power_supply_get_drvdata(psy);
29*25fd3303SLinus Walleij 	struct power_supply_battery_info *info = bat->info;
30fb24ccfbSArtur Rojek 	int ret;
31fb24ccfbSArtur Rojek 
32fb24ccfbSArtur Rojek 	switch (psp) {
33fb24ccfbSArtur Rojek 	case POWER_SUPPLY_PROP_HEALTH:
34fb24ccfbSArtur Rojek 		ret = iio_read_channel_processed(bat->channel, &val->intval);
35fb24ccfbSArtur Rojek 		val->intval *= 1000;
36fb24ccfbSArtur Rojek 		if (val->intval < info->voltage_min_design_uv)
37fb24ccfbSArtur Rojek 			val->intval = POWER_SUPPLY_HEALTH_DEAD;
38fb24ccfbSArtur Rojek 		else if (val->intval > info->voltage_max_design_uv)
39fb24ccfbSArtur Rojek 			val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
40fb24ccfbSArtur Rojek 		else
41fb24ccfbSArtur Rojek 			val->intval = POWER_SUPPLY_HEALTH_GOOD;
42fb24ccfbSArtur Rojek 		return ret;
43fb24ccfbSArtur Rojek 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
44fb24ccfbSArtur Rojek 		ret = iio_read_channel_processed(bat->channel, &val->intval);
45fb24ccfbSArtur Rojek 		val->intval *= 1000;
46fb24ccfbSArtur Rojek 		return ret;
47fb24ccfbSArtur Rojek 	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
48fb24ccfbSArtur Rojek 		val->intval = info->voltage_min_design_uv;
49fb24ccfbSArtur Rojek 		return 0;
50fb24ccfbSArtur Rojek 	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
51fb24ccfbSArtur Rojek 		val->intval = info->voltage_max_design_uv;
52fb24ccfbSArtur Rojek 		return 0;
53fb24ccfbSArtur Rojek 	default:
54fb24ccfbSArtur Rojek 		return -EINVAL;
551e625fe6STom Rix 	}
56fb24ccfbSArtur Rojek }
57fb24ccfbSArtur Rojek 
58fb24ccfbSArtur Rojek /* Set the most appropriate IIO channel voltage reference scale
59fb24ccfbSArtur Rojek  * based on the battery's max voltage.
60fb24ccfbSArtur Rojek  */
ingenic_battery_set_scale(struct ingenic_battery * bat)61fb24ccfbSArtur Rojek static int ingenic_battery_set_scale(struct ingenic_battery *bat)
62fb24ccfbSArtur Rojek {
63fb24ccfbSArtur Rojek 	const int *scale_raw;
64fb24ccfbSArtur Rojek 	int scale_len, scale_type, best_idx = -1, best_mV, max_raw, i, ret;
65fb24ccfbSArtur Rojek 	u64 max_mV;
66fb24ccfbSArtur Rojek 
67fb24ccfbSArtur Rojek 	ret = iio_read_max_channel_raw(bat->channel, &max_raw);
68fb24ccfbSArtur Rojek 	if (ret) {
69fb24ccfbSArtur Rojek 		dev_err(bat->dev, "Unable to read max raw channel value\n");
70fb24ccfbSArtur Rojek 		return ret;
71fb24ccfbSArtur Rojek 	}
72fb24ccfbSArtur Rojek 
73fb24ccfbSArtur Rojek 	ret = iio_read_avail_channel_attribute(bat->channel, &scale_raw,
74fb24ccfbSArtur Rojek 					       &scale_type, &scale_len,
75fb24ccfbSArtur Rojek 					       IIO_CHAN_INFO_SCALE);
76fb24ccfbSArtur Rojek 	if (ret < 0) {
77fb24ccfbSArtur Rojek 		dev_err(bat->dev, "Unable to read channel avail scale\n");
78fb24ccfbSArtur Rojek 		return ret;
79fb24ccfbSArtur Rojek 	}
80fb24ccfbSArtur Rojek 	if (ret != IIO_AVAIL_LIST || scale_type != IIO_VAL_FRACTIONAL_LOG2)
81fb24ccfbSArtur Rojek 		return -EINVAL;
82fb24ccfbSArtur Rojek 
83*25fd3303SLinus Walleij 	max_mV = bat->info->voltage_max_design_uv / 1000;
84fb24ccfbSArtur Rojek 
85fb24ccfbSArtur Rojek 	for (i = 0; i < scale_len; i += 2) {
86fb24ccfbSArtur Rojek 		u64 scale_mV = (max_raw * scale_raw[i]) >> scale_raw[i + 1];
87fb24ccfbSArtur Rojek 
88fb24ccfbSArtur Rojek 		if (scale_mV < max_mV)
89fb24ccfbSArtur Rojek 			continue;
90fb24ccfbSArtur Rojek 
91fb24ccfbSArtur Rojek 		if (best_idx >= 0 && scale_mV > best_mV)
92fb24ccfbSArtur Rojek 			continue;
93fb24ccfbSArtur Rojek 
94fb24ccfbSArtur Rojek 		best_mV = scale_mV;
95fb24ccfbSArtur Rojek 		best_idx = i;
96fb24ccfbSArtur Rojek 	}
97fb24ccfbSArtur Rojek 
98fb24ccfbSArtur Rojek 	if (best_idx < 0) {
99fb24ccfbSArtur Rojek 		dev_err(bat->dev, "Unable to find matching voltage scale\n");
100fb24ccfbSArtur Rojek 		return -EINVAL;
101fb24ccfbSArtur Rojek 	}
102fb24ccfbSArtur Rojek 
10386b9182dSPaul Cercueil 	/* Only set scale if there is more than one (fractional) entry */
10486b9182dSPaul Cercueil 	if (scale_len > 2) {
10586b9182dSPaul Cercueil 		ret = iio_write_channel_attribute(bat->channel,
106fb24ccfbSArtur Rojek 						  scale_raw[best_idx],
107fb24ccfbSArtur Rojek 						  scale_raw[best_idx + 1],
108fb24ccfbSArtur Rojek 						  IIO_CHAN_INFO_SCALE);
10986b9182dSPaul Cercueil 		if (ret)
11086b9182dSPaul Cercueil 			return ret;
11186b9182dSPaul Cercueil 	}
11286b9182dSPaul Cercueil 
11386b9182dSPaul Cercueil 	return 0;
114fb24ccfbSArtur Rojek }
115fb24ccfbSArtur Rojek 
116fb24ccfbSArtur Rojek static enum power_supply_property ingenic_battery_properties[] = {
117fb24ccfbSArtur Rojek 	POWER_SUPPLY_PROP_HEALTH,
118fb24ccfbSArtur Rojek 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
119fb24ccfbSArtur Rojek 	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
120fb24ccfbSArtur Rojek 	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
121fb24ccfbSArtur Rojek };
122fb24ccfbSArtur Rojek 
ingenic_battery_probe(struct platform_device * pdev)123fb24ccfbSArtur Rojek static int ingenic_battery_probe(struct platform_device *pdev)
124fb24ccfbSArtur Rojek {
125fb24ccfbSArtur Rojek 	struct device *dev = &pdev->dev;
126fb24ccfbSArtur Rojek 	struct ingenic_battery *bat;
127fb24ccfbSArtur Rojek 	struct power_supply_config psy_cfg = {};
128fb24ccfbSArtur Rojek 	struct power_supply_desc *desc;
129fb24ccfbSArtur Rojek 	int ret;
130fb24ccfbSArtur Rojek 
131fb24ccfbSArtur Rojek 	bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL);
132fb24ccfbSArtur Rojek 	if (!bat)
133fb24ccfbSArtur Rojek 		return -ENOMEM;
134fb24ccfbSArtur Rojek 
135fb24ccfbSArtur Rojek 	bat->dev = dev;
136fb24ccfbSArtur Rojek 	bat->channel = devm_iio_channel_get(dev, "battery");
137fb24ccfbSArtur Rojek 	if (IS_ERR(bat->channel))
138fb24ccfbSArtur Rojek 		return PTR_ERR(bat->channel);
139fb24ccfbSArtur Rojek 
140fb24ccfbSArtur Rojek 	desc = &bat->desc;
141fb24ccfbSArtur Rojek 	desc->name = "jz-battery";
142fb24ccfbSArtur Rojek 	desc->type = POWER_SUPPLY_TYPE_BATTERY;
143fb24ccfbSArtur Rojek 	desc->properties = ingenic_battery_properties;
144fb24ccfbSArtur Rojek 	desc->num_properties = ARRAY_SIZE(ingenic_battery_properties);
145fb24ccfbSArtur Rojek 	desc->get_property = ingenic_battery_get_property;
146fb24ccfbSArtur Rojek 	psy_cfg.drv_data = bat;
147fb24ccfbSArtur Rojek 	psy_cfg.of_node = dev->of_node;
148fb24ccfbSArtur Rojek 
149fb24ccfbSArtur Rojek 	bat->battery = devm_power_supply_register(dev, desc, &psy_cfg);
15031873dc2SKrzysztof Kozlowski 	if (IS_ERR(bat->battery))
15131873dc2SKrzysztof Kozlowski 		return dev_err_probe(dev, PTR_ERR(bat->battery),
15231873dc2SKrzysztof Kozlowski 				     "Unable to register battery\n");
153fb24ccfbSArtur Rojek 
154fb24ccfbSArtur Rojek 	ret = power_supply_get_battery_info(bat->battery, &bat->info);
155fb24ccfbSArtur Rojek 	if (ret) {
156fb24ccfbSArtur Rojek 		dev_err(dev, "Unable to get battery info: %d\n", ret);
157fb24ccfbSArtur Rojek 		return ret;
158fb24ccfbSArtur Rojek 	}
159*25fd3303SLinus Walleij 	if (bat->info->voltage_min_design_uv < 0) {
160fb24ccfbSArtur Rojek 		dev_err(dev, "Unable to get voltage min design\n");
161*25fd3303SLinus Walleij 		return bat->info->voltage_min_design_uv;
162fb24ccfbSArtur Rojek 	}
163*25fd3303SLinus Walleij 	if (bat->info->voltage_max_design_uv < 0) {
164fb24ccfbSArtur Rojek 		dev_err(dev, "Unable to get voltage max design\n");
165*25fd3303SLinus Walleij 		return bat->info->voltage_max_design_uv;
166fb24ccfbSArtur Rojek 	}
167fb24ccfbSArtur Rojek 
168fb24ccfbSArtur Rojek 	return ingenic_battery_set_scale(bat);
169fb24ccfbSArtur Rojek }
170fb24ccfbSArtur Rojek 
171fb24ccfbSArtur Rojek #ifdef CONFIG_OF
172fb24ccfbSArtur Rojek static const struct of_device_id ingenic_battery_of_match[] = {
173fb24ccfbSArtur Rojek 	{ .compatible = "ingenic,jz4740-battery", },
174fb24ccfbSArtur Rojek 	{ },
175fb24ccfbSArtur Rojek };
176fb24ccfbSArtur Rojek MODULE_DEVICE_TABLE(of, ingenic_battery_of_match);
177fb24ccfbSArtur Rojek #endif
178fb24ccfbSArtur Rojek 
179fb24ccfbSArtur Rojek static struct platform_driver ingenic_battery_driver = {
180fb24ccfbSArtur Rojek 	.driver = {
181fb24ccfbSArtur Rojek 		.name = "ingenic-battery",
182fb24ccfbSArtur Rojek 		.of_match_table = of_match_ptr(ingenic_battery_of_match),
183fb24ccfbSArtur Rojek 	},
184fb24ccfbSArtur Rojek 	.probe = ingenic_battery_probe,
185fb24ccfbSArtur Rojek };
186fb24ccfbSArtur Rojek module_platform_driver(ingenic_battery_driver);
187fb24ccfbSArtur Rojek 
188fb24ccfbSArtur Rojek MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs");
189fb24ccfbSArtur Rojek MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
190fb24ccfbSArtur Rojek MODULE_LICENSE("GPL");
191