Commit d6ff7d0f authored by Alexey Charkov's avatar Alexey Charkov Committed by Paul Mundt
Browse files

ARM: Add support for the display controllers in VT8500 and WM8505



This adds drivers for the LCD controller found in VIA VT8500 SoC,
GOVR display controller found in WonderMedia WM8505 SoC and for the
Graphics Engine present in both of them that provides hardware
accelerated raster operations (used for copyarea and fillrect).

Signed-off-by: default avatarAlexey Charkov <alchark@gmail.com>
Signed-off-by: default avatarPaul Mundt <lethal@linux-sh.org>
parent a7bcf21e
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -186,6 +186,14 @@ config FB_SYS_FOPS
       depends on FB
       default n

config FB_WMT_GE_ROPS
	tristate
	depends on FB
	default n
	---help---
	  Include functions for accelerated rectangle filling and area
	  copying using WonderMedia Graphics Engine operations.

config FB_DEFERRED_IO
	bool
	depends on FB
@@ -1722,6 +1730,24 @@ config FB_AU1200
	  various panels and CRTs by passing in kernel cmd line option
	  au1200fb:panel=<name>.

config FB_VT8500
	bool "VT8500 LCD Driver"
	depends on (FB = y) && ARM && ARCH_VT8500 && VTWM_VERSION_VT8500
	select FB_WMT_GE_ROPS
	select FB_SYS_IMAGEBLIT
	help
	  This is the framebuffer driver for VIA VT8500 integrated LCD
	  controller.

config FB_WM8505
	bool "WM8505 frame buffer support"
	depends on (FB = y) && ARM && ARCH_VT8500 && VTWM_VERSION_WM8505
	select FB_WMT_GE_ROPS
	select FB_SYS_IMAGEBLIT
	help
	  This is the framebuffer driver for WonderMedia WM8505
	  integrated LCD controller.

source "drivers/video/geode/Kconfig"

config FB_HIT
+3 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ obj-$(CONFIG_FB_SVGALIB) += svgalib.o
obj-$(CONFIG_FB_MACMODES)      += macmodes.o
obj-$(CONFIG_FB_DDC)           += fb_ddc.o
obj-$(CONFIG_FB_DEFERRED_IO)   += fb_defio.o
obj-$(CONFIG_FB_WMT_GE_ROPS)   += wmt_ge_rops.o

