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