1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Mainly by David Woodhouse, somewhat modified by Jordan Crouse |
4 | * |
5 | * Copyright © 2006-2007 Red Hat, Inc. |
6 | * Copyright © 2006-2007 Advanced Micro Devices, Inc. |
7 | * Copyright © 2009 VIA Technology, Inc. |
8 | * Copyright (c) 2010-2011 Andres Salomon <dilinger@queued.net> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/kernel.h> |
14 | #include <linux/fb.h> |
15 | #include <linux/console.h> |
16 | #include <linux/i2c.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/interrupt.h> |
19 | #include <linux/delay.h> |
20 | #include <linux/module.h> |
21 | #include <linux/backlight.h> |
22 | #include <linux/device.h> |
23 | #include <linux/uaccess.h> |
24 | #include <linux/ctype.h> |
25 | #include <linux/panic_notifier.h> |
26 | #include <linux/reboot.h> |
27 | #include <linux/olpc-ec.h> |
28 | #include <asm/tsc.h> |
29 | #include <asm/olpc.h> |
30 | |
31 | #include "olpc_dcon.h" |
32 | |
33 | /* Module definitions */ |
34 | |
35 | static ushort resumeline = 898; |
36 | module_param(resumeline, ushort, 0444); |
37 | |
38 | static struct dcon_platform_data *pdata; |
39 | |
40 | /* I2C structures */ |
41 | |
42 | /* Platform devices */ |
43 | static struct platform_device *dcon_device; |
44 | |
45 | static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END }; |
46 | |
47 | static s32 dcon_write(struct dcon_priv *dcon, u8 reg, u16 val) |
48 | { |
49 | return i2c_smbus_write_word_data(client: dcon->client, command: reg, value: val); |
50 | } |
51 | |
52 | static s32 dcon_read(struct dcon_priv *dcon, u8 reg) |
53 | { |
54 | return i2c_smbus_read_word_data(client: dcon->client, command: reg); |
55 | } |
56 | |
57 | /* ===== API functions - these are called by a variety of users ==== */ |
58 | |
59 | static int dcon_hw_init(struct dcon_priv *dcon, int is_init) |
60 | { |
61 | u16 ver; |
62 | int rc = 0; |
63 | |
64 | ver = dcon_read(dcon, DCON_REG_ID); |
65 | if ((ver >> 8) != 0xDC) { |
66 | pr_err("DCON ID not 0xDCxx: 0x%04x instead.\n" , ver); |
67 | rc = -ENXIO; |
68 | goto err; |
69 | } |
70 | |
71 | if (is_init) { |
72 | pr_info("Discovered DCON version %x\n" , ver & 0xFF); |
73 | rc = pdata->init(dcon); |
74 | if (rc != 0) { |
75 | pr_err("Unable to init.\n" ); |
76 | goto err; |
77 | } |
78 | } |
79 | |
80 | if (ver < 0xdc02) { |
81 | dev_err(&dcon->client->dev, |
82 | "DCON v1 is unsupported, giving up..\n" ); |
83 | rc = -ENODEV; |
84 | goto err; |
85 | } |
86 | |
87 | /* SDRAM setup/hold time */ |
88 | dcon_write(dcon, reg: 0x3a, val: 0xc040); |
89 | dcon_write(dcon, DCON_REG_MEM_OPT_A, val: 0x0000); /* clear option bits */ |
90 | dcon_write(dcon, DCON_REG_MEM_OPT_A, |
91 | MEM_DLL_CLOCK_DELAY | MEM_POWER_DOWN); |
92 | dcon_write(dcon, DCON_REG_MEM_OPT_B, MEM_SOFT_RESET); |
93 | |
94 | /* Colour swizzle, AA, no passthrough, backlight */ |
95 | if (is_init) { |
96 | dcon->disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE | |
97 | MODE_CSWIZZLE | MODE_COL_AA; |
98 | } |
99 | dcon_write(dcon, DCON_REG_MODE, val: dcon->disp_mode); |
100 | |
101 | /* Set the scanline to interrupt on during resume */ |
102 | dcon_write(dcon, DCON_REG_SCAN_INT, val: resumeline); |
103 | |
104 | err: |
105 | return rc; |
106 | } |
107 | |
108 | /* |
109 | * The smbus doesn't always come back due to what is believed to be |
110 | * hardware (power rail) bugs. For older models where this is known to |
111 | * occur, our solution is to attempt to wait for the bus to stabilize; |
112 | * if it doesn't happen, cut power to the dcon, repower it, and wait |
113 | * for the bus to stabilize. Rinse, repeat until we have a working |
114 | * smbus. For newer models, we simply BUG(); we want to know if this |
115 | * still happens despite the power fixes that have been made! |
116 | */ |
117 | static int dcon_bus_stabilize(struct dcon_priv *dcon, int is_powered_down) |
118 | { |
119 | unsigned long timeout; |
120 | u8 pm; |
121 | int x; |
122 | |
123 | power_up: |
124 | if (is_powered_down) { |
125 | pm = 1; |
126 | x = olpc_ec_cmd(EC_DCON_POWER_MODE, inbuf: &pm, inlen: 1, NULL, outlen: 0); |
127 | if (x) { |
128 | pr_warn("unable to force dcon to power up: %d!\n" , x); |
129 | return x; |
130 | } |
131 | usleep_range(min: 10000, max: 11000); /* we'll be conservative */ |
132 | } |
133 | |
134 | pdata->bus_stabilize_wiggle(); |
135 | |
136 | for (x = -1, timeout = 50; timeout && x < 0; timeout--) { |
137 | usleep_range(min: 1000, max: 1100); |
138 | x = dcon_read(dcon, DCON_REG_ID); |
139 | } |
140 | if (x < 0) { |
141 | pr_err("unable to stabilize dcon's smbus, reasserting power and praying.\n" ); |
142 | BUG_ON(olpc_board_at_least(olpc_board(0xc2))); |
143 | pm = 0; |
144 | olpc_ec_cmd(EC_DCON_POWER_MODE, inbuf: &pm, inlen: 1, NULL, outlen: 0); |
145 | msleep(msecs: 100); |
146 | is_powered_down = 1; |
147 | goto power_up; /* argh, stupid hardware.. */ |
148 | } |
149 | |
150 | if (is_powered_down) |
151 | return dcon_hw_init(dcon, is_init: 0); |
152 | return 0; |
153 | } |
154 | |
155 | static void dcon_set_backlight(struct dcon_priv *dcon, u8 level) |
156 | { |
157 | dcon->bl_val = level; |
158 | dcon_write(dcon, DCON_REG_BRIGHT, val: dcon->bl_val); |
159 | |
160 | /* Purposely turn off the backlight when we go to level 0 */ |
161 | if (dcon->bl_val == 0) { |
162 | dcon->disp_mode &= ~MODE_BL_ENABLE; |
163 | dcon_write(dcon, DCON_REG_MODE, val: dcon->disp_mode); |
164 | } else if (!(dcon->disp_mode & MODE_BL_ENABLE)) { |
165 | dcon->disp_mode |= MODE_BL_ENABLE; |
166 | dcon_write(dcon, DCON_REG_MODE, val: dcon->disp_mode); |
167 | } |
168 | } |
169 | |
170 | /* Set the output type to either color or mono */ |
171 | static int dcon_set_mono_mode(struct dcon_priv *dcon, bool enable_mono) |
172 | { |
173 | if (dcon->mono == enable_mono) |
174 | return 0; |
175 | |
176 | dcon->mono = enable_mono; |
177 | |
178 | if (enable_mono) { |
179 | dcon->disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA); |
180 | dcon->disp_mode |= MODE_MONO_LUMA; |
181 | } else { |
182 | dcon->disp_mode &= ~(MODE_MONO_LUMA); |
183 | dcon->disp_mode |= MODE_CSWIZZLE | MODE_COL_AA; |
184 | } |
185 | |
186 | dcon_write(dcon, DCON_REG_MODE, val: dcon->disp_mode); |
187 | return 0; |
188 | } |
189 | |
190 | /* For now, this will be really stupid - we need to address how |
191 | * DCONLOAD works in a sleep and account for it accordingly |
192 | */ |
193 | |
194 | static void dcon_sleep(struct dcon_priv *dcon, bool sleep) |
195 | { |
196 | int x; |
197 | |
198 | /* Turn off the backlight and put the DCON to sleep */ |
199 | |
200 | if (dcon->asleep == sleep) |
201 | return; |
202 | |
203 | if (!olpc_board_at_least(olpc_board(0xc2))) |
204 | return; |
205 | |
206 | if (sleep) { |
207 | u8 pm = 0; |
208 | |
209 | x = olpc_ec_cmd(EC_DCON_POWER_MODE, inbuf: &pm, inlen: 1, NULL, outlen: 0); |
210 | if (x) |
211 | pr_warn("unable to force dcon to power down: %d!\n" , x); |
212 | else |
213 | dcon->asleep = sleep; |
214 | } else { |
215 | /* Only re-enable the backlight if the backlight value is set */ |
216 | if (dcon->bl_val != 0) |
217 | dcon->disp_mode |= MODE_BL_ENABLE; |
218 | x = dcon_bus_stabilize(dcon, is_powered_down: 1); |
219 | if (x) |
220 | pr_warn("unable to reinit dcon hardware: %d!\n" , x); |
221 | else |
222 | dcon->asleep = sleep; |
223 | |
224 | /* Restore backlight */ |
225 | dcon_set_backlight(dcon, level: dcon->bl_val); |
226 | } |
227 | |
228 | /* We should turn off some stuff in the framebuffer - but what? */ |
229 | } |
230 | |
231 | /* the DCON seems to get confused if we change DCONLOAD too |
232 | * frequently -- i.e., approximately faster than frame time. |
233 | * normally we don't change it this fast, so in general we won't |
234 | * delay here. |
235 | */ |
236 | static void dcon_load_holdoff(struct dcon_priv *dcon) |
237 | { |
238 | ktime_t delta_t, now; |
239 | |
240 | while (1) { |
241 | now = ktime_get(); |
242 | delta_t = ktime_sub(now, dcon->load_time); |
243 | if (ktime_to_ns(kt: delta_t) > NSEC_PER_MSEC * 20) |
244 | break; |
245 | mdelay(4); |
246 | } |
247 | } |
248 | |
249 | static bool dcon_blank_fb(struct dcon_priv *dcon, bool blank) |
250 | { |
251 | int err; |
252 | |
253 | console_lock(); |
254 | lock_fb_info(info: dcon->fbinfo); |
255 | |
256 | dcon->ignore_fb_events = true; |
257 | err = fb_blank(info: dcon->fbinfo, |
258 | blank: blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); |
259 | dcon->ignore_fb_events = false; |
260 | unlock_fb_info(info: dcon->fbinfo); |
261 | console_unlock(); |
262 | |
263 | if (err) { |
264 | dev_err(&dcon->client->dev, "couldn't %sblank framebuffer\n" , |
265 | blank ? "" : "un" ); |
266 | return false; |
267 | } |
268 | return true; |
269 | } |
270 | |
271 | /* Set the source of the display (CPU or DCON) */ |
272 | static void dcon_source_switch(struct work_struct *work) |
273 | { |
274 | struct dcon_priv *dcon = container_of(work, struct dcon_priv, |
275 | switch_source); |
276 | int source = dcon->pending_src; |
277 | |
278 | if (dcon->curr_src == source) |
279 | return; |
280 | |
281 | dcon_load_holdoff(dcon); |
282 | |
283 | dcon->switched = false; |
284 | |
285 | switch (source) { |
286 | case DCON_SOURCE_CPU: |
287 | pr_info("%s to CPU\n" , __func__); |
288 | /* Enable the scanline interrupt bit */ |
289 | if (dcon_write(dcon, DCON_REG_MODE, |
290 | val: dcon->disp_mode | MODE_SCAN_INT)) |
291 | pr_err("couldn't enable scanline interrupt!\n" ); |
292 | else |
293 | /* Wait up to one second for the scanline interrupt */ |
294 | wait_event_timeout(dcon->waitq, dcon->switched, HZ); |
295 | |
296 | if (!dcon->switched) |
297 | pr_err("Timeout entering CPU mode; expect a screen glitch.\n" ); |
298 | |
299 | /* Turn off the scanline interrupt */ |
300 | if (dcon_write(dcon, DCON_REG_MODE, val: dcon->disp_mode)) |
301 | pr_err("couldn't disable scanline interrupt!\n" ); |
302 | |
303 | /* |
304 | * Ideally we'd like to disable interrupts here so that the |
305 | * fb unblanking and DCON turn on happen at a known time value; |
306 | * however, we can't do that right now with fb_blank |
307 | * messing with semaphores. |
308 | * |
309 | * For now, we just hope.. |
310 | */ |
311 | if (!dcon_blank_fb(dcon, blank: false)) { |
312 | pr_err("Failed to enter CPU mode\n" ); |
313 | dcon->pending_src = DCON_SOURCE_DCON; |
314 | return; |
315 | } |
316 | |
317 | /* And turn off the DCON */ |
318 | pdata->set_dconload(1); |
319 | dcon->load_time = ktime_get(); |
320 | |
321 | pr_info("The CPU has control\n" ); |
322 | break; |
323 | case DCON_SOURCE_DCON: |
324 | { |
325 | ktime_t delta_t; |
326 | |
327 | pr_info("%s to DCON\n" , __func__); |
328 | |
329 | /* Clear DCONLOAD - this implies that the DCON is in control */ |
330 | pdata->set_dconload(0); |
331 | dcon->load_time = ktime_get(); |
332 | |
333 | wait_event_timeout(dcon->waitq, dcon->switched, HZ / 2); |
334 | |
335 | if (!dcon->switched) { |
336 | pr_err("Timeout entering DCON mode; expect a screen glitch.\n" ); |
337 | } else { |
338 | /* sometimes the DCON doesn't follow its own rules, |
339 | * and doesn't wait for two vsync pulses before |
340 | * ack'ing the frame load with an IRQ. the result |
341 | * is that the display shows the *previously* |
342 | * loaded frame. we can detect this by looking at |
343 | * the time between asserting DCONLOAD and the IRQ -- |
344 | * if it's less than 20msec, then the DCON couldn't |
345 | * have seen two VSYNC pulses. in that case we |
346 | * deassert and reassert, and hope for the best. |
347 | * see http://dev.laptop.org/ticket/9664 |
348 | */ |
349 | delta_t = ktime_sub(dcon->irq_time, dcon->load_time); |
350 | if (dcon->switched && ktime_to_ns(kt: delta_t) |
351 | < NSEC_PER_MSEC * 20) { |
352 | pr_err("missed loading, retrying\n" ); |
353 | pdata->set_dconload(1); |
354 | mdelay(41); |
355 | pdata->set_dconload(0); |
356 | dcon->load_time = ktime_get(); |
357 | mdelay(41); |
358 | } |
359 | } |
360 | |
361 | dcon_blank_fb(dcon, blank: true); |
362 | pr_info("The DCON has control\n" ); |
363 | break; |
364 | } |
365 | default: |
366 | BUG(); |
367 | } |
368 | |
369 | dcon->curr_src = source; |
370 | } |
371 | |
372 | static void dcon_set_source(struct dcon_priv *dcon, int arg) |
373 | { |
374 | if (dcon->pending_src == arg) |
375 | return; |
376 | |
377 | dcon->pending_src = arg; |
378 | |
379 | if (dcon->curr_src != arg) |
380 | schedule_work(work: &dcon->switch_source); |
381 | } |
382 | |
383 | static void dcon_set_source_sync(struct dcon_priv *dcon, int arg) |
384 | { |
385 | dcon_set_source(dcon, arg); |
386 | flush_work(work: &dcon->switch_source); |
387 | } |
388 | |
389 | static ssize_t dcon_mode_show(struct device *dev, |
390 | struct device_attribute *attr, |
391 | char *buf) |
392 | { |
393 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
394 | |
395 | return sprintf(buf, fmt: "%4.4X\n" , dcon->disp_mode); |
396 | } |
397 | |
398 | static ssize_t dcon_sleep_show(struct device *dev, |
399 | struct device_attribute *attr, |
400 | char *buf) |
401 | { |
402 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
403 | |
404 | return sprintf(buf, fmt: "%d\n" , dcon->asleep); |
405 | } |
406 | |
407 | static ssize_t dcon_freeze_show(struct device *dev, |
408 | struct device_attribute *attr, |
409 | char *buf) |
410 | { |
411 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
412 | |
413 | return sprintf(buf, fmt: "%d\n" , dcon->curr_src == DCON_SOURCE_DCON ? 1 : 0); |
414 | } |
415 | |
416 | static ssize_t dcon_mono_show(struct device *dev, |
417 | struct device_attribute *attr, |
418 | char *buf) |
419 | { |
420 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
421 | |
422 | return sprintf(buf, fmt: "%d\n" , dcon->mono); |
423 | } |
424 | |
425 | static ssize_t dcon_resumeline_show(struct device *dev, |
426 | struct device_attribute *attr, |
427 | char *buf) |
428 | { |
429 | return sprintf(buf, fmt: "%d\n" , resumeline); |
430 | } |
431 | |
432 | static ssize_t dcon_mono_store(struct device *dev, |
433 | struct device_attribute *attr, |
434 | const char *buf, size_t count) |
435 | { |
436 | unsigned long enable_mono; |
437 | int rc; |
438 | |
439 | rc = kstrtoul(s: buf, base: 10, res: &enable_mono); |
440 | if (rc) |
441 | return rc; |
442 | |
443 | dcon_set_mono_mode(dcon: dev_get_drvdata(dev), enable_mono: enable_mono ? true : false); |
444 | |
445 | return count; |
446 | } |
447 | |
448 | static ssize_t dcon_freeze_store(struct device *dev, |
449 | struct device_attribute *attr, |
450 | const char *buf, size_t count) |
451 | { |
452 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
453 | unsigned long output; |
454 | int ret; |
455 | |
456 | ret = kstrtoul(s: buf, base: 10, res: &output); |
457 | if (ret) |
458 | return ret; |
459 | |
460 | switch (output) { |
461 | case 0: |
462 | dcon_set_source(dcon, DCON_SOURCE_CPU); |
463 | break; |
464 | case 1: |
465 | dcon_set_source_sync(dcon, DCON_SOURCE_DCON); |
466 | break; |
467 | case 2: /* normally unused */ |
468 | dcon_set_source(dcon, DCON_SOURCE_DCON); |
469 | break; |
470 | default: |
471 | return -EINVAL; |
472 | } |
473 | |
474 | return count; |
475 | } |
476 | |
477 | static ssize_t dcon_resumeline_store(struct device *dev, |
478 | struct device_attribute *attr, |
479 | const char *buf, size_t count) |
480 | { |
481 | unsigned short rl; |
482 | int rc; |
483 | |
484 | rc = kstrtou16(s: buf, base: 10, res: &rl); |
485 | if (rc) |
486 | return rc; |
487 | |
488 | resumeline = rl; |
489 | dcon_write(dcon: dev_get_drvdata(dev), DCON_REG_SCAN_INT, val: resumeline); |
490 | |
491 | return count; |
492 | } |
493 | |
494 | static ssize_t dcon_sleep_store(struct device *dev, |
495 | struct device_attribute *attr, |
496 | const char *buf, size_t count) |
497 | { |
498 | unsigned long output; |
499 | int ret; |
500 | |
501 | ret = kstrtoul(s: buf, base: 10, res: &output); |
502 | if (ret) |
503 | return ret; |
504 | |
505 | dcon_sleep(dcon: dev_get_drvdata(dev), sleep: output ? true : false); |
506 | return count; |
507 | } |
508 | |
509 | static struct device_attribute dcon_device_files[] = { |
510 | __ATTR(mode, 0444, dcon_mode_show, NULL), |
511 | __ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store), |
512 | __ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store), |
513 | __ATTR(monochrome, 0644, dcon_mono_show, dcon_mono_store), |
514 | __ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store), |
515 | }; |
516 | |
517 | static int dcon_bl_update(struct backlight_device *dev) |
518 | { |
519 | struct dcon_priv *dcon = bl_get_data(bl_dev: dev); |
520 | u8 level = backlight_get_brightness(bd: dev) & 0x0F; |
521 | |
522 | if (level != dcon->bl_val) |
523 | dcon_set_backlight(dcon, level); |
524 | |
525 | /* power down the DCON when the screen is blanked */ |
526 | if (!dcon->ignore_fb_events) |
527 | dcon_sleep(dcon, sleep: !!(dev->props.state & BL_CORE_FBBLANK)); |
528 | |
529 | return 0; |
530 | } |
531 | |
532 | static int dcon_bl_get(struct backlight_device *dev) |
533 | { |
534 | struct dcon_priv *dcon = bl_get_data(bl_dev: dev); |
535 | |
536 | return dcon->bl_val; |
537 | } |
538 | |
539 | static const struct backlight_ops dcon_bl_ops = { |
540 | .update_status = dcon_bl_update, |
541 | .get_brightness = dcon_bl_get, |
542 | }; |
543 | |
544 | static struct backlight_properties dcon_bl_props = { |
545 | .max_brightness = 15, |
546 | .type = BACKLIGHT_RAW, |
547 | .power = FB_BLANK_UNBLANK, |
548 | }; |
549 | |
550 | static int dcon_reboot_notify(struct notifier_block *nb, |
551 | unsigned long foo, void *bar) |
552 | { |
553 | struct dcon_priv *dcon = container_of(nb, struct dcon_priv, reboot_nb); |
554 | |
555 | if (!dcon || !dcon->client) |
556 | return NOTIFY_DONE; |
557 | |
558 | /* Turn off the DCON. Entirely. */ |
559 | dcon_write(dcon, DCON_REG_MODE, val: 0x39); |
560 | dcon_write(dcon, DCON_REG_MODE, val: 0x32); |
561 | return NOTIFY_DONE; |
562 | } |
563 | |
564 | static int unfreeze_on_panic(struct notifier_block *nb, |
565 | unsigned long e, void *p) |
566 | { |
567 | pdata->set_dconload(1); |
568 | return NOTIFY_DONE; |
569 | } |
570 | |
571 | static struct notifier_block dcon_panic_nb = { |
572 | .notifier_call = unfreeze_on_panic, |
573 | }; |
574 | |
575 | static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info) |
576 | { |
577 | strscpy(info->type, "olpc_dcon" , I2C_NAME_SIZE); |
578 | |
579 | return 0; |
580 | } |
581 | |
582 | static int dcon_probe(struct i2c_client *client) |
583 | { |
584 | struct dcon_priv *dcon; |
585 | int rc, i, j; |
586 | |
587 | if (!pdata) |
588 | return -ENXIO; |
589 | |
590 | dcon = kzalloc(size: sizeof(*dcon), GFP_KERNEL); |
591 | if (!dcon) |
592 | return -ENOMEM; |
593 | |
594 | dcon->client = client; |
595 | init_waitqueue_head(&dcon->waitq); |
596 | INIT_WORK(&dcon->switch_source, dcon_source_switch); |
597 | dcon->reboot_nb.notifier_call = dcon_reboot_notify; |
598 | dcon->reboot_nb.priority = -1; |
599 | |
600 | i2c_set_clientdata(client, data: dcon); |
601 | |
602 | if (num_registered_fb < 1) { |
603 | dev_err(&client->dev, "DCON driver requires a registered fb\n" ); |
604 | rc = -EIO; |
605 | goto einit; |
606 | } |
607 | dcon->fbinfo = registered_fb[0]; |
608 | |
609 | rc = dcon_hw_init(dcon, is_init: 1); |
610 | if (rc) |
611 | goto einit; |
612 | |
613 | /* Add the DCON device */ |
614 | |
615 | dcon_device = platform_device_alloc(name: "dcon" , id: -1); |
616 | |
617 | if (!dcon_device) { |
618 | pr_err("Unable to create the DCON device\n" ); |
619 | rc = -ENOMEM; |
620 | goto eirq; |
621 | } |
622 | rc = platform_device_add(pdev: dcon_device); |
623 | platform_set_drvdata(pdev: dcon_device, data: dcon); |
624 | |
625 | if (rc) { |
626 | pr_err("Unable to add the DCON device\n" ); |
627 | goto edev; |
628 | } |
629 | |
630 | for (i = 0; i < ARRAY_SIZE(dcon_device_files); i++) { |
631 | rc = device_create_file(device: &dcon_device->dev, |
632 | entry: &dcon_device_files[i]); |
633 | if (rc) { |
634 | dev_err(&dcon_device->dev, "Cannot create sysfs file\n" ); |
635 | goto ecreate; |
636 | } |
637 | } |
638 | |
639 | dcon->bl_val = dcon_read(dcon, DCON_REG_BRIGHT) & 0x0F; |
640 | |
641 | /* Add the backlight device for the DCON */ |
642 | dcon_bl_props.brightness = dcon->bl_val; |
643 | dcon->bl_dev = backlight_device_register(name: "dcon-bl" , dev: &dcon_device->dev, |
644 | devdata: dcon, ops: &dcon_bl_ops, |
645 | props: &dcon_bl_props); |
646 | if (IS_ERR(ptr: dcon->bl_dev)) { |
647 | dev_err(&client->dev, "cannot register backlight dev (%ld)\n" , |
648 | PTR_ERR(dcon->bl_dev)); |
649 | dcon->bl_dev = NULL; |
650 | } |
651 | |
652 | register_reboot_notifier(&dcon->reboot_nb); |
653 | atomic_notifier_chain_register(nh: &panic_notifier_list, nb: &dcon_panic_nb); |
654 | |
655 | return 0; |
656 | |
657 | ecreate: |
658 | for (j = 0; j < i; j++) |
659 | device_remove_file(dev: &dcon_device->dev, attr: &dcon_device_files[j]); |
660 | platform_device_del(pdev: dcon_device); |
661 | edev: |
662 | platform_device_put(pdev: dcon_device); |
663 | dcon_device = NULL; |
664 | eirq: |
665 | free_irq(DCON_IRQ, dcon); |
666 | einit: |
667 | kfree(objp: dcon); |
668 | return rc; |
669 | } |
670 | |
671 | static void dcon_remove(struct i2c_client *client) |
672 | { |
673 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
674 | |
675 | unregister_reboot_notifier(&dcon->reboot_nb); |
676 | atomic_notifier_chain_unregister(nh: &panic_notifier_list, nb: &dcon_panic_nb); |
677 | |
678 | free_irq(DCON_IRQ, dcon); |
679 | |
680 | backlight_device_unregister(bd: dcon->bl_dev); |
681 | |
682 | if (dcon_device) |
683 | platform_device_unregister(dcon_device); |
684 | cancel_work_sync(work: &dcon->switch_source); |
685 | |
686 | kfree(objp: dcon); |
687 | } |
688 | |
689 | #ifdef CONFIG_PM |
690 | static int dcon_suspend(struct device *dev) |
691 | { |
692 | struct i2c_client *client = to_i2c_client(dev); |
693 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
694 | |
695 | if (!dcon->asleep) { |
696 | /* Set up the DCON to have the source */ |
697 | dcon_set_source_sync(dcon, DCON_SOURCE_DCON); |
698 | } |
699 | |
700 | return 0; |
701 | } |
702 | |
703 | static int dcon_resume(struct device *dev) |
704 | { |
705 | struct i2c_client *client = to_i2c_client(dev); |
706 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
707 | |
708 | if (!dcon->asleep) { |
709 | dcon_bus_stabilize(dcon, is_powered_down: 0); |
710 | dcon_set_source(dcon, DCON_SOURCE_CPU); |
711 | } |
712 | |
713 | return 0; |
714 | } |
715 | |
716 | #else |
717 | |
718 | #define dcon_suspend NULL |
719 | #define dcon_resume NULL |
720 | |
721 | #endif /* CONFIG_PM */ |
722 | |
723 | irqreturn_t dcon_interrupt(int irq, void *id) |
724 | { |
725 | struct dcon_priv *dcon = id; |
726 | u8 status; |
727 | |
728 | if (pdata->read_status(&status)) |
729 | return IRQ_NONE; |
730 | |
731 | switch (status & 3) { |
732 | case 3: |
733 | pr_debug("DCONLOAD_MISSED interrupt\n" ); |
734 | break; |
735 | |
736 | case 2: /* switch to DCON mode */ |
737 | case 1: /* switch to CPU mode */ |
738 | dcon->switched = true; |
739 | dcon->irq_time = ktime_get(); |
740 | wake_up(&dcon->waitq); |
741 | break; |
742 | |
743 | case 0: |
744 | /* workaround resume case: the DCON (on 1.5) doesn't |
745 | * ever assert status 0x01 when switching to CPU mode |
746 | * during resume. this is because DCONLOAD is de-asserted |
747 | * _immediately_ upon exiting S3, so the actual release |
748 | * of the DCON happened long before this point. |
749 | * see http://dev.laptop.org/ticket/9869 |
750 | */ |
751 | if (dcon->curr_src != dcon->pending_src && !dcon->switched) { |
752 | dcon->switched = true; |
753 | dcon->irq_time = ktime_get(); |
754 | wake_up(&dcon->waitq); |
755 | pr_debug("switching w/ status 0/0\n" ); |
756 | } else { |
757 | pr_debug("scanline interrupt w/CPU\n" ); |
758 | } |
759 | } |
760 | |
761 | return IRQ_HANDLED; |
762 | } |
763 | |
764 | static const struct dev_pm_ops dcon_pm_ops = { |
765 | .suspend = dcon_suspend, |
766 | .resume = dcon_resume, |
767 | }; |
768 | |
769 | static const struct i2c_device_id dcon_idtable[] = { |
770 | { "olpc_dcon" , 0 }, |
771 | { } |
772 | }; |
773 | MODULE_DEVICE_TABLE(i2c, dcon_idtable); |
774 | |
775 | static struct i2c_driver dcon_driver = { |
776 | .driver = { |
777 | .name = "olpc_dcon" , |
778 | .pm = &dcon_pm_ops, |
779 | }, |
780 | .class = I2C_CLASS_HWMON, |
781 | .id_table = dcon_idtable, |
782 | .probe = dcon_probe, |
783 | .remove = dcon_remove, |
784 | .detect = dcon_detect, |
785 | .address_list = normal_i2c, |
786 | }; |
787 | |
788 | static int __init olpc_dcon_init(void) |
789 | { |
790 | /* XO-1.5 */ |
791 | if (olpc_board_at_least(olpc_board(0xd0))) |
792 | pdata = &dcon_pdata_xo_1_5; |
793 | else |
794 | pdata = &dcon_pdata_xo_1; |
795 | |
796 | return i2c_add_driver(&dcon_driver); |
797 | } |
798 | |
799 | static void __exit olpc_dcon_exit(void) |
800 | { |
801 | i2c_del_driver(driver: &dcon_driver); |
802 | } |
803 | |
804 | module_init(olpc_dcon_init); |
805 | module_exit(olpc_dcon_exit); |
806 | |
807 | MODULE_LICENSE("GPL" ); |
808 | |