diff options
Diffstat (limited to 'drivers/video/fbdev/metronomefb.c')
-rw-r--r-- | drivers/video/fbdev/metronomefb.c | 780 |
1 files changed, 780 insertions, 0 deletions
diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c new file mode 100644 index 000000000000..195cc2db4c2c --- /dev/null +++ b/drivers/video/fbdev/metronomefb.c | |||
@@ -0,0 +1,780 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/metronomefb.c -- FB driver for Metronome 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 work was made possible by help and equipment support from E-Ink | ||
13 | * Corporation. http://www.eink.com/ | ||
14 | * | ||
15 | * This driver is written to be used with the Metronome display controller. | ||
16 | * It is intended to be architecture independent. A board specific driver | ||
17 | * must be used to perform all the physical IO interactions. An example | ||
18 | * is provided as am200epd.c | ||
19 | * | ||
20 | */ | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/errno.h> | ||
24 | #include <linux/string.h> | ||
25 | #include <linux/mm.h> | ||
26 | #include <linux/vmalloc.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | #include <linux/fb.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | #include <linux/list.h> | ||
33 | #include <linux/firmware.h> | ||
34 | #include <linux/dma-mapping.h> | ||
35 | #include <linux/uaccess.h> | ||
36 | #include <linux/irq.h> | ||
37 | |||
38 | #include <video/metronomefb.h> | ||
39 | |||
40 | #include <asm/unaligned.h> | ||
41 | |||
42 | /* Display specific information */ | ||
43 | #define DPY_W 832 | ||
44 | #define DPY_H 622 | ||
45 | |||
46 | static int user_wfm_size; | ||
47 | |||
48 | /* frame differs from image. frame includes non-visible pixels */ | ||
49 | struct epd_frame { | ||
50 | int fw; /* frame width */ | ||
51 | int fh; /* frame height */ | ||
52 | u16 config[4]; | ||
53 | int wfm_size; | ||
54 | }; | ||
55 | |||
56 | static struct epd_frame epd_frame_table[] = { | ||
57 | { | ||
58 | .fw = 832, | ||
59 | .fh = 622, | ||
60 | .config = { | ||
61 | 15 /* sdlew */ | ||
62 | | 2 << 8 /* sdosz */ | ||
63 | | 0 << 11 /* sdor */ | ||
64 | | 0 << 12 /* sdces */ | ||
65 | | 0 << 15, /* sdcer */ | ||
66 | 42 /* gdspl */ | ||
67 | | 1 << 8 /* gdr1 */ | ||
68 | | 1 << 9 /* sdshr */ | ||
69 | | 0 << 15, /* gdspp */ | ||
70 | 18 /* gdspw */ | ||
71 | | 0 << 15, /* dispc */ | ||
72 | 599 /* vdlc */ | ||
73 | | 0 << 11 /* dsi */ | ||
74 | | 0 << 12, /* dsic */ | ||
75 | }, | ||
76 | .wfm_size = 47001, | ||
77 | }, | ||
78 | { | ||
79 | .fw = 1088, | ||
80 | .fh = 791, | ||
81 | .config = { | ||
82 | 0x0104, | ||
83 | 0x031f, | ||
84 | 0x0088, | ||
85 | 0x02ff, | ||
86 | }, | ||
87 | .wfm_size = 46770, | ||
88 | }, | ||
89 | { | ||
90 | .fw = 1200, | ||
91 | .fh = 842, | ||
92 | .config = { | ||
93 | 0x0101, | ||
94 | 0x030e, | ||
95 | 0x0012, | ||
96 | 0x0280, | ||
97 | }, | ||
98 | .wfm_size = 46770, | ||
99 | }, | ||
100 | }; | ||
101 | |||
102 | static struct fb_fix_screeninfo metronomefb_fix = { | ||
103 | .id = "metronomefb", | ||
104 | .type = FB_TYPE_PACKED_PIXELS, | ||
105 | .visual = FB_VISUAL_STATIC_PSEUDOCOLOR, | ||
106 | .xpanstep = 0, | ||
107 | .ypanstep = 0, | ||
108 | .ywrapstep = 0, | ||
109 | .line_length = DPY_W, | ||
110 | .accel = FB_ACCEL_NONE, | ||
111 | }; | ||
112 | |||
113 | static struct fb_var_screeninfo metronomefb_var = { | ||
114 | .xres = DPY_W, | ||
115 | .yres = DPY_H, | ||
116 | .xres_virtual = DPY_W, | ||
117 | .yres_virtual = DPY_H, | ||
118 | .bits_per_pixel = 8, | ||
119 | .grayscale = 1, | ||
120 | .nonstd = 1, | ||
121 | .red = { 4, 3, 0 }, | ||
122 | .green = { 0, 0, 0 }, | ||
123 | .blue = { 0, 0, 0 }, | ||
124 | .transp = { 0, 0, 0 }, | ||
125 | }; | ||
126 | |||
127 | /* the waveform structure that is coming from userspace firmware */ | ||
128 | struct waveform_hdr { | ||
129 | u8 stuff[32]; | ||
130 | |||
131 | u8 wmta[3]; | ||
132 | u8 fvsn; | ||
133 | |||
134 | u8 luts; | ||
135 | u8 mc; | ||
136 | u8 trc; | ||
137 | u8 stuff3; | ||
138 | |||
139 | u8 endb; | ||
140 | u8 swtb; | ||
141 | u8 stuff2a[2]; | ||
142 | |||
143 | u8 stuff2b[3]; | ||
144 | u8 wfm_cs; | ||
145 | } __attribute__ ((packed)); | ||
146 | |||
147 | /* main metronomefb functions */ | ||
148 | static u8 calc_cksum(int start, int end, u8 *mem) | ||
149 | { | ||
150 | u8 tmp = 0; | ||
151 | int i; | ||
152 | |||
153 | for (i = start; i < end; i++) | ||
154 | tmp += mem[i]; | ||
155 | |||
156 | return tmp; | ||
157 | } | ||
158 | |||
159 | static u16 calc_img_cksum(u16 *start, int length) | ||
160 | { | ||
161 | u16 tmp = 0; | ||
162 | |||
163 | while (length--) | ||
164 | tmp += *start++; | ||
165 | |||
166 | return tmp; | ||
167 | } | ||
168 | |||
169 | /* here we decode the incoming waveform file and populate metromem */ | ||
170 | static int load_waveform(u8 *mem, size_t size, int m, int t, | ||
171 | struct metronomefb_par *par) | ||
172 | { | ||
173 | int tta; | ||
174 | int wmta; | ||
175 | int trn = 0; | ||
176 | int i; | ||
177 | unsigned char v; | ||
178 | u8 cksum; | ||
179 | int cksum_idx; | ||
180 | int wfm_idx, owfm_idx; | ||
181 | int mem_idx = 0; | ||
182 | struct waveform_hdr *wfm_hdr; | ||
183 | u8 *metromem = par->metromem_wfm; | ||
184 | struct device *dev = par->info->dev; | ||
185 | |||
186 | if (user_wfm_size) | ||
187 | epd_frame_table[par->dt].wfm_size = user_wfm_size; | ||
188 | |||
189 | if (size != epd_frame_table[par->dt].wfm_size) { | ||
190 | dev_err(dev, "Error: unexpected size %Zd != %d\n", size, | ||
191 | epd_frame_table[par->dt].wfm_size); | ||
192 | return -EINVAL; | ||
193 | } | ||
194 | |||
195 | wfm_hdr = (struct waveform_hdr *) mem; | ||
196 | |||
197 | if (wfm_hdr->fvsn != 1) { | ||
198 | dev_err(dev, "Error: bad fvsn %x\n", wfm_hdr->fvsn); | ||
199 | return -EINVAL; | ||
200 | } | ||
201 | if (wfm_hdr->luts != 0) { | ||
202 | dev_err(dev, "Error: bad luts %x\n", wfm_hdr->luts); | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | cksum = calc_cksum(32, 47, mem); | ||
206 | if (cksum != wfm_hdr->wfm_cs) { | ||
207 | dev_err(dev, "Error: bad cksum %x != %x\n", cksum, | ||
208 | wfm_hdr->wfm_cs); | ||
209 | return -EINVAL; | ||
210 | } | ||
211 | wfm_hdr->mc += 1; | ||
212 | wfm_hdr->trc += 1; | ||
213 | for (i = 0; i < 5; i++) { | ||
214 | if (*(wfm_hdr->stuff2a + i) != 0) { | ||
215 | dev_err(dev, "Error: unexpected value in padding\n"); | ||
216 | return -EINVAL; | ||
217 | } | ||
218 | } | ||
219 | |||
220 | /* calculating trn. trn is something used to index into | ||
221 | the waveform. presumably selecting the right one for the | ||
222 | desired temperature. it works out the offset of the first | ||
223 | v that exceeds the specified temperature */ | ||
224 | if ((sizeof(*wfm_hdr) + wfm_hdr->trc) > size) | ||
225 | return -EINVAL; | ||
226 | |||
227 | for (i = sizeof(*wfm_hdr); i <= sizeof(*wfm_hdr) + wfm_hdr->trc; i++) { | ||
228 | if (mem[i] > t) { | ||
229 | trn = i - sizeof(*wfm_hdr) - 1; | ||
230 | break; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | /* check temperature range table checksum */ | ||
235 | cksum_idx = sizeof(*wfm_hdr) + wfm_hdr->trc + 1; | ||
236 | if (cksum_idx > size) | ||
237 | return -EINVAL; | ||
238 | cksum = calc_cksum(sizeof(*wfm_hdr), cksum_idx, mem); | ||
239 | if (cksum != mem[cksum_idx]) { | ||
240 | dev_err(dev, "Error: bad temperature range table cksum" | ||
241 | " %x != %x\n", cksum, mem[cksum_idx]); | ||
242 | return -EINVAL; | ||
243 | } | ||
244 | |||
245 | /* check waveform mode table address checksum */ | ||
246 | wmta = get_unaligned_le32(wfm_hdr->wmta) & 0x00FFFFFF; | ||
247 | cksum_idx = wmta + m*4 + 3; | ||
248 | if (cksum_idx > size) | ||
249 | return -EINVAL; | ||
250 | cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem); | ||
251 | if (cksum != mem[cksum_idx]) { | ||
252 | dev_err(dev, "Error: bad mode table address cksum" | ||
253 | " %x != %x\n", cksum, mem[cksum_idx]); | ||
254 | return -EINVAL; | ||
255 | } | ||
256 | |||
257 | /* check waveform temperature table address checksum */ | ||
258 | tta = get_unaligned_le32(mem + wmta + m * 4) & 0x00FFFFFF; | ||
259 | cksum_idx = tta + trn*4 + 3; | ||
260 | if (cksum_idx > size) | ||
261 | return -EINVAL; | ||
262 | cksum = calc_cksum(cksum_idx - 3, cksum_idx, mem); | ||
263 | if (cksum != mem[cksum_idx]) { | ||
264 | dev_err(dev, "Error: bad temperature table address cksum" | ||
265 | " %x != %x\n", cksum, mem[cksum_idx]); | ||
266 | return -EINVAL; | ||
267 | } | ||
268 | |||
269 | /* here we do the real work of putting the waveform into the | ||
270 | metromem buffer. this does runlength decoding of the waveform */ | ||
271 | wfm_idx = get_unaligned_le32(mem + tta + trn * 4) & 0x00FFFFFF; | ||
272 | owfm_idx = wfm_idx; | ||
273 | if (wfm_idx > size) | ||
274 | return -EINVAL; | ||
275 | while (wfm_idx < size) { | ||
276 | unsigned char rl; | ||
277 | v = mem[wfm_idx++]; | ||
278 | if (v == wfm_hdr->swtb) { | ||
279 | while (((v = mem[wfm_idx++]) != wfm_hdr->swtb) && | ||
280 | wfm_idx < size) | ||
281 | metromem[mem_idx++] = v; | ||
282 | |||
283 | continue; | ||
284 | } | ||
285 | |||
286 | if (v == wfm_hdr->endb) | ||
287 | break; | ||
288 | |||
289 | rl = mem[wfm_idx++]; | ||
290 | for (i = 0; i <= rl; i++) | ||
291 | metromem[mem_idx++] = v; | ||
292 | } | ||
293 | |||
294 | cksum_idx = wfm_idx; | ||
295 | if (cksum_idx > size) | ||
296 | return -EINVAL; | ||
297 | cksum = calc_cksum(owfm_idx, cksum_idx, mem); | ||
298 | if (cksum != mem[cksum_idx]) { | ||
299 | dev_err(dev, "Error: bad waveform data cksum" | ||
300 | " %x != %x\n", cksum, mem[cksum_idx]); | ||
301 | return -EINVAL; | ||
302 | } | ||
303 | par->frame_count = (mem_idx/64); | ||
304 | |||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | static int metronome_display_cmd(struct metronomefb_par *par) | ||
309 | { | ||
310 | int i; | ||
311 | u16 cs; | ||
312 | u16 opcode; | ||
313 | static u8 borderval; | ||
314 | |||
315 | /* setup display command | ||
316 | we can't immediately set the opcode since the controller | ||
317 | will try parse the command before we've set it all up | ||
318 | so we just set cs here and set the opcode at the end */ | ||
319 | |||
320 | if (par->metromem_cmd->opcode == 0xCC40) | ||
321 | opcode = cs = 0xCC41; | ||
322 | else | ||
323 | opcode = cs = 0xCC40; | ||
324 | |||
325 | /* set the args ( 2 bytes ) for display */ | ||
326 | i = 0; | ||
327 | par->metromem_cmd->args[i] = 1 << 3 /* border update */ | ||
328 | | ((borderval++ % 4) & 0x0F) << 4 | ||
329 | | (par->frame_count - 1) << 8; | ||
330 | cs += par->metromem_cmd->args[i++]; | ||
331 | |||
332 | /* the rest are 0 */ | ||
333 | memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); | ||
334 | |||
335 | par->metromem_cmd->csum = cs; | ||
336 | par->metromem_cmd->opcode = opcode; /* display cmd */ | ||
337 | |||
338 | return par->board->met_wait_event_intr(par); | ||
339 | } | ||
340 | |||
341 | static int metronome_powerup_cmd(struct metronomefb_par *par) | ||
342 | { | ||
343 | int i; | ||
344 | u16 cs; | ||
345 | |||
346 | /* setup power up command */ | ||
347 | par->metromem_cmd->opcode = 0x1234; /* pwr up pseudo cmd */ | ||
348 | cs = par->metromem_cmd->opcode; | ||
349 | |||
350 | /* set pwr1,2,3 to 1024 */ | ||
351 | for (i = 0; i < 3; i++) { | ||
352 | par->metromem_cmd->args[i] = 1024; | ||
353 | cs += par->metromem_cmd->args[i]; | ||
354 | } | ||
355 | |||
356 | /* the rest are 0 */ | ||
357 | memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); | ||
358 | |||
359 | par->metromem_cmd->csum = cs; | ||
360 | |||
361 | msleep(1); | ||
362 | par->board->set_rst(par, 1); | ||
363 | |||
364 | msleep(1); | ||
365 | par->board->set_stdby(par, 1); | ||
366 | |||
367 | return par->board->met_wait_event(par); | ||
368 | } | ||
369 | |||
370 | static int metronome_config_cmd(struct metronomefb_par *par) | ||
371 | { | ||
372 | /* setup config command | ||
373 | we can't immediately set the opcode since the controller | ||
374 | will try parse the command before we've set it all up */ | ||
375 | |||
376 | memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config, | ||
377 | sizeof(epd_frame_table[par->dt].config)); | ||
378 | /* the rest are 0 */ | ||
379 | memset((u8 *) (par->metromem_cmd->args + 4), 0, (32-4)*2); | ||
380 | |||
381 | par->metromem_cmd->csum = 0xCC10; | ||
382 | par->metromem_cmd->csum += calc_img_cksum(par->metromem_cmd->args, 4); | ||
383 | par->metromem_cmd->opcode = 0xCC10; /* config cmd */ | ||
384 | |||
385 | return par->board->met_wait_event(par); | ||
386 | } | ||
387 | |||
388 | static int metronome_init_cmd(struct metronomefb_par *par) | ||
389 | { | ||
390 | int i; | ||
391 | u16 cs; | ||
392 | |||
393 | /* setup init command | ||
394 | we can't immediately set the opcode since the controller | ||
395 | will try parse the command before we've set it all up | ||
396 | so we just set cs here and set the opcode at the end */ | ||
397 | |||
398 | cs = 0xCC20; | ||
399 | |||
400 | /* set the args ( 2 bytes ) for init */ | ||
401 | i = 0; | ||
402 | par->metromem_cmd->args[i] = 0; | ||
403 | cs += par->metromem_cmd->args[i++]; | ||
404 | |||
405 | /* the rest are 0 */ | ||
406 | memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); | ||
407 | |||
408 | par->metromem_cmd->csum = cs; | ||
409 | par->metromem_cmd->opcode = 0xCC20; /* init cmd */ | ||
410 | |||
411 | return par->board->met_wait_event(par); | ||
412 | } | ||
413 | |||
414 | static int metronome_init_regs(struct metronomefb_par *par) | ||
415 | { | ||
416 | int res; | ||
417 | |||
418 | res = par->board->setup_io(par); | ||
419 | if (res) | ||
420 | return res; | ||
421 | |||
422 | res = metronome_powerup_cmd(par); | ||
423 | if (res) | ||
424 | return res; | ||
425 | |||
426 | res = metronome_config_cmd(par); | ||
427 | if (res) | ||
428 | return res; | ||
429 | |||
430 | res = metronome_init_cmd(par); | ||
431 | |||
432 | return res; | ||
433 | } | ||
434 | |||
435 | static void metronomefb_dpy_update(struct metronomefb_par *par) | ||
436 | { | ||
437 | int fbsize; | ||
438 | u16 cksum; | ||
439 | unsigned char *buf = (unsigned char __force *)par->info->screen_base; | ||
440 | |||
441 | fbsize = par->info->fix.smem_len; | ||
442 | /* copy from vm to metromem */ | ||
443 | memcpy(par->metromem_img, buf, fbsize); | ||
444 | |||
445 | cksum = calc_img_cksum((u16 *) par->metromem_img, fbsize/2); | ||
446 | *((u16 *)(par->metromem_img) + fbsize/2) = cksum; | ||
447 | metronome_display_cmd(par); | ||
448 | } | ||
449 | |||
450 | static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index) | ||
451 | { | ||
452 | int i; | ||
453 | u16 csum = 0; | ||
454 | u16 *buf = (u16 __force *)(par->info->screen_base + index); | ||
455 | u16 *img = (u16 *)(par->metromem_img + index); | ||
456 | |||
457 | /* swizzle from vm to metromem and recalc cksum at the same time*/ | ||
458 | for (i = 0; i < PAGE_SIZE/2; i++) { | ||
459 | *(img + i) = (buf[i] << 5) & 0xE0E0; | ||
460 | csum += *(img + i); | ||
461 | } | ||
462 | return csum; | ||
463 | } | ||
464 | |||
465 | /* this is called back from the deferred io workqueue */ | ||
466 | static void metronomefb_dpy_deferred_io(struct fb_info *info, | ||
467 | struct list_head *pagelist) | ||
468 | { | ||
469 | u16 cksum; | ||
470 | struct page *cur; | ||
471 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
472 | struct metronomefb_par *par = info->par; | ||
473 | |||
474 | /* walk the written page list and swizzle the data */ | ||
475 | list_for_each_entry(cur, &fbdefio->pagelist, lru) { | ||
476 | cksum = metronomefb_dpy_update_page(par, | ||
477 | (cur->index << PAGE_SHIFT)); | ||
478 | par->metromem_img_csum -= par->csum_table[cur->index]; | ||
479 | par->csum_table[cur->index] = cksum; | ||
480 | par->metromem_img_csum += cksum; | ||
481 | } | ||
482 | |||
483 | metronome_display_cmd(par); | ||
484 | } | ||
485 | |||
486 | static void metronomefb_fillrect(struct fb_info *info, | ||
487 | const struct fb_fillrect *rect) | ||
488 | { | ||
489 | struct metronomefb_par *par = info->par; | ||
490 | |||
491 | sys_fillrect(info, rect); | ||
492 | metronomefb_dpy_update(par); | ||
493 | } | ||
494 | |||
495 | static void metronomefb_copyarea(struct fb_info *info, | ||
496 | const struct fb_copyarea *area) | ||
497 | { | ||
498 | struct metronomefb_par *par = info->par; | ||
499 | |||
500 | sys_copyarea(info, area); | ||
501 | metronomefb_dpy_update(par); | ||
502 | } | ||
503 | |||
504 | static void metronomefb_imageblit(struct fb_info *info, | ||
505 | const struct fb_image *image) | ||
506 | { | ||
507 | struct metronomefb_par *par = info->par; | ||
508 | |||
509 | sys_imageblit(info, image); | ||
510 | metronomefb_dpy_update(par); | ||
511 | } | ||
512 | |||
513 | /* | ||
514 | * this is the slow path from userspace. they can seek and write to | ||
515 | * the fb. it is based on fb_sys_write | ||
516 | */ | ||
517 | static ssize_t metronomefb_write(struct fb_info *info, const char __user *buf, | ||
518 | size_t count, loff_t *ppos) | ||
519 | { | ||
520 | struct metronomefb_par *par = info->par; | ||
521 | unsigned long p = *ppos; | ||
522 | void *dst; | ||
523 | int err = 0; | ||
524 | unsigned long total_size; | ||
525 | |||
526 | if (info->state != FBINFO_STATE_RUNNING) | ||
527 | return -EPERM; | ||
528 | |||
529 | total_size = info->fix.smem_len; | ||
530 | |||
531 | if (p > total_size) | ||
532 | return -EFBIG; | ||
533 | |||
534 | if (count > total_size) { | ||
535 | err = -EFBIG; | ||
536 | count = total_size; | ||
537 | } | ||
538 | |||
539 | if (count + p > total_size) { | ||
540 | if (!err) | ||
541 | err = -ENOSPC; | ||
542 | |||
543 | count = total_size - p; | ||
544 | } | ||
545 | |||
546 | dst = (void __force *)(info->screen_base + p); | ||
547 | |||
548 | if (copy_from_user(dst, buf, count)) | ||
549 | err = -EFAULT; | ||
550 | |||
551 | if (!err) | ||
552 | *ppos += count; | ||
553 | |||
554 | metronomefb_dpy_update(par); | ||
555 | |||
556 | return (err) ? err : count; | ||
557 | } | ||
558 | |||
559 | static struct fb_ops metronomefb_ops = { | ||
560 | .owner = THIS_MODULE, | ||
561 | .fb_write = metronomefb_write, | ||
562 | .fb_fillrect = metronomefb_fillrect, | ||
563 | .fb_copyarea = metronomefb_copyarea, | ||
564 | .fb_imageblit = metronomefb_imageblit, | ||
565 | }; | ||
566 | |||
567 | static struct fb_deferred_io metronomefb_defio = { | ||
568 | .delay = HZ, | ||
569 | .deferred_io = metronomefb_dpy_deferred_io, | ||
570 | }; | ||
571 | |||
572 | static int metronomefb_probe(struct platform_device *dev) | ||
573 | { | ||
574 | struct fb_info *info; | ||
575 | struct metronome_board *board; | ||
576 | int retval = -ENOMEM; | ||
577 | int videomemorysize; | ||
578 | unsigned char *videomemory; | ||
579 | struct metronomefb_par *par; | ||
580 | const struct firmware *fw_entry; | ||
581 | int i; | ||
582 | int panel_type; | ||
583 | int fw, fh; | ||
584 | int epd_dt_index; | ||
585 | |||
586 | /* pick up board specific routines */ | ||
587 | board = dev->dev.platform_data; | ||
588 | if (!board) | ||
589 | return -EINVAL; | ||
590 | |||
591 | /* try to count device specific driver, if can't, platform recalls */ | ||
592 | if (!try_module_get(board->owner)) | ||
593 | return -ENODEV; | ||
594 | |||
595 | info = framebuffer_alloc(sizeof(struct metronomefb_par), &dev->dev); | ||
596 | if (!info) | ||
597 | goto err; | ||
598 | |||
599 | /* we have two blocks of memory. | ||
600 | info->screen_base which is vm, and is the fb used by apps. | ||
601 | par->metromem which is physically contiguous memory and | ||
602 | contains the display controller commands, waveform, | ||
603 | processed image data and padding. this is the data pulled | ||
604 | by the device's LCD controller and pushed to Metronome. | ||
605 | the metromem memory is allocated by the board driver and | ||
606 | is provided to us */ | ||
607 | |||
608 | panel_type = board->get_panel_type(); | ||
609 | switch (panel_type) { | ||
610 | case 6: | ||
611 | epd_dt_index = 0; | ||
612 | break; | ||
613 | case 8: | ||
614 | epd_dt_index = 1; | ||
615 | break; | ||
616 | case 97: | ||
617 | epd_dt_index = 2; | ||
618 | break; | ||
619 | default: | ||
620 | dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n"); | ||
621 | epd_dt_index = 0; | ||
622 | break; | ||
623 | } | ||
624 | |||
625 | fw = epd_frame_table[epd_dt_index].fw; | ||
626 | fh = epd_frame_table[epd_dt_index].fh; | ||
627 | |||
628 | /* we need to add a spare page because our csum caching scheme walks | ||
629 | * to the end of the page */ | ||
630 | videomemorysize = PAGE_SIZE + (fw * fh); | ||
631 | videomemory = vzalloc(videomemorysize); | ||
632 | if (!videomemory) | ||
633 | goto err_fb_rel; | ||
634 | |||
635 | info->screen_base = (char __force __iomem *)videomemory; | ||
636 | info->fbops = &metronomefb_ops; | ||
637 | |||
638 | metronomefb_fix.line_length = fw; | ||
639 | metronomefb_var.xres = fw; | ||
640 | metronomefb_var.yres = fh; | ||
641 | metronomefb_var.xres_virtual = fw; | ||
642 | metronomefb_var.yres_virtual = fh; | ||
643 | info->var = metronomefb_var; | ||
644 | info->fix = metronomefb_fix; | ||
645 | info->fix.smem_len = videomemorysize; | ||
646 | par = info->par; | ||
647 | par->info = info; | ||
648 | par->board = board; | ||
649 | par->dt = epd_dt_index; | ||
650 | init_waitqueue_head(&par->waitq); | ||
651 | |||
652 | /* this table caches per page csum values. */ | ||
653 | par->csum_table = vmalloc(videomemorysize/PAGE_SIZE); | ||
654 | if (!par->csum_table) | ||
655 | goto err_vfree; | ||
656 | |||
657 | /* the physical framebuffer that we use is setup by | ||
658 | * the platform device driver. It will provide us | ||
659 | * with cmd, wfm and image memory in a contiguous area. */ | ||
660 | retval = board->setup_fb(par); | ||
661 | if (retval) { | ||
662 | dev_err(&dev->dev, "Failed to setup fb\n"); | ||
663 | goto err_csum_table; | ||
664 | } | ||
665 | |||
666 | /* after this point we should have a framebuffer */ | ||
667 | if ((!par->metromem_wfm) || (!par->metromem_img) || | ||
668 | (!par->metromem_dma)) { | ||
669 | dev_err(&dev->dev, "fb access failure\n"); | ||
670 | retval = -EINVAL; | ||
671 | goto err_csum_table; | ||
672 | } | ||
673 | |||
674 | info->fix.smem_start = par->metromem_dma; | ||
675 | |||
676 | /* load the waveform in. assume mode 3, temp 31 for now | ||
677 | a) request the waveform file from userspace | ||
678 | b) process waveform and decode into metromem */ | ||
679 | retval = request_firmware(&fw_entry, "metronome.wbf", &dev->dev); | ||
680 | if (retval < 0) { | ||
681 | dev_err(&dev->dev, "Failed to get waveform\n"); | ||
682 | goto err_csum_table; | ||
683 | } | ||
684 | |||
685 | retval = load_waveform((u8 *) fw_entry->data, fw_entry->size, 3, 31, | ||
686 | par); | ||
687 | release_firmware(fw_entry); | ||
688 | if (retval < 0) { | ||
689 | dev_err(&dev->dev, "Failed processing waveform\n"); | ||
690 | goto err_csum_table; | ||
691 | } | ||
692 | |||
693 | retval = board->setup_irq(info); | ||
694 | if (retval) | ||
695 | goto err_csum_table; | ||
696 | |||
697 | retval = metronome_init_regs(par); | ||
698 | if (retval < 0) | ||
699 | goto err_free_irq; | ||
700 | |||
701 | info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; | ||
702 | |||
703 | info->fbdefio = &metronomefb_defio; | ||
704 | fb_deferred_io_init(info); | ||
705 | |||
706 | retval = fb_alloc_cmap(&info->cmap, 8, 0); | ||
707 | if (retval < 0) { | ||
708 | dev_err(&dev->dev, "Failed to allocate colormap\n"); | ||
709 | goto err_free_irq; | ||
710 | } | ||
711 | |||
712 | /* set cmap */ | ||
713 | for (i = 0; i < 8; i++) | ||
714 | info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16; | ||
715 | memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8); | ||
716 | memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8); | ||
717 | |||
718 | retval = register_framebuffer(info); | ||
719 | if (retval < 0) | ||
720 | goto err_cmap; | ||
721 | |||
722 | platform_set_drvdata(dev, info); | ||
723 | |||
724 | dev_dbg(&dev->dev, | ||
725 | "fb%d: Metronome frame buffer device, using %dK of video" | ||
726 | " memory\n", info->node, videomemorysize >> 10); | ||
727 | |||
728 | return 0; | ||
729 | |||
730 | err_cmap: | ||
731 | fb_dealloc_cmap(&info->cmap); | ||
732 | err_free_irq: | ||
733 | board->cleanup(par); | ||
734 | err_csum_table: | ||
735 | vfree(par->csum_table); | ||
736 | err_vfree: | ||
737 | vfree(videomemory); | ||
738 | err_fb_rel: | ||
739 | framebuffer_release(info); | ||
740 | err: | ||
741 | module_put(board->owner); | ||
742 | return retval; | ||
743 | } | ||
744 | |||
745 | static int metronomefb_remove(struct platform_device *dev) | ||
746 | { | ||
747 | struct fb_info *info = platform_get_drvdata(dev); | ||
748 | |||
749 | if (info) { | ||
750 | struct metronomefb_par *par = info->par; | ||
751 | |||
752 | unregister_framebuffer(info); | ||
753 | fb_deferred_io_cleanup(info); | ||
754 | fb_dealloc_cmap(&info->cmap); | ||
755 | par->board->cleanup(par); | ||
756 | vfree(par->csum_table); | ||
757 | vfree((void __force *)info->screen_base); | ||
758 | module_put(par->board->owner); | ||
759 | dev_dbg(&dev->dev, "calling release\n"); | ||
760 | framebuffer_release(info); | ||
761 | } | ||
762 | return 0; | ||
763 | } | ||
764 | |||
765 | static struct platform_driver metronomefb_driver = { | ||
766 | .probe = metronomefb_probe, | ||
767 | .remove = metronomefb_remove, | ||
768 | .driver = { | ||
769 | .owner = THIS_MODULE, | ||
770 | .name = "metronomefb", | ||
771 | }, | ||
772 | }; | ||
773 | module_platform_driver(metronomefb_driver); | ||
774 | |||
775 | module_param(user_wfm_size, uint, 0); | ||
776 | MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size"); | ||
777 | |||
778 | MODULE_DESCRIPTION("fbdev driver for Metronome controller"); | ||
779 | MODULE_AUTHOR("Jaya Kumar"); | ||
780 | MODULE_LICENSE("GPL"); | ||