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