18c2ecf20Sopenharmony_ci/* sunxvr2500.c: Sun 3DLABS XVR-2500 et al. fb driver for sparc64 systems
28c2ecf20Sopenharmony_ci *
38c2ecf20Sopenharmony_ci * License: GPL
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2007 David S. Miller (davem@davemloft.net)
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/fb.h>
108c2ecf20Sopenharmony_ci#include <linux/pci.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/of_device.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <asm/io.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistruct s3d_info {
178c2ecf20Sopenharmony_ci	struct fb_info		*info;
188c2ecf20Sopenharmony_ci	struct pci_dev		*pdev;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	char __iomem		*fb_base;
218c2ecf20Sopenharmony_ci	unsigned long		fb_base_phys;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	struct device_node	*of_node;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	unsigned int		width;
268c2ecf20Sopenharmony_ci	unsigned int		height;
278c2ecf20Sopenharmony_ci	unsigned int		depth;
288c2ecf20Sopenharmony_ci	unsigned int		fb_size;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	u32			pseudo_palette[16];
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic int s3d_get_props(struct s3d_info *sp)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	sp->width = of_getintprop_default(sp->of_node, "width", 0);
368c2ecf20Sopenharmony_ci	sp->height = of_getintprop_default(sp->of_node, "height", 0);
378c2ecf20Sopenharmony_ci	sp->depth = of_getintprop_default(sp->of_node, "depth", 8);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	if (!sp->width || !sp->height) {
408c2ecf20Sopenharmony_ci		printk(KERN_ERR "s3d: Critical properties missing for %s\n",
418c2ecf20Sopenharmony_ci		       pci_name(sp->pdev));
428c2ecf20Sopenharmony_ci		return -EINVAL;
438c2ecf20Sopenharmony_ci	}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	return 0;
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic int s3d_setcolreg(unsigned regno,
498c2ecf20Sopenharmony_ci			 unsigned red, unsigned green, unsigned blue,
508c2ecf20Sopenharmony_ci			 unsigned transp, struct fb_info *info)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	u32 value;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (regno < 16) {
558c2ecf20Sopenharmony_ci		red >>= 8;
568c2ecf20Sopenharmony_ci		green >>= 8;
578c2ecf20Sopenharmony_ci		blue >>= 8;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci		value = (blue << 24) | (green << 16) | (red << 8);
608c2ecf20Sopenharmony_ci		((u32 *)info->pseudo_palette)[regno] = value;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return 0;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic const struct fb_ops s3d_ops = {
678c2ecf20Sopenharmony_ci	.owner			= THIS_MODULE,
688c2ecf20Sopenharmony_ci	.fb_setcolreg		= s3d_setcolreg,
698c2ecf20Sopenharmony_ci	.fb_fillrect		= cfb_fillrect,
708c2ecf20Sopenharmony_ci	.fb_copyarea		= cfb_copyarea,
718c2ecf20Sopenharmony_ci	.fb_imageblit		= cfb_imageblit,
728c2ecf20Sopenharmony_ci};
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int s3d_set_fbinfo(struct s3d_info *sp)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	struct fb_info *info = sp->info;
778c2ecf20Sopenharmony_ci	struct fb_var_screeninfo *var = &info->var;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	info->flags = FBINFO_DEFAULT;
808c2ecf20Sopenharmony_ci	info->fbops = &s3d_ops;
818c2ecf20Sopenharmony_ci	info->screen_base = sp->fb_base;
828c2ecf20Sopenharmony_ci	info->screen_size = sp->fb_size;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	info->pseudo_palette = sp->pseudo_palette;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	/* Fill fix common fields */
878c2ecf20Sopenharmony_ci	strlcpy(info->fix.id, "s3d", sizeof(info->fix.id));
888c2ecf20Sopenharmony_ci        info->fix.smem_start = sp->fb_base_phys;
898c2ecf20Sopenharmony_ci        info->fix.smem_len = sp->fb_size;
908c2ecf20Sopenharmony_ci        info->fix.type = FB_TYPE_PACKED_PIXELS;
918c2ecf20Sopenharmony_ci	if (sp->depth == 32 || sp->depth == 24)
928c2ecf20Sopenharmony_ci		info->fix.visual = FB_VISUAL_TRUECOLOR;
938c2ecf20Sopenharmony_ci	else
948c2ecf20Sopenharmony_ci		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	var->xres = sp->width;
978c2ecf20Sopenharmony_ci	var->yres = sp->height;
988c2ecf20Sopenharmony_ci	var->xres_virtual = var->xres;
998c2ecf20Sopenharmony_ci	var->yres_virtual = var->yres;
1008c2ecf20Sopenharmony_ci	var->bits_per_pixel = sp->depth;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	var->red.offset = 8;
1038c2ecf20Sopenharmony_ci	var->red.length = 8;
1048c2ecf20Sopenharmony_ci	var->green.offset = 16;
1058c2ecf20Sopenharmony_ci	var->green.length = 8;
1068c2ecf20Sopenharmony_ci	var->blue.offset = 24;
1078c2ecf20Sopenharmony_ci	var->blue.length = 8;
1088c2ecf20Sopenharmony_ci	var->transp.offset = 0;
1098c2ecf20Sopenharmony_ci	var->transp.length = 0;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (fb_alloc_cmap(&info->cmap, 256, 0)) {
1128c2ecf20Sopenharmony_ci		printk(KERN_ERR "s3d: Cannot allocate color map.\n");
1138c2ecf20Sopenharmony_ci		return -ENOMEM;
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci        return 0;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int s3d_pci_register(struct pci_dev *pdev,
1208c2ecf20Sopenharmony_ci			    const struct pci_device_id *ent)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct fb_info *info;
1238c2ecf20Sopenharmony_ci	struct s3d_info *sp;
1248c2ecf20Sopenharmony_ci	int err;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	err = pci_enable_device(pdev);
1278c2ecf20Sopenharmony_ci	if (err < 0) {
1288c2ecf20Sopenharmony_ci		printk(KERN_ERR "s3d: Cannot enable PCI device %s\n",
1298c2ecf20Sopenharmony_ci		       pci_name(pdev));
1308c2ecf20Sopenharmony_ci		goto err_out;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct s3d_info), &pdev->dev);
1348c2ecf20Sopenharmony_ci	if (!info) {
1358c2ecf20Sopenharmony_ci		err = -ENOMEM;
1368c2ecf20Sopenharmony_ci		goto err_disable;
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	sp = info->par;
1408c2ecf20Sopenharmony_ci	sp->info = info;
1418c2ecf20Sopenharmony_ci	sp->pdev = pdev;
1428c2ecf20Sopenharmony_ci	sp->of_node = pci_device_to_OF_node(pdev);
1438c2ecf20Sopenharmony_ci	if (!sp->of_node) {
1448c2ecf20Sopenharmony_ci		printk(KERN_ERR "s3d: Cannot find OF node of %s\n",
1458c2ecf20Sopenharmony_ci		       pci_name(pdev));
1468c2ecf20Sopenharmony_ci		err = -ENODEV;
1478c2ecf20Sopenharmony_ci		goto err_release_fb;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	sp->fb_base_phys = pci_resource_start (pdev, 1);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	err = pci_request_region(pdev, 1, "s3d framebuffer");
1538c2ecf20Sopenharmony_ci	if (err < 0) {
1548c2ecf20Sopenharmony_ci		printk("s3d: Cannot request region 1 for %s\n",
1558c2ecf20Sopenharmony_ci		       pci_name(pdev));
1568c2ecf20Sopenharmony_ci		goto err_release_fb;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	err = s3d_get_props(sp);
1608c2ecf20Sopenharmony_ci	if (err)
1618c2ecf20Sopenharmony_ci		goto err_release_pci;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	/* XXX 'linebytes' is often wrong, it is equal to the width
1648c2ecf20Sopenharmony_ci	 * XXX with depth of 32 on my XVR-2500 which is clearly not
1658c2ecf20Sopenharmony_ci	 * XXX right.  So we don't try to use it.
1668c2ecf20Sopenharmony_ci	 */
1678c2ecf20Sopenharmony_ci	switch (sp->depth) {
1688c2ecf20Sopenharmony_ci	case 8:
1698c2ecf20Sopenharmony_ci		info->fix.line_length = sp->width;
1708c2ecf20Sopenharmony_ci		break;
1718c2ecf20Sopenharmony_ci	case 16:
1728c2ecf20Sopenharmony_ci		info->fix.line_length = sp->width * 2;
1738c2ecf20Sopenharmony_ci		break;
1748c2ecf20Sopenharmony_ci	case 24:
1758c2ecf20Sopenharmony_ci		info->fix.line_length = sp->width * 3;
1768c2ecf20Sopenharmony_ci		break;
1778c2ecf20Sopenharmony_ci	case 32:
1788c2ecf20Sopenharmony_ci		info->fix.line_length = sp->width * 4;
1798c2ecf20Sopenharmony_ci		break;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci	sp->fb_size = info->fix.line_length * sp->height;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	sp->fb_base = ioremap(sp->fb_base_phys, sp->fb_size);
1848c2ecf20Sopenharmony_ci	if (!sp->fb_base) {
1858c2ecf20Sopenharmony_ci		err = -ENOMEM;
1868c2ecf20Sopenharmony_ci		goto err_release_pci;
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	err = s3d_set_fbinfo(sp);
1908c2ecf20Sopenharmony_ci	if (err)
1918c2ecf20Sopenharmony_ci		goto err_unmap_fb;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	pci_set_drvdata(pdev, info);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	printk("s3d: Found device at %s\n", pci_name(pdev));
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	err = register_framebuffer(info);
1988c2ecf20Sopenharmony_ci	if (err < 0) {
1998c2ecf20Sopenharmony_ci		printk(KERN_ERR "s3d: Could not register framebuffer %s\n",
2008c2ecf20Sopenharmony_ci		       pci_name(pdev));
2018c2ecf20Sopenharmony_ci		goto err_unmap_fb;
2028c2ecf20Sopenharmony_ci	}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	return 0;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cierr_unmap_fb:
2078c2ecf20Sopenharmony_ci	iounmap(sp->fb_base);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cierr_release_pci:
2108c2ecf20Sopenharmony_ci	pci_release_region(pdev, 1);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cierr_release_fb:
2138c2ecf20Sopenharmony_ci        framebuffer_release(info);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cierr_disable:
2168c2ecf20Sopenharmony_ci	pci_disable_device(pdev);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_cierr_out:
2198c2ecf20Sopenharmony_ci	return err;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic const struct pci_device_id s3d_pci_table[] = {
2238c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002c),	},
2248c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002d),	},
2258c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002e),	},
2268c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x002f),	},
2278c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0030),	},
2288c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0031),	},
2298c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0032),	},
2308c2ecf20Sopenharmony_ci	{	PCI_DEVICE(PCI_VENDOR_ID_3DLABS, 0x0033),	},
2318c2ecf20Sopenharmony_ci	{ 0, }
2328c2ecf20Sopenharmony_ci};
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic struct pci_driver s3d_driver = {
2358c2ecf20Sopenharmony_ci	.driver = {
2368c2ecf20Sopenharmony_ci		.suppress_bind_attrs = true,
2378c2ecf20Sopenharmony_ci	},
2388c2ecf20Sopenharmony_ci	.name		= "s3d",
2398c2ecf20Sopenharmony_ci	.id_table	= s3d_pci_table,
2408c2ecf20Sopenharmony_ci	.probe		= s3d_pci_register,
2418c2ecf20Sopenharmony_ci};
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_cistatic int __init s3d_init(void)
2448c2ecf20Sopenharmony_ci{
2458c2ecf20Sopenharmony_ci	if (fb_get_options("s3d", NULL))
2468c2ecf20Sopenharmony_ci		return -ENODEV;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return pci_register_driver(&s3d_driver);
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_cidevice_initcall(s3d_init);
251