# Hardware specific drivers go first
obj-$(CONFIG_FB_AMIGA)            += amifb.o c2p_planar.o
@@ -104,6 +105,8 @@ obj-$(CONFIG_FB_W100) += w100fb.o
obj-$(CONFIG_FB_TMIO)		  += tmiofb.o
obj-$(CONFIG_FB_AU1100)		  += au1100fb.o
obj-$(CONFIG_FB_AU1200)		  += au1200fb.o
obj-$(CONFIG_FB_VT8500)		  += vt8500lcdfb.o
obj-$(CONFIG_FB_WM8505)		  += wm8505fb.o
obj-$(CONFIG_FB_PMAG_AA)	  += pmag-aa-fb.o
obj-$(CONFIG_FB_PMAG_BA)	  += pmag-ba-fb.o
obj-$(CONFIG_FB_PMAGB_B)	  += pmagb-b-fb.o
+447 −0
Original line number Diff line number Diff line
/*
 *  linux/drivers/video/vt8500lcdfb.c
 *
 *  Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
 *
 * Based on skeletonfb.c and pxafb.c
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/wait.h>

#include <mach/vt8500fb.h>

#include "vt8500lcdfb.h"
#include "wmt_ge_rops.h"

#define to_vt8500lcd_info(__info) container_of(__info, \
						struct vt8500lcd_info, fb)

static int vt8500lcd_set_par(struct fb_info *info)
{
	struct vt8500lcd_info *fbi = to_vt8500lcd_info(info);
	int reg_bpp = 5; /* 16bpp */
	int i;
	unsigned long control0;

	if (!fbi)
		return -EINVAL;

	if (info->var.bits_per_pixel <= 8) {
		/* palettized */
		info->var.red.offset    = 0;
		info->var.red.length    = info->var.bits_per_pixel;
		info->var.red.msb_right = 0;

		info->var.green.offset  = 0;
		info->var.green.length  = info->var.bits_per_pixel;
		info->var.green.msb_right = 0;

		info->var.blue.offset   = 0;
		info->var.blue.length   = info->var.bits_per_pixel;
		info->var.blue.msb_right = 0;

		info->var.transp.offset = 0;
		info->var.transp.length = 0;
		info->var.transp.msb_right = 0;

		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
		info->fix.line_length = info->var.xres_virtual /
						(8/info->var.bits_per_pixel);
	} else {
		/* non-palettized */
		info->var.transp.offset = 0;
		info->var.transp.length = 0;
		info->var.transp.msb_right = 0;

		if (info->var.bits_per_pixel == 16) {
			/* RGB565 */
			info->var.red.offset = 11;
			info->var.red.length = 5;
			info->var.red.msb_right = 0;
			info->var.green.offset = 5;
			info->var.green.length = 6;
			info->var.green.msb_right = 0;
			info->var.blue.offset = 0;
			info->var.blue.length = 5;
			info->var.blue.msb_right = 0;
		} else {
			/* Equal depths per channel */
			info->var.red.offset = info->var.bits_per_pixel
							* 2 / 3;
			info->var.red.length = info->var.bits_per_pixel / 3;
			info->var.red.msb_right = 0;
			info->var.green.offset = info->var.bits_per_pixel / 3;
			info->var.green.length = info->var.bits_per_pixel / 3;
			info->var.green.msb_right = 0;
			info->var.blue.offset = 0;
			info->var.blue.length = info->var.bits_per_pixel / 3;
			info->var.blue.msb_right = 0;
		}

		info->fix.visual = FB_VISUAL_TRUECOLOR;
		info->fix.line_length = info->var.bits_per_pixel > 16 ?
					info->var.xres_virtual << 2 :
					info->var.xres_virtual << 1;
	}

	for (i = 0; i < 8; i++) {
		if (bpp_values[i] == info->var.bits_per_pixel) {
			reg_bpp = i;
			continue;
		}
	}

	control0 = readl(fbi->regbase) & ~0xf;
	writel(0, fbi->regbase);
	while (readl(fbi->regbase + 0x38) & 0x10)
		/* wait */;
	writel((((info->var.hsync_len - 1) & 0x3f) << 26)
		| ((info->var.left_margin & 0xff) << 18)
		| (((info->var.xres - 1) & 0x3ff) << 8)
		| (info->var.right_margin & 0xff), fbi->regbase + 0x4);
	writel((((info->var.vsync_len - 1) & 0x3f) << 26)
		| ((info->var.upper_margin & 0xff) << 18)
		| (((info->var.yres - 1) & 0x3ff) << 8)
		| (info->var.lower_margin & 0xff), fbi->regbase + 0x8);
	writel((((info->var.yres - 1) & 0x400) << 2)
		| ((info->var.xres - 1) & 0x400), fbi->regbase + 0x10);
	writel(0x80000000, fbi->regbase + 0x20);
	writel(control0 | (reg_bpp << 1) | 0x100, fbi->regbase);

	return 0;
}

static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int vt8500lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp,
			   struct fb_info *info) {
	struct vt8500lcd_info *fbi = to_vt8500lcd_info(info);
	int ret = 1;
	unsigned int val;
	if (regno >= 256)
		return -EINVAL;

	if (info->var.grayscale)
		red = green = blue =
			(19595 * red + 38470 * green + 7471 * blue) >> 16;

	switch (fbi->fb.fix.visual) {
	case FB_VISUAL_TRUECOLOR:
		if (regno < 16) {
			u32 *pal = fbi->fb.pseudo_palette;

			val  = chan_to_field(red, &fbi->fb.var.red);
			val |= chan_to_field(green, &fbi->fb.var.green);
			val |= chan_to_field(blue, &fbi->fb.var.blue);

			pal[regno] = val;
			ret = 0;
		}
		break;

	case FB_VISUAL_STATIC_PSEUDOCOLOR:
	case FB_VISUAL_PSEUDOCOLOR:
		writew((red & 0xf800)
		      | ((green >> 5) & 0x7e0)
		      | ((blue >> 11) & 0x1f),
		       fbi->palette_cpu + sizeof(u16) * regno);
		break;
	}

	return ret;
}

