1 | /* |
2 | * Battery driver for LEGO MINDSTORMS EV3 |
3 | * |
4 | * Copyright (C) 2017 David Lechner <david@lechnology.com> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License version 2 as |
8 | * published by the Free Software Foundation. |
9 | |
10 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
11 | * kind, whether express or implied; without even the implied warranty |
12 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | */ |
15 | |
16 | #include <linux/delay.h> |
17 | #include <linux/err.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/iio/consumer.h> |
20 | #include <linux/iio/types.h> |
21 | #include <linux/kernel.h> |
22 | #include <linux/module.h> |
23 | #include <linux/mod_devicetable.h> |
24 | #include <linux/platform_device.h> |
25 | #include <linux/power_supply.h> |
26 | |
27 | struct lego_ev3_battery { |
28 | struct iio_channel *iio_v; |
29 | struct iio_channel *iio_i; |
30 | struct gpio_desc *rechargeable_gpio; |
31 | struct power_supply *psy; |
32 | int technology; |
33 | int v_max; |
34 | int v_min; |
35 | }; |
36 | |
37 | static int lego_ev3_battery_get_property(struct power_supply *psy, |
38 | enum power_supply_property psp, |
39 | union power_supply_propval *val) |
40 | { |
41 | struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); |
42 | int ret, val2; |
43 | |
44 | switch (psp) { |
45 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
46 | val->intval = batt->technology; |
47 | break; |
48 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
49 | /* battery voltage is iio channel * 2 + Vce of transistor */ |
50 | ret = iio_read_channel_processed(chan: batt->iio_v, val: &val->intval); |
51 | if (ret) |
52 | return ret; |
53 | |
54 | val->intval *= 2000; |
55 | val->intval += 50000; |
56 | |
57 | /* plus adjust for shunt resistor drop */ |
58 | ret = iio_read_channel_processed(chan: batt->iio_i, val: &val2); |
59 | if (ret) |
60 | return ret; |
61 | |
62 | val2 *= 1000; |
63 | val2 /= 15; |
64 | val->intval += val2; |
65 | break; |
66 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
67 | val->intval = batt->v_max; |
68 | break; |
69 | case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
70 | val->intval = batt->v_min; |
71 | break; |
72 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
73 | /* battery current is iio channel / 15 / 0.05 ohms */ |
74 | ret = iio_read_channel_processed(chan: batt->iio_i, val: &val->intval); |
75 | if (ret) |
76 | return ret; |
77 | |
78 | val->intval *= 20000; |
79 | val->intval /= 15; |
80 | break; |
81 | case POWER_SUPPLY_PROP_SCOPE: |
82 | val->intval = POWER_SUPPLY_SCOPE_SYSTEM; |
83 | break; |
84 | default: |
85 | return -EINVAL; |
86 | } |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int lego_ev3_battery_set_property(struct power_supply *psy, |
92 | enum power_supply_property psp, |
93 | const union power_supply_propval *val) |
94 | { |
95 | struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); |
96 | |
97 | switch (psp) { |
98 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
99 | /* |
100 | * Only allow changing technology from Unknown to NiMH. Li-ion |
101 | * batteries are automatically detected and should not be |
102 | * overridden. Rechargeable AA batteries, on the other hand, |
103 | * cannot be automatically detected, and so must be manually |
104 | * specified. This should only be set once during system init, |
105 | * so there is no mechanism to go back to Unknown. |
106 | */ |
107 | if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) |
108 | return -EINVAL; |
109 | switch (val->intval) { |
110 | case POWER_SUPPLY_TECHNOLOGY_NiMH: |
111 | batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; |
112 | batt->v_max = 7800000; |
113 | batt->v_min = 5400000; |
114 | break; |
115 | default: |
116 | return -EINVAL; |
117 | } |
118 | break; |
119 | default: |
120 | return -EINVAL; |
121 | } |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static int lego_ev3_battery_property_is_writeable(struct power_supply *psy, |
127 | enum power_supply_property psp) |
128 | { |
129 | struct lego_ev3_battery *batt = power_supply_get_drvdata(psy); |
130 | |
131 | return psp == POWER_SUPPLY_PROP_TECHNOLOGY && |
132 | batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN; |
133 | } |
134 | |
135 | static enum power_supply_property lego_ev3_battery_props[] = { |
136 | POWER_SUPPLY_PROP_TECHNOLOGY, |
137 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
138 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
139 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
140 | POWER_SUPPLY_PROP_CURRENT_NOW, |
141 | POWER_SUPPLY_PROP_SCOPE, |
142 | }; |
143 | |
144 | static const struct power_supply_desc lego_ev3_battery_desc = { |
145 | .name = "lego-ev3-battery" , |
146 | .type = POWER_SUPPLY_TYPE_BATTERY, |
147 | .properties = lego_ev3_battery_props, |
148 | .num_properties = ARRAY_SIZE(lego_ev3_battery_props), |
149 | .get_property = lego_ev3_battery_get_property, |
150 | .set_property = lego_ev3_battery_set_property, |
151 | .property_is_writeable = lego_ev3_battery_property_is_writeable, |
152 | }; |
153 | |
154 | static int lego_ev3_battery_probe(struct platform_device *pdev) |
155 | { |
156 | struct device *dev = &pdev->dev; |
157 | struct lego_ev3_battery *batt; |
158 | struct power_supply_config psy_cfg = {}; |
159 | int err; |
160 | |
161 | batt = devm_kzalloc(dev, size: sizeof(*batt), GFP_KERNEL); |
162 | if (!batt) |
163 | return -ENOMEM; |
164 | |
165 | platform_set_drvdata(pdev, data: batt); |
166 | |
167 | batt->iio_v = devm_iio_channel_get(dev, consumer_channel: "voltage" ); |
168 | err = PTR_ERR_OR_ZERO(ptr: batt->iio_v); |
169 | if (err) |
170 | return dev_err_probe(dev, err, |
171 | fmt: "Failed to get voltage iio channel\n" ); |
172 | |
173 | batt->iio_i = devm_iio_channel_get(dev, consumer_channel: "current" ); |
174 | err = PTR_ERR_OR_ZERO(ptr: batt->iio_i); |
175 | if (err) |
176 | return dev_err_probe(dev, err, |
177 | fmt: "Failed to get current iio channel\n" ); |
178 | |
179 | batt->rechargeable_gpio = devm_gpiod_get(dev, con_id: "rechargeable" , flags: GPIOD_IN); |
180 | err = PTR_ERR_OR_ZERO(ptr: batt->rechargeable_gpio); |
181 | if (err) |
182 | return dev_err_probe(dev, err, |
183 | fmt: "Failed to get rechargeable gpio\n" ); |
184 | |
185 | /* |
186 | * The rechargeable battery indication switch cannot be changed without |
187 | * removing the battery, so we only need to read it once. |
188 | */ |
189 | if (gpiod_get_value(desc: batt->rechargeable_gpio)) { |
190 | /* 2-cell Li-ion, 7.4V nominal */ |
191 | batt->technology = POWER_SUPPLY_TECHNOLOGY_LION; |
192 | batt->v_max = 84000000; |
193 | batt->v_min = 60000000; |
194 | } else { |
195 | /* 6x AA Alkaline, 9V nominal */ |
196 | batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; |
197 | batt->v_max = 90000000; |
198 | batt->v_min = 48000000; |
199 | } |
200 | |
201 | psy_cfg.of_node = pdev->dev.of_node; |
202 | psy_cfg.drv_data = batt; |
203 | |
204 | batt->psy = devm_power_supply_register(parent: dev, desc: &lego_ev3_battery_desc, |
205 | cfg: &psy_cfg); |
206 | err = PTR_ERR_OR_ZERO(ptr: batt->psy); |
207 | if (err) { |
208 | dev_err(dev, "failed to register power supply\n" ); |
209 | return err; |
210 | } |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | static const struct of_device_id of_lego_ev3_battery_match[] = { |
216 | { .compatible = "lego,ev3-battery" , }, |
217 | { } |
218 | }; |
219 | MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match); |
220 | |
221 | static struct platform_driver lego_ev3_battery_driver = { |
222 | .driver = { |
223 | .name = "lego-ev3-battery" , |
224 | .of_match_table = of_lego_ev3_battery_match, |
225 | }, |
226 | .probe = lego_ev3_battery_probe, |
227 | }; |
228 | module_platform_driver(lego_ev3_battery_driver); |
229 | |
230 | MODULE_LICENSE("GPL" ); |
231 | MODULE_AUTHOR("David Lechner <david@lechnology.com>" ); |
232 | MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver" ); |
233 | |