162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SH7760/SH7763 LCDC Framebuffer driver.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2006-2008 MSC Vertriebsges.m.b.H.,
662306a36Sopenharmony_ci *             Manuel Lauss <mano@roarinelk.homelinux.net>
762306a36Sopenharmony_ci * (c) 2008 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * PLEASE HAVE A LOOK AT Documentation/fb/sh7760fb.rst!
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Thanks to Siegfried Schaefer <s.schaefer at schaefer-edv.de>
1262306a36Sopenharmony_ci *     for his original source and testing!
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * sh7760_setcolreg get from drivers/video/sh_mobile_lcdcfb.c
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/completion.h>
1862306a36Sopenharmony_ci#include <linux/delay.h>
1962306a36Sopenharmony_ci#include <linux/dma-mapping.h>
2062306a36Sopenharmony_ci#include <linux/fb.h>
2162306a36Sopenharmony_ci#include <linux/interrupt.h>
2262306a36Sopenharmony_ci#include <linux/io.h>
2362306a36Sopenharmony_ci#include <linux/kernel.h>
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <linux/platform_device.h>
2662306a36Sopenharmony_ci#include <linux/slab.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include <asm/sh7760fb.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct sh7760fb_par {
3162306a36Sopenharmony_ci	void __iomem *base;
3262306a36Sopenharmony_ci	int irq;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	struct sh7760fb_platdata *pd;	/* display information */
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	dma_addr_t fbdma;	/* physical address */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	int rot;		/* rotation enabled? */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	u32 pseudo_palette[16];
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	struct platform_device *dev;
4362306a36Sopenharmony_ci	struct resource *ioarea;
4462306a36Sopenharmony_ci	struct completion vsync;	/* vsync irq event */
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic irqreturn_t sh7760fb_irq(int irq, void *data)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct completion *c = data;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	complete(c);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return IRQ_HANDLED;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* wait_for_lps - wait until power supply has reached a certain state. */
5762306a36Sopenharmony_cistatic int wait_for_lps(struct sh7760fb_par *par, int val)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	int i = 100;
6062306a36Sopenharmony_ci	while (--i && ((ioread16(par->base + LDPMMR) & 3) != val))
6162306a36Sopenharmony_ci		msleep(1);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (i <= 0)
6462306a36Sopenharmony_ci		return -ETIMEDOUT;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return 0;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/* en/disable the LCDC */
7062306a36Sopenharmony_cistatic int sh7760fb_blank(int blank, struct fb_info *info)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
7362306a36Sopenharmony_ci	struct sh7760fb_platdata *pd = par->pd;
7462306a36Sopenharmony_ci	unsigned short cntr = ioread16(par->base + LDCNTR);
7562306a36Sopenharmony_ci	unsigned short intr = ioread16(par->base + LDINTR);
7662306a36Sopenharmony_ci	int lps;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (blank == FB_BLANK_UNBLANK) {
7962306a36Sopenharmony_ci		intr |= VINT_START;
8062306a36Sopenharmony_ci		cntr = LDCNTR_DON2 | LDCNTR_DON;
8162306a36Sopenharmony_ci		lps = 3;
8262306a36Sopenharmony_ci	} else {
8362306a36Sopenharmony_ci		intr &= ~VINT_START;
8462306a36Sopenharmony_ci		cntr = LDCNTR_DON2;
8562306a36Sopenharmony_ci		lps = 0;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (pd->blank)
8962306a36Sopenharmony_ci		pd->blank(blank);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	iowrite16(intr, par->base + LDINTR);
9262306a36Sopenharmony_ci	iowrite16(cntr, par->base + LDCNTR);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return wait_for_lps(par, lps);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int sh7760_setcolreg (u_int regno,
9862306a36Sopenharmony_ci	u_int red, u_int green, u_int blue,
9962306a36Sopenharmony_ci	u_int transp, struct fb_info *info)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	u32 *palette = info->pseudo_palette;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (regno >= 16)
10462306a36Sopenharmony_ci		return -EINVAL;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* only FB_VISUAL_TRUECOLOR supported */
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	red >>= 16 - info->var.red.length;
10962306a36Sopenharmony_ci	green >>= 16 - info->var.green.length;
11062306a36Sopenharmony_ci	blue >>= 16 - info->var.blue.length;
11162306a36Sopenharmony_ci	transp >>= 16 - info->var.transp.length;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	palette[regno] = (red << info->var.red.offset) |
11462306a36Sopenharmony_ci		(green << info->var.green.offset) |
11562306a36Sopenharmony_ci		(blue << info->var.blue.offset) |
11662306a36Sopenharmony_ci		(transp << info->var.transp.offset);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int sh7760fb_get_color_info(struct fb_info *info,
12262306a36Sopenharmony_ci				   u16 lddfr, int *bpp, int *gray)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	int lbpp, lgray;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	lgray = lbpp = 0;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	switch (lddfr & LDDFR_COLOR_MASK) {
12962306a36Sopenharmony_ci	case LDDFR_1BPP_MONO:
13062306a36Sopenharmony_ci		lgray = 1;
13162306a36Sopenharmony_ci		lbpp = 1;
13262306a36Sopenharmony_ci		break;
13362306a36Sopenharmony_ci	case LDDFR_2BPP_MONO:
13462306a36Sopenharmony_ci		lgray = 1;
13562306a36Sopenharmony_ci		lbpp = 2;
13662306a36Sopenharmony_ci		break;
13762306a36Sopenharmony_ci	case LDDFR_4BPP_MONO:
13862306a36Sopenharmony_ci		lgray = 1;
13962306a36Sopenharmony_ci		fallthrough;
14062306a36Sopenharmony_ci	case LDDFR_4BPP:
14162306a36Sopenharmony_ci		lbpp = 4;
14262306a36Sopenharmony_ci		break;
14362306a36Sopenharmony_ci	case LDDFR_6BPP_MONO:
14462306a36Sopenharmony_ci		lgray = 1;
14562306a36Sopenharmony_ci		fallthrough;
14662306a36Sopenharmony_ci	case LDDFR_8BPP:
14762306a36Sopenharmony_ci		lbpp = 8;
14862306a36Sopenharmony_ci		break;
14962306a36Sopenharmony_ci	case LDDFR_16BPP_RGB555:
15062306a36Sopenharmony_ci	case LDDFR_16BPP_RGB565:
15162306a36Sopenharmony_ci		lbpp = 16;
15262306a36Sopenharmony_ci		lgray = 0;
15362306a36Sopenharmony_ci		break;
15462306a36Sopenharmony_ci	default:
15562306a36Sopenharmony_ci		fb_dbg(info, "unsupported LDDFR bit depth.\n");
15662306a36Sopenharmony_ci		return -EINVAL;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (bpp)
16062306a36Sopenharmony_ci		*bpp = lbpp;
16162306a36Sopenharmony_ci	if (gray)
16262306a36Sopenharmony_ci		*gray = lgray;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return 0;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int sh7760fb_check_var(struct fb_var_screeninfo *var,
16862306a36Sopenharmony_ci			      struct fb_info *info)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct fb_fix_screeninfo *fix = &info->fix;
17162306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
17262306a36Sopenharmony_ci	int ret, bpp;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* get color info from register value */
17562306a36Sopenharmony_ci	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
17662306a36Sopenharmony_ci	if (ret)
17762306a36Sopenharmony_ci		return ret;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	var->bits_per_pixel = bpp;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if ((var->grayscale) && (var->bits_per_pixel == 1))
18262306a36Sopenharmony_ci		fix->visual = FB_VISUAL_MONO10;
18362306a36Sopenharmony_ci	else if (var->bits_per_pixel >= 15)
18462306a36Sopenharmony_ci		fix->visual = FB_VISUAL_TRUECOLOR;
18562306a36Sopenharmony_ci	else
18662306a36Sopenharmony_ci		fix->visual = FB_VISUAL_PSEUDOCOLOR;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	/* TODO: add some more validation here */
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci/*
19362306a36Sopenharmony_ci * sh7760fb_set_par - set videomode.
19462306a36Sopenharmony_ci *
19562306a36Sopenharmony_ci * NOTE: The rotation, grayscale and DSTN codepaths are
19662306a36Sopenharmony_ci *     totally untested!
19762306a36Sopenharmony_ci */
19862306a36Sopenharmony_cistatic int sh7760fb_set_par(struct fb_info *info)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
20162306a36Sopenharmony_ci	struct fb_videomode *vm = par->pd->def_mode;
20262306a36Sopenharmony_ci	unsigned long sbase, dstn_off, ldsarl, stride;
20362306a36Sopenharmony_ci	unsigned short hsynp, hsynw, htcn, hdcn;
20462306a36Sopenharmony_ci	unsigned short vsynp, vsynw, vtln, vdln;
20562306a36Sopenharmony_ci	unsigned short lddfr, ldmtr;
20662306a36Sopenharmony_ci	int ret, bpp, gray;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	par->rot = par->pd->rotate;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/* rotate only works with xres <= 320 */
21162306a36Sopenharmony_ci	if (par->rot && (vm->xres > 320)) {
21262306a36Sopenharmony_ci		fb_dbg(info, "rotation disabled due to display size\n");
21362306a36Sopenharmony_ci		par->rot = 0;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* calculate LCDC reg vals from display parameters */
21762306a36Sopenharmony_ci	hsynp = vm->right_margin + vm->xres;
21862306a36Sopenharmony_ci	hsynw = vm->hsync_len;
21962306a36Sopenharmony_ci	htcn = vm->left_margin + hsynp + hsynw;
22062306a36Sopenharmony_ci	hdcn = vm->xres;
22162306a36Sopenharmony_ci	vsynp = vm->lower_margin + vm->yres;
22262306a36Sopenharmony_ci	vsynw = vm->vsync_len;
22362306a36Sopenharmony_ci	vtln = vm->upper_margin + vsynp + vsynw;
22462306a36Sopenharmony_ci	vdln = vm->yres;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	/* get color info from register value */
22762306a36Sopenharmony_ci	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, &gray);
22862306a36Sopenharmony_ci	if (ret)
22962306a36Sopenharmony_ci		return ret;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	fb_dbg(info, "%dx%d %dbpp %s (orientation %s)\n", hdcn,
23262306a36Sopenharmony_ci		vdln, bpp, gray ? "grayscale" : "color",
23362306a36Sopenharmony_ci		par->rot ? "rotated" : "normal");
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci#ifdef CONFIG_CPU_LITTLE_ENDIAN
23662306a36Sopenharmony_ci	lddfr = par->pd->lddfr | (1 << 8);
23762306a36Sopenharmony_ci#else
23862306a36Sopenharmony_ci	lddfr = par->pd->lddfr & ~(1 << 8);
23962306a36Sopenharmony_ci#endif
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	ldmtr = par->pd->ldmtr;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (!(vm->sync & FB_SYNC_HOR_HIGH_ACT))
24462306a36Sopenharmony_ci		ldmtr |= LDMTR_CL1POL;
24562306a36Sopenharmony_ci	if (!(vm->sync & FB_SYNC_VERT_HIGH_ACT))
24662306a36Sopenharmony_ci		ldmtr |= LDMTR_FLMPOL;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	/* shut down LCDC before changing display parameters */
24962306a36Sopenharmony_ci	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	iowrite16(par->pd->ldickr, par->base + LDICKR);	/* pixclock */
25262306a36Sopenharmony_ci	iowrite16(ldmtr, par->base + LDMTR);	/* polarities */
25362306a36Sopenharmony_ci	iowrite16(lddfr, par->base + LDDFR);	/* color/depth */
25462306a36Sopenharmony_ci	iowrite16((par->rot ? 1 << 13 : 0), par->base + LDSMR);	/* rotate */
25562306a36Sopenharmony_ci	iowrite16(par->pd->ldpmmr, par->base + LDPMMR);	/* Power Management */
25662306a36Sopenharmony_ci	iowrite16(par->pd->ldpspr, par->base + LDPSPR);	/* Power Supply Ctrl */
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	/* display resolution */
25962306a36Sopenharmony_ci	iowrite16(((htcn >> 3) - 1) | (((hdcn >> 3) - 1) << 8),
26062306a36Sopenharmony_ci		  par->base + LDHCNR);
26162306a36Sopenharmony_ci	iowrite16(vdln - 1, par->base + LDVDLNR);
26262306a36Sopenharmony_ci	iowrite16(vtln - 1, par->base + LDVTLNR);
26362306a36Sopenharmony_ci	/* h/v sync signals */
26462306a36Sopenharmony_ci	iowrite16((vsynp - 1) | ((vsynw - 1) << 12), par->base + LDVSYNR);
26562306a36Sopenharmony_ci	iowrite16(((hsynp >> 3) - 1) | (((hsynw >> 3) - 1) << 12),
26662306a36Sopenharmony_ci		  par->base + LDHSYNR);
26762306a36Sopenharmony_ci	/* AC modulation sig */
26862306a36Sopenharmony_ci	iowrite16(par->pd->ldaclnr, par->base + LDACLNR);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	stride = (par->rot) ? vtln : hdcn;
27162306a36Sopenharmony_ci	if (!gray)
27262306a36Sopenharmony_ci		stride *= (bpp + 7) >> 3;
27362306a36Sopenharmony_ci	else {
27462306a36Sopenharmony_ci		if (bpp == 1)
27562306a36Sopenharmony_ci			stride >>= 3;
27662306a36Sopenharmony_ci		else if (bpp == 2)
27762306a36Sopenharmony_ci			stride >>= 2;
27862306a36Sopenharmony_ci		else if (bpp == 4)
27962306a36Sopenharmony_ci			stride >>= 1;
28062306a36Sopenharmony_ci		/* 6 bpp == 8 bpp */
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	/* if rotated, stride must be power of 2 */
28462306a36Sopenharmony_ci	if (par->rot) {
28562306a36Sopenharmony_ci		unsigned long bit = 1 << 31;
28662306a36Sopenharmony_ci		while (bit) {
28762306a36Sopenharmony_ci			if (stride & bit)
28862306a36Sopenharmony_ci				break;
28962306a36Sopenharmony_ci			bit >>= 1;
29062306a36Sopenharmony_ci		}
29162306a36Sopenharmony_ci		if (stride & ~bit)
29262306a36Sopenharmony_ci			stride = bit << 1;	/* not P-o-2, round up */
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci	iowrite16(stride, par->base + LDLAOR);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	/* set display mem start address */
29762306a36Sopenharmony_ci	sbase = (unsigned long)par->fbdma;
29862306a36Sopenharmony_ci	if (par->rot)
29962306a36Sopenharmony_ci		sbase += (hdcn - 1) * stride;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	iowrite32(sbase, par->base + LDSARU);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	/*
30462306a36Sopenharmony_ci	 * for DSTN need to set address for lower half.
30562306a36Sopenharmony_ci	 * I (mlau) don't know which address to set it to,
30662306a36Sopenharmony_ci	 * so I guessed at (stride * yres/2).
30762306a36Sopenharmony_ci	 */
30862306a36Sopenharmony_ci	if (((ldmtr & 0x003f) >= LDMTR_DSTN_MONO_8) &&
30962306a36Sopenharmony_ci	    ((ldmtr & 0x003f) <= LDMTR_DSTN_COLOR_16)) {
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		fb_dbg(info, " ***** DSTN untested! *****\n");
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci		dstn_off = stride;
31462306a36Sopenharmony_ci		if (par->rot)
31562306a36Sopenharmony_ci			dstn_off *= hdcn >> 1;
31662306a36Sopenharmony_ci		else
31762306a36Sopenharmony_ci			dstn_off *= vdln >> 1;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci		ldsarl = sbase + dstn_off;
32062306a36Sopenharmony_ci	} else
32162306a36Sopenharmony_ci		ldsarl = 0;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	iowrite32(ldsarl, par->base + LDSARL);	/* mem for lower half of DSTN */
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	info->fix.line_length = stride;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	sh7760fb_check_var(&info->var, info);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	sh7760fb_blank(FB_BLANK_UNBLANK, info);	/* panel on! */
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	fb_dbg(info, "hdcn  : %6d htcn  : %6d\n", hdcn, htcn);
33262306a36Sopenharmony_ci	fb_dbg(info, "hsynw : %6d hsynp : %6d\n", hsynw, hsynp);
33362306a36Sopenharmony_ci	fb_dbg(info, "vdln  : %6d vtln  : %6d\n", vdln, vtln);
33462306a36Sopenharmony_ci	fb_dbg(info, "vsynw : %6d vsynp : %6d\n", vsynw, vsynp);
33562306a36Sopenharmony_ci	fb_dbg(info, "clksrc: %6d clkdiv: %6d\n",
33662306a36Sopenharmony_ci		(par->pd->ldickr >> 12) & 3, par->pd->ldickr & 0x1f);
33762306a36Sopenharmony_ci	fb_dbg(info, "ldpmmr: 0x%04x ldpspr: 0x%04x\n", par->pd->ldpmmr,
33862306a36Sopenharmony_ci		par->pd->ldpspr);
33962306a36Sopenharmony_ci	fb_dbg(info, "ldmtr : 0x%04x lddfr : 0x%04x\n", ldmtr, lddfr);
34062306a36Sopenharmony_ci	fb_dbg(info, "ldlaor: %ld\n", stride);
34162306a36Sopenharmony_ci	fb_dbg(info, "ldsaru: 0x%08lx ldsarl: 0x%08lx\n", sbase, ldsarl);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return 0;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic const struct fb_ops sh7760fb_ops = {
34762306a36Sopenharmony_ci	.owner = THIS_MODULE,
34862306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
34962306a36Sopenharmony_ci	.fb_blank = sh7760fb_blank,
35062306a36Sopenharmony_ci	.fb_check_var = sh7760fb_check_var,
35162306a36Sopenharmony_ci	.fb_setcolreg = sh7760_setcolreg,
35262306a36Sopenharmony_ci	.fb_set_par = sh7760fb_set_par,
35362306a36Sopenharmony_ci};
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic void sh7760fb_free_mem(struct fb_info *info)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	if (!info->screen_base)
36062306a36Sopenharmony_ci		return;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	dma_free_coherent(info->device, info->screen_size,
36362306a36Sopenharmony_ci			  info->screen_base, par->fbdma);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	par->fbdma = 0;
36662306a36Sopenharmony_ci	info->screen_base = NULL;
36762306a36Sopenharmony_ci	info->screen_size = 0;
36862306a36Sopenharmony_ci}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci/* allocate the framebuffer memory. This memory must be in Area3,
37162306a36Sopenharmony_ci * (dictated by the DMA engine) and contiguous, at a 512 byte boundary.
37262306a36Sopenharmony_ci */
37362306a36Sopenharmony_cistatic int sh7760fb_alloc_mem(struct fb_info *info)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
37662306a36Sopenharmony_ci	void *fbmem;
37762306a36Sopenharmony_ci	unsigned long vram;
37862306a36Sopenharmony_ci	int ret, bpp;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	if (info->screen_base)
38162306a36Sopenharmony_ci		return 0;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	/* get color info from register value */
38462306a36Sopenharmony_ci	ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
38562306a36Sopenharmony_ci	if (ret) {
38662306a36Sopenharmony_ci		printk(KERN_ERR "colinfo\n");
38762306a36Sopenharmony_ci		return ret;
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	/* min VRAM: xres_min = 16, yres_min = 1, bpp = 1: 2byte -> 1 page
39162306a36Sopenharmony_ci	   max VRAM: xres_max = 1024, yres_max = 1024, bpp = 16: 2MB */
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	vram = info->var.xres * info->var.yres;
39462306a36Sopenharmony_ci	if (info->var.grayscale) {
39562306a36Sopenharmony_ci		if (bpp == 1)
39662306a36Sopenharmony_ci			vram >>= 3;
39762306a36Sopenharmony_ci		else if (bpp == 2)
39862306a36Sopenharmony_ci			vram >>= 2;
39962306a36Sopenharmony_ci		else if (bpp == 4)
40062306a36Sopenharmony_ci			vram >>= 1;
40162306a36Sopenharmony_ci	} else if (bpp > 8)
40262306a36Sopenharmony_ci		vram *= 2;
40362306a36Sopenharmony_ci	if ((vram < 1) || (vram > 1024 * 2048)) {
40462306a36Sopenharmony_ci		fb_dbg(info, "too much VRAM required. Check settings\n");
40562306a36Sopenharmony_ci		return -ENODEV;
40662306a36Sopenharmony_ci	}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	if (vram < PAGE_SIZE)
40962306a36Sopenharmony_ci		vram = PAGE_SIZE;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	fbmem = dma_alloc_coherent(info->device, vram, &par->fbdma, GFP_KERNEL);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	if (!fbmem)
41462306a36Sopenharmony_ci		return -ENOMEM;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	if ((par->fbdma & SH7760FB_DMA_MASK) != SH7760FB_DMA_MASK) {
41762306a36Sopenharmony_ci		sh7760fb_free_mem(info);
41862306a36Sopenharmony_ci		dev_err(info->device, "kernel gave me memory at 0x%08lx, which is"
41962306a36Sopenharmony_ci			"unusable for the LCDC\n", (unsigned long)par->fbdma);
42062306a36Sopenharmony_ci		return -ENOMEM;
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	info->screen_base = fbmem;
42462306a36Sopenharmony_ci	info->screen_size = vram;
42562306a36Sopenharmony_ci	info->fix.smem_start = (unsigned long)info->screen_base;
42662306a36Sopenharmony_ci	info->fix.smem_len = info->screen_size;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	return 0;
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_cistatic int sh7760fb_probe(struct platform_device *pdev)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct fb_info *info;
43462306a36Sopenharmony_ci	struct resource *res;
43562306a36Sopenharmony_ci	struct sh7760fb_par *par;
43662306a36Sopenharmony_ci	int ret;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
43962306a36Sopenharmony_ci	if (unlikely(res == NULL)) {
44062306a36Sopenharmony_ci		dev_err(&pdev->dev, "invalid resource\n");
44162306a36Sopenharmony_ci		return -EINVAL;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct sh7760fb_par), &pdev->dev);
44562306a36Sopenharmony_ci	if (!info)
44662306a36Sopenharmony_ci		return -ENOMEM;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	par = info->par;
44962306a36Sopenharmony_ci	par->dev = pdev;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	par->pd = pdev->dev.platform_data;
45262306a36Sopenharmony_ci	if (!par->pd) {
45362306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "no display setup data!\n");
45462306a36Sopenharmony_ci		ret = -ENODEV;
45562306a36Sopenharmony_ci		goto out_fb;
45662306a36Sopenharmony_ci	}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	par->ioarea = request_mem_region(res->start,
45962306a36Sopenharmony_ci					 resource_size(res), pdev->name);
46062306a36Sopenharmony_ci	if (!par->ioarea) {
46162306a36Sopenharmony_ci		dev_err(&pdev->dev, "mmio area busy\n");
46262306a36Sopenharmony_ci		ret = -EBUSY;
46362306a36Sopenharmony_ci		goto out_fb;
46462306a36Sopenharmony_ci	}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	par->base = ioremap(res->start, resource_size(res));
46762306a36Sopenharmony_ci	if (!par->base) {
46862306a36Sopenharmony_ci		dev_err(&pdev->dev, "cannot remap\n");
46962306a36Sopenharmony_ci		ret = -ENODEV;
47062306a36Sopenharmony_ci		goto out_res;
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	iowrite16(0, par->base + LDINTR);	/* disable vsync irq */
47462306a36Sopenharmony_ci	par->irq = platform_get_irq(pdev, 0);
47562306a36Sopenharmony_ci	if (par->irq >= 0) {
47662306a36Sopenharmony_ci		ret = request_irq(par->irq, sh7760fb_irq, 0,
47762306a36Sopenharmony_ci				  "sh7760-lcdc", &par->vsync);
47862306a36Sopenharmony_ci		if (ret) {
47962306a36Sopenharmony_ci			dev_err(&pdev->dev, "cannot grab IRQ\n");
48062306a36Sopenharmony_ci			par->irq = -ENXIO;
48162306a36Sopenharmony_ci		} else
48262306a36Sopenharmony_ci			disable_irq_nosync(par->irq);
48362306a36Sopenharmony_ci	}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	fb_videomode_to_var(&info->var, par->pd->def_mode);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	ret = sh7760fb_alloc_mem(info);
48862306a36Sopenharmony_ci	if (ret) {
48962306a36Sopenharmony_ci		dev_dbg(info->device, "framebuffer memory allocation failed!\n");
49062306a36Sopenharmony_ci		goto out_unmap;
49162306a36Sopenharmony_ci	}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	info->pseudo_palette = par->pseudo_palette;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	/* fixup color register bitpositions. These are fixed by hardware */
49662306a36Sopenharmony_ci	info->var.red.offset = 11;
49762306a36Sopenharmony_ci	info->var.red.length = 5;
49862306a36Sopenharmony_ci	info->var.red.msb_right = 0;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	info->var.green.offset = 5;
50162306a36Sopenharmony_ci	info->var.green.length = 6;
50262306a36Sopenharmony_ci	info->var.green.msb_right = 0;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	info->var.blue.offset = 0;
50562306a36Sopenharmony_ci	info->var.blue.length = 5;
50662306a36Sopenharmony_ci	info->var.blue.msb_right = 0;
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	info->var.transp.offset = 0;
50962306a36Sopenharmony_ci	info->var.transp.length = 0;
51062306a36Sopenharmony_ci	info->var.transp.msb_right = 0;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	strcpy(info->fix.id, "sh7760-lcdc");
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	/* set the DON2 bit now, before cmap allocation, as it will randomize
51562306a36Sopenharmony_ci	 * palette memory.
51662306a36Sopenharmony_ci	 */
51762306a36Sopenharmony_ci	iowrite16(LDCNTR_DON2, par->base + LDCNTR);
51862306a36Sopenharmony_ci	info->fbops = &sh7760fb_ops;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	ret = fb_alloc_cmap(&info->cmap, 256, 0);
52162306a36Sopenharmony_ci	if (ret) {
52262306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "Unable to allocate cmap memory\n");
52362306a36Sopenharmony_ci		goto out_mem;
52462306a36Sopenharmony_ci	}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	ret = register_framebuffer(info);
52762306a36Sopenharmony_ci	if (ret < 0) {
52862306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "cannot register fb!\n");
52962306a36Sopenharmony_ci		goto out_cmap;
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci	platform_set_drvdata(pdev, info);
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	printk(KERN_INFO "%s: memory at phys 0x%08lx-0x%08lx, size %ld KiB\n",
53462306a36Sopenharmony_ci	       pdev->name,
53562306a36Sopenharmony_ci	       (unsigned long)par->fbdma,
53662306a36Sopenharmony_ci	       (unsigned long)(par->fbdma + info->screen_size - 1),
53762306a36Sopenharmony_ci	       info->screen_size >> 10);
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	return 0;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ciout_cmap:
54262306a36Sopenharmony_ci	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
54362306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
54462306a36Sopenharmony_ciout_mem:
54562306a36Sopenharmony_ci	sh7760fb_free_mem(info);
54662306a36Sopenharmony_ciout_unmap:
54762306a36Sopenharmony_ci	if (par->irq >= 0)
54862306a36Sopenharmony_ci		free_irq(par->irq, &par->vsync);
54962306a36Sopenharmony_ci	iounmap(par->base);
55062306a36Sopenharmony_ciout_res:
55162306a36Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
55262306a36Sopenharmony_ciout_fb:
55362306a36Sopenharmony_ci	framebuffer_release(info);
55462306a36Sopenharmony_ci	return ret;
55562306a36Sopenharmony_ci}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cistatic void sh7760fb_remove(struct platform_device *dev)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(dev);
56062306a36Sopenharmony_ci	struct sh7760fb_par *par = info->par;
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	sh7760fb_blank(FB_BLANK_POWERDOWN, info);
56362306a36Sopenharmony_ci	unregister_framebuffer(info);
56462306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
56562306a36Sopenharmony_ci	sh7760fb_free_mem(info);
56662306a36Sopenharmony_ci	if (par->irq >= 0)
56762306a36Sopenharmony_ci		free_irq(par->irq, &par->vsync);
56862306a36Sopenharmony_ci	iounmap(par->base);
56962306a36Sopenharmony_ci	release_mem_region(par->ioarea->start, resource_size(par->ioarea));
57062306a36Sopenharmony_ci	framebuffer_release(info);
57162306a36Sopenharmony_ci}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_cistatic struct platform_driver sh7760_lcdc_driver = {
57462306a36Sopenharmony_ci	.driver = {
57562306a36Sopenharmony_ci		   .name = "sh7760-lcdc",
57662306a36Sopenharmony_ci		   },
57762306a36Sopenharmony_ci	.probe = sh7760fb_probe,
57862306a36Sopenharmony_ci	.remove_new = sh7760fb_remove,
57962306a36Sopenharmony_ci};
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_cimodule_platform_driver(sh7760_lcdc_driver);
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ciMODULE_AUTHOR("Nobuhiro Iwamatsu, Manuel Lauss");
58462306a36Sopenharmony_ciMODULE_DESCRIPTION("FBdev for SH7760/63 integrated LCD Controller");
58562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
586