1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * drivers/video/geode/gx1fb_core.c |
4 | * -- Geode GX1 framebuffer driver |
5 | * |
6 | * Copyright (C) 2005 Arcom Control Systems Ltd. |
7 | */ |
8 | |
9 | #include <linux/aperture.h> |
10 | #include <linux/module.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/string.h> |
14 | #include <linux/mm.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/fb.h> |
17 | #include <linux/init.h> |
18 | #include <linux/pci.h> |
19 | |
20 | #include "geodefb.h" |
21 | #include "display_gx1.h" |
22 | #include "video_cs5530.h" |
23 | |
24 | static char mode_option[32] = "640x480-16@60" ; |
25 | static int crt_option = 1; |
26 | static char panel_option[32] = "" ; |
27 | |
28 | /* Modes relevant to the GX1 (taken from modedb.c) */ |
29 | static const struct fb_videomode gx1_modedb[] = { |
30 | /* 640x480-60 VESA */ |
31 | { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, |
32 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
33 | /* 640x480-75 VESA */ |
34 | { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, |
35 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
36 | /* 640x480-85 VESA */ |
37 | { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, |
38 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
39 | /* 800x600-60 VESA */ |
40 | { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, |
41 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
42 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
43 | /* 800x600-75 VESA */ |
44 | { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, |
45 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
46 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
47 | /* 800x600-85 VESA */ |
48 | { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, |
49 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
50 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
51 | /* 1024x768-60 VESA */ |
52 | { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, |
53 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
54 | /* 1024x768-75 VESA */ |
55 | { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, |
56 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
57 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
58 | /* 1024x768-85 VESA */ |
59 | { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, |
60 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
61 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
62 | /* 1280x960-60 VESA */ |
63 | { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, |
64 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
65 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
66 | /* 1280x960-85 VESA */ |
67 | { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, |
68 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
69 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
70 | /* 1280x1024-60 VESA */ |
71 | { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, |
72 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
73 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
74 | /* 1280x1024-75 VESA */ |
75 | { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, |
76 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
77 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
78 | /* 1280x1024-85 VESA */ |
79 | { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, |
80 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
81 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, |
82 | }; |
83 | |
84 | static int gx1_line_delta(int xres, int bpp) |
85 | { |
86 | int line_delta = xres * (bpp >> 3); |
87 | |
88 | if (line_delta > 2048) |
89 | line_delta = 4096; |
90 | else if (line_delta > 1024) |
91 | line_delta = 2048; |
92 | else |
93 | line_delta = 1024; |
94 | return line_delta; |
95 | } |
96 | |
97 | static int gx1fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
98 | { |
99 | struct geodefb_par *par = info->par; |
100 | |
101 | /* Maximum resolution is 1280x1024. */ |
102 | if (var->xres > 1280 || var->yres > 1024) |
103 | return -EINVAL; |
104 | |
105 | if (par->panel_x && (var->xres > par->panel_x || var->yres > par->panel_y)) |
106 | return -EINVAL; |
107 | |
108 | /* Only 16 bpp and 8 bpp is supported by the hardware. */ |
109 | if (var->bits_per_pixel == 16) { |
110 | var->red.offset = 11; var->red.length = 5; |
111 | var->green.offset = 5; var->green.length = 6; |
112 | var->blue.offset = 0; var->blue.length = 5; |
113 | var->transp.offset = 0; var->transp.length = 0; |
114 | } else if (var->bits_per_pixel == 8) { |
115 | var->red.offset = 0; var->red.length = 8; |
116 | var->green.offset = 0; var->green.length = 8; |
117 | var->blue.offset = 0; var->blue.length = 8; |
118 | var->transp.offset = 0; var->transp.length = 0; |
119 | } else |
120 | return -EINVAL; |
121 | |
122 | /* Enough video memory? */ |
123 | if (gx1_line_delta(xres: var->xres, bpp: var->bits_per_pixel) * var->yres > info->fix.smem_len) |
124 | return -EINVAL; |
125 | |
126 | /* FIXME: Check timing parameters here? */ |
127 | |
128 | return 0; |
129 | } |
130 | |
131 | static int gx1fb_set_par(struct fb_info *info) |
132 | { |
133 | struct geodefb_par *par = info->par; |
134 | |
135 | if (info->var.bits_per_pixel == 16) |
136 | info->fix.visual = FB_VISUAL_TRUECOLOR; |
137 | else |
138 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; |
139 | |
140 | info->fix.line_length = gx1_line_delta(xres: info->var.xres, bpp: info->var.bits_per_pixel); |
141 | |
142 | par->dc_ops->set_mode(info); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) |
148 | { |
149 | chan &= 0xffff; |
150 | chan >>= 16 - bf->length; |
151 | return chan << bf->offset; |
152 | } |
153 | |
154 | static int gx1fb_setcolreg(unsigned regno, unsigned red, unsigned green, |
155 | unsigned blue, unsigned transp, |
156 | struct fb_info *info) |
157 | { |
158 | struct geodefb_par *par = info->par; |
159 | |
160 | if (info->var.grayscale) { |
161 | /* grayscale = 0.30*R + 0.59*G + 0.11*B */ |
162 | red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; |
163 | } |
164 | |
165 | /* Truecolor has hardware independent palette */ |
166 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) { |
167 | u32 *pal = info->pseudo_palette; |
168 | u32 v; |
169 | |
170 | if (regno >= 16) |
171 | return -EINVAL; |
172 | |
173 | v = chan_to_field(chan: red, bf: &info->var.red); |
174 | v |= chan_to_field(chan: green, bf: &info->var.green); |
175 | v |= chan_to_field(chan: blue, bf: &info->var.blue); |
176 | |
177 | pal[regno] = v; |
178 | } else { |
179 | if (regno >= 256) |
180 | return -EINVAL; |
181 | |
182 | par->dc_ops->set_palette_reg(info, regno, red, green, blue); |
183 | } |
184 | |
185 | return 0; |
186 | } |
187 | |
188 | static int gx1fb_blank(int blank_mode, struct fb_info *info) |
189 | { |
190 | struct geodefb_par *par = info->par; |
191 | |
192 | return par->vid_ops->blank_display(info, blank_mode); |
193 | } |
194 | |
195 | static int gx1fb_map_video_memory(struct fb_info *info, struct pci_dev *dev) |
196 | { |
197 | struct geodefb_par *par = info->par; |
198 | unsigned gx_base; |
199 | int fb_len; |
200 | int ret; |
201 | |
202 | gx_base = gx1_gx_base(); |
203 | if (!gx_base) |
204 | return -ENODEV; |
205 | |
206 | ret = pci_enable_device(dev); |
207 | if (ret < 0) |
208 | return ret; |
209 | |
210 | ret = pci_request_region(dev, 0, "gx1fb (video)" ); |
211 | if (ret < 0) |
212 | return ret; |
213 | par->vid_regs = pci_ioremap_bar(pdev: dev, bar: 0); |
214 | if (!par->vid_regs) |
215 | return -ENOMEM; |
216 | |
217 | if (!request_mem_region(gx_base + 0x8300, 0x100, "gx1fb (display controller)" )) |
218 | return -EBUSY; |
219 | par->dc_regs = ioremap(offset: gx_base + 0x8300, size: 0x100); |
220 | if (!par->dc_regs) |
221 | return -ENOMEM; |
222 | |
223 | if ((fb_len = gx1_frame_buffer_size()) < 0) |
224 | return -ENOMEM; |
225 | info->fix.smem_start = gx_base + 0x800000; |
226 | info->fix.smem_len = fb_len; |
227 | info->screen_base = ioremap(offset: info->fix.smem_start, size: info->fix.smem_len); |
228 | if (!info->screen_base) |
229 | return -ENOMEM; |
230 | |
231 | dev_info(&dev->dev, "%d Kibyte of video memory at 0x%lx\n" , |
232 | info->fix.smem_len / 1024, info->fix.smem_start); |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static int parse_panel_option(struct fb_info *info) |
238 | { |
239 | struct geodefb_par *par = info->par; |
240 | |
241 | if (strcmp(panel_option, "" ) != 0) { |
242 | int x, y; |
243 | char *s; |
244 | x = simple_strtol(panel_option, &s, 10); |
245 | if (!x) |
246 | return -EINVAL; |
247 | y = simple_strtol(s + 1, NULL, 10); |
248 | if (!y) |
249 | return -EINVAL; |
250 | par->panel_x = x; |
251 | par->panel_y = y; |
252 | } |
253 | return 0; |
254 | } |
255 | |
256 | static const struct fb_ops gx1fb_ops = { |
257 | .owner = THIS_MODULE, |
258 | FB_DEFAULT_IOMEM_OPS, |
259 | .fb_check_var = gx1fb_check_var, |
260 | .fb_set_par = gx1fb_set_par, |
261 | .fb_setcolreg = gx1fb_setcolreg, |
262 | .fb_blank = gx1fb_blank, |
263 | }; |
264 | |
265 | static struct fb_info *gx1fb_init_fbinfo(struct device *dev) |
266 | { |
267 | struct geodefb_par *par; |
268 | struct fb_info *info; |
269 | |
270 | /* Alloc enough space for the pseudo palette. */ |
271 | info = framebuffer_alloc(size: sizeof(struct geodefb_par) + sizeof(u32) * 16, dev); |
272 | if (!info) |
273 | return NULL; |
274 | |
275 | par = info->par; |
276 | |
277 | strcpy(p: info->fix.id, q: "GX1" ); |
278 | |
279 | info->fix.type = FB_TYPE_PACKED_PIXELS; |
280 | info->fix.type_aux = 0; |
281 | info->fix.xpanstep = 0; |
282 | info->fix.ypanstep = 0; |
283 | info->fix.ywrapstep = 0; |
284 | info->fix.accel = FB_ACCEL_NONE; |
285 | |
286 | info->var.nonstd = 0; |
287 | info->var.activate = FB_ACTIVATE_NOW; |
288 | info->var.height = -1; |
289 | info->var.width = -1; |
290 | info->var.accel_flags = 0; |
291 | info->var.vmode = FB_VMODE_NONINTERLACED; |
292 | |
293 | info->fbops = &gx1fb_ops; |
294 | info->node = -1; |
295 | |
296 | info->pseudo_palette = (void *)par + sizeof(struct geodefb_par); |
297 | |
298 | info->var.grayscale = 0; |
299 | |
300 | /* CRT and panel options */ |
301 | par->enable_crt = crt_option; |
302 | if (parse_panel_option(info) < 0) |
303 | printk(KERN_WARNING "gx1fb: invalid 'panel' option -- disabling flat panel\n" ); |
304 | if (!par->panel_x) |
305 | par->enable_crt = 1; /* fall back to CRT if no panel is specified */ |
306 | |
307 | if (fb_alloc_cmap(cmap: &info->cmap, len: 256, transp: 0) < 0) { |
308 | framebuffer_release(info); |
309 | return NULL; |
310 | } |
311 | return info; |
312 | } |
313 | |
314 | static int gx1fb_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
315 | { |
316 | struct geodefb_par *par; |
317 | struct fb_info *info; |
318 | int ret; |
319 | |
320 | ret = aperture_remove_conflicting_pci_devices(pdev, name: "gx1fb" ); |
321 | if (ret) |
322 | return ret; |
323 | |
324 | info = gx1fb_init_fbinfo(dev: &pdev->dev); |
325 | if (!info) |
326 | return -ENOMEM; |
327 | par = info->par; |
328 | |
329 | /* GX1 display controller and CS5530 video device */ |
330 | par->dc_ops = &gx1_dc_ops; |
331 | par->vid_ops = &cs5530_vid_ops; |
332 | |
333 | if ((ret = gx1fb_map_video_memory(info, dev: pdev)) < 0) { |
334 | dev_err(&pdev->dev, "failed to map frame buffer or controller registers\n" ); |
335 | goto err; |
336 | } |
337 | |
338 | ret = fb_find_mode(var: &info->var, info, mode_option, |
339 | db: gx1_modedb, ARRAY_SIZE(gx1_modedb), NULL, default_bpp: 16); |
340 | if (ret == 0 || ret == 4) { |
341 | dev_err(&pdev->dev, "could not find valid video mode\n" ); |
342 | ret = -EINVAL; |
343 | goto err; |
344 | } |
345 | |
346 | /* Clear the frame buffer of garbage. */ |
347 | memset_io(info->screen_base, 0, info->fix.smem_len); |
348 | |
349 | gx1fb_check_var(var: &info->var, info); |
350 | gx1fb_set_par(info); |
351 | |
352 | if (register_framebuffer(fb_info: info) < 0) { |
353 | ret = -EINVAL; |
354 | goto err; |
355 | } |
356 | pci_set_drvdata(pdev, data: info); |
357 | fb_info(info, "%s frame buffer device\n" , info->fix.id); |
358 | return 0; |
359 | |
360 | err: |
361 | if (info->screen_base) { |
362 | iounmap(addr: info->screen_base); |
363 | pci_release_region(pdev, 0); |
364 | } |
365 | if (par->vid_regs) { |
366 | iounmap(addr: par->vid_regs); |
367 | pci_release_region(pdev, 1); |
368 | } |
369 | if (par->dc_regs) { |
370 | iounmap(addr: par->dc_regs); |
371 | release_mem_region(gx1_gx_base() + 0x8300, 0x100); |
372 | } |
373 | |
374 | fb_dealloc_cmap(cmap: &info->cmap); |
375 | framebuffer_release(info); |
376 | |
377 | return ret; |
378 | } |
379 | |
380 | static void gx1fb_remove(struct pci_dev *pdev) |
381 | { |
382 | struct fb_info *info = pci_get_drvdata(pdev); |
383 | struct geodefb_par *par = info->par; |
384 | |
385 | unregister_framebuffer(fb_info: info); |
386 | |
387 | iounmap(addr: (void __iomem *)info->screen_base); |
388 | pci_release_region(pdev, 0); |
389 | |
390 | iounmap(addr: par->vid_regs); |
391 | pci_release_region(pdev, 1); |
392 | |
393 | iounmap(addr: par->dc_regs); |
394 | release_mem_region(gx1_gx_base() + 0x8300, 0x100); |
395 | |
396 | fb_dealloc_cmap(cmap: &info->cmap); |
397 | |
398 | framebuffer_release(info); |
399 | } |
400 | |
401 | #ifndef MODULE |
402 | static void __init gx1fb_setup(char *options) |
403 | { |
404 | char *this_opt; |
405 | |
406 | if (!options || !*options) |
407 | return; |
408 | |
409 | while ((this_opt = strsep(&options, "," ))) { |
410 | if (!*this_opt) |
411 | continue; |
412 | |
413 | if (!strncmp(this_opt, "mode:" , 5)) |
414 | strscpy(mode_option, this_opt + 5, sizeof(mode_option)); |
415 | else if (!strncmp(this_opt, "crt:" , 4)) |
416 | crt_option = !!simple_strtoul(this_opt + 4, NULL, 0); |
417 | else if (!strncmp(this_opt, "panel:" , 6)) |
418 | strscpy(panel_option, this_opt + 6, sizeof(panel_option)); |
419 | else |
420 | strscpy(mode_option, this_opt, sizeof(mode_option)); |
421 | } |
422 | } |
423 | #endif |
424 | |
425 | static struct pci_device_id gx1fb_id_table[] = { |
426 | { PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_VIDEO, |
427 | PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, |
428 | 0xff0000, 0 }, |
429 | { 0, } |
430 | }; |
431 | |
432 | MODULE_DEVICE_TABLE(pci, gx1fb_id_table); |
433 | |
434 | static struct pci_driver gx1fb_driver = { |
435 | .name = "gx1fb" , |
436 | .id_table = gx1fb_id_table, |
437 | .probe = gx1fb_probe, |
438 | .remove = gx1fb_remove, |
439 | }; |
440 | |
441 | static int __init gx1fb_init(void) |
442 | { |
443 | #ifndef MODULE |
444 | char *option = NULL; |
445 | #endif |
446 | |
447 | if (fb_modesetting_disabled(drvname: "gx1fb" )) |
448 | return -ENODEV; |
449 | |
450 | #ifndef MODULE |
451 | if (fb_get_options(name: "gx1fb" , option: &option)) |
452 | return -ENODEV; |
453 | gx1fb_setup(options: option); |
454 | #endif |
455 | return pci_register_driver(&gx1fb_driver); |
456 | } |
457 | |
458 | static void gx1fb_cleanup(void) |
459 | { |
460 | pci_unregister_driver(dev: &gx1fb_driver); |
461 | } |
462 | |
463 | module_init(gx1fb_init); |
464 | module_exit(gx1fb_cleanup); |
465 | |
466 | module_param_string(mode, mode_option, sizeof(mode_option), 0444); |
467 | MODULE_PARM_DESC(mode, "video mode (<x>x<y>[-<bpp>][@<refr>])" ); |
468 | |
469 | module_param_named(crt, crt_option, int, 0444); |
470 | MODULE_PARM_DESC(crt, "enable CRT output. 0 = off, 1 = on (default)" ); |
471 | |
472 | module_param_string(panel, panel_option, sizeof(panel_option), 0444); |
473 | MODULE_PARM_DESC(panel, "size of attached flat panel (<x>x<y>)" ); |
474 | |
475 | MODULE_DESCRIPTION("framebuffer driver for the AMD Geode GX1" ); |
476 | MODULE_LICENSE("GPL" ); |
477 | |