1 | /* |
2 | * Xilinx TFT frame buffer driver |
3 | * |
4 | * Author: MontaVista Software, Inc. |
5 | * source@mvista.com |
6 | * |
7 | * 2002-2007 (c) MontaVista Software, Inc. |
8 | * 2007 (c) Secret Lab Technologies, Ltd. |
9 | * 2009 (c) Xilinx Inc. |
10 | * |
11 | * This file is licensed under the terms of the GNU General Public License |
12 | * version 2. This program is licensed "as is" without any warranty of any |
13 | * kind, whether express or implied. |
14 | */ |
15 | |
16 | /* |
17 | * This driver was based on au1100fb.c by MontaVista rewritten for 2.6 |
18 | * by Embedded Alley Solutions <source@embeddedalley.com>, which in turn |
19 | * was based on skeletonfb.c, Skeleton for a frame buffer device by |
20 | * Geert Uytterhoeven. |
21 | */ |
22 | |
23 | #include <linux/device.h> |
24 | #include <linux/module.h> |
25 | #include <linux/kernel.h> |
26 | #include <linux/errno.h> |
27 | #include <linux/platform_device.h> |
28 | #include <linux/string.h> |
29 | #include <linux/mm.h> |
30 | #include <linux/fb.h> |
31 | #include <linux/init.h> |
32 | #include <linux/dma-mapping.h> |
33 | #include <linux/of.h> |
34 | #include <linux/io.h> |
35 | #include <linux/slab.h> |
36 | |
37 | #ifdef CONFIG_PPC_DCR |
38 | #include <asm/dcr.h> |
39 | #endif |
40 | |
41 | #define DRIVER_NAME "xilinxfb" |
42 | |
43 | /* |
44 | * Xilinx calls it "TFT LCD Controller" though it can also be used for |
45 | * the VGA port on the Xilinx ML40x board. This is a hardware display |
46 | * controller for a 640x480 resolution TFT or VGA screen. |
47 | * |
48 | * The interface to the framebuffer is nice and simple. There are two |
49 | * control registers. The first tells the LCD interface where in memory |
50 | * the frame buffer is (only the 11 most significant bits are used, so |
51 | * don't start thinking about scrolling). The second allows the LCD to |
52 | * be turned on or off as well as rotated 180 degrees. |
53 | * |
54 | * In case of direct BUS access the second control register will be at |
55 | * an offset of 4 as compared to the DCR access where the offset is 1 |
56 | * i.e. REG_CTRL. So this is taken care in the function |
57 | * xilinx_fb_out32 where it left shifts the offset 2 times in case of |
58 | * direct BUS access. |
59 | */ |
60 | #define NUM_REGS 2 |
61 | #define REG_FB_ADDR 0 |
62 | #define REG_CTRL 1 |
63 | #define REG_CTRL_ENABLE 0x0001 |
64 | #define REG_CTRL_ROTATE 0x0002 |
65 | |
66 | /* |
67 | * The hardware only handles a single mode: 640x480 24 bit true |
68 | * color. Each pixel gets a word (32 bits) of memory. Within each word, |
69 | * the 8 most significant bits are ignored, the next 8 bits are the red |
70 | * level, the next 8 bits are the green level and the 8 least |
71 | * significant bits are the blue level. Each row of the LCD uses 1024 |
72 | * words, but only the first 640 pixels are displayed with the other 384 |
73 | * words being ignored. There are 480 rows. |
74 | */ |
75 | #define BYTES_PER_PIXEL 4 |
76 | #define BITS_PER_PIXEL (BYTES_PER_PIXEL * 8) |
77 | |
78 | #define RED_SHIFT 16 |
79 | #define GREEN_SHIFT 8 |
80 | #define BLUE_SHIFT 0 |
81 | |
82 | #define PALETTE_ENTRIES_NO 16 /* passed to fb_alloc_cmap() */ |
83 | |
84 | /* ML300/403 reference design framebuffer driver platform data struct */ |
85 | struct xilinxfb_platform_data { |
86 | u32 rotate_screen; /* Flag to rotate display 180 degrees */ |
87 | u32 screen_height_mm; /* Physical dimensions of screen in mm */ |
88 | u32 screen_width_mm; |
89 | u32 xres, yres; /* resolution of screen in pixels */ |
90 | u32 xvirt, yvirt; /* resolution of memory buffer */ |
91 | |
92 | /* Physical address of framebuffer memory; If non-zero, driver |
93 | * will use provided memory address instead of allocating one from |
94 | * the consistent pool. |
95 | */ |
96 | u32 fb_phys; |
97 | }; |
98 | |
99 | /* |
100 | * Default xilinxfb configuration |
101 | */ |
102 | static const struct xilinxfb_platform_data xilinx_fb_default_pdata = { |
103 | .xres = 640, |
104 | .yres = 480, |
105 | .xvirt = 1024, |
106 | .yvirt = 480, |
107 | }; |
108 | |
109 | /* |
110 | * Here are the default fb_fix_screeninfo and fb_var_screeninfo structures |
111 | */ |
112 | static const struct fb_fix_screeninfo xilinx_fb_fix = { |
113 | .id = "Xilinx" , |
114 | .type = FB_TYPE_PACKED_PIXELS, |
115 | .visual = FB_VISUAL_TRUECOLOR, |
116 | .accel = FB_ACCEL_NONE |
117 | }; |
118 | |
119 | static const struct fb_var_screeninfo xilinx_fb_var = { |
120 | .bits_per_pixel = BITS_PER_PIXEL, |
121 | |
122 | .red = { RED_SHIFT, 8, 0 }, |
123 | .green = { GREEN_SHIFT, 8, 0 }, |
124 | .blue = { BLUE_SHIFT, 8, 0 }, |
125 | .transp = { 0, 0, 0 }, |
126 | |
127 | .activate = FB_ACTIVATE_NOW |
128 | }; |
129 | |
130 | #define BUS_ACCESS_FLAG 0x1 /* 1 = BUS, 0 = DCR */ |
131 | #define LITTLE_ENDIAN_ACCESS 0x2 /* LITTLE ENDIAN IO functions */ |
132 | |
133 | struct xilinxfb_drvdata { |
134 | struct fb_info info; /* FB driver info record */ |
135 | |
136 | phys_addr_t regs_phys; /* phys. address of the control |
137 | * registers |
138 | */ |
139 | void __iomem *regs; /* virt. address of the control |
140 | * registers |
141 | */ |
142 | #ifdef CONFIG_PPC_DCR |
143 | dcr_host_t dcr_host; |
144 | unsigned int dcr_len; |
145 | #endif |
146 | void *fb_virt; /* virt. address of the frame buffer */ |
147 | dma_addr_t fb_phys; /* phys. address of the frame buffer */ |
148 | int fb_alloced; /* Flag, was the fb memory alloced? */ |
149 | |
150 | u8 flags; /* features of the driver */ |
151 | |
152 | u32 reg_ctrl_default; |
153 | |
154 | u32 pseudo_palette[PALETTE_ENTRIES_NO]; |
155 | /* Fake palette of 16 colors */ |
156 | }; |
157 | |
158 | #define to_xilinxfb_drvdata(_info) \ |
159 | container_of(_info, struct xilinxfb_drvdata, info) |
160 | |
161 | /* |
162 | * The XPS TFT Controller can be accessed through BUS or DCR interface. |
163 | * To perform the read/write on the registers we need to check on |
164 | * which bus its connected and call the appropriate write API. |
165 | */ |
166 | static void xilinx_fb_out32(struct xilinxfb_drvdata *drvdata, u32 offset, |
167 | u32 val) |
168 | { |
169 | if (drvdata->flags & BUS_ACCESS_FLAG) { |
170 | if (drvdata->flags & LITTLE_ENDIAN_ACCESS) |
171 | iowrite32(val, drvdata->regs + (offset << 2)); |
172 | else |
173 | iowrite32be(val, drvdata->regs + (offset << 2)); |
174 | } |
175 | #ifdef CONFIG_PPC_DCR |
176 | else |
177 | dcr_write(drvdata->dcr_host, offset, val); |
178 | #endif |
179 | } |
180 | |
181 | static u32 xilinx_fb_in32(struct xilinxfb_drvdata *drvdata, u32 offset) |
182 | { |
183 | if (drvdata->flags & BUS_ACCESS_FLAG) { |
184 | if (drvdata->flags & LITTLE_ENDIAN_ACCESS) |
185 | return ioread32(drvdata->regs + (offset << 2)); |
186 | else |
187 | return ioread32be(drvdata->regs + (offset << 2)); |
188 | } |
189 | #ifdef CONFIG_PPC_DCR |
190 | else |
191 | return dcr_read(drvdata->dcr_host, offset); |
192 | #endif |
193 | return 0; |
194 | } |
195 | |
196 | static int |
197 | xilinx_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, |
198 | unsigned int blue, unsigned int transp, struct fb_info *fbi) |
199 | { |
200 | u32 *palette = fbi->pseudo_palette; |
201 | |
202 | if (regno >= PALETTE_ENTRIES_NO) |
203 | return -EINVAL; |
204 | |
205 | if (fbi->var.grayscale) { |
206 | /* Convert color to grayscale. |
207 | * grayscale = 0.30*R + 0.59*G + 0.11*B |
208 | */ |
209 | blue = (red * 77 + green * 151 + blue * 28 + 127) >> 8; |
210 | green = blue; |
211 | red = green; |
212 | } |
213 | |
214 | /* fbi->fix.visual is always FB_VISUAL_TRUECOLOR */ |
215 | |
216 | /* We only handle 8 bits of each color. */ |
217 | red >>= 8; |
218 | green >>= 8; |
219 | blue >>= 8; |
220 | palette[regno] = (red << RED_SHIFT) | (green << GREEN_SHIFT) | |
221 | (blue << BLUE_SHIFT); |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static int |
227 | xilinx_fb_blank(int blank_mode, struct fb_info *fbi) |
228 | { |
229 | struct xilinxfb_drvdata *drvdata = to_xilinxfb_drvdata(fbi); |
230 | |
231 | switch (blank_mode) { |
232 | case FB_BLANK_UNBLANK: |
233 | /* turn on panel */ |
234 | xilinx_fb_out32(drvdata, REG_CTRL, val: drvdata->reg_ctrl_default); |
235 | break; |
236 | |
237 | case FB_BLANK_NORMAL: |
238 | case FB_BLANK_VSYNC_SUSPEND: |
239 | case FB_BLANK_HSYNC_SUSPEND: |
240 | case FB_BLANK_POWERDOWN: |
241 | /* turn off panel */ |
242 | xilinx_fb_out32(drvdata, REG_CTRL, val: 0); |
243 | break; |
244 | |
245 | default: |
246 | break; |
247 | } |
248 | return 0; /* success */ |
249 | } |
250 | |
251 | static const struct fb_ops xilinxfb_ops = { |
252 | .owner = THIS_MODULE, |
253 | FB_DEFAULT_IOMEM_OPS, |
254 | .fb_setcolreg = xilinx_fb_setcolreg, |
255 | .fb_blank = xilinx_fb_blank, |
256 | }; |
257 | |
258 | /* --------------------------------------------------------------------- |
259 | * Bus independent setup/teardown |
260 | */ |
261 | |
262 | static int xilinxfb_assign(struct platform_device *pdev, |
263 | struct xilinxfb_drvdata *drvdata, |
264 | struct xilinxfb_platform_data *pdata) |
265 | { |
266 | int rc; |
267 | struct device *dev = &pdev->dev; |
268 | int fbsize = pdata->xvirt * pdata->yvirt * BYTES_PER_PIXEL; |
269 | |
270 | if (drvdata->flags & BUS_ACCESS_FLAG) { |
271 | struct resource *res; |
272 | |
273 | drvdata->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
274 | if (IS_ERR(ptr: drvdata->regs)) |
275 | return PTR_ERR(ptr: drvdata->regs); |
276 | |
277 | drvdata->regs_phys = res->start; |
278 | } |
279 | |
280 | /* Allocate the framebuffer memory */ |
281 | if (pdata->fb_phys) { |
282 | drvdata->fb_phys = pdata->fb_phys; |
283 | drvdata->fb_virt = ioremap(offset: pdata->fb_phys, size: fbsize); |
284 | } else { |
285 | drvdata->fb_alloced = 1; |
286 | drvdata->fb_virt = dma_alloc_coherent(dev, PAGE_ALIGN(fbsize), |
287 | dma_handle: &drvdata->fb_phys, |
288 | GFP_KERNEL); |
289 | } |
290 | |
291 | if (!drvdata->fb_virt) { |
292 | dev_err(dev, "Could not allocate frame buffer memory\n" ); |
293 | return -ENOMEM; |
294 | } |
295 | |
296 | /* Clear (turn to black) the framebuffer */ |
297 | memset_io((void __iomem *)drvdata->fb_virt, 0, fbsize); |
298 | |
299 | /* Tell the hardware where the frame buffer is */ |
300 | xilinx_fb_out32(drvdata, REG_FB_ADDR, val: drvdata->fb_phys); |
301 | rc = xilinx_fb_in32(drvdata, REG_FB_ADDR); |
302 | /* Endianness detection */ |
303 | if (rc != drvdata->fb_phys) { |
304 | drvdata->flags |= LITTLE_ENDIAN_ACCESS; |
305 | xilinx_fb_out32(drvdata, REG_FB_ADDR, val: drvdata->fb_phys); |
306 | } |
307 | |
308 | /* Turn on the display */ |
309 | drvdata->reg_ctrl_default = REG_CTRL_ENABLE; |
310 | if (pdata->rotate_screen) |
311 | drvdata->reg_ctrl_default |= REG_CTRL_ROTATE; |
312 | xilinx_fb_out32(drvdata, REG_CTRL, val: drvdata->reg_ctrl_default); |
313 | |
314 | /* Fill struct fb_info */ |
315 | drvdata->info.device = dev; |
316 | drvdata->info.screen_base = (void __iomem *)drvdata->fb_virt; |
317 | drvdata->info.fbops = &xilinxfb_ops; |
318 | drvdata->info.fix = xilinx_fb_fix; |
319 | drvdata->info.fix.smem_start = drvdata->fb_phys; |
320 | drvdata->info.fix.smem_len = fbsize; |
321 | drvdata->info.fix.line_length = pdata->xvirt * BYTES_PER_PIXEL; |
322 | |
323 | drvdata->info.pseudo_palette = drvdata->pseudo_palette; |
324 | drvdata->info.var = xilinx_fb_var; |
325 | drvdata->info.var.height = pdata->screen_height_mm; |
326 | drvdata->info.var.width = pdata->screen_width_mm; |
327 | drvdata->info.var.xres = pdata->xres; |
328 | drvdata->info.var.yres = pdata->yres; |
329 | drvdata->info.var.xres_virtual = pdata->xvirt; |
330 | drvdata->info.var.yres_virtual = pdata->yvirt; |
331 | |
332 | /* Allocate a colour map */ |
333 | rc = fb_alloc_cmap(cmap: &drvdata->info.cmap, PALETTE_ENTRIES_NO, transp: 0); |
334 | if (rc) { |
335 | dev_err(dev, "Fail to allocate colormap (%d entries)\n" , |
336 | PALETTE_ENTRIES_NO); |
337 | goto err_cmap; |
338 | } |
339 | |
340 | /* Register new frame buffer */ |
341 | rc = register_framebuffer(fb_info: &drvdata->info); |
342 | if (rc) { |
343 | dev_err(dev, "Could not register frame buffer\n" ); |
344 | goto err_regfb; |
345 | } |
346 | |
347 | if (drvdata->flags & BUS_ACCESS_FLAG) { |
348 | /* Put a banner in the log (for DEBUG) */ |
349 | dev_dbg(dev, "regs: phys=%pa, virt=%p\n" , |
350 | &drvdata->regs_phys, drvdata->regs); |
351 | } |
352 | /* Put a banner in the log (for DEBUG) */ |
353 | dev_dbg(dev, "fb: phys=%llx, virt=%p, size=%x\n" , |
354 | (unsigned long long)drvdata->fb_phys, drvdata->fb_virt, fbsize); |
355 | |
356 | return 0; /* success */ |
357 | |
358 | err_regfb: |
359 | fb_dealloc_cmap(cmap: &drvdata->info.cmap); |
360 | |
361 | err_cmap: |
362 | if (drvdata->fb_alloced) |
363 | dma_free_coherent(dev, PAGE_ALIGN(fbsize), cpu_addr: drvdata->fb_virt, |
364 | dma_handle: drvdata->fb_phys); |
365 | else |
366 | iounmap(addr: drvdata->fb_virt); |
367 | |
368 | /* Turn off the display */ |
369 | xilinx_fb_out32(drvdata, REG_CTRL, val: 0); |
370 | |
371 | return rc; |
372 | } |
373 | |
374 | static void xilinxfb_release(struct device *dev) |
375 | { |
376 | struct xilinxfb_drvdata *drvdata = dev_get_drvdata(dev); |
377 | |
378 | #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) |
379 | xilinx_fb_blank(VESA_POWERDOWN, &drvdata->info); |
380 | #endif |
381 | |
382 | unregister_framebuffer(fb_info: &drvdata->info); |
383 | |
384 | fb_dealloc_cmap(cmap: &drvdata->info.cmap); |
385 | |
386 | if (drvdata->fb_alloced) |
387 | dma_free_coherent(dev, PAGE_ALIGN(drvdata->info.fix.smem_len), |
388 | cpu_addr: drvdata->fb_virt, dma_handle: drvdata->fb_phys); |
389 | else |
390 | iounmap(addr: drvdata->fb_virt); |
391 | |
392 | /* Turn off the display */ |
393 | xilinx_fb_out32(drvdata, REG_CTRL, val: 0); |
394 | |
395 | #ifdef CONFIG_PPC_DCR |
396 | /* Release the resources, as allocated based on interface */ |
397 | if (!(drvdata->flags & BUS_ACCESS_FLAG)) |
398 | dcr_unmap(drvdata->dcr_host, drvdata->dcr_len); |
399 | #endif |
400 | } |
401 | |
402 | /* --------------------------------------------------------------------- |
403 | * OF bus binding |
404 | */ |
405 | |
406 | static int xilinxfb_of_probe(struct platform_device *pdev) |
407 | { |
408 | const u32 *prop; |
409 | u32 tft_access = 0; |
410 | struct xilinxfb_platform_data pdata; |
411 | int size; |
412 | struct xilinxfb_drvdata *drvdata; |
413 | |
414 | /* Copy with the default pdata (not a ptr reference!) */ |
415 | pdata = xilinx_fb_default_pdata; |
416 | |
417 | /* Allocate the driver data region */ |
418 | drvdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*drvdata), GFP_KERNEL); |
419 | if (!drvdata) |
420 | return -ENOMEM; |
421 | |
422 | /* |
423 | * To check whether the core is connected directly to DCR or BUS |
424 | * interface and initialize the tft_access accordingly. |
425 | */ |
426 | of_property_read_u32(np: pdev->dev.of_node, propname: "xlnx,dcr-splb-slave-if" , |
427 | out_value: &tft_access); |
428 | |
429 | /* |
430 | * Fill the resource structure if its direct BUS interface |
431 | * otherwise fill the dcr_host structure. |
432 | */ |
433 | if (tft_access) |
434 | drvdata->flags |= BUS_ACCESS_FLAG; |
435 | #ifdef CONFIG_PPC_DCR |
436 | else { |
437 | int start; |
438 | |
439 | start = dcr_resource_start(pdev->dev.of_node, 0); |
440 | drvdata->dcr_len = dcr_resource_len(pdev->dev.of_node, 0); |
441 | drvdata->dcr_host = dcr_map(pdev->dev.of_node, start, drvdata->dcr_len); |
442 | if (!DCR_MAP_OK(drvdata->dcr_host)) { |
443 | dev_err(&pdev->dev, "invalid DCR address\n" ); |
444 | return -ENODEV; |
445 | } |
446 | } |
447 | #endif |
448 | |
449 | prop = of_get_property(node: pdev->dev.of_node, name: "phys-size" , lenp: &size); |
450 | if ((prop) && (size >= sizeof(u32) * 2)) { |
451 | pdata.screen_width_mm = prop[0]; |
452 | pdata.screen_height_mm = prop[1]; |
453 | } |
454 | |
455 | prop = of_get_property(node: pdev->dev.of_node, name: "resolution" , lenp: &size); |
456 | if ((prop) && (size >= sizeof(u32) * 2)) { |
457 | pdata.xres = prop[0]; |
458 | pdata.yres = prop[1]; |
459 | } |
460 | |
461 | prop = of_get_property(node: pdev->dev.of_node, name: "virtual-resolution" , lenp: &size); |
462 | if ((prop) && (size >= sizeof(u32) * 2)) { |
463 | pdata.xvirt = prop[0]; |
464 | pdata.yvirt = prop[1]; |
465 | } |
466 | |
467 | pdata.rotate_screen = of_property_read_bool(np: pdev->dev.of_node, propname: "rotate-display" ); |
468 | |
469 | platform_set_drvdata(pdev, data: drvdata); |
470 | return xilinxfb_assign(pdev, drvdata, pdata: &pdata); |
471 | } |
472 | |
473 | static void xilinxfb_of_remove(struct platform_device *op) |
474 | { |
475 | xilinxfb_release(dev: &op->dev); |
476 | } |
477 | |
478 | /* Match table for of_platform binding */ |
479 | static const struct of_device_id xilinxfb_of_match[] = { |
480 | { .compatible = "xlnx,xps-tft-1.00.a" , }, |
481 | { .compatible = "xlnx,xps-tft-2.00.a" , }, |
482 | { .compatible = "xlnx,xps-tft-2.01.a" , }, |
483 | { .compatible = "xlnx,plb-tft-cntlr-ref-1.00.a" , }, |
484 | { .compatible = "xlnx,plb-dvi-cntlr-ref-1.00.c" , }, |
485 | {}, |
486 | }; |
487 | MODULE_DEVICE_TABLE(of, xilinxfb_of_match); |
488 | |
489 | static struct platform_driver xilinxfb_of_driver = { |
490 | .probe = xilinxfb_of_probe, |
491 | .remove_new = xilinxfb_of_remove, |
492 | .driver = { |
493 | .name = DRIVER_NAME, |
494 | .of_match_table = xilinxfb_of_match, |
495 | }, |
496 | }; |
497 | |
498 | module_platform_driver(xilinxfb_of_driver); |
499 | |
500 | MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>" ); |
501 | MODULE_DESCRIPTION("Xilinx TFT frame buffer driver" ); |
502 | MODULE_LICENSE("GPL" ); |
503 | |