Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
nvec_power.c
Go to the documentation of this file.
1 /*
2  * nvec_power: power supply driver for a NVIDIA compliant embedded controller
3  *
4  * Copyright (C) 2011 The AC100 Kernel Team <[email protected]>
5  *
6  * Authors: Ilya Petrov <[email protected]>
7  * Marc Dietrich <[email protected]>
8  *
9  * This file is subject to the terms and conditions of the GNU General Public
10  * License. See the file "COPYING" in the main directory of this archive
11  * for more details.
12  *
13  */
14 
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/err.h>
18 #include <linux/power_supply.h>
19 #include <linux/slab.h>
20 #include <linux/workqueue.h>
21 #include <linux/delay.h>
22 
23 #include "nvec.h"
24 
25 struct nvec_power {
28  struct nvec_chip *nvec;
29  int on;
41  int bat_cap;
43  char bat_manu[30];
44  char bat_model[30];
45  char bat_type[30];
46 };
47 
48 enum {
63 };
64 
65 enum {
66  AC,
67  BAT,
68 };
69 
70 struct bat_response {
75  /* payload */
76  union {
77  char plc[30];
80  };
81 };
82 
83 static struct power_supply nvec_bat_psy;
84 static struct power_supply nvec_psy;
85 
86 static int nvec_power_notifier(struct notifier_block *nb,
87  unsigned long event_type, void *data)
88 {
89  struct nvec_power *power =
90  container_of(nb, struct nvec_power, notifier);
91  struct bat_response *res = (struct bat_response *)data;
92 
93  if (event_type != NVEC_SYS)
94  return NOTIFY_DONE;
95 
96  if (res->sub_type == 0) {
97  if (power->on != res->plu) {
98  power->on = res->plu;
99  power_supply_changed(&nvec_psy);
100  }
101  return NOTIFY_STOP;
102  }
103  return NOTIFY_OK;
104 }
105 
106 static const int bat_init[] = {
109 };
110 
111 static void get_bat_mfg_data(struct nvec_power *power)
112 {
113  int i;
114  char buf[] = { '\x02', '\x00' };
115 
116  for (i = 0; i < ARRAY_SIZE(bat_init); i++) {
117  buf[1] = bat_init[i];
118  nvec_write_async(power->nvec, buf, 2);
119  }
120 }
121 
122 static int nvec_power_bat_notifier(struct notifier_block *nb,
123  unsigned long event_type, void *data)
124 {
125  struct nvec_power *power =
126  container_of(nb, struct nvec_power, notifier);
127  struct bat_response *res = (struct bat_response *)data;
128  int status_changed = 0;
129 
130  if (event_type != NVEC_BAT)
131  return NOTIFY_DONE;
132 
133  switch (res->sub_type) {
134  case SLOT_STATUS:
135  if (res->plc[0] & 1) {
136  if (power->bat_present == 0) {
137  status_changed = 1;
138  get_bat_mfg_data(power);
139  }
140 
141  power->bat_present = 1;
142 
143  switch ((res->plc[0] >> 1) & 3) {
144  case 0:
145  power->bat_status =
147  break;
148  case 1:
149  power->bat_status =
151  break;
152  case 2:
153  power->bat_status =
155  break;
156  default:
158  }
159  } else {
160  if (power->bat_present == 1)
161  status_changed = 1;
162 
163  power->bat_present = 0;
165  }
166  power->bat_cap = res->plc[1];
167  if (status_changed)
168  power_supply_changed(&nvec_bat_psy);
169  break;
170  case VOLTAGE:
171  power->bat_voltage_now = res->plu * 1000;
172  break;
173  case TIME_REMAINING:
174  power->time_remain = res->plu * 3600;
175  break;
176  case CURRENT:
177  power->bat_current_now = res->pls * 1000;
178  break;
179  case AVERAGE_CURRENT:
180  power->bat_current_avg = res->pls * 1000;
181  break;
182  case CAPACITY_REMAINING:
183  power->capacity_remain = res->plu * 1000;
184  break;
186  power->charge_last_full = res->plu * 1000;
187  break;
188  case DESIGN_CAPACITY:
189  power->charge_full_design = res->plu * 1000;
190  break;
191  case CRITICAL_CAPACITY:
192  power->critical_capacity = res->plu * 1000;
193  break;
194  case TEMPERATURE:
195  power->bat_temperature = res->plu - 2732;
196  break;
197  case MANUFACTURER:
198  memcpy(power->bat_manu, &res->plc, res->length - 2);
199  power->bat_model[res->length - 2] = '\0';
200  break;
201  case MODEL:
202  memcpy(power->bat_model, &res->plc, res->length - 2);
203  power->bat_model[res->length - 2] = '\0';
204  break;
205  case TYPE:
206  memcpy(power->bat_type, &res->plc, res->length - 2);
207  power->bat_type[res->length - 2] = '\0';
208  /* this differs a little from the spec
209  fill in more if you find some */
210  if (!strncmp(power->bat_type, "Li", 30))
212  else
214  break;
215  default:
216  return NOTIFY_STOP;
217  }
218 
219  return NOTIFY_STOP;
220 }
221 
222 static int nvec_power_get_property(struct power_supply *psy,
223  enum power_supply_property psp,
224  union power_supply_propval *val)
225 {
226  struct nvec_power *power = dev_get_drvdata(psy->dev->parent);
227  switch (psp) {
229  val->intval = power->on;
230  break;
231  default:
232  return -EINVAL;
233  }
234  return 0;
235 }
236 
237 static int nvec_battery_get_property(struct power_supply *psy,
238  enum power_supply_property psp,
239  union power_supply_propval *val)
240 {
241  struct nvec_power *power = dev_get_drvdata(psy->dev->parent);
242 
243  switch (psp) {
245  val->intval = power->bat_status;
246  break;
248  val->intval = power->bat_cap;
249  break;
251  val->intval = power->bat_present;
252  break;
254  val->intval = power->bat_voltage_now;
255  break;
257  val->intval = power->bat_current_now;
258  break;
260  val->intval = power->bat_current_avg;
261  break;
263  val->intval = power->time_remain;
264  break;
266  val->intval = power->charge_full_design;
267  break;
269  val->intval = power->charge_last_full;
270  break;
272  val->intval = power->critical_capacity;
273  break;
275  val->intval = power->capacity_remain;
276  break;
278  val->intval = power->bat_temperature;
279  break;
281  val->strval = power->bat_manu;
282  break;
284  val->strval = power->bat_model;
285  break;
287  val->intval = power->bat_type_enum;
288  break;
289  default:
290  return -EINVAL;
291  }
292  return 0;
293 }
294 
295 static enum power_supply_property nvec_power_props[] = {
297 };
298 
299 static enum power_supply_property nvec_battery_props[] = {
305 #ifdef EC_FULL_DIAG
309 #endif
317 };
318 
319 static char *nvec_power_supplied_to[] = {
320  "battery",
321 };
322 
323 static struct power_supply nvec_bat_psy = {
324  .name = "battery",
326  .properties = nvec_battery_props,
327  .num_properties = ARRAY_SIZE(nvec_battery_props),
328  .get_property = nvec_battery_get_property,
329 };
330 
331 static struct power_supply nvec_psy = {
332  .name = "ac",
333  .type = POWER_SUPPLY_TYPE_MAINS,
334  .supplied_to = nvec_power_supplied_to,
335  .num_supplicants = ARRAY_SIZE(nvec_power_supplied_to),
336  .properties = nvec_power_props,
337  .num_properties = ARRAY_SIZE(nvec_power_props),
338  .get_property = nvec_power_get_property,
339 };
340 
341 static int counter;
342 static int const bat_iter[] = {
344 #ifdef EC_FULL_DIAG
346 #endif
347 };
348 
349 static void nvec_power_poll(struct work_struct *work)
350 {
351  char buf[] = { '\x01', '\x00' };
352  struct nvec_power *power = container_of(work, struct nvec_power,
353  poller.work);
354 
355  if (counter >= ARRAY_SIZE(bat_iter))
356  counter = 0;
357 
358 /* AC status via sys req */
359  nvec_write_async(power->nvec, buf, 2);
360  msleep(100);
361 
362 /* select a battery request function via round robin
363  doing it all at once seems to overload the power supply */
364  buf[0] = '\x02'; /* battery */
365  buf[1] = bat_iter[counter++];
366  nvec_write_async(power->nvec, buf, 2);
367 
368  schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000));
369 };
370 
371 static int __devinit nvec_power_probe(struct platform_device *pdev)
372 {
373  struct power_supply *psy;
374  struct nvec_power *power;
375  struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
376 
377  power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT);
378  if (power == NULL)
379  return -ENOMEM;
380 
381  dev_set_drvdata(&pdev->dev, power);
382  power->nvec = nvec;
383 
384  switch (pdev->id) {
385  case AC:
386  psy = &nvec_psy;
387 
388  power->notifier.notifier_call = nvec_power_notifier;
389 
390  INIT_DELAYED_WORK(&power->poller, nvec_power_poll);
392  break;
393  case BAT:
394  psy = &nvec_bat_psy;
395 
396  power->notifier.notifier_call = nvec_power_bat_notifier;
397  break;
398  default:
399  return -ENODEV;
400  }
401 
402  nvec_register_notifier(nvec, &power->notifier, NVEC_SYS);
403 
404  if (pdev->id == BAT)
405  get_bat_mfg_data(power);
406 
407  return power_supply_register(&pdev->dev, psy);
408 }
409 
410 static int __devexit nvec_power_remove(struct platform_device *pdev)
411 {
412  struct nvec_power *power = platform_get_drvdata(pdev);
413 
415  switch (pdev->id) {
416  case AC:
417  power_supply_unregister(&nvec_psy);
418  break;
419  case BAT:
420  power_supply_unregister(&nvec_bat_psy);
421  }
422 
423  return 0;
424 }
425 
426 static struct platform_driver nvec_power_driver = {
427  .probe = nvec_power_probe,
428  .remove = __devexit_p(nvec_power_remove),
429  .driver = {
430  .name = "nvec-power",
431  .owner = THIS_MODULE,
432  }
433 };
434 
435 module_platform_driver(nvec_power_driver);
436 
437 MODULE_AUTHOR("Ilya Petrov <[email protected]>");
438 MODULE_LICENSE("GPL");
439 MODULE_DESCRIPTION("NVEC battery and AC driver");
440 MODULE_ALIAS("platform:nvec-power");