Root/target/linux/omap24xx/patches-3.1/900-n810-battery-management.patch

1--- a/drivers/cbus/Kconfig
2+++ b/drivers/cbus/Kconfig
3@@ -83,4 +83,12 @@ config CBUS_RETU_HEADSET
4 
5 endif # CBUS_RETU
6 
7+config N810BM
8+ depends on CBUS_RETU && CBUS_TAHVO
9+ tristate "Nokia n810 battery management"
10+ ---help---
11+ Nokia n810 device battery management.
12+
13+ If unsure, say N.
14+
15 endmenu
16--- a/drivers/cbus/Makefile
17+++ b/drivers/cbus/Makefile
18@@ -11,3 +11,6 @@ obj-$(CONFIG_CBUS_RETU_POWERBUTTON) += r
19 obj-$(CONFIG_CBUS_RETU_RTC) += retu-rtc.o
20 obj-$(CONFIG_CBUS_RETU_WDT) += retu-wdt.o
21 obj-$(CONFIG_CBUS_RETU_HEADSET) += retu-headset.o
22+n810bm-y += n810bm_main.o
23+n810bm-y += lipocharge.o
24+obj-$(CONFIG_N810BM) += n810bm.o
25--- /dev/null
26+++ b/drivers/cbus/n810bm_main.c
27@@ -0,0 +1,1572 @@
28+/*
29+ * Nokia n810 battery management
30+ *
31+ * WARNING: This driver is based on unconfirmed documentation.
32+ * It is possibly dangerous to use this software.
33+ * Use this software at your own risk!
34+ *
35+ * Copyright (c) 2010-2011 Michael Buesch <mb@bu3sch.de>
36+ *
37+ * This program is free software; you can redistribute it and/or
38+ * modify it under the terms of the GNU General Public License
39+ * as published by the Free Software Foundation; either version 2
40+ * of the License, or (at your option) any later version.
41+ *
42+ * This program is distributed in the hope that it will be useful,
43+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
44+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45+ * GNU General Public License for more details.
46+ */
47+
48+#define DEBUG
49+
50+#include <linux/module.h>
51+#include <linux/device.h>
52+#include <linux/platform_device.h>
53+#include <linux/slab.h>
54+#include <linux/mutex.h>
55+#include <linux/timer.h>
56+#include <linux/firmware.h>
57+#include <linux/bitops.h>
58+#include <linux/workqueue.h>
59+#include <linux/delay.h>
60+#include <linux/interrupt.h>
61+
62+#include "cbus.h"
63+#include "retu.h"
64+#include "tahvo.h"
65+#include "lipocharge.h"
66+
67+
68+#define N810BM_PMM_BLOCK_FILENAME "n810-cal-bme-pmm.fw"
69+#define N810BM_PMM_BLOCK_SIZE 0x600
70+#define N810BM_PMM_GROUP_SIZE 0x200
71+#define N810BM_PMM_ELEM_SIZE 0x10
72+
73+#define N810BM_CHECK_INTERVAL (HZ * 2)
74+#define N810BM_MIN_VOLTAGE_THRES 3200 /* Absolute minimum voltage threshold */
75+
76+
77+/* RETU_ADC_BSI
78+ * The battery size indicator ADC measures the resistance between
79+ * the battery BSI pin and ground. This is used to detect the battery
80+ * capacity, as the BSI resistor is related to capacity.
81+ *
82+ * Manually measured lookup table.
83+ * Hard to measure, thus not very accurate.
84+ *
85+ * Resistance | ADC value
86+ * ========================
87+ * 120k | 0x3AC
88+ * 110k | 0x37C
89+ * 100k | 0x351
90+ * 90k | 0x329
91+ */
92+
93+/* RETU_ADC_BATTVOLT
94+ * Manually measured lookup table.
95+ * Hard to measure, thus not very accurate.
96+ *
97+ * Voltage | ADC value
98+ * =====================
99+ * 2.80V | 0x037
100+ * 2.90V | 0x05E
101+ * 3.00V | 0x090
102+ * 3.10V | 0x0A4
103+ * 3.20V | 0x0CC
104+ * 3.30V | 0x0EF
105+ * 3.40V | 0x115
106+ * 3.50V | 0x136
107+ * 3.60V | 0x15C
108+ * 3.70V | 0x187
109+ * 3.80V | 0x1A5
110+ * 3.90V | 0x1C9
111+ * 4.00V | 0x1ED
112+ * 4.10V | 0x212
113+ * 4.20V | 0x236
114+ */
115+
116+
117+/* PMM block ADC IDs */
118+enum n810bm_pmm_adc_id {
119+ N810BM_PMM_ADC_BATVOLT = 0x01, /* Battery voltage */
120+ N810BM_PMM_ADC_CHGVOLT = 0x02, /* Charger voltage */
121+ N810BM_PMM_ADC_GND2 = 0x03, /* Ground 0V */
122+ N810BM_PMM_ADC_BSI = 0x04, /* Battery size indicator */
123+ N810BM_PMM_ADC_BATTEMP = 0x05, /* Battery temperature */
124+ N810BM_PMM_ADC_HEADSET = 0x06, /* Headset detection */
125+ N810BM_PMM_ADC_HOOKDET = 0x07, /* Hook detection */
126+ N810BM_PMM_ADC_LIGHTSENS = 0x08, /* Light sensor */
127+ N810BM_PMM_ADC_BATCURR = 0x0E, /* Battery current */
128+ N810BM_PMM_ADC_BKUPVOLT = 0x13, /* Backup battery voltage */
129+ N810BM_PMM_ADC_LIGHTTEMP = 0x14, /* Light sensor temperature */
130+ N810BM_PMM_ADC_RFGP = 0x15, /* RF GP */
131+ N810BM_PMM_ADC_WBTX = 0x16, /* Wideband TX detection */
132+ N810BM_PMM_ADC_RETUTEMP = 0x17, /* RETU chip temperature */
133+ N810BM_PMM_ADC_0xFE = 0xFE,
134+};
135+
136+struct n810bm_adc_calib {
137+ enum n810bm_pmm_adc_id id;
138+ u8 flags;
139+ u8 adc_groupnr;
140+ u32 field1;
141+ u32 field2;
142+ u16 field3;
143+ u16 field4;
144+};
145+
146+struct n810bm_calib {
147+ struct n810bm_adc_calib adc[25];
148+};
149+
150+enum n810bm_capacity {
151+ N810BM_CAP_UNKNOWN = -1,
152+ N810BM_CAP_NONE = 0,
153+ N810BM_CAP_1500MAH = 1500, /* 1500 mAh battery */
154+};
155+
156+enum n810bm_notify_flags {
157+ N810BM_NOTIFY_charger_present,
158+ N810BM_NOTIFY_charger_state,
159+ N810BM_NOTIFY_charger_pwm,
160+};
161+
162+struct n810bm {
163+ int tahvo_irq;
164+ bool tahvo_irq_enabled;
165+
166+ bool battery_present; /* A battery is inserted */
167+ bool charger_present; /* The charger is connected */
168+ enum n810bm_capacity capacity; /* The capacity of the inserted battery (if any) */
169+
170+ bool charger_enabled; /* Want to charge? */
171+ struct lipocharge charger; /* Charger subsystem */
172+ unsigned int active_current_pwm; /* Active value of TAHVO_REG_CHGCURR */
173+ int current_measure_enabled; /* Current measure enable refcount */
174+
175+ struct platform_device *pdev;
176+ struct n810bm_calib calib; /* Calibration data */
177+
178+ bool verbose_charge_log; /* Verbose charge logging */
179+
180+ unsigned long notify_flags;
181+ struct work_struct notify_work;
182+ struct delayed_work periodic_check_work;
183+
184+ bool initialized; /* The hardware was initialized */
185+ struct mutex mutex;
186+};
187+
188+static void n810bm_notify_charger_present(struct n810bm *bm);
189+static void n810bm_notify_charger_state(struct n810bm *bm);
190+static void n810bm_notify_charger_pwm(struct n810bm *bm);
191+
192+
193+static struct platform_device *n810bm_retu_device;
194+static struct platform_device *n810bm_tahvo_device;
195+
196+
197+static inline struct n810bm * device_to_n810bm(struct device *dev)
198+{
199+ struct platform_device *pdev = to_platform_device(dev);
200+ struct n810bm *bm = platform_get_drvdata(pdev);
201+
202+ return bm;
203+}
204+
205+static inline bool n810bm_known_battery_present(struct n810bm *bm)
206+{
207+ return bm->battery_present &&
208+ bm->capacity != N810BM_CAP_UNKNOWN &&
209+ bm->capacity != N810BM_CAP_NONE;
210+}
211+
212+static NORET_TYPE void n810bm_emergency(struct n810bm *bm, const char *message) ATTRIB_NORET;
213+static void n810bm_emergency(struct n810bm *bm, const char *message)
214+{
215+ printk(KERN_EMERG "n810 battery management fatal fault: %s\n", message);
216+ cbus_emergency();
217+}
218+
219+static u16 tahvo_read(struct n810bm *bm, unsigned int reg)
220+{
221+ return tahvo_read_reg(&n810bm_tahvo_device->dev, reg);
222+}
223+
224+static void tahvo_maskset(struct n810bm *bm, unsigned int reg, u16 mask, u16 set)
225+{
226+ tahvo_set_clear_reg_bits(&n810bm_tahvo_device->dev, reg, set, mask);
227+}
228+
229+static inline void tahvo_write(struct n810bm *bm, unsigned int reg, u16 value)
230+{
231+ tahvo_write_reg(&n810bm_tahvo_device->dev, reg, value);
232+}
233+
234+static inline void tahvo_set(struct n810bm *bm, unsigned int reg, u16 mask)
235+{
236+ tahvo_set_clear_reg_bits(&n810bm_tahvo_device->dev, reg, mask, mask);
237+}
238+
239+static inline void tahvo_clear(struct n810bm *bm, unsigned int reg, u16 mask)
240+{
241+ tahvo_set_clear_reg_bits(&n810bm_tahvo_device->dev, reg, 0, mask);
242+}
243+
244+static u16 retu_read(struct n810bm *bm, unsigned int reg)
245+{
246+ return retu_read_reg(&n810bm_retu_device->dev, reg);
247+}
248+
249+static void retu_maskset(struct n810bm *bm, unsigned int reg, u16 mask, u16 set)
250+{
251+ retu_set_clear_reg_bits(&n810bm_retu_device->dev, reg, set, mask);
252+}
253+
254+static inline void retu_write(struct n810bm *bm, unsigned int reg, u16 value)
255+{
256+ retu_write_reg(&n810bm_retu_device->dev, reg, value);
257+}
258+
259+static int retu_adc_average(struct n810bm *bm, unsigned int chan,
260+ unsigned int nr_passes)
261+{
262+ unsigned int i, value = 0;
263+ int ret;
264+
265+ if (WARN_ON(!nr_passes))
266+ return 0;
267+ for (i = 0; i < nr_passes; i++) {
268+ ret = retu_read_adc(&n810bm_retu_device->dev, chan);
269+ if (ret < 0)
270+ return ret;
271+ value += ret;
272+ }
273+ value /= nr_passes;
274+
275+ return value;
276+}
277+
278+static struct n810bm_adc_calib * n810bm_get_adc_calib(struct n810bm *bm,
279+ enum n810bm_pmm_adc_id id)
280+{
281+ unsigned int index = 0;
282+ struct n810bm_adc_calib *cal;
283+
284+ if (id != N810BM_PMM_ADC_0xFE)
285+ index = (unsigned int)id + 1;
286+ if (index >= ARRAY_SIZE(bm->calib.adc))
287+ return NULL;
288+
289+ cal = &bm->calib.adc[index];
290+ WARN_ON(cal->id && cal->id != id);
291+
292+ return cal;
293+}
294+
295+static int pmm_record_get(struct n810bm *bm,
296+ const struct firmware *pmm_block,
297+ void *buffer, size_t length,
298+ unsigned int group, unsigned int element, unsigned int offset)
299+{
300+ const u8 *pmm_area = pmm_block->data;
301+ u8 active_group_mask;
302+
303+ if (pmm_block->size != N810BM_PMM_BLOCK_SIZE)
304+ return -EINVAL;
305+ if (group >= N810BM_PMM_BLOCK_SIZE / N810BM_PMM_GROUP_SIZE)
306+ return -EINVAL;
307+ if (element >= N810BM_PMM_GROUP_SIZE / N810BM_PMM_ELEM_SIZE)
308+ return -EINVAL;
309+ if (offset >= N810BM_PMM_ELEM_SIZE || length > N810BM_PMM_ELEM_SIZE ||
310+ length + offset > N810BM_PMM_ELEM_SIZE)
311+ return -EINVAL;
312+
313+ active_group_mask = pmm_area[16];
314+ if (!(active_group_mask & (1 << group))) {
315+ dev_dbg(&bm->pdev->dev, "pwm_record_get: Requested group %u, "
316+ "but group is not active", group);
317+ return -ENOENT;
318+ }
319+
320+ memcpy(buffer,
321+ pmm_area + group * N810BM_PMM_GROUP_SIZE
322+ + element * N810BM_PMM_ELEM_SIZE
323+ + offset,
324+ length);
325+
326+ return 0;
327+}
328+
329+/* PMM block group 1 element */
330+struct group1_element {
331+ u8 id;
332+ u8 flags;
333+ u8 adc_groupnr;
334+ u8 _padding;
335+ __le32 field1;
336+ __le32 field2;
337+} __packed;
338+
339+static int extract_group1_elem(struct n810bm *bm,
340+ const struct firmware *pmm_block,
341+ const enum n810bm_pmm_adc_id *pmm_adc_ids, size_t nr_pmm_adc_ids,
342+ u32 field1_mask, u32 field2_mask)
343+{
344+ struct group1_element elem;
345+ int err;
346+ unsigned int i, element_nr;
347+ struct n810bm_adc_calib *adc_calib;
348+
349+ for (i = 0; i < nr_pmm_adc_ids; i++) {
350+ element_nr = (unsigned int)(pmm_adc_ids[i]) + 3;
351+
352+ err = pmm_record_get(bm, pmm_block, &elem, sizeof(elem),
353+ 1, element_nr, 0);
354+ if (err)
355+ continue;
356+ adc_calib = n810bm_get_adc_calib(bm, elem.id);
357+ if (!adc_calib) {
358+ dev_err(&bm->pdev->dev, "extract_group1_elem: "
359+ "Could not get calib element for 0x%02X",
360+ elem.id);
361+ return -EINVAL;
362+ }
363+
364+ if (adc_calib->flags == elem.flags) {
365+ adc_calib->field1 = le32_to_cpu(elem.field1) & field1_mask;
366+ adc_calib->field2 = le32_to_cpu(elem.field2) & field2_mask;
367+ } else {
368+ dev_dbg(&bm->pdev->dev, "extract_group1_elem: "
369+ "Not extracting fields due to flags mismatch: "
370+ "0x%02X vs 0x%02X",
371+ adc_calib->flags, elem.flags);
372+ }
373+ }
374+
375+ return 0;
376+}
377+
378+static int n810bm_parse_pmm_group1(struct n810bm *bm,
379+ const struct firmware *pmm_block)
380+{
381+ struct n810bm_adc_calib *adc_calib;
382+ struct group1_element elem;
383+ int err;
384+
385+ static const enum n810bm_pmm_adc_id pmm_adc_ids_1[] = {
386+ N810BM_PMM_ADC_BATVOLT,
387+ N810BM_PMM_ADC_CHGVOLT,
388+ N810BM_PMM_ADC_BKUPVOLT,
389+ N810BM_PMM_ADC_BATCURR,
390+ };
391+ static const enum n810bm_pmm_adc_id pmm_adc_ids_2[] = {
392+ N810BM_PMM_ADC_BSI,
393+ };
394+ static const enum n810bm_pmm_adc_id pmm_adc_ids_3[] = {
395+ N810BM_PMM_ADC_BATTEMP,
396+ };
397+
398+ /* Parse element 2 */
399+ err = pmm_record_get(bm, pmm_block, &elem, sizeof(elem),
400+ 1, 2, 0);
401+ if (err) {
402+ dev_err(&bm->pdev->dev,
403+ "PMM: Failed to get group 1 / element 2");
404+ return err;
405+ }
406+ if (elem.id == N810BM_PMM_ADC_0xFE && elem.flags == 0x05) {
407+ adc_calib = n810bm_get_adc_calib(bm, elem.id);
408+ if (!adc_calib) {
409+ dev_err(&bm->pdev->dev,
410+ "calib extract: Failed to get 0xFE calib");
411+ return -EINVAL;
412+ }
413+ adc_calib->id = elem.id;
414+ adc_calib->flags = elem.flags;
415+ adc_calib->field1 = le32_to_cpu(elem.field1);
416+ adc_calib->field2 = le32_to_cpu(elem.field2);
417+ }
418+
419+ err = extract_group1_elem(bm, pmm_block,
420+ pmm_adc_ids_1, ARRAY_SIZE(pmm_adc_ids_1),
421+ 0xFFFFFFFF, 0xFFFFFFFF);
422+ if (err)
423+ return err;
424+ err = extract_group1_elem(bm, pmm_block,
425+ pmm_adc_ids_2, ARRAY_SIZE(pmm_adc_ids_2),
426+ 0xFFFFFFFF, 0);
427+ if (err)
428+ return err;
429+ err = extract_group1_elem(bm, pmm_block,
430+ pmm_adc_ids_3, ARRAY_SIZE(pmm_adc_ids_3),
431+ 0xFFFFFFFF, 0x0000FFFF);
432+ if (err)
433+ return err;
434+
435+ return 0;
436+}
437+
438+static int n810bm_parse_pmm_group2(struct n810bm *bm,
439+ const struct firmware *pmm_block)
440+{
441+ dev_err(&bm->pdev->dev, "TODO: CAL BME PMM group 2 parser not implemented, yet");
442+ return -EOPNOTSUPP;
443+}
444+
445+static void n810bm_adc_calib_set_defaults(struct n810bm *bm)
446+{
447+ struct n810bm_adc_calib *adc_calib;
448+ unsigned int i;
449+
450+ static const struct n810bm_adc_calib defaults[] = {
451+ /* ADC group-nr 0 */
452+ {
453+ .id = N810BM_PMM_ADC_HEADSET,
454+ .flags = 0x00,
455+ .adc_groupnr = 0,
456+ }, {
457+ .id = N810BM_PMM_ADC_HOOKDET,
458+ .flags = 0x00,
459+ .adc_groupnr = 0,
460+ }, {
461+ .id = N810BM_PMM_ADC_RFGP,
462+ .flags = 0x00,
463+ .adc_groupnr = 0,
464+ }, {
465+ .id = N810BM_PMM_ADC_LIGHTSENS,
466+ .flags = 0x00,
467+ .adc_groupnr = 0,
468+ }, {
469+ .id = N810BM_PMM_ADC_WBTX,
470+ .flags = 0x00,
471+ .adc_groupnr = 0,
472+ }, {
473+ .id = N810BM_PMM_ADC_RETUTEMP,
474+ .flags = 0x00,
475+ .adc_groupnr = 0,
476+ }, {
477+ .id = N810BM_PMM_ADC_GND2,
478+ .flags = 0x00,
479+ .adc_groupnr = 0,
480+ },
481+ /* ADC group-nr 1 */
482+ {
483+ .id = N810BM_PMM_ADC_0xFE,
484+ .flags = 0x05,
485+ .adc_groupnr = 1,
486+ .field1 = (u32)-2,
487+ .field2 = 13189,
488+ }, {
489+ .id = N810BM_PMM_ADC_BATVOLT,
490+ .flags = 0x01,
491+ .adc_groupnr = 1,
492+ .field1 = 2527,
493+ .field2 = 21373,
494+ }, {
495+ .id = N810BM_PMM_ADC_CHGVOLT,
496+ .flags = 0x01,
497+ .adc_groupnr = 1,
498+ .field1 = 0,
499+ .field2 = 129848,
500+ }, {
501+ .id = N810BM_PMM_ADC_BKUPVOLT,
502+ .flags = 0x01,
503+ .adc_groupnr = 1,
504+ .field1 = 0,
505+ .field2 = 20000,
506+ }, {
507+ .id = N810BM_PMM_ADC_BATCURR,
508+ .flags = 0x06,
509+ .adc_groupnr = 1,
510+ .field1 = 0,
511+ .field2 = 9660,
512+ },
513+ /* ADC group-nr 2 */
514+ {
515+ .id = N810BM_PMM_ADC_BSI,
516+ .flags = 0x02,
517+ .adc_groupnr = 2,
518+ .field1 = 1169,
519+ .field2 = 0,
520+ },
521+ /* ADC group-nr 3 */
522+ {
523+ .id = N810BM_PMM_ADC_BATTEMP,
524+ .flags = 0x03,
525+ .adc_groupnr = 3,
526+ .field1 = 265423000,
527+ .field2 = 298,
528+ },
529+ /* ADC group-nr 4 */
530+ {
531+ .id = N810BM_PMM_ADC_LIGHTTEMP,
532+ .flags = 0x04,
533+ .adc_groupnr = 4,
534+ .field1 = 19533778,
535+ .field2 = 308019670,
536+ .field3 = 4700,
537+ .field4 = 2500,
538+ },
539+ };
540+
541+ /* Clear the array */
542+ memset(&bm->calib.adc, 0, sizeof(bm->calib.adc));
543+ for (i = 0; i < ARRAY_SIZE(bm->calib.adc); i++)
544+ bm->calib.adc[i].flags = 0xFF;
545+
546+ /* Copy the defaults */
547+ for (i = 0; i < ARRAY_SIZE(defaults); i++) {
548+ adc_calib = n810bm_get_adc_calib(bm, defaults[i].id);
549+ if (WARN_ON(!adc_calib))
550+ continue;
551+ *adc_calib = defaults[i];
552+ }
553+}
554+
555+static int n810bm_parse_pmm_block(struct n810bm *bm,
556+ const struct firmware *pmm_block)
557+{
558+ u8 byte;
559+ int err;
560+ unsigned int i, count;
561+ struct n810bm_adc_calib *adc_calib;
562+
563+ /* Initialize to defaults */
564+ n810bm_adc_calib_set_defaults(bm);
565+
566+ /* Parse the PMM data */
567+ err = pmm_record_get(bm, pmm_block, &byte, sizeof(byte),
568+ 1, 0, 0); /* group 1 / element 0 */
569+ err |= (byte != 0x01);
570+ err |= pmm_record_get(bm, pmm_block, &byte, sizeof(byte),
571+ 1, 1, 0); /* group 1 / element 1 */
572+ err |= (byte != 0x01);
573+ if (err)
574+ err = n810bm_parse_pmm_group2(bm, pmm_block);
575+ else
576+ err = n810bm_parse_pmm_group1(bm, pmm_block);
577+ if (err)
578+ return err;
579+
580+ /* Sanity checks */
581+ for (i = 0, count = 0; i < ARRAY_SIZE(bm->calib.adc); i++) {
582+ adc_calib = &bm->calib.adc[i];
583+ if (adc_calib->flags == 0xFF)
584+ continue;
585+ switch (adc_calib->id) {
586+ case N810BM_PMM_ADC_BATVOLT:
587+ if (adc_calib->field1 < 2400 ||
588+ adc_calib->field1 > 2700)
589+ goto value_check_fail;
590+ if (adc_calib->field2 < 20000 ||
591+ adc_calib->field2 > 23000)
592+ goto value_check_fail;
593+ count++;
594+ break;
595+ case N810BM_PMM_ADC_BSI:
596+ if (adc_calib->field1 < 1100 ||
597+ adc_calib->field1 > 1300)
598+ goto value_check_fail;
599+ count++;
600+ break;
601+ case N810BM_PMM_ADC_BATCURR:
602+ if (adc_calib->field2 < 7000 ||
603+ adc_calib->field2 > 12000)
604+ goto value_check_fail;
605+ count++;
606+ break;
607+ case N810BM_PMM_ADC_0xFE:
608+ if ((s32)adc_calib->field1 > 14 ||
609+ (s32)adc_calib->field1 < -14)
610+ goto value_check_fail;
611+ if (adc_calib->field2 < 13000 ||
612+ adc_calib->field2 > 13350)
613+ goto value_check_fail;
614+ count++;
615+ break;
616+ case N810BM_PMM_ADC_CHGVOLT:
617+ case N810BM_PMM_ADC_BATTEMP:
618+ case N810BM_PMM_ADC_BKUPVOLT:
619+ count++;
620+ break;
621+ case N810BM_PMM_ADC_GND2:
622+ case N810BM_PMM_ADC_HOOKDET:
623+ case N810BM_PMM_ADC_LIGHTSENS:
624+ case N810BM_PMM_ADC_HEADSET:
625+ case N810BM_PMM_ADC_LIGHTTEMP:
626+ case N810BM_PMM_ADC_RFGP:
627+ case N810BM_PMM_ADC_WBTX:
628+ case N810BM_PMM_ADC_RETUTEMP:
629+ break;
630+ }
631+ dev_dbg(&bm->pdev->dev,
632+ "ADC 0x%02X calib: 0x%02X 0x%02X 0x%08X 0x%08X 0x%04X 0x%04X",
633+ adc_calib->id, adc_calib->flags, adc_calib->adc_groupnr,
634+ adc_calib->field1, adc_calib->field2,
635+ adc_calib->field3, adc_calib->field4);
636+ }
637+ if (count != 7) {
638+ dev_err(&bm->pdev->dev, "PMM sanity check: Did not find "
639+ "all required values (count=%u)", count);
640+ goto check_fail;
641+ }
642+
643+ return 0;
644+
645+value_check_fail:
646+ dev_err(&bm->pdev->dev, "PMM image sanity check failed "
647+ "(id=%02X, field1=%08X, field2=%08X)",
648+ adc_calib->id, adc_calib->field1, adc_calib->field2);
649+check_fail:
650+ return -EILSEQ;
651+}
652+
653+/* Set the current measure timer that triggers on Tahvo IRQ 7
654+ * An interval of zero disables the timer. */
655+static void n810bm_set_current_measure_timer(struct n810bm *bm,
656+ u16 millisec_interval)
657+{
658+ u16 value = millisec_interval;
659+
660+ if (value <= 0xF905) {
661+ value = ((u64)0x10624DD3 * (u64)(value + 0xF9)) >> 32;
662+ value /= 16;
663+ } else
664+ value = 0xFF;
665+
666+ tahvo_write(bm, TAHVO_REG_BATCURRTIMER, value & 0xFF);
667+
668+ tahvo_set(bm, TAHVO_REG_CHGCTL,
669+ TAHVO_REG_CHGCTL_CURTIMRST);
670+ tahvo_clear(bm, TAHVO_REG_CHGCTL,
671+ TAHVO_REG_CHGCTL_CURTIMRST);
672+
673+ if (millisec_interval) {
674+ if (!bm->tahvo_irq_enabled) {
675+ bm->tahvo_irq_enabled = 1;
676+ enable_irq(bm->tahvo_irq);
677+ }
678+ } else {
679+ if (bm->tahvo_irq_enabled) {
680+ bm->tahvo_irq_enabled = 0;
681+ disable_irq_nosync(bm->tahvo_irq);
682+ }
683+ }
684+
685+ //TODO also do a software timer for safety.
686+}
687+
688+static void n810bm_enable_current_measure(struct n810bm *bm)
689+{
690+ WARN_ON(bm->current_measure_enabled < 0);
691+ if (!bm->current_measure_enabled) {
692+ /* Enable the current measurement circuitry */
693+ tahvo_set(bm, TAHVO_REG_CHGCTL,
694+ TAHVO_REG_CHGCTL_CURMEAS);
695+ dev_dbg(&bm->pdev->dev,
696+ "Current measurement circuitry enabled");
697+ }
698+ bm->current_measure_enabled++;
699+}
700+
701+static void n810bm_disable_current_measure(struct n810bm *bm)
702+{
703+ bm->current_measure_enabled--;
704+ WARN_ON(bm->current_measure_enabled < 0);
705+ if (!bm->current_measure_enabled) {
706+ /* Disable the current measurement circuitry */
707+ tahvo_clear(bm, TAHVO_REG_CHGCTL,
708+ TAHVO_REG_CHGCTL_CURMEAS);
709+ dev_dbg(&bm->pdev->dev,
710+ "Current measurement circuitry disabled");
711+ }
712+}
713+
714+/* Measure the actual battery current. Returns a signed value in mA.
715+ * Does only work, if current measurement was enabled. */
716+static int n810bm_measure_batt_current(struct n810bm *bm)
717+{
718+ u16 retval;
719+ int adc = 0, ma, i;
720+
721+ if (WARN_ON(bm->current_measure_enabled <= 0))
722+ return 0;
723+ for (i = 0; i < 3; i++) {
724+ retval = tahvo_read(bm, TAHVO_REG_BATCURR);
725+ adc += (s16)retval; /* Value is signed */
726+ }
727+ adc /= 3;
728+
729+ //TODO convert to mA
730+ ma = adc;
731+
732+ return ma;
733+}
734+
735+/* Requires bm->mutex locked */
736+static int n810bm_measure_batt_current_async(struct n810bm *bm)
737+{
738+ int ma;
739+ bool charging = lipocharge_is_charging(&bm->charger);
740+
741+ n810bm_enable_current_measure(bm);
742+ if (!charging)
743+ WARN_ON(bm->active_current_pwm != 0);
744+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
745+ TAHVO_REG_CHGCTL_EN |
746+ TAHVO_REG_CHGCTL_PWMOVR |
747+ TAHVO_REG_CHGCTL_PWMOVRZERO,
748+ TAHVO_REG_CHGCTL_EN |
749+ TAHVO_REG_CHGCTL_PWMOVR |
750+ (charging ? 0 : TAHVO_REG_CHGCTL_PWMOVRZERO));
751+ ma = n810bm_measure_batt_current(bm);
752+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
753+ TAHVO_REG_CHGCTL_EN |
754+ TAHVO_REG_CHGCTL_PWMOVR |
755+ TAHVO_REG_CHGCTL_PWMOVRZERO,
756+ (charging ? TAHVO_REG_CHGCTL_EN : 0));
757+ n810bm_disable_current_measure(bm);
758+
759+ return ma;
760+}
761+
762+static int adc_sanity_check(struct n810bm *bm, unsigned int channel)
763+{
764+ int value;
765+
766+ value = retu_read_adc(&n810bm_retu_device->dev, channel);
767+ if (value < 0) {
768+ dev_err(&bm->pdev->dev, "Failed to read GND ADC channel %u",
769+ channel);
770+ return -EIO;
771+ }
772+ dev_dbg(&bm->pdev->dev,
773+ "GND ADC channel %u sanity check got value: %d",
774+ channel, value);
775+ if (value > 5) {
776+ n810bm_emergency(bm, "GND ADC sanity check failed");
777+ return -EIO;
778+ }
779+
780+ return 0;
781+}
782+
783+static int n810bm_check_adc_sanity(struct n810bm *bm)
784+{
785+ int err;
786+
787+ /* Discard one conversion */
788+ retu_write(bm, RETU_REG_ADCSCR, 0);
789+ retu_read_adc(&n810bm_retu_device->dev, RETU_ADC_GND2);
790+
791+ err = adc_sanity_check(bm, RETU_ADC_GND2);
792+ if (err)
793+ return err;
794+
795+ return 0;
796+}
797+
798+/* Measure the battery voltage. Returns the value in mV (or negative value on error). */
799+static int n810bm_measure_batt_voltage(struct n810bm *bm)
800+{
801+ int adc;
802+ unsigned int mv;
803+ const unsigned int scale = 1000;
804+
805+ adc = retu_adc_average(bm, RETU_ADC_BATTVOLT, 5);
806+ if (adc < 0)
807+ return adc;
808+ if (adc <= 0x37)
809+ return 2800;
810+ mv = 2800 + ((adc - 0x37) * (((4200 - 2800) * scale) / (0x236 - 0x37))) / scale;
811+
812+ //TODO compensate for power consumption
813+ //TODO honor calibration values
814+
815+ return mv;
816+}
817+
818+/* Measure the charger voltage. Returns the value in mV (or negative value on error). */
819+static int n810bm_measure_charger_voltage(struct n810bm *bm)
820+{
821+ int adc;
822+ unsigned int mv;
823+
824+ adc = retu_adc_average(bm, RETU_ADC_CHGVOLT, 5);
825+ if (adc < 0)
826+ return adc;
827+ //TODO convert to mV
828+ mv = adc;
829+
830+ return mv;
831+}
832+
833+/* Measure backup battery voltage. Returns the value in mV (or negative value on error). */
834+static int n810bm_measure_backup_batt_voltage(struct n810bm *bm)
835+{
836+ int adc;
837+ unsigned int mv;
838+
839+ adc = retu_adc_average(bm, RETU_ADC_BKUPVOLT, 3);
840+ if (adc < 0)
841+ return adc;
842+ //TODO convert to mV
843+ mv = adc;
844+
845+ return mv;
846+}
847+
848+/* Measure the battery temperature. Returns the value in K (or negative value on error). */
849+static int n810bm_measure_batt_temp(struct n810bm *bm)
850+{
851+ int adc;
852+ unsigned int k;
853+
854+ adc = retu_adc_average(bm, RETU_ADC_BATTEMP, 3);
855+ if (adc < 0)
856+ return adc;
857+ //TODO convert to K
858+ k = adc;
859+
860+ return k;
861+}
862+
863+/* Read the battery capacity via BSI pin. */
864+static enum n810bm_capacity n810bm_read_batt_capacity(struct n810bm *bm)
865+{
866+ int adc;
867+ const unsigned int hyst = 20;
868+
869+ adc = retu_adc_average(bm, RETU_ADC_BSI, 5);
870+ if (adc < 0) {
871+ dev_err(&bm->pdev->dev, "Failed to read BSI ADC");
872+ return N810BM_CAP_UNKNOWN;
873+ }
874+
875+ if (adc >= 0x3B5 - hyst && adc <= 0x3B5 + hyst)
876+ return N810BM_CAP_1500MAH;
877+
878+ dev_err(&bm->pdev->dev, "Capacity indicator 0x%X unknown", adc);
879+
880+ return N810BM_CAP_UNKNOWN;
881+}
882+
883+/* Convert a battery voltage (in mV) to percentage. */
884+static unsigned int n810bm_mvolt2percent(unsigned int mv)
885+{
886+ const unsigned int minv = 3700;
887+ const unsigned int maxv = 4150;
888+ unsigned int percent;
889+
890+ mv = clamp(mv, minv, maxv);
891+ percent = (mv - minv) * 100 / (maxv - minv);
892+
893+ return percent;
894+}
895+
896+static void n810bm_start_charge(struct n810bm *bm)
897+{
898+ int err;
899+
900+ WARN_ON(!bm->battery_present);
901+ WARN_ON(!bm->charger_present);
902+
903+ /* Set PWM to zero */
904+ bm->active_current_pwm = 0;
905+ tahvo_write(bm, TAHVO_REG_CHGCURR, bm->active_current_pwm);
906+
907+ /* Charge global enable */
908+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
909+ TAHVO_REG_CHGCTL_EN |
910+ TAHVO_REG_CHGCTL_PWMOVR |
911+ TAHVO_REG_CHGCTL_PWMOVRZERO,
912+ TAHVO_REG_CHGCTL_EN);
913+
914+ WARN_ON((int)bm->capacity <= 0);
915+ bm->charger.capacity = bm->capacity;
916+ err = lipocharge_start(&bm->charger);
917+ WARN_ON(err);
918+
919+ /* Initialize current measurement circuitry */
920+ n810bm_enable_current_measure(bm);
921+ n810bm_set_current_measure_timer(bm, 250);
922+
923+ dev_info(&bm->pdev->dev, "Charging battery");
924+ n810bm_notify_charger_state(bm);
925+ n810bm_notify_charger_pwm(bm);
926+}
927+
928+static void n810bm_stop_charge(struct n810bm *bm)
929+{
930+ if (lipocharge_is_charging(&bm->charger)) {
931+ n810bm_set_current_measure_timer(bm, 0);
932+ n810bm_disable_current_measure(bm);
933+ }
934+ lipocharge_stop(&bm->charger);
935+
936+ /* Set PWM to zero */
937+ bm->active_current_pwm = 0;
938+ tahvo_write(bm, TAHVO_REG_CHGCURR, bm->active_current_pwm);
939+
940+ /* Charge global disable */
941+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
942+ TAHVO_REG_CHGCTL_EN |
943+ TAHVO_REG_CHGCTL_PWMOVR |
944+ TAHVO_REG_CHGCTL_PWMOVRZERO,
945+ 0);
946+
947+ dev_info(&bm->pdev->dev, "Not charging battery");
948+ n810bm_notify_charger_state(bm);
949+ n810bm_notify_charger_pwm(bm);
950+}
951+
952+/* Periodic check */
953+static void n810bm_periodic_check_work(struct work_struct *work)
954+{
955+ struct n810bm *bm = container_of(to_delayed_work(work),
956+ struct n810bm, periodic_check_work);
957+ u16 status;
958+ bool battery_was_present, charger_was_present;
959+ int mv;
960+
961+ mutex_lock(&bm->mutex);
962+
963+ status = retu_read(bm, RETU_REG_STATUS);
964+ battery_was_present = bm->battery_present;
965+ charger_was_present = bm->charger_present;
966+ bm->battery_present = !!(status & RETU_REG_STATUS_BATAVAIL);
967+ bm->charger_present = !!(status & RETU_REG_STATUS_CHGPLUG);
968+
969+ if (bm->battery_present != battery_was_present) {
970+ /* Battery state changed */
971+ if (bm->battery_present) {
972+ bm->capacity = n810bm_read_batt_capacity(bm);
973+ if (bm->capacity == N810BM_CAP_UNKNOWN) {
974+ dev_err(&bm->pdev->dev, "Unknown battery detected");
975+ } else {
976+ dev_info(&bm->pdev->dev, "Detected %u mAh battery",
977+ (unsigned int)bm->capacity);
978+ }
979+ } else {
980+ bm->capacity = N810BM_CAP_NONE;
981+ dev_info(&bm->pdev->dev, "The main battery was removed");
982+ //TODO disable charging
983+ }
984+ }
985+
986+ if (bm->charger_present != charger_was_present) {
987+ /* Charger state changed */
988+ dev_info(&bm->pdev->dev, "The charger was %s",
989+ bm->charger_present ? "plugged in" : "removed");
990+ n810bm_notify_charger_present(bm);
991+ }
992+
993+ if ((bm->battery_present && !bm->charger_present) ||
994+ !n810bm_known_battery_present(bm)){
995+ /* We're draining the battery */
996+ mv = n810bm_measure_batt_voltage(bm);
997+ if (mv < 0) {
998+ n810bm_emergency(bm,
999+ "check: Failed to measure voltage");
1000+ }
1001+ if (mv < N810BM_MIN_VOLTAGE_THRES) {
1002+ n810bm_emergency(bm,
1003+ "check: Minimum voltage threshold reached");
1004+ }
1005+ }
1006+
1007+ if (bm->charger_present && n810bm_known_battery_present(bm)) {
1008+ /* Known battery and charger are connected */
1009+ if (bm->charger_enabled) {
1010+ /* Charger is enabled */
1011+ if (!lipocharge_is_charging(&bm->charger)) {
1012+ //TODO start charging, if battery is below some threshold
1013+ n810bm_start_charge(bm);
1014+ }
1015+ }
1016+ }
1017+
1018+ if (lipocharge_is_charging(&bm->charger) && !bm->charger_present) {
1019+ /* Charger was unplugged. */
1020+ n810bm_stop_charge(bm);
1021+ }
1022+
1023+ mutex_unlock(&bm->mutex);
1024+ schedule_delayed_work(&bm->periodic_check_work,
1025+ round_jiffies_relative(N810BM_CHECK_INTERVAL));
1026+}
1027+
1028+/*XXX
1029+static void n810bm_adc_irq_handler(unsigned long data)
1030+{
1031+ struct n810bm *bm = (struct n810bm *)data;
1032+
1033+ retu_ack_irq(RETU_INT_ADCS);
1034+ //TODO
1035+dev_info(&bm->pdev->dev, "ADC interrupt triggered\n");
1036+}
1037+*/
1038+
1039+static irqreturn_t n810bm_tahvo_current_measure_irq_handler(int irq, void *data)
1040+{
1041+ struct n810bm *bm = data;
1042+ int res, ma, mv, temp;
1043+
1044+ mutex_lock(&bm->mutex);
1045+ if (!lipocharge_is_charging(&bm->charger))
1046+ goto out_unlock;
1047+
1048+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
1049+ TAHVO_REG_CHGCTL_PWMOVR |
1050+ TAHVO_REG_CHGCTL_PWMOVRZERO,
1051+ TAHVO_REG_CHGCTL_PWMOVR);
1052+ ma = n810bm_measure_batt_current(bm);
1053+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
1054+ TAHVO_REG_CHGCTL_PWMOVR |
1055+ TAHVO_REG_CHGCTL_PWMOVRZERO,
1056+ TAHVO_REG_CHGCTL_PWMOVR |
1057+ TAHVO_REG_CHGCTL_PWMOVRZERO);
1058+ msleep(10);
1059+ mv = n810bm_measure_batt_voltage(bm);
1060+ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
1061+ TAHVO_REG_CHGCTL_PWMOVR |
1062+ TAHVO_REG_CHGCTL_PWMOVRZERO,
1063+ 0);
1064+ temp = n810bm_measure_batt_temp(bm);
1065+ if (WARN_ON(mv < 0))
1066+ goto out_unlock;
1067+ if (WARN_ON(temp < 0))
1068+ goto out_unlock;
1069+
1070+ if (bm->verbose_charge_log) {
1071+ dev_info(&bm->pdev->dev,
1072+ "Battery charge state: %d mV, %d mA (%s)",
1073+ mv, ma,
1074+ (ma <= 0) ? "discharging" : "charging");
1075+ }
1076+ res = lipocharge_update_state(&bm->charger, mv, ma, temp);
1077+ if (res) {
1078+ if (res > 0)
1079+ dev_info(&bm->pdev->dev, "Battery fully charged");
1080+ n810bm_stop_charge(bm);
1081+ }
1082+out_unlock:
1083+ mutex_unlock(&bm->mutex);
1084+
1085+ return IRQ_HANDLED;
1086+}
1087+
1088+#define DEFINE_ATTR_NOTIFY(attr_name) \
1089+ void n810bm_notify_##attr_name(struct n810bm *bm) \
1090+ { \
1091+ set_bit(N810BM_NOTIFY_##attr_name, &bm->notify_flags); \
1092+ wmb(); \
1093+ schedule_work(&bm->notify_work); \
1094+ }
1095+
1096+#define DEFINE_SHOW_INT_FUNC(name, member) \
1097+ static ssize_t n810bm_attr_##name##_show(struct device *dev, \
1098+ struct device_attribute *attr, \
1099+ char *buf) \
1100+ { \
1101+ struct n810bm *bm = device_to_n810bm(dev); \
1102+ ssize_t count; \
1103+ \
1104+ mutex_lock(&bm->mutex); \
1105+ count = snprintf(buf, PAGE_SIZE, "%d\n", (int)(bm->member)); \
1106+ mutex_unlock(&bm->mutex); \
1107+ \
1108+ return count; \
1109+ }
1110+
1111+#define DEFINE_STORE_INT_FUNC(name, member) \
1112+ static ssize_t n810bm_attr_##name##_store(struct device *dev, \
1113+ struct device_attribute *attr,\
1114+ const char *buf, size_t count)\
1115+ { \
1116+ struct n810bm *bm = device_to_n810bm(dev); \
1117+ long val; \
1118+ int err; \
1119+ \
1120+ mutex_lock(&bm->mutex); \
1121+ err = strict_strtol(buf, 0, &val); \
1122+ if (!err) \
1123+ bm->member = (typeof(bm->member))val; \
1124+ mutex_unlock(&bm->mutex); \
1125+ \
1126+ return err ? err : count; \
1127+ }
1128+
1129+#define DEFINE_ATTR_SHOW_INT(name, member) \
1130+ DEFINE_SHOW_INT_FUNC(name, member) \
1131+ static DEVICE_ATTR(name, S_IRUGO, \
1132+ n810bm_attr_##name##_show, NULL);
1133+
1134+#define DEFINE_ATTR_SHOW_STORE_INT(name, member) \
1135+ DEFINE_SHOW_INT_FUNC(name, member) \
1136+ DEFINE_STORE_INT_FUNC(name, member) \
1137+ static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \
1138+ n810bm_attr_##name##_show, \
1139+ n810bm_attr_##name##_store);
1140+
1141+DEFINE_ATTR_SHOW_INT(battery_present, battery_present);
1142+DEFINE_ATTR_SHOW_INT(charger_present, charger_present);
1143+static DEFINE_ATTR_NOTIFY(charger_present);
1144+DEFINE_ATTR_SHOW_INT(charger_state, charger.state);
1145+static DEFINE_ATTR_NOTIFY(charger_state);
1146+DEFINE_ATTR_SHOW_INT(charger_pwm, active_current_pwm);
1147+static DEFINE_ATTR_NOTIFY(charger_pwm);
1148+DEFINE_ATTR_SHOW_STORE_INT(charger_enable, charger_enabled);
1149+DEFINE_ATTR_SHOW_STORE_INT(charger_verbose, verbose_charge_log);
1150+
1151+static ssize_t n810bm_attr_battery_level_show(struct device *dev,
1152+ struct device_attribute *attr,
1153+ char *buf)
1154+{
1155+ struct n810bm *bm = device_to_n810bm(dev);
1156+ ssize_t count = -ENODEV;
1157+ int millivolt;
1158+
1159+ mutex_lock(&bm->mutex);
1160+ if (!bm->battery_present || lipocharge_is_charging(&bm->charger))
1161+ millivolt = 0;
1162+ else
1163+ millivolt = n810bm_measure_batt_voltage(bm);
1164+ if (millivolt >= 0) {
1165+ count = snprintf(buf, PAGE_SIZE, "%u\n",
1166+ n810bm_mvolt2percent(millivolt));
1167+ }
1168+ mutex_unlock(&bm->mutex);
1169+
1170+ return count;
1171+}
1172+static DEVICE_ATTR(battery_level, S_IRUGO,
1173+ n810bm_attr_battery_level_show, NULL);
1174+
1175+static ssize_t n810bm_attr_battery_capacity_show(struct device *dev,
1176+ struct device_attribute *attr,
1177+ char *buf)
1178+{
1179+ struct n810bm *bm = device_to_n810bm(dev);
1180+ ssize_t count;
1181+ int capacity = 0;
1182+
1183+ mutex_lock(&bm->mutex);
1184+ if (n810bm_known_battery_present(bm))
1185+ capacity = (int)bm->capacity;
1186+ count = snprintf(buf, PAGE_SIZE, "%d\n", capacity);
1187+ mutex_unlock(&bm->mutex);
1188+
1189+ return count;
1190+}
1191+static DEVICE_ATTR(battery_capacity, S_IRUGO,
1192+ n810bm_attr_battery_capacity_show, NULL);
1193+
1194+static ssize_t n810bm_attr_battery_temp_show(struct device *dev,
1195+ struct device_attribute *attr,
1196+ char *buf)
1197+{
1198+ struct n810bm *bm = device_to_n810bm(dev);
1199+ ssize_t count = -ENODEV;
1200+ int k;
1201+
1202+ mutex_lock(&bm->mutex);
1203+ k = n810bm_measure_batt_temp(bm);
1204+ if (k >= 0)
1205+ count = snprintf(buf, PAGE_SIZE, "%d\n", k);
1206+ mutex_unlock(&bm->mutex);
1207+
1208+ return count;
1209+}
1210+static DEVICE_ATTR(battery_temp, S_IRUGO,
1211+ n810bm_attr_battery_temp_show, NULL);
1212+
1213+static ssize_t n810bm_attr_charger_voltage_show(struct device *dev,
1214+ struct device_attribute *attr,
1215+ char *buf)
1216+{
1217+ struct n810bm *bm = device_to_n810bm(dev);
1218+ ssize_t count = -ENODEV;
1219+ int mv = 0;
1220+
1221+ mutex_lock(&bm->mutex);
1222+ if (bm->charger_present)
1223+ mv = n810bm_measure_charger_voltage(bm);
1224+ if (mv >= 0)
1225+ count = snprintf(buf, PAGE_SIZE, "%d\n", mv);
1226+ mutex_unlock(&bm->mutex);
1227+
1228+ return count;
1229+}
1230+static DEVICE_ATTR(charger_voltage, S_IRUGO,
1231+ n810bm_attr_charger_voltage_show, NULL);
1232+
1233+static ssize_t n810bm_attr_backup_battery_voltage_show(struct device *dev,
1234+ struct device_attribute *attr,
1235+ char *buf)
1236+{
1237+ struct n810bm *bm = device_to_n810bm(dev);
1238+ ssize_t count = -ENODEV;
1239+ int mv;
1240+
1241+ mutex_lock(&bm->mutex);
1242+ mv = n810bm_measure_backup_batt_voltage(bm);
1243+ if (mv >= 0)
1244+ count = snprintf(buf, PAGE_SIZE, "%d\n", mv);
1245+ mutex_unlock(&bm->mutex);
1246+
1247+ return count;
1248+}
1249+static DEVICE_ATTR(backup_battery_voltage, S_IRUGO,
1250+ n810bm_attr_backup_battery_voltage_show, NULL);
1251+
1252+static ssize_t n810bm_attr_battery_current_show(struct device *dev,
1253+ struct device_attribute *attr,
1254+ char *buf)
1255+{
1256+ struct n810bm *bm = device_to_n810bm(dev);
1257+ ssize_t count = -ENODEV;
1258+ int ma = 0;
1259+
1260+ mutex_lock(&bm->mutex);
1261+ if (bm->battery_present)
1262+ ma = n810bm_measure_batt_current_async(bm);
1263+ count = snprintf(buf, PAGE_SIZE, "%d\n", ma);
1264+ mutex_unlock(&bm->mutex);
1265+
1266+ return count;
1267+}
1268+static DEVICE_ATTR(battery_current, S_IRUGO,
1269+ n810bm_attr_battery_current_show, NULL);
1270+
1271+static const struct device_attribute *n810bm_attrs[] = {
1272+ &dev_attr_battery_present,
1273+ &dev_attr_battery_level,
1274+ &dev_attr_battery_current,
1275+ &dev_attr_battery_capacity,
1276+ &dev_attr_battery_temp,
1277+ &dev_attr_backup_battery_voltage,
1278+ &dev_attr_charger_present,
1279+ &dev_attr_charger_state,
1280+ &dev_attr_charger_verbose,
1281+ &dev_attr_charger_voltage,
1282+ &dev_attr_charger_enable,
1283+ &dev_attr_charger_pwm,
1284+};
1285+
1286+static void n810bm_notify_work(struct work_struct *work)
1287+{
1288+ struct n810bm *bm = container_of(work, struct n810bm, notify_work);
1289+ unsigned long notify_flags;
1290+
1291+ notify_flags = xchg(&bm->notify_flags, 0);
1292+ mb();
1293+
1294+#define do_notify(attr_name) \
1295+ do { \
1296+ if (notify_flags & (1 << N810BM_NOTIFY_##attr_name)) { \
1297+ sysfs_notify(&bm->pdev->dev.kobj, NULL, \
1298+ dev_attr_##attr_name.attr.name); \
1299+ } \
1300+ } while (0)
1301+
1302+ do_notify(charger_present);
1303+ do_notify(charger_state);
1304+ do_notify(charger_pwm);
1305+}
1306+
1307+static int n810bm_charger_set_current_pwm(struct lipocharge *c,
1308+ unsigned int duty_cycle)
1309+{
1310+ struct n810bm *bm = container_of(c, struct n810bm, charger);
1311+ int err = -EINVAL;
1312+
1313+ WARN_ON(!mutex_is_locked(&bm->mutex));
1314+ if (WARN_ON(duty_cycle > 0xFF))
1315+ goto out;
1316+ if (WARN_ON(!bm->charger_enabled))
1317+ goto out;
1318+ if (WARN_ON(!bm->battery_present || !bm->charger_present))
1319+ goto out;
1320+
1321+ if (duty_cycle != bm->active_current_pwm) {
1322+ bm->active_current_pwm = duty_cycle;
1323+ tahvo_write(bm, TAHVO_REG_CHGCURR, duty_cycle);
1324+ n810bm_notify_charger_pwm(bm);
1325+ }
1326+
1327+ err = 0;
1328+out:
1329+
1330+ return err;
1331+}
1332+
1333+static void n810bm_charger_emergency(struct lipocharge *c)
1334+{
1335+ struct n810bm *bm = container_of(c, struct n810bm, charger);
1336+
1337+ n810bm_emergency(bm, "Battery charger fault");
1338+}
1339+
1340+static void n810bm_hw_exit(struct n810bm *bm)
1341+{
1342+ n810bm_stop_charge(bm);
1343+ retu_write(bm, RETU_REG_ADCSCR, 0);
1344+}
1345+
1346+static int n810bm_hw_init(struct n810bm *bm)
1347+{
1348+ int err;
1349+
1350+ err = n810bm_check_adc_sanity(bm);
1351+ if (err)
1352+ return err;
1353+
1354+ n810bm_stop_charge(bm);
1355+
1356+ return 0;
1357+}
1358+
1359+static void n810bm_cancel_and_flush_work(struct n810bm *bm)
1360+{
1361+ cancel_delayed_work_sync(&bm->periodic_check_work);
1362+ cancel_work_sync(&bm->notify_work);
1363+ flush_scheduled_work();
1364+}
1365+
1366+static int n810bm_device_init(struct n810bm *bm)
1367+{
1368+ int attr_index;
1369+ int err;
1370+
1371+ bm->charger.rate = LIPORATE_p6C;
1372+ bm->charger.top_voltage = 4100;
1373+ bm->charger.duty_cycle_max = 0xFF;
1374+ bm->charger.set_current_pwm = n810bm_charger_set_current_pwm;
1375+ bm->charger.emergency = n810bm_charger_emergency;
1376+ lipocharge_init(&bm->charger, &bm->pdev->dev);
1377+
1378+ err = n810bm_hw_init(bm);
1379+ if (err)
1380+ goto error;
1381+ for (attr_index = 0; attr_index < ARRAY_SIZE(n810bm_attrs); attr_index++) {
1382+ err = device_create_file(&bm->pdev->dev, n810bm_attrs[attr_index]);
1383+ if (err)
1384+ goto err_unwind_attrs;
1385+ }
1386+/*XXX
1387+ err = retu_request_irq(RETU_INT_ADCS,
1388+ n810bm_adc_irq_handler,
1389+ (unsigned long)bm, "n810bm");
1390+ if (err)
1391+ goto err_unwind_attrs;
1392+*/
1393+ bm->tahvo_irq = platform_get_irq(n810bm_tahvo_device, 0);
1394+ err = request_threaded_irq(bm->tahvo_irq, NULL,
1395+ n810bm_tahvo_current_measure_irq_handler,
1396+ IRQF_ONESHOT, "tahvo-n810bm", bm);
1397+ if (err)
1398+ goto err_free_retu_irq;
1399+ disable_irq_nosync(bm->tahvo_irq);
1400+ bm->tahvo_irq_enabled = 0;
1401+
1402+ schedule_delayed_work(&bm->periodic_check_work,
1403+ round_jiffies_relative(N810BM_CHECK_INTERVAL));
1404+
1405+ bm->initialized = 1;
1406+ dev_info(&bm->pdev->dev, "Battery management initialized");
1407+
1408+ return 0;
1409+
1410+err_free_retu_irq:
1411+//XXX retu_free_irq(RETU_INT_ADCS);
1412+err_unwind_attrs:
1413+ for (attr_index--; attr_index >= 0; attr_index--)
1414+ device_remove_file(&bm->pdev->dev, n810bm_attrs[attr_index]);
1415+/*err_exit:*/
1416+ n810bm_hw_exit(bm);
1417+error:
1418+ n810bm_cancel_and_flush_work(bm);
1419+
1420+ return err;
1421+}
1422+
1423+static void n810bm_device_exit(struct n810bm *bm)
1424+{
1425+ int i;
1426+
1427+ if (!bm->initialized)
1428+ return;
1429+
1430+ lipocharge_exit(&bm->charger);
1431+ free_irq(bm->tahvo_irq, bm);
1432+//XXX retu_free_irq(RETU_INT_ADCS);
1433+ for (i = 0; i < ARRAY_SIZE(n810bm_attrs); i++)
1434+ device_remove_file(&bm->pdev->dev, n810bm_attrs[i]);
1435+
1436+ n810bm_cancel_and_flush_work(bm);
1437+
1438+ n810bm_hw_exit(bm);
1439+
1440+ bm->initialized = 0;
1441+}
1442+
1443+static void n810bm_pmm_block_found(const struct firmware *fw, void *context)
1444+{
1445+ struct n810bm *bm = context;
1446+ int err;
1447+
1448+ if (!fw) {
1449+ dev_err(&bm->pdev->dev,
1450+ "CAL PMM block image file not found");
1451+ goto err_release;
1452+ }
1453+ if (fw->size != N810BM_PMM_BLOCK_SIZE ||
1454+ memcmp(fw->data, "BME-PMM-BLOCK01", 15) != 0) {
1455+ dev_err(&bm->pdev->dev,
1456+ "CAL PMM block image file has an invalid format");
1457+ goto err_release;
1458+ }
1459+
1460+ err = n810bm_parse_pmm_block(bm, fw);
1461+ if (err)
1462+ goto err_release;
1463+ release_firmware(fw);
1464+
1465+ err = n810bm_device_init(bm);
1466+ if (err) {
1467+ dev_err(&bm->pdev->dev,
1468+ "Failed to initialized battery management (%d)", err);
1469+ goto error;
1470+ }
1471+
1472+ return;
1473+err_release:
1474+ release_firmware(fw);
1475+error:
1476+ return;
1477+}
1478+
1479+static int __devinit n810bm_probe(void)
1480+{
1481+ struct n810bm *bm;
1482+ int err;
1483+
1484+ if (!n810bm_retu_device || !n810bm_tahvo_device)
1485+ return 0;
1486+
1487+ bm = kzalloc(sizeof(*bm), GFP_KERNEL);
1488+ if (!bm)
1489+ return -ENOMEM;
1490+ bm->pdev = n810bm_retu_device;
1491+ platform_set_drvdata(n810bm_retu_device, bm);
1492+ platform_set_drvdata(n810bm_tahvo_device, bm);
1493+ mutex_init(&bm->mutex);
1494+ INIT_DELAYED_WORK(&bm->periodic_check_work, n810bm_periodic_check_work);
1495+ INIT_WORK(&bm->notify_work, n810bm_notify_work);
1496+
1497+ dev_info(&bm->pdev->dev, "Requesting CAL BME PMM block firmware file "
1498+ N810BM_PMM_BLOCK_FILENAME);
1499+ err = request_firmware_nowait(THIS_MODULE, 1,
1500+ N810BM_PMM_BLOCK_FILENAME,
1501+ &bm->pdev->dev, GFP_KERNEL,
1502+ bm, n810bm_pmm_block_found);
1503+ if (err) {
1504+ dev_err(&bm->pdev->dev,
1505+ "Failed to request CAL PMM block image file (%d)", err);
1506+ goto err_free;
1507+ }
1508+
1509+ return 0;
1510+
1511+err_free:
1512+ kfree(bm);
1513+
1514+ return err;
1515+}
1516+
1517+static void __devexit n810bm_remove(void)
1518+{
1519+ struct n810bm *bm;
1520+
1521+ if (!n810bm_retu_device || !n810bm_tahvo_device)
1522+ return;
1523+ bm = platform_get_drvdata(n810bm_retu_device);
1524+
1525+ n810bm_device_exit(bm);
1526+
1527+ kfree(bm);
1528+ platform_set_drvdata(n810bm_retu_device, NULL);
1529+ platform_set_drvdata(n810bm_tahvo_device, NULL);
1530+}
1531+
1532+static int __devinit n810bm_retu_probe(struct platform_device *pdev)
1533+{
1534+ n810bm_retu_device = pdev;
1535+ return n810bm_probe();
1536+}
1537+
1538+static int __devexit n810bm_retu_remove(struct platform_device *pdev)
1539+{
1540+ n810bm_remove();
1541+ n810bm_retu_device = NULL;
1542+ return 0;
1543+}
1544+
1545+static int __devinit n810bm_tahvo_probe(struct platform_device *pdev)
1546+{
1547+ n810bm_tahvo_device = pdev;
1548+ return n810bm_probe();
1549+}
1550+
1551+static int __devexit n810bm_tahvo_remove(struct platform_device *pdev)
1552+{
1553+ n810bm_remove();
1554+ n810bm_tahvo_device = NULL;
1555+ return 0;
1556+}
1557+
1558+static struct platform_driver n810bm_retu_driver = {
1559+ .remove = __devexit_p(n810bm_retu_remove),
1560+ .driver = {
1561+ .name = "retu-n810bm",
1562+ }
1563+};
1564+
1565+static struct platform_driver n810bm_tahvo_driver = {
1566+ .remove = __devexit_p(n810bm_tahvo_remove),
1567+ .driver = {
1568+ .name = "tahvo-n810bm",
1569+ }
1570+};
1571+
1572+static int __init n810bm_modinit(void)
1573+{
1574+ int err;
1575+
1576+ err = platform_driver_probe(&n810bm_retu_driver, n810bm_retu_probe);
1577+ if (err)
1578+ return err;
1579+ err = platform_driver_probe(&n810bm_tahvo_driver, n810bm_tahvo_probe);
1580+ if (err) {
1581+ platform_driver_unregister(&n810bm_retu_driver);
1582+ return err;
1583+ }
1584+
1585+ return 0;
1586+}
1587+module_init(n810bm_modinit);
1588+
1589+static void __exit n810bm_modexit(void)
1590+{
1591+ platform_driver_unregister(&n810bm_tahvo_driver);
1592+ platform_driver_unregister(&n810bm_retu_driver);
1593+}
1594+module_exit(n810bm_modexit);
1595+
1596+MODULE_DESCRIPTION("Nokia n810 battery management");
1597+MODULE_FIRMWARE(N810BM_PMM_BLOCK_FILENAME);
1598+MODULE_LICENSE("GPL");
1599+MODULE_AUTHOR("Michael Buesch");
1600--- /dev/null
1601+++ b/drivers/cbus/lipocharge.c
1602@@ -0,0 +1,183 @@
1603+/*
1604+ * Generic LIPO battery charger
1605+ *
1606+ * Copyright (c) 2010-2011 Michael Buesch <mb@bu3sch.de>
1607+ *
1608+ * This program is free software; you can redistribute it and/or
1609+ * modify it under the terms of the GNU General Public License
1610+ * as published by the Free Software Foundation; either version 2
1611+ * of the License, or (at your option) any later version.
1612+ *
1613+ * This program is distributed in the hope that it will be useful,
1614+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1615+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616+ * GNU General Public License for more details.
1617+ */
1618+
1619+#define DEBUG
1620+
1621+#include "lipocharge.h"
1622+
1623+#include <linux/slab.h>
1624+
1625+
1626+/* Hysteresis constants */
1627+#define CURRENT_HYST 30 /* mA */
1628+#define VOLTAGE_HYST 10 /* mV */
1629+
1630+/* Threshold constants */
1631+#define FINISH_CURRENT_PERCENT 3
1632+
1633+
1634+/* Returns the requested first-stage charge current in mA */
1635+static inline unsigned int get_stage1_charge_current(struct lipocharge *c)
1636+{
1637+ /* current = (capacity * C) */
1638+ return c->capacity * c->rate / 1000;
1639+}
1640+
1641+void lipocharge_init(struct lipocharge *c, struct device *dev)
1642+{
1643+ c->dev = dev;
1644+ c->state = LIPO_IDLE;
1645+}
1646+
1647+void lipocharge_exit(struct lipocharge *c)
1648+{
1649+ c->state = LIPO_IDLE;
1650+}
1651+
1652+int lipocharge_start(struct lipocharge *c)
1653+{
1654+ int err;
1655+
1656+ if (c->state != LIPO_IDLE)
1657+ return -EBUSY;
1658+ if (!c->set_current_pwm || !c->emergency)
1659+ return -EINVAL;
1660+ if (!c->top_voltage || c->top_voltage > 4200)
1661+ return -EINVAL;
1662+
1663+ c->active_duty_cycle = 0;
1664+ err = c->set_current_pwm(c, c->active_duty_cycle);
1665+ if (err)
1666+ return err;
1667+ c->state = LIPO_FIRST_STAGE;
1668+
1669+ return 0;
1670+}
1671+
1672+void lipocharge_stop(struct lipocharge *c)
1673+{
1674+ if (c->state == LIPO_IDLE)
1675+ return;
1676+ c->state = LIPO_IDLE;
1677+}
1678+
1679+static int lipocharge_increase_current(struct lipocharge *c,
1680+ unsigned int inc_permille)
1681+{
1682+ int old_pwm, new_pwm;
1683+
1684+ if (c->active_duty_cycle >= c->duty_cycle_max)
1685+ return 0;
1686+
1687+ old_pwm = c->active_duty_cycle;
1688+ new_pwm = old_pwm + (c->duty_cycle_max * inc_permille / 1000);
1689+ new_pwm = min(new_pwm, (int)c->duty_cycle_max);
1690+ c->active_duty_cycle = new_pwm;
1691+
1692+ dev_dbg(c->dev, "lipo: Increasing duty_cycle by "
1693+ "%u permille (0x%02X -> 0x%02X)",
1694+ inc_permille, old_pwm, new_pwm);
1695+
1696+ return c->set_current_pwm(c, c->active_duty_cycle);
1697+}
1698+
1699+static int lipocharge_decrease_current(struct lipocharge *c,
1700+ unsigned int dec_permille)
1701+{
1702+ int old_pwm, new_pwm;
1703+
1704+ if (c->active_duty_cycle <= 0)
1705+ return 0;
1706+
1707+ old_pwm = c->active_duty_cycle;
1708+ new_pwm = old_pwm - (c->duty_cycle_max * dec_permille / 1000);
1709+ new_pwm = max(0, new_pwm);
1710+ c->active_duty_cycle = new_pwm;
1711+
1712+ dev_dbg(c->dev, "lipo: Decreasing duty_cycle by "
1713+ "%u permille (0x%02X -> 0x%02X)",
1714+ dec_permille, old_pwm, new_pwm);
1715+
1716+ return c->set_current_pwm(c, c->active_duty_cycle);
1717+}
1718+
1719+/** lipocharge_update_state - Update the charge state
1720+ * @c: The context.
1721+ * @voltage_mV: The measured battery voltage.
1722+ * @current_mA: The measured charge current.
1723+ * negative -> drain.
1724+ * positive -> charge.
1725+ * @temp_K: Battery temperature in K.
1726+ *
1727+ * Returns 0 on success, -1 on error.
1728+ * Returns 1, if the charging process is finished.
1729+ */
1730+int lipocharge_update_state(struct lipocharge *c,
1731+ unsigned int voltage_mV,
1732+ int current_mA,
1733+ unsigned int temp_K)
1734+{
1735+ int requested_current, current_diff;
1736+ int err;
1737+ unsigned int permille;
1738+
1739+ //TODO temp
1740+
1741+restart:
1742+ switch (c->state) {
1743+ case LIPO_IDLE:
1744+ dev_err(c->dev, "%s: called while idle", __func__);
1745+ return -EINVAL;
1746+ case LIPO_FIRST_STAGE: /* Constant current */
1747+//printk("GOT %u %d %u\n", voltage_mV, current_mA, temp_K);
1748+ if (voltage_mV >= c->top_voltage) {
1749+ /* Float voltage reached.
1750+ * Switch charger mode to "constant current" */
1751+ c->state = LIPO_SECOND_STAGE;
1752+ dev_dbg(c->dev, "Switched to second charging stage.");
1753+ goto restart;
1754+ }
1755+ /* Float voltage not reached, yet.
1756+ * Try to get the requested constant current. */
1757+ requested_current = get_stage1_charge_current(c);
1758+ if (current_mA < 0)
1759+ current_mA = 0;
1760+ current_diff = requested_current - current_mA;
1761+ if (abs(requested_current - current_mA) > CURRENT_HYST) {
1762+ if (current_diff > 0) {
1763+ /* Increase current */
1764+ permille = current_diff * 1000 / requested_current;
1765+ permille /= 2;
1766+ err = lipocharge_increase_current(c, permille);
1767+ if (err)
1768+ return err;
1769+ } else {
1770+ /* Decrease current */
1771+ permille = (-current_diff) * 1000 / requested_current;
1772+ permille /= 2;
1773+ err = lipocharge_decrease_current(c, permille);
1774+ if (err)
1775+ return err;
1776+ }
1777+ }
1778+ break;
1779+ case LIPO_SECOND_STAGE: /* Constant voltage */
1780+ //TODO
1781+ break;
1782+ }
1783+
1784+ return 0;
1785+}
1786--- /dev/null
1787+++ b/drivers/cbus/lipocharge.h
1788@@ -0,0 +1,60 @@
1789+#ifndef LIPOCHARGE_H_
1790+#define LIPOCHARGE_H_
1791+
1792+#include <linux/types.h>
1793+#include <linux/device.h>
1794+
1795+
1796+#define LIPORATE(a,b) (((a) * 1000) + ((b) * 100))
1797+#define LIPORATE_p6C LIPORATE(0,6) /* 0.6C */
1798+
1799+enum lipocharge_state {
1800+ LIPO_IDLE = 0, /* Not charging */
1801+ LIPO_FIRST_STAGE, /* Charging: constant current */
1802+ LIPO_SECOND_STAGE, /* Charging: constant voltage */
1803+};
1804+
1805+/** struct lipocharge - A generic LIPO charger
1806+ *
1807+ * @capacity: Battery capacity in mAh.
1808+ * @rate: Charge rate.
1809+ * @top_voltage: Fully charged voltage, in mV.
1810+ * @duty_cycle_max: Max value for duty_cycle.
1811+ *
1812+ * @set_charge_current: Set the charge current PWM duty cycle.
1813+ * @emergency: Something went wrong. Force shutdown.
1814+ */
1815+struct lipocharge {
1816+ unsigned int capacity;
1817+ unsigned int rate;
1818+ unsigned int top_voltage;
1819+ unsigned int duty_cycle_max;
1820+
1821+ int (*set_current_pwm)(struct lipocharge *c, unsigned int duty_cycle);
1822+ void (*emergency)(struct lipocharge *c);
1823+
1824+ /* internal */
1825+ struct device *dev;
1826+ enum lipocharge_state state;
1827+ unsigned int active_duty_cycle;
1828+
1829+ //TODO implement timer to cut power after maximum charge time.
1830+};
1831+
1832+void lipocharge_init(struct lipocharge *c, struct device *dev);
1833+void lipocharge_exit(struct lipocharge *c);
1834+
1835+int lipocharge_start(struct lipocharge *c);
1836+void lipocharge_stop(struct lipocharge *c);
1837+
1838+int lipocharge_update_state(struct lipocharge *c,
1839+ unsigned int voltage_mV,
1840+ int current_mA,
1841+ unsigned int temp_K);
1842+
1843+static inline bool lipocharge_is_charging(struct lipocharge *c)
1844+{
1845+ return (c->state != LIPO_IDLE);
1846+}
1847+
1848+#endif /* LIPOCHARGE_H_ */
1849--- a/drivers/cbus/cbus.c
1850+++ b/drivers/cbus/cbus.c
1851@@ -34,6 +34,7 @@
1852 #include <linux/gpio.h>
1853 #include <linux/platform_device.h>
1854 #include <linux/platform_data/cbus.h>
1855+#include <linux/reboot.h>
1856 
1857 #include "cbus.h"
1858 
1859@@ -324,6 +325,13 @@ static void __exit cbus_bus_exit(void)
1860 }
1861 module_exit(cbus_bus_exit);
1862 
1863+void cbus_emergency(void)
1864+{
1865+ machine_power_off();
1866+ panic("cbus: Failed to halt machine in emergency state\n");
1867+}
1868+EXPORT_SYMBOL(cbus_emergency);
1869+
1870 MODULE_DESCRIPTION("CBUS serial protocol");
1871 MODULE_LICENSE("GPL");
1872 MODULE_AUTHOR("Juha Yrjölä");
1873--- a/drivers/cbus/cbus.h
1874+++ b/drivers/cbus/cbus.h
1875@@ -27,4 +27,6 @@ extern int cbus_read_reg(struct device *
1876 extern int cbus_write_reg(struct device *, unsigned dev, unsigned reg,
1877         unsigned val);
1878 
1879+NORET_TYPE void cbus_emergency(void) ATTRIB_NORET;
1880+
1881 #endif /* __DRIVERS_CBUS_CBUS_H */
1882--- a/drivers/cbus/retu.c
1883+++ b/drivers/cbus/retu.c
1884@@ -399,6 +399,11 @@ static int retu_allocate_children(struct
1885     if (!child)
1886         return -ENOMEM;
1887 
1888+ child = retu_allocate_child("retu-n810bm", parent, irq_base,
1889+ RETU_INT_ADCS, -1, 1);
1890+ if (!child)
1891+ return -ENOMEM;
1892+
1893     return 0;
1894 }
1895 
1896--- a/drivers/cbus/tahvo.c
1897+++ b/drivers/cbus/tahvo.c
1898@@ -129,6 +129,7 @@ void tahvo_set_clear_reg_bits(struct dev
1899     __tahvo_write_reg(tahvo, reg, w);
1900     mutex_unlock(&tahvo->mutex);
1901 }
1902+EXPORT_SYMBOL(tahvo_set_clear_reg_bits);
1903 
1904 static irqreturn_t tahvo_irq_handler(int irq, void *_tahvo)
1905 {
1906@@ -292,6 +293,11 @@ static int tahvo_allocate_children(struc
1907     if (!child)
1908         return -ENOMEM;
1909 
1910+ child = tahvo_allocate_child("tahvo-n810bm", parent,
1911+ irq_base + TAHVO_INT_BATCURR);
1912+ if (!child)
1913+ return -ENOMEM;
1914+
1915     return 0;
1916 }
1917 
1918

Archive Download this file



interactive