static int vt8500lcd_ioctl(struct fb_info *info, unsigned int cmd,
			 unsigned long arg)
{
	int ret = 0;
	struct vt8500lcd_info *fbi = to_vt8500lcd_info(info);

	if (cmd == FBIO_WAITFORVSYNC) {
		/* Unmask End of Frame interrupt */
		writel(0xffffffff ^ (1 << 3), fbi->regbase + 0x3c);
		ret = wait_event_interruptible_timeout(fbi->wait,
			readl(fbi->regbase + 0x38) & (1 << 3), HZ / 10);
		/* Mask back to reduce unwanted interrupt traffic */
		writel(0xffffffff, fbi->regbase + 0x3c);
		if (ret < 0)
			return ret;
		if (ret == 0)
			return -ETIMEDOUT;
	}

	return ret;
}

static int vt8500lcd_pan_display(struct fb_var_screeninfo *var,
				struct fb_info *info)
{
	unsigned pixlen = info->fix.line_length / info->var.xres_virtual;
	unsigned off = pixlen * var->xoffset
		      + info->fix.line_length * var->yoffset;
	struct vt8500lcd_info *fbi = to_vt8500lcd_info(info);

	writel((1 << 31)
		| (((var->xres_virtual - var->xres) * pixlen / 4) << 20)
		| (off >> 2), fbi->regbase + 0x20);
	return 0;
}

static struct fb_ops vt8500lcd_ops = {
	.owner		= THIS_MODULE,
	.fb_set_par	= vt8500lcd_set_par,
	.fb_setcolreg	= vt8500lcd_setcolreg,
	.fb_fillrect	= wmt_ge_fillrect,
	.fb_copyarea	= wmt_ge_copyarea,
	.fb_imageblit	= sys_imageblit,
	.fb_sync	= wmt_ge_sync,
	.fb_ioctl	= vt8500lcd_ioctl,
	.fb_pan_display	= vt8500lcd_pan_display,
};

static irqreturn_t vt8500lcd_handle_irq(int irq, void *dev_id)
{
	struct vt8500lcd_info *fbi = dev_id;

	if (readl(fbi->regbase + 0x38) & (1 << 3))
		wake_up_interruptible(&fbi->wait);

	writel(0xffffffff, fbi->regbase + 0x38);
	return IRQ_HANDLED;
}

