1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* tcx.c: TCX frame buffer driver |
3 | * |
4 | * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) |
5 | * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) |
6 | * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) |
7 | * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) |
8 | * |
9 | * Driver layout based loosely on tgafb.c, see that file for credits. |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/string.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/init.h> |
18 | #include <linux/fb.h> |
19 | #include <linux/mm.h> |
20 | #include <linux/of.h> |
21 | #include <linux/platform_device.h> |
22 | |
23 | #include <asm/io.h> |
24 | #include <asm/fbio.h> |
25 | |
26 | #include "sbuslib.h" |
27 | |
28 | /* |
29 | * Local functions. |
30 | */ |
31 | |
32 | static int tcx_setcolreg(unsigned, unsigned, unsigned, unsigned, |
33 | unsigned, struct fb_info *); |
34 | static int tcx_blank(int, struct fb_info *); |
35 | static int tcx_pan_display(struct fb_var_screeninfo *, struct fb_info *); |
36 | |
37 | static int tcx_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma); |
38 | static int tcx_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); |
39 | |
40 | /* |
41 | * Frame buffer operations |
42 | */ |
43 | |
44 | static const struct fb_ops tcx_ops = { |
45 | .owner = THIS_MODULE, |
46 | FB_DEFAULT_SBUS_OPS(tcx), |
47 | .fb_setcolreg = tcx_setcolreg, |
48 | .fb_blank = tcx_blank, |
49 | .fb_pan_display = tcx_pan_display, |
50 | }; |
51 | |
52 | /* THC definitions */ |
53 | #define TCX_THC_MISC_REV_SHIFT 16 |
54 | #define TCX_THC_MISC_REV_MASK 15 |
55 | #define TCX_THC_MISC_VSYNC_DIS (1 << 25) |
56 | #define TCX_THC_MISC_HSYNC_DIS (1 << 24) |
57 | #define TCX_THC_MISC_RESET (1 << 12) |
58 | #define TCX_THC_MISC_VIDEO (1 << 10) |
59 | #define TCX_THC_MISC_SYNC (1 << 9) |
60 | #define TCX_THC_MISC_VSYNC (1 << 8) |
61 | #define TCX_THC_MISC_SYNC_ENAB (1 << 7) |
62 | #define TCX_THC_MISC_CURS_RES (1 << 6) |
63 | #define TCX_THC_MISC_INT_ENAB (1 << 5) |
64 | #define TCX_THC_MISC_INT (1 << 4) |
65 | #define TCX_THC_MISC_INIT 0x9f |
66 | #define TCX_THC_REV_REV_SHIFT 20 |
67 | #define TCX_THC_REV_REV_MASK 15 |
68 | #define TCX_THC_REV_MINREV_SHIFT 28 |
69 | #define TCX_THC_REV_MINREV_MASK 15 |
70 | |
71 | /* The contents are unknown */ |
72 | struct tcx_tec { |
73 | u32 tec_matrix; |
74 | u32 tec_clip; |
75 | u32 tec_vdc; |
76 | }; |
77 | |
78 | struct tcx_thc { |
79 | u32 thc_rev; |
80 | u32 thc_pad0[511]; |
81 | u32 thc_hs; /* hsync timing */ |
82 | u32 thc_hsdvs; |
83 | u32 thc_hd; |
84 | u32 thc_vs; /* vsync timing */ |
85 | u32 thc_vd; |
86 | u32 thc_refresh; |
87 | u32 thc_misc; |
88 | u32 thc_pad1[56]; |
89 | u32 thc_cursxy; /* cursor x,y position (16 bits each) */ |
90 | u32 thc_cursmask[32]; /* cursor mask bits */ |
91 | u32 thc_cursbits[32]; /* what to show where mask enabled */ |
92 | }; |
93 | |
94 | struct bt_regs { |
95 | u32 addr; |
96 | u32 color_map; |
97 | u32 control; |
98 | u32 cursor; |
99 | }; |
100 | |
101 | #define TCX_MMAP_ENTRIES 14 |
102 | |
103 | struct tcx_par { |
104 | spinlock_t lock; |
105 | struct bt_regs __iomem *bt; |
106 | struct tcx_thc __iomem *thc; |
107 | struct tcx_tec __iomem *tec; |
108 | u32 __iomem *cplane; |
109 | |
110 | u32 flags; |
111 | #define TCX_FLAG_BLANKED 0x00000001 |
112 | |
113 | unsigned long which_io; |
114 | |
115 | struct sbus_mmap_map mmap_map[TCX_MMAP_ENTRIES]; |
116 | int lowdepth; |
117 | }; |
118 | |
119 | /* Reset control plane so that WID is 8-bit plane. */ |
120 | static void __tcx_set_control_plane(struct fb_info *info) |
121 | { |
122 | struct tcx_par *par = info->par; |
123 | u32 __iomem *p, *pend; |
124 | |
125 | if (par->lowdepth) |
126 | return; |
127 | |
128 | p = par->cplane; |
129 | if (p == NULL) |
130 | return; |
131 | for (pend = p + info->fix.smem_len; p < pend; p++) { |
132 | u32 tmp = sbus_readl(p); |
133 | |
134 | tmp &= 0xffffff; |
135 | sbus_writel(tmp, p); |
136 | } |
137 | } |
138 | |
139 | static void tcx_reset(struct fb_info *info) |
140 | { |
141 | struct tcx_par *par = (struct tcx_par *) info->par; |
142 | unsigned long flags; |
143 | |
144 | spin_lock_irqsave(&par->lock, flags); |
145 | __tcx_set_control_plane(info); |
146 | spin_unlock_irqrestore(lock: &par->lock, flags); |
147 | } |
148 | |
149 | static int tcx_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) |
150 | { |
151 | tcx_reset(info); |
152 | return 0; |
153 | } |
154 | |
155 | /** |
156 | * tcx_setcolreg - Optional function. Sets a color register. |
157 | * @regno: boolean, 0 copy local, 1 get_user() function |
158 | * @red: frame buffer colormap structure |
159 | * @green: The green value which can be up to 16 bits wide |
160 | * @blue: The blue value which can be up to 16 bits wide. |
161 | * @transp: If supported the alpha value which can be up to 16 bits wide. |
162 | * @info: frame buffer info structure |
163 | */ |
164 | static int tcx_setcolreg(unsigned regno, |
165 | unsigned red, unsigned green, unsigned blue, |
166 | unsigned transp, struct fb_info *info) |
167 | { |
168 | struct tcx_par *par = (struct tcx_par *) info->par; |
169 | struct bt_regs __iomem *bt = par->bt; |
170 | unsigned long flags; |
171 | |
172 | if (regno >= 256) |
173 | return 1; |
174 | |
175 | red >>= 8; |
176 | green >>= 8; |
177 | blue >>= 8; |
178 | |
179 | spin_lock_irqsave(&par->lock, flags); |
180 | |
181 | sbus_writel(regno << 24, &bt->addr); |
182 | sbus_writel(red << 24, &bt->color_map); |
183 | sbus_writel(green << 24, &bt->color_map); |
184 | sbus_writel(blue << 24, &bt->color_map); |
185 | |
186 | spin_unlock_irqrestore(lock: &par->lock, flags); |
187 | |
188 | return 0; |
189 | } |
190 | |
191 | /** |
192 | * tcx_blank - Optional function. Blanks the display. |
193 | * @blank: the blank mode we want. |
194 | * @info: frame buffer structure that represents a single frame buffer |
195 | */ |
196 | static int |
197 | tcx_blank(int blank, struct fb_info *info) |
198 | { |
199 | struct tcx_par *par = (struct tcx_par *) info->par; |
200 | struct tcx_thc __iomem *thc = par->thc; |
201 | unsigned long flags; |
202 | u32 val; |
203 | |
204 | spin_lock_irqsave(&par->lock, flags); |
205 | |
206 | val = sbus_readl(&thc->thc_misc); |
207 | |
208 | switch (blank) { |
209 | case FB_BLANK_UNBLANK: /* Unblanking */ |
210 | val &= ~(TCX_THC_MISC_VSYNC_DIS | |
211 | TCX_THC_MISC_HSYNC_DIS); |
212 | val |= TCX_THC_MISC_VIDEO; |
213 | par->flags &= ~TCX_FLAG_BLANKED; |
214 | break; |
215 | |
216 | case FB_BLANK_NORMAL: /* Normal blanking */ |
217 | val &= ~TCX_THC_MISC_VIDEO; |
218 | par->flags |= TCX_FLAG_BLANKED; |
219 | break; |
220 | |
221 | case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ |
222 | val |= TCX_THC_MISC_VSYNC_DIS; |
223 | break; |
224 | case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ |
225 | val |= TCX_THC_MISC_HSYNC_DIS; |
226 | break; |
227 | |
228 | case FB_BLANK_POWERDOWN: /* Poweroff */ |
229 | break; |
230 | } |
231 | |
232 | sbus_writel(val, &thc->thc_misc); |
233 | |
234 | spin_unlock_irqrestore(lock: &par->lock, flags); |
235 | |
236 | return 0; |
237 | } |
238 | |
239 | static struct sbus_mmap_map __tcx_mmap_map[TCX_MMAP_ENTRIES] = { |
240 | { |
241 | .voff = TCX_RAM8BIT, |
242 | .size = SBUS_MMAP_FBSIZE(1) |
243 | }, |
244 | { |
245 | .voff = TCX_RAM24BIT, |
246 | .size = SBUS_MMAP_FBSIZE(4) |
247 | }, |
248 | { |
249 | .voff = TCX_UNK3, |
250 | .size = SBUS_MMAP_FBSIZE(8) |
251 | }, |
252 | { |
253 | .voff = TCX_UNK4, |
254 | .size = SBUS_MMAP_FBSIZE(8) |
255 | }, |
256 | { |
257 | .voff = TCX_CONTROLPLANE, |
258 | .size = SBUS_MMAP_FBSIZE(4) |
259 | }, |
260 | { |
261 | .voff = TCX_UNK6, |
262 | .size = SBUS_MMAP_FBSIZE(8) |
263 | }, |
264 | { |
265 | .voff = TCX_UNK7, |
266 | .size = SBUS_MMAP_FBSIZE(8) |
267 | }, |
268 | { |
269 | .voff = TCX_TEC, |
270 | .size = PAGE_SIZE |
271 | }, |
272 | { |
273 | .voff = TCX_BTREGS, |
274 | .size = PAGE_SIZE |
275 | }, |
276 | { |
277 | .voff = TCX_THC, |
278 | .size = PAGE_SIZE |
279 | }, |
280 | { |
281 | .voff = TCX_DHC, |
282 | .size = PAGE_SIZE |
283 | }, |
284 | { |
285 | .voff = TCX_ALT, |
286 | .size = PAGE_SIZE |
287 | }, |
288 | { |
289 | .voff = TCX_UNK2, |
290 | .size = 0x20000 |
291 | }, |
292 | { .size = 0 } |
293 | }; |
294 | |
295 | static int tcx_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
296 | { |
297 | struct tcx_par *par = (struct tcx_par *)info->par; |
298 | |
299 | return sbusfb_mmap_helper(map: par->mmap_map, |
300 | physbase: info->fix.smem_start, fbsize: info->fix.smem_len, |
301 | iospace: par->which_io, vma); |
302 | } |
303 | |
304 | static int tcx_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) |
305 | { |
306 | struct tcx_par *par = (struct tcx_par *) info->par; |
307 | |
308 | return sbusfb_ioctl_helper(cmd, arg, info, |
309 | type: FBTYPE_TCXCOLOR, |
310 | fb_depth: (par->lowdepth ? 8 : 24), |
311 | fb_size: info->fix.smem_len); |
312 | } |
313 | |
314 | /* |
315 | * Initialisation |
316 | */ |
317 | |
318 | static void |
319 | tcx_init_fix(struct fb_info *info, int linebytes) |
320 | { |
321 | struct tcx_par *par = (struct tcx_par *)info->par; |
322 | const char *tcx_name; |
323 | |
324 | if (par->lowdepth) |
325 | tcx_name = "TCX8" ; |
326 | else |
327 | tcx_name = "TCX24" ; |
328 | |
329 | strscpy(info->fix.id, tcx_name, sizeof(info->fix.id)); |
330 | |
331 | info->fix.type = FB_TYPE_PACKED_PIXELS; |
332 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; |
333 | |
334 | info->fix.line_length = linebytes; |
335 | |
336 | info->fix.accel = FB_ACCEL_SUN_TCX; |
337 | } |
338 | |
339 | static void tcx_unmap_regs(struct platform_device *op, struct fb_info *info, |
340 | struct tcx_par *par) |
341 | { |
342 | if (par->tec) |
343 | of_iounmap(&op->resource[7], |
344 | par->tec, sizeof(struct tcx_tec)); |
345 | if (par->thc) |
346 | of_iounmap(&op->resource[9], |
347 | par->thc, sizeof(struct tcx_thc)); |
348 | if (par->bt) |
349 | of_iounmap(&op->resource[8], |
350 | par->bt, sizeof(struct bt_regs)); |
351 | if (par->cplane) |
352 | of_iounmap(&op->resource[4], |
353 | par->cplane, info->fix.smem_len * sizeof(u32)); |
354 | if (info->screen_base) |
355 | of_iounmap(&op->resource[0], |
356 | info->screen_base, info->fix.smem_len); |
357 | } |
358 | |
359 | static int tcx_probe(struct platform_device *op) |
360 | { |
361 | struct device_node *dp = op->dev.of_node; |
362 | struct fb_info *info; |
363 | struct tcx_par *par; |
364 | int linebytes, i, err; |
365 | |
366 | info = framebuffer_alloc(size: sizeof(struct tcx_par), dev: &op->dev); |
367 | |
368 | err = -ENOMEM; |
369 | if (!info) |
370 | goto out_err; |
371 | par = info->par; |
372 | |
373 | spin_lock_init(&par->lock); |
374 | |
375 | par->lowdepth = of_property_read_bool(np: dp, propname: "tcx-8-bit" ); |
376 | |
377 | sbusfb_fill_var(var: &info->var, dp, bpp: 8); |
378 | info->var.red.length = 8; |
379 | info->var.green.length = 8; |
380 | info->var.blue.length = 8; |
381 | |
382 | linebytes = of_getintprop_default(dp, "linebytes" , |
383 | info->var.xres); |
384 | info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres); |
385 | |
386 | par->tec = of_ioremap(&op->resource[7], 0, |
387 | sizeof(struct tcx_tec), "tcx tec" ); |
388 | par->thc = of_ioremap(&op->resource[9], 0, |
389 | sizeof(struct tcx_thc), "tcx thc" ); |
390 | par->bt = of_ioremap(&op->resource[8], 0, |
391 | sizeof(struct bt_regs), "tcx dac" ); |
392 | info->screen_base = of_ioremap(&op->resource[0], 0, |
393 | info->fix.smem_len, "tcx ram" ); |
394 | if (!par->tec || !par->thc || |
395 | !par->bt || !info->screen_base) |
396 | goto out_unmap_regs; |
397 | |
398 | memcpy(&par->mmap_map, &__tcx_mmap_map, sizeof(par->mmap_map)); |
399 | if (!par->lowdepth) { |
400 | par->cplane = of_ioremap(&op->resource[4], 0, |
401 | info->fix.smem_len * sizeof(u32), |
402 | "tcx cplane" ); |
403 | if (!par->cplane) |
404 | goto out_unmap_regs; |
405 | } else { |
406 | par->mmap_map[1].size = SBUS_MMAP_EMPTY; |
407 | par->mmap_map[4].size = SBUS_MMAP_EMPTY; |
408 | par->mmap_map[5].size = SBUS_MMAP_EMPTY; |
409 | par->mmap_map[6].size = SBUS_MMAP_EMPTY; |
410 | } |
411 | |
412 | info->fix.smem_start = op->resource[0].start; |
413 | par->which_io = op->resource[0].flags & IORESOURCE_BITS; |
414 | |
415 | for (i = 0; i < TCX_MMAP_ENTRIES; i++) { |
416 | int j; |
417 | |
418 | switch (i) { |
419 | case 10: |
420 | j = 12; |
421 | break; |
422 | |
423 | case 11: case 12: |
424 | j = i - 1; |
425 | break; |
426 | |
427 | default: |
428 | j = i; |
429 | break; |
430 | } |
431 | par->mmap_map[i].poff = op->resource[j].start; |
432 | } |
433 | |
434 | info->fbops = &tcx_ops; |
435 | |
436 | /* Initialize brooktree DAC. */ |
437 | sbus_writel(0x04 << 24, &par->bt->addr); /* color planes */ |
438 | sbus_writel(0xff << 24, &par->bt->control); |
439 | sbus_writel(0x05 << 24, &par->bt->addr); |
440 | sbus_writel(0x00 << 24, &par->bt->control); |
441 | sbus_writel(0x06 << 24, &par->bt->addr); /* overlay plane */ |
442 | sbus_writel(0x73 << 24, &par->bt->control); |
443 | sbus_writel(0x07 << 24, &par->bt->addr); |
444 | sbus_writel(0x00 << 24, &par->bt->control); |
445 | |
446 | tcx_reset(info); |
447 | |
448 | tcx_blank(blank: FB_BLANK_UNBLANK, info); |
449 | |
450 | if (fb_alloc_cmap(cmap: &info->cmap, len: 256, transp: 0)) |
451 | goto out_unmap_regs; |
452 | |
453 | fb_set_cmap(cmap: &info->cmap, fb_info: info); |
454 | tcx_init_fix(info, linebytes); |
455 | |
456 | err = register_framebuffer(fb_info: info); |
457 | if (err < 0) |
458 | goto out_dealloc_cmap; |
459 | |
460 | dev_set_drvdata(dev: &op->dev, data: info); |
461 | |
462 | printk(KERN_INFO "%pOF: TCX at %lx:%lx, %s\n" , |
463 | dp, |
464 | par->which_io, |
465 | info->fix.smem_start, |
466 | par->lowdepth ? "8-bit only" : "24-bit depth" ); |
467 | |
468 | return 0; |
469 | |
470 | out_dealloc_cmap: |
471 | fb_dealloc_cmap(cmap: &info->cmap); |
472 | |
473 | out_unmap_regs: |
474 | tcx_unmap_regs(op, info, par); |
475 | framebuffer_release(info); |
476 | |
477 | out_err: |
478 | return err; |
479 | } |
480 | |
481 | static void tcx_remove(struct platform_device *op) |
482 | { |
483 | struct fb_info *info = dev_get_drvdata(dev: &op->dev); |
484 | struct tcx_par *par = info->par; |
485 | |
486 | unregister_framebuffer(fb_info: info); |
487 | fb_dealloc_cmap(cmap: &info->cmap); |
488 | |
489 | tcx_unmap_regs(op, info, par); |
490 | |
491 | framebuffer_release(info); |
492 | } |
493 | |
494 | static const struct of_device_id tcx_match[] = { |
495 | { |
496 | .name = "SUNW,tcx" , |
497 | }, |
498 | {}, |
499 | }; |
500 | MODULE_DEVICE_TABLE(of, tcx_match); |
501 | |
502 | static struct platform_driver tcx_driver = { |
503 | .driver = { |
504 | .name = "tcx" , |
505 | .of_match_table = tcx_match, |
506 | }, |
507 | .probe = tcx_probe, |
508 | .remove_new = tcx_remove, |
509 | }; |
510 | |
511 | static int __init tcx_init(void) |
512 | { |
513 | if (fb_get_options(name: "tcxfb" , NULL)) |
514 | return -ENODEV; |
515 | |
516 | return platform_driver_register(&tcx_driver); |
517 | } |
518 | |
519 | static void __exit tcx_exit(void) |
520 | { |
521 | platform_driver_unregister(&tcx_driver); |
522 | } |
523 | |
524 | module_init(tcx_init); |
525 | module_exit(tcx_exit); |
526 | |
527 | MODULE_DESCRIPTION("framebuffer driver for TCX chipsets" ); |
528 | MODULE_AUTHOR("David S. Miller <davem@davemloft.net>" ); |
529 | MODULE_VERSION("2.0" ); |
530 | MODULE_LICENSE("GPL" ); |
531 | |