1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * DA9150 Fuel-Gauge Driver |
4 | * |
5 | * Copyright (c) 2015 Dialog Semiconductor |
6 | * |
7 | * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/of.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/power_supply.h> |
18 | #include <linux/list.h> |
19 | #include <asm/div64.h> |
20 | #include <linux/mfd/da9150/core.h> |
21 | #include <linux/mfd/da9150/registers.h> |
22 | #include <linux/devm-helpers.h> |
23 | |
24 | /* Core2Wire */ |
25 | #define DA9150_QIF_READ (0x0 << 7) |
26 | #define DA9150_QIF_WRITE (0x1 << 7) |
27 | #define DA9150_QIF_CODE_MASK 0x7F |
28 | |
29 | #define DA9150_QIF_BYTE_SIZE 8 |
30 | #define DA9150_QIF_BYTE_MASK 0xFF |
31 | #define DA9150_QIF_SHORT_SIZE 2 |
32 | #define DA9150_QIF_LONG_SIZE 4 |
33 | |
34 | /* QIF Codes */ |
35 | #define DA9150_QIF_UAVG 6 |
36 | #define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE |
37 | #define DA9150_QIF_IAVG 8 |
38 | #define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE |
39 | #define DA9150_QIF_NTCAVG 12 |
40 | #define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE |
41 | #define DA9150_QIF_SHUNT_VAL 36 |
42 | #define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE |
43 | #define DA9150_QIF_SD_GAIN 38 |
44 | #define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE |
45 | #define DA9150_QIF_FCC_MAH 40 |
46 | #define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE |
47 | #define DA9150_QIF_SOC_PCT 43 |
48 | #define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE |
49 | #define DA9150_QIF_CHARGE_LIMIT 44 |
50 | #define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE |
51 | #define DA9150_QIF_DISCHARGE_LIMIT 45 |
52 | #define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE |
53 | #define DA9150_QIF_FW_MAIN_VER 118 |
54 | #define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE |
55 | #define DA9150_QIF_E_FG_STATUS 126 |
56 | #define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE |
57 | #define DA9150_QIF_SYNC 127 |
58 | #define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE |
59 | #define DA9150_QIF_MAX_CODES 128 |
60 | |
61 | /* QIF Sync Timeout */ |
62 | #define DA9150_QIF_SYNC_TIMEOUT 1000 |
63 | #define DA9150_QIF_SYNC_RETRIES 10 |
64 | |
65 | /* QIF E_FG_STATUS */ |
66 | #define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0) |
67 | #define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1) |
68 | #define DA9150_FG_IRQ_SOC_MASK \ |
69 | (DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK) |
70 | |
71 | /* Private data */ |
72 | struct da9150_fg { |
73 | struct da9150 *da9150; |
74 | struct device *dev; |
75 | |
76 | struct mutex io_lock; |
77 | |
78 | struct power_supply *battery; |
79 | struct delayed_work work; |
80 | u32 interval; |
81 | |
82 | int warn_soc; |
83 | int crit_soc; |
84 | int soc; |
85 | }; |
86 | |
87 | /* Battery Properties */ |
88 | static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size) |
89 | |
90 | { |
91 | u8 buf[DA9150_QIF_LONG_SIZE]; |
92 | u8 read_addr; |
93 | u32 res = 0; |
94 | int i; |
95 | |
96 | /* Set QIF code (READ mode) */ |
97 | read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ; |
98 | |
99 | da9150_read_qif(da9150: fg->da9150, addr: read_addr, count: size, buf); |
100 | for (i = 0; i < size; ++i) |
101 | res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE)); |
102 | |
103 | return res; |
104 | } |
105 | |
106 | static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size, |
107 | u32 val) |
108 | |
109 | { |
110 | u8 buf[DA9150_QIF_LONG_SIZE]; |
111 | u8 write_addr; |
112 | int i; |
113 | |
114 | /* Set QIF code (WRITE mode) */ |
115 | write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE; |
116 | |
117 | for (i = 0; i < size; ++i) { |
118 | buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) & |
119 | DA9150_QIF_BYTE_MASK; |
120 | } |
121 | da9150_write_qif(da9150: fg->da9150, addr: write_addr, count: size, buf); |
122 | } |
123 | |
124 | /* Trigger QIF Sync to update QIF readable data */ |
125 | static void da9150_fg_read_sync_start(struct da9150_fg *fg) |
126 | { |
127 | int i = 0; |
128 | u32 res = 0; |
129 | |
130 | mutex_lock(&fg->io_lock); |
131 | |
132 | /* Check if QIF sync already requested, and write to sync if not */ |
133 | res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, |
134 | DA9150_QIF_SYNC_SIZE); |
135 | if (res > 0) |
136 | da9150_fg_write_attr(fg, DA9150_QIF_SYNC, |
137 | DA9150_QIF_SYNC_SIZE, val: 0); |
138 | |
139 | /* Wait for sync to complete */ |
140 | res = 0; |
141 | while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { |
142 | usleep_range(DA9150_QIF_SYNC_TIMEOUT, |
143 | DA9150_QIF_SYNC_TIMEOUT * 2); |
144 | res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, |
145 | DA9150_QIF_SYNC_SIZE); |
146 | } |
147 | |
148 | /* Check if sync completed */ |
149 | if (res == 0) |
150 | dev_err(fg->dev, "Failed to perform QIF read sync!\n" ); |
151 | } |
152 | |
153 | /* |
154 | * Should always be called after QIF sync read has been performed, and all |
155 | * attributes required have been accessed. |
156 | */ |
157 | static inline void da9150_fg_read_sync_end(struct da9150_fg *fg) |
158 | { |
159 | mutex_unlock(lock: &fg->io_lock); |
160 | } |
161 | |
162 | /* Sync read of single QIF attribute */ |
163 | static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size) |
164 | { |
165 | u32 val; |
166 | |
167 | da9150_fg_read_sync_start(fg); |
168 | val = da9150_fg_read_attr(fg, code, size); |
169 | da9150_fg_read_sync_end(fg); |
170 | |
171 | return val; |
172 | } |
173 | |
174 | /* Wait for QIF Sync, write QIF data and wait for ack */ |
175 | static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size, |
176 | u32 val) |
177 | { |
178 | int i = 0; |
179 | u32 res = 0, sync_val; |
180 | |
181 | mutex_lock(&fg->io_lock); |
182 | |
183 | /* Check if QIF sync already requested */ |
184 | res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, |
185 | DA9150_QIF_SYNC_SIZE); |
186 | |
187 | /* Wait for an existing sync to complete */ |
188 | while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { |
189 | usleep_range(DA9150_QIF_SYNC_TIMEOUT, |
190 | DA9150_QIF_SYNC_TIMEOUT * 2); |
191 | res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, |
192 | DA9150_QIF_SYNC_SIZE); |
193 | } |
194 | |
195 | if (res == 0) { |
196 | dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n" ); |
197 | mutex_unlock(lock: &fg->io_lock); |
198 | return; |
199 | } |
200 | |
201 | /* Write value for QIF code */ |
202 | da9150_fg_write_attr(fg, code, size, val); |
203 | |
204 | /* Wait for write acknowledgment */ |
205 | i = 0; |
206 | sync_val = res; |
207 | while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) { |
208 | usleep_range(DA9150_QIF_SYNC_TIMEOUT, |
209 | DA9150_QIF_SYNC_TIMEOUT * 2); |
210 | res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, |
211 | DA9150_QIF_SYNC_SIZE); |
212 | } |
213 | |
214 | mutex_unlock(lock: &fg->io_lock); |
215 | |
216 | /* Check write was actually successful */ |
217 | if (res != (sync_val + 1)) |
218 | dev_err(fg->dev, "Error performing QIF sync write for code %d\n" , |
219 | code); |
220 | } |
221 | |
222 | /* Power Supply attributes */ |
223 | static int da9150_fg_capacity(struct da9150_fg *fg, |
224 | union power_supply_propval *val) |
225 | { |
226 | val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, |
227 | DA9150_QIF_SOC_PCT_SIZE); |
228 | |
229 | if (val->intval > 100) |
230 | val->intval = 100; |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | static int da9150_fg_current_avg(struct da9150_fg *fg, |
236 | union power_supply_propval *val) |
237 | { |
238 | u32 iavg, sd_gain, shunt_val; |
239 | u64 div, res; |
240 | |
241 | da9150_fg_read_sync_start(fg); |
242 | iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG, |
243 | DA9150_QIF_IAVG_SIZE); |
244 | shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL, |
245 | DA9150_QIF_SHUNT_VAL_SIZE); |
246 | sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN, |
247 | DA9150_QIF_SD_GAIN_SIZE); |
248 | da9150_fg_read_sync_end(fg); |
249 | |
250 | div = (u64) (sd_gain * shunt_val * 65536ULL); |
251 | do_div(div, 1000000); |
252 | res = (u64) (iavg * 1000000ULL); |
253 | do_div(res, div); |
254 | |
255 | val->intval = (int) res; |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | static int da9150_fg_voltage_avg(struct da9150_fg *fg, |
261 | union power_supply_propval *val) |
262 | { |
263 | u64 res; |
264 | |
265 | val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG, |
266 | DA9150_QIF_UAVG_SIZE); |
267 | |
268 | res = (u64) (val->intval * 186ULL); |
269 | do_div(res, 10000); |
270 | val->intval = (int) res; |
271 | |
272 | return 0; |
273 | } |
274 | |
275 | static int da9150_fg_charge_full(struct da9150_fg *fg, |
276 | union power_supply_propval *val) |
277 | { |
278 | val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH, |
279 | DA9150_QIF_FCC_MAH_SIZE); |
280 | |
281 | val->intval = val->intval * 1000; |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | /* |
287 | * Temperature reading from device is only valid if battery/system provides |
288 | * valid NTC to associated pin of DA9150 chip. |
289 | */ |
290 | static int da9150_fg_temp(struct da9150_fg *fg, |
291 | union power_supply_propval *val) |
292 | { |
293 | val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG, |
294 | DA9150_QIF_NTCAVG_SIZE); |
295 | |
296 | val->intval = (val->intval * 10) / 1048576; |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static enum power_supply_property da9150_fg_props[] = { |
302 | POWER_SUPPLY_PROP_CAPACITY, |
303 | POWER_SUPPLY_PROP_CURRENT_AVG, |
304 | POWER_SUPPLY_PROP_VOLTAGE_AVG, |
305 | POWER_SUPPLY_PROP_CHARGE_FULL, |
306 | POWER_SUPPLY_PROP_TEMP, |
307 | }; |
308 | |
309 | static int da9150_fg_get_prop(struct power_supply *psy, |
310 | enum power_supply_property psp, |
311 | union power_supply_propval *val) |
312 | { |
313 | struct da9150_fg *fg = dev_get_drvdata(dev: psy->dev.parent); |
314 | int ret; |
315 | |
316 | switch (psp) { |
317 | case POWER_SUPPLY_PROP_CAPACITY: |
318 | ret = da9150_fg_capacity(fg, val); |
319 | break; |
320 | case POWER_SUPPLY_PROP_CURRENT_AVG: |
321 | ret = da9150_fg_current_avg(fg, val); |
322 | break; |
323 | case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
324 | ret = da9150_fg_voltage_avg(fg, val); |
325 | break; |
326 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
327 | ret = da9150_fg_charge_full(fg, val); |
328 | break; |
329 | case POWER_SUPPLY_PROP_TEMP: |
330 | ret = da9150_fg_temp(fg, val); |
331 | break; |
332 | default: |
333 | ret = -EINVAL; |
334 | break; |
335 | } |
336 | |
337 | return ret; |
338 | } |
339 | |
340 | /* Repeated SOC check */ |
341 | static bool da9150_fg_soc_changed(struct da9150_fg *fg) |
342 | { |
343 | union power_supply_propval val; |
344 | |
345 | da9150_fg_capacity(fg, val: &val); |
346 | if (val.intval != fg->soc) { |
347 | fg->soc = val.intval; |
348 | return true; |
349 | } |
350 | |
351 | return false; |
352 | } |
353 | |
354 | static void da9150_fg_work(struct work_struct *work) |
355 | { |
356 | struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work); |
357 | |
358 | /* Report if SOC has changed */ |
359 | if (da9150_fg_soc_changed(fg)) |
360 | power_supply_changed(psy: fg->battery); |
361 | |
362 | schedule_delayed_work(dwork: &fg->work, delay: msecs_to_jiffies(m: fg->interval)); |
363 | } |
364 | |
365 | /* SOC level event configuration */ |
366 | static void da9150_fg_soc_event_config(struct da9150_fg *fg) |
367 | { |
368 | int soc; |
369 | |
370 | soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, |
371 | DA9150_QIF_SOC_PCT_SIZE); |
372 | |
373 | if (soc > fg->warn_soc) { |
374 | /* If SOC > warn level, set discharge warn level event */ |
375 | da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, |
376 | DA9150_QIF_DISCHARGE_LIMIT_SIZE, |
377 | val: fg->warn_soc + 1); |
378 | } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) { |
379 | /* |
380 | * If SOC <= warn level, set discharge crit level event, |
381 | * and set charge warn level event. |
382 | */ |
383 | da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, |
384 | DA9150_QIF_DISCHARGE_LIMIT_SIZE, |
385 | val: fg->crit_soc + 1); |
386 | |
387 | da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, |
388 | DA9150_QIF_CHARGE_LIMIT_SIZE, |
389 | val: fg->warn_soc); |
390 | } else if (soc <= fg->crit_soc) { |
391 | /* If SOC <= crit level, set charge crit level event */ |
392 | da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, |
393 | DA9150_QIF_CHARGE_LIMIT_SIZE, |
394 | val: fg->crit_soc); |
395 | } |
396 | } |
397 | |
398 | static irqreturn_t da9150_fg_irq(int irq, void *data) |
399 | { |
400 | struct da9150_fg *fg = data; |
401 | u32 e_fg_status; |
402 | |
403 | /* Read FG IRQ status info */ |
404 | e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS, |
405 | DA9150_QIF_E_FG_STATUS_SIZE); |
406 | |
407 | /* Handle warning/critical threhold events */ |
408 | if (e_fg_status & DA9150_FG_IRQ_SOC_MASK) |
409 | da9150_fg_soc_event_config(fg); |
410 | |
411 | /* Clear any FG IRQs */ |
412 | da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS, |
413 | DA9150_QIF_E_FG_STATUS_SIZE, val: e_fg_status); |
414 | |
415 | return IRQ_HANDLED; |
416 | } |
417 | |
418 | static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev) |
419 | { |
420 | struct device_node *fg_node = dev->of_node; |
421 | struct da9150_fg_pdata *pdata; |
422 | |
423 | pdata = devm_kzalloc(dev, size: sizeof(struct da9150_fg_pdata), GFP_KERNEL); |
424 | if (!pdata) |
425 | return NULL; |
426 | |
427 | of_property_read_u32(np: fg_node, propname: "dlg,update-interval" , |
428 | out_value: &pdata->update_interval); |
429 | of_property_read_u8(np: fg_node, propname: "dlg,warn-soc-level" , |
430 | out_value: &pdata->warn_soc_lvl); |
431 | of_property_read_u8(np: fg_node, propname: "dlg,crit-soc-level" , |
432 | out_value: &pdata->crit_soc_lvl); |
433 | |
434 | return pdata; |
435 | } |
436 | |
437 | static const struct power_supply_desc fg_desc = { |
438 | .name = "da9150-fg" , |
439 | .type = POWER_SUPPLY_TYPE_BATTERY, |
440 | .properties = da9150_fg_props, |
441 | .num_properties = ARRAY_SIZE(da9150_fg_props), |
442 | .get_property = da9150_fg_get_prop, |
443 | }; |
444 | |
445 | static int da9150_fg_probe(struct platform_device *pdev) |
446 | { |
447 | struct device *dev = &pdev->dev; |
448 | struct da9150 *da9150 = dev_get_drvdata(dev: dev->parent); |
449 | struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev); |
450 | struct da9150_fg *fg; |
451 | int ver, irq, ret = 0; |
452 | |
453 | fg = devm_kzalloc(dev, size: sizeof(*fg), GFP_KERNEL); |
454 | if (fg == NULL) |
455 | return -ENOMEM; |
456 | |
457 | platform_set_drvdata(pdev, data: fg); |
458 | fg->da9150 = da9150; |
459 | fg->dev = dev; |
460 | |
461 | mutex_init(&fg->io_lock); |
462 | |
463 | /* Enable QIF */ |
464 | da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK, |
465 | DA9150_FG_QIF_EN_MASK); |
466 | |
467 | fg->battery = devm_power_supply_register(parent: dev, desc: &fg_desc, NULL); |
468 | if (IS_ERR(ptr: fg->battery)) { |
469 | ret = PTR_ERR(ptr: fg->battery); |
470 | return ret; |
471 | } |
472 | |
473 | ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER, |
474 | DA9150_QIF_FW_MAIN_VER_SIZE); |
475 | dev_info(dev, "Version: 0x%x\n" , ver); |
476 | |
477 | /* Handle DT data if provided */ |
478 | if (dev->of_node) { |
479 | fg_pdata = da9150_fg_dt_pdata(dev); |
480 | dev->platform_data = fg_pdata; |
481 | } |
482 | |
483 | /* Handle any pdata provided */ |
484 | if (fg_pdata) { |
485 | fg->interval = fg_pdata->update_interval; |
486 | |
487 | if (fg_pdata->warn_soc_lvl > 100) |
488 | dev_warn(dev, "Invalid SOC warning level provided, Ignoring" ); |
489 | else |
490 | fg->warn_soc = fg_pdata->warn_soc_lvl; |
491 | |
492 | if ((fg_pdata->crit_soc_lvl > 100) || |
493 | (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl)) |
494 | dev_warn(dev, "Invalid SOC critical level provided, Ignoring" ); |
495 | else |
496 | fg->crit_soc = fg_pdata->crit_soc_lvl; |
497 | |
498 | |
499 | } |
500 | |
501 | /* Configure initial SOC level events */ |
502 | da9150_fg_soc_event_config(fg); |
503 | |
504 | /* |
505 | * If an interval period has been provided then setup repeating |
506 | * work for reporting data updates. |
507 | */ |
508 | if (fg->interval) { |
509 | ret = devm_delayed_work_autocancel(dev, w: &fg->work, |
510 | worker: da9150_fg_work); |
511 | if (ret) { |
512 | dev_err(dev, "Failed to init work\n" ); |
513 | return ret; |
514 | } |
515 | |
516 | schedule_delayed_work(dwork: &fg->work, |
517 | delay: msecs_to_jiffies(m: fg->interval)); |
518 | } |
519 | |
520 | /* Register IRQ */ |
521 | irq = platform_get_irq_byname(pdev, "FG" ); |
522 | if (irq < 0) |
523 | return irq; |
524 | |
525 | ret = devm_request_threaded_irq(dev, irq, NULL, thread_fn: da9150_fg_irq, |
526 | IRQF_ONESHOT, devname: "FG" , dev_id: fg); |
527 | if (ret) { |
528 | dev_err(dev, "Failed to request IRQ %d: %d\n" , irq, ret); |
529 | return ret; |
530 | } |
531 | |
532 | return 0; |
533 | } |
534 | |
535 | static int da9150_fg_resume(struct platform_device *pdev) |
536 | { |
537 | struct da9150_fg *fg = platform_get_drvdata(pdev); |
538 | |
539 | /* |
540 | * Trigger SOC check to happen now so as to indicate any value change |
541 | * since last check before suspend. |
542 | */ |
543 | if (fg->interval) |
544 | flush_delayed_work(dwork: &fg->work); |
545 | |
546 | return 0; |
547 | } |
548 | |
549 | static struct platform_driver da9150_fg_driver = { |
550 | .driver = { |
551 | .name = "da9150-fuel-gauge" , |
552 | }, |
553 | .probe = da9150_fg_probe, |
554 | .resume = da9150_fg_resume, |
555 | }; |
556 | |
557 | module_platform_driver(da9150_fg_driver); |
558 | |
559 | MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150" ); |
560 | MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>" ); |
561 | MODULE_LICENSE("GPL" ); |
562 | |