[PATCH] Add DCON support to the GX framebuffer driver

Jordan Crouse jordan.crouse at amd.unroutablecom
Wed Nov 29 11:14:54 EST 2006


Commit:     75588273ae499a26d01f6a0a828306b1cb5d34c3
Parent:     21330ae883083e08be81fe4732d969d9eb5e97e9
commit 75588273ae499a26d01f6a0a828306b1cb5d34c3
Author:     Jordan Crouse <jordan.crouse at amd.com>
AuthorDate: Thu Sep 21 16:39:54 2006 -0600
Commit:     Jordan Crouse <jordan.crouse at amd.com>
CommitDate: Tue Oct 3 13:48:52 2006 -0600

    [PATCH] Add DCON support to the GX framebuffer driver
    
    This patch adds support for the OLPC DCON controller.
    Original code by David Woodhouse, additional code by Jordan Crouse
---
 drivers/video/geode/Kconfig     |    9 +
 drivers/video/geode/Makefile    |    2 
 drivers/video/geode/gxfb_core.c |   25 ++
 drivers/video/geode/gxfb_dcon.c |  542 +++++++++++++++++++++++++++++++++++++++
 drivers/video/geode/gxfb_dcon.h |   71 +++++
 drivers/video/geode/video_gx.c  |   81 ++++++
 drivers/video/geode/video_gx.h  |    2 
 include/linux/i2c-id.h          |    1 
 8 files changed, 729 insertions(+), 4 deletions(-)

diff --git a/drivers/video/geode/Kconfig b/drivers/video/geode/Kconfig
index 4e173ef..60791ea 100644
--- a/drivers/video/geode/Kconfig
+++ b/drivers/video/geode/Kconfig
@@ -23,6 +23,15 @@ config FB_GEODE_GX
 
 	  If unsure, say N.
 
+config FB_GEODE_GX_DCON
+	tristate "One Laptop Per Child Display CONtroller support"
+	depends on FB_GEODE_GX
+	select I2C
+	---help---
+	  Add support fo the OLPC DCON controller.  This controller is only
+	  available on OLPC platforms.   Unless you have one of these
+	  platforms,you will want to say 'N'.
+
 config FB_GEODE_GX1
 	tristate "AMD Geode GX1 framebuffer support (EXPERIMENTAL)"
 	depends on FB && FB_GEODE && EXPERIMENTAL
diff --git a/drivers/video/geode/Makefile b/drivers/video/geode/Makefile
index f896565..390a97d 100644
--- a/drivers/video/geode/Makefile
+++ b/drivers/video/geode/Makefile
@@ -3,5 +3,7 @@ # Makefile for the Geode family framebuf
 obj-$(CONFIG_FB_GEODE_GX1) += gx1fb.o
 obj-$(CONFIG_FB_GEODE_GX)  += gxfb.o
 
+obj-$(CONFIG_FB_GEODE_GX_DCON) += gxfb_dcon.o
+
 gx1fb-objs := gx1fb_core.o display_gx1.o video_cs5530.o
 gxfb-objs  := gxfb_core.o display_gx.o video_gx.o
diff --git a/drivers/video/geode/gxfb_core.c b/drivers/video/geode/gxfb_core.c
index 0f5b068..ab14dda 100644
--- a/drivers/video/geode/gxfb_core.c
+++ b/drivers/video/geode/gxfb_core.c
@@ -121,6 +121,9 @@ static const struct fb_videomode gx_dcon
 extern int olpc_dcon_present;
 #endif
 
+unsigned long gxfb_dc_regs;
+EXPORT_SYMBOL(gxfb_dc_regs);
+
 static int gxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
 {
 	if (var->xres > 1600 || var->yres > 1200)
@@ -246,6 +249,8 @@ static int __init gxfb_map_video_memory(
 	if (!par->dc_regs)
 		return -ENOMEM;
 
+	gxfb_dc_regs = par->dc_regs;
+
 	ret = pci_request_region(dev, 0, "gxfb (framebuffer)");
 	if (ret < 0)
 		return ret;
@@ -276,12 +281,26 @@ static int __init gxfb_map_video_memory(
 	return 0;
 }
 
+int (*gxfb_ioctl_func)(struct fb_info *info, unsigned int cmd, unsigned long arg);
+EXPORT_SYMBOL(gxfb_ioctl_func);
+
+gxfb_ioctl( struct fb_info *info, unsigned int cmd, unsigned long arg) {
+  
+	int ret = -ENOTTY;
+  
+	if (gxfb_ioctl_func != NULL)
+		ret = gxfb_ioctl_func(info, cmd, arg);
+
+	return ret;
+}
+
 static struct fb_ops gxfb_ops = {
 	.owner		= THIS_MODULE,
 	.fb_check_var	= gxfb_check_var,
 	.fb_set_par	= gxfb_set_par,
 	.fb_setcolreg	= gxfb_setcolreg,
 	.fb_blank       = gxfb_blank,
+	.fb_ioctl       = gxfb_ioctl,
 	/* No HW acceleration for now. */
 	.fb_fillrect	= cfb_fillrect,
 	.fb_copyarea	= cfb_copyarea,
@@ -360,8 +379,9 @@ static int __init gxfb_probe(struct pci_
 	else
 		par->enable_crt = 1;
 
-	/* If the OLPC DCON is present, then we use a special
-	 * mode database (don't say we support modes that we don't).
+	/* We need to determine a display mode right now, so we will
+	 * check to see if the DCON was previously detected by the BIOS
+	 * and use that to make our mode database decision.
 	 */
 
 	modedb_ptr = (struct fb_videomode *) gx_modedb;
@@ -369,7 +389,6 @@ static int __init gxfb_probe(struct pci_
 
 #ifdef CONFIG_OLPC
 	if (olpc_dcon_present) {
-		printk(KERN_INFO "gxfb:  DCON detected.\n");
 		modedb_ptr = (struct fb_videomode *) gx_dcon_modedb;
 		modedb_size = ARRAY_SIZE(gx_dcon_modedb);
 	}
diff --git a/drivers/video/geode/gxfb_dcon.c b/drivers/video/geode/gxfb_dcon.c
new file mode 100644
index 0000000..e1eed59
--- /dev/null
+++ b/drivers/video/geode/gxfb_dcon.c
@@ -0,0 +1,542 @@
+/*
+ *  Mainly by David Woodhouse, somewhat modified by Jordan Crouse
+ *  
+ * Jordan's work is copyright (C) 2006 Advanced Micro Devices, Inc.
+ *
+ * This program is free software.  You can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation: either version 1 or
+ * (at your option) any later version.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/fb.h>
+#include "geodefb.h"
+#include <linux/i2c.h>
+#include <linux/i2c-id.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <asm/tsc.h>
+
+#include "gxfb_dcon.h"
+
+/* Module definitions */
+
+int resumeline = 905;
+module_param(resumeline, int, 0444);
+
+static int noinit;
+module_param(noinit, int, 0444);
+
+/* ioctl() defines */
+
+#define DCONIOC_SOURCE		_IOW('d', 0, int)
+#define DCONIOC_OUTPUT		_IOW('d', 1, int)
+#define DCONIOC_SETREG		_IOW('d', 2, int)
+#define DCONIOC_DUMPREG		_IOW('d', 3, int)
+#define DCONIOC_GETREG		_IOW('d', 4, int)
+#define DCONIOC_SETBL           _IOW('d', 5, int)
+#define DCONIOC_GETBL           _IOW('d', 6, int)
+
+/* I2C structures */
+
+static struct i2c_driver dcon_driver;
+static struct i2c_client *dcon_client;
+
+/* Base address of the GPIO registers */
+static unsigned long gpio_base;
+
+/* Current source */
+static int dcon_source = DCON_SOURCE_CPU;
+
+/* Current output type */
+static int dcon_output = DCON_OUTPUT_COLOR;
+
+/* Shadow register for the DCON_REG_MODE register */
+static unsigned short dcon_disp_mode;
+
+/* Variables used during switches */
+int dcon_waiting;
+
+static DECLARE_WAIT_QUEUE_HEAD(dcon_mode_wq);
+extern unsigned long gxfb_dc_regs;
+
+extern int gxfb_powerdown(struct fb_info *info);
+extern int gxfb_powerup(struct fb_info *info);
+
+static int dcon_set_source(struct fb_info *info, int arg)
+{
+	struct geodefb_par *par = info->par;
+	unsigned long val;
+
+	if (dcon_source == arg)
+		return 0;
+
+	dcon_source = arg;
+
+	if (arg == DCON_SOURCE_CPU) {
+		struct geodefb_par *par = info->par;
+		unsigned long timeo = jiffies + (10 * HZ);
+		unsigned long scanline1, scanline2;
+
+		/* Power up the graphics engine */
+		gxfb_powerup(info);
+
+		/* Wait for up to a second for our output to coincide with 
+		   DCON's */
+		
+		while (time_before(jiffies, timeo)) {
+			unsigned long dconblnk = 
+				(inl(gpio_base + GPIOx_READ_BACK) >> 12) & 1;
+			scanline1 = 
+				readl((void __iomem *)(gxfb_dc_regs + 0x6c));
+			scanline1 >>= 16;
+			scanline1 &= 0x7ff;
+
+			if (!dconblnk && scanline1 >= resumeline && 
+			    scanline1 <= (resumeline+2))
+				goto ready;
+
+#if 0
+			if (!dconblnk)
+				printk("Not ready. blnk %ld, line %ld\n",
+				       dconblnk, scanline1);
+#endif
+		}
+
+		printk("Wait for VGA ready timed out\n");
+ready:
+		outl(1<<11, gpio_base + GPIOx_OUT_VAL);
+		scanline2 = readl((void __iomem *)(gxfb_dc_regs + 0x6c));
+		scanline2 >>= 16;
+		scanline2 &= 0x7ff;
+		printk("Re-enabled between %ld and %ld\n",
+				scanline1, scanline2);
+
+	} else {
+		int t;
+		DECLARE_WAITQUEUE(wait, current);
+
+		add_wait_queue(&dcon_mode_wq, &wait);
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		dcon_waiting = 1;
+
+		/* Clear GPIO11 (DCONLOAD) - this implies that the DCON is in 
+		   control */
+
+		outl(1 << (11 + 16), gpio_base + GPIOx_OUT_VAL);
+		
+		t = schedule_timeout(HZ/2);
+		remove_wait_queue(&dcon_mode_wq, &wait);
+		set_current_state(TASK_RUNNING);
+
+		if (dcon_waiting) {
+			printk("Timeout entering DCON mode\n");
+
+			/* Re-enable GPIO11 - we never gave up control */
+			outl(1 << 11, gpio_base + GPIOx_OUT_VAL);
+
+			return -1;
+		}
+
+		/* Turn off the graphics engine completely */
+		gxfb_powerdown(info);
+	}
+
+	return 0;
+}
+
+static int dcon_set_output(struct fb_info *info, int arg)
+{
+	if (dcon_output == arg)
+		return 0;
+
+	dcon_output = arg;
+
+	if (arg == DCON_OUTPUT_MONO) {
+		dcon_disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA);
+		dcon_disp_mode |= MODE_MONO_LUMA;
+	}
+	else {
+		dcon_disp_mode &= ~(MODE_MONO_LUMA);
+		dcon_disp_mode |= (MODE_CSWIZZLE | MODE_COL_AA);
+	}
+
+	i2c_smbus_write_word_data(dcon_client, DCON_REG_MODE, dcon_disp_mode);
+
+	return 0;
+}
+
+static int gxfb_dcon_ioctl(struct fb_info *info, unsigned int cmd,
+			   unsigned long arg)
+{
+	int karg;
+	int ret = 0;
+	unsigned short reg, val;
+
+	switch (cmd) {
+	case DCONIOC_SOURCE:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+
+		if (karg > 1 || karg < -1)
+			return -EINVAL;
+
+		if (karg > -1)
+			ret = dcon_set_source(info, karg);
+
+		if (ret) 
+			karg = -1;
+		else
+			karg = dcon_source;
+
+		ret = 0;
+		break;
+
+	case DCONIOC_OUTPUT:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+
+		if (karg > 1 || karg < -1)
+			return -EINVAL;
+
+		if (karg > -1)
+			ret = dcon_set_output(info, karg);
+
+		if (ret) 
+			return ret;
+
+		karg = dcon_output;
+		break;
+
+	case DCONIOC_SETREG:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+		reg = karg >> 16;
+		val = karg & 0xffff;
+
+		if (reg >= 128)
+			return -EINVAL;
+
+		i2c_smbus_write_word_data(dcon_client, reg, val);
+		karg = 0;
+		break;
+
+	case DCONIOC_GETREG:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+
+		if (karg >= 128)
+			return -EINVAL;
+
+		karg = i2c_smbus_read_word_data(dcon_client, karg);
+		break;
+
+	case DCONIOC_DUMPREG:
+		for (reg = 0; reg < 11; reg++) {
+			val = i2c_smbus_read_word_data(dcon_client, reg);
+			printk("Reg %d: 0x%x\n", reg, val);
+		}
+		karg = 0;
+		break;
+
+	case DCONIOC_SETBL:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+
+		i2c_smbus_write_word_data(dcon_client, DCON_REG_BRIGHT, 
+			karg & 0x0F);
+		karg = 0;
+		break;
+
+	case DCONIOC_GETBL:
+		if (get_user(karg, (int __user *)arg))
+			return -EFAULT;
+
+		val = i2c_smbus_read_word_data(dcon_client, DCON_REG_BRIGHT);
+		karg = val & 0x0F;
+		break;
+
+	default:
+		return -ENOTTY;
+	}
+	if (put_user(karg, (int __user *)arg))
+		return -EFAULT;
+	return ret;
+}
+
+static unsigned short normal_i2c[] = { 0x0D, I2C_CLIENT_END };
+I2C_CLIENT_INSMOD;
+
+extern int (*gxfb_ioctl_func)(struct fb_info *info, unsigned int cmd,
+		       unsigned long arg);
+
+
+static int dcon_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+	struct i2c_client *client;
+	uint16_t ver;
+	int rc;
+
+	if (adap->id != I2C_HW_SMBUS_SCX200) {
+		printk(KERN_ERR "gxfb-dcon: Invalid I2C bus (%d not %d)\n",
+				adap->id, I2C_HW_SMBUS_SCX200);
+		return -ENXIO;
+	}
+
+	client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+	if (client == NULL)
+		return -ENOMEM;
+
+	strncpy(client->name, "DCON", I2C_NAME_SIZE);
+	client->addr = addr;
+	client->adapter = adap;
+	client->driver = &dcon_driver;
+
+	if ((rc = i2c_attach_client(client)) != 0) {
+		printk(KERN_ERR "gxfb-dcon: Unable to attach the I2C client.\n");
+		kfree(client);
+		return rc;
+	}
+
+	ver = i2c_smbus_read_word_data(client, DCON_REG_ID);
+
+	if ((ver >> 8) != 0xDC) {
+		/* Not a DCON chip? */
+		printk(KERN_ERR "gxfb-dcon: DCON ID not 0xDCxx: 0x%04x instead.\n", ver);
+		i2c_detach_client(client);
+		kfree(client);
+		return -ENXIO;
+	}
+
+	printk(KERN_INFO "gxfb-dcon: Discovered DCON version %x\n", ver & 0xFF);
+
+	dcon_client = client;
+	gxfb_ioctl_func = gxfb_dcon_ioctl;
+
+	if (!noinit) {
+		/* Initialize the DCON registers */
+
+		/* Switch to OLPC mode */
+		i2c_smbus_write_word_data(client, 0x0b, 0x002);
+
+		/* Colour swizzle, AA, no passthrough, backlight */
+
+		dcon_disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE |
+			MODE_CSWIZZLE | MODE_COL_AA;
+
+		i2c_smbus_write_word_data(client, DCON_REG_MODE,
+				dcon_disp_mode);
+
+		/* Initialise SDRAM */
+
+		i2c_smbus_write_word_data(client, 0x3a, 0xe040);
+		i2c_smbus_write_word_data(client, 0x3b, 0x0028);
+		i2c_smbus_write_word_data(client, 0x3c, 0x0000);
+		i2c_smbus_write_word_data(client, 0x3d, 0x0000);
+		i2c_smbus_write_word_data(client, 0x3e, 0x400f);
+		i2c_smbus_write_word_data(client, 0x3f, 0x04b0);
+		i2c_smbus_write_word_data(client, 0x40, 0x0384);
+		i2c_smbus_write_word_data(client, 0x41, 0x0101);
+		i2c_smbus_write_word_data(client, 0x42, 0x0101);
+		i2c_smbus_write_word_data(client, 0x43, 0x0101);
+	}
+
+	return 0;
+}
+
+static int dcon_attach(struct i2c_adapter *adap)
+{
+	int ret;
+
+	ret = i2c_probe(adap, &addr_data, dcon_probe);
+
+	if (dcon_client == NULL)
+		printk(KERN_ERR "gxfb-dcon: No DCON found on SMBus\n");
+
+	return ret;
+}
+
+static int dcon_detach(struct i2c_client *client)
+{
+	int rc;
+	gxfb_ioctl_func = NULL;
+	dcon_client = NULL;
+
+	if ((rc = i2c_detach_client(client)) == 0)
+		kfree(i2c_get_clientdata(client));
+
+	return rc;
+}
+
+static struct i2c_driver dcon_driver = {
+	.driver = {
+		.name	= "DCON",
+	},
+	.id = I2C_DRIVERID_DCON,
+	.attach_adapter = dcon_attach,
+	.detach_client = dcon_detach,
+};
+
+
+
+int dcon_interrupt(int irq, void *id, struct pt_regs *regs)
+{
+	unsigned long gpios = inl(gpio_base + GPIOx_READ_BACK);
+	int dconstat = (gpios >> 5) & 3;
+	int dconblnk = (gpios >> 12) & 1;
+	cycles_t foo = get_cycles();
+	static cycles_t foo1;
+	static int lastec7;
+
+	unsigned long scanline = readl((void __iomem *)(gxfb_dc_regs + 0x6c));
+	int ec7 = inl(gpio_base + 0xDC);
+	int dconirq = (gpios >> 7) & 1;
+
+	scanline >>= 16;
+	scanline &= 0x7ff;
+	printk("IRQ! TSC %08lx (+%ld), line %ld, STAT %d BLNK %d IRQ %d EC7 %d:+%d\n",
+	       (long)foo, (long)(foo-foo1), scanline, dconstat, dconblnk, dconirq, ec7, ec7-lastec7);
+	foo1 = foo;
+	lastec7 = ec7;
+
+	/* Clear the negative edge status for GPIO7 */
+	outl(1 << 7, gpio_base + GPIOx_NEGEDGE_STS);
+
+#if 0
+	printk("GPIOL_NEGEDGE_STS[7] is %d\n", (inl(gpio_base+0x4c)>>7) & 1);
+
+	outl(1<<7, gpio_base+0x4c);
+	outl(1<<(16+7), gpio_base + GPIOx_EVNTCNT_EN);
+	outl(1<<(7), gpio_base + GPIOx_EVNTCNT_EN);
+#endif
+	if (dconstat == 2) {
+		dcon_waiting = 0;
+		wake_up(&dcon_mode_wq);
+	}
+	return IRQ_HANDLED;
+}
+
+
+/* List of GPIOs that we care about:
+   (in)  GPIO12   -- DCONBLNK
+   (in)  GPIO[56] -- DCONSTAT[01]
+   (out) GPIO11   -- DCONLOAD
+*/
+
+#define IN_GPIOS ((1<<5) | (1<<6) | (1<<12))
+#define OUT_GPIOS (1<<11)
+
+int __init gxfb_dcon_init(void)
+{
+	unsigned long lo, hi;
+	unsigned char lob;
+
+	rdmsr(MSR_LBAR_GPIO, lo, hi);
+
+	/* Check the mask and whether GPIO is enabled (sanity check) */
+	if (hi != 0x0000f001) {
+		printk(KERN_WARNING "GPIO not enabled -- cannot use DCON\n");
+		return -ENODEV;
+	}
+
+	/* Mask off the IO base address */
+	gpio_base = lo & 0x0000ff00;
+
+	/* Set the directions for the GPIO pins */
+
+	outl(OUT_GPIOS | (IN_GPIOS << 16), gpio_base + GPIOx_OUT_EN);
+	outl(IN_GPIOS | (OUT_GPIOS << 16), gpio_base + GPIOx_IN_EN);
+
+	/* Turn off the event enable for GPIO7 just to be safe */
+	outl(1 << (16 + 7), gpio_base + GPIOx_EVNT_EN);
+
+	/* Set up the interrupt mappings first, so we can collect the
+	 * first interrupt when it happens 
+	 */
+
+	hi = inl(gpio_base + GPIO_MAP_X);
+	hi &= 0x0fffffff;
+	hi |= 0x70000000;
+	outl(hi, gpio_base + GPIO_MAP_X);
+
+	/* Don't map the GPIO12 interrupt */
+
+	hi = inl(gpio_base + GPIO_MAP_Y);
+	hi &= 0xfff0ffff;
+	hi |= 0x00000000;	
+	outl(hi, gpio_base + GPIO_MAP_Y);
+
+	/* Enable GPIO IRQ 7 to trigger the PIC interrupt in the Z sources */
+
+	rdmsr(0x51400023, hi, lo);
+	hi &= 0x0fffffff;
+	hi |= (DCON_IRQ << 28);
+	wrmsrl(0x51400023, hi);
+
+	/* Select edge level for interrupt (in PIC) */
+
+	lob = inb(0x4d0);
+	lob &= ~(1 << DCON_IRQ);
+	outb(lob, 0x4d0);
+
+	/* Register the interupt handler */
+	request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", &dcon_driver);
+
+	/* Clear INV_EN for GPIO7 (DCONIRQ) */
+	outl((1<<(16+7)), gpio_base + GPIOx_INV_EN);
+
+	/* Enable filter for GPIO12 (DCONBLANK) */
+	outl(1<<(12), gpio_base + GPIOx_IN_FLTR_EN);
+
+	/* Disable filter for GPIO7 */
+	outl(1<<(16+7), gpio_base + GPIOx_IN_FLTR_EN);
+
+	/* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
+
+	outl(1<<(16+7), gpio_base + GPIOx_EVNTCNT_EN);
+	outl(1<<(16+12), gpio_base + GPIOx_EVNTCNT_EN);
+
+	/* Add GPIO12 to the Filter Event Pair #7 */
+	outb(12, gpio_base + GPIO_FE7_SEL);
+
+	/* Turn off negative Edge Enable for GPIO12 */
+	outl(1<<(16+12), gpio_base + GPIOx_NEGEDGE_EN);
+
+	/* Enable negative Edge Enable for GPIO7 */
+	outl(1<<7, gpio_base + GPIOx_NEGEDGE_EN);
+
+	/* Zero the filter amount for Filter Event Pair #7 */
+	outw(0, gpio_base + GPIO_FLT7_AMNT);
+
+	/* Clear the negative edge status for GPIO7 and GPIO12 */
+	outl((1<<7) | (1<<12), gpio_base+0x4c);
+
+	/* FIXME:  Clear the posiitive status as well, just to be sure */
+	outl((1<<7) | (1<<12), gpio_base+0x48);
+
+	/* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
+	outl((1<<(7))|(1<<12), gpio_base + GPIOx_EVNT_EN);
+
+	/* Assert DCONLOAD - this asserts that the CPU is still in control */
+	outl(1<<11, gpio_base + GPIOx_OUT_VAL);
+
+	/* Attach the I2C driver */
+	i2c_add_driver(&dcon_driver);
+
+	return 0;
+}
+
+void __exit gxfb_dcon_exit(void)
+{
+	free_irq(DCON_IRQ, &dcon_driver);
+	i2c_del_driver(&dcon_driver);
+}
+
+module_init(gxfb_dcon_init);
+module_exit(gxfb_dcon_exit);
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/geode/gxfb_dcon.h b/drivers/video/geode/gxfb_dcon.h
new file mode 100644
index 0000000..31598e9
--- /dev/null
+++ b/drivers/video/geode/gxfb_dcon.h
@@ -0,0 +1,71 @@
+#ifndef GXFB_DCON_H_
+#define GXFB_DCON_H_
+
+/* DCON registers */
+
+#define DCON_REG_ID		 0
+#define DCON_REG_MODE		 1
+
+#define MODE_PASSTHRU	(1<<0)
+#define MODE_SLEEP	(1<<1)
+#define MODE_SLEEP_AUTO	(1<<2)
+#define MODE_BL_ENABLE	(1<<3)
+#define MODE_BLANK	(1<<4)
+#define MODE_CSWIZZLE	(1<<5)
+#define MODE_COL_AA	(1<<6)
+#define MODE_MONO_LUMA	(1<<7)
+#define MODE_SCAN_INT	(1<<8)
+#define MODE_CLOCKDIV	(1<<9)
+#define MODE_DEBUG	(1<<14)
+#define MODE_SELFTEST	(1<<15)
+
+#define DCON_REG_HRES		2
+#define DCON_REG_HTOTAL		3
+#define DCON_REG_HSYNC_WIDTH	4
+#define DCON_REG_VRES		5
+#define DCON_REG_VTOTAL		6
+#define DCON_REG_VSYNC_WIDTH	7
+#define DCON_REG_TIMEOUT	8
+#define DCON_REG_SCAN_INT	9
+#define DCON_REG_BRIGHT		10
+
+/* GPIO registers (CS5536) */
+
+#define MSR_LBAR_GPIO		0x5140000C
+
+#define GPIOx_OUT_VAL     0x00
+#define GPIOx_OUT_EN      0x04
+#define GPIOx_IN_EN       0x20
+#define GPIOx_INV_EN      0x24
+#define GPIOx_IN_FLTR_EN  0x28
+#define GPIOx_EVNTCNT_EN  0x2C
+#define GPIOx_READ_BACK   0x30
+#define GPIOx_EVNT_EN     0x38
+#define GPIOx_NEGEDGE_EN  0x44
+#define GPIOx_NEGEDGE_STS 0x4C
+#define GPIO_FLT7_AMNT    0xD8
+#define GPIO_MAP_X        0xE0
+#define GPIO_MAP_Y        0xE4
+#define GPIO_FE7_SEL      0xF7
+
+
+/* Status values */
+
+#define DCONSTAT_SCANINT	0
+#define DCONSTAT_SCANINT_DCON	1
+#define DCONSTAT_DISPLAYLOAD	2
+#define DCONSTAT_MISSED		3
+
+/* Source values */
+
+#define DCON_SOURCE_DCON        0
+#define DCON_SOURCE_CPU         1
+
+/* Output values */
+#define DCON_OUTPUT_COLOR       0
+#define DCON_OUTPUT_MONO        1
+
+/* Interrupt */
+#define DCON_IRQ                6
+
+#endif
diff --git a/drivers/video/geode/video_gx.c b/drivers/video/geode/video_gx.c
index ed6a174..16d5b54 100644
--- a/drivers/video/geode/video_gx.c
+++ b/drivers/video/geode/video_gx.c
@@ -19,6 +19,7 @@ #include <asm/msr.h>
 
 #include "geodefb.h"
 #include "video_gx.h"
+#include "display_gx.h"
 
 
 /*
@@ -289,6 +290,77 @@ static void gx_configure_display(struct 
 		gx_configure_tft(info);
 }
 
+/* This is a speedier way to power down the graphics engine -
+   it is intended to be called through the blanking infrastructure below
+   but also to be called by things like the DCON driver
+*/
+
+static int gxfb_powered_down;
+unsigned long gx_pm_regs[5];
+
+#define VC_VCFG  0
+#define VC_DCFG  1
+#define VC_FP_PM 2
+#define DC_GCFG  3
+#define DC_DCFG  4
+
+int gxfb_powerdown(struct fb_info *info) {
+
+	struct geodefb_par *par = info->par;
+
+	/* Bail if we have already saved our state and powered down */
+	if (gxfb_powered_down == 1)
+		return 0;
+
+	/* Disable the video hardware */
+	gx_pm_regs[VC_VCFG] = readl(par->vid_regs + GX_VCFG);
+	writel(gx_pm_regs[VC_VCFG] & ~0x01, par->vid_regs + GX_VCFG);
+
+	/* Black the dacs, turn off horiz / vert sync and turn off crt */
+	gx_pm_regs[VC_DCFG] = readl(par->vid_regs + GX_DCFG);
+	writel(gx_pm_regs[VC_DCFG] & ~0x0F, par->vid_regs + GX_DCFG);
+
+	/* Turn off the flat panel */
+	gx_pm_regs[VC_FP_PM] = readl(par->vid_regs + GX_FP_PM);
+	writel(gx_pm_regs[VC_FP_PM] & ~GX_FP_PM_P, par->vid_regs + GX_FP_PM);
+
+	/* Unlock the DC - this will remain unlocked until power up */
+	writel(0x4758, par->dc_regs + DC_UNLOCK);
+
+	/* Disable video, icon, cursor and the FIFO */
+	gx_pm_regs[DC_GCFG] = readl(par->dc_regs + DC_GENERAL_CFG);
+	writel(gx_pm_regs[DC_GCFG] & ~0x0F, par->dc_regs + DC_GENERAL_CFG);
+	
+	/* Disable video data enable, graphics data enable and the timing generator */
+	gx_pm_regs[DC_DCFG] = readl(par->dc_regs + DC_DISPLAY_CFG);
+	writel(gx_pm_regs[DC_DCFG] & ~0x19, par->dc_regs + DC_DISPLAY_CFG);
+
+	gxfb_powered_down = 1;
+
+	return 0;
+}
+ 
+int gxfb_powerup(struct fb_info *info) {
+
+	struct geodefb_par *par = info->par;
+
+	if (gxfb_powered_down == 0)
+		return 0;
+
+	writel(gx_pm_regs[VC_VCFG], par->vid_regs + GX_VCFG);
+	writel(gx_pm_regs[VC_DCFG], par->vid_regs + GX_DCFG);
+	writel(gx_pm_regs[VC_FP_PM], par->vid_regs + GX_FP_PM);	
+	writel(gx_pm_regs[DC_DCFG], par->dc_regs + DC_DISPLAY_CFG);
+
+	/* Do this one last because it will turn on the FIFO which will start the line count */
+	writel(gx_pm_regs[DC_GCFG], par->dc_regs + DC_GENERAL_CFG);
+
+	writel(0x0, par->dc_regs + DC_UNLOCK);
+
+	gxfb_powered_down  = 0;
+	return 0;
+}
+
 static int gx_blank_display(struct fb_info *info, int blank_mode)
 {
 	struct geodefb_par *par = info->par;
@@ -315,6 +387,7 @@ static int gx_blank_display(struct fb_in
 	default:
 		return -EINVAL;
 	}
+
 	dcfg = readl(par->vid_regs + GX_DCFG);
 	dcfg &= ~(GX_DCFG_DAC_BL_EN
 		  | GX_DCFG_HSYNC_EN | GX_DCFG_VSYNC_EN);
@@ -326,7 +399,7 @@ static int gx_blank_display(struct fb_in
 		dcfg |= GX_DCFG_VSYNC_EN;
 	writel(dcfg, par->vid_regs + GX_DCFG);
 
-	/* Power on/off flat panel. */
+	/* Power on/off flat panel */
 
 	if (par->enable_crt == 0) {
 		fp_pm = readl(par->vid_regs + GX_FP_PM);
@@ -345,3 +418,9 @@ struct geode_vid_ops gx_vid_ops = {
 	.configure_display = gx_configure_display,
 	.blank_display	   = gx_blank_display,
 };
+
+/* We define these as special hooks for the DCON driver to hook into */
+
+EXPORT_SYMBOL(gxfb_powerdown);
+EXPORT_SYMBOL(gxfb_powerup);
+
diff --git a/drivers/video/geode/video_gx.h b/drivers/video/geode/video_gx.h
index ce28d8f..4f9c9b3 100644
--- a/drivers/video/geode/video_gx.h
+++ b/drivers/video/geode/video_gx.h
@@ -20,6 +20,8 @@ #define GX_VP_PAD_SELECT_TFT           0
 
 /* Geode GX video processor registers */
 
+#define GX_VCFG         0x0000
+
 #define GX_DCFG		0x0008
 #  define GX_DCFG_CRT_EN		0x00000001
 #  define GX_DCFG_HSYNC_EN		0x00000002
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
index 9418519..1d8aa7d 100644
--- a/include/linux/i2c-id.h
+++ b/include/linux/i2c-id.h
@@ -116,6 +116,7 @@ #define I2C_DRIVERID_BT866	85	/* Conexan
 #define I2C_DRIVERID_KS0127	86	/* Samsung ks0127 video decoder */
 #define I2C_DRIVERID_TLV320AIC23B 87	/* TI TLV320AIC23B audio codec  */
 #define I2C_DRIVERID_ISL1208	88	/* Intersil ISL1208 RTC		*/
+#define I2C_DRIVERID_DCON       89
 
 #define I2C_DRIVERID_I2CDEV	900
 #define I2C_DRIVERID_ARP        902    /* SMBus ARP Client              */


More information about the Commits-kernel mailing list