1 | /* sunxvr2500.c: Sun 3DLABS XVR-2500 et al. fb driver for sparc64 systems |
2 | * |
3 | * License: GPL |
4 | * |
5 | * Copyright (C) 2007 David S. Miller (davem@davemloft.net) |
6 | */ |
7 | |
8 | #include <linux/aperture.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/fb.h> |
11 | #include <linux/pci.h> |
12 | #include <linux/init.h> |
13 | #include <linux/of.h> |
14 | |
15 | #include <asm/io.h> |
16 | |
17 | struct s3d_info { |
18 | struct fb_info *info; |
19 | struct pci_dev *pdev; |
20 | |
21 | char __iomem *fb_base; |
22 | unsigned long fb_base_phys; |
23 | |
24 | struct device_node *of_node; |
25 | |
26 | unsigned int width; |
27 | unsigned int height; |
28 | unsigned int depth; |
29 | unsigned int fb_size; |
30 | |
31 | u32 pseudo_palette[16]; |
32 | }; |
33 | |
34 | static int s3d_get_props(struct s3d_info *sp) |
35 | { |
36 | sp->width = of_getintprop_default(sp->of_node, "width" , 0); |
37 | sp->height = of_getintprop_default(sp->of_node, "height" , 0); |
38 | sp->depth = of_getintprop_default(sp->of_node, "depth" , 8); |
39 | |
40 | if (!sp->width || !sp->height) { |
41 | printk(KERN_ERR "s3d: Critical properties missing for %s\n" , |
42 | pci_name(sp->pdev)); |
43 | return -EINVAL; |
44 | } |
45 | |
46 | return 0; |
47 | } |
48 | |
49 | static int s3d_setcolreg(unsigned regno, |
50 | unsigned red, unsigned green, unsigned blue, |
51 | unsigned transp, struct fb_info *info) |
52 | { |
53 | u32 value; |
54 | |
55 | if (regno < 16) { |
56 | red >>= 8; |
57 | green >>= 8; |
58 | blue >>= 8; |
59 | |
60 | value = (blue << 24) | (green << 16) | (red << 8); |
61 | ((u32 *)info->pseudo_palette)[regno] = value; |
62 | } |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static const struct fb_ops s3d_ops = { |
68 | .owner = THIS_MODULE, |
69 | FB_DEFAULT_IOMEM_OPS, |
70 | .fb_setcolreg = s3d_setcolreg, |
71 | }; |
72 | |
73 | static int s3d_set_fbinfo(struct s3d_info *sp) |
74 | { |
75 | struct fb_info *info = sp->info; |
76 | struct fb_var_screeninfo *var = &info->var; |
77 | |
78 | info->fbops = &s3d_ops; |
79 | info->screen_base = sp->fb_base; |
80 | info->screen_size = sp->fb_size; |
81 | |
82 | info->pseudo_palette = sp->pseudo_palette; |
83 | |
84 | /* Fill fix common fields */ |
85 | strscpy(info->fix.id, "s3d" , sizeof(info->fix.id)); |
86 | info->fix.smem_start = sp->fb_base_phys; |
87 | info->fix.smem_len = sp->fb_size; |
88 | info->fix.type = FB_TYPE_PACKED_PIXELS; |
89 | if (sp->depth == 32 || sp->depth == 24) |
90 | info->fix.visual = FB_VISUAL_TRUECOLOR; |
91 | else |
92 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; |
93 | |
94 | var->xres = sp->width; |
95 | var->yres = sp->height; |
96 | var->xres_virtual = var->xres; |
97 | var->yres_virtual = var->yres; |
98 | var->bits_per_pixel = sp->depth; |
99 | |
100 | var->red.offset = 8; |
101 | var->red.length = 8; |
102 | var->green.offset = 16; |
103 | var->green.length = 8; |
104 | var->blue.offset = 24; |
105 | var->blue.length = 8; |
106 | var->transp.offset = 0; |
107 | var->transp.length = 0; |
108 | |
109 | if (fb_alloc_cmap(cmap: &info->cmap, len: 256, transp: 0)) { |
110 | printk(KERN_ERR "s3d: Cannot allocate color map.\n" ); |
111 | return -ENOMEM; |
112 | } |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static int s3d_pci_register(struct pci_dev *pdev, |
118 | const struct pci_device_id *ent) |
119 | { |
120 | struct fb_info *info; |
121 | struct s3d_info *sp; |
122 | int err; |
123 | |
124 | err = aperture_remove_conflicting_pci_devices(pdev, name: "s3dfb" ); |
125 | if (err) |
126 | return err; |
127 | |
128 | err = pci_enable_device(dev: pdev); |
129 | if (err < 0) { |
130 | printk(KERN_ERR "s3d: Cannot enable PCI device %s\n" , |
131 | pci_name(pdev)); |
132 | goto err_out; |
133 | } |
134 | |
135 | info = framebuffer_alloc(size: sizeof(struct s3d_info), dev: &pdev->dev); |
136 | if (!info) { |
137 | err = -ENOMEM; |
138 | goto err_disable; |
139 | } |
140 | |
141 | sp = info->par; |
142 | sp->info = info; |
143 | sp->pdev = pdev; |
144 | sp->of_node = pci_device_to_OF_node(pdev); |
145 | if (!sp->of_node) { |
146 | printk(KERN_ERR "s3d: Cannot find OF node of %s\n" , |
147 | pci_name(pdev)); |
148 | err = -ENODEV; |
149 | goto err_release_fb; |
150 | } |
151 | |
152 | sp->fb_base_phys = pci_resource_start (pdev, 1); |
153 | |
154 | err = pci_request_region(pdev, 1, "s3d framebuffer" ); |
155 | if (err < 0) { |
156 | printk("s3d: Cannot request region 1 for %s\n" , |
157 | pci_name(pdev)); |
158 | goto err_release_fb; |
159 | } |
160 | |
161 | err = s3d_get_props(sp); |
162 | if (err) |
163 | goto err_release_pci; |
164 | |
165 | /* XXX 'linebytes' is often wrong, it is equal to the width |
166 | * XXX with depth of 32 on my XVR-2500 which is clearly not |
167 | * XXX right. So we don't try to use it. |
168 | */ |
169 | switch (sp->depth) { |
170 | case 8: |
171 | info->fix.line_length = sp->width; |
172 | break; |
173 | case 16: |
174 | info->fix.line_length = sp->width * 2; |
175 | break; |
176 | case 24: |
177 | info->fix.line_length = sp->width * 3; |
178 | break; |
179 | case 32: |
180 | info->fix.line_length = sp->width * 4; |
181 | break; |
182 | } |
183 | sp->fb_size = info->fix.line_length * sp->height; |
184 | |
185 | sp->fb_base = ioremap(offset: sp->fb_base_phys, size: sp->fb_size); |
186 | if (!sp->fb_base) { |
187 | err = -ENOMEM; |
188 | goto err_release_pci; |
189 | } |
190 | |
191 | err = s3d_set_fbinfo(sp); |
192 | if (err) |
193 | goto err_unmap_fb; |
194 | |
195 | pci_set_drvdata(pdev, data: info); |
196 | |
197 | printk("s3d: Found device at %s\n" , pci_name(pdev)); |
198 | |
199 | err = register_framebuffer(fb_info: info); |
200 | if (err < 0) { |
201 | printk(KERN_ERR "s3d: Could not register framebuffer %s\n" , |
202 | pci_name(pdev)); |
203 | goto err_unmap_fb; |
204 | } |
205 | |
206 | return 0; |
207 | |
208 | err_unmap_fb: |
209 | iounmap(addr: sp->fb_base); |
210 | |
211 | err_release_pci: |
212 | pci_release_region(pdev, 1); |
213 | |
214 | err_release_fb: |
215 | framebuffer_release(info); |
216 | |
217 | err_disable: |
218 | pci_disable_device(dev: pdev); |
219 | |
220 | err_out: |
221 | return err; |
222 | } |
223 | |
224 | static const struct pci_device_id s3d_pci_table[] = { |
225 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002c), }, |
226 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002d), }, |
227 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002e), }, |
228 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002f), }, |
229 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0030), }, |
230 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0031), }, |
231 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0032), }, |
232 | { PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0033), }, |
233 | { 0, } |
234 | }; |
235 | |
236 | static struct pci_driver s3d_driver = { |
237 | .driver = { |
238 | .suppress_bind_attrs = true, |
239 | }, |
240 | .name = "s3d" , |
241 | .id_table = s3d_pci_table, |
242 | .probe = s3d_pci_register, |
243 | }; |
244 | |
245 | static int __init s3d_init(void) |
246 | { |
247 | if (fb_modesetting_disabled(drvname: "s3d" )) |
248 | return -ENODEV; |
249 | |
250 | if (fb_get_options(name: "s3d" , NULL)) |
251 | return -ENODEV; |
252 | |
253 | return pci_register_driver(&s3d_driver); |
254 | } |
255 | device_initcall(s3d_init); |
256 | |