2 * nvec_power: power supply driver for a NVIDIA compliant embedded controller
4 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net>
6 * Authors: Ilya Petrov <ilya.muromec@gmail.com>
7 * Marc Dietrich <marvin24@gmx.de>
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
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>
25 #define GET_SYSTEM_STATUS 0x00
28 struct notifier_block notifier
;
29 struct delayed_work poller
;
30 struct nvec_chip
*nvec
;
38 int charge_full_design
;
40 int critical_capacity
;
56 AVERAGING_TIME_INTERVAL
,
58 LAST_FULL_CHARGE_CAPACITY
,
85 static struct power_supply
*nvec_bat_psy
;
86 static struct power_supply
*nvec_psy
;
88 static int nvec_power_notifier(struct notifier_block
*nb
,
89 unsigned long event_type
, void *data
)
91 struct nvec_power
*power
=
92 container_of(nb
, struct nvec_power
, notifier
);
93 struct bat_response
*res
= (struct bat_response
*)data
;
95 if (event_type
!= NVEC_SYS
)
98 if (res
->sub_type
== 0) {
99 if (power
->on
!= res
->plu
) {
100 power
->on
= res
->plu
;
101 power_supply_changed(nvec_psy
);
108 static const int bat_init
[] = {
109 LAST_FULL_CHARGE_CAPACITY
, DESIGN_CAPACITY
, CRITICAL_CAPACITY
,
110 MANUFACTURER
, MODEL
, TYPE
,
113 static void get_bat_mfg_data(struct nvec_power
*power
)
116 char buf
[] = { NVEC_BAT
, SLOT_STATUS
};
118 for (i
= 0; i
< ARRAY_SIZE(bat_init
); i
++) {
119 buf
[1] = bat_init
[i
];
120 nvec_write_async(power
->nvec
, buf
, 2);
124 static int nvec_power_bat_notifier(struct notifier_block
*nb
,
125 unsigned long event_type
, void *data
)
127 struct nvec_power
*power
=
128 container_of(nb
, struct nvec_power
, notifier
);
129 struct bat_response
*res
= (struct bat_response
*)data
;
130 int status_changed
= 0;
132 if (event_type
!= NVEC_BAT
)
135 switch (res
->sub_type
) {
137 if (res
->plc
[0] & 1) {
138 if (power
->bat_present
== 0) {
140 get_bat_mfg_data(power
);
143 power
->bat_present
= 1;
145 switch ((res
->plc
[0] >> 1) & 3) {
148 POWER_SUPPLY_STATUS_NOT_CHARGING
;
152 POWER_SUPPLY_STATUS_CHARGING
;
156 POWER_SUPPLY_STATUS_DISCHARGING
;
159 power
->bat_status
= POWER_SUPPLY_STATUS_UNKNOWN
;
162 if (power
->bat_present
== 1)
165 power
->bat_present
= 0;
166 power
->bat_status
= POWER_SUPPLY_STATUS_UNKNOWN
;
168 power
->bat_cap
= res
->plc
[1];
170 power_supply_changed(nvec_bat_psy
);
173 power
->bat_voltage_now
= res
->plu
* 1000;
176 power
->time_remain
= res
->plu
* 3600;
179 power
->bat_current_now
= res
->pls
* 1000;
181 case AVERAGE_CURRENT
:
182 power
->bat_current_avg
= res
->pls
* 1000;
184 case CAPACITY_REMAINING
:
185 power
->capacity_remain
= res
->plu
* 1000;
187 case LAST_FULL_CHARGE_CAPACITY
:
188 power
->charge_last_full
= res
->plu
* 1000;
190 case DESIGN_CAPACITY
:
191 power
->charge_full_design
= res
->plu
* 1000;
193 case CRITICAL_CAPACITY
:
194 power
->critical_capacity
= res
->plu
* 1000;
197 power
->bat_temperature
= res
->plu
- 2732;
200 memcpy(power
->bat_manu
, &res
->plc
, res
->length
- 2);
201 power
->bat_model
[res
->length
- 2] = '\0';
204 memcpy(power
->bat_model
, &res
->plc
, res
->length
- 2);
205 power
->bat_model
[res
->length
- 2] = '\0';
208 memcpy(power
->bat_type
, &res
->plc
, res
->length
- 2);
209 power
->bat_type
[res
->length
- 2] = '\0';
210 /* this differs a little from the spec
211 fill in more if you find some */
212 if (!strncmp(power
->bat_type
, "Li", 30))
213 power
->bat_type_enum
= POWER_SUPPLY_TECHNOLOGY_LION
;
215 power
->bat_type_enum
= POWER_SUPPLY_TECHNOLOGY_UNKNOWN
;
224 static int nvec_power_get_property(struct power_supply
*psy
,
225 enum power_supply_property psp
,
226 union power_supply_propval
*val
)
228 struct nvec_power
*power
= dev_get_drvdata(psy
->dev
.parent
);
231 case POWER_SUPPLY_PROP_ONLINE
:
232 val
->intval
= power
->on
;
240 static int nvec_battery_get_property(struct power_supply
*psy
,
241 enum power_supply_property psp
,
242 union power_supply_propval
*val
)
244 struct nvec_power
*power
= dev_get_drvdata(psy
->dev
.parent
);
247 case POWER_SUPPLY_PROP_STATUS
:
248 val
->intval
= power
->bat_status
;
250 case POWER_SUPPLY_PROP_CAPACITY
:
251 val
->intval
= power
->bat_cap
;
253 case POWER_SUPPLY_PROP_PRESENT
:
254 val
->intval
= power
->bat_present
;
256 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
257 val
->intval
= power
->bat_voltage_now
;
259 case POWER_SUPPLY_PROP_CURRENT_NOW
:
260 val
->intval
= power
->bat_current_now
;
262 case POWER_SUPPLY_PROP_CURRENT_AVG
:
263 val
->intval
= power
->bat_current_avg
;
265 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW
:
266 val
->intval
= power
->time_remain
;
268 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN
:
269 val
->intval
= power
->charge_full_design
;
271 case POWER_SUPPLY_PROP_CHARGE_FULL
:
272 val
->intval
= power
->charge_last_full
;
274 case POWER_SUPPLY_PROP_CHARGE_EMPTY
:
275 val
->intval
= power
->critical_capacity
;
277 case POWER_SUPPLY_PROP_CHARGE_NOW
:
278 val
->intval
= power
->capacity_remain
;
280 case POWER_SUPPLY_PROP_TEMP
:
281 val
->intval
= power
->bat_temperature
;
283 case POWER_SUPPLY_PROP_MANUFACTURER
:
284 val
->strval
= power
->bat_manu
;
286 case POWER_SUPPLY_PROP_MODEL_NAME
:
287 val
->strval
= power
->bat_model
;
289 case POWER_SUPPLY_PROP_TECHNOLOGY
:
290 val
->intval
= power
->bat_type_enum
;
298 static enum power_supply_property nvec_power_props
[] = {
299 POWER_SUPPLY_PROP_ONLINE
,
302 static enum power_supply_property nvec_battery_props
[] = {
303 POWER_SUPPLY_PROP_STATUS
,
304 POWER_SUPPLY_PROP_PRESENT
,
305 POWER_SUPPLY_PROP_CAPACITY
,
306 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
307 POWER_SUPPLY_PROP_CURRENT_NOW
,
309 POWER_SUPPLY_PROP_CURRENT_AVG
,
310 POWER_SUPPLY_PROP_TEMP
,
311 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW
,
313 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN
,
314 POWER_SUPPLY_PROP_CHARGE_FULL
,
315 POWER_SUPPLY_PROP_CHARGE_EMPTY
,
316 POWER_SUPPLY_PROP_CHARGE_NOW
,
317 POWER_SUPPLY_PROP_MANUFACTURER
,
318 POWER_SUPPLY_PROP_MODEL_NAME
,
319 POWER_SUPPLY_PROP_TECHNOLOGY
,
322 static char *nvec_power_supplied_to
[] = {
326 static const struct power_supply_desc nvec_bat_psy_desc
= {
328 .type
= POWER_SUPPLY_TYPE_BATTERY
,
329 .properties
= nvec_battery_props
,
330 .num_properties
= ARRAY_SIZE(nvec_battery_props
),
331 .get_property
= nvec_battery_get_property
,
334 static const struct power_supply_desc nvec_psy_desc
= {
336 .type
= POWER_SUPPLY_TYPE_MAINS
,
337 .properties
= nvec_power_props
,
338 .num_properties
= ARRAY_SIZE(nvec_power_props
),
339 .get_property
= nvec_power_get_property
,
343 static int const bat_iter
[] = {
344 SLOT_STATUS
, VOLTAGE
, CURRENT
, CAPACITY_REMAINING
,
346 AVERAGE_CURRENT
, TEMPERATURE
, TIME_REMAINING
,
350 static void nvec_power_poll(struct work_struct
*work
)
352 char buf
[] = { NVEC_SYS
, GET_SYSTEM_STATUS
};
353 struct nvec_power
*power
= container_of(work
, struct nvec_power
,
356 if (counter
>= ARRAY_SIZE(bat_iter
))
359 /* AC status via sys req */
360 nvec_write_async(power
->nvec
, buf
, 2);
363 /* select a battery request function via round robin
364 doing it all at once seems to overload the power supply */
366 buf
[1] = bat_iter
[counter
++];
367 nvec_write_async(power
->nvec
, buf
, 2);
369 schedule_delayed_work(to_delayed_work(work
), msecs_to_jiffies(5000));
372 static int nvec_power_probe(struct platform_device
*pdev
)
374 struct power_supply
**psy
;
375 const struct power_supply_desc
*psy_desc
;
376 struct nvec_power
*power
;
377 struct nvec_chip
*nvec
= dev_get_drvdata(pdev
->dev
.parent
);
378 struct power_supply_config psy_cfg
= {};
380 power
= devm_kzalloc(&pdev
->dev
, sizeof(struct nvec_power
), GFP_NOWAIT
);
384 dev_set_drvdata(&pdev
->dev
, power
);
390 psy_desc
= &nvec_psy_desc
;
391 psy_cfg
.supplied_to
= nvec_power_supplied_to
;
392 psy_cfg
.num_supplicants
= ARRAY_SIZE(nvec_power_supplied_to
);
394 power
->notifier
.notifier_call
= nvec_power_notifier
;
396 INIT_DELAYED_WORK(&power
->poller
, nvec_power_poll
);
397 schedule_delayed_work(&power
->poller
, msecs_to_jiffies(5000));
401 psy_desc
= &nvec_bat_psy_desc
;
403 power
->notifier
.notifier_call
= nvec_power_bat_notifier
;
409 nvec_register_notifier(nvec
, &power
->notifier
, NVEC_SYS
);
412 get_bat_mfg_data(power
);
414 *psy
= power_supply_register(&pdev
->dev
, psy_desc
, &psy_cfg
);
416 return PTR_ERR_OR_ZERO(*psy
);
419 static int nvec_power_remove(struct platform_device
*pdev
)
421 struct nvec_power
*power
= platform_get_drvdata(pdev
);
423 cancel_delayed_work_sync(&power
->poller
);
424 nvec_unregister_notifier(power
->nvec
, &power
->notifier
);
427 power_supply_unregister(nvec_psy
);
430 power_supply_unregister(nvec_bat_psy
);
436 static struct platform_driver nvec_power_driver
= {
437 .probe
= nvec_power_probe
,
438 .remove
= nvec_power_remove
,
440 .name
= "nvec-power",
444 module_platform_driver(nvec_power_driver
);
446 MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
447 MODULE_LICENSE("GPL");
448 MODULE_DESCRIPTION("NVEC battery and AC driver");
449 MODULE_ALIAS("platform:nvec-power");