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 | 240 | { |
851f7c0e | 241 | ktime_t delta_t, now; |
7949f30a | 242 | |
e107e6eb | 243 | while (1) { |
851f7c0e KS |
244 | now = ktime_get(); |
245 | delta_t = ktime_sub(now, dcon->load_time); | |
cea07e52 | 246 | if (ktime_to_ns(delta_t) > NSEC_PER_MSEC * 20) |
eecb3e4e | 247 | break; |
eecb3e4e AS |
248 | mdelay(4); |
249 | } | |
250 | } | |
eecb3e4e | 251 | |
45bfe972 AS |
252 | static bool dcon_blank_fb(struct dcon_priv *dcon, bool blank) |
253 | { | |
254 | int err; | |
255 | ||
fc0524b0 | 256 | console_lock(); |
45bfe972 | 257 | if (!lock_fb_info(dcon->fbinfo)) { |
fc0524b0 | 258 | console_unlock(); |
45bfe972 AS |
259 | dev_err(&dcon->client->dev, "unable to lock framebuffer\n"); |
260 | return false; | |
261 | } | |
fc0524b0 | 262 | |
45bfe972 AS |
263 | dcon->ignore_fb_events = true; |
264 | err = fb_blank(dcon->fbinfo, | |
265 | blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); | |
266 | dcon->ignore_fb_events = false; | |
45bfe972 | 267 | unlock_fb_info(dcon->fbinfo); |
fc0524b0 | 268 | console_unlock(); |
45bfe972 AS |
269 | |
270 | if (err) { | |
271 | dev_err(&dcon->client->dev, "couldn't %sblank framebuffer\n", | |
272 | blank ? "" : "un"); | |
273 | return false; | |
274 | } | |
275 | return true; | |
276 | } | |
277 | ||
278 | /* Set the source of the display (CPU or DCON) */ | |
eecb3e4e AS |
279 | static void dcon_source_switch(struct work_struct *work) |
280 | { | |
8d2d3dd1 AS |
281 | struct dcon_priv *dcon = container_of(work, struct dcon_priv, |
282 | switch_source); | |
bbe963f1 | 283 | int source = dcon->pending_src; |
eecb3e4e | 284 | |
bbe963f1 | 285 | if (dcon->curr_src == source) |
eecb3e4e AS |
286 | return; |
287 | ||
309ef2a2 | 288 | dcon_load_holdoff(dcon); |
eecb3e4e | 289 | |
309ef2a2 | 290 | dcon->switched = false; |
eecb3e4e AS |
291 | |
292 | switch (source) { | |
293 | case DCON_SOURCE_CPU: | |
ac9bbd08 | 294 | pr_info("dcon_source_switch to CPU\n"); |
eecb3e4e | 295 | /* Enable the scanline interrupt bit */ |
8d2d3dd1 | 296 | if (dcon_write(dcon, DCON_REG_MODE, |
bada46e5 | 297 | dcon->disp_mode | MODE_SCAN_INT)) |
ac9bbd08 | 298 | pr_err("couldn't enable scanline interrupt!\n"); |
c40f20da | 299 | else |
eecb3e4e | 300 | /* Wait up to one second for the scanline interrupt */ |
c40f20da | 301 | wait_event_timeout(dcon->waitq, dcon->switched, HZ); |
eecb3e4e | 302 | |
309ef2a2 | 303 | if (!dcon->switched) |
ac9bbd08 | 304 | pr_err("Timeout entering CPU mode; expect a screen glitch.\n"); |
eecb3e4e AS |
305 | |
306 | /* Turn off the scanline interrupt */ | |
bada46e5 | 307 | if (dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode)) |
ac9bbd08 | 308 | pr_err("couldn't disable scanline interrupt!\n"); |
eecb3e4e AS |
309 | |
310 | /* | |
311 | * Ideally we'd like to disable interrupts here so that the | |
312 | * fb unblanking and DCON turn on happen at a known time value; | |
313 | * however, we can't do that right now with fb_blank | |
314 | * messing with semaphores. | |
315 | * | |
316 | * For now, we just hope.. | |
317 | */ | |
45bfe972 | 318 | if (!dcon_blank_fb(dcon, false)) { |
ac9bbd08 | 319 | pr_err("Failed to enter CPU mode\n"); |
bbe963f1 | 320 | dcon->pending_src = DCON_SOURCE_DCON; |
eecb3e4e AS |
321 | return; |
322 | } | |
eecb3e4e AS |
323 | |
324 | /* And turn off the DCON */ | |
325 | pdata->set_dconload(1); | |
851f7c0e | 326 | dcon->load_time = ktime_get(); |
eecb3e4e | 327 | |
ac9bbd08 | 328 | pr_info("The CPU has control\n"); |
eecb3e4e AS |
329 | break; |
330 | case DCON_SOURCE_DCON: | |
331 | { | |
851f7c0e | 332 | ktime_t delta_t; |
eecb3e4e | 333 | |
ac9bbd08 | 334 | pr_info("dcon_source_switch to DCON\n"); |
eecb3e4e | 335 | |
eecb3e4e AS |
336 | /* Clear DCONLOAD - this implies that the DCON is in control */ |
337 | pdata->set_dconload(0); | |
851f7c0e | 338 | dcon->load_time = ktime_get(); |
eecb3e4e | 339 | |
c40f20da | 340 | wait_event_timeout(dcon->waitq, dcon->switched, HZ/2); |
eecb3e4e | 341 | |
309ef2a2 | 342 | if (!dcon->switched) { |
ac9bbd08 | 343 | pr_err("Timeout entering DCON mode; expect a screen glitch.\n"); |
eecb3e4e AS |
344 | } else { |
345 | /* sometimes the DCON doesn't follow its own rules, | |
346 | * and doesn't wait for two vsync pulses before | |
347 | * ack'ing the frame load with an IRQ. the result | |
348 | * is that the display shows the *previously* | |
349 | * loaded frame. we can detect this by looking at | |
350 | * the time between asserting DCONLOAD and the IRQ -- | |
351 | * if it's less than 20msec, then the DCON couldn't | |
352 | * have seen two VSYNC pulses. in that case we | |
e107e6eb | 353 | * deassert and reassert, and hope for the best. |
eecb3e4e AS |
354 | * see http://dev.laptop.org/ticket/9664 |
355 | */ | |
851f7c0e KS |
356 | delta_t = ktime_sub(dcon->irq_time, dcon->load_time); |
357 | if (dcon->switched && ktime_to_ns(delta_t) | |
358 | < NSEC_PER_MSEC * 20) { | |
ac9bbd08 | 359 | pr_err("missed loading, retrying\n"); |
eecb3e4e AS |
360 | pdata->set_dconload(1); |
361 | mdelay(41); | |
362 | pdata->set_dconload(0); | |
851f7c0e | 363 | dcon->load_time = ktime_get(); |
eecb3e4e AS |
364 | mdelay(41); |
365 | } | |
366 | } | |
367 | ||
45bfe972 | 368 | dcon_blank_fb(dcon, true); |
ac9bbd08 | 369 | pr_info("The DCON has control\n"); |
eecb3e4e AS |
370 | break; |
371 | } | |
372 | default: | |
373 | BUG(); | |
374 | } | |
375 | ||
bbe963f1 | 376 | dcon->curr_src = source; |
eecb3e4e AS |
377 | } |
378 | ||
8d2d3dd1 | 379 | static void dcon_set_source(struct dcon_priv *dcon, int arg) |
eecb3e4e | 380 | { |
bbe963f1 | 381 | if (dcon->pending_src == arg) |
eecb3e4e AS |
382 | return; |
383 | ||
bbe963f1 | 384 | dcon->pending_src = arg; |
eecb3e4e | 385 | |
14ab3daa | 386 | if (dcon->curr_src != arg) |
8d2d3dd1 | 387 | schedule_work(&dcon->switch_source); |
eecb3e4e AS |
388 | } |
389 | ||
8d2d3dd1 | 390 | static void dcon_set_source_sync(struct dcon_priv *dcon, int arg) |
eecb3e4e | 391 | { |
8d2d3dd1 | 392 | dcon_set_source(dcon, arg); |
eecb3e4e AS |
393 | flush_scheduled_work(); |
394 | } | |
395 | ||
eecb3e4e AS |
396 | static ssize_t dcon_mode_show(struct device *dev, |
397 | struct device_attribute *attr, char *buf) | |
398 | { | |
bada46e5 | 399 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
7949f30a | 400 | |
bada46e5 | 401 | return sprintf(buf, "%4.4X\n", dcon->disp_mode); |
eecb3e4e AS |
402 | } |
403 | ||
404 | static ssize_t dcon_sleep_show(struct device *dev, | |
405 | struct device_attribute *attr, char *buf) | |
406 | { | |
bada46e5 | 407 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
7949f30a | 408 | |
9ed62423 | 409 | return sprintf(buf, "%d\n", dcon->asleep); |
eecb3e4e AS |
410 | } |
411 | ||
412 | static ssize_t dcon_freeze_show(struct device *dev, | |
413 | struct device_attribute *attr, char *buf) | |
414 | { | |
bbe963f1 | 415 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
7949f30a | 416 | |
bbe963f1 | 417 | return sprintf(buf, "%d\n", dcon->curr_src == DCON_SOURCE_DCON ? 1 : 0); |
eecb3e4e AS |
418 | } |
419 | ||
bb410354 | 420 | static ssize_t dcon_mono_show(struct device *dev, |
eecb3e4e AS |
421 | struct device_attribute *attr, char *buf) |
422 | { | |
bb410354 | 423 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
7949f30a | 424 | |
9ed62423 | 425 | return sprintf(buf, "%d\n", dcon->mono); |
eecb3e4e AS |
426 | } |
427 | ||
428 | static ssize_t dcon_resumeline_show(struct device *dev, | |
429 | struct device_attribute *attr, char *buf) | |
430 | { | |
431 | return sprintf(buf, "%d\n", resumeline); | |
432 | } | |
433 | ||
bb410354 | 434 | static ssize_t dcon_mono_store(struct device *dev, |
eecb3e4e AS |
435 | struct device_attribute *attr, const char *buf, size_t count) |
436 | { | |
31a3da41 MB |
437 | unsigned long enable_mono; |
438 | int rc; | |
eecb3e4e | 439 | |
88e09a5e | 440 | rc = kstrtoul(buf, 10, &enable_mono); |
31a3da41 MB |
441 | if (rc) |
442 | return rc; | |
eecb3e4e | 443 | |
9ed62423 | 444 | dcon_set_mono_mode(dev_get_drvdata(dev), enable_mono ? true : false); |
eecb3e4e | 445 | |
31a3da41 | 446 | return count; |
eecb3e4e AS |
447 | } |
448 | ||
449 | static ssize_t dcon_freeze_store(struct device *dev, | |
450 | struct device_attribute *attr, const char *buf, size_t count) | |
451 | { | |
8d2d3dd1 | 452 | struct dcon_priv *dcon = dev_get_drvdata(dev); |
31a3da41 MB |
453 | unsigned long output; |
454 | int ret; | |
eecb3e4e | 455 | |
88e09a5e | 456 | ret = kstrtoul(buf, 10, &output); |
31a3da41 MB |
457 | if (ret) |
458 | return ret; | |
eecb3e4e | 459 | |
ac9bbd08 | 460 | pr_info("dcon_freeze_store: %lu\n", output); |
eecb3e4e AS |
461 | |
462 | switch (output) { | |
463 | case 0: | |
8d2d3dd1 | 464 | dcon_set_source(dcon, DCON_SOURCE_CPU); |
eecb3e4e AS |
465 | break; |
466 | case 1: | |
8d2d3dd1 | 467 | dcon_set_source_sync(dcon, DCON_SOURCE_DCON); |
eecb3e4e | 468 | break; |
e107e6eb | 469 | case 2: /* normally unused */ |
8d2d3dd1 | 470 | dcon_set_source(dcon, DCON_SOURCE_DCON); |
eecb3e4e AS |
471 | break; |
472 | default: | |
473 | return -EINVAL; | |
474 | } | |
475 | ||
476 | return count; | |
477 | } | |
478 | ||
479 | static ssize_t dcon_resumeline_store(struct device *dev, | |
480 | struct device_attribute *attr, const char *buf, size_t count) | |
481 | { | |
c542341d | 482 | unsigned short rl; |
31a3da41 | 483 | int rc; |
eecb3e4e | 484 | |
c542341d | 485 | rc = kstrtou16(buf, 10, &rl); |
31a3da41 | 486 | if (rc) |
eecb3e4e AS |
487 | return rc; |
488 | ||
489 | resumeline = rl; | |
8d2d3dd1 | 490 | dcon_write(dev_get_drvdata(dev), DCON_REG_SCAN_INT, resumeline); |
eecb3e4e | 491 | |
31a3da41 | 492 | return count; |
eecb3e4e AS |
493 | } |
494 | ||
495 | static ssize_t dcon_sleep_store(struct device *dev, | |
496 | struct device_attribute *attr, const char *buf, size_t count) | |
497 | { | |
31a3da41 MB |
498 | unsigned long output; |
499 | int ret; | |
eecb3e4e | 500 | |
88e09a5e | 501 | ret = kstrtoul(buf, 10, &output); |
31a3da41 MB |
502 | if (ret) |
503 | return ret; | |
eecb3e4e | 504 | |
bada46e5 | 505 | dcon_sleep(dev_get_drvdata(dev), output ? true : false); |
eecb3e4e AS |
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), | |
bb410354 | 513 | __ATTR(monochrome, 0644, dcon_mono_show, dcon_mono_store), |
eecb3e4e AS |
514 | __ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store), |
515 | }; | |
516 | ||
c59eef17 AS |
517 | static int dcon_bl_update(struct backlight_device *dev) |
518 | { | |
519 | struct dcon_priv *dcon = bl_get_data(dev); | |
520 | u8 level = dev->props.brightness & 0x0F; | |
521 | ||
522 | if (dev->props.power != FB_BLANK_UNBLANK) | |
523 | level = 0; | |
524 | ||
525 | if (level != dcon->bl_val) | |
526 | dcon_set_backlight(dcon, level); | |
527 | ||
20b27c61 AS |
528 | /* power down the DCON when the screen is blanked */ |
529 | if (!dcon->ignore_fb_events) | |
530 | dcon_sleep(dcon, !!(dev->props.state & BL_CORE_FBBLANK)); | |
531 | ||
c59eef17 AS |
532 | return 0; |
533 | } | |
534 | ||
535 | static int dcon_bl_get(struct backlight_device *dev) | |
536 | { | |
537 | struct dcon_priv *dcon = bl_get_data(dev); | |
7949f30a | 538 | |
c59eef17 AS |
539 | return dcon->bl_val; |
540 | } | |
541 | ||
acc2472e | 542 | static const struct backlight_ops dcon_bl_ops = { |
c59eef17 AS |
543 | .update_status = dcon_bl_update, |
544 | .get_brightness = dcon_bl_get, | |
eecb3e4e AS |
545 | }; |
546 | ||
c59eef17 AS |
547 | static struct backlight_properties dcon_bl_props = { |
548 | .max_brightness = 15, | |
bb7ca747 | 549 | .type = BACKLIGHT_RAW, |
c59eef17 AS |
550 | .power = FB_BLANK_UNBLANK, |
551 | }; | |
eecb3e4e | 552 | |
e107e6eb MB |
553 | static int dcon_reboot_notify(struct notifier_block *nb, |
554 | unsigned long foo, void *bar) | |
eecb3e4e | 555 | { |
8d2d3dd1 AS |
556 | struct dcon_priv *dcon = container_of(nb, struct dcon_priv, reboot_nb); |
557 | ||
558 | if (!dcon || !dcon->client) | |
2cc5939d | 559 | return NOTIFY_DONE; |
eecb3e4e AS |
560 | |
561 | /* Turn off the DCON. Entirely. */ | |
8d2d3dd1 AS |
562 | dcon_write(dcon, DCON_REG_MODE, 0x39); |
563 | dcon_write(dcon, DCON_REG_MODE, 0x32); | |
2cc5939d | 564 | return NOTIFY_DONE; |
eecb3e4e AS |
565 | } |
566 | ||
e107e6eb MB |
567 | static int unfreeze_on_panic(struct notifier_block *nb, |
568 | unsigned long e, void *p) | |
eecb3e4e AS |
569 | { |
570 | pdata->set_dconload(1); | |
571 | return NOTIFY_DONE; | |
572 | } | |
573 | ||
574 | static struct notifier_block dcon_panic_nb = { | |
575 | .notifier_call = unfreeze_on_panic, | |
576 | }; | |
577 | ||
eecb3e4e AS |
578 | static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info) |
579 | { | |
580 | strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE); | |
581 | ||
582 | return 0; | |
583 | } | |
584 | ||
585 | static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id) | |
586 | { | |
8d2d3dd1 | 587 | struct dcon_priv *dcon; |
a90dcd4f | 588 | int rc, i, j; |
eecb3e4e | 589 | |
097cd83a AS |
590 | if (!pdata) |
591 | return -ENXIO; | |
592 | ||
8d2d3dd1 AS |
593 | dcon = kzalloc(sizeof(*dcon), GFP_KERNEL); |
594 | if (!dcon) | |
595 | return -ENOMEM; | |
596 | ||
597 | dcon->client = client; | |
c40f20da | 598 | init_waitqueue_head(&dcon->waitq); |
8d2d3dd1 AS |
599 | INIT_WORK(&dcon->switch_source, dcon_source_switch); |
600 | dcon->reboot_nb.notifier_call = dcon_reboot_notify; | |
601 | dcon->reboot_nb.priority = -1; | |
602 | ||
603 | i2c_set_clientdata(client, dcon); | |
604 | ||
45bfe972 AS |
605 | if (num_registered_fb < 1) { |
606 | dev_err(&client->dev, "DCON driver requires a registered fb\n"); | |
607 | rc = -EIO; | |
608 | goto einit; | |
609 | } | |
610 | dcon->fbinfo = registered_fb[0]; | |
eecb3e4e | 611 | |
8d2d3dd1 | 612 | rc = dcon_hw_init(dcon, 1); |
eecb3e4e AS |
613 | if (rc) |
614 | goto einit; | |
615 | ||
616 | /* Add the DCON device */ | |
617 | ||
618 | dcon_device = platform_device_alloc("dcon", -1); | |
619 | ||
620 | if (dcon_device == NULL) { | |
ac9bbd08 | 621 | pr_err("Unable to create the DCON device\n"); |
eecb3e4e AS |
622 | rc = -ENOMEM; |
623 | goto eirq; | |
624 | } | |
e107e6eb | 625 | rc = platform_device_add(dcon_device); |
8d2d3dd1 | 626 | platform_set_drvdata(dcon_device, dcon); |
eecb3e4e | 627 | |
e107e6eb | 628 | if (rc) { |
ac9bbd08 | 629 | pr_err("Unable to add the DCON device\n"); |
eecb3e4e AS |
630 | goto edev; |
631 | } | |
632 | ||
e107e6eb | 633 | for (i = 0; i < ARRAY_SIZE(dcon_device_files); i++) { |
a90dcd4f MB |
634 | rc = device_create_file(&dcon_device->dev, |
635 | &dcon_device_files[i]); | |
636 | if (rc) { | |
637 | dev_err(&dcon_device->dev, "Cannot create sysfs file\n"); | |
638 | goto ecreate; | |
639 | } | |
640 | } | |
eecb3e4e | 641 | |
c59eef17 | 642 | dcon->bl_val = dcon_read(dcon, DCON_REG_BRIGHT) & 0x0F; |
eecb3e4e | 643 | |
c59eef17 AS |
644 | /* Add the backlight device for the DCON */ |
645 | dcon_bl_props.brightness = dcon->bl_val; | |
646 | dcon->bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev, | |
647 | dcon, &dcon_bl_ops, &dcon_bl_props); | |
648 | if (IS_ERR(dcon->bl_dev)) { | |
649 | dev_err(&client->dev, "cannot register backlight dev (%ld)\n", | |
650 | PTR_ERR(dcon->bl_dev)); | |
651 | dcon->bl_dev = NULL; | |
eecb3e4e AS |
652 | } |
653 | ||
8d2d3dd1 | 654 | register_reboot_notifier(&dcon->reboot_nb); |
eecb3e4e | 655 | atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb); |
eecb3e4e AS |
656 | |
657 | return 0; | |
658 | ||
a90dcd4f MB |
659 | ecreate: |
660 | for (j = 0; j < i; j++) | |
661 | device_remove_file(&dcon_device->dev, &dcon_device_files[j]); | |
eecb3e4e AS |
662 | edev: |
663 | platform_device_unregister(dcon_device); | |
664 | dcon_device = NULL; | |
eecb3e4e | 665 | eirq: |
bbe963f1 | 666 | free_irq(DCON_IRQ, dcon); |
eecb3e4e | 667 | einit: |
8d2d3dd1 | 668 | kfree(dcon); |
eecb3e4e AS |
669 | return rc; |
670 | } | |
671 | ||
672 | static int dcon_remove(struct i2c_client *client) | |
673 | { | |
8d2d3dd1 AS |
674 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
675 | ||
8d2d3dd1 | 676 | unregister_reboot_notifier(&dcon->reboot_nb); |
eecb3e4e AS |
677 | atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb); |
678 | ||
bbe963f1 | 679 | free_irq(DCON_IRQ, dcon); |
eecb3e4e | 680 | |
16ceb728 | 681 | backlight_device_unregister(dcon->bl_dev); |
eecb3e4e AS |
682 | |
683 | if (dcon_device != NULL) | |
684 | platform_device_unregister(dcon_device); | |
8d2d3dd1 AS |
685 | cancel_work_sync(&dcon->switch_source); |
686 | ||
687 | kfree(dcon); | |
eecb3e4e | 688 | |
eecb3e4e AS |
689 | return 0; |
690 | } | |
691 | ||
692 | #ifdef CONFIG_PM | |
3e5e624b | 693 | static int dcon_suspend(struct device *dev) |
eecb3e4e | 694 | { |
3e5e624b | 695 | struct i2c_client *client = to_i2c_client(dev); |
8d2d3dd1 AS |
696 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
697 | ||
bada46e5 | 698 | if (!dcon->asleep) { |
eecb3e4e | 699 | /* Set up the DCON to have the source */ |
8d2d3dd1 | 700 | dcon_set_source_sync(dcon, DCON_SOURCE_DCON); |
eecb3e4e AS |
701 | } |
702 | ||
703 | return 0; | |
704 | } | |
705 | ||
3e5e624b | 706 | static int dcon_resume(struct device *dev) |
eecb3e4e | 707 | { |
3e5e624b | 708 | struct i2c_client *client = to_i2c_client(dev); |
8d2d3dd1 AS |
709 | struct dcon_priv *dcon = i2c_get_clientdata(client); |
710 | ||
bada46e5 | 711 | if (!dcon->asleep) { |
8d2d3dd1 AS |
712 | dcon_bus_stabilize(dcon, 0); |
713 | dcon_set_source(dcon, DCON_SOURCE_CPU); | |
eecb3e4e AS |
714 | } |
715 | ||
716 | return 0; | |
717 | } | |
718 | ||
3e5e624b AS |
719 | #else |
720 | ||
721 | #define dcon_suspend NULL | |
722 | #define dcon_resume NULL | |
723 | ||
724 | #endif /* CONFIG_PM */ | |
eecb3e4e AS |
725 | |
726 | ||
097cd83a | 727 | irqreturn_t dcon_interrupt(int irq, void *id) |
eecb3e4e | 728 | { |
bbe963f1 | 729 | struct dcon_priv *dcon = id; |
91762057 | 730 | u8 status; |
eecb3e4e | 731 | |
91762057 | 732 | if (pdata->read_status(&status)) |
eecb3e4e AS |
733 | return IRQ_NONE; |
734 | ||
735 | switch (status & 3) { | |
736 | case 3: | |
ac9bbd08 | 737 | pr_debug("DCONLOAD_MISSED interrupt\n"); |
eecb3e4e AS |
738 | break; |
739 | ||
740 | case 2: /* switch to DCON mode */ | |
741 | case 1: /* switch to CPU mode */ | |
309ef2a2 | 742 | dcon->switched = true; |
851f7c0e | 743 | dcon->irq_time = ktime_get(); |
c40f20da | 744 | wake_up(&dcon->waitq); |
eecb3e4e AS |
745 | break; |
746 | ||
747 | case 0: | |
748 | /* workaround resume case: the DCON (on 1.5) doesn't | |
749 | * ever assert status 0x01 when switching to CPU mode | |
750 | * during resume. this is because DCONLOAD is de-asserted | |
751 | * _immediately_ upon exiting S3, so the actual release | |
752 | * of the DCON happened long before this point. | |
753 | * see http://dev.laptop.org/ticket/9869 | |
754 | */ | |
309ef2a2 AS |
755 | if (dcon->curr_src != dcon->pending_src && !dcon->switched) { |
756 | dcon->switched = true; | |
851f7c0e | 757 | dcon->irq_time = ktime_get(); |
c40f20da | 758 | wake_up(&dcon->waitq); |
ac9bbd08 | 759 | pr_debug("switching w/ status 0/0\n"); |
eecb3e4e | 760 | } else { |
ac9bbd08 | 761 | pr_debug("scanline interrupt w/CPU\n"); |
eecb3e4e AS |
762 | } |
763 | } | |
764 | ||
765 | return IRQ_HANDLED; | |
766 | } | |
767 | ||
3e5e624b AS |
768 | static const struct dev_pm_ops dcon_pm_ops = { |
769 | .suspend = dcon_suspend, | |
770 | .resume = dcon_resume, | |
771 | }; | |
772 | ||
56463de0 | 773 | static const struct i2c_device_id dcon_idtable[] = { |
eecb3e4e AS |
774 | { "olpc_dcon", 0 }, |
775 | { } | |
776 | }; | |
eecb3e4e AS |
777 | MODULE_DEVICE_TABLE(i2c, dcon_idtable); |
778 | ||
4e8e8716 | 779 | static struct i2c_driver dcon_driver = { |
eecb3e4e AS |
780 | .driver = { |
781 | .name = "olpc_dcon", | |
3e5e624b | 782 | .pm = &dcon_pm_ops, |
eecb3e4e AS |
783 | }, |
784 | .class = I2C_CLASS_DDC | I2C_CLASS_HWMON, | |
785 | .id_table = dcon_idtable, | |
786 | .probe = dcon_probe, | |
c8ddc220 | 787 | .remove = dcon_remove, |
eecb3e4e AS |
788 | .detect = dcon_detect, |
789 | .address_list = normal_i2c, | |
eecb3e4e AS |
790 | }; |
791 | ||
eecb3e4e AS |
792 | static int __init olpc_dcon_init(void) |
793 | { | |
097cd83a AS |
794 | #ifdef CONFIG_FB_OLPC_DCON_1_5 |
795 | /* XO-1.5 */ | |
796 | if (olpc_board_at_least(olpc_board(0xd0))) | |
797 | pdata = &dcon_pdata_xo_1_5; | |
798 | #endif | |
799 | #ifdef CONFIG_FB_OLPC_DCON_1 | |
800 | if (!pdata) | |
801 | pdata = &dcon_pdata_xo_1; | |
802 | #endif | |
eecb3e4e | 803 | |
56463de0 | 804 | return i2c_add_driver(&dcon_driver); |
eecb3e4e AS |
805 | } |
806 | ||
807 | static void __exit olpc_dcon_exit(void) | |
808 | { | |
809 | i2c_del_driver(&dcon_driver); | |
810 | } | |
811 | ||
812 | module_init(olpc_dcon_init); | |
813 | module_exit(olpc_dcon_exit); | |
814 | ||
815 | MODULE_LICENSE("GPL"); |