static int __devinit vt8500lcd_probe(struct platform_device *pdev)
{
	struct vt8500lcd_info *fbi;
	struct resource *res;
	struct vt8500fb_platform_data *pdata = pdev->dev.platform_data;
	void *addr;
	int irq, ret;

	ret = -ENOMEM;
	fbi = NULL;

	fbi = kzalloc(sizeof(struct vt8500lcd_info) + sizeof(u32) * 16,
							GFP_KERNEL);
	if (!fbi) {
		dev_err(&pdev->dev, "Failed to initialize framebuffer device\n");
		ret = -ENOMEM;
		goto failed;
	}

	strcpy(fbi->fb.fix.id, "VT8500 LCD");

	fbi->fb.fix.type	= FB_TYPE_PACKED_PIXELS;
	fbi->fb.fix.xpanstep	= 0;
	fbi->fb.fix.ypanstep	= 1;
	fbi->fb.fix.ywrapstep	= 0;
	fbi->fb.fix.accel	= FB_ACCEL_NONE;

	fbi->fb.var.nonstd	= 0;
	fbi->fb.var.activate	= FB_ACTIVATE_NOW;
	fbi->fb.var.height	= -1;
	fbi->fb.var.width	= -1;
	fbi->fb.var.vmode	= FB_VMODE_NONINTERLACED;

	fbi->fb.fbops		= &vt8500lcd_ops;
	fbi->fb.flags		= FBINFO_DEFAULT
				| FBINFO_HWACCEL_COPYAREA
				| FBINFO_HWACCEL_FILLRECT
				| FBINFO_HWACCEL_YPAN
				| FBINFO_VIRTFB
				| FBINFO_PARTIAL_PAN_OK;
	fbi->fb.node		= -1;

	addr = fbi;
	addr = addr + sizeof(struct vt8500lcd_info);
	fbi->fb.pseudo_palette	= addr;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "no I/O memory resource defined\n");
		ret = -ENODEV;
		goto failed_fbi;
	}

	res = request_mem_region(res->start, resource_size(res), "vt8500lcd");
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to request I/O memory\n");
		ret = -EBUSY;
		goto failed_fbi;
	}

	fbi->regbase = ioremap(res->start, resource_size(res));
	if (fbi->regbase == NULL) {
		dev_err(&pdev->dev, "failed to map I/O memory\n");
		ret = -EBUSY;
		goto failed_free_res;
	}

	fbi->fb.fix.smem_start	= pdata->video_mem_phys;
	fbi->fb.fix.smem_len	= pdata->video_mem_len;
	fbi->fb.screen_base	= pdata->video_mem_virt;

	fbi->palette_size	= PAGE_ALIGN(512);
	fbi->palette_cpu	= dma_alloc_coherent(&pdev->dev,
						     fbi->palette_size,
						     &fbi->palette_phys,
						     GFP_KERNEL);
	if (fbi->palette_cpu == NULL) {
		dev_err(&pdev->dev, "Failed to allocate palette buffer\n");
		ret = -ENOMEM;
		goto failed_free_io;
	}

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no IRQ defined\n");
		ret = -ENODEV;
		goto failed_free_palette;
	}

	ret = request_irq(irq, vt8500lcd_handle_irq, IRQF_DISABLED, "LCD", fbi);
	if (ret) {
		dev_err(&pdev->dev, "request_irq failed: %d\n", ret);
		ret = -EBUSY;
		goto failed_free_palette;
	}

	init_waitqueue_head(&fbi->wait);

	if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) {
		dev_err(&pdev->dev, "Failed to allocate color map\n");
		ret = -ENOMEM;
		goto failed_free_irq;
	}

	fb_videomode_to_var(&fbi->fb.var, &pdata->mode);
	fbi->fb.var.bits_per_pixel	= pdata->bpp;
	fbi->fb.var.xres_virtual	= pdata->xres_virtual;
	fbi->fb.var.yres_virtual	= pdata->yres_virtual;

	ret = vt8500lcd_set_par(&fbi->fb);
	if (ret) {
		dev_err(&pdev->dev, "Failed to set parameters\n");
		goto failed_free_cmap;
	}

	writel(fbi->fb.fix.smem_start >> 22, fbi->regbase + 0x1c);
	writel((fbi->palette_phys & 0xfffffe00) | 1, fbi->regbase + 0x18);

	platform_set_drvdata(pdev, fbi);

	ret = register_framebuffer(&fbi->fb);
	if (ret < 0) {
		dev_err(&pdev->dev,
			"Failed to register framebuffer device: %d\n", ret);
		goto failed_free_cmap;
	}

	/*
	 * Ok, now enable the LCD controller
	 */
	writel(readl(fbi->regbase) | 1, fbi->regbase);

	return 0;

failed_free_cmap:
	if (fbi->fb.cmap.len)
		fb_dealloc_cmap(&fbi->fb.cmap);
failed_free_irq:
	free_irq(irq, fbi);
failed_free_palette:
	dma_free_coherent(&pdev->dev, fbi->palette_size,
			  fbi->palette_cpu, fbi->palette_phys);
failed_free_io:
	iounmap(fbi->regbase);
failed_free_res:
	release_mem_region(res->start, resource_size(res));
failed_fbi:
	platform_set_drvdata(pdev, NULL);
	kfree(fbi);
failed:
	return ret;
}

static int __devexit vt8500lcd_remove(struct platform_device *pdev)
{
	struct vt8500lcd_info *fbi = platform_get_drvdata(pdev);
	struct resource *res;
	int irq;

	unregister_framebuffer(&fbi->fb);

	writel(0, fbi->regbase);

	if (fbi->fb.cmap.len)
		fb_dealloc_cmap(&fbi->fb.cmap);

	irq = platform_get_irq(pdev, 0);
	free_irq(irq, fbi);

	dma_free_coherent(&pdev->dev, fbi->palette_size,
			  fbi->palette_cpu, fbi->palette_phys);

	iounmap(fbi->regbase);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	release_mem_region(res->start, resource_size(res));

	kfree(fbi);

	return 0;
}

static struct platform_driver vt8500lcd_driver = {
	.probe		= vt8500lcd_probe,
	.remove		= __devexit_p(vt8500lcd_remove),
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "vt8500-lcd",
	},
};

static int __init vt8500lcd_init(void)
{
	return platform_driver_register(&vt8500lcd_driver);
}

static void __exit vt8500lcd_exit(void)
{
	platform_driver_unregister(&vt8500lcd_driver);
}

module_init(vt8500lcd_init);
module_exit(vt8500lcd_exit);

MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>");
MODULE_DESCRIPTION("LCD controller driver for VIA VT8500");
MODULE_LICENSE("GPL");
+34 −0
Original line number Diff line number Diff line
/*
 *  linux/drivers/video/vt8500lcdfb.h
 *
 *  Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

struct vt8500lcd_info {
	struct fb_info		fb;
	void __iomem		*regbase;
	void __iomem		*palette_cpu;
	dma_addr_t		palette_phys;
	size_t			palette_size;
	wait_queue_head_t	wait;
};

static int bpp_values[] = {
	1,
	2,
	4,
	8,
	12,
	16,
	18,
	24,
};
+422 −0
Original line number Diff line number Diff line
/*
 *  WonderMedia WM8505 Frame Buffer device driver
 *
 *  Copyright (C) 2010 Ed Spiridonov <edo.rus@gmail.com>
 *    Based on vt8500lcdfb.c
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/wait.h>

#include <mach/vt8500fb.h>

#include "wm8505fb_regs.h"
#include "wmt_ge_rops.h"

#define DRIVER_NAME "wm8505-fb"

#define to_wm8505fb_info(__info) container_of(__info, \
						struct wm8505fb_info, fb)
struct wm8505fb_info {
	struct fb_info		fb;
	void __iomem		*regbase;
	unsigned int		contrast;
};


static int wm8505fb_init_hw(struct fb_info *info)
{
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	int i;

	/* I know the purpose only of few registers, so clear unknown */
	for (i = 0; i < 0x200; i += 4)
		writel(0, fbi->regbase + i);

	/* Set frame buffer address */
	writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR);
	writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR1);

	/* Set in-memory picture format to RGB 32bpp */
	writel(0x1c,		       fbi->regbase + WMT_GOVR_COLORSPACE);
	writel(1,		       fbi->regbase + WMT_GOVR_COLORSPACE1);

	/* Virtual buffer size */
	writel(info->var.xres,	       fbi->regbase + WMT_GOVR_XRES);
	writel(info->var.xres_virtual, fbi->regbase + WMT_GOVR_XRES_VIRTUAL);

	/* black magic ;) */
	writel(0xf,		       fbi->regbase + WMT_GOVR_FHI);
	writel(4,		       fbi->regbase + WMT_GOVR_DVO_SET);
	writel(1,		       fbi->regbase + WMT_GOVR_MIF_ENABLE);
	writel(1,		       fbi->regbase + WMT_GOVR_REG_UPDATE);

	return 0;
}

static int wm8505fb_set_timing(struct fb_info *info)
{
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	int h_start = info->var.left_margin;
	int h_end = h_start + info->var.xres;
	int h_all = h_end + info->var.right_margin;
	int h_sync = info->var.hsync_len;

	int v_start = info->var.upper_margin;
	int v_end = v_start + info->var.yres;
	int v_all = v_end + info->var.lower_margin;
	int v_sync = info->var.vsync_len + 1;

	writel(0, fbi->regbase + WMT_GOVR_TG);

	writel(h_start, fbi->regbase + WMT_GOVR_TIMING_H_START);
	writel(h_end,   fbi->regbase + WMT_GOVR_TIMING_H_END);
	writel(h_all,   fbi->regbase + WMT_GOVR_TIMING_H_ALL);
	writel(h_sync,  fbi->regbase + WMT_GOVR_TIMING_H_SYNC);

	writel(v_start, fbi->regbase + WMT_GOVR_TIMING_V_START);
	writel(v_end,   fbi->regbase + WMT_GOVR_TIMING_V_END);
	writel(v_all,   fbi->regbase + WMT_GOVR_TIMING_V_ALL);
	writel(v_sync,  fbi->regbase + WMT_GOVR_TIMING_V_SYNC);

	writel(1, fbi->regbase + WMT_GOVR_TG);

	return 0;
}


static int wm8505fb_set_par(struct fb_info *info)
{
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	if (!fbi)
		return -EINVAL;

	if (info->var.bits_per_pixel == 32) {
		info->var.red.offset = 16;
		info->var.red.length = 8;
		info->var.red.msb_right = 0;
		info->var.green.offset = 8;
		info->var.green.length = 8;
		info->var.green.msb_right = 0;
		info->var.blue.offset = 0;
		info->var.blue.length = 8;
		info->var.blue.msb_right = 0;
		info->fix.visual = FB_VISUAL_TRUECOLOR;
		info->fix.line_length = info->var.xres_virtual << 2;
	}

	wm8505fb_set_timing(info);

	writel(fbi->contrast<<16 | fbi->contrast<<8 | fbi->contrast,
		fbi->regbase + WMT_GOVR_CONTRAST);

	return 0;
}

static ssize_t contrast_show(struct device *dev,
			     struct device_attribute *attr, char *buf)
{
	struct fb_info *info = dev_get_drvdata(dev);
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	return sprintf(buf, "%d\n", fbi->contrast);
}

static ssize_t contrast_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t count)
{
	struct fb_info *info = dev_get_drvdata(dev);
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);
	unsigned long tmp;

	if (strict_strtoul(buf, 10, &tmp) || (tmp > 0xff))
		return -EINVAL;
	fbi->contrast = tmp;

	wm8505fb_set_par(info);

	return count;
}

static DEVICE_ATTR(contrast, 0644, contrast_show, contrast_store);

static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int wm8505fb_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp,
			   struct fb_info *info) {
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);
	int ret = 1;
	unsigned int val;
	if (regno >= 256)
		return -EINVAL;

	if (info->var.grayscale)
		red = green = blue =
			(19595 * red + 38470 * green + 7471 * blue) >> 16;

	switch (fbi->fb.fix.visual) {
	case FB_VISUAL_TRUECOLOR:
		if (regno < 16) {
			u32 *pal = info->pseudo_palette;

			val  = chan_to_field(red, &fbi->fb.var.red);
			val |= chan_to_field(green, &fbi->fb.var.green);
			val |= chan_to_field(blue, &fbi->fb.var.blue);

			pal[regno] = val;
			ret = 0;
		}
		break;
	}

	return ret;
}

static int wm8505fb_pan_display(struct fb_var_screeninfo *var,
				struct fb_info *info)
{
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	writel(var->xoffset, fbi->regbase + WMT_GOVR_XPAN);
	writel(var->yoffset, fbi->regbase + WMT_GOVR_YPAN);
	return 0;
}

static int wm8505fb_blank(int blank, struct fb_info *info)
{
	struct wm8505fb_info *fbi = to_wm8505fb_info(info);

	switch (blank) {
	case FB_BLANK_UNBLANK:
		wm8505fb_set_timing(info);
		break;
	default:
		writel(0,  fbi->regbase + WMT_GOVR_TIMING_V_SYNC);
		break;
	}

	return 0;
}

static struct fb_ops wm8505fb_ops = {
	.owner		= THIS_MODULE,
	.fb_set_par	= wm8505fb_set_par,
	.fb_setcolreg	= wm8505fb_setcolreg,
	.fb_fillrect	= wmt_ge_fillrect,
	.fb_copyarea	= wmt_ge_copyarea,
	.fb_imageblit	= sys_imageblit,
	.fb_sync	= wmt_ge_sync,
	.fb_pan_display	= wm8505fb_pan_display,
	.fb_blank	= wm8505fb_blank,
};

static int __devinit wm8505fb_probe(struct platform_device *pdev)
{
	struct wm8505fb_info	*fbi;
	struct resource		*res;
	void			*addr;
	struct vt8500fb_platform_data *pdata;
	int ret;

	pdata = pdev->dev.platform_data;

	ret = -ENOMEM;
	fbi = NULL;

	fbi = kzalloc(sizeof(struct wm8505fb_info) + sizeof(u32) * 16,
							GFP_KERNEL);
	if (!fbi) {
		dev_err(&pdev->dev, "Failed to initialize framebuffer device\n");
		ret = -ENOMEM;
		goto failed;
	}

	strcpy(fbi->fb.fix.id, DRIVER_NAME);

	fbi->fb.fix.type	= FB_TYPE_PACKED_PIXELS;
	fbi->fb.fix.xpanstep	= 1;
	fbi->fb.fix.ypanstep	= 1;
	fbi->fb.fix.ywrapstep	= 0;
	fbi->fb.fix.accel	= FB_ACCEL_NONE;

	fbi->fb.fbops		= &wm8505fb_ops;
	fbi->fb.flags		= FBINFO_DEFAULT
				| FBINFO_HWACCEL_COPYAREA
				| FBINFO_HWACCEL_FILLRECT
				| FBINFO_HWACCEL_XPAN
				| FBINFO_HWACCEL_YPAN
				| FBINFO_VIRTFB
				| FBINFO_PARTIAL_PAN_OK;
	fbi->fb.node		= -1;

	addr = fbi;
	addr = addr + sizeof(struct wm8505fb_info);
	fbi->fb.pseudo_palette	= addr;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "no I/O memory resource defined\n");
		ret = -ENODEV;
		goto failed_fbi;
	}

	res = request_mem_region(res->start, resource_size(res), "wm8505fb");
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to request I/O memory\n");
		ret = -EBUSY;
		goto failed_fbi;
	}

	fbi->regbase = ioremap(res->start, resource_size(res));
	if (fbi->regbase == NULL) {
		dev_err(&pdev->dev, "failed to map I/O memory\n");
		ret = -EBUSY;
		goto failed_free_res;
	}

	fb_videomode_to_var(&fbi->fb.var, &pdata->mode);

	fbi->fb.var.nonstd		= 0;
	fbi->fb.var.activate		= FB_ACTIVATE_NOW;

	fbi->fb.var.height		= -1;
	fbi->fb.var.width		= -1;
	fbi->fb.var.xres_virtual	= pdata->xres_virtual;
	fbi->fb.var.yres_virtual	= pdata->yres_virtual;
	fbi->fb.var.bits_per_pixel	= pdata->bpp;

	fbi->fb.fix.smem_start	= pdata->video_mem_phys;
	fbi->fb.fix.smem_len	= pdata->video_mem_len;
	fbi->fb.screen_base	= pdata->video_mem_virt;
	fbi->fb.screen_size	= pdata->video_mem_len;

	if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) {
		dev_err(&pdev->dev, "Failed to allocate color map\n");
		ret = -ENOMEM;
		goto failed_free_io;
	}

	wm8505fb_init_hw(&fbi->fb);

	fbi->contrast = 0x80;
	ret = wm8505fb_set_par(&fbi->fb);
	if (ret) {
		dev_err(&pdev->dev, "Failed to set parameters\n");
		goto failed_free_cmap;
	}

	platform_set_drvdata(pdev, fbi);

	ret = register_framebuffer(&fbi->fb);
	if (ret < 0) {
		dev_err(&pdev->dev,
			"Failed to register framebuffer device: %d\n", ret);
		goto failed_free_cmap;
	}

	ret = device_create_file(&pdev->dev, &dev_attr_contrast);
	if (ret < 0) {
		printk(KERN_WARNING "fb%d: failed to register attributes (%d)\n",
			fbi->fb.node, ret);
	}

	printk(KERN_INFO "fb%d: %s frame buffer at 0x%lx-0x%lx\n",
	       fbi->fb.node, fbi->fb.fix.id, fbi->fb.fix.smem_start,
	       fbi->fb.fix.smem_start + fbi->fb.fix.smem_len - 1);

	return 0;

failed_free_cmap:
	if (fbi->fb.cmap.len)
		fb_dealloc_cmap(&fbi->fb.cmap);
failed_free_io:
	iounmap(fbi->regbase);
failed_free_res:
	release_mem_region(res->start, resource_size(res));
failed_fbi:
	platform_set_drvdata(pdev, NULL);
	kfree(fbi);
failed:
	return ret;
}

static int __devexit wm8505fb_remove(struct platform_device *pdev)
{
	struct wm8505fb_info *fbi = platform_get_drvdata(pdev);
	struct resource *res;

	device_remove_file(&pdev->dev, &dev_attr_contrast);

	unregister_framebuffer(&fbi->fb);

	writel(0, fbi->regbase);

	if (fbi->fb.cmap.len)
		fb_dealloc_cmap(&fbi->fb.cmap);

	iounmap(fbi->regbase);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	release_mem_region(res->start, resource_size(res));

	kfree(fbi);

	return 0;
}

static struct platform_driver wm8505fb_driver = {
	.probe		= wm8505fb_probe,
	.remove		= __devexit_p(wm8505fb_remove),
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= DRIVER_NAME,
	},
};

static int __init wm8505fb_init(void)
{
	return platform_driver_register(&wm8505fb_driver);
}

static void __exit wm8505fb_exit(void)
{
	platform_driver_unregister(&wm8505fb_driver);
}

module_init(wm8505fb_init);
module_exit(wm8505fb_exit);

MODULE_AUTHOR("Ed Spiridonov <edo.rus@gmail.com>");
MODULE_DESCRIPTION("Framebuffer driver for WMT WM8505");
MODULE_LICENSE("GPL");
Loading