blob: 592b68d5115a585ee7666c676d7866107032b145 [file] [log] [blame]
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -07001/*
2 * Battery driver for a Greybus module.
3 *
4 * Copyright 2014 Google Inc.
5 *
6 * Released under the GPLv2 only.
7 */
8
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/slab.h>
12#include <linux/power_supply.h>
13#include "greybus.h"
14
15struct gb_battery {
16 struct power_supply bat;
Greg Kroah-Hartmand47aa762014-09-07 15:54:24 -070017 // FIXME
18 // we will want to keep the battery stats in here as we will be getting
19 // updates from the SVC "on the fly" so we don't have to always go ask
20 // the battery for some information. Hopefully...
Greg Kroah-Hartman2bb7eae2014-10-20 15:24:57 +080021 struct gb_connection *connection;
22 u8 version_major;
23 u8 version_minor;
24
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -070025};
26#define to_gb_battery(x) container_of(x, struct gb_battery, bat)
27
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +080028/* Version of the Greybus battery protocol we support */
29#define GB_BATTERY_VERSION_MAJOR 0x00
30#define GB_BATTERY_VERSION_MINOR 0x01
31
32/* Greybus battery request types */
33#define GB_BATTERY_TYPE_INVALID 0x00
34#define GB_BATTERY_TYPE_PROTOCOL_VERSION 0x01
35#define GB_BATTERY_TYPE_TECHNOLOGY 0x02
36#define GB_BATTERY_TYPE_STATUS 0x03
37#define GB_BATTERY_TYPE_MAX_VOLTAGE 0x04
38#define GB_BATTERY_TYPE_CAPACITY 0x05
39#define GB_BATTERY_TYPE_TEMPERATURE 0x06
40#define GB_BATTERY_TYPE_VOLTAGE 0x07
41
42struct gb_battery_proto_version_response {
43 __u8 status;
44 __u8 major;
45 __u8 minor;
46};
47
48/* Should match up with battery types in linux/power_supply.h */
49#define GB_BATTERY_TECH_UNKNOWN 0x0000
50#define GB_BATTERY_TECH_NiMH 0x0001
51#define GB_BATTERY_TECH_LION 0x0002
52#define GB_BATTERY_TECH_LIPO 0x0003
53#define GB_BATTERY_TECH_LiFe 0x0004
54#define GB_BATTERY_TECH_NiCd 0x0005
55#define GB_BATTERY_TECH_LiMn 0x0006
56
57struct gb_battery_technology_request {
58 __u8 status;
59 __le32 technology;
60};
61
62/* Should match up with battery status in linux/power_supply.h */
63#define GB_BATTERY_STATUS_UNKNOWN 0x0000
64#define GB_BATTERY_STATUS_CHARGING 0x0001
65#define GB_BATTERY_STATUS_DISCHARGING 0x0002
66#define GB_BATTERY_STATUS_NOT_CHARGING 0x0003
67#define GB_BATTERY_STATUS_FULL 0x0004
68
69struct gb_battery_status_request {
70 __u8 status;
71 __le16 battery_status;
72};
73
74struct gb_battery_max_voltage_request {
75 __u8 status;
76 __le32 max_voltage;
77};
78
79struct gb_battery_capacity_request {
80 __u8 status;
81 __le32 capacity;
82};
83
84struct gb_battery_temperature_request {
85 __u8 status;
86 __le32 temperature;
87};
88
89struct gb_battery_voltage_request {
90 __u8 status;
91 __le32 voltage;
92};
93
94
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -070095static const struct greybus_module_id id_table[] = {
96 { GREYBUS_DEVICE(0x42, 0x42) }, /* make shit up */
97 { }, /* terminating NULL entry */
98};
99
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800100static int battery_operation(struct gb_battery *gb, int type,
101 void *response, int response_size)
102{
103 struct gb_connection *connection = gb->connection;
104 struct gb_operation *operation;
105 struct gb_battery_technology_request *fake_request;
106 u8 *local_response;
107 int ret;
108
109 local_response = kmalloc(response_size, GFP_KERNEL);
110 if (!local_response)
111 return -ENOMEM;
112
113 operation = gb_operation_create(connection, type, 0, response_size);
114 if (!operation) {
115 kfree(local_response);
116 return -ENOMEM;
117 }
118
119 /* Synchronous operation--no callback */
120 ret = gb_operation_request_send(operation, NULL);
121 if (ret) {
122 pr_err("version operation failed (%d)\n", ret);
123 goto out;
124 }
125
126 /*
127 * We only want to look at the status, and all requests have the same
128 * layout for where the status is, so cast this to a random request so
129 * we can see the status easier.
130 */
131 fake_request = (struct gb_battery_technology_request *)local_response;
132 if (fake_request->status) {
133 gb_connection_err(connection, "version response %hhu",
134 fake_request->status);
135 ret = -EIO;
136 } else {
137 /* Good request, so copy to the caller's buffer */
138 memcpy(response, local_response, response_size);
139 }
140out:
141 gb_operation_destroy(operation);
Greg Kroah-Hartmanc9346e12014-10-21 15:51:53 +0800142 kfree(local_response);
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800143
144 return ret;
145}
146
147/*
148 * This request only uses the connection field, and if successful,
149 * fills in the major and minor protocol version of the target.
150 */
151static int get_version(struct gb_battery *gb)
152{
153 struct gb_battery_proto_version_response version_request;
154 int retval;
155
156 retval = battery_operation(gb, GB_BATTERY_TYPE_PROTOCOL_VERSION,
157 &version_request, sizeof(version_request));
158 if (retval)
159 return retval;
160
161 if (version_request.major > GB_BATTERY_VERSION_MAJOR) {
162 pr_err("unsupported major version (%hhu > %hhu)\n",
163 version_request.major, GB_BATTERY_VERSION_MAJOR);
164 return -ENOTSUPP;
165 }
166
167 gb->version_major = version_request.major;
168 gb->version_minor = version_request.minor;
169 return 0;
170}
171
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800172static int get_tech(struct gb_battery *gb)
173{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800174 struct gb_battery_technology_request tech_request;
175 u32 technology;
176 int retval;
177
178 retval = battery_operation(gb, GB_BATTERY_TYPE_TECHNOLOGY,
179 &tech_request, sizeof(tech_request));
180 if (retval)
181 return retval;
182
183 /*
Greg Kroah-Hartman0369a452014-10-21 16:25:13 +0800184 * Map greybus values to power_supply values. Hopefully these are
185 * "identical" which should allow gcc to optomize the code away to
186 * nothing.
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800187 */
188 technology = le32_to_cpu(tech_request.technology);
Greg Kroah-Hartman0369a452014-10-21 16:25:13 +0800189 switch (technology) {
190 case GB_BATTERY_TECH_NiMH:
191 technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
192 break;
193 case GB_BATTERY_TECH_LION:
194 technology = POWER_SUPPLY_TECHNOLOGY_LION;
195 break;
196 case GB_BATTERY_TECH_LIPO:
197 technology = POWER_SUPPLY_TECHNOLOGY_LIPO;
198 break;
199 case GB_BATTERY_TECH_LiFe:
200 technology = POWER_SUPPLY_TECHNOLOGY_LiFe;
201 break;
202 case GB_BATTERY_TECH_NiCd:
203 technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
204 break;
205 case GB_BATTERY_TECH_LiMn:
206 technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
207 break;
208 case GB_BATTERY_TECH_UNKNOWN:
209 default:
210 technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
211 break;
212 }
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800213 return technology;
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800214}
215
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700216static int get_status(struct gb_battery *gb)
217{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800218 struct gb_battery_status_request status_request;
219 u16 battery_status;
220 int retval;
221
222 retval = battery_operation(gb, GB_BATTERY_TYPE_STATUS,
223 &status_request, sizeof(status_request));
224 if (retval)
225 return retval;
226
227 /*
Greg Kroah-Hartman0369a452014-10-21 16:25:13 +0800228 * Map greybus values to power_supply values. Hopefully these are
229 * "identical" which should allow gcc to optomize the code away to
230 * nothing.
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800231 */
232 battery_status = le16_to_cpu(status_request.battery_status);
Greg Kroah-Hartman0369a452014-10-21 16:25:13 +0800233 switch (battery_status) {
234 case GB_BATTERY_STATUS_CHARGING:
235 battery_status = POWER_SUPPLY_STATUS_CHARGING;
236 break;
237 case GB_BATTERY_STATUS_DISCHARGING:
238 battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
239 break;
240 case GB_BATTERY_STATUS_NOT_CHARGING:
241 battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
242 break;
243 case GB_BATTERY_STATUS_FULL:
244 battery_status = POWER_SUPPLY_STATUS_FULL;
245 break;
246 case GB_BATTERY_STATUS_UNKNOWN:
247 default:
248 battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
249 break;
250 }
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800251 return battery_status;
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700252}
253
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800254static int get_max_voltage(struct gb_battery *gb)
255{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800256 struct gb_battery_max_voltage_request volt_request;
257 u32 max_voltage;
258 int retval;
259
260 retval = battery_operation(gb, GB_BATTERY_TYPE_MAX_VOLTAGE,
261 &volt_request, sizeof(volt_request));
262 if (retval)
263 return retval;
264
265 max_voltage = le32_to_cpu(volt_request.max_voltage);
266 return max_voltage;
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800267}
268
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700269static int get_capacity(struct gb_battery *gb)
270{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800271 struct gb_battery_capacity_request capacity_request;
272 u32 capacity;
273 int retval;
274
275 retval = battery_operation(gb, GB_BATTERY_TYPE_CAPACITY,
276 &capacity_request, sizeof(capacity_request));
277 if (retval)
278 return retval;
279
280 capacity = le32_to_cpu(capacity_request.capacity);
281 return capacity;
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700282}
283
284static int get_temp(struct gb_battery *gb)
285{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800286 struct gb_battery_temperature_request temp_request;
287 u32 temperature;
288 int retval;
289
290 retval = battery_operation(gb, GB_BATTERY_TYPE_TEMPERATURE,
291 &temp_request, sizeof(temp_request));
292 if (retval)
293 return retval;
294
295 temperature = le32_to_cpu(temp_request.temperature);
296 return temperature;
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700297}
298
299static int get_voltage(struct gb_battery *gb)
300{
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800301 struct gb_battery_voltage_request voltage_request;
302 u32 voltage;
303 int retval;
304
305 retval = battery_operation(gb, GB_BATTERY_TYPE_VOLTAGE,
306 &voltage_request, sizeof(voltage_request));
307 if (retval)
308 return retval;
309
310 voltage = le32_to_cpu(voltage_request.voltage);
311 return voltage;
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700312}
313
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700314static int get_property(struct power_supply *b,
315 enum power_supply_property psp,
316 union power_supply_propval *val)
317{
318 struct gb_battery *gb = to_gb_battery(b);
319
320 switch (psp) {
321 case POWER_SUPPLY_PROP_TECHNOLOGY:
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800322 val->intval = get_tech(gb);
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700323 break;
324
325 case POWER_SUPPLY_PROP_STATUS:
326 val->intval = get_status(gb);
327 break;
328
329 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
Greg Kroah-Hartman43789c32014-10-20 15:09:49 +0800330 val->intval = get_max_voltage(gb);
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700331 break;
332
333 case POWER_SUPPLY_PROP_CAPACITY:
334 val->intval = get_capacity(gb);
335 break;
336
337 case POWER_SUPPLY_PROP_TEMP:
338 val->intval = get_temp(gb);
339 break;
340
341 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
342 val->intval = get_voltage(gb);
343 break;
344
345 default:
346 return -EINVAL;
347 }
348
349 return 0;
350}
351
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700352// FIXME - verify this list, odds are some can be removed and others added.
353static enum power_supply_property battery_props[] = {
354 POWER_SUPPLY_PROP_TECHNOLOGY,
355 POWER_SUPPLY_PROP_STATUS,
356 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
357 POWER_SUPPLY_PROP_CAPACITY,
358 POWER_SUPPLY_PROP_TEMP,
359 POWER_SUPPLY_PROP_VOLTAGE_NOW,
360};
361
Greg Kroah-Hartman2bb7eae2014-10-20 15:24:57 +0800362int gb_battery_device_init(struct gb_connection *connection)
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700363{
364 struct gb_battery *gb;
365 struct power_supply *b;
366 int retval;
367
368 gb = kzalloc(sizeof(*gb), GFP_KERNEL);
369 if (!gb)
370 return -ENOMEM;
371
Greg Kroah-Hartman2bb7eae2014-10-20 15:24:57 +0800372 gb->connection = connection; // FIXME refcount!
Alex Elderfb305c32014-10-20 23:01:03 -0500373 connection->private = gb;
Greg Kroah-Hartman2bb7eae2014-10-20 15:24:57 +0800374
Greg Kroah-Hartmanc0855bf2014-10-21 14:31:24 +0800375 /* Check the version */
376 retval = get_version(gb);
377 if (retval) {
378 kfree(gb);
379 return retval;
380 }
381
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700382 b = &gb->bat;
383 // FIXME - get a better (i.e. unique) name
384 // FIXME - anything else needs to be set?
385 b->name = "gb_battery";
386 b->type = POWER_SUPPLY_TYPE_BATTERY,
387 b->properties = battery_props,
388 b->num_properties = ARRAY_SIZE(battery_props),
389 b->get_property = get_property,
390
Greg Kroah-Hartman2bb7eae2014-10-20 15:24:57 +0800391 retval = power_supply_register(&connection->interface->gmod->dev, b);
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700392 if (retval) {
393 kfree(gb);
394 return retval;
395 }
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700396
397 return 0;
398}
399
Alex Elder697e55d2014-10-20 23:01:04 -0500400void gb_battery_device_exit(struct gb_connection *connection)
401{
402 struct gb_battery *gb = connection->private;
403
404 power_supply_unregister(&gb->bat);
405 kfree(gb);
406}
407
Alex Eldere1e9dbd2014-10-01 21:54:11 -0500408void gb_battery_disconnect(struct gb_module *gmod)
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700409{
Alex Elder09c521d2014-10-20 10:27:57 -0500410#if 0
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700411 struct gb_battery *gb;
412
Alex Elder778c69c2014-09-22 19:19:03 -0500413 gb = gmod->gb_battery;
Alex Elder051fb042014-10-16 06:35:24 -0500414 if (!gb)
415 return;
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700416
417 power_supply_unregister(&gb->bat);
418
419 kfree(gb);
Alex Elder09c521d2014-10-20 10:27:57 -0500420#endif
Greg Kroah-Hartman33ea3a32014-09-07 15:39:34 -0700421}
422
423#if 0
424static struct greybus_driver battery_gb_driver = {
425 .probe = gb_battery_probe,
426 .disconnect = gb_battery_disconnect,
427 .id_table = id_table,
428};
429
430module_greybus_driver(battery_gb_driver);
431MODULE_LICENSE("GPL");
432MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");
433#endif