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. https://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->device; |
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(start: 32, end: 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(start: sizeof(*wfm_hdr), end: 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(p: wfm_hdr->wmta) & 0x00FFFFFF; |
247 | cksum_idx = wmta + m*4 + 3; |
248 | if (cksum_idx >= size) |
249 | return -EINVAL; |
250 | cksum = calc_cksum(start: cksum_idx - 3, end: 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(p: mem + wmta + m * 4) & 0x00FFFFFF; |
259 | cksum_idx = tta + trn*4 + 3; |
260 | if (cksum_idx >= size) |
261 | return -EINVAL; |
262 | cksum = calc_cksum(start: cksum_idx - 3, end: 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(p: 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(start: owfm_idx, end: 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(&par->metromem_cmd->args[i], 0, |
358 | (ARRAY_SIZE(par->metromem_cmd->args) - i) * 2); |
359 | |
360 | par->metromem_cmd->csum = cs; |
361 | |
362 | msleep(msecs: 1); |
363 | par->board->set_rst(par, 1); |
364 | |
365 | msleep(msecs: 1); |
366 | par->board->set_stdby(par, 1); |
367 | |
368 | return par->board->met_wait_event(par); |
369 | } |
370 | |
371 | static int metronome_config_cmd(struct metronomefb_par *par) |
372 | { |
373 | /* setup config command |
374 | we can't immediately set the opcode since the controller |
375 | will try parse the command before we've set it all up */ |
376 | |
377 | memcpy(par->metromem_cmd->args, epd_frame_table[par->dt].config, |
378 | sizeof(epd_frame_table[par->dt].config)); |
379 | /* the rest are 0 */ |
380 | memset(&par->metromem_cmd->args[4], 0, |
381 | (ARRAY_SIZE(par->metromem_cmd->args) - 4) * 2); |
382 | |
383 | par->metromem_cmd->csum = 0xCC10; |
384 | par->metromem_cmd->csum += calc_img_cksum(start: par->metromem_cmd->args, length: 4); |
385 | par->metromem_cmd->opcode = 0xCC10; /* config cmd */ |
386 | |
387 | return par->board->met_wait_event(par); |
388 | } |
389 | |
390 | static int metronome_init_cmd(struct metronomefb_par *par) |
391 | { |
392 | int i; |
393 | u16 cs; |
394 | |
395 | /* setup init command |
396 | we can't immediately set the opcode since the controller |
397 | will try parse the command before we've set it all up |
398 | so we just set cs here and set the opcode at the end */ |
399 | |
400 | cs = 0xCC20; |
401 | |
402 | /* set the args ( 2 bytes ) for init */ |
403 | i = 0; |
404 | par->metromem_cmd->args[i] = 0; |
405 | cs += par->metromem_cmd->args[i++]; |
406 | |
407 | /* the rest are 0 */ |
408 | memset((u8 *) (par->metromem_cmd->args + i), 0, (32-i)*2); |
409 | |
410 | par->metromem_cmd->csum = cs; |
411 | par->metromem_cmd->opcode = 0xCC20; /* init cmd */ |
412 | |
413 | return par->board->met_wait_event(par); |
414 | } |
415 | |
416 | static int metronome_init_regs(struct metronomefb_par *par) |
417 | { |
418 | int res; |
419 | |
420 | res = par->board->setup_io(par); |
421 | if (res) |
422 | return res; |
423 | |
424 | res = metronome_powerup_cmd(par); |
425 | if (res) |
426 | return res; |
427 | |
428 | res = metronome_config_cmd(par); |
429 | if (res) |
430 | return res; |
431 | |
432 | res = metronome_init_cmd(par); |
433 | |
434 | return res; |
435 | } |
436 | |
437 | static void metronomefb_dpy_update(struct metronomefb_par *par) |
438 | { |
439 | int fbsize; |
440 | u16 cksum; |
441 | unsigned char *buf = par->info->screen_buffer; |
442 | |
443 | fbsize = par->info->fix.smem_len; |
444 | /* copy from vm to metromem */ |
445 | memcpy(par->metromem_img, buf, fbsize); |
446 | |
447 | cksum = calc_img_cksum(start: (u16 *) par->metromem_img, length: fbsize/2); |
448 | *((u16 *)(par->metromem_img) + fbsize/2) = cksum; |
449 | metronome_display_cmd(par); |
450 | } |
451 | |
452 | static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index) |
453 | { |
454 | int i; |
455 | u16 csum = 0; |
456 | u16 *buf = (u16 *)(par->info->screen_buffer + index); |
457 | u16 *img = (u16 *)(par->metromem_img + index); |
458 | |
459 | /* swizzle from vm to metromem and recalc cksum at the same time*/ |
460 | for (i = 0; i < PAGE_SIZE/2; i++) { |
461 | *(img + i) = (buf[i] << 5) & 0xE0E0; |
462 | csum += *(img + i); |
463 | } |
464 | return csum; |
465 | } |
466 | |
467 | /* this is called back from the deferred io workqueue */ |
468 | static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *) |
469 | { |
470 | u16 cksum; |
471 | struct fb_deferred_io_pageref *; |
472 | struct metronomefb_par *par = info->par; |
473 | |
474 | /* walk the written page list and swizzle the data */ |
475 | list_for_each_entry(pageref, pagereflist, list) { |
476 | unsigned long pgoffset = pageref->offset >> PAGE_SHIFT; |
477 | cksum = metronomefb_dpy_update_page(par, index: pageref->offset); |
478 | par->metromem_img_csum -= par->csum_table[pgoffset]; |
479 | par->csum_table[pgoffset] = cksum; |
480 | par->metromem_img_csum += cksum; |
481 | } |
482 | |
483 | metronome_display_cmd(par); |
484 | } |
485 | |
486 | static void metronomefb_defio_damage_range(struct fb_info *info, off_t off, size_t len) |
487 | { |
488 | struct metronomefb_par *par = info->par; |
489 | |
490 | metronomefb_dpy_update(par); |
491 | } |
492 | |
493 | static void metronomefb_defio_damage_area(struct fb_info *info, u32 x, u32 y, |
494 | u32 width, u32 height) |
495 | { |
496 | struct metronomefb_par *par = info->par; |
497 | |
498 | metronomefb_dpy_update(par); |
499 | } |
500 | |
501 | FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(metronomefb, |
502 | metronomefb_defio_damage_range, |
503 | metronomefb_defio_damage_area) |
504 | |
505 | static const struct fb_ops metronomefb_ops = { |
506 | .owner = THIS_MODULE, |
507 | FB_DEFAULT_DEFERRED_OPS(metronomefb), |
508 | }; |
509 | |
510 | static struct fb_deferred_io metronomefb_defio = { |
511 | .delay = HZ, |
512 | .sort_pagereflist = true, |
513 | .deferred_io = metronomefb_dpy_deferred_io, |
514 | }; |
515 | |
516 | static int metronomefb_probe(struct platform_device *dev) |
517 | { |
518 | struct fb_info *info; |
519 | struct metronome_board *board; |
520 | int retval = -ENOMEM; |
521 | int videomemorysize; |
522 | unsigned char *videomemory; |
523 | struct metronomefb_par *par; |
524 | const struct firmware *fw_entry; |
525 | int i; |
526 | int panel_type; |
527 | int fw, fh; |
528 | int epd_dt_index; |
529 | |
530 | /* pick up board specific routines */ |
531 | board = dev->dev.platform_data; |
532 | if (!board) |
533 | return -EINVAL; |
534 | |
535 | /* try to count device specific driver, if can't, platform recalls */ |
536 | if (!try_module_get(module: board->owner)) |
537 | return -ENODEV; |
538 | |
539 | info = framebuffer_alloc(size: sizeof(struct metronomefb_par), dev: &dev->dev); |
540 | if (!info) |
541 | goto err; |
542 | |
543 | /* we have two blocks of memory. |
544 | info->screen_buffer which is vm, and is the fb used by apps. |
545 | par->metromem which is physically contiguous memory and |
546 | contains the display controller commands, waveform, |
547 | processed image data and padding. this is the data pulled |
548 | by the device's LCD controller and pushed to Metronome. |
549 | the metromem memory is allocated by the board driver and |
550 | is provided to us */ |
551 | |
552 | panel_type = board->get_panel_type(); |
553 | switch (panel_type) { |
554 | case 6: |
555 | epd_dt_index = 0; |
556 | break; |
557 | case 8: |
558 | epd_dt_index = 1; |
559 | break; |
560 | case 97: |
561 | epd_dt_index = 2; |
562 | break; |
563 | default: |
564 | dev_err(&dev->dev, "Unexpected panel type. Defaulting to 6\n" ); |
565 | epd_dt_index = 0; |
566 | break; |
567 | } |
568 | |
569 | fw = epd_frame_table[epd_dt_index].fw; |
570 | fh = epd_frame_table[epd_dt_index].fh; |
571 | |
572 | /* we need to add a spare page because our csum caching scheme walks |
573 | * to the end of the page */ |
574 | videomemorysize = PAGE_SIZE + (fw * fh); |
575 | videomemory = vzalloc(size: videomemorysize); |
576 | if (!videomemory) |
577 | goto err_fb_rel; |
578 | |
579 | info->screen_buffer = videomemory; |
580 | info->fbops = &metronomefb_ops; |
581 | |
582 | metronomefb_fix.line_length = fw; |
583 | metronomefb_var.xres = fw; |
584 | metronomefb_var.yres = fh; |
585 | metronomefb_var.xres_virtual = fw; |
586 | metronomefb_var.yres_virtual = fh; |
587 | info->var = metronomefb_var; |
588 | info->fix = metronomefb_fix; |
589 | info->fix.smem_len = videomemorysize; |
590 | par = info->par; |
591 | par->info = info; |
592 | par->board = board; |
593 | par->dt = epd_dt_index; |
594 | init_waitqueue_head(&par->waitq); |
595 | |
596 | /* this table caches per page csum values. */ |
597 | par->csum_table = vmalloc(size: videomemorysize/PAGE_SIZE); |
598 | if (!par->csum_table) |
599 | goto err_vfree; |
600 | |
601 | /* the physical framebuffer that we use is setup by |
602 | * the platform device driver. It will provide us |
603 | * with cmd, wfm and image memory in a contiguous area. */ |
604 | retval = board->setup_fb(par); |
605 | if (retval) { |
606 | dev_err(&dev->dev, "Failed to setup fb\n" ); |
607 | goto err_csum_table; |
608 | } |
609 | |
610 | /* after this point we should have a framebuffer */ |
611 | if ((!par->metromem_wfm) || (!par->metromem_img) || |
612 | (!par->metromem_dma)) { |
613 | dev_err(&dev->dev, "fb access failure\n" ); |
614 | retval = -EINVAL; |
615 | goto err_csum_table; |
616 | } |
617 | |
618 | info->fix.smem_start = par->metromem_dma; |
619 | |
620 | /* load the waveform in. assume mode 3, temp 31 for now |
621 | a) request the waveform file from userspace |
622 | b) process waveform and decode into metromem */ |
623 | retval = request_firmware(fw: &fw_entry, name: "metronome.wbf" , device: &dev->dev); |
624 | if (retval < 0) { |
625 | dev_err(&dev->dev, "Failed to get waveform\n" ); |
626 | goto err_csum_table; |
627 | } |
628 | |
629 | retval = load_waveform(mem: (u8 *) fw_entry->data, size: fw_entry->size, m: 3, t: 31, |
630 | par); |
631 | release_firmware(fw: fw_entry); |
632 | if (retval < 0) { |
633 | dev_err(&dev->dev, "Failed processing waveform\n" ); |
634 | goto err_csum_table; |
635 | } |
636 | |
637 | retval = board->setup_irq(info); |
638 | if (retval) |
639 | goto err_csum_table; |
640 | |
641 | retval = metronome_init_regs(par); |
642 | if (retval < 0) |
643 | goto err_free_irq; |
644 | |
645 | info->flags = FBINFO_VIRTFB; |
646 | |
647 | info->fbdefio = &metronomefb_defio; |
648 | fb_deferred_io_init(info); |
649 | |
650 | retval = fb_alloc_cmap(cmap: &info->cmap, len: 8, transp: 0); |
651 | if (retval < 0) { |
652 | dev_err(&dev->dev, "Failed to allocate colormap\n" ); |
653 | goto err_free_irq; |
654 | } |
655 | |
656 | /* set cmap */ |
657 | for (i = 0; i < 8; i++) |
658 | info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/16; |
659 | memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*8); |
660 | memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*8); |
661 | |
662 | retval = register_framebuffer(fb_info: info); |
663 | if (retval < 0) |
664 | goto err_cmap; |
665 | |
666 | platform_set_drvdata(pdev: dev, data: info); |
667 | |
668 | dev_dbg(&dev->dev, |
669 | "fb%d: Metronome frame buffer device, using %dK of video" |
670 | " memory\n" , info->node, videomemorysize >> 10); |
671 | |
672 | return 0; |
673 | |
674 | err_cmap: |
675 | fb_dealloc_cmap(cmap: &info->cmap); |
676 | err_free_irq: |
677 | board->cleanup(par); |
678 | err_csum_table: |
679 | vfree(addr: par->csum_table); |
680 | err_vfree: |
681 | vfree(addr: videomemory); |
682 | err_fb_rel: |
683 | framebuffer_release(info); |
684 | err: |
685 | module_put(module: board->owner); |
686 | return retval; |
687 | } |
688 | |
689 | static void metronomefb_remove(struct platform_device *dev) |
690 | { |
691 | struct fb_info *info = platform_get_drvdata(pdev: dev); |
692 | |
693 | if (info) { |
694 | struct metronomefb_par *par = info->par; |
695 | |
696 | unregister_framebuffer(fb_info: info); |
697 | fb_deferred_io_cleanup(info); |
698 | fb_dealloc_cmap(cmap: &info->cmap); |
699 | par->board->cleanup(par); |
700 | vfree(addr: par->csum_table); |
701 | vfree(addr: info->screen_buffer); |
702 | module_put(module: par->board->owner); |
703 | dev_dbg(&dev->dev, "calling release\n" ); |
704 | framebuffer_release(info); |
705 | } |
706 | } |
707 | |
708 | static struct platform_driver metronomefb_driver = { |
709 | .probe = metronomefb_probe, |
710 | .remove_new = metronomefb_remove, |
711 | .driver = { |
712 | .name = "metronomefb" , |
713 | }, |
714 | }; |
715 | module_platform_driver(metronomefb_driver); |
716 | |
717 | module_param(user_wfm_size, uint, 0); |
718 | MODULE_PARM_DESC(user_wfm_size, "Set custom waveform size" ); |
719 | |
720 | MODULE_DESCRIPTION("fbdev driver for Metronome controller" ); |
721 | MODULE_AUTHOR("Jaya Kumar" ); |
722 | MODULE_LICENSE("GPL" ); |
723 | |
724 | MODULE_FIRMWARE("metronome.wbf" ); |
725 | |