162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Framebuffer driver for mdpy (mediated virtual pci display device).
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * See mdpy-defs.h for device specs
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *   (c) Gerd Hoffmann <kraxel@redhat.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Using some code snippets from simplefb and cirrusfb.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it
1262306a36Sopenharmony_ci * under the terms and conditions of the GNU General Public License,
1362306a36Sopenharmony_ci * version 2, as published by the Free Software Foundation.
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * This program is distributed in the hope it will be useful, but WITHOUT
1662306a36Sopenharmony_ci * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1762306a36Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
1862306a36Sopenharmony_ci * more details.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci#include <linux/errno.h>
2162306a36Sopenharmony_ci#include <linux/fb.h>
2262306a36Sopenharmony_ci#include <linux/io.h>
2362306a36Sopenharmony_ci#include <linux/pci.h>
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <drm/drm_fourcc.h>
2662306a36Sopenharmony_ci#include "mdpy-defs.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic const struct fb_fix_screeninfo mdpy_fb_fix = {
2962306a36Sopenharmony_ci	.id		= "mdpy-fb",
3062306a36Sopenharmony_ci	.type		= FB_TYPE_PACKED_PIXELS,
3162306a36Sopenharmony_ci	.visual		= FB_VISUAL_TRUECOLOR,
3262306a36Sopenharmony_ci	.accel		= FB_ACCEL_NONE,
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic const struct fb_var_screeninfo mdpy_fb_var = {
3662306a36Sopenharmony_ci	.height		= -1,
3762306a36Sopenharmony_ci	.width		= -1,
3862306a36Sopenharmony_ci	.activate	= FB_ACTIVATE_NOW,
3962306a36Sopenharmony_ci	.vmode		= FB_VMODE_NONINTERLACED,
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	.bits_per_pixel = 32,
4262306a36Sopenharmony_ci	.transp.offset	= 24,
4362306a36Sopenharmony_ci	.red.offset	= 16,
4462306a36Sopenharmony_ci	.green.offset	= 8,
4562306a36Sopenharmony_ci	.blue.offset	= 0,
4662306a36Sopenharmony_ci	.transp.length	= 8,
4762306a36Sopenharmony_ci	.red.length	= 8,
4862306a36Sopenharmony_ci	.green.length	= 8,
4962306a36Sopenharmony_ci	.blue.length	= 8,
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define PSEUDO_PALETTE_SIZE 16
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistruct mdpy_fb_par {
5562306a36Sopenharmony_ci	u32 palette[PSEUDO_PALETTE_SIZE];
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int mdpy_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
5962306a36Sopenharmony_ci			      u_int transp, struct fb_info *info)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	u32 *pal = info->pseudo_palette;
6262306a36Sopenharmony_ci	u32 cr = red >> (16 - info->var.red.length);
6362306a36Sopenharmony_ci	u32 cg = green >> (16 - info->var.green.length);
6462306a36Sopenharmony_ci	u32 cb = blue >> (16 - info->var.blue.length);
6562306a36Sopenharmony_ci	u32 value, mask;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (regno >= PSEUDO_PALETTE_SIZE)
6862306a36Sopenharmony_ci		return -EINVAL;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	value = (cr << info->var.red.offset) |
7162306a36Sopenharmony_ci		(cg << info->var.green.offset) |
7262306a36Sopenharmony_ci		(cb << info->var.blue.offset);
7362306a36Sopenharmony_ci	if (info->var.transp.length > 0) {
7462306a36Sopenharmony_ci		mask = (1 << info->var.transp.length) - 1;
7562306a36Sopenharmony_ci		mask <<= info->var.transp.offset;
7662306a36Sopenharmony_ci		value |= mask;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci	pal[regno] = value;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return 0;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic void mdpy_fb_destroy(struct fb_info *info)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	if (info->screen_base)
8662306a36Sopenharmony_ci		iounmap(info->screen_base);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic const struct fb_ops mdpy_fb_ops = {
9062306a36Sopenharmony_ci	.owner		= THIS_MODULE,
9162306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
9262306a36Sopenharmony_ci	.fb_destroy	= mdpy_fb_destroy,
9362306a36Sopenharmony_ci	.fb_setcolreg	= mdpy_fb_setcolreg,
9462306a36Sopenharmony_ci};
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic int mdpy_fb_probe(struct pci_dev *pdev,
9762306a36Sopenharmony_ci			 const struct pci_device_id *ent)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct fb_info *info;
10062306a36Sopenharmony_ci	struct mdpy_fb_par *par;
10162306a36Sopenharmony_ci	u32 format, width, height;
10262306a36Sopenharmony_ci	int ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ret = pci_enable_device(pdev);
10562306a36Sopenharmony_ci	if (ret < 0)
10662306a36Sopenharmony_ci		return ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	ret = pci_request_regions(pdev, "mdpy-fb");
10962306a36Sopenharmony_ci	if (ret < 0)
11062306a36Sopenharmony_ci		goto err_disable_dev;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	pci_read_config_dword(pdev, MDPY_FORMAT_OFFSET, &format);
11362306a36Sopenharmony_ci	pci_read_config_dword(pdev, MDPY_WIDTH_OFFSET,	&width);
11462306a36Sopenharmony_ci	pci_read_config_dword(pdev, MDPY_HEIGHT_OFFSET, &height);
11562306a36Sopenharmony_ci	if (format != DRM_FORMAT_XRGB8888) {
11662306a36Sopenharmony_ci		pci_err(pdev, "format mismatch (0x%x != 0x%x)\n",
11762306a36Sopenharmony_ci			format, DRM_FORMAT_XRGB8888);
11862306a36Sopenharmony_ci		ret = -EINVAL;
11962306a36Sopenharmony_ci		goto err_release_regions;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci	if (width < 100	 || width > 10000) {
12262306a36Sopenharmony_ci		pci_err(pdev, "width (%d) out of range\n", width);
12362306a36Sopenharmony_ci		ret = -EINVAL;
12462306a36Sopenharmony_ci		goto err_release_regions;
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci	if (height < 100 || height > 10000) {
12762306a36Sopenharmony_ci		pci_err(pdev, "height (%d) out of range\n", height);
12862306a36Sopenharmony_ci		ret = -EINVAL;
12962306a36Sopenharmony_ci		goto err_release_regions;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci	pci_info(pdev, "mdpy found: %dx%d framebuffer\n",
13262306a36Sopenharmony_ci		 width, height);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct mdpy_fb_par), &pdev->dev);
13562306a36Sopenharmony_ci	if (!info) {
13662306a36Sopenharmony_ci		ret = -ENOMEM;
13762306a36Sopenharmony_ci		goto err_release_regions;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci	pci_set_drvdata(pdev, info);
14062306a36Sopenharmony_ci	par = info->par;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	info->fix = mdpy_fb_fix;
14362306a36Sopenharmony_ci	info->fix.smem_start = pci_resource_start(pdev, 0);
14462306a36Sopenharmony_ci	info->fix.smem_len = pci_resource_len(pdev, 0);
14562306a36Sopenharmony_ci	info->fix.line_length = width * 4;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	info->var = mdpy_fb_var;
14862306a36Sopenharmony_ci	info->var.xres = width;
14962306a36Sopenharmony_ci	info->var.yres = height;
15062306a36Sopenharmony_ci	info->var.xres_virtual = width;
15162306a36Sopenharmony_ci	info->var.yres_virtual = height;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	info->screen_size = info->fix.smem_len;
15462306a36Sopenharmony_ci	info->screen_base = ioremap(info->fix.smem_start,
15562306a36Sopenharmony_ci				    info->screen_size);
15662306a36Sopenharmony_ci	if (!info->screen_base) {
15762306a36Sopenharmony_ci		pci_err(pdev, "ioremap(pcibar) failed\n");
15862306a36Sopenharmony_ci		ret = -EIO;
15962306a36Sopenharmony_ci		goto err_release_fb;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	info->fbops = &mdpy_fb_ops;
16362306a36Sopenharmony_ci	info->pseudo_palette = par->palette;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	ret = register_framebuffer(info);
16662306a36Sopenharmony_ci	if (ret < 0) {
16762306a36Sopenharmony_ci		pci_err(pdev, "mdpy-fb device register failed: %d\n", ret);
16862306a36Sopenharmony_ci		goto err_unmap;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	pci_info(pdev, "fb%d registered\n", info->node);
17262306a36Sopenharmony_ci	return 0;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cierr_unmap:
17562306a36Sopenharmony_ci	iounmap(info->screen_base);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cierr_release_fb:
17862306a36Sopenharmony_ci	framebuffer_release(info);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cierr_release_regions:
18162306a36Sopenharmony_ci	pci_release_regions(pdev);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cierr_disable_dev:
18462306a36Sopenharmony_ci	pci_disable_device(pdev);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return ret;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void mdpy_fb_remove(struct pci_dev *pdev)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct fb_info *info = pci_get_drvdata(pdev);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	unregister_framebuffer(info);
19462306a36Sopenharmony_ci	iounmap(info->screen_base);
19562306a36Sopenharmony_ci	framebuffer_release(info);
19662306a36Sopenharmony_ci	pci_release_regions(pdev);
19762306a36Sopenharmony_ci	pci_disable_device(pdev);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic struct pci_device_id mdpy_fb_pci_table[] = {
20162306a36Sopenharmony_ci	{
20262306a36Sopenharmony_ci		.vendor	   = MDPY_PCI_VENDOR_ID,
20362306a36Sopenharmony_ci		.device	   = MDPY_PCI_DEVICE_ID,
20462306a36Sopenharmony_ci		.subvendor = MDPY_PCI_SUBVENDOR_ID,
20562306a36Sopenharmony_ci		.subdevice = MDPY_PCI_SUBDEVICE_ID,
20662306a36Sopenharmony_ci	}, {
20762306a36Sopenharmony_ci		/* end of list */
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci};
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic struct pci_driver mdpy_fb_pci_driver = {
21262306a36Sopenharmony_ci	.name		= "mdpy-fb",
21362306a36Sopenharmony_ci	.id_table	= mdpy_fb_pci_table,
21462306a36Sopenharmony_ci	.probe		= mdpy_fb_probe,
21562306a36Sopenharmony_ci	.remove		= mdpy_fb_remove,
21662306a36Sopenharmony_ci};
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int __init mdpy_fb_init(void)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	int ret;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	ret = pci_register_driver(&mdpy_fb_pci_driver);
22362306a36Sopenharmony_ci	if (ret)
22462306a36Sopenharmony_ci		return ret;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return 0;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cimodule_init(mdpy_fb_init);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, mdpy_fb_pci_table);
23262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
233