1 // SPDX-License-Identifier: GPL-2.0-only
3 * WonderMedia WM8505 Frame Buffer device driver
5 * Copyright (C) 2010 Ed Spiridonov <edo.rus@gmail.com>
6 * Based on vt8500lcdfb.c
9 #include <linux/delay.h>
10 #include <linux/dma-mapping.h>
12 #include <linux/errno.h>
13 #include <linux/err.h>
14 #include <linux/init.h>
15 #include <linux/interrupt.h>
17 #include <linux/kernel.h>
18 #include <linux/memblock.h>
20 #include <linux/module.h>
22 #include <linux/of_fdt.h>
23 #include <linux/platform_device.h>
24 #include <linux/slab.h>
25 #include <linux/string.h>
26 #include <linux/wait.h>
27 #include <video/of_display_timing.h>
29 #include "wm8505fb_regs.h"
30 #include "wmt_ge_rops.h"
32 #define DRIVER_NAME "wm8505-fb"
34 #define to_wm8505fb_info(__info) container_of(__info, \
35 struct wm8505fb_info, fb)
36 struct wm8505fb_info
{
38 void __iomem
*regbase
;
39 unsigned int contrast
;
43 static int wm8505fb_init_hw(struct fb_info
*info
)
45 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
49 /* I know the purpose only of few registers, so clear unknown */
50 for (i
= 0; i
< 0x200; i
+= 4)
51 writel(0, fbi
->regbase
+ i
);
53 /* Set frame buffer address */
54 writel(fbi
->fb
.fix
.smem_start
, fbi
->regbase
+ WMT_GOVR_FBADDR
);
55 writel(fbi
->fb
.fix
.smem_start
, fbi
->regbase
+ WMT_GOVR_FBADDR1
);
58 * Set in-memory picture format to RGB
59 * 0x31C sets the correct color mode (RGB565) for WM8650
60 * Bit 8+9 (0x300) are ignored on WM8505 as reserved
62 writel(0x31c, fbi
->regbase
+ WMT_GOVR_COLORSPACE
);
63 writel(1, fbi
->regbase
+ WMT_GOVR_COLORSPACE1
);
65 /* Virtual buffer size */
66 writel(info
->var
.xres
, fbi
->regbase
+ WMT_GOVR_XRES
);
67 writel(info
->var
.xres_virtual
, fbi
->regbase
+ WMT_GOVR_XRES_VIRTUAL
);
70 writel(0xf, fbi
->regbase
+ WMT_GOVR_FHI
);
71 writel(4, fbi
->regbase
+ WMT_GOVR_DVO_SET
);
72 writel(1, fbi
->regbase
+ WMT_GOVR_MIF_ENABLE
);
73 writel(1, fbi
->regbase
+ WMT_GOVR_REG_UPDATE
);
78 static int wm8505fb_set_timing(struct fb_info
*info
)
80 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
82 int h_start
= info
->var
.left_margin
;
83 int h_end
= h_start
+ info
->var
.xres
;
84 int h_all
= h_end
+ info
->var
.right_margin
;
85 int h_sync
= info
->var
.hsync_len
;
87 int v_start
= info
->var
.upper_margin
;
88 int v_end
= v_start
+ info
->var
.yres
;
89 int v_all
= v_end
+ info
->var
.lower_margin
;
90 int v_sync
= info
->var
.vsync_len
;
92 writel(0, fbi
->regbase
+ WMT_GOVR_TG
);
94 writel(h_start
, fbi
->regbase
+ WMT_GOVR_TIMING_H_START
);
95 writel(h_end
, fbi
->regbase
+ WMT_GOVR_TIMING_H_END
);
96 writel(h_all
, fbi
->regbase
+ WMT_GOVR_TIMING_H_ALL
);
97 writel(h_sync
, fbi
->regbase
+ WMT_GOVR_TIMING_H_SYNC
);
99 writel(v_start
, fbi
->regbase
+ WMT_GOVR_TIMING_V_START
);
100 writel(v_end
, fbi
->regbase
+ WMT_GOVR_TIMING_V_END
);
101 writel(v_all
, fbi
->regbase
+ WMT_GOVR_TIMING_V_ALL
);
102 writel(v_sync
, fbi
->regbase
+ WMT_GOVR_TIMING_V_SYNC
);
104 writel(1, fbi
->regbase
+ WMT_GOVR_TG
);
110 static int wm8505fb_set_par(struct fb_info
*info
)
112 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
117 if (info
->var
.bits_per_pixel
== 32) {
118 info
->var
.red
.offset
= 16;
119 info
->var
.red
.length
= 8;
120 info
->var
.red
.msb_right
= 0;
121 info
->var
.green
.offset
= 8;
122 info
->var
.green
.length
= 8;
123 info
->var
.green
.msb_right
= 0;
124 info
->var
.blue
.offset
= 0;
125 info
->var
.blue
.length
= 8;
126 info
->var
.blue
.msb_right
= 0;
127 info
->fix
.visual
= FB_VISUAL_TRUECOLOR
;
128 info
->fix
.line_length
= info
->var
.xres_virtual
<< 2;
129 } else if (info
->var
.bits_per_pixel
== 16) {
130 info
->var
.red
.offset
= 11;
131 info
->var
.red
.length
= 5;
132 info
->var
.red
.msb_right
= 0;
133 info
->var
.green
.offset
= 5;
134 info
->var
.green
.length
= 6;
135 info
->var
.green
.msb_right
= 0;
136 info
->var
.blue
.offset
= 0;
137 info
->var
.blue
.length
= 5;
138 info
->var
.blue
.msb_right
= 0;
139 info
->fix
.visual
= FB_VISUAL_TRUECOLOR
;
140 info
->fix
.line_length
= info
->var
.xres_virtual
<< 1;
143 wm8505fb_set_timing(info
);
145 writel(fbi
->contrast
<<16 | fbi
->contrast
<<8 | fbi
->contrast
,
146 fbi
->regbase
+ WMT_GOVR_CONTRAST
);
151 static ssize_t
contrast_show(struct device
*dev
,
152 struct device_attribute
*attr
, char *buf
)
154 struct fb_info
*info
= dev_get_drvdata(dev
);
155 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
157 return sprintf(buf
, "%u\n", fbi
->contrast
);
160 static ssize_t
contrast_store(struct device
*dev
,
161 struct device_attribute
*attr
,
162 const char *buf
, size_t count
)
164 struct fb_info
*info
= dev_get_drvdata(dev
);
165 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
168 if (kstrtoul(buf
, 10, &tmp
) || (tmp
> 0xff))
172 wm8505fb_set_par(info
);
177 static DEVICE_ATTR_RW(contrast
);
179 static struct attribute
*wm8505fb_attrs
[] = {
180 &dev_attr_contrast
.attr
,
183 ATTRIBUTE_GROUPS(wm8505fb
);
185 static inline u_int
chan_to_field(u_int chan
, struct fb_bitfield
*bf
)
188 chan
>>= 16 - bf
->length
;
189 return chan
<< bf
->offset
;
192 static int wm8505fb_setcolreg(unsigned regno
, unsigned red
, unsigned green
,
193 unsigned blue
, unsigned transp
,
194 struct fb_info
*info
) {
195 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
201 if (info
->var
.grayscale
)
203 (19595 * red
+ 38470 * green
+ 7471 * blue
) >> 16;
205 switch (fbi
->fb
.fix
.visual
) {
206 case FB_VISUAL_TRUECOLOR
:
208 u32
*pal
= info
->pseudo_palette
;
210 val
= chan_to_field(red
, &fbi
->fb
.var
.red
);
211 val
|= chan_to_field(green
, &fbi
->fb
.var
.green
);
212 val
|= chan_to_field(blue
, &fbi
->fb
.var
.blue
);
223 static int wm8505fb_pan_display(struct fb_var_screeninfo
*var
,
224 struct fb_info
*info
)
226 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
228 writel(var
->xoffset
, fbi
->regbase
+ WMT_GOVR_XPAN
);
229 writel(var
->yoffset
, fbi
->regbase
+ WMT_GOVR_YPAN
);
233 static int wm8505fb_blank(int blank
, struct fb_info
*info
)
235 struct wm8505fb_info
*fbi
= to_wm8505fb_info(info
);
238 case FB_BLANK_UNBLANK
:
239 wm8505fb_set_timing(info
);
242 writel(0, fbi
->regbase
+ WMT_GOVR_TIMING_V_SYNC
);
249 static const struct fb_ops wm8505fb_ops
= {
250 .owner
= THIS_MODULE
,
251 .fb_set_par
= wm8505fb_set_par
,
252 .fb_setcolreg
= wm8505fb_setcolreg
,
253 .fb_fillrect
= wmt_ge_fillrect
,
254 .fb_copyarea
= wmt_ge_copyarea
,
255 .fb_imageblit
= sys_imageblit
,
256 .fb_sync
= wmt_ge_sync
,
257 .fb_pan_display
= wm8505fb_pan_display
,
258 .fb_blank
= wm8505fb_blank
,
261 static int wm8505fb_probe(struct platform_device
*pdev
)
263 struct wm8505fb_info
*fbi
;
264 struct resource
*res
;
265 struct display_timings
*disp_timing
;
269 struct fb_videomode mode
;
271 dma_addr_t fb_mem_phys
;
272 unsigned long fb_mem_len
;
275 fbi
= devm_kzalloc(&pdev
->dev
, sizeof(struct wm8505fb_info
) +
276 sizeof(u32
) * 16, GFP_KERNEL
);
280 strcpy(fbi
->fb
.fix
.id
, DRIVER_NAME
);
282 fbi
->fb
.fix
.type
= FB_TYPE_PACKED_PIXELS
;
283 fbi
->fb
.fix
.xpanstep
= 1;
284 fbi
->fb
.fix
.ypanstep
= 1;
285 fbi
->fb
.fix
.ywrapstep
= 0;
286 fbi
->fb
.fix
.accel
= FB_ACCEL_NONE
;
288 fbi
->fb
.fbops
= &wm8505fb_ops
;
289 fbi
->fb
.flags
= FBINFO_DEFAULT
290 | FBINFO_HWACCEL_COPYAREA
291 | FBINFO_HWACCEL_FILLRECT
292 | FBINFO_HWACCEL_XPAN
293 | FBINFO_HWACCEL_YPAN
295 | FBINFO_PARTIAL_PAN_OK
;
299 addr
= addr
+ sizeof(struct wm8505fb_info
);
300 fbi
->fb
.pseudo_palette
= addr
;
302 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
303 fbi
->regbase
= devm_ioremap_resource(&pdev
->dev
, res
);
304 if (IS_ERR(fbi
->regbase
))
305 return PTR_ERR(fbi
->regbase
);
307 disp_timing
= of_get_display_timings(pdev
->dev
.of_node
);
311 ret
= of_get_fb_videomode(pdev
->dev
.of_node
, &mode
, OF_USE_NATIVE_MODE
);
315 ret
= of_property_read_u32(pdev
->dev
.of_node
, "bits-per-pixel", &bpp
);
319 fb_videomode_to_var(&fbi
->fb
.var
, &mode
);
321 fbi
->fb
.var
.nonstd
= 0;
322 fbi
->fb
.var
.activate
= FB_ACTIVATE_NOW
;
324 fbi
->fb
.var
.height
= -1;
325 fbi
->fb
.var
.width
= -1;
327 /* try allocating the framebuffer */
328 fb_mem_len
= mode
.xres
* mode
.yres
* 2 * (bpp
/ 8);
329 fb_mem_virt
= dmam_alloc_coherent(&pdev
->dev
, fb_mem_len
, &fb_mem_phys
,
332 pr_err("%s: Failed to allocate framebuffer\n", __func__
);
336 fbi
->fb
.var
.xres_virtual
= mode
.xres
;
337 fbi
->fb
.var
.yres_virtual
= mode
.yres
* 2;
338 fbi
->fb
.var
.bits_per_pixel
= bpp
;
340 fbi
->fb
.fix
.smem_start
= fb_mem_phys
;
341 fbi
->fb
.fix
.smem_len
= fb_mem_len
;
342 fbi
->fb
.screen_buffer
= fb_mem_virt
;
343 fbi
->fb
.screen_size
= fb_mem_len
;
345 fbi
->contrast
= 0x10;
346 ret
= wm8505fb_set_par(&fbi
->fb
);
348 dev_err(&pdev
->dev
, "Failed to set parameters\n");
352 if (fb_alloc_cmap(&fbi
->fb
.cmap
, 256, 0) < 0) {
353 dev_err(&pdev
->dev
, "Failed to allocate color map\n");
357 wm8505fb_init_hw(&fbi
->fb
);
359 platform_set_drvdata(pdev
, fbi
);
361 ret
= register_framebuffer(&fbi
->fb
);
364 "Failed to register framebuffer device: %d\n", ret
);
365 if (fbi
->fb
.cmap
.len
)
366 fb_dealloc_cmap(&fbi
->fb
.cmap
);
370 fb_info(&fbi
->fb
, "%s frame buffer at 0x%lx-0x%lx\n",
371 fbi
->fb
.fix
.id
, fbi
->fb
.fix
.smem_start
,
372 fbi
->fb
.fix
.smem_start
+ fbi
->fb
.fix
.smem_len
- 1);
377 static int wm8505fb_remove(struct platform_device
*pdev
)
379 struct wm8505fb_info
*fbi
= platform_get_drvdata(pdev
);
381 unregister_framebuffer(&fbi
->fb
);
383 writel(0, fbi
->regbase
);
385 if (fbi
->fb
.cmap
.len
)
386 fb_dealloc_cmap(&fbi
->fb
.cmap
);
391 static const struct of_device_id wmt_dt_ids
[] = {
392 { .compatible
= "wm,wm8505-fb", },
396 static struct platform_driver wm8505fb_driver
= {
397 .probe
= wm8505fb_probe
,
398 .remove
= wm8505fb_remove
,
401 .of_match_table
= wmt_dt_ids
,
402 .dev_groups
= wm8505fb_groups
,
406 module_platform_driver(wm8505fb_driver
);
408 MODULE_AUTHOR("Ed Spiridonov <edo.rus@gmail.com>");
409 MODULE_DESCRIPTION("Framebuffer driver for WMT WM8505");
410 MODULE_LICENSE("GPL v2");
411 MODULE_DEVICE_TABLE(of
, wmt_dt_ids
);