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