1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * WonderMedia WM8505 Frame Buffer device driver |
4 | * |
5 | * Copyright (C) 2010 Ed Spiridonov <edo.rus@gmail.com> |
6 | * Based on vt8500lcdfb.c |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/fb.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/err.h> |
14 | #include <linux/init.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/io.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/memblock.h> |
19 | #include <linux/mm.h> |
20 | #include <linux/module.h> |
21 | #include <linux/of.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> |
28 | |
29 | #include "wm8505fb_regs.h" |
30 | #include "wmt_ge_rops.h" |
31 | |
32 | #define DRIVER_NAME "wm8505-fb" |
33 | |
34 | #define to_wm8505fb_info(__info) container_of(__info, \ |
35 | struct wm8505fb_info, fb) |
36 | struct wm8505fb_info { |
37 | struct fb_info fb; |
38 | void __iomem *regbase; |
39 | unsigned int contrast; |
40 | }; |
41 | |
42 | |
43 | static int wm8505fb_init_hw(struct fb_info *info) |
44 | { |
45 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
46 | |
47 | int i; |
48 | |
49 | /* I know the purpose only of few registers, so clear unknown */ |
50 | for (i = 0; i < 0x200; i += 4) |
51 | writel(val: 0, addr: fbi->regbase + i); |
52 | |
53 | /* Set frame buffer address */ |
54 | writel(val: fbi->fb.fix.smem_start, addr: fbi->regbase + WMT_GOVR_FBADDR); |
55 | writel(val: fbi->fb.fix.smem_start, addr: fbi->regbase + WMT_GOVR_FBADDR1); |
56 | |
57 | /* |
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 |
61 | */ |
62 | writel(val: 0x31c, addr: fbi->regbase + WMT_GOVR_COLORSPACE); |
63 | writel(val: 1, addr: fbi->regbase + WMT_GOVR_COLORSPACE1); |
64 | |
65 | /* Virtual buffer size */ |
66 | writel(val: info->var.xres, addr: fbi->regbase + WMT_GOVR_XRES); |
67 | writel(val: info->var.xres_virtual, addr: fbi->regbase + WMT_GOVR_XRES_VIRTUAL); |
68 | |
69 | /* black magic ;) */ |
70 | writel(val: 0xf, addr: fbi->regbase + WMT_GOVR_FHI); |
71 | writel(val: 4, addr: fbi->regbase + WMT_GOVR_DVO_SET); |
72 | writel(val: 1, addr: fbi->regbase + WMT_GOVR_MIF_ENABLE); |
73 | writel(val: 1, addr: fbi->regbase + WMT_GOVR_REG_UPDATE); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int wm8505fb_set_timing(struct fb_info *info) |
79 | { |
80 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
81 | |
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; |
86 | |
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; |
91 | |
92 | writel(val: 0, addr: fbi->regbase + WMT_GOVR_TG); |
93 | |
94 | writel(val: h_start, addr: fbi->regbase + WMT_GOVR_TIMING_H_START); |
95 | writel(val: h_end, addr: fbi->regbase + WMT_GOVR_TIMING_H_END); |
96 | writel(val: h_all, addr: fbi->regbase + WMT_GOVR_TIMING_H_ALL); |
97 | writel(val: h_sync, addr: fbi->regbase + WMT_GOVR_TIMING_H_SYNC); |
98 | |
99 | writel(val: v_start, addr: fbi->regbase + WMT_GOVR_TIMING_V_START); |
100 | writel(val: v_end, addr: fbi->regbase + WMT_GOVR_TIMING_V_END); |
101 | writel(val: v_all, addr: fbi->regbase + WMT_GOVR_TIMING_V_ALL); |
102 | writel(val: v_sync, addr: fbi->regbase + WMT_GOVR_TIMING_V_SYNC); |
103 | |
104 | writel(val: 1, addr: fbi->regbase + WMT_GOVR_TG); |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | |
110 | static int wm8505fb_set_par(struct fb_info *info) |
111 | { |
112 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
113 | |
114 | if (!fbi) |
115 | return -EINVAL; |
116 | |
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; |
141 | } |
142 | |
143 | wm8505fb_set_timing(info); |
144 | |
145 | writel(val: fbi->contrast<<16 | fbi->contrast<<8 | fbi->contrast, |
146 | addr: fbi->regbase + WMT_GOVR_CONTRAST); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static ssize_t contrast_show(struct device *dev, |
152 | struct device_attribute *attr, char *buf) |
153 | { |
154 | struct fb_info *info = dev_get_drvdata(dev); |
155 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
156 | |
157 | return sprintf(buf, fmt: "%u\n" , fbi->contrast); |
158 | } |
159 | |
160 | static ssize_t contrast_store(struct device *dev, |
161 | struct device_attribute *attr, |
162 | const char *buf, size_t count) |
163 | { |
164 | struct fb_info *info = dev_get_drvdata(dev); |
165 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
166 | unsigned long tmp; |
167 | |
168 | if (kstrtoul(s: buf, base: 10, res: &tmp) || (tmp > 0xff)) |
169 | return -EINVAL; |
170 | fbi->contrast = tmp; |
171 | |
172 | wm8505fb_set_par(info); |
173 | |
174 | return count; |
175 | } |
176 | |
177 | static DEVICE_ATTR_RW(contrast); |
178 | |
179 | static struct attribute *wm8505fb_attrs[] = { |
180 | &dev_attr_contrast.attr, |
181 | NULL, |
182 | }; |
183 | ATTRIBUTE_GROUPS(wm8505fb); |
184 | |
185 | static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) |
186 | { |
187 | chan &= 0xffff; |
188 | chan >>= 16 - bf->length; |
189 | return chan << bf->offset; |
190 | } |
191 | |
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); |
196 | int ret = 1; |
197 | unsigned int val; |
198 | if (regno >= 256) |
199 | return -EINVAL; |
200 | |
201 | if (info->var.grayscale) |
202 | red = green = blue = |
203 | (19595 * red + 38470 * green + 7471 * blue) >> 16; |
204 | |
205 | switch (fbi->fb.fix.visual) { |
206 | case FB_VISUAL_TRUECOLOR: |
207 | if (regno < 16) { |
208 | u32 *pal = info->pseudo_palette; |
209 | |
210 | val = chan_to_field(chan: red, bf: &fbi->fb.var.red); |
211 | val |= chan_to_field(chan: green, bf: &fbi->fb.var.green); |
212 | val |= chan_to_field(chan: blue, bf: &fbi->fb.var.blue); |
213 | |
214 | pal[regno] = val; |
215 | ret = 0; |
216 | } |
217 | break; |
218 | } |
219 | |
220 | return ret; |
221 | } |
222 | |
223 | static int wm8505fb_pan_display(struct fb_var_screeninfo *var, |
224 | struct fb_info *info) |
225 | { |
226 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
227 | |
228 | writel(val: var->xoffset, addr: fbi->regbase + WMT_GOVR_XPAN); |
229 | writel(val: var->yoffset, addr: fbi->regbase + WMT_GOVR_YPAN); |
230 | return 0; |
231 | } |
232 | |
233 | static int wm8505fb_blank(int blank, struct fb_info *info) |
234 | { |
235 | struct wm8505fb_info *fbi = to_wm8505fb_info(info); |
236 | |
237 | switch (blank) { |
238 | case FB_BLANK_UNBLANK: |
239 | wm8505fb_set_timing(info); |
240 | break; |
241 | default: |
242 | writel(val: 0, addr: fbi->regbase + WMT_GOVR_TIMING_V_SYNC); |
243 | break; |
244 | } |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static const struct fb_ops wm8505fb_ops = { |
250 | .owner = THIS_MODULE, |
251 | __FB_DEFAULT_DMAMEM_OPS_RDWR, |
252 | .fb_set_par = wm8505fb_set_par, |
253 | .fb_setcolreg = wm8505fb_setcolreg, |
254 | .fb_fillrect = wmt_ge_fillrect, |
255 | .fb_copyarea = wmt_ge_copyarea, |
256 | .fb_imageblit = sys_imageblit, |
257 | .fb_sync = wmt_ge_sync, |
258 | .fb_pan_display = wm8505fb_pan_display, |
259 | .fb_blank = wm8505fb_blank, |
260 | __FB_DEFAULT_IOMEM_OPS_MMAP, |
261 | }; |
262 | |
263 | static int wm8505fb_probe(struct platform_device *pdev) |
264 | { |
265 | struct wm8505fb_info *fbi; |
266 | struct display_timings *disp_timing; |
267 | void *addr; |
268 | int ret; |
269 | |
270 | struct fb_videomode mode; |
271 | u32 bpp; |
272 | dma_addr_t fb_mem_phys; |
273 | unsigned long fb_mem_len; |
274 | void *fb_mem_virt; |
275 | |
276 | fbi = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct wm8505fb_info) + |
277 | sizeof(u32) * 16, GFP_KERNEL); |
278 | if (!fbi) |
279 | return -ENOMEM; |
280 | |
281 | strcpy(p: fbi->fb.fix.id, DRIVER_NAME); |
282 | |
283 | fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; |
284 | fbi->fb.fix.xpanstep = 1; |
285 | fbi->fb.fix.ypanstep = 1; |
286 | fbi->fb.fix.ywrapstep = 0; |
287 | fbi->fb.fix.accel = FB_ACCEL_NONE; |
288 | |
289 | fbi->fb.fbops = &wm8505fb_ops; |
290 | fbi->fb.flags = FBINFO_HWACCEL_COPYAREA |
291 | | FBINFO_HWACCEL_FILLRECT |
292 | | FBINFO_HWACCEL_XPAN |
293 | | FBINFO_HWACCEL_YPAN |
294 | | FBINFO_VIRTFB |
295 | | FBINFO_PARTIAL_PAN_OK; |
296 | fbi->fb.node = -1; |
297 | |
298 | addr = fbi; |
299 | addr = addr + sizeof(struct wm8505fb_info); |
300 | fbi->fb.pseudo_palette = addr; |
301 | |
302 | fbi->regbase = devm_platform_ioremap_resource(pdev, index: 0); |
303 | if (IS_ERR(ptr: fbi->regbase)) |
304 | return PTR_ERR(ptr: fbi->regbase); |
305 | |
306 | disp_timing = of_get_display_timings(np: pdev->dev.of_node); |
307 | if (!disp_timing) |
308 | return -EINVAL; |
309 | |
310 | ret = of_get_fb_videomode(np: pdev->dev.of_node, fb: &mode, OF_USE_NATIVE_MODE); |
311 | if (ret) |
312 | return ret; |
313 | |
314 | ret = of_property_read_u32(np: pdev->dev.of_node, propname: "bits-per-pixel" , out_value: &bpp); |
315 | if (ret) |
316 | return ret; |
317 | |
318 | fb_videomode_to_var(var: &fbi->fb.var, mode: &mode); |
319 | |
320 | fbi->fb.var.nonstd = 0; |
321 | fbi->fb.var.activate = FB_ACTIVATE_NOW; |
322 | |
323 | fbi->fb.var.height = -1; |
324 | fbi->fb.var.width = -1; |
325 | |
326 | /* try allocating the framebuffer */ |
327 | fb_mem_len = mode.xres * mode.yres * 2 * (bpp / 8); |
328 | fb_mem_virt = dmam_alloc_coherent(dev: &pdev->dev, size: fb_mem_len, dma_handle: &fb_mem_phys, |
329 | GFP_KERNEL); |
330 | if (!fb_mem_virt) { |
331 | pr_err("%s: Failed to allocate framebuffer\n" , __func__); |
332 | return -ENOMEM; |
333 | } |
334 | |
335 | fbi->fb.var.xres_virtual = mode.xres; |
336 | fbi->fb.var.yres_virtual = mode.yres * 2; |
337 | fbi->fb.var.bits_per_pixel = bpp; |
338 | |
339 | fbi->fb.fix.smem_start = fb_mem_phys; |
340 | fbi->fb.fix.smem_len = fb_mem_len; |
341 | fbi->fb.screen_buffer = fb_mem_virt; |
342 | fbi->fb.screen_size = fb_mem_len; |
343 | |
344 | fbi->contrast = 0x10; |
345 | ret = wm8505fb_set_par(info: &fbi->fb); |
346 | if (ret) { |
347 | dev_err(&pdev->dev, "Failed to set parameters\n" ); |
348 | return ret; |
349 | } |
350 | |
351 | if (fb_alloc_cmap(cmap: &fbi->fb.cmap, len: 256, transp: 0) < 0) { |
352 | dev_err(&pdev->dev, "Failed to allocate color map\n" ); |
353 | return -ENOMEM; |
354 | } |
355 | |
356 | wm8505fb_init_hw(info: &fbi->fb); |
357 | |
358 | platform_set_drvdata(pdev, data: fbi); |
359 | |
360 | ret = register_framebuffer(fb_info: &fbi->fb); |
361 | if (ret < 0) { |
362 | dev_err(&pdev->dev, |
363 | "Failed to register framebuffer device: %d\n" , ret); |
364 | if (fbi->fb.cmap.len) |
365 | fb_dealloc_cmap(cmap: &fbi->fb.cmap); |
366 | return ret; |
367 | } |
368 | |
369 | fb_info(&fbi->fb, "%s frame buffer at 0x%lx-0x%lx\n" , |
370 | fbi->fb.fix.id, fbi->fb.fix.smem_start, |
371 | fbi->fb.fix.smem_start + fbi->fb.fix.smem_len - 1); |
372 | |
373 | return 0; |
374 | } |
375 | |
376 | static void wm8505fb_remove(struct platform_device *pdev) |
377 | { |
378 | struct wm8505fb_info *fbi = platform_get_drvdata(pdev); |
379 | |
380 | unregister_framebuffer(fb_info: &fbi->fb); |
381 | |
382 | writel(val: 0, addr: fbi->regbase); |
383 | |
384 | if (fbi->fb.cmap.len) |
385 | fb_dealloc_cmap(cmap: &fbi->fb.cmap); |
386 | } |
387 | |
388 | static const struct of_device_id wmt_dt_ids[] = { |
389 | { .compatible = "wm,wm8505-fb" , }, |
390 | {} |
391 | }; |
392 | |
393 | static struct platform_driver wm8505fb_driver = { |
394 | .probe = wm8505fb_probe, |
395 | .remove_new = wm8505fb_remove, |
396 | .driver = { |
397 | .name = DRIVER_NAME, |
398 | .of_match_table = wmt_dt_ids, |
399 | .dev_groups = wm8505fb_groups, |
400 | }, |
401 | }; |
402 | |
403 | module_platform_driver(wm8505fb_driver); |
404 | |
405 | MODULE_AUTHOR("Ed Spiridonov <edo.rus@gmail.com>" ); |
406 | MODULE_DESCRIPTION("Framebuffer driver for WMT WM8505" ); |
407 | MODULE_DEVICE_TABLE(of, wmt_dt_ids); |
408 | |