Root/
Source at commit 7e910043632ede17d8049af5546b4607f0792421 created 12 years 9 months ago. By Maarten ter Huurne, MIPS: JZ4740: SLCD framebufer driver. | |
---|---|
1 | /* |
2 | * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> |
3 | * Copyright (C) 2010, Maarten ter Huurne <maarten@treewalker.org> |
4 | * JZ4720/JZ4740 SoC LCD framebuffer driver |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it |
7 | * under the terms of the GNU General Public License as published by the |
8 | * Free Software Foundation; either version 2 of the License, or (at your |
9 | * option) any later version. |
10 | * |
11 | * You should have received a copy of the GNU General Public License along |
12 | * with this program; if not, write to the Free Software Foundation, Inc., |
13 | * 675 Mass Ave, Cambridge, MA 02139, USA. |
14 | * |
15 | */ |
16 | |
17 | #include <linux/kernel.h> |
18 | #include <linux/module.h> |
19 | #include <linux/mutex.h> |
20 | #include <linux/platform_device.h> |
21 | |
22 | #include <linux/clk.h> |
23 | #include <linux/delay.h> |
24 | |
25 | #include <linux/console.h> |
26 | #include <linux/fb.h> |
27 | |
28 | #include <linux/dma-mapping.h> |
29 | |
30 | #include <asm/mach-jz4740/dma.h> |
31 | #include <asm/mach-jz4740/gpio.h> |
32 | #include <asm/mach-jz4740/jz4740_fb.h> |
33 | |
34 | #include "jz4740_lcd.h" |
35 | #include "jz4740_slcd.h" |
36 | |
37 | #define FB_A320TV_OFF 0 |
38 | #define FB_A320TV_NTSC 1 |
39 | #define FB_A320TV_PAL50 2 |
40 | #define FB_A320TV_PAL60 3 |
41 | #define FB_A320TV_PAL_M 4 |
42 | #define FB_A320TV_LAST 4 |
43 | |
44 | static const char *jzfb_tv_out_norm[] = { |
45 | "off", "ntsc", "pal", "pal-60", "pal-m", |
46 | }; |
47 | |
48 | static struct fb_fix_screeninfo jzfb_fix __devinitdata = { |
49 | .id = "JZ4740 SLCD FB", |
50 | .type = FB_TYPE_PACKED_PIXELS, |
51 | .visual = FB_VISUAL_TRUECOLOR, |
52 | .xpanstep = 0, |
53 | .ypanstep = 1, |
54 | .ywrapstep = 0, |
55 | .accel = FB_ACCEL_NONE, |
56 | }; |
57 | |
58 | const static struct jz_gpio_bulk_request jz_slcd_ctrl_pins[] = { |
59 | JZ_GPIO_BULK_PIN(LCD_PCLK), |
60 | JZ_GPIO_BULK_PIN(SLCD_RS), |
61 | JZ_GPIO_BULK_PIN(SLCD_CS), |
62 | }; |
63 | |
64 | const static struct jz_gpio_bulk_request jz_slcd_data_pins[] = { |
65 | JZ_GPIO_BULK_PIN(LCD_DATA0), |
66 | JZ_GPIO_BULK_PIN(LCD_DATA1), |
67 | JZ_GPIO_BULK_PIN(LCD_DATA2), |
68 | JZ_GPIO_BULK_PIN(LCD_DATA3), |
69 | JZ_GPIO_BULK_PIN(LCD_DATA4), |
70 | JZ_GPIO_BULK_PIN(LCD_DATA5), |
71 | JZ_GPIO_BULK_PIN(LCD_DATA6), |
72 | JZ_GPIO_BULK_PIN(LCD_DATA7), |
73 | JZ_GPIO_BULK_PIN(LCD_DATA8), |
74 | JZ_GPIO_BULK_PIN(LCD_DATA9), |
75 | JZ_GPIO_BULK_PIN(LCD_DATA10), |
76 | JZ_GPIO_BULK_PIN(LCD_DATA11), |
77 | JZ_GPIO_BULK_PIN(LCD_DATA12), |
78 | JZ_GPIO_BULK_PIN(LCD_DATA13), |
79 | JZ_GPIO_BULK_PIN(LCD_DATA14), |
80 | JZ_GPIO_BULK_PIN(LCD_DATA15), |
81 | JZ_GPIO_BULK_PIN(LCD_DATA16), |
82 | JZ_GPIO_BULK_PIN(LCD_DATA17), |
83 | }; |
84 | |
85 | static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb) |
86 | { |
87 | return ARRAY_SIZE(jz_slcd_ctrl_pins); |
88 | } |
89 | |
90 | static unsigned int jzfb_num_data_pins(struct jzfb *jzfb) |
91 | { |
92 | switch (jzfb->pdata->lcd_type) { |
93 | case JZ_LCD_TYPE_SMART_PARALLEL_8_BIT: |
94 | return 8; |
95 | case JZ_LCD_TYPE_SMART_PARALLEL_16_BIT: |
96 | return 16; |
97 | case JZ_LCD_TYPE_SMART_PARALLEL_18_BIT: |
98 | return 18; |
99 | default: |
100 | return 0; |
101 | } |
102 | } |
103 | |
104 | static void jzfb_free_gpio_pins(struct jzfb *jzfb) |
105 | { |
106 | jz_gpio_bulk_free(jz_slcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); |
107 | if (jzfb->pdata->lcd_type & (1 << 6)) { |
108 | /* serial */ |
109 | jz_gpio_bulk_free(&jz_slcd_data_pins[15], 1); |
110 | } else { |
111 | /* parallel */ |
112 | jz_gpio_bulk_free(jz_slcd_data_pins, |
113 | jzfb_num_data_pins(jzfb)); |
114 | } |
115 | } |
116 | |
117 | static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green, |
118 | unsigned blue, unsigned transp, struct fb_info *fb) |
119 | { |
120 | if (regno >= 16) |
121 | return -EINVAL; |
122 | |
123 | red = (red * ((1 << fb->var.red.length ) - 1)) / ((1 << 16) - 1); |
124 | green = (green * ((1 << fb->var.green.length) - 1)) / ((1 << 16) - 1); |
125 | blue = (blue * ((1 << fb->var.blue.length ) - 1)) / ((1 << 16) - 1); |
126 | |
127 | ((uint32_t *)fb->pseudo_palette)[regno] = |
128 | (red << fb->var.red.offset ) | |
129 | (green << fb->var.green.offset) | |
130 | (blue << fb->var.blue.offset ); |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static int jzfb_get_controller_bpp(struct jzfb *jzfb) |
136 | { |
137 | switch (jzfb->pdata->bpp) { |
138 | case 18: |
139 | case 24: |
140 | return 32; |
141 | case 15: |
142 | return 16; |
143 | default: |
144 | return jzfb->pdata->bpp; |
145 | } |
146 | } |
147 | |
148 | static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb, struct fb_var_screeninfo *var) |
149 | { |
150 | size_t i; |
151 | struct fb_videomode *mode = jzfb->pdata->modes; |
152 | |
153 | for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) { |
154 | if (mode->xres == var->xres && mode->yres == var->yres) |
155 | return mode; |
156 | } |
157 | |
158 | return NULL; |
159 | } |
160 | |
161 | static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb) |
162 | { |
163 | struct jzfb *jzfb = fb->par; |
164 | struct fb_videomode *mode; |
165 | |
166 | if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) && |
167 | var->bits_per_pixel != jzfb->pdata->bpp) |
168 | return -EINVAL; |
169 | |
170 | mode = jzfb_get_mode(jzfb, var); |
171 | if (mode == NULL) |
172 | return -EINVAL; |
173 | |
174 | fb_videomode_to_var(var, mode); |
175 | |
176 | /* Reserve space for double buffering. */ |
177 | var->yres_virtual = var->yres * 2; |
178 | |
179 | switch (jzfb->pdata->bpp) { |
180 | case 8: |
181 | break; |
182 | case 15: |
183 | var->red.offset = 10; |
184 | var->red.length = 5; |
185 | var->green.offset = 5; |
186 | var->green.length = 5; |
187 | var->blue.offset = 0; |
188 | var->blue.length = 5; |
189 | break; |
190 | case 16: |
191 | var->red.offset = 11; |
192 | var->red.length = 5; |
193 | var->green.offset = 5; |
194 | var->green.length = 6; |
195 | var->blue.offset = 0; |
196 | var->blue.length = 5; |
197 | break; |
198 | case 18: |
199 | var->red.offset = 16; |
200 | var->red.length = 6; |
201 | var->green.offset = 8; |
202 | var->green.length = 6; |
203 | var->blue.offset = 0; |
204 | var->blue.length = 6; |
205 | var->bits_per_pixel = 32; |
206 | break; |
207 | case 32: |
208 | case 24: |
209 | var->transp.offset = 24; |
210 | var->transp.length = 8; |
211 | var->red.offset = 16; |
212 | var->red.length = 8; |
213 | var->green.offset = 8; |
214 | var->green.length = 8; |
215 | var->blue.offset = 0; |
216 | var->blue.length = 8; |
217 | var->bits_per_pixel = 32; |
218 | break; |
219 | default: |
220 | break; |
221 | } |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static void jzfb_disable_dma(struct jzfb *jzfb) |
227 | { |
228 | jz4740_dma_disable(jzfb->dma); |
229 | while (readb(jzfb->base + JZ_REG_SLCD_STATE) & SLCD_STATE_BUSY); |
230 | writeb(readb(jzfb->base + JZ_REG_SLCD_CTRL) & ~SLCD_CTRL_DMA_EN, |
231 | jzfb->base + JZ_REG_SLCD_CTRL); |
232 | } |
233 | |
234 | static struct jz4740_dma_config jzfb_slcd_dma_config = { |
235 | .src_width = JZ4740_DMA_WIDTH_32BIT, |
236 | .dst_width = JZ4740_DMA_WIDTH_16BIT, |
237 | .transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE, |
238 | .request_type = JZ4740_DMA_TYPE_SLCD, |
239 | .flags = JZ4740_DMA_SRC_AUTOINC, |
240 | .mode = JZ4740_DMA_MODE_BLOCK, |
241 | }; |
242 | |
243 | static void jzfb_upload_frame_dma(struct jzfb *jzfb) |
244 | { |
245 | struct fb_info *fb = jzfb->fb; |
246 | struct fb_videomode *mode = fb->mode; |
247 | __u32 offset = fb->fix.line_length * fb->var.yoffset; |
248 | __u32 size = fb->fix.line_length * mode->yres; |
249 | |
250 | /* Ensure that the data to be uploaded is in memory. */ |
251 | dma_cache_sync(fb->device, jzfb->vidmem + offset, size, |
252 | DMA_TO_DEVICE); |
253 | |
254 | jz4740_dma_set_src_addr(jzfb->dma, jzfb->vidmem_phys + offset); |
255 | jz4740_dma_set_dst_addr(jzfb->dma, |
256 | CPHYSADDR(jzfb->base + JZ_REG_SLCD_FIFO)); |
257 | jz4740_dma_set_transfer_count(jzfb->dma, size); |
258 | |
259 | while (readb(jzfb->base + JZ_REG_SLCD_STATE) & SLCD_STATE_BUSY); |
260 | writeb(readb(jzfb->base + JZ_REG_SLCD_CTRL) | SLCD_CTRL_DMA_EN, |
261 | jzfb->base + JZ_REG_SLCD_CTRL); |
262 | jz4740_dma_enable(jzfb->dma); |
263 | } |
264 | |
265 | static void jzfb_upload_frame_cpu(struct jzfb *jzfb) |
266 | { |
267 | const int num_pixels = jzfb->fb->mode->xres * jzfb->fb->mode->yres; |
268 | uint16_t *p = jzfb->vidmem; |
269 | int i; |
270 | |
271 | jzfb_disable_dma(jzfb); |
272 | for (i = 0; i < num_pixels; i++) { |
273 | uint16_t rgb = *p++; |
274 | while (readb(jzfb->base + JZ_REG_SLCD_STATE) & SLCD_STATE_BUSY); |
275 | writel(SLCD_DATA_RS_DATA | rgb, jzfb->base + JZ_REG_SLCD_DATA); |
276 | } |
277 | } |
278 | |
279 | static void jzfb_refresh_work(struct work_struct *work) |
280 | { |
281 | struct jzfb *jzfb = container_of(work, struct jzfb, refresh_work.work); |
282 | |
283 | mutex_lock(&jzfb->lock); |
284 | if (jzfb->is_enabled) { |
285 | if (1) { |
286 | int interval; |
287 | |
288 | if (jzfb->dma_completion.done) { |
289 | if (jzfb->refresh_on_pan) |
290 | interval = HZ / 5; |
291 | else |
292 | interval = HZ / 60; |
293 | jzfb->refresh_on_pan = 0; |
294 | |
295 | INIT_COMPLETION(jzfb->dma_completion); |
296 | jzfb_upload_frame_dma(jzfb); |
297 | } else |
298 | interval = HZ / 250; |
299 | |
300 | schedule_delayed_work(&jzfb->refresh_work, interval); |
301 | } else { |
302 | jzfb_upload_frame_cpu(jzfb); |
303 | schedule_delayed_work(&jzfb->refresh_work, HZ / 10); |
304 | } |
305 | } |
306 | mutex_unlock(&jzfb->lock); |
307 | } |
308 | |
309 | static void jzfb_refresh_work_complete( |
310 | struct jz4740_dma_chan *dma, int res, void *dev) |
311 | { |
312 | struct jzfb *jzfb = dev_get_drvdata(dev); |
313 | complete_all(&jzfb->dma_completion); |
314 | } |
315 | |
316 | static int jzfb_set_par(struct fb_info *info) |
317 | { |
318 | struct jzfb *jzfb = info->par; |
319 | struct fb_var_screeninfo *var = &info->var; |
320 | struct fb_videomode *mode; |
321 | uint16_t slcd_cfg; |
322 | |
323 | mode = jzfb_get_mode(jzfb, var); |
324 | if (mode == NULL) |
325 | return -EINVAL; |
326 | |
327 | info->mode = mode; |
328 | |
329 | slcd_cfg = SLCD_CFG_BURST_8_WORD; |
330 | /* command size */ |
331 | slcd_cfg |= (jzfb->pdata->lcd_type & 3) << SLCD_CFG_CWIDTH_BIT; |
332 | /* data size */ |
333 | if (jzfb->pdata->lcd_type & (1 << 6)) { |
334 | /* serial */ |
335 | unsigned int num_bits; |
336 | switch (jzfb->pdata->lcd_type) { |
337 | case JZ_LCD_TYPE_SMART_SERIAL_8_BIT: |
338 | slcd_cfg |= SLCD_CFG_DWIDTH_8_x1; |
339 | num_bits = 8; |
340 | break; |
341 | case JZ_LCD_TYPE_SMART_SERIAL_16_BIT: |
342 | slcd_cfg |= SLCD_CFG_DWIDTH_16; |
343 | num_bits = 16; |
344 | break; |
345 | case JZ_LCD_TYPE_SMART_SERIAL_18_BIT: |
346 | slcd_cfg |= SLCD_CFG_DWIDTH_18; |
347 | num_bits = 18; |
348 | break; |
349 | default: |
350 | num_bits = 0; |
351 | break; |
352 | } |
353 | if (num_bits != jzfb->pdata->bpp) { |
354 | dev_err(&jzfb->pdev->dev, |
355 | "Data size (%d) does not match bpp (%d)\n", |
356 | num_bits, jzfb->pdata->bpp); |
357 | } |
358 | slcd_cfg |= SLCD_CFG_TYPE_SERIAL; |
359 | } else { |
360 | /* parallel */ |
361 | switch (jzfb->pdata->bpp) { |
362 | case 8: |
363 | slcd_cfg |= SLCD_CFG_DWIDTH_8_x1; |
364 | break; |
365 | case 15: |
366 | case 16: |
367 | switch (jzfb->pdata->lcd_type) { |
368 | case JZ_LCD_TYPE_SMART_PARALLEL_8_BIT: |
369 | slcd_cfg |= SLCD_CFG_DWIDTH_8_x2; |
370 | break; |
371 | default: |
372 | slcd_cfg |= SLCD_CFG_DWIDTH_16; |
373 | break; |
374 | } |
375 | break; |
376 | case 18: |
377 | switch (jzfb->pdata->lcd_type) { |
378 | case JZ_LCD_TYPE_SMART_PARALLEL_8_BIT: |
379 | slcd_cfg |= SLCD_CFG_DWIDTH_8_x3; |
380 | break; |
381 | case JZ_LCD_TYPE_SMART_PARALLEL_16_BIT: |
382 | slcd_cfg |= SLCD_CFG_DWIDTH_9_x2; |
383 | break; |
384 | case JZ_LCD_TYPE_SMART_PARALLEL_18_BIT: |
385 | slcd_cfg |= SLCD_CFG_DWIDTH_18; |
386 | break; |
387 | default: |
388 | break; |
389 | } |
390 | break; |
391 | case 24: |
392 | slcd_cfg |= SLCD_CFG_DWIDTH_8_x3; |
393 | break; |
394 | default: |
395 | dev_err(&jzfb->pdev->dev, |
396 | "Unsupported value for bpp: %d\n", |
397 | jzfb->pdata->bpp); |
398 | } |
399 | slcd_cfg |= SLCD_CFG_TYPE_PARALLEL; |
400 | } |
401 | if (!jzfb->pdata->chip_select_active_low) |
402 | slcd_cfg |= SLCD_CFG_CS_ACTIVE_HIGH; |
403 | if (!jzfb->pdata->register_select_active_low) |
404 | slcd_cfg |= SLCD_CFG_RS_CMD_HIGH; |
405 | if (!jzfb->pdata->pixclk_falling_edge) |
406 | slcd_cfg |= SLCD_CFG_CLK_ACTIVE_RISING; |
407 | |
408 | #if 0 |
409 | // TODO(MtH): Compute rate from refresh or vice versa. |
410 | if (mode->pixclock) { |
411 | rate = PICOS2KHZ(mode->pixclock) * 1000; |
412 | mode->refresh = rate / vt / ht; |
413 | } else { |
414 | if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL) |
415 | rate = mode->refresh * (vt + 2 * mode->xres) * ht; |
416 | else |
417 | rate = mode->refresh * vt * ht; |
418 | |
419 | mode->pixclock = KHZ2PICOS(rate / 1000); |
420 | } |
421 | #endif |
422 | |
423 | mutex_lock(&jzfb->lock); |
424 | if (!jzfb->is_enabled) |
425 | clk_enable(jzfb->ldclk); |
426 | |
427 | // TODO(MtH): We should not change config while DMA might be running. |
428 | writew(slcd_cfg, jzfb->base + JZ_REG_SLCD_CFG); |
429 | |
430 | if (!jzfb->is_enabled) |
431 | clk_disable(jzfb->ldclk); |
432 | mutex_unlock(&jzfb->lock); |
433 | |
434 | // TODO(MtH): Use maximum transfer speed that panel can handle. |
435 | // ILI9325 can do 10 MHz. |
436 | clk_set_rate(jzfb->lpclk, 12000000); |
437 | clk_set_rate(jzfb->ldclk, 42000000); |
438 | |
439 | return 0; |
440 | } |
441 | |
442 | static void jzfb_enable(struct jzfb *jzfb) |
443 | { |
444 | uint32_t ctrl; |
445 | |
446 | clk_enable(jzfb->ldclk); |
447 | |
448 | jz_gpio_bulk_resume(jz_slcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); |
449 | if (jzfb->pdata->lcd_type & (1 << 6)) { |
450 | /* serial */ |
451 | jz_gpio_bulk_resume(&jz_slcd_data_pins[15], 1); |
452 | } else { |
453 | /* parallel */ |
454 | jz_gpio_bulk_resume(jz_slcd_data_pins, |
455 | jzfb_num_data_pins(jzfb)); |
456 | } |
457 | jzfb_disable_dma(jzfb); |
458 | complete_all(&jzfb->dma_completion); |
459 | jzfb->panel->enable(jzfb); |
460 | |
461 | ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); |
462 | ctrl |= JZ_LCD_CTRL_ENABLE; |
463 | ctrl &= ~JZ_LCD_CTRL_DISABLE; |
464 | writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); |
465 | |
466 | schedule_delayed_work(&jzfb->refresh_work, 0); |
467 | } |
468 | |
469 | static void jzfb_disable(struct jzfb *jzfb) |
470 | { |
471 | /* It is safe but wasteful to call refresh_work() while disabled. */ |
472 | cancel_delayed_work_sync(&jzfb->refresh_work); |
473 | |
474 | /* Abort any DMA transfer that might be in progress and allow direct |
475 | writes to the panel. */ |
476 | jzfb_disable_dma(jzfb); |
477 | complete_all(&jzfb->dma_completion); |
478 | |
479 | jzfb->panel->disable(jzfb); |
480 | jz_gpio_bulk_suspend(jz_slcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); |
481 | if (jzfb->pdata->lcd_type & (1 << 6)) { |
482 | /* serial */ |
483 | jz_gpio_bulk_suspend(&jz_slcd_data_pins[15], 1); |
484 | } else { |
485 | /* parallel */ |
486 | jz_gpio_bulk_suspend(jz_slcd_data_pins, |
487 | jzfb_num_data_pins(jzfb)); |
488 | } |
489 | |
490 | clk_disable(jzfb->ldclk); |
491 | } |
492 | |
493 | static int jzfb_blank(int blank_mode, struct fb_info *info) |
494 | { |
495 | struct jzfb *jzfb = info->par; |
496 | int ret = 0; |
497 | int new_enabled = (blank_mode == FB_BLANK_UNBLANK); |
498 | |
499 | mutex_lock(&jzfb->lock); |
500 | if (new_enabled) { |
501 | if (!jzfb->is_enabled) |
502 | jzfb_enable(jzfb); |
503 | } else { |
504 | if (jzfb->is_enabled) { |
505 | /* No sleep in TV-out mode. */ |
506 | if (jzfb->tv_out == FB_A320TV_OFF) |
507 | jzfb_disable(jzfb); |
508 | else |
509 | ret = -EBUSY; |
510 | } |
511 | } |
512 | if (!ret) |
513 | jzfb->is_enabled = new_enabled; |
514 | mutex_unlock(&jzfb->lock); |
515 | |
516 | return ret; |
517 | } |
518 | |
519 | static int jzfb_wait_for_vsync(struct fb_info *info) |
520 | { |
521 | struct jzfb *jzfb = info->par; |
522 | |
523 | if (jzfb->tv_out != FB_A320TV_OFF && |
524 | !jzfb->tv_out_vsync) |
525 | return 0; |
526 | return wait_for_completion_interruptible(&jzfb->dma_completion); |
527 | } |
528 | |
529 | static int jzfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) |
530 | { |
531 | struct jzfb *jzfb = info->par; |
532 | info->var.yoffset = var->yoffset; |
533 | |
534 | /* Ensure that the data to be uploaded is in memory. */ |
535 | dma_cache_sync(&jzfb->pdev->dev, jzfb->vidmem |
536 | + info->fix.line_length * var->yoffset, |
537 | info->fix.line_length * var->yres, |
538 | DMA_TO_DEVICE); |
539 | |
540 | /* update frame start address for TV-out mode */ |
541 | (*jzfb->framedesc)[1].addr = jzfb->vidmem_phys |
542 | + info->fix.line_length * var->yoffset; |
543 | |
544 | jzfb_wait_for_vsync(info); |
545 | |
546 | jzfb->refresh_on_pan = 1; |
547 | flush_delayed_work(&jzfb->refresh_work); |
548 | return 0; |
549 | } |
550 | |
551 | static int jzfb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
552 | { |
553 | const unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; |
554 | const unsigned long size = vma->vm_end - vma->vm_start; |
555 | |
556 | if (offset + size > info->fix.smem_len) |
557 | return -EINVAL; |
558 | |
559 | if (remap_pfn_range(vma, vma->vm_start, |
560 | (info->fix.smem_start + offset) >> PAGE_SHIFT, |
561 | size, vma->vm_page_prot)) |
562 | return -EAGAIN; |
563 | |
564 | return 0; |
565 | } |
566 | |
567 | static int jzfb_alloc_devmem(struct jzfb *jzfb) |
568 | { |
569 | int max_linesize = 0, max_framesize = 0; |
570 | int bytes_per_pixel; |
571 | struct fb_videomode *mode = jzfb->pdata->modes; |
572 | void *page; |
573 | int i; |
574 | |
575 | for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) { |
576 | if (max_linesize < mode->xres) |
577 | max_linesize = mode->xres; |
578 | if (max_framesize < mode->xres * mode->yres) |
579 | max_framesize = mode->xres * mode->yres; |
580 | } |
581 | |
582 | bytes_per_pixel = jzfb_get_controller_bpp(jzfb) >> 3; |
583 | max_linesize *= bytes_per_pixel; |
584 | max_framesize *= bytes_per_pixel; |
585 | |
586 | jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev, |
587 | sizeof(*jzfb->framedesc), |
588 | &jzfb->framedesc_phys, GFP_KERNEL); |
589 | if (!jzfb->framedesc) |
590 | return -ENOMEM; |
591 | |
592 | jzfb->blackline_size = max_linesize; |
593 | jzfb->blackline = dma_alloc_coherent(&jzfb->pdev->dev, |
594 | jzfb->blackline_size, |
595 | &jzfb->blackline_phys, GFP_KERNEL); |
596 | if (!jzfb->blackline) |
597 | goto err_free_framedesc; |
598 | |
599 | /* Set the black line to black... */ |
600 | memset(jzfb->blackline, 0, jzfb->blackline_size); |
601 | |
602 | /* reserve memory for two frames to allow double buffering */ |
603 | jzfb->vidmem_size = PAGE_ALIGN(max_framesize * 2); |
604 | jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev, |
605 | jzfb->vidmem_size, |
606 | &jzfb->vidmem_phys, GFP_KERNEL); |
607 | |
608 | if (!jzfb->vidmem) |
609 | goto err_free_blackline; |
610 | |
611 | for (page = jzfb->vidmem; |
612 | page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size); |
613 | page += PAGE_SIZE) { |
614 | SetPageReserved(virt_to_page(page)); |
615 | } |
616 | |
617 | for (i = 0; i < 3; i++) |
618 | (*jzfb->framedesc)[i].next = jzfb->framedesc_phys |
619 | + ((i + 1) % 3) * sizeof(struct jzfb_framedesc); |
620 | (*jzfb->framedesc)[0].addr = (*jzfb->framedesc)[2].addr = |
621 | jzfb->blackline_phys; |
622 | (*jzfb->framedesc)[0].id = 0xdadabeeb; |
623 | (*jzfb->framedesc)[2].id = 0xfadefeed; |
624 | (*jzfb->framedesc)[0].cmd = (*jzfb->framedesc)[2].cmd = |
625 | jzfb->blackline_size / 4; |
626 | (*jzfb->framedesc)[1].addr = jzfb->vidmem_phys; |
627 | (*jzfb->framedesc)[1].id = 0xdeafbead; |
628 | (*jzfb->framedesc)[1].cmd = (max_framesize / 4) |
629 | | JZ_LCD_CMD_EOF_IRQ | JZ_LCD_CMD_SOF_IRQ; |
630 | |
631 | return 0; |
632 | |
633 | err_free_blackline: |
634 | dma_free_coherent(&jzfb->pdev->dev, jzfb->blackline_size, |
635 | jzfb->blackline, jzfb->blackline_phys); |
636 | err_free_framedesc: |
637 | dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), |
638 | jzfb->framedesc, jzfb->framedesc_phys); |
639 | return -ENOMEM; |
640 | } |
641 | |
642 | static void jzfb_free_devmem(struct jzfb *jzfb) |
643 | { |
644 | dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size, |
645 | jzfb->vidmem, jzfb->vidmem_phys); |
646 | dma_free_coherent(&jzfb->pdev->dev, jzfb->blackline_size, |
647 | jzfb->blackline, jzfb->blackline_phys); |
648 | dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), |
649 | jzfb->framedesc, jzfb->framedesc_phys); |
650 | } |
651 | |
652 | static int jzfb_tv_out(struct jzfb *jzfb, unsigned int mode) |
653 | { |
654 | int blank = jzfb->is_enabled ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; |
655 | struct fb_event event = { |
656 | .info = jzfb->fb, |
657 | .data = &blank, |
658 | }; |
659 | |
660 | printk("A320 TV out: %d\n", mode); |
661 | if (mode > FB_A320TV_LAST) |
662 | return -EINVAL; |
663 | if (mode == jzfb->tv_out) |
664 | return 0; |
665 | |
666 | if (mode != FB_A320TV_OFF) { |
667 | cancel_delayed_work_sync(&jzfb->refresh_work); |
668 | /* Abort any DMA transfer that might be in progress and |
669 | allow direct writes to the panel. */ |
670 | jzfb_disable_dma(jzfb); |
671 | jzfb->panel->disable(jzfb); |
672 | complete_all(&jzfb->dma_completion); |
673 | |
674 | /* set up LCD controller for TV output */ |
675 | |
676 | writel(JZ_LCD_CFG_HSYNC_ACTIVE_LOW | |
677 | JZ_LCD_CFG_VSYNC_ACTIVE_LOW, |
678 | jzfb->base + JZ_REG_LCD_CFG); |
679 | |
680 | /* V-Sync pulse end position */ |
681 | writel(10, jzfb->base + JZ_REG_LCD_VSYNC); |
682 | |
683 | if (mode == FB_A320TV_PAL50) { |
684 | /* PAL 50 Hz */ |
685 | /* H-Sync pulse start position */ |
686 | writel(0x0000007d, jzfb->base + JZ_REG_LCD_HSYNC); |
687 | /* virtual area size */ |
688 | writel(0x036c0112, jzfb->base + JZ_REG_LCD_VAT); |
689 | /* horizontal start/end point */ |
690 | writel(0x02240364, jzfb->base + JZ_REG_LCD_DAH); |
691 | /* vertical start/end point */ |
692 | writel(0x001a010c, jzfb->base + JZ_REG_LCD_DAV); |
693 | } else { |
694 | /* NTSC and PAL 60 Hz */ |
695 | writel(0x0000003c, jzfb->base + JZ_REG_LCD_HSYNC); |
696 | writel(0x02e00110, jzfb->base + JZ_REG_LCD_VAT); |
697 | writel(0x019902d9, jzfb->base + JZ_REG_LCD_DAH); |
698 | writel(0x001c010e, jzfb->base + JZ_REG_LCD_DAV); |
699 | } |
700 | writel(0, jzfb->base + JZ_REG_LCD_PS); |
701 | writel(0, jzfb->base + JZ_REG_LCD_CLS); |
702 | writel(0, jzfb->base + JZ_REG_LCD_SPL); |
703 | writel(0, jzfb->base + JZ_REG_LCD_REV); |
704 | /* reset status register */ |
705 | writel(0, jzfb->base + JZ_REG_LCD_STATE); |
706 | |
707 | /* tell LCDC about the frame descriptor address */ |
708 | writel(jzfb->framedesc_phys, jzfb->base + JZ_REG_LCD_DA0); |
709 | |
710 | writel(JZ_LCD_CTRL_BURST_16 | JZ_LCD_CTRL_ENABLE | |
711 | JZ_LCD_CTRL_BPP_15_16 | |
712 | JZ_LCD_CTRL_EOF_IRQ | JZ_LCD_CTRL_SOF_IRQ, |
713 | jzfb->base + JZ_REG_LCD_CTRL); |
714 | } else { |
715 | /* disable EOF/SOF interrupts */ |
716 | unsigned long ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); |
717 | ctrl &= ~(JZ_LCD_CTRL_EOF_IRQ | JZ_LCD_CTRL_SOF_IRQ); |
718 | writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); |
719 | |
720 | /* disable LCD controller and re-enable SLCD */ |
721 | writel(JZ_LCD_CFG_SLCD, jzfb->base + JZ_REG_LCD_CFG); |
722 | jzfb->panel->enable(jzfb); |
723 | |
724 | jzfb->refresh_on_pan = 0; |
725 | complete_all(&jzfb->dma_completion); |
726 | schedule_delayed_work(&jzfb->refresh_work, 0); |
727 | } |
728 | |
729 | /* reaffirm the current blanking state, to trigger a backlight update */ |
730 | console_lock(); |
731 | fb_notifier_call_chain(FB_EVENT_BLANK, &event); |
732 | console_unlock(); |
733 | jzfb->tv_out = mode; |
734 | return 0; |
735 | } |
736 | |
737 | static ssize_t jzfb_tv_out_show(struct device *dev, struct device_attribute *attr, |
738 | char *buf) |
739 | { |
740 | struct jzfb *jzfb = dev_get_drvdata(dev); |
741 | |
742 | if (jzfb->tv_out > FB_A320TV_LAST) { |
743 | dev_err(dev, "Unknown norm for TV-out\n"); |
744 | return -1; |
745 | } |
746 | |
747 | return sprintf(buf, "%s\n", jzfb_tv_out_norm[jzfb->tv_out]); |
748 | } |
749 | |
750 | static ssize_t jzfb_tv_out_store(struct device *dev, struct device_attribute *attr, |
751 | const char *buf, size_t n) |
752 | { |
753 | size_t i; |
754 | struct jzfb *jzfb = dev_get_drvdata(dev); |
755 | |
756 | for (i = 0; i <= FB_A320TV_LAST; i++) { |
757 | if (sysfs_streq(jzfb_tv_out_norm[i], buf)) { |
758 | jzfb_tv_out(jzfb, i); |
759 | return n; |
760 | } |
761 | } |
762 | return -EINVAL; |
763 | } |
764 | |
765 | static DEVICE_ATTR(tv_out, 0644, jzfb_tv_out_show, jzfb_tv_out_store); |
766 | |
767 | static ssize_t jzfb_vsync_show(struct device *dev, struct device_attribute *attr, |
768 | char *buf) |
769 | { |
770 | struct jzfb *jzfb = dev_get_drvdata(dev); |
771 | return sprintf(buf, "%u\n", jzfb->tv_out_vsync); |
772 | } |
773 | |
774 | static ssize_t jzfb_vsync_store(struct device *dev, struct device_attribute *attr, |
775 | const char *buf, size_t n) |
776 | { |
777 | struct jzfb *jzfb = dev_get_drvdata(dev); |
778 | unsigned int vsync; |
779 | |
780 | if (sscanf(buf, "%u", &vsync) < 1) |
781 | return -EINVAL; |
782 | |
783 | jzfb->tv_out_vsync = vsync; |
784 | return n; |
785 | } |
786 | |
787 | static DEVICE_ATTR(tv_out_vsync, 0644, jzfb_vsync_show, jzfb_vsync_store); |
788 | |
789 | static ssize_t jzfb_panel_show(struct device *dev, struct device_attribute *attr, |
790 | char *buf) |
791 | { |
792 | struct jzfb *jzfb = dev_get_drvdata(dev); |
793 | return sprintf(buf, "%s\n", jzfb->panel->name); |
794 | } |
795 | |
796 | static ssize_t jzfb_panel_store(struct device *dev, struct device_attribute *attr, |
797 | const char *buf, size_t n) |
798 | { |
799 | struct jzfb *jzfb = dev_get_drvdata(dev); |
800 | const struct jz_slcd_panel *panel = jz_slcd_panel_from_name(buf); |
801 | |
802 | if (!panel) { |
803 | dev_err(dev, "Unknown SLCD panel: %s\n", buf); |
804 | return -EINVAL; |
805 | } |
806 | |
807 | if (panel != jzfb->panel) { |
808 | jzfb->panel->disable(jzfb); |
809 | jzfb->panel->exit(jzfb); |
810 | jzfb->panel = panel; |
811 | panel->init(jzfb); |
812 | panel->enable(jzfb); |
813 | } |
814 | return n; |
815 | } |
816 | |
817 | static DEVICE_ATTR(panel, 0644, jzfb_panel_show, jzfb_panel_store); |
818 | |
819 | static struct fb_ops jzfb_ops = { |
820 | .owner = THIS_MODULE, |
821 | .fb_check_var = jzfb_check_var, |
822 | .fb_set_par = jzfb_set_par, |
823 | .fb_setcolreg = jzfb_setcolreg, |
824 | .fb_blank = jzfb_blank, |
825 | .fb_pan_display = jzfb_pan_display, |
826 | .fb_fillrect = sys_fillrect, |
827 | .fb_copyarea = sys_copyarea, |
828 | .fb_imageblit = sys_imageblit, |
829 | .fb_mmap = jzfb_mmap, |
830 | }; |
831 | |
832 | static irqreturn_t jz4740_lcd_irq(int irq, void *dev_id) |
833 | { |
834 | struct jzfb *jzfb = dev_id; |
835 | unsigned long state = readl(jzfb->base + JZ_REG_LCD_STATE); |
836 | |
837 | if (state & JZ_LCD_STATE_SOF) { |
838 | INIT_COMPLETION(jzfb->dma_completion); |
839 | state &= ~JZ_LCD_STATE_SOF; |
840 | } else { |
841 | complete_all(&jzfb->dma_completion); |
842 | state &= ~JZ_LCD_STATE_EOF; |
843 | } |
844 | |
845 | /* Acknowledge the interrupt */ |
846 | writel(state, jzfb->base + JZ_REG_LCD_STATE); |
847 | return IRQ_HANDLED; |
848 | } |
849 | |
850 | static int __devinit jzfb_probe(struct platform_device *pdev) |
851 | { |
852 | int ret; |
853 | struct jzfb *jzfb; |
854 | struct fb_info *fb; |
855 | struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data; |
856 | struct resource *mem; |
857 | |
858 | if (!pdata) { |
859 | dev_err(&pdev->dev, "Missing platform data\n"); |
860 | return -ENOENT; |
861 | } |
862 | |
863 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
864 | |
865 | if (!mem) { |
866 | dev_err(&pdev->dev, "Failed to get register memory resource\n"); |
867 | return -ENOENT; |
868 | } |
869 | |
870 | mem = request_mem_region(mem->start, resource_size(mem), pdev->name); |
871 | |
872 | if (!mem) { |
873 | dev_err(&pdev->dev, "Failed to request register memory region\n"); |
874 | return -EBUSY; |
875 | } |
876 | |
877 | fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev); |
878 | |
879 | if (!fb) { |
880 | dev_err(&pdev->dev, "Failed to allocate framebuffer device\n"); |
881 | ret = -ENOMEM; |
882 | goto err_release_mem_region; |
883 | } |
884 | |
885 | fb->fbops = &jzfb_ops; |
886 | fb->flags = FBINFO_DEFAULT; |
887 | |
888 | jzfb = fb->par; |
889 | jzfb->pdev = pdev; |
890 | jzfb->pdata = pdata; |
891 | jzfb->mem = mem; |
892 | |
893 | jzfb->tv_out = FB_A320TV_OFF; |
894 | jzfb->tv_out_vsync = 1; |
895 | jzfb->refresh_on_pan = 0; |
896 | init_completion(&jzfb->dma_completion); |
897 | complete_all(&jzfb->dma_completion); |
898 | |
899 | jzfb->dma = jz4740_dma_request(&pdev->dev, dev_name(&pdev->dev), 0); |
900 | if (!jzfb->dma) { |
901 | dev_err(&pdev->dev, "Failed to get DMA channel\n"); |
902 | ret = -EBUSY; |
903 | goto err_framebuffer_release; |
904 | } |
905 | jz4740_dma_configure(jzfb->dma, &jzfb_slcd_dma_config); |
906 | jz4740_dma_set_complete_cb(jzfb->dma, &jzfb_refresh_work_complete); |
907 | |
908 | jzfb->ldclk = clk_get(&pdev->dev, "lcd"); |
909 | if (IS_ERR(jzfb->ldclk)) { |
910 | ret = PTR_ERR(jzfb->ldclk); |
911 | dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret); |
912 | goto err_free_dma; |
913 | } |
914 | |
915 | jzfb->lpclk = clk_get(&pdev->dev, "lcd_pclk"); |
916 | if (IS_ERR(jzfb->lpclk)) { |
917 | ret = PTR_ERR(jzfb->lpclk); |
918 | dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret); |
919 | goto err_put_ldclk; |
920 | } |
921 | |
922 | jzfb->base = ioremap(mem->start, resource_size(mem)); |
923 | |
924 | if (!jzfb->base) { |
925 | dev_err(&pdev->dev, "Failed to ioremap register memory region\n"); |
926 | ret = -EBUSY; |
927 | goto err_put_lpclk; |
928 | } |
929 | |
930 | platform_set_drvdata(pdev, jzfb); |
931 | |
932 | fb_videomode_to_modelist(pdata->modes, pdata->num_modes, |
933 | &fb->modelist); |
934 | fb->mode = pdata->modes; |
935 | |
936 | fb_videomode_to_var(&fb->var, fb->mode); |
937 | fb->var.bits_per_pixel = pdata->bpp; |
938 | jzfb_check_var(&fb->var, fb); |
939 | |
940 | ret = jzfb_alloc_devmem(jzfb); |
941 | if (ret) { |
942 | dev_err(&pdev->dev, "Failed to allocate video memory\n"); |
943 | goto err_iounmap; |
944 | } |
945 | |
946 | fb->fix = jzfb_fix; |
947 | fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8; |
948 | fb->fix.mmio_start = mem->start; |
949 | fb->fix.mmio_len = resource_size(mem); |
950 | fb->fix.smem_start = jzfb->vidmem_phys; |
951 | fb->fix.smem_len = fb->fix.line_length * fb->var.yres_virtual; |
952 | fb->screen_base = jzfb->vidmem; |
953 | fb->pseudo_palette = jzfb->pseudo_palette; |
954 | |
955 | fb_alloc_cmap(&fb->cmap, 256, 0); |
956 | |
957 | mutex_init(&jzfb->lock); |
958 | |
959 | clk_enable(jzfb->ldclk); |
960 | jzfb->is_enabled = 1; |
961 | |
962 | writel(JZ_LCD_CFG_SLCD, jzfb->base + JZ_REG_LCD_CFG); |
963 | writeb(0, jzfb->base + JZ_REG_SLCD_CTRL); |
964 | |
965 | jzfb_set_par(fb); |
966 | |
967 | jz_gpio_bulk_request(jz_slcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb)); |
968 | if (jzfb->pdata->lcd_type & (1 << 6)) { |
969 | /* serial */ |
970 | jz_gpio_bulk_request(&jz_slcd_data_pins[15], 1); |
971 | } else { |
972 | /* parallel */ |
973 | jz_gpio_bulk_request(jz_slcd_data_pins, |
974 | jzfb_num_data_pins(jzfb)); |
975 | } |
976 | |
977 | ret = request_irq(JZ4740_IRQ_LCD, jz4740_lcd_irq, 0, "LCD", jzfb); |
978 | if (ret) { |
979 | dev_err(&pdev->dev, "Failed to request IRQ\n"); |
980 | goto err_free_devmem; |
981 | } |
982 | |
983 | jzfb->panel = jz_slcd_panels_probe(jzfb); |
984 | if (!jzfb->panel) { |
985 | dev_err(&pdev->dev, "Failed to find panel driver\n"); |
986 | ret = -ENOENT; |
987 | goto err_free_irq; |
988 | } |
989 | jzfb_disable_dma(jzfb); |
990 | jzfb->panel->init(jzfb); |
991 | |
992 | jzfb->panel->enable(jzfb); |
993 | |
994 | ret = register_framebuffer(fb); |
995 | if (ret) { |
996 | dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret); |
997 | goto err_free_panel; |
998 | } |
999 | |
1000 | jzfb->fb = fb; |
1001 | fb_prepare_logo(jzfb->fb, 0); |
1002 | fb_show_logo(jzfb->fb, 0); |
1003 | |
1004 | INIT_DELAYED_WORK(&jzfb->refresh_work, jzfb_refresh_work); |
1005 | schedule_delayed_work(&jzfb->refresh_work, 0); |
1006 | |
1007 | ret = device_create_file(&pdev->dev, &dev_attr_panel); |
1008 | if (ret) |
1009 | goto err_cancel_work; |
1010 | |
1011 | ret = device_create_file(&pdev->dev, &dev_attr_tv_out); |
1012 | if (ret) |
1013 | goto err_remove_file_panel; |
1014 | |
1015 | ret = device_create_file(&pdev->dev, &dev_attr_tv_out_vsync); |
1016 | if (!ret) |
1017 | return 0; |
1018 | |
1019 | device_remove_file(&pdev->dev, &dev_attr_tv_out); |
1020 | err_remove_file_panel: |
1021 | device_remove_file(&pdev->dev, &dev_attr_panel); |
1022 | err_cancel_work: |
1023 | cancel_delayed_work_sync(&jzfb->refresh_work); |
1024 | err_free_panel: |
1025 | jzfb->panel->exit(jzfb); |
1026 | err_free_irq: |
1027 | free_irq(JZ4740_IRQ_LCD, jzfb); |
1028 | err_free_devmem: |
1029 | jzfb_free_gpio_pins(jzfb); |
1030 | |
1031 | fb_dealloc_cmap(&fb->cmap); |
1032 | jzfb_free_devmem(jzfb); |
1033 | err_iounmap: |
1034 | iounmap(jzfb->base); |
1035 | err_put_lpclk: |
1036 | clk_put(jzfb->lpclk); |
1037 | err_put_ldclk: |
1038 | clk_put(jzfb->ldclk); |
1039 | err_free_dma: |
1040 | jz4740_dma_free(jzfb->dma); |
1041 | err_framebuffer_release: |
1042 | framebuffer_release(fb); |
1043 | err_release_mem_region: |
1044 | release_mem_region(mem->start, resource_size(mem)); |
1045 | return ret; |
1046 | } |
1047 | |
1048 | static int __devexit jzfb_remove(struct platform_device *pdev) |
1049 | { |
1050 | struct jzfb *jzfb = platform_get_drvdata(pdev); |
1051 | |
1052 | device_remove_file(&pdev->dev, &dev_attr_tv_out_vsync); |
1053 | device_remove_file(&pdev->dev, &dev_attr_tv_out); |
1054 | device_remove_file(&pdev->dev, &dev_attr_panel); |
1055 | jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb); |
1056 | |
1057 | free_irq(JZ4740_IRQ_LCD, jzfb); |
1058 | |
1059 | /* Blanking will prevent future refreshes from behind scheduled. |
1060 | Now wait for a possible refresh in progress to finish. */ |
1061 | cancel_delayed_work_sync(&jzfb->refresh_work); |
1062 | |
1063 | jzfb->panel->exit(jzfb); |
1064 | |
1065 | jzfb_free_gpio_pins(jzfb); |
1066 | |
1067 | jz4740_dma_free(jzfb->dma); |
1068 | |
1069 | iounmap(jzfb->base); |
1070 | release_mem_region(jzfb->mem->start, resource_size(jzfb->mem)); |
1071 | |
1072 | fb_dealloc_cmap(&jzfb->fb->cmap); |
1073 | jzfb_free_devmem(jzfb); |
1074 | |
1075 | platform_set_drvdata(pdev, NULL); |
1076 | |
1077 | clk_put(jzfb->lpclk); |
1078 | clk_put(jzfb->ldclk); |
1079 | |
1080 | framebuffer_release(jzfb->fb); |
1081 | |
1082 | return 0; |
1083 | } |
1084 | |
1085 | #ifdef CONFIG_PM |
1086 | |
1087 | static int jzfb_suspend(struct device *dev) |
1088 | { |
1089 | struct jzfb *jzfb = dev_get_drvdata(dev); |
1090 | |
1091 | console_lock(); |
1092 | fb_set_suspend(jzfb->fb, 1); |
1093 | console_unlock(); |
1094 | |
1095 | mutex_lock(&jzfb->lock); |
1096 | if (jzfb->is_enabled) |
1097 | jzfb_disable(jzfb); |
1098 | mutex_unlock(&jzfb->lock); |
1099 | |
1100 | return 0; |
1101 | } |
1102 | |
1103 | static int jzfb_resume(struct device *dev) |
1104 | { |
1105 | struct jzfb *jzfb = dev_get_drvdata(dev); |
1106 | |
1107 | mutex_lock(&jzfb->lock); |
1108 | if (jzfb->is_enabled) |
1109 | jzfb_enable(jzfb); |
1110 | mutex_unlock(&jzfb->lock); |
1111 | |
1112 | console_lock(); |
1113 | fb_set_suspend(jzfb->fb, 0); |
1114 | console_unlock(); |
1115 | |
1116 | return 0; |
1117 | } |
1118 | |
1119 | static SIMPLE_DEV_PM_OPS(jzfb_pm_ops, jzfb_suspend, jzfb_resume); |
1120 | |
1121 | #define JZFB_PM_OPS (&jzfb_pm_ops) |
1122 | |
1123 | #else |
1124 | #define JZFB_PM_OPS NULL |
1125 | #endif |
1126 | |
1127 | static struct platform_driver jzfb_driver = { |
1128 | .probe = jzfb_probe, |
1129 | .remove = __devexit_p(jzfb_remove), |
1130 | .driver = { |
1131 | .name = "jz4740-fb", |
1132 | .pm = JZFB_PM_OPS, |
1133 | }, |
1134 | }; |
1135 | |
1136 | static int __init jzfb_init(void) |
1137 | { |
1138 | return platform_driver_register(&jzfb_driver); |
1139 | } |
1140 | module_init(jzfb_init); |
1141 | |
1142 | static void __exit jzfb_exit(void) |
1143 | { |
1144 | platform_driver_unregister(&jzfb_driver); |
1145 | } |
1146 | module_exit(jzfb_exit); |
1147 | |
1148 | MODULE_LICENSE("GPL"); |
1149 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>, Maarten ter Huurne <maarten@treewalker.org>"); |
1150 | MODULE_DESCRIPTION("JZ4740 SoC SLCD framebuffer driver"); |
1151 | MODULE_ALIAS("platform:jz4740-fb"); |
1152 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9