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