]>
Commit | Line | Data |
---|---|---|
a912e80b | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
7a92d545 LPC |
2 | /* |
3 | * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> | |
4 | * JZ4740 SoC LCD framebuffer driver | |
7a92d545 LPC |
5 | */ |
6 | ||
7 | #include <linux/kernel.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/mutex.h> | |
10 | #include <linux/platform_device.h> | |
695ff985 | 11 | #include <linux/pinctrl/consumer.h> |
7a92d545 LPC |
12 | |
13 | #include <linux/clk.h> | |
14 | #include <linux/delay.h> | |
15 | ||
16 | #include <linux/console.h> | |
17 | #include <linux/fb.h> | |
18 | ||
19 | #include <linux/dma-mapping.h> | |
20 | ||
21 | #include <asm/mach-jz4740/jz4740_fb.h> | |
7a92d545 LPC |
22 | |
23 | #define JZ_REG_LCD_CFG 0x00 | |
24 | #define JZ_REG_LCD_VSYNC 0x04 | |
25 | #define JZ_REG_LCD_HSYNC 0x08 | |
26 | #define JZ_REG_LCD_VAT 0x0C | |
27 | #define JZ_REG_LCD_DAH 0x10 | |
28 | #define JZ_REG_LCD_DAV 0x14 | |
29 | #define JZ_REG_LCD_PS 0x18 | |
30 | #define JZ_REG_LCD_CLS 0x1C | |
31 | #define JZ_REG_LCD_SPL 0x20 | |
32 | #define JZ_REG_LCD_REV 0x24 | |
33 | #define JZ_REG_LCD_CTRL 0x30 | |
34 | #define JZ_REG_LCD_STATE 0x34 | |
35 | #define JZ_REG_LCD_IID 0x38 | |
36 | #define JZ_REG_LCD_DA0 0x40 | |
37 | #define JZ_REG_LCD_SA0 0x44 | |
38 | #define JZ_REG_LCD_FID0 0x48 | |
39 | #define JZ_REG_LCD_CMD0 0x4C | |
40 | #define JZ_REG_LCD_DA1 0x50 | |
41 | #define JZ_REG_LCD_SA1 0x54 | |
42 | #define JZ_REG_LCD_FID1 0x58 | |
43 | #define JZ_REG_LCD_CMD1 0x5C | |
44 | ||
45 | #define JZ_LCD_CFG_SLCD BIT(31) | |
46 | #define JZ_LCD_CFG_PS_DISABLE BIT(23) | |
47 | #define JZ_LCD_CFG_CLS_DISABLE BIT(22) | |
48 | #define JZ_LCD_CFG_SPL_DISABLE BIT(21) | |
49 | #define JZ_LCD_CFG_REV_DISABLE BIT(20) | |
50 | #define JZ_LCD_CFG_HSYNCM BIT(19) | |
51 | #define JZ_LCD_CFG_PCLKM BIT(18) | |
52 | #define JZ_LCD_CFG_INV BIT(17) | |
53 | #define JZ_LCD_CFG_SYNC_DIR BIT(16) | |
54 | #define JZ_LCD_CFG_PS_POLARITY BIT(15) | |
55 | #define JZ_LCD_CFG_CLS_POLARITY BIT(14) | |
56 | #define JZ_LCD_CFG_SPL_POLARITY BIT(13) | |
57 | #define JZ_LCD_CFG_REV_POLARITY BIT(12) | |
58 | #define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11) | |
59 | #define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10) | |
60 | #define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9) | |
61 | #define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8) | |
62 | #define JZ_LCD_CFG_18_BIT BIT(7) | |
63 | #define JZ_LCD_CFG_PDW (BIT(5) | BIT(4)) | |
64 | #define JZ_LCD_CFG_MODE_MASK 0xf | |
65 | ||
66 | #define JZ_LCD_CTRL_BURST_4 (0x0 << 28) | |
67 | #define JZ_LCD_CTRL_BURST_8 (0x1 << 28) | |
68 | #define JZ_LCD_CTRL_BURST_16 (0x2 << 28) | |
69 | #define JZ_LCD_CTRL_RGB555 BIT(27) | |
70 | #define JZ_LCD_CTRL_OFUP BIT(26) | |
71 | #define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24) | |
72 | #define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24) | |
73 | #define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24) | |
74 | #define JZ_LCD_CTRL_PDD_MASK (0xff << 16) | |
75 | #define JZ_LCD_CTRL_EOF_IRQ BIT(13) | |
76 | #define JZ_LCD_CTRL_SOF_IRQ BIT(12) | |
77 | #define JZ_LCD_CTRL_OFU_IRQ BIT(11) | |
78 | #define JZ_LCD_CTRL_IFU0_IRQ BIT(10) | |
79 | #define JZ_LCD_CTRL_IFU1_IRQ BIT(9) | |
80 | #define JZ_LCD_CTRL_DD_IRQ BIT(8) | |
81 | #define JZ_LCD_CTRL_QDD_IRQ BIT(7) | |
82 | #define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6) | |
83 | #define JZ_LCD_CTRL_LSB_FISRT BIT(5) | |
84 | #define JZ_LCD_CTRL_DISABLE BIT(4) | |
85 | #define JZ_LCD_CTRL_ENABLE BIT(3) | |
86 | #define JZ_LCD_CTRL_BPP_1 0x0 | |
87 | #define JZ_LCD_CTRL_BPP_2 0x1 | |
88 | #define JZ_LCD_CTRL_BPP_4 0x2 | |
89 | #define JZ_LCD_CTRL_BPP_8 0x3 | |
90 | #define JZ_LCD_CTRL_BPP_15_16 0x4 | |
91 | #define JZ_LCD_CTRL_BPP_18_24 0x5 | |
92 | ||
8a519c43 LPC |
93 | #define JZ_LCD_CMD_SOF_IRQ BIT(31) |
94 | #define JZ_LCD_CMD_EOF_IRQ BIT(30) | |
95 | #define JZ_LCD_CMD_ENABLE_PAL BIT(28) | |
7a92d545 LPC |
96 | |
97 | #define JZ_LCD_SYNC_MASK 0x3ff | |
98 | ||
99 | #define JZ_LCD_STATE_DISABLED BIT(0) | |
100 | ||
101 | struct jzfb_framedesc { | |
102 | uint32_t next; | |
103 | uint32_t addr; | |
104 | uint32_t id; | |
105 | uint32_t cmd; | |
106 | } __packed; | |
107 | ||
108 | struct jzfb { | |
109 | struct fb_info *fb; | |
110 | struct platform_device *pdev; | |
111 | void __iomem *base; | |
112 | struct resource *mem; | |
113 | struct jz4740_fb_platform_data *pdata; | |
114 | ||
115 | size_t vidmem_size; | |
116 | void *vidmem; | |
117 | dma_addr_t vidmem_phys; | |
118 | struct jzfb_framedesc *framedesc; | |
119 | dma_addr_t framedesc_phys; | |
120 | ||
121 | struct clk *ldclk; | |
122 | struct clk *lpclk; | |
123 | ||
124 | unsigned is_enabled:1; | |
125 | struct mutex lock; | |
126 | ||
127 | uint32_t pseudo_palette[16]; | |
128 | }; | |
129 | ||
48c68c4f | 130 | static const struct fb_fix_screeninfo jzfb_fix = { |
7a92d545 LPC |
131 | .id = "JZ4740 FB", |
132 | .type = FB_TYPE_PACKED_PIXELS, | |
133 | .visual = FB_VISUAL_TRUECOLOR, | |
134 | .xpanstep = 0, | |
135 | .ypanstep = 0, | |
136 | .ywrapstep = 0, | |
137 | .accel = FB_ACCEL_NONE, | |
138 | }; | |
139 | ||
7a92d545 LPC |
140 | /* Based on CNVT_TOHW macro from skeletonfb.c */ |
141 | static inline uint32_t jzfb_convert_color_to_hw(unsigned val, | |
142 | struct fb_bitfield *bf) | |
143 | { | |
144 | return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset; | |
145 | } | |
146 | ||
147 | static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green, | |
148 | unsigned blue, unsigned transp, struct fb_info *fb) | |
149 | { | |
150 | uint32_t color; | |
151 | ||
152 | if (regno >= 16) | |
153 | return -EINVAL; | |
154 | ||
155 | color = jzfb_convert_color_to_hw(red, &fb->var.red); | |
156 | color |= jzfb_convert_color_to_hw(green, &fb->var.green); | |
157 | color |= jzfb_convert_color_to_hw(blue, &fb->var.blue); | |
158 | color |= jzfb_convert_color_to_hw(transp, &fb->var.transp); | |
159 | ||
160 | ((uint32_t *)(fb->pseudo_palette))[regno] = color; | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | static int jzfb_get_controller_bpp(struct jzfb *jzfb) | |
166 | { | |
167 | switch (jzfb->pdata->bpp) { | |
168 | case 18: | |
169 | case 24: | |
170 | return 32; | |
171 | case 15: | |
172 | return 16; | |
173 | default: | |
174 | return jzfb->pdata->bpp; | |
175 | } | |
176 | } | |
177 | ||
178 | static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb, | |
179 | struct fb_var_screeninfo *var) | |
180 | { | |
181 | size_t i; | |
182 | struct fb_videomode *mode = jzfb->pdata->modes; | |
183 | ||
184 | for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) { | |
185 | if (mode->xres == var->xres && mode->yres == var->yres) | |
186 | return mode; | |
187 | } | |
188 | ||
189 | return NULL; | |
190 | } | |
191 | ||
192 | static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb) | |
193 | { | |
194 | struct jzfb *jzfb = fb->par; | |
195 | struct fb_videomode *mode; | |
196 | ||
197 | if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) && | |
198 | var->bits_per_pixel != jzfb->pdata->bpp) | |
199 | return -EINVAL; | |
200 | ||
201 | mode = jzfb_get_mode(jzfb, var); | |
202 | if (mode == NULL) | |
203 | return -EINVAL; | |
204 | ||
205 | fb_videomode_to_var(var, mode); | |
206 | ||
207 | switch (jzfb->pdata->bpp) { | |
208 | case 8: | |
209 | break; | |
210 | case 15: | |
211 | var->red.offset = 10; | |
212 | var->red.length = 5; | |
213 | var->green.offset = 6; | |
214 | var->green.length = 5; | |
215 | var->blue.offset = 0; | |
216 | var->blue.length = 5; | |
217 | break; | |
218 | case 16: | |
219 | var->red.offset = 11; | |
220 | var->red.length = 5; | |
221 | var->green.offset = 5; | |
222 | var->green.length = 6; | |
223 | var->blue.offset = 0; | |
224 | var->blue.length = 5; | |
225 | break; | |
226 | case 18: | |
227 | var->red.offset = 16; | |
228 | var->red.length = 6; | |
229 | var->green.offset = 8; | |
230 | var->green.length = 6; | |
231 | var->blue.offset = 0; | |
232 | var->blue.length = 6; | |
233 | var->bits_per_pixel = 32; | |
234 | break; | |
235 | case 32: | |
236 | case 24: | |
237 | var->transp.offset = 24; | |
238 | var->transp.length = 8; | |
239 | var->red.offset = 16; | |
240 | var->red.length = 8; | |
241 | var->green.offset = 8; | |
242 | var->green.length = 8; | |
243 | var->blue.offset = 0; | |
244 | var->blue.length = 8; | |
245 | var->bits_per_pixel = 32; | |
246 | break; | |
247 | default: | |
248 | break; | |
249 | } | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | static int jzfb_set_par(struct fb_info *info) | |
255 | { | |
256 | struct jzfb *jzfb = info->par; | |
257 | struct jz4740_fb_platform_data *pdata = jzfb->pdata; | |
258 | struct fb_var_screeninfo *var = &info->var; | |
259 | struct fb_videomode *mode; | |
260 | uint16_t hds, vds; | |
261 | uint16_t hde, vde; | |
262 | uint16_t ht, vt; | |
263 | uint32_t ctrl; | |
264 | uint32_t cfg; | |
265 | unsigned long rate; | |
266 | ||
267 | mode = jzfb_get_mode(jzfb, var); | |
268 | if (mode == NULL) | |
269 | return -EINVAL; | |
270 | ||
271 | if (mode == info->mode) | |
272 | return 0; | |
273 | ||
274 | info->mode = mode; | |
275 | ||
276 | hds = mode->hsync_len + mode->left_margin; | |
277 | hde = hds + mode->xres; | |
278 | ht = hde + mode->right_margin; | |
279 | ||
280 | vds = mode->vsync_len + mode->upper_margin; | |
281 | vde = vds + mode->yres; | |
282 | vt = vde + mode->lower_margin; | |
283 | ||
284 | ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16; | |
285 | ||
286 | switch (pdata->bpp) { | |
287 | case 1: | |
288 | ctrl |= JZ_LCD_CTRL_BPP_1; | |
289 | break; | |
290 | case 2: | |
291 | ctrl |= JZ_LCD_CTRL_BPP_2; | |
292 | break; | |
293 | case 4: | |
294 | ctrl |= JZ_LCD_CTRL_BPP_4; | |
295 | break; | |
296 | case 8: | |
297 | ctrl |= JZ_LCD_CTRL_BPP_8; | |
298 | break; | |
299 | case 15: | |
300 | ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */ | |
301 | case 16: | |
302 | ctrl |= JZ_LCD_CTRL_BPP_15_16; | |
303 | break; | |
304 | case 18: | |
305 | case 24: | |
306 | case 32: | |
307 | ctrl |= JZ_LCD_CTRL_BPP_18_24; | |
308 | break; | |
309 | default: | |
310 | break; | |
311 | } | |
312 | ||
313 | cfg = pdata->lcd_type & 0xf; | |
314 | ||
315 | if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) | |
316 | cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW; | |
317 | ||
318 | if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) | |
319 | cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW; | |
320 | ||
321 | if (pdata->pixclk_falling_edge) | |
322 | cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE; | |
323 | ||
324 | if (pdata->date_enable_active_low) | |
325 | cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW; | |
326 | ||
327 | if (pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT) | |
328 | cfg |= JZ_LCD_CFG_18_BIT; | |
329 | ||
330 | if (mode->pixclock) { | |
331 | rate = PICOS2KHZ(mode->pixclock) * 1000; | |
332 | mode->refresh = rate / vt / ht; | |
333 | } else { | |
334 | if (pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL) | |
335 | rate = mode->refresh * (vt + 2 * mode->xres) * ht; | |
336 | else | |
337 | rate = mode->refresh * vt * ht; | |
338 | ||
339 | mode->pixclock = KHZ2PICOS(rate / 1000); | |
340 | } | |
341 | ||
342 | mutex_lock(&jzfb->lock); | |
343 | if (!jzfb->is_enabled) | |
344 | clk_enable(jzfb->ldclk); | |
345 | else | |
346 | ctrl |= JZ_LCD_CTRL_ENABLE; | |
347 | ||
348 | switch (pdata->lcd_type) { | |
349 | case JZ_LCD_TYPE_SPECIAL_TFT_1: | |
350 | case JZ_LCD_TYPE_SPECIAL_TFT_2: | |
351 | case JZ_LCD_TYPE_SPECIAL_TFT_3: | |
352 | writel(pdata->special_tft_config.spl, jzfb->base + JZ_REG_LCD_SPL); | |
353 | writel(pdata->special_tft_config.cls, jzfb->base + JZ_REG_LCD_CLS); | |
354 | writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_PS); | |
355 | writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_REV); | |
356 | break; | |
357 | default: | |
358 | cfg |= JZ_LCD_CFG_PS_DISABLE; | |
359 | cfg |= JZ_LCD_CFG_CLS_DISABLE; | |
360 | cfg |= JZ_LCD_CFG_SPL_DISABLE; | |
361 | cfg |= JZ_LCD_CFG_REV_DISABLE; | |
362 | break; | |
363 | } | |
364 | ||
365 | writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC); | |
366 | writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC); | |
367 | ||
368 | writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT); | |
369 | ||
370 | writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH); | |
371 | writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV); | |
372 | ||
373 | writel(cfg, jzfb->base + JZ_REG_LCD_CFG); | |
374 | ||
375 | writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); | |
376 | ||
377 | if (!jzfb->is_enabled) | |
aeac9e3f | 378 | clk_disable_unprepare(jzfb->ldclk); |
7a92d545 LPC |
379 | |
380 | mutex_unlock(&jzfb->lock); | |
381 | ||
382 | clk_set_rate(jzfb->lpclk, rate); | |
383 | clk_set_rate(jzfb->ldclk, rate * 3); | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
388 | static void jzfb_enable(struct jzfb *jzfb) | |
389 | { | |
390 | uint32_t ctrl; | |
391 | ||
aeac9e3f | 392 | clk_prepare_enable(jzfb->ldclk); |
7a92d545 | 393 | |
695ff985 | 394 | pinctrl_pm_select_default_state(&jzfb->pdev->dev); |
7a92d545 LPC |
395 | |
396 | writel(0, jzfb->base + JZ_REG_LCD_STATE); | |
397 | ||
398 | writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); | |
399 | ||
400 | ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); | |
401 | ctrl |= JZ_LCD_CTRL_ENABLE; | |
402 | ctrl &= ~JZ_LCD_CTRL_DISABLE; | |
403 | writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); | |
404 | } | |
405 | ||
406 | static void jzfb_disable(struct jzfb *jzfb) | |
407 | { | |
408 | uint32_t ctrl; | |
409 | ||
410 | ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL); | |
411 | ctrl |= JZ_LCD_CTRL_DISABLE; | |
412 | writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL); | |
413 | do { | |
414 | ctrl = readl(jzfb->base + JZ_REG_LCD_STATE); | |
415 | } while (!(ctrl & JZ_LCD_STATE_DISABLED)); | |
416 | ||
695ff985 | 417 | pinctrl_pm_select_sleep_state(&jzfb->pdev->dev); |
7a92d545 | 418 | |
aeac9e3f | 419 | clk_disable_unprepare(jzfb->ldclk); |
7a92d545 LPC |
420 | } |
421 | ||
422 | static int jzfb_blank(int blank_mode, struct fb_info *info) | |
423 | { | |
424 | struct jzfb *jzfb = info->par; | |
425 | ||
426 | switch (blank_mode) { | |
427 | case FB_BLANK_UNBLANK: | |
428 | mutex_lock(&jzfb->lock); | |
429 | if (jzfb->is_enabled) { | |
430 | mutex_unlock(&jzfb->lock); | |
431 | return 0; | |
432 | } | |
433 | ||
434 | jzfb_enable(jzfb); | |
435 | jzfb->is_enabled = 1; | |
436 | ||
437 | mutex_unlock(&jzfb->lock); | |
438 | break; | |
439 | default: | |
440 | mutex_lock(&jzfb->lock); | |
441 | if (!jzfb->is_enabled) { | |
442 | mutex_unlock(&jzfb->lock); | |
443 | return 0; | |
444 | } | |
445 | ||
446 | jzfb_disable(jzfb); | |
447 | jzfb->is_enabled = 0; | |
448 | ||
449 | mutex_unlock(&jzfb->lock); | |
450 | break; | |
451 | } | |
452 | ||
453 | return 0; | |
454 | } | |
455 | ||
456 | static int jzfb_alloc_devmem(struct jzfb *jzfb) | |
457 | { | |
458 | int max_videosize = 0; | |
459 | struct fb_videomode *mode = jzfb->pdata->modes; | |
460 | void *page; | |
461 | int i; | |
462 | ||
463 | for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) { | |
464 | if (max_videosize < mode->xres * mode->yres) | |
465 | max_videosize = mode->xres * mode->yres; | |
466 | } | |
467 | ||
468 | max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3; | |
469 | ||
470 | jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev, | |
471 | sizeof(*jzfb->framedesc), | |
472 | &jzfb->framedesc_phys, GFP_KERNEL); | |
473 | ||
474 | if (!jzfb->framedesc) | |
475 | return -ENOMEM; | |
476 | ||
477 | jzfb->vidmem_size = PAGE_ALIGN(max_videosize); | |
478 | jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev, | |
479 | jzfb->vidmem_size, | |
480 | &jzfb->vidmem_phys, GFP_KERNEL); | |
481 | ||
482 | if (!jzfb->vidmem) | |
483 | goto err_free_framedesc; | |
484 | ||
485 | for (page = jzfb->vidmem; | |
486 | page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size); | |
487 | page += PAGE_SIZE) { | |
488 | SetPageReserved(virt_to_page(page)); | |
489 | } | |
490 | ||
491 | jzfb->framedesc->next = jzfb->framedesc_phys; | |
492 | jzfb->framedesc->addr = jzfb->vidmem_phys; | |
493 | jzfb->framedesc->id = 0xdeafbead; | |
494 | jzfb->framedesc->cmd = 0; | |
495 | jzfb->framedesc->cmd |= max_videosize / 4; | |
496 | ||
497 | return 0; | |
498 | ||
499 | err_free_framedesc: | |
500 | dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), | |
501 | jzfb->framedesc, jzfb->framedesc_phys); | |
502 | return -ENOMEM; | |
503 | } | |
504 | ||
505 | static void jzfb_free_devmem(struct jzfb *jzfb) | |
506 | { | |
507 | dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size, | |
508 | jzfb->vidmem, jzfb->vidmem_phys); | |
509 | dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc), | |
510 | jzfb->framedesc, jzfb->framedesc_phys); | |
511 | } | |
512 | ||
513 | static struct fb_ops jzfb_ops = { | |
514 | .owner = THIS_MODULE, | |
515 | .fb_check_var = jzfb_check_var, | |
516 | .fb_set_par = jzfb_set_par, | |
517 | .fb_blank = jzfb_blank, | |
518 | .fb_fillrect = sys_fillrect, | |
519 | .fb_copyarea = sys_copyarea, | |
520 | .fb_imageblit = sys_imageblit, | |
521 | .fb_setcolreg = jzfb_setcolreg, | |
522 | }; | |
523 | ||
48c68c4f | 524 | static int jzfb_probe(struct platform_device *pdev) |
7a92d545 LPC |
525 | { |
526 | int ret; | |
527 | struct jzfb *jzfb; | |
528 | struct fb_info *fb; | |
529 | struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data; | |
530 | struct resource *mem; | |
531 | ||
532 | if (!pdata) { | |
533 | dev_err(&pdev->dev, "Missing platform data\n"); | |
534 | return -ENXIO; | |
535 | } | |
536 | ||
7a92d545 LPC |
537 | fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev); |
538 | if (!fb) { | |
539 | dev_err(&pdev->dev, "Failed to allocate framebuffer device\n"); | |
b5d4190f | 540 | return -ENOMEM; |
7a92d545 LPC |
541 | } |
542 | ||
543 | fb->fbops = &jzfb_ops; | |
544 | fb->flags = FBINFO_DEFAULT; | |
545 | ||
546 | jzfb = fb->par; | |
547 | jzfb->pdev = pdev; | |
548 | jzfb->pdata = pdata; | |
7a92d545 | 549 | |
b2ca7f4d | 550 | jzfb->ldclk = devm_clk_get(&pdev->dev, "lcd"); |
7a92d545 LPC |
551 | if (IS_ERR(jzfb->ldclk)) { |
552 | ret = PTR_ERR(jzfb->ldclk); | |
553 | dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret); | |
554 | goto err_framebuffer_release; | |
555 | } | |
556 | ||
b2ca7f4d | 557 | jzfb->lpclk = devm_clk_get(&pdev->dev, "lcd_pclk"); |
7a92d545 LPC |
558 | if (IS_ERR(jzfb->lpclk)) { |
559 | ret = PTR_ERR(jzfb->lpclk); | |
560 | dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret); | |
b2ca7f4d | 561 | goto err_framebuffer_release; |
7a92d545 LPC |
562 | } |
563 | ||
b5d4190f | 564 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
bc3bad16 TR |
565 | jzfb->base = devm_ioremap_resource(&pdev->dev, mem); |
566 | if (IS_ERR(jzfb->base)) { | |
567 | ret = PTR_ERR(jzfb->base); | |
b2ca7f4d | 568 | goto err_framebuffer_release; |
7a92d545 LPC |
569 | } |
570 | ||
571 | platform_set_drvdata(pdev, jzfb); | |
572 | ||
573 | mutex_init(&jzfb->lock); | |
574 | ||
575 | fb_videomode_to_modelist(pdata->modes, pdata->num_modes, | |
576 | &fb->modelist); | |
577 | fb_videomode_to_var(&fb->var, pdata->modes); | |
578 | fb->var.bits_per_pixel = pdata->bpp; | |
579 | jzfb_check_var(&fb->var, fb); | |
580 | ||
581 | ret = jzfb_alloc_devmem(jzfb); | |
582 | if (ret) { | |
583 | dev_err(&pdev->dev, "Failed to allocate video memory\n"); | |
b2ca7f4d | 584 | goto err_framebuffer_release; |
7a92d545 LPC |
585 | } |
586 | ||
587 | fb->fix = jzfb_fix; | |
588 | fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8; | |
589 | fb->fix.mmio_start = mem->start; | |
590 | fb->fix.mmio_len = resource_size(mem); | |
591 | fb->fix.smem_start = jzfb->vidmem_phys; | |
592 | fb->fix.smem_len = fb->fix.line_length * fb->var.yres; | |
593 | fb->screen_base = jzfb->vidmem; | |
594 | fb->pseudo_palette = jzfb->pseudo_palette; | |
595 | ||
596 | fb_alloc_cmap(&fb->cmap, 256, 0); | |
597 | ||
aeac9e3f | 598 | clk_prepare_enable(jzfb->ldclk); |
7a92d545 LPC |
599 | jzfb->is_enabled = 1; |
600 | ||
601 | writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0); | |
602 | ||
603 | fb->mode = NULL; | |
604 | jzfb_set_par(fb); | |
605 | ||
7a92d545 LPC |
606 | ret = register_framebuffer(fb); |
607 | if (ret) { | |
608 | dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret); | |
609 | goto err_free_devmem; | |
610 | } | |
611 | ||
612 | jzfb->fb = fb; | |
613 | ||
614 | return 0; | |
615 | ||
616 | err_free_devmem: | |
7a92d545 LPC |
617 | fb_dealloc_cmap(&fb->cmap); |
618 | jzfb_free_devmem(jzfb); | |
7a92d545 LPC |
619 | err_framebuffer_release: |
620 | framebuffer_release(fb); | |
7a92d545 LPC |
621 | return ret; |
622 | } | |
623 | ||
48c68c4f | 624 | static int jzfb_remove(struct platform_device *pdev) |
7a92d545 LPC |
625 | { |
626 | struct jzfb *jzfb = platform_get_drvdata(pdev); | |
627 | ||
628 | jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb); | |
629 | ||
7a92d545 LPC |
630 | fb_dealloc_cmap(&jzfb->fb->cmap); |
631 | jzfb_free_devmem(jzfb); | |
632 | ||
7a92d545 LPC |
633 | framebuffer_release(jzfb->fb); |
634 | ||
635 | return 0; | |
636 | } | |
637 | ||
638 | #ifdef CONFIG_PM | |
639 | ||
640 | static int jzfb_suspend(struct device *dev) | |
641 | { | |
642 | struct jzfb *jzfb = dev_get_drvdata(dev); | |
643 | ||
ac751efa | 644 | console_lock(); |
7a92d545 | 645 | fb_set_suspend(jzfb->fb, 1); |
ac751efa | 646 | console_unlock(); |
7a92d545 LPC |
647 | |
648 | mutex_lock(&jzfb->lock); | |
649 | if (jzfb->is_enabled) | |
650 | jzfb_disable(jzfb); | |
651 | mutex_unlock(&jzfb->lock); | |
652 | ||
653 | return 0; | |
654 | } | |
655 | ||
656 | static int jzfb_resume(struct device *dev) | |
657 | { | |
658 | struct jzfb *jzfb = dev_get_drvdata(dev); | |
aeac9e3f | 659 | clk_prepare_enable(jzfb->ldclk); |
7a92d545 LPC |
660 | |
661 | mutex_lock(&jzfb->lock); | |
662 | if (jzfb->is_enabled) | |
663 | jzfb_enable(jzfb); | |
664 | mutex_unlock(&jzfb->lock); | |
665 | ||
ac751efa | 666 | console_lock(); |
7a92d545 | 667 | fb_set_suspend(jzfb->fb, 0); |
ac751efa | 668 | console_unlock(); |
7a92d545 LPC |
669 | |
670 | return 0; | |
671 | } | |
672 | ||
673 | static const struct dev_pm_ops jzfb_pm_ops = { | |
674 | .suspend = jzfb_suspend, | |
675 | .resume = jzfb_resume, | |
676 | .poweroff = jzfb_suspend, | |
677 | .restore = jzfb_resume, | |
678 | }; | |
679 | ||
680 | #define JZFB_PM_OPS (&jzfb_pm_ops) | |
681 | ||
682 | #else | |
683 | #define JZFB_PM_OPS NULL | |
684 | #endif | |
685 | ||
686 | static struct platform_driver jzfb_driver = { | |
687 | .probe = jzfb_probe, | |
48c68c4f | 688 | .remove = jzfb_remove, |
7a92d545 LPC |
689 | .driver = { |
690 | .name = "jz4740-fb", | |
691 | .pm = JZFB_PM_OPS, | |
692 | }, | |
693 | }; | |
5fc89379 | 694 | module_platform_driver(jzfb_driver); |
7a92d545 LPC |
695 | |
696 | MODULE_LICENSE("GPL"); | |
697 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
698 | MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver"); | |
699 | MODULE_ALIAS("platform:jz4740-fb"); |