diff options
Diffstat (limited to 'drivers/video/fbdev/broadsheetfb.c')
-rw-r--r-- | drivers/video/fbdev/broadsheetfb.c | 1223 |
1 files changed, 1223 insertions, 0 deletions
diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c new file mode 100644 index 000000000000..8556264b16b7 --- /dev/null +++ b/drivers/video/fbdev/broadsheetfb.c | |||
@@ -0,0 +1,1223 @@ | |||
1 | /* | ||
2 | * broadsheetfb.c -- FB driver for E-Ink Broadsheet controller | ||
3 | * | ||
4 | * Copyright (C) 2008, Jaya Kumar | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of the GNU General Public | ||
7 | * License. See the file COPYING in the main directory of this archive for | ||
8 | * more details. | ||
9 | * | ||
10 | * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. | ||
11 | * | ||
12 | * This driver is written to be used with the Broadsheet display controller. | ||
13 | * | ||
14 | * It is intended to be architecture independent. A board specific driver | ||
15 | * must be used to perform all the physical IO interactions. | ||
16 | * | ||
17 | */ | ||
18 | |||
19 | #include <linux/module.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/errno.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/mm.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/vmalloc.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/interrupt.h> | ||
28 | #include <linux/fb.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/list.h> | ||
32 | #include <linux/firmware.h> | ||
33 | #include <linux/uaccess.h> | ||
34 | |||
35 | #include <video/broadsheetfb.h> | ||
36 | |||
37 | /* track panel specific parameters */ | ||
38 | struct panel_info { | ||
39 | int w; | ||
40 | int h; | ||
41 | u16 sdcfg; | ||
42 | u16 gdcfg; | ||
43 | u16 lutfmt; | ||
44 | u16 fsynclen; | ||
45 | u16 fendfbegin; | ||
46 | u16 lsynclen; | ||
47 | u16 lendlbegin; | ||
48 | u16 pixclk; | ||
49 | }; | ||
50 | |||
51 | /* table of panel specific parameters to be indexed into by the board drivers */ | ||
52 | static struct panel_info panel_table[] = { | ||
53 | { /* standard 6" on TFT backplane */ | ||
54 | .w = 800, | ||
55 | .h = 600, | ||
56 | .sdcfg = (100 | (1 << 8) | (1 << 9)), | ||
57 | .gdcfg = 2, | ||
58 | .lutfmt = (4 | (1 << 7)), | ||
59 | .fsynclen = 4, | ||
60 | .fendfbegin = (10 << 8) | 4, | ||
61 | .lsynclen = 10, | ||
62 | .lendlbegin = (100 << 8) | 4, | ||
63 | .pixclk = 6, | ||
64 | }, | ||
65 | { /* custom 3.7" flexible on PET or steel */ | ||
66 | .w = 320, | ||
67 | .h = 240, | ||
68 | .sdcfg = (67 | (0 << 8) | (0 << 9) | (0 << 10) | (0 << 12)), | ||
69 | .gdcfg = 3, | ||
70 | .lutfmt = (4 | (1 << 7)), | ||
71 | .fsynclen = 0, | ||
72 | .fendfbegin = (80 << 8) | 4, | ||
73 | .lsynclen = 10, | ||
74 | .lendlbegin = (80 << 8) | 20, | ||
75 | .pixclk = 14, | ||
76 | }, | ||
77 | { /* standard 9.7" on TFT backplane */ | ||
78 | .w = 1200, | ||
79 | .h = 825, | ||
80 | .sdcfg = (100 | (1 << 8) | (1 << 9) | (0 << 10) | (0 << 12)), | ||
81 | .gdcfg = 2, | ||
82 | .lutfmt = (4 | (1 << 7)), | ||
83 | .fsynclen = 0, | ||
84 | .fendfbegin = (4 << 8) | 4, | ||
85 | .lsynclen = 4, | ||
86 | .lendlbegin = (60 << 8) | 10, | ||
87 | .pixclk = 3, | ||
88 | }, | ||
89 | }; | ||
90 | |||
91 | #define DPY_W 800 | ||
92 | #define DPY_H 600 | ||
93 | |||
94 | static struct fb_fix_screeninfo broadsheetfb_fix = { | ||
95 | .id = "broadsheetfb", | ||
96 | .type = FB_TYPE_PACKED_PIXELS, | ||
97 | .visual = FB_VISUAL_STATIC_PSEUDOCOLOR, | ||
98 | .xpanstep = 0, | ||
99 | .ypanstep = 0, | ||
100 | .ywrapstep = 0, | ||
101 | .line_length = DPY_W, | ||
102 | .accel = FB_ACCEL_NONE, | ||
103 | }; | ||
104 | |||
105 | static struct fb_var_screeninfo broadsheetfb_var = { | ||
106 | .xres = DPY_W, | ||
107 | .yres = DPY_H, | ||
108 | .xres_virtual = DPY_W, | ||
109 | .yres_virtual = DPY_H, | ||
110 | .bits_per_pixel = 8, | ||
111 | .grayscale = 1, | ||
112 | .red = { 0, 4, 0 }, | ||
113 | .green = { 0, 4, 0 }, | ||
114 | .blue = { 0, 4, 0 }, | ||
115 | .transp = { 0, 0, 0 }, | ||
116 | }; | ||
117 | |||
118 | /* main broadsheetfb functions */ | ||
119 | static void broadsheet_gpio_issue_data(struct broadsheetfb_par *par, u16 data) | ||
120 | { | ||
121 | par->board->set_ctl(par, BS_WR, 0); | ||
122 | par->board->set_hdb(par, data); | ||
123 | par->board->set_ctl(par, BS_WR, 1); | ||
124 | } | ||
125 | |||
126 | static void broadsheet_gpio_issue_cmd(struct broadsheetfb_par *par, u16 data) | ||
127 | { | ||
128 | par->board->set_ctl(par, BS_DC, 0); | ||
129 | broadsheet_gpio_issue_data(par, data); | ||
130 | } | ||
131 | |||
132 | static void broadsheet_gpio_send_command(struct broadsheetfb_par *par, u16 data) | ||
133 | { | ||
134 | par->board->wait_for_rdy(par); | ||
135 | |||
136 | par->board->set_ctl(par, BS_CS, 0); | ||
137 | broadsheet_gpio_issue_cmd(par, data); | ||
138 | par->board->set_ctl(par, BS_DC, 1); | ||
139 | par->board->set_ctl(par, BS_CS, 1); | ||
140 | } | ||
141 | |||
142 | static void broadsheet_gpio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, | ||
143 | int argc, u16 *argv) | ||
144 | { | ||
145 | int i; | ||
146 | |||
147 | par->board->wait_for_rdy(par); | ||
148 | |||
149 | par->board->set_ctl(par, BS_CS, 0); | ||
150 | broadsheet_gpio_issue_cmd(par, cmd); | ||
151 | par->board->set_ctl(par, BS_DC, 1); | ||
152 | |||
153 | for (i = 0; i < argc; i++) | ||
154 | broadsheet_gpio_issue_data(par, argv[i]); | ||
155 | par->board->set_ctl(par, BS_CS, 1); | ||
156 | } | ||
157 | |||
158 | static void broadsheet_mmio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, | ||
159 | int argc, u16 *argv) | ||
160 | { | ||
161 | int i; | ||
162 | |||
163 | par->board->mmio_write(par, BS_MMIO_CMD, cmd); | ||
164 | |||
165 | for (i = 0; i < argc; i++) | ||
166 | par->board->mmio_write(par, BS_MMIO_DATA, argv[i]); | ||
167 | } | ||
168 | |||
169 | static void broadsheet_send_command(struct broadsheetfb_par *par, u16 data) | ||
170 | { | ||
171 | if (par->board->mmio_write) | ||
172 | par->board->mmio_write(par, BS_MMIO_CMD, data); | ||
173 | else | ||
174 | broadsheet_gpio_send_command(par, data); | ||
175 | } | ||
176 | |||
177 | static void broadsheet_send_cmdargs(struct broadsheetfb_par *par, u16 cmd, | ||
178 | int argc, u16 *argv) | ||
179 | { | ||
180 | if (par->board->mmio_write) | ||
181 | broadsheet_mmio_send_cmdargs(par, cmd, argc, argv); | ||
182 | else | ||
183 | broadsheet_gpio_send_cmdargs(par, cmd, argc, argv); | ||
184 | } | ||
185 | |||
186 | static void broadsheet_gpio_burst_write(struct broadsheetfb_par *par, int size, | ||
187 | u16 *data) | ||
188 | { | ||
189 | int i; | ||
190 | u16 tmp; | ||
191 | |||
192 | par->board->set_ctl(par, BS_CS, 0); | ||
193 | par->board->set_ctl(par, BS_DC, 1); | ||
194 | |||
195 | for (i = 0; i < size; i++) { | ||
196 | par->board->set_ctl(par, BS_WR, 0); | ||
197 | tmp = (data[i] & 0x0F) << 4; | ||
198 | tmp |= (data[i] & 0x0F00) << 4; | ||
199 | par->board->set_hdb(par, tmp); | ||
200 | par->board->set_ctl(par, BS_WR, 1); | ||
201 | } | ||
202 | |||
203 | par->board->set_ctl(par, BS_CS, 1); | ||
204 | } | ||
205 | |||
206 | static void broadsheet_mmio_burst_write(struct broadsheetfb_par *par, int size, | ||
207 | u16 *data) | ||
208 | { | ||
209 | int i; | ||
210 | u16 tmp; | ||
211 | |||
212 | for (i = 0; i < size; i++) { | ||
213 | tmp = (data[i] & 0x0F) << 4; | ||
214 | tmp |= (data[i] & 0x0F00) << 4; | ||
215 | par->board->mmio_write(par, BS_MMIO_DATA, tmp); | ||
216 | } | ||
217 | |||
218 | } | ||
219 | |||
220 | static void broadsheet_burst_write(struct broadsheetfb_par *par, int size, | ||
221 | u16 *data) | ||
222 | { | ||
223 | if (par->board->mmio_write) | ||
224 | broadsheet_mmio_burst_write(par, size, data); | ||
225 | else | ||
226 | broadsheet_gpio_burst_write(par, size, data); | ||
227 | } | ||
228 | |||
229 | static u16 broadsheet_gpio_get_data(struct broadsheetfb_par *par) | ||
230 | { | ||
231 | u16 res; | ||
232 | /* wait for ready to go hi. (lo is busy) */ | ||
233 | par->board->wait_for_rdy(par); | ||
234 | |||
235 | /* cs lo, dc lo for cmd, we lo for each data, db as usual */ | ||
236 | par->board->set_ctl(par, BS_DC, 1); | ||
237 | par->board->set_ctl(par, BS_CS, 0); | ||
238 | par->board->set_ctl(par, BS_WR, 0); | ||
239 | |||
240 | res = par->board->get_hdb(par); | ||
241 | |||
242 | /* strobe wr */ | ||
243 | par->board->set_ctl(par, BS_WR, 1); | ||
244 | par->board->set_ctl(par, BS_CS, 1); | ||
245 | |||
246 | return res; | ||
247 | } | ||
248 | |||
249 | |||
250 | static u16 broadsheet_get_data(struct broadsheetfb_par *par) | ||
251 | { | ||
252 | if (par->board->mmio_read) | ||
253 | return par->board->mmio_read(par); | ||
254 | else | ||
255 | return broadsheet_gpio_get_data(par); | ||
256 | } | ||
257 | |||
258 | static void broadsheet_gpio_write_reg(struct broadsheetfb_par *par, u16 reg, | ||
259 | u16 data) | ||
260 | { | ||
261 | /* wait for ready to go hi. (lo is busy) */ | ||
262 | par->board->wait_for_rdy(par); | ||
263 | |||
264 | /* cs lo, dc lo for cmd, we lo for each data, db as usual */ | ||
265 | par->board->set_ctl(par, BS_CS, 0); | ||
266 | |||
267 | broadsheet_gpio_issue_cmd(par, BS_CMD_WR_REG); | ||
268 | |||
269 | par->board->set_ctl(par, BS_DC, 1); | ||
270 | |||
271 | broadsheet_gpio_issue_data(par, reg); | ||
272 | broadsheet_gpio_issue_data(par, data); | ||
273 | |||
274 | par->board->set_ctl(par, BS_CS, 1); | ||
275 | } | ||
276 | |||
277 | static void broadsheet_mmio_write_reg(struct broadsheetfb_par *par, u16 reg, | ||
278 | u16 data) | ||
279 | { | ||
280 | par->board->mmio_write(par, BS_MMIO_CMD, BS_CMD_WR_REG); | ||
281 | par->board->mmio_write(par, BS_MMIO_DATA, reg); | ||
282 | par->board->mmio_write(par, BS_MMIO_DATA, data); | ||
283 | |||
284 | } | ||
285 | |||
286 | static void broadsheet_write_reg(struct broadsheetfb_par *par, u16 reg, | ||
287 | u16 data) | ||
288 | { | ||
289 | if (par->board->mmio_write) | ||
290 | broadsheet_mmio_write_reg(par, reg, data); | ||
291 | else | ||
292 | broadsheet_gpio_write_reg(par, reg, data); | ||
293 | } | ||
294 | |||
295 | static void broadsheet_write_reg32(struct broadsheetfb_par *par, u16 reg, | ||
296 | u32 data) | ||
297 | { | ||
298 | broadsheet_write_reg(par, reg, cpu_to_le32(data) & 0xFFFF); | ||
299 | broadsheet_write_reg(par, reg + 2, (cpu_to_le32(data) >> 16) & 0xFFFF); | ||
300 | } | ||
301 | |||
302 | |||
303 | static u16 broadsheet_read_reg(struct broadsheetfb_par *par, u16 reg) | ||
304 | { | ||
305 | broadsheet_send_cmdargs(par, BS_CMD_RD_REG, 1, ®); | ||
306 | par->board->wait_for_rdy(par); | ||
307 | return broadsheet_get_data(par); | ||
308 | } | ||
309 | |||
310 | /* functions for waveform manipulation */ | ||
311 | static int is_broadsheet_pll_locked(struct broadsheetfb_par *par) | ||
312 | { | ||
313 | return broadsheet_read_reg(par, 0x000A) & 0x0001; | ||
314 | } | ||
315 | |||
316 | static int broadsheet_setup_plls(struct broadsheetfb_par *par) | ||
317 | { | ||
318 | int retry_count = 0; | ||
319 | u16 tmp; | ||
320 | |||
321 | /* disable arral saemipu mode */ | ||
322 | broadsheet_write_reg(par, 0x0006, 0x0000); | ||
323 | |||
324 | broadsheet_write_reg(par, 0x0010, 0x0004); | ||
325 | broadsheet_write_reg(par, 0x0012, 0x5949); | ||
326 | broadsheet_write_reg(par, 0x0014, 0x0040); | ||
327 | broadsheet_write_reg(par, 0x0016, 0x0000); | ||
328 | |||
329 | do { | ||
330 | if (retry_count++ > 100) | ||
331 | return -ETIMEDOUT; | ||
332 | mdelay(1); | ||
333 | } while (!is_broadsheet_pll_locked(par)); | ||
334 | |||
335 | tmp = broadsheet_read_reg(par, 0x0006); | ||
336 | tmp &= ~0x1; | ||
337 | broadsheet_write_reg(par, 0x0006, tmp); | ||
338 | |||
339 | return 0; | ||
340 | } | ||
341 | |||
342 | static int broadsheet_setup_spi(struct broadsheetfb_par *par) | ||
343 | { | ||
344 | |||
345 | broadsheet_write_reg(par, 0x0204, ((3 << 3) | 1)); | ||
346 | broadsheet_write_reg(par, 0x0208, 0x0001); | ||
347 | |||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | static int broadsheet_setup_spiflash(struct broadsheetfb_par *par, | ||
352 | u16 *orig_sfmcd) | ||
353 | { | ||
354 | |||
355 | *orig_sfmcd = broadsheet_read_reg(par, 0x0204); | ||
356 | broadsheet_write_reg(par, 0x0208, 0); | ||
357 | broadsheet_write_reg(par, 0x0204, 0); | ||
358 | broadsheet_write_reg(par, 0x0204, ((3 << 3) | 1)); | ||
359 | |||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static int broadsheet_spiflash_wait_for_bit(struct broadsheetfb_par *par, | ||
364 | u16 reg, int bitnum, int val, | ||
365 | int timeout) | ||
366 | { | ||
367 | u16 tmp; | ||
368 | |||
369 | do { | ||
370 | tmp = broadsheet_read_reg(par, reg); | ||
371 | if (((tmp >> bitnum) & 1) == val) | ||
372 | return 0; | ||
373 | mdelay(1); | ||
374 | } while (timeout--); | ||
375 | |||
376 | return -ETIMEDOUT; | ||
377 | } | ||
378 | |||
379 | static int broadsheet_spiflash_write_byte(struct broadsheetfb_par *par, u8 data) | ||
380 | { | ||
381 | broadsheet_write_reg(par, 0x0202, (data | 0x100)); | ||
382 | |||
383 | return broadsheet_spiflash_wait_for_bit(par, 0x0206, 3, 0, 100); | ||
384 | } | ||
385 | |||
386 | static int broadsheet_spiflash_read_byte(struct broadsheetfb_par *par, u8 *data) | ||
387 | { | ||
388 | int err; | ||
389 | u16 tmp; | ||
390 | |||
391 | broadsheet_write_reg(par, 0x0202, 0); | ||
392 | |||
393 | err = broadsheet_spiflash_wait_for_bit(par, 0x0206, 3, 0, 100); | ||
394 | if (err) | ||
395 | return err; | ||
396 | |||
397 | tmp = broadsheet_read_reg(par, 0x200); | ||
398 | |||
399 | *data = tmp & 0xFF; | ||
400 | |||
401 | return 0; | ||
402 | } | ||
403 | |||
404 | static int broadsheet_spiflash_wait_for_status(struct broadsheetfb_par *par, | ||
405 | int timeout) | ||
406 | { | ||
407 | u8 tmp; | ||
408 | int err; | ||
409 | |||
410 | do { | ||
411 | broadsheet_write_reg(par, 0x0208, 1); | ||
412 | |||
413 | err = broadsheet_spiflash_write_byte(par, 0x05); | ||
414 | if (err) | ||
415 | goto failout; | ||
416 | |||
417 | err = broadsheet_spiflash_read_byte(par, &tmp); | ||
418 | if (err) | ||
419 | goto failout; | ||
420 | |||
421 | broadsheet_write_reg(par, 0x0208, 0); | ||
422 | |||
423 | if (!(tmp & 0x1)) | ||
424 | return 0; | ||
425 | |||
426 | mdelay(5); | ||
427 | } while (timeout--); | ||
428 | |||
429 | dev_err(par->info->device, "Timed out waiting for spiflash status\n"); | ||
430 | return -ETIMEDOUT; | ||
431 | |||
432 | failout: | ||
433 | broadsheet_write_reg(par, 0x0208, 0); | ||
434 | return err; | ||
435 | } | ||
436 | |||
437 | static int broadsheet_spiflash_op_on_address(struct broadsheetfb_par *par, | ||
438 | u8 op, u32 addr) | ||
439 | { | ||
440 | int i; | ||
441 | u8 tmp; | ||
442 | int err; | ||
443 | |||
444 | broadsheet_write_reg(par, 0x0208, 1); | ||
445 | |||
446 | err = broadsheet_spiflash_write_byte(par, op); | ||
447 | if (err) | ||
448 | return err; | ||
449 | |||
450 | for (i = 2; i >= 0; i--) { | ||
451 | tmp = ((addr >> (i * 8)) & 0xFF); | ||
452 | err = broadsheet_spiflash_write_byte(par, tmp); | ||
453 | if (err) | ||
454 | return err; | ||
455 | } | ||
456 | |||
457 | return err; | ||
458 | } | ||
459 | |||
460 | static int broadsheet_verify_spiflash(struct broadsheetfb_par *par, | ||
461 | int *flash_type) | ||
462 | { | ||
463 | int err = 0; | ||
464 | u8 sig; | ||
465 | |||
466 | err = broadsheet_spiflash_op_on_address(par, 0xAB, 0x00000000); | ||
467 | if (err) | ||
468 | goto failout; | ||
469 | |||
470 | err = broadsheet_spiflash_read_byte(par, &sig); | ||
471 | if (err) | ||
472 | goto failout; | ||
473 | |||
474 | if ((sig != 0x10) && (sig != 0x11)) { | ||
475 | dev_err(par->info->device, "Unexpected flash type\n"); | ||
476 | err = -EINVAL; | ||
477 | goto failout; | ||
478 | } | ||
479 | |||
480 | *flash_type = sig; | ||
481 | |||
482 | failout: | ||
483 | broadsheet_write_reg(par, 0x0208, 0); | ||
484 | return err; | ||
485 | } | ||
486 | |||
487 | static int broadsheet_setup_for_wfm_write(struct broadsheetfb_par *par, | ||
488 | u16 *initial_sfmcd, int *flash_type) | ||
489 | |||
490 | { | ||
491 | int err; | ||
492 | |||
493 | err = broadsheet_setup_plls(par); | ||
494 | if (err) | ||
495 | return err; | ||
496 | |||
497 | broadsheet_write_reg(par, 0x0106, 0x0203); | ||
498 | |||
499 | err = broadsheet_setup_spi(par); | ||
500 | if (err) | ||
501 | return err; | ||
502 | |||
503 | err = broadsheet_setup_spiflash(par, initial_sfmcd); | ||
504 | if (err) | ||
505 | return err; | ||
506 | |||
507 | return broadsheet_verify_spiflash(par, flash_type); | ||
508 | } | ||
509 | |||
510 | static int broadsheet_spiflash_write_control(struct broadsheetfb_par *par, | ||
511 | int mode) | ||
512 | { | ||
513 | int err; | ||
514 | |||
515 | broadsheet_write_reg(par, 0x0208, 1); | ||
516 | if (mode) | ||
517 | err = broadsheet_spiflash_write_byte(par, 0x06); | ||
518 | else | ||
519 | err = broadsheet_spiflash_write_byte(par, 0x04); | ||
520 | |||
521 | broadsheet_write_reg(par, 0x0208, 0); | ||
522 | return err; | ||
523 | } | ||
524 | |||
525 | static int broadsheet_spiflash_erase_sector(struct broadsheetfb_par *par, | ||
526 | int addr) | ||
527 | { | ||
528 | int err; | ||
529 | |||
530 | broadsheet_spiflash_write_control(par, 1); | ||
531 | |||
532 | err = broadsheet_spiflash_op_on_address(par, 0xD8, addr); | ||
533 | |||
534 | broadsheet_write_reg(par, 0x0208, 0); | ||
535 | |||
536 | if (err) | ||
537 | return err; | ||
538 | |||
539 | err = broadsheet_spiflash_wait_for_status(par, 1000); | ||
540 | |||
541 | return err; | ||
542 | } | ||
543 | |||
544 | static int broadsheet_spiflash_read_range(struct broadsheetfb_par *par, | ||
545 | int addr, int size, char *data) | ||
546 | { | ||
547 | int err; | ||
548 | int i; | ||
549 | |||
550 | err = broadsheet_spiflash_op_on_address(par, 0x03, addr); | ||
551 | if (err) | ||
552 | goto failout; | ||
553 | |||
554 | for (i = 0; i < size; i++) { | ||
555 | err = broadsheet_spiflash_read_byte(par, &data[i]); | ||
556 | if (err) | ||
557 | goto failout; | ||
558 | } | ||
559 | |||
560 | failout: | ||
561 | broadsheet_write_reg(par, 0x0208, 0); | ||
562 | return err; | ||
563 | } | ||
564 | |||
565 | #define BS_SPIFLASH_PAGE_SIZE 256 | ||
566 | static int broadsheet_spiflash_write_page(struct broadsheetfb_par *par, | ||
567 | int addr, const char *data) | ||
568 | { | ||
569 | int err; | ||
570 | int i; | ||
571 | |||
572 | broadsheet_spiflash_write_control(par, 1); | ||
573 | |||
574 | err = broadsheet_spiflash_op_on_address(par, 0x02, addr); | ||
575 | if (err) | ||
576 | goto failout; | ||
577 | |||
578 | for (i = 0; i < BS_SPIFLASH_PAGE_SIZE; i++) { | ||
579 | err = broadsheet_spiflash_write_byte(par, data[i]); | ||
580 | if (err) | ||
581 | goto failout; | ||
582 | } | ||
583 | |||
584 | broadsheet_write_reg(par, 0x0208, 0); | ||
585 | |||
586 | err = broadsheet_spiflash_wait_for_status(par, 100); | ||
587 | |||
588 | failout: | ||
589 | return err; | ||
590 | } | ||
591 | |||
592 | static int broadsheet_spiflash_write_sector(struct broadsheetfb_par *par, | ||
593 | int addr, const char *data, int sector_size) | ||
594 | { | ||
595 | int i; | ||
596 | int err; | ||
597 | |||
598 | for (i = 0; i < sector_size; i += BS_SPIFLASH_PAGE_SIZE) { | ||
599 | err = broadsheet_spiflash_write_page(par, addr + i, &data[i]); | ||
600 | if (err) | ||
601 | return err; | ||
602 | } | ||
603 | return 0; | ||
604 | } | ||
605 | |||
606 | /* | ||
607 | * The caller must guarantee that the data to be rewritten is entirely | ||
608 | * contained within this sector. That is, data_start_addr + data_len | ||
609 | * must be less than sector_start_addr + sector_size. | ||
610 | */ | ||
611 | static int broadsheet_spiflash_rewrite_sector(struct broadsheetfb_par *par, | ||
612 | int sector_size, int data_start_addr, | ||
613 | int data_len, const char *data) | ||
614 | { | ||
615 | int err; | ||
616 | char *sector_buffer; | ||
617 | int tail_start_addr; | ||
618 | int start_sector_addr; | ||
619 | |||
620 | sector_buffer = kzalloc(sizeof(char)*sector_size, GFP_KERNEL); | ||
621 | if (!sector_buffer) | ||
622 | return -ENOMEM; | ||
623 | |||
624 | /* the start address of the sector is the 0th byte of that sector */ | ||
625 | start_sector_addr = (data_start_addr / sector_size) * sector_size; | ||
626 | |||
627 | /* | ||
628 | * check if there is head data that we need to readback into our sector | ||
629 | * buffer first | ||
630 | */ | ||
631 | if (data_start_addr != start_sector_addr) { | ||
632 | /* | ||
633 | * we need to read every byte up till the start address of our | ||
634 | * data and we put it into our sector buffer. | ||
635 | */ | ||
636 | err = broadsheet_spiflash_read_range(par, start_sector_addr, | ||
637 | data_start_addr, sector_buffer); | ||
638 | if (err) | ||
639 | return err; | ||
640 | } | ||
641 | |||
642 | /* now we copy our data into the right place in the sector buffer */ | ||
643 | memcpy(sector_buffer + data_start_addr, data, data_len); | ||
644 | |||
645 | /* | ||
646 | * now we check if there is a tail section of the sector that we need to | ||
647 | * readback. | ||
648 | */ | ||
649 | tail_start_addr = (data_start_addr + data_len) % sector_size; | ||
650 | |||
651 | if (tail_start_addr) { | ||
652 | int tail_len; | ||
653 | |||
654 | tail_len = sector_size - tail_start_addr; | ||
655 | |||
656 | /* now we read this tail into our sector buffer */ | ||
657 | err = broadsheet_spiflash_read_range(par, tail_start_addr, | ||
658 | tail_len, sector_buffer + tail_start_addr); | ||
659 | if (err) | ||
660 | return err; | ||
661 | } | ||
662 | |||
663 | /* if we got here we have the full sector that we want to rewrite. */ | ||
664 | |||
665 | /* first erase the sector */ | ||
666 | err = broadsheet_spiflash_erase_sector(par, start_sector_addr); | ||
667 | if (err) | ||
668 | return err; | ||
669 | |||
670 | /* now write it */ | ||
671 | err = broadsheet_spiflash_write_sector(par, start_sector_addr, | ||
672 | sector_buffer, sector_size); | ||
673 | return err; | ||
674 | } | ||
675 | |||
676 | static int broadsheet_write_spiflash(struct broadsheetfb_par *par, u32 wfm_addr, | ||
677 | const u8 *wfm, int bytecount, int flash_type) | ||
678 | { | ||
679 | int sector_size; | ||
680 | int err; | ||
681 | int cur_addr; | ||
682 | int writecount; | ||
683 | int maxlen; | ||
684 | int offset = 0; | ||
685 | |||
686 | switch (flash_type) { | ||
687 | case 0x10: | ||
688 | sector_size = 32*1024; | ||
689 | break; | ||
690 | case 0x11: | ||
691 | default: | ||
692 | sector_size = 64*1024; | ||
693 | break; | ||
694 | } | ||
695 | |||
696 | while (bytecount) { | ||
697 | cur_addr = wfm_addr + offset; | ||
698 | maxlen = roundup(cur_addr, sector_size) - cur_addr; | ||
699 | writecount = min(bytecount, maxlen); | ||
700 | |||
701 | err = broadsheet_spiflash_rewrite_sector(par, sector_size, | ||
702 | cur_addr, writecount, wfm + offset); | ||
703 | if (err) | ||
704 | return err; | ||
705 | |||
706 | offset += writecount; | ||
707 | bytecount -= writecount; | ||
708 | } | ||
709 | |||
710 | return 0; | ||
711 | } | ||
712 | |||
713 | static int broadsheet_store_waveform_to_spiflash(struct broadsheetfb_par *par, | ||
714 | const u8 *wfm, size_t wfm_size) | ||
715 | { | ||
716 | int err = 0; | ||
717 | u16 initial_sfmcd = 0; | ||
718 | int flash_type = 0; | ||
719 | |||
720 | err = broadsheet_setup_for_wfm_write(par, &initial_sfmcd, &flash_type); | ||
721 | if (err) | ||
722 | goto failout; | ||
723 | |||
724 | err = broadsheet_write_spiflash(par, 0x886, wfm, wfm_size, flash_type); | ||
725 | |||
726 | failout: | ||
727 | broadsheet_write_reg(par, 0x0204, initial_sfmcd); | ||
728 | return err; | ||
729 | } | ||
730 | |||
731 | static ssize_t broadsheet_loadstore_waveform(struct device *dev, | ||
732 | struct device_attribute *attr, | ||
733 | const char *buf, size_t len) | ||
734 | { | ||
735 | int err; | ||
736 | struct fb_info *info = dev_get_drvdata(dev); | ||
737 | struct broadsheetfb_par *par = info->par; | ||
738 | const struct firmware *fw_entry; | ||
739 | |||
740 | if (len < 1) | ||
741 | return -EINVAL; | ||
742 | |||
743 | err = request_firmware(&fw_entry, "broadsheet.wbf", dev); | ||
744 | if (err < 0) { | ||
745 | dev_err(dev, "Failed to get broadsheet waveform\n"); | ||
746 | goto err_failed; | ||
747 | } | ||
748 | |||
749 | /* try to enforce reasonable min max on waveform */ | ||
750 | if ((fw_entry->size < 8*1024) || (fw_entry->size > 64*1024)) { | ||
751 | dev_err(dev, "Invalid waveform\n"); | ||
752 | err = -EINVAL; | ||
753 | goto err_failed; | ||
754 | } | ||
755 | |||
756 | mutex_lock(&(par->io_lock)); | ||
757 | err = broadsheet_store_waveform_to_spiflash(par, fw_entry->data, | ||
758 | fw_entry->size); | ||
759 | |||
760 | mutex_unlock(&(par->io_lock)); | ||
761 | if (err < 0) { | ||
762 | dev_err(dev, "Failed to store broadsheet waveform\n"); | ||
763 | goto err_failed; | ||
764 | } | ||
765 | |||
766 | dev_info(dev, "Stored broadsheet waveform, size %zd\n", fw_entry->size); | ||
767 | |||
768 | return len; | ||
769 | |||
770 | err_failed: | ||
771 | return err; | ||
772 | } | ||
773 | static DEVICE_ATTR(loadstore_waveform, S_IWUSR, NULL, | ||
774 | broadsheet_loadstore_waveform); | ||
775 | |||
776 | /* upper level functions that manipulate the display and other stuff */ | ||
777 | static void broadsheet_init_display(struct broadsheetfb_par *par) | ||
778 | { | ||
779 | u16 args[5]; | ||
780 | int xres = par->info->var.xres; | ||
781 | int yres = par->info->var.yres; | ||
782 | |||
783 | args[0] = panel_table[par->panel_index].w; | ||
784 | args[1] = panel_table[par->panel_index].h; | ||
785 | args[2] = panel_table[par->panel_index].sdcfg; | ||
786 | args[3] = panel_table[par->panel_index].gdcfg; | ||
787 | args[4] = panel_table[par->panel_index].lutfmt; | ||
788 | broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args); | ||
789 | |||
790 | /* did the controller really set it? */ | ||
791 | broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args); | ||
792 | |||
793 | args[0] = panel_table[par->panel_index].fsynclen; | ||
794 | args[1] = panel_table[par->panel_index].fendfbegin; | ||
795 | args[2] = panel_table[par->panel_index].lsynclen; | ||
796 | args[3] = panel_table[par->panel_index].lendlbegin; | ||
797 | args[4] = panel_table[par->panel_index].pixclk; | ||
798 | broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_TMG, 5, args); | ||
799 | |||
800 | broadsheet_write_reg32(par, 0x310, xres*yres*2); | ||
801 | |||
802 | /* setup waveform */ | ||
803 | args[0] = 0x886; | ||
804 | args[1] = 0; | ||
805 | broadsheet_send_cmdargs(par, BS_CMD_RD_WFM_INFO, 2, args); | ||
806 | |||
807 | broadsheet_send_command(par, BS_CMD_UPD_GDRV_CLR); | ||
808 | |||
809 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); | ||
810 | |||
811 | broadsheet_write_reg(par, 0x330, 0x84); | ||
812 | |||
813 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); | ||
814 | |||
815 | args[0] = (0x3 << 4); | ||
816 | broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args); | ||
817 | |||
818 | args[0] = 0x154; | ||
819 | broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); | ||
820 | |||
821 | broadsheet_burst_write(par, (panel_table[par->panel_index].w * | ||
822 | panel_table[par->panel_index].h)/2, | ||
823 | (u16 *) par->info->screen_base); | ||
824 | |||
825 | broadsheet_send_command(par, BS_CMD_LD_IMG_END); | ||
826 | |||
827 | args[0] = 0x4300; | ||
828 | broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); | ||
829 | |||
830 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); | ||
831 | |||
832 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); | ||
833 | |||
834 | par->board->wait_for_rdy(par); | ||
835 | } | ||
836 | |||
837 | static void broadsheet_identify(struct broadsheetfb_par *par) | ||
838 | { | ||
839 | u16 rev, prc; | ||
840 | struct device *dev = par->info->device; | ||
841 | |||
842 | rev = broadsheet_read_reg(par, BS_REG_REV); | ||
843 | prc = broadsheet_read_reg(par, BS_REG_PRC); | ||
844 | dev_info(dev, "Broadsheet Rev 0x%x, Product Code 0x%x\n", rev, prc); | ||
845 | |||
846 | if (prc != 0x0047) | ||
847 | dev_warn(dev, "Unrecognized Broadsheet Product Code\n"); | ||
848 | if (rev != 0x0100) | ||
849 | dev_warn(dev, "Unrecognized Broadsheet Revision\n"); | ||
850 | } | ||
851 | |||
852 | static void broadsheet_init(struct broadsheetfb_par *par) | ||
853 | { | ||
854 | broadsheet_send_command(par, BS_CMD_INIT_SYS_RUN); | ||
855 | /* the controller needs a second */ | ||
856 | msleep(1000); | ||
857 | broadsheet_init_display(par); | ||
858 | } | ||
859 | |||
860 | static void broadsheetfb_dpy_update_pages(struct broadsheetfb_par *par, | ||
861 | u16 y1, u16 y2) | ||
862 | { | ||
863 | u16 args[5]; | ||
864 | unsigned char *buf = (unsigned char *)par->info->screen_base; | ||
865 | |||
866 | mutex_lock(&(par->io_lock)); | ||
867 | /* y1 must be a multiple of 4 so drop the lower bits */ | ||
868 | y1 &= 0xFFFC; | ||
869 | /* y2 must be a multiple of 4 , but - 1 so up the lower bits */ | ||
870 | y2 |= 0x0003; | ||
871 | |||
872 | args[0] = 0x3 << 4; | ||
873 | args[1] = 0; | ||
874 | args[2] = y1; | ||
875 | args[3] = cpu_to_le16(par->info->var.xres); | ||
876 | args[4] = y2; | ||
877 | broadsheet_send_cmdargs(par, BS_CMD_LD_IMG_AREA, 5, args); | ||
878 | |||
879 | args[0] = 0x154; | ||
880 | broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); | ||
881 | |||
882 | buf += y1 * par->info->var.xres; | ||
883 | broadsheet_burst_write(par, ((1 + y2 - y1) * par->info->var.xres)/2, | ||
884 | (u16 *) buf); | ||
885 | |||
886 | broadsheet_send_command(par, BS_CMD_LD_IMG_END); | ||
887 | |||
888 | args[0] = 0x4300; | ||
889 | broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); | ||
890 | |||
891 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); | ||
892 | |||
893 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); | ||
894 | |||
895 | par->board->wait_for_rdy(par); | ||
896 | mutex_unlock(&(par->io_lock)); | ||
897 | |||
898 | } | ||
899 | |||
900 | static void broadsheetfb_dpy_update(struct broadsheetfb_par *par) | ||
901 | { | ||
902 | u16 args[5]; | ||
903 | |||
904 | mutex_lock(&(par->io_lock)); | ||
905 | args[0] = 0x3 << 4; | ||
906 | broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args); | ||
907 | |||
908 | args[0] = 0x154; | ||
909 | broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args); | ||
910 | broadsheet_burst_write(par, (panel_table[par->panel_index].w * | ||
911 | panel_table[par->panel_index].h)/2, | ||
912 | (u16 *) par->info->screen_base); | ||
913 | |||
914 | broadsheet_send_command(par, BS_CMD_LD_IMG_END); | ||
915 | |||
916 | args[0] = 0x4300; | ||
917 | broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args); | ||
918 | |||
919 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG); | ||
920 | |||
921 | broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND); | ||
922 | |||
923 | par->board->wait_for_rdy(par); | ||
924 | mutex_unlock(&(par->io_lock)); | ||
925 | } | ||
926 | |||
927 | /* this is called back from the deferred io workqueue */ | ||
928 | static void broadsheetfb_dpy_deferred_io(struct fb_info *info, | ||
929 | struct list_head *pagelist) | ||
930 | { | ||
931 | u16 y1 = 0, h = 0; | ||
932 | int prev_index = -1; | ||
933 | struct page *cur; | ||
934 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
935 | int h_inc; | ||
936 | u16 yres = info->var.yres; | ||
937 | u16 xres = info->var.xres; | ||
938 | |||
939 | /* height increment is fixed per page */ | ||
940 | h_inc = DIV_ROUND_UP(PAGE_SIZE , xres); | ||
941 | |||
942 | /* walk the written page list and swizzle the data */ | ||
943 | list_for_each_entry(cur, &fbdefio->pagelist, lru) { | ||
944 | if (prev_index < 0) { | ||
945 | /* just starting so assign first page */ | ||
946 | y1 = (cur->index << PAGE_SHIFT) / xres; | ||
947 | h = h_inc; | ||
948 | } else if ((prev_index + 1) == cur->index) { | ||
949 | /* this page is consecutive so increase our height */ | ||
950 | h += h_inc; | ||
951 | } else { | ||
952 | /* page not consecutive, issue previous update first */ | ||
953 | broadsheetfb_dpy_update_pages(info->par, y1, y1 + h); | ||
954 | /* start over with our non consecutive page */ | ||
955 | y1 = (cur->index << PAGE_SHIFT) / xres; | ||
956 | h = h_inc; | ||
957 | } | ||
958 | prev_index = cur->index; | ||
959 | } | ||
960 | |||
961 | /* if we still have any pages to update we do so now */ | ||
962 | if (h >= yres) { | ||
963 | /* its a full screen update, just do it */ | ||
964 | broadsheetfb_dpy_update(info->par); | ||
965 | } else { | ||
966 | broadsheetfb_dpy_update_pages(info->par, y1, | ||
967 | min((u16) (y1 + h), yres)); | ||
968 | } | ||
969 | } | ||
970 | |||
971 | static void broadsheetfb_fillrect(struct fb_info *info, | ||
972 | const struct fb_fillrect *rect) | ||
973 | { | ||
974 | struct broadsheetfb_par *par = info->par; | ||
975 | |||
976 | sys_fillrect(info, rect); | ||
977 | |||
978 | broadsheetfb_dpy_update(par); | ||
979 | } | ||
980 | |||
981 | static void broadsheetfb_copyarea(struct fb_info *info, | ||
982 | const struct fb_copyarea *area) | ||
983 | { | ||
984 | struct broadsheetfb_par *par = info->par; | ||
985 | |||
986 | sys_copyarea(info, area); | ||
987 | |||
988 | broadsheetfb_dpy_update(par); | ||
989 | } | ||
990 | |||
991 | static void broadsheetfb_imageblit(struct fb_info *info, | ||
992 | const struct fb_image *image) | ||
993 | { | ||
994 | struct broadsheetfb_par *par = info->par; | ||
995 | |||
996 | sys_imageblit(info, image); | ||
997 | |||
998 | broadsheetfb_dpy_update(par); | ||
999 | } | ||
1000 | |||
1001 | /* | ||
1002 | * this is the slow path from userspace. they can seek and write to | ||
1003 | * the fb. it's inefficient to do anything less than a full screen draw | ||
1004 | */ | ||
1005 | static ssize_t broadsheetfb_write(struct fb_info *info, const char __user *buf, | ||
1006 | size_t count, loff_t *ppos) | ||
1007 | { | ||
1008 | struct broadsheetfb_par *par = info->par; | ||
1009 | unsigned long p = *ppos; | ||
1010 | void *dst; | ||
1011 | int err = 0; | ||
1012 | unsigned long total_size; | ||
1013 | |||
1014 | if (info->state != FBINFO_STATE_RUNNING) | ||
1015 | return -EPERM; | ||
1016 | |||
1017 | total_size = info->fix.smem_len; | ||
1018 | |||
1019 | if (p > total_size) | ||
1020 | return -EFBIG; | ||
1021 | |||
1022 | if (count > total_size) { | ||
1023 | err = -EFBIG; | ||
1024 | count = total_size; | ||
1025 | } | ||
1026 | |||
1027 | if (count + p > total_size) { | ||
1028 | if (!err) | ||
1029 | err = -ENOSPC; | ||
1030 | |||
1031 | count = total_size - p; | ||
1032 | } | ||
1033 | |||
1034 | dst = (void *)(info->screen_base + p); | ||
1035 | |||
1036 | if (copy_from_user(dst, buf, count)) | ||
1037 | err = -EFAULT; | ||
1038 | |||
1039 | if (!err) | ||
1040 | *ppos += count; | ||
1041 | |||
1042 | broadsheetfb_dpy_update(par); | ||
1043 | |||
1044 | return (err) ? err : count; | ||
1045 | } | ||
1046 | |||
1047 | static struct fb_ops broadsheetfb_ops = { | ||
1048 | .owner = THIS_MODULE, | ||
1049 | .fb_read = fb_sys_read, | ||
1050 | .fb_write = broadsheetfb_write, | ||
1051 | .fb_fillrect = broadsheetfb_fillrect, | ||
1052 | .fb_copyarea = broadsheetfb_copyarea, | ||
1053 | .fb_imageblit = broadsheetfb_imageblit, | ||
1054 | }; | ||
1055 | |||
1056 | static struct fb_deferred_io broadsheetfb_defio = { | ||
1057 | .delay = HZ/4, | ||
1058 | .deferred_io = broadsheetfb_dpy_deferred_io, | ||
1059 | }; | ||
1060 | |||
1061 | static int broadsheetfb_probe(struct platform_device *dev) | ||
1062 | { | ||
1063 | struct fb_info *info; | ||
1064 | struct broadsheet_board *board; | ||
1065 | int retval = -ENOMEM; | ||
1066 | int videomemorysize; | ||
1067 | unsigned char *videomemory; | ||
1068 | struct broadsheetfb_par *par; | ||
1069 | int i; | ||
1070 | int dpyw, dpyh; | ||
1071 | int panel_index; | ||
1072 | |||
1073 | /* pick up board specific routines */ | ||
1074 | board = dev->dev.platform_data; | ||
1075 | if (!board) | ||
1076 | return -EINVAL; | ||
1077 | |||
1078 | /* try to count device specific driver, if can't, platform recalls */ | ||
1079 | if (!try_module_get(board->owner)) | ||
1080 | return -ENODEV; | ||
1081 | |||
1082 | info = framebuffer_alloc(sizeof(struct broadsheetfb_par), &dev->dev); | ||
1083 | if (!info) | ||
1084 | goto err; | ||
1085 | |||
1086 | switch (board->get_panel_type()) { | ||
1087 | case 37: | ||
1088 | panel_index = 1; | ||
1089 | break; | ||
1090 | case 97: | ||
1091 | panel_index = 2; | ||
1092 | break; | ||
1093 | case 6: | ||
1094 | default: | ||
1095 | panel_index = 0; | ||
1096 | break; | ||
1097 | } | ||
1098 | |||
1099 | dpyw = panel_table[panel_index].w; | ||
1100 | dpyh = panel_table[panel_index].h; | ||
1101 | |||
1102 | videomemorysize = roundup((dpyw*dpyh), PAGE_SIZE); | ||
1103 | |||
1104 | videomemory = vzalloc(videomemorysize); | ||
1105 | if (!videomemory) | ||
1106 | goto err_fb_rel; | ||
1107 | |||
1108 | info->screen_base = (char *)videomemory; | ||
1109 | info->fbops = &broadsheetfb_ops; | ||
1110 | |||
1111 | broadsheetfb_var.xres = dpyw; | ||
1112 | broadsheetfb_var.yres = dpyh; | ||
1113 | broadsheetfb_var.xres_virtual = dpyw; | ||
1114 | broadsheetfb_var.yres_virtual = dpyh; | ||
1115 | info->var = broadsheetfb_var; | ||
1116 | |||
1117 | broadsheetfb_fix.line_length = dpyw; | ||
1118 | info->fix = broadsheetfb_fix; | ||
1119 | info->fix.smem_len = videomemorysize; | ||
1120 | par = info->par; | ||
1121 | par->panel_index = panel_index; | ||
1122 | par->info = info; | ||
1123 | par->board = board; | ||
1124 | par->write_reg = broadsheet_write_reg; | ||
1125 | par->read_reg = broadsheet_read_reg; | ||
1126 | init_waitqueue_head(&par->waitq); | ||
1127 | |||
1128 | mutex_init(&par->io_lock); | ||
1129 | |||
1130 | info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; | ||
1131 | |||
1132 | info->fbdefio = &broadsheetfb_defio; | ||
1133 | fb_deferred_io_init(info); | ||
1134 | |||
1135 | retval = fb_alloc_cmap(&info->cmap, 16, 0); | ||
1136 | if (retval < 0) { | ||
1137 | dev_err(&dev->dev, "Failed to allocate colormap\n"); | ||
1138 | goto err_vfree; | ||
1139 | } | ||
1140 | |||
1141 | /* set cmap */ | ||
1142 | for (i = 0; i < 16; i++) | ||
1143 | info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/32; | ||
1144 | memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*16); | ||
1145 | memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*16); | ||
1146 | |||
1147 | retval = par->board->setup_irq(info); | ||
1148 | if (retval < 0) | ||
1149 | goto err_cmap; | ||
1150 | |||
1151 | /* this inits the dpy */ | ||
1152 | retval = board->init(par); | ||
1153 | if (retval < 0) | ||
1154 | goto err_free_irq; | ||
1155 | |||
1156 | broadsheet_identify(par); | ||
1157 | |||
1158 | broadsheet_init(par); | ||
1159 | |||
1160 | retval = register_framebuffer(info); | ||
1161 | if (retval < 0) | ||
1162 | goto err_free_irq; | ||
1163 | |||
1164 | platform_set_drvdata(dev, info); | ||
1165 | |||
1166 | retval = device_create_file(&dev->dev, &dev_attr_loadstore_waveform); | ||
1167 | if (retval < 0) | ||
1168 | goto err_unreg_fb; | ||
1169 | |||
1170 | fb_info(info, "Broadsheet frame buffer, using %dK of video memory\n", | ||
1171 | videomemorysize >> 10); | ||
1172 | |||
1173 | |||
1174 | return 0; | ||
1175 | |||
1176 | err_unreg_fb: | ||
1177 | unregister_framebuffer(info); | ||
1178 | err_free_irq: | ||
1179 | board->cleanup(par); | ||
1180 | err_cmap: | ||
1181 | fb_dealloc_cmap(&info->cmap); | ||
1182 | err_vfree: | ||
1183 | vfree(videomemory); | ||
1184 | err_fb_rel: | ||
1185 | framebuffer_release(info); | ||
1186 | err: | ||
1187 | module_put(board->owner); | ||
1188 | return retval; | ||
1189 | |||
1190 | } | ||
1191 | |||
1192 | static int broadsheetfb_remove(struct platform_device *dev) | ||
1193 | { | ||
1194 | struct fb_info *info = platform_get_drvdata(dev); | ||
1195 | |||
1196 | if (info) { | ||
1197 | struct broadsheetfb_par *par = info->par; | ||
1198 | |||
1199 | device_remove_file(info->dev, &dev_attr_loadstore_waveform); | ||
1200 | unregister_framebuffer(info); | ||
1201 | fb_deferred_io_cleanup(info); | ||
1202 | par->board->cleanup(par); | ||
1203 | fb_dealloc_cmap(&info->cmap); | ||
1204 | vfree((void *)info->screen_base); | ||
1205 | module_put(par->board->owner); | ||
1206 | framebuffer_release(info); | ||
1207 | } | ||
1208 | return 0; | ||
1209 | } | ||
1210 | |||
1211 | static struct platform_driver broadsheetfb_driver = { | ||
1212 | .probe = broadsheetfb_probe, | ||
1213 | .remove = broadsheetfb_remove, | ||
1214 | .driver = { | ||
1215 | .owner = THIS_MODULE, | ||
1216 | .name = "broadsheetfb", | ||
1217 | }, | ||
1218 | }; | ||
1219 | module_platform_driver(broadsheetfb_driver); | ||
1220 | |||
1221 | MODULE_DESCRIPTION("fbdev driver for Broadsheet controller"); | ||
1222 | MODULE_AUTHOR("Jaya Kumar"); | ||
1223 | MODULE_LICENSE("GPL"); | ||