162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * OpenCores VGA/LCD 2.0 core frame buffer driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2013 Stefan Kristiansson, stefan.kristiansson@saunalahti.fi
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public License
762306a36Sopenharmony_ci * version 2.  This program is licensed "as is" without any warranty of any
862306a36Sopenharmony_ci * kind, whether express or implied.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/delay.h>
1262306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/fb.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci#include <linux/kernel.h>
1862306a36Sopenharmony_ci#include <linux/mm.h>
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/of.h>
2162306a36Sopenharmony_ci#include <linux/platform_device.h>
2262306a36Sopenharmony_ci#include <linux/string.h>
2362306a36Sopenharmony_ci#include <linux/slab.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/* OCFB register defines */
2662306a36Sopenharmony_ci#define OCFB_CTRL	0x000
2762306a36Sopenharmony_ci#define OCFB_STAT	0x004
2862306a36Sopenharmony_ci#define OCFB_HTIM	0x008
2962306a36Sopenharmony_ci#define OCFB_VTIM	0x00c
3062306a36Sopenharmony_ci#define OCFB_HVLEN	0x010
3162306a36Sopenharmony_ci#define OCFB_VBARA	0x014
3262306a36Sopenharmony_ci#define OCFB_PALETTE	0x800
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define OCFB_CTRL_VEN	0x00000001 /* Video Enable */
3562306a36Sopenharmony_ci#define OCFB_CTRL_HIE	0x00000002 /* HSync Interrupt Enable */
3662306a36Sopenharmony_ci#define OCFB_CTRL_PC	0x00000800 /* 8-bit Pseudo Color Enable*/
3762306a36Sopenharmony_ci#define OCFB_CTRL_CD8	0x00000000 /* Color Depth 8 */
3862306a36Sopenharmony_ci#define OCFB_CTRL_CD16	0x00000200 /* Color Depth 16 */
3962306a36Sopenharmony_ci#define OCFB_CTRL_CD24	0x00000400 /* Color Depth 24 */
4062306a36Sopenharmony_ci#define OCFB_CTRL_CD32	0x00000600 /* Color Depth 32 */
4162306a36Sopenharmony_ci#define OCFB_CTRL_VBL1	0x00000000 /* Burst Length 1 */
4262306a36Sopenharmony_ci#define OCFB_CTRL_VBL2	0x00000080 /* Burst Length 2 */
4362306a36Sopenharmony_ci#define OCFB_CTRL_VBL4	0x00000100 /* Burst Length 4 */
4462306a36Sopenharmony_ci#define OCFB_CTRL_VBL8	0x00000180 /* Burst Length 8 */
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define PALETTE_SIZE	256
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci#define OCFB_NAME	"OC VGA/LCD"
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic char *mode_option;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic const struct fb_videomode default_mode = {
5362306a36Sopenharmony_ci	/* 640x480 @ 60 Hz, 31.5 kHz hsync */
5462306a36Sopenharmony_ci	NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2,
5562306a36Sopenharmony_ci	0, FB_VMODE_NONINTERLACED
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistruct ocfb_dev {
5962306a36Sopenharmony_ci	struct fb_info info;
6062306a36Sopenharmony_ci	void __iomem *regs;
6162306a36Sopenharmony_ci	/* flag indicating whether the regs are little endian accessed */
6262306a36Sopenharmony_ci	int little_endian;
6362306a36Sopenharmony_ci	/* Physical and virtual addresses of framebuffer */
6462306a36Sopenharmony_ci	dma_addr_t fb_phys;
6562306a36Sopenharmony_ci	void __iomem *fb_virt;
6662306a36Sopenharmony_ci	u32 pseudo_palette[PALETTE_SIZE];
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#ifndef MODULE
7062306a36Sopenharmony_cistatic int __init ocfb_setup(char *options)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	char *curr_opt;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (!options || !*options)
7562306a36Sopenharmony_ci		return 0;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	while ((curr_opt = strsep(&options, ",")) != NULL) {
7862306a36Sopenharmony_ci		if (!*curr_opt)
7962306a36Sopenharmony_ci			continue;
8062306a36Sopenharmony_ci		mode_option = curr_opt;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci#endif
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic inline u32 ocfb_readreg(struct ocfb_dev *fbdev, loff_t offset)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	if (fbdev->little_endian)
9062306a36Sopenharmony_ci		return ioread32(fbdev->regs + offset);
9162306a36Sopenharmony_ci	else
9262306a36Sopenharmony_ci		return ioread32be(fbdev->regs + offset);
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void ocfb_writereg(struct ocfb_dev *fbdev, loff_t offset, u32 data)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	if (fbdev->little_endian)
9862306a36Sopenharmony_ci		iowrite32(data, fbdev->regs + offset);
9962306a36Sopenharmony_ci	else
10062306a36Sopenharmony_ci		iowrite32be(data, fbdev->regs + offset);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic int ocfb_setupfb(struct ocfb_dev *fbdev)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	unsigned long bpp_config;
10662306a36Sopenharmony_ci	struct fb_var_screeninfo *var = &fbdev->info.var;
10762306a36Sopenharmony_ci	struct device *dev = fbdev->info.device;
10862306a36Sopenharmony_ci	u32 hlen;
10962306a36Sopenharmony_ci	u32 vlen;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	/* Disable display */
11262306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_CTRL, 0);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* Register framebuffer address */
11562306a36Sopenharmony_ci	fbdev->little_endian = 0;
11662306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_VBARA, fbdev->fb_phys);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* Detect endianess */
11962306a36Sopenharmony_ci	if (ocfb_readreg(fbdev, OCFB_VBARA) != fbdev->fb_phys) {
12062306a36Sopenharmony_ci		fbdev->little_endian = 1;
12162306a36Sopenharmony_ci		ocfb_writereg(fbdev, OCFB_VBARA, fbdev->fb_phys);
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Horizontal timings */
12562306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_HTIM, (var->hsync_len - 1) << 24 |
12662306a36Sopenharmony_ci		      (var->left_margin - 1) << 16 | (var->xres - 1));
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Vertical timings */
12962306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_VTIM, (var->vsync_len - 1) << 24 |
13062306a36Sopenharmony_ci		      (var->upper_margin - 1) << 16 | (var->yres - 1));
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* Total length of frame */
13362306a36Sopenharmony_ci	hlen = var->left_margin + var->right_margin + var->hsync_len +
13462306a36Sopenharmony_ci		var->xres;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	vlen = var->upper_margin + var->lower_margin + var->vsync_len +
13762306a36Sopenharmony_ci		var->yres;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_HVLEN, (hlen - 1) << 16 | (vlen - 1));
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	bpp_config = OCFB_CTRL_CD8;
14262306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
14362306a36Sopenharmony_ci	case 8:
14462306a36Sopenharmony_ci		if (!var->grayscale)
14562306a36Sopenharmony_ci			bpp_config |= OCFB_CTRL_PC;  /* enable palette */
14662306a36Sopenharmony_ci		break;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	case 16:
14962306a36Sopenharmony_ci		bpp_config |= OCFB_CTRL_CD16;
15062306a36Sopenharmony_ci		break;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	case 24:
15362306a36Sopenharmony_ci		bpp_config |= OCFB_CTRL_CD24;
15462306a36Sopenharmony_ci		break;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	case 32:
15762306a36Sopenharmony_ci		bpp_config |= OCFB_CTRL_CD32;
15862306a36Sopenharmony_ci		break;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	default:
16162306a36Sopenharmony_ci		dev_err(dev, "no bpp specified\n");
16262306a36Sopenharmony_ci		break;
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* maximum (8) VBL (video memory burst length) */
16662306a36Sopenharmony_ci	bpp_config |= OCFB_CTRL_VBL8;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	/* Enable output */
16962306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_CTRL, (OCFB_CTRL_VEN | bpp_config));
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	return 0;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int ocfb_setcolreg(unsigned regno, unsigned red, unsigned green,
17562306a36Sopenharmony_ci			  unsigned blue, unsigned transp,
17662306a36Sopenharmony_ci			  struct fb_info *info)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	struct ocfb_dev *fbdev = (struct ocfb_dev *)info->par;
17962306a36Sopenharmony_ci	u32 color;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (regno >= info->cmap.len) {
18262306a36Sopenharmony_ci		dev_err(info->device, "regno >= cmap.len\n");
18362306a36Sopenharmony_ci		return 1;
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (info->var.grayscale) {
18762306a36Sopenharmony_ci		/* grayscale = 0.30*R + 0.59*G + 0.11*B */
18862306a36Sopenharmony_ci		red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	red >>= (16 - info->var.red.length);
19262306a36Sopenharmony_ci	green >>= (16 - info->var.green.length);
19362306a36Sopenharmony_ci	blue >>= (16 - info->var.blue.length);
19462306a36Sopenharmony_ci	transp >>= (16 - info->var.transp.length);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (info->var.bits_per_pixel == 8 && !info->var.grayscale) {
19762306a36Sopenharmony_ci		regno <<= 2;
19862306a36Sopenharmony_ci		color = (red << 16) | (green << 8) | blue;
19962306a36Sopenharmony_ci		ocfb_writereg(fbdev, OCFB_PALETTE + regno, color);
20062306a36Sopenharmony_ci	} else {
20162306a36Sopenharmony_ci		((u32 *)(info->pseudo_palette))[regno] =
20262306a36Sopenharmony_ci			(red << info->var.red.offset) |
20362306a36Sopenharmony_ci			(green << info->var.green.offset) |
20462306a36Sopenharmony_ci			(blue << info->var.blue.offset) |
20562306a36Sopenharmony_ci			(transp << info->var.transp.offset);
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return 0;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic int ocfb_init_fix(struct ocfb_dev *fbdev)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct fb_var_screeninfo *var = &fbdev->info.var;
21462306a36Sopenharmony_ci	struct fb_fix_screeninfo *fix = &fbdev->info.fix;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	strcpy(fix->id, OCFB_NAME);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	fix->line_length = var->xres * var->bits_per_pixel/8;
21962306a36Sopenharmony_ci	fix->smem_len = fix->line_length * var->yres;
22062306a36Sopenharmony_ci	fix->type = FB_TYPE_PACKED_PIXELS;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (var->bits_per_pixel == 8 && !var->grayscale)
22362306a36Sopenharmony_ci		fix->visual = FB_VISUAL_PSEUDOCOLOR;
22462306a36Sopenharmony_ci	else
22562306a36Sopenharmony_ci		fix->visual = FB_VISUAL_TRUECOLOR;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	return 0;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int ocfb_init_var(struct ocfb_dev *fbdev)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct fb_var_screeninfo *var = &fbdev->info.var;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	var->accel_flags = FB_ACCEL_NONE;
23562306a36Sopenharmony_ci	var->activate = FB_ACTIVATE_NOW;
23662306a36Sopenharmony_ci	var->xres_virtual = var->xres;
23762306a36Sopenharmony_ci	var->yres_virtual = var->yres;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
24062306a36Sopenharmony_ci	case 8:
24162306a36Sopenharmony_ci		var->transp.offset = 0;
24262306a36Sopenharmony_ci		var->transp.length = 0;
24362306a36Sopenharmony_ci		var->red.offset = 0;
24462306a36Sopenharmony_ci		var->red.length = 8;
24562306a36Sopenharmony_ci		var->green.offset = 0;
24662306a36Sopenharmony_ci		var->green.length = 8;
24762306a36Sopenharmony_ci		var->blue.offset = 0;
24862306a36Sopenharmony_ci		var->blue.length = 8;
24962306a36Sopenharmony_ci		break;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	case 16:
25262306a36Sopenharmony_ci		var->transp.offset = 0;
25362306a36Sopenharmony_ci		var->transp.length = 0;
25462306a36Sopenharmony_ci		var->red.offset = 11;
25562306a36Sopenharmony_ci		var->red.length = 5;
25662306a36Sopenharmony_ci		var->green.offset = 5;
25762306a36Sopenharmony_ci		var->green.length = 6;
25862306a36Sopenharmony_ci		var->blue.offset = 0;
25962306a36Sopenharmony_ci		var->blue.length  = 5;
26062306a36Sopenharmony_ci		break;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	case 24:
26362306a36Sopenharmony_ci		var->transp.offset = 0;
26462306a36Sopenharmony_ci		var->transp.length = 0;
26562306a36Sopenharmony_ci		var->red.offset = 16;
26662306a36Sopenharmony_ci		var->red.length = 8;
26762306a36Sopenharmony_ci		var->green.offset = 8;
26862306a36Sopenharmony_ci		var->green.length = 8;
26962306a36Sopenharmony_ci		var->blue.offset = 0;
27062306a36Sopenharmony_ci		var->blue.length = 8;
27162306a36Sopenharmony_ci		break;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	case 32:
27462306a36Sopenharmony_ci		var->transp.offset = 24;
27562306a36Sopenharmony_ci		var->transp.length = 8;
27662306a36Sopenharmony_ci		var->red.offset = 16;
27762306a36Sopenharmony_ci		var->red.length = 8;
27862306a36Sopenharmony_ci		var->green.offset = 8;
27962306a36Sopenharmony_ci		var->green.length = 8;
28062306a36Sopenharmony_ci		var->blue.offset = 0;
28162306a36Sopenharmony_ci		var->blue.length = 8;
28262306a36Sopenharmony_ci		break;
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return 0;
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic const struct fb_ops ocfb_ops = {
28962306a36Sopenharmony_ci	.owner		= THIS_MODULE,
29062306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
29162306a36Sopenharmony_ci	.fb_setcolreg	= ocfb_setcolreg,
29262306a36Sopenharmony_ci};
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int ocfb_probe(struct platform_device *pdev)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	int ret = 0;
29762306a36Sopenharmony_ci	struct ocfb_dev *fbdev;
29862306a36Sopenharmony_ci	int fbsize;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	fbdev = devm_kzalloc(&pdev->dev, sizeof(*fbdev), GFP_KERNEL);
30162306a36Sopenharmony_ci	if (!fbdev)
30262306a36Sopenharmony_ci		return -ENOMEM;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	platform_set_drvdata(pdev, fbdev);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	fbdev->info.fbops = &ocfb_ops;
30762306a36Sopenharmony_ci	fbdev->info.device = &pdev->dev;
30862306a36Sopenharmony_ci	fbdev->info.par = fbdev;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	/* Video mode setup */
31162306a36Sopenharmony_ci	if (!fb_find_mode(&fbdev->info.var, &fbdev->info, mode_option,
31262306a36Sopenharmony_ci			  NULL, 0, &default_mode, 16)) {
31362306a36Sopenharmony_ci		dev_err(&pdev->dev, "No valid video modes found\n");
31462306a36Sopenharmony_ci		return -EINVAL;
31562306a36Sopenharmony_ci	}
31662306a36Sopenharmony_ci	ocfb_init_var(fbdev);
31762306a36Sopenharmony_ci	ocfb_init_fix(fbdev);
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	fbdev->regs = devm_platform_ioremap_resource(pdev, 0);
32062306a36Sopenharmony_ci	if (IS_ERR(fbdev->regs))
32162306a36Sopenharmony_ci		return PTR_ERR(fbdev->regs);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	/* Allocate framebuffer memory */
32462306a36Sopenharmony_ci	fbsize = fbdev->info.fix.smem_len;
32562306a36Sopenharmony_ci	fbdev->fb_virt = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbsize),
32662306a36Sopenharmony_ci					    &fbdev->fb_phys, GFP_KERNEL);
32762306a36Sopenharmony_ci	if (!fbdev->fb_virt) {
32862306a36Sopenharmony_ci		dev_err(&pdev->dev,
32962306a36Sopenharmony_ci			"Frame buffer memory allocation failed\n");
33062306a36Sopenharmony_ci		return -ENOMEM;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci	fbdev->info.fix.smem_start = fbdev->fb_phys;
33362306a36Sopenharmony_ci	fbdev->info.screen_base = fbdev->fb_virt;
33462306a36Sopenharmony_ci	fbdev->info.pseudo_palette = fbdev->pseudo_palette;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	/* Clear framebuffer */
33762306a36Sopenharmony_ci	memset_io(fbdev->fb_virt, 0, fbsize);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	/* Setup and enable the framebuffer */
34062306a36Sopenharmony_ci	ocfb_setupfb(fbdev);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	if (fbdev->little_endian)
34362306a36Sopenharmony_ci		fbdev->info.flags |= FBINFO_FOREIGN_ENDIAN;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* Allocate color map */
34662306a36Sopenharmony_ci	ret = fb_alloc_cmap(&fbdev->info.cmap, PALETTE_SIZE, 0);
34762306a36Sopenharmony_ci	if (ret) {
34862306a36Sopenharmony_ci		dev_err(&pdev->dev, "Color map allocation failed\n");
34962306a36Sopenharmony_ci		goto err_dma_free;
35062306a36Sopenharmony_ci	}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* Register framebuffer */
35362306a36Sopenharmony_ci	ret = register_framebuffer(&fbdev->info);
35462306a36Sopenharmony_ci	if (ret) {
35562306a36Sopenharmony_ci		dev_err(&pdev->dev, "Framebuffer registration failed\n");
35662306a36Sopenharmony_ci		goto err_dealloc_cmap;
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	return 0;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cierr_dealloc_cmap:
36262306a36Sopenharmony_ci	fb_dealloc_cmap(&fbdev->info.cmap);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cierr_dma_free:
36562306a36Sopenharmony_ci	dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbsize), fbdev->fb_virt,
36662306a36Sopenharmony_ci			  fbdev->fb_phys);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	return ret;
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_cistatic void ocfb_remove(struct platform_device *pdev)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	struct ocfb_dev *fbdev = platform_get_drvdata(pdev);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	unregister_framebuffer(&fbdev->info);
37662306a36Sopenharmony_ci	fb_dealloc_cmap(&fbdev->info.cmap);
37762306a36Sopenharmony_ci	dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbdev->info.fix.smem_len),
37862306a36Sopenharmony_ci			  fbdev->fb_virt, fbdev->fb_phys);
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	/* Disable display */
38162306a36Sopenharmony_ci	ocfb_writereg(fbdev, OCFB_CTRL, 0);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	platform_set_drvdata(pdev, NULL);
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cistatic const struct of_device_id ocfb_match[] = {
38762306a36Sopenharmony_ci	{ .compatible = "opencores,ocfb", },
38862306a36Sopenharmony_ci	{},
38962306a36Sopenharmony_ci};
39062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ocfb_match);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic struct platform_driver ocfb_driver = {
39362306a36Sopenharmony_ci	.probe  = ocfb_probe,
39462306a36Sopenharmony_ci	.remove_new = ocfb_remove,
39562306a36Sopenharmony_ci	.driver = {
39662306a36Sopenharmony_ci		.name = "ocfb_fb",
39762306a36Sopenharmony_ci		.of_match_table = ocfb_match,
39862306a36Sopenharmony_ci	}
39962306a36Sopenharmony_ci};
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci/*
40262306a36Sopenharmony_ci * Init and exit routines
40362306a36Sopenharmony_ci */
40462306a36Sopenharmony_cistatic int __init ocfb_init(void)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci#ifndef MODULE
40762306a36Sopenharmony_ci	char *option = NULL;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (fb_get_options("ocfb", &option))
41062306a36Sopenharmony_ci		return -ENODEV;
41162306a36Sopenharmony_ci	ocfb_setup(option);
41262306a36Sopenharmony_ci#endif
41362306a36Sopenharmony_ci	return platform_driver_register(&ocfb_driver);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic void __exit ocfb_exit(void)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	platform_driver_unregister(&ocfb_driver);
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_cimodule_init(ocfb_init);
42262306a36Sopenharmony_cimodule_exit(ocfb_exit);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ciMODULE_AUTHOR("Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>");
42562306a36Sopenharmony_ciMODULE_DESCRIPTION("OpenCores VGA/LCD 2.0 frame buffer driver");
42662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
42762306a36Sopenharmony_cimodule_param(mode_option, charp, 0);
42862306a36Sopenharmony_ciMODULE_PARM_DESC(mode_option, "Video mode ('<xres>x<yres>[-<bpp>][@refresh]')");
429