diff -BNburp linux-3.3.8/arch/mips/include/asm/mach-ralink/rt305x/irq.h linux-3.3.8-clockradio/arch/mips/include/asm/mach-ralink/rt305x/irq.h
--- linux-3.3.8/arch/mips/include/asm/mach-ralink/rt305x/irq.h	2014-09-17 15:05:51.210016660 +0200
+++ linux-3.3.8-clockradio/arch/mips/include/asm/mach-ralink/rt305x/irq.h	2014-08-16 12:14:50.000000000 +0200
@@ -10,7 +10,7 @@
 #define __ASM_MACH_RALINK_RT305X_IRQ_H
 
 #define MIPS_CPU_IRQ_BASE	0
-#define NR_IRQS			48
+#define NR_IRQS			54
 
 #include_next <irq.h>
 
diff -BNburp linux-3.3.8/arch/mips/ralink/common/gpio.c linux-3.3.8-clockradio/arch/mips/ralink/common/gpio.c
--- linux-3.3.8/arch/mips/ralink/common/gpio.c	2014-09-17 15:05:51.690016643 +0200
+++ linux-3.3.8-clockradio/arch/mips/ralink/common/gpio.c	2014-08-16 12:06:54.000000000 +0200
@@ -169,6 +169,7 @@ static int ramips_gpio_irq_set_type(stru
 	unsigned int val;
 
 	if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+		printk("Invalid irq mode for gpio %d\n", d->irq - gpio_data->irq_base - rg->chip.base);
 		return -EINVAL;
 	}
 
diff -BNburp linux-3.3.8/arch/mips/ralink/rt305x/mach-carambola.c linux-3.3.8-clockradio/arch/mips/ralink/rt305x/mach-carambola.c
--- linux-3.3.8/arch/mips/ralink/rt305x/mach-carambola.c	2014-09-17 15:05:51.638016645 +0200
+++ linux-3.3.8-clockradio/arch/mips/ralink/rt305x/mach-carambola.c	2014-08-17 14:37:33.000000000 +0200
@@ -15,6 +15,8 @@
 #include <linux/mtd/partitions.h>
 #include <linux/mtd/physmap.h>
 #include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
 
 #include <asm/mach-ralink/machine.h>
 #include <asm/mach-ralink/rt305x.h>
@@ -22,6 +24,14 @@
 #include <asm/sizes.h>
 #include <linux/i2c.h>
 #include <linux/i2c-gpio.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+#include <linux/spi/spi_gpio.h>
+#include <linux/spi/mcp23s08.h>
+#include <linux/i2c/apds990x.h>
+#include <linux/gpio_keys.h>
+#include <linux/input.h>
 
 #include "devices.h"
 
@@ -76,6 +86,33 @@ static int __init carambola_register_gpi
        return 0;
 }
 
+
+static struct mcp23s08_platform_data mcp23008_data = {
+	.chip[0] = {
+		.pullups = 0x00ff,
+	},
+	.base = 52, //ToDo: use const instead of magic number
+};
+
+static struct apds990x_platform_data apds990x_data = {
+	.cf		= {},
+	.pdrive	= APDS_IRLED_CURR_50mA,
+};
+
+static struct i2c_board_info carambola_i2c_board_info[] __initdata = {
+	{
+		I2C_BOARD_INFO("ds3231", 0x68),
+	},
+	{
+		I2C_BOARD_INFO("apds990x", 0x39),
+		.platform_data=&apds990x_data,
+	},
+	{
+		I2C_BOARD_INFO("mcp23008", 0x20),
+		.platform_data=&mcp23008_data,
+	}
+};
+
 static struct i2c_gpio_platform_data carambola_i2c_gpio_data = {
 	.sda_pin        = 1,
 	.scl_pin        = 2,
@@ -89,25 +126,79 @@ static struct platform_device carambola_
 	},
 };
 
+
+/* GPIO KEY */
+#define GPIO_KEY(c, g, d) { .code = c, .gpio = g, .desc = d, .active_low = 1 }
+
+static struct gpio_keys_button gpio_buttons[] = {
+	GPIO_KEY(KEY_A, 52, "a"),
+	GPIO_KEY(KEY_B, 53, "b"),
+	GPIO_KEY(KEY_C, 54, "c"),
+	GPIO_KEY(KEY_D, 55, "d"),
+	GPIO_KEY(KEY_E, 56, "e"),
+	GPIO_KEY(KEY_F, 57, "f"),
+	GPIO_KEY(KEY_G, 58, "g"),
+	GPIO_KEY(KEY_H, 59, "h"),
+};
+
+static struct gpio_keys_platform_data gpio_key_info = {
+	.buttons        = gpio_buttons,
+	.nbuttons       = ARRAY_SIZE(gpio_buttons),
+	.poll_interval = 100,
+};
+
+static struct platform_device gpio_keys_device = {
+	.name   = "gpio-keys-polled",
+	.id     = -1,
+	.dev    = {
+		.platform_data  = &gpio_key_info,
+	},
+};
+
+
 static struct platform_device *carambola_devices[] __initdata = {
-        &carambola_i2c_gpio
+	&carambola_i2c_gpio,
+	&gpio_keys_device,
 };
 
-static struct spi_board_info carambola_spi_info[] = {
-	{
+
+static struct spi_board_info __initdata carambola_spi_devices[] = {
+	{ /* Display, on native SPI-bus (id=0) */
+		.modalias		= "spi-os288048",
+		.irq            = -1,
+		.max_speed_hz   = 52428800,
 		.bus_num	= 0,
 		.chip_select	= 0,
-		.max_speed_hz	= 0,
-		.modalias	= "spidev",
-	}
+		.platform_data = (void *) RT305X_GPIO_14, //D/C line
+	},
 };
 
+
 static void __init carambola_init(void)
 {
-	rt305x_gpio_init((RT305X_GPIO_MODE_GPIO << RT305X_GPIO_MODE_UART0_SHIFT) |
+	int r;
+	int *regs;
+	rt305x_gpio_init((RT305X_GPIO_MODE_GPIO << RT305X_GPIO_MODE_UART0_SHIFT)|
 			 RT305X_GPIO_MODE_I2C);
+
 	carambola_register_gpiodev();
+
+	r=gpio_request(RT305X_GPIO_13, "DS3231 IRQ");
+	if (r<0) {
+		printk("Request for ds3231 irq gpio failed: %d\n", r);
+	}
+	gpio_direction_input(RT305X_GPIO_13);
+	irq_set_irq_type(gpio_to_irq(RT305X_GPIO_13), IRQF_TRIGGER_FALLING);
+	r=gpio_request(RT305X_GPIO_11, "APDS990x IRQ");
+	if (r<0) {
+		printk("Request for adps990x irq gpio failed: %d\n", r);
+	}
+	gpio_direction_input(RT305X_GPIO_11);
+	carambola_i2c_board_info[0].irq=gpio_to_irq(RT305X_GPIO_13);
+	carambola_i2c_board_info[1].irq=gpio_to_irq(RT305X_GPIO_11);
 	platform_add_devices(carambola_devices, ARRAY_SIZE(carambola_devices));
+	i2c_register_board_info(0, carambola_i2c_board_info,
+			ARRAY_SIZE(carambola_i2c_board_info));
 
 	/* we want fixed partitions sizes for now */
 	__rt305x_register_flash(0,
@@ -118,8 +209,10 @@ static void __init carambola_init(void)
 	rt305x_register_ethernet();
 	rt305x_register_wifi();
 	rt305x_register_wdt();
-	rt305x_register_spi(carambola_spi_info, ARRAY_SIZE(carambola_spi_info));
+	rt305x_register_spi(carambola_spi_devices, ARRAY_SIZE(carambola_spi_devices));
 	rt305x_register_usb();
+
+
 }
 
 MIPS_MACHINE(RAMIPS_MACH_CARAMBOLA, "CARAMBOLA", "CARAMBOLA",
diff -BNburp linux-3.3.8/drivers/rtc/rtc-ds1307.c linux-3.3.8-clockradio/drivers/rtc/rtc-ds1307.c
--- linux-3.3.8/drivers/rtc/rtc-ds1307.c	2014-09-17 15:05:39.826017060 +0200
+++ linux-3.3.8-clockradio/drivers/rtc/rtc-ds1307.c	2014-08-16 13:12:30.000000000 +0200
@@ -871,6 +871,7 @@ read_rtc:
 				bin2bcd(tmp));
 	}
 
+	device_set_wakeup_capable(&client->dev, 1);
 	ds1307->rtc = rtc_device_register(client->name, &client->dev,
 				&ds13xx_rtc_ops, THIS_MODULE);
 	if (IS_ERR(ds1307->rtc)) {
@@ -889,7 +890,6 @@ read_rtc:
 			goto exit_irq;
 		}
 
-		device_set_wakeup_capable(&client->dev, 1);
 		set_bit(HAS_ALARM, &ds1307->flags);
 		dev_dbg(&client->dev, "got IRQ %d\n", client->irq);
 	}
diff -BNburp linux-3.3.8/drivers/spi/spi-ramips.c linux-3.3.8-clockradio/drivers/spi/spi-ramips.c
--- linux-3.3.8/drivers/spi/spi-ramips.c	2014-09-17 15:05:24.034017615 +0200
+++ linux-3.3.8-clockradio/drivers/spi/spi-ramips.c	2014-05-03 15:21:46.000000000 +0200
@@ -198,7 +198,8 @@ static inline int ramips_spi_wait_till_r
 		if ((status & SPISTAT_BUSY) == 0)
 			return 0;
 
-		udelay(1);
+//This slows things down without actually seeming to do something useful.
+//		udelay(1);
 	}
 
 	return -ETIMEDOUT;
diff -BNburp linux-3.3.8/drivers/video/Kconfig linux-3.3.8-clockradio/drivers/video/Kconfig
--- linux-3.3.8/drivers/video/Kconfig	2014-09-17 15:05:43.838016919 +0200
+++ linux-3.3.8-clockradio/drivers/video/Kconfig	2014-09-17 15:18:49.953989291 +0200
@@ -2409,6 +2409,17 @@ config FB_PUV3_UNIGFX
 	  Choose this option if you want to use the Unigfx device as a
 	  framebuffer device. Without the support of PCI & AGP.
 
+config FB_OS288048
+	tristate "Osram Pictiva OS288048 OLED display"
+	depends on FB
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select FB_SYS_FOPS
+	select FB_DEFERRED_IO
+	help
+	  This is a framebuffer device for the Osram Pictiva OS288048 OLED display
+
 source "drivers/video/omap/Kconfig"
 source "drivers/video/omap2/Kconfig"
 
diff -BNburp linux-3.3.8/drivers/video/Makefile linux-3.3.8-clockradio/drivers/video/Makefile
--- linux-3.3.8/drivers/video/Makefile	2014-09-17 15:05:43.502016931 +0200
+++ linux-3.3.8-clockradio/drivers/video/Makefile	2014-05-03 15:43:49.000000000 +0200
@@ -122,6 +122,7 @@ obj-$(CONFIG_FB_S3C)		  += s3c-fb.o
 obj-$(CONFIG_FB_S3C2410)	  += s3c2410fb.o
 obj-$(CONFIG_FB_FSL_DIU)	  += fsl-diu-fb.o
 obj-$(CONFIG_FB_COBALT)           += cobalt_lcdfb.o
+obj-$(CONFIG_FB_OS288048)	  += os288048.o
 obj-$(CONFIG_FB_PNX4008_DUM)	  += pnx4008/
 obj-$(CONFIG_FB_PNX4008_DUM_RGB)  += pnx4008/
 obj-$(CONFIG_FB_IBM_GXT4500)	  += gxt4500.o
diff -BNburp linux-3.3.8/drivers/video/os288048.c linux-3.3.8-clockradio/drivers/video/os288048.c
--- linux-3.3.8/drivers/video/os288048.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-3.3.8-clockradio/drivers/video/os288048.c	2014-09-17 15:21:53.297982847 +0200
@@ -0,0 +1,638 @@
+/*
+ * OSS288048 Framebuffer
+ *
+ *
+ * Original: Copyright (c) 2009 Jean-Christian de Rivaz
+ *
+ * SPI mods:
+ * Copyright (c) 2012 Jeroen Domburg <jeroen@spritesmods.com>
+ *
+ * Bits and pieces borrowed from the fsl-ssd1289.c:
+ * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Author: Alison Wang <b18965@freescale.com>
+ *         Jason Jin <Jason.jin@freescale.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+//#define DEBUG
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/fb.h>
+#include <asm/io.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+
+struct oss288048_page {
+	unsigned short x;
+	unsigned short y;
+	unsigned short *buffer;
+	unsigned short *oldbuffer;
+	unsigned short len;
+	int must_update;
+};
+
+struct oss288048 {
+	struct device *dev;
+	struct spi_device *spidev;
+	struct fb_info *info;
+	unsigned int pages_count;
+	struct oss288048_page *pages;
+	unsigned long pseudo_palette[17];
+	int backlight;
+	int gpiodc;
+};
+
+
+#define SCREENW 288
+#define SCREENH 48
+
+#define BLOCKLEN (4096)
+static u8 oss288048_spiblock[BLOCKLEN*4];
+
+
+/* Set the DC pin to a certain state */
+static void os288048_setdc(struct oss288048 *item, int isData)
+{
+	gpio_set_value(item->gpiodc, isData);
+}
+
+//Convert color to grayscale value
+static int col2gray(unsigned int c) {
+	int red=((c>>11)<<3)&0xff;
+	int green=((c>>5)<<2)&0xff;
+	int blue=((c)<<3)&0xff;
+	return (19595 * red + 38470 * green + 7471 * blue) >> 16;
+}
+
+
+/*
+This routine will clock in len words of pixel data, converted to grayscale.
+*/
+static int oss288048_spi_write_datablock(struct oss288048 *item, 
+					unsigned short *block, int len) 
+{
+	int x, t;
+	unsigned short value;
+
+	//ToDo: send in parts if needed
+	if (len>BLOCKLEN) {
+		dev_err(item->dev, "%s: len > blocklen (%i > %i)\n",
+			__func__, len, BLOCKLEN);
+		len=BLOCKLEN;
+	}
+
+	t=0;
+	for (x=0; x<len; x+=3) {
+		int gray1=col2gray(block[x]);
+		int gray2=col2gray(block[x+1]);
+		int gray3=col2gray(block[x+2]);
+		value=((gray1>>3)<<11)|((gray2>>3)<<6)|(gray3>>3);
+
+		//ToDo: convert values to bytes
+		oss288048_spiblock[t++]=(value>>8)&0xff;
+		oss288048_spiblock[t++]=(value)&0xff;
+	}
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+
+	os288048_setdc(item, 1);
+	spi_write(item->spidev, oss288048_spiblock, t);
+	return 0;
+}
+
+static void oss288048_send(struct oss288048 *item, unsigned char *data, int len, int isData)
+{
+	os288048_setdc(item, isData);
+	spi_write(item->spidev, data, len);
+}
+
+
+static void oss288048_copy(struct oss288048 *item, unsigned int index)
+{
+	char cmdRow[3]={0x15, 0, 96-1};
+	char cmdCol[3]={0x75, 0, 48};
+	unsigned int ystart, yend;
+	int chstart, chend;
+	unsigned int x;
+	unsigned int y;
+	unsigned short *buffer, *oldbuffer;
+	unsigned int len;
+	x = item->pages[index].x;
+	y = item->pages[index].y;
+	len = item->pages[index].len;
+	ystart = y;
+	yend = y+(len/(SCREENW))+1;
+	dev_dbg(item->dev,
+		"%s: page[%u]: x=%3hu y=%3hu buffer=0x%p len=%3hu\n",
+		__func__, index, x, y, buffer, len);
+
+	//Move to start of line.
+	buffer=item->pages[index].buffer-x;
+	oldbuffer=item->pages[index].oldbuffer-x;
+	x=0;
+
+	//If we arrive here, we basically assume something is written at the lines
+	//starting with y and lasting the page.
+	for (y=ystart; y<yend; y++) {
+		//Find start and end of changed data
+		chstart=-1;
+		for (x=0; x<SCREENW; x++) {
+			if (buffer[x]!=oldbuffer[x]) {
+				oldbuffer[x]=buffer[x];
+				if (chstart==-1) chstart=x;
+				chend=x;
+			}
+		}
+		if (chstart!=-1) {
+			//Something changed this line! chstart and chend contain start and end x-coords.
+			//LCD wants data per three pixels. Round to that.
+			chstart-=(chstart%3);
+			chend+=2; //oss288048_spi_write_datablock will take care of this
+
+			//set ra, address of oled unit
+			cmdRow[1]=chstart/3;
+			cmdCol[1]=y+1; //oled display seems to start at 1
+			oss288048_send(item, cmdCol, 3, 0);
+			oss288048_send(item, cmdRow, 3, 0);
+
+			oss288048_spi_write_datablock(item, &buffer[chstart], chend-chstart);
+		}
+		buffer+=SCREENW;
+		oldbuffer+=SCREENW;
+	}
+
+}
+
+static void oss288048_update_all(struct oss288048 *item)
+{
+	unsigned short i;
+	struct fb_deferred_io *fbdefio = item->info->fbdefio;
+	for (i = 0; i < item->pages_count; i++) {
+		item->pages[i].must_update=1;
+	}
+	schedule_delayed_work(&item->info->deferred_work, fbdefio->delay);
+}
+
+static void oss288048_update(struct fb_info *info, struct list_head *pagelist)
+{
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	struct page *page;
+	int i;
+
+	//We can be called because of pagefaults (mmap'ed framebuffer, pages
+	//returned in *pagelist) or because of kernel activity 
+	//(pages[i]/must_update!=0). Add the former to the list of the latter.
+	list_for_each_entry(page, pagelist, lru) {
+		item->pages[page->index].must_update=1;
+	}
+
+	//Copy changed pages.
+	for (i=0; i<item->pages_count; i++) {
+		//ToDo: Small race here between checking and setting must_update, 
+		//maybe lock?
+		if (item->pages[i].must_update) {
+			item->pages[i].must_update=0;
+			oss288048_copy(item, i);
+		}
+	}
+
+}
+
+
+static void __init oss288048_setup(struct oss288048 *item)
+{
+	char initRows[2]={0xA8, 48};
+	char initCols[2]={0xA0, 0x72};
+	char initEn[1]={0xAF};
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+	oss288048_send(item, initRows, 2, 0); //probably not needed, but may clear the spi cruft out
+	oss288048_send(item, initRows, 2, 0);
+	oss288048_send(item, initCols, 2, 0);
+	oss288048_send(item, initEn, 1, 0);
+}
+
+//This routine will allocate the buffer for the complete framebuffer. This
+//is one continuous chunk of 16-bit pixel values; userspace programs
+//will write here.
+static int __init oss288048_video_alloc(struct oss288048 *item)
+{
+	unsigned int frame_size;
+
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+	frame_size = item->info->fix.line_length * item->info->var.yres;
+	dev_dbg(item->dev, "%s: item=0x%p frame_size=%u\n",
+		__func__, (void *)item, frame_size);
+
+	item->pages_count = frame_size / PAGE_SIZE;
+	if ((item->pages_count * PAGE_SIZE) < frame_size) {
+		item->pages_count++;
+	}
+	dev_dbg(item->dev, "%s: item=0x%p pages_count=%u\n",
+		__func__, (void *)item, item->pages_count);
+
+	item->info->fix.smem_len = item->pages_count * PAGE_SIZE;
+	item->info->fix.smem_start =
+	    (unsigned long)vmalloc(item->info->fix.smem_len);
+	if (!item->info->fix.smem_start) {
+		dev_err(item->dev, "%s: unable to vmalloc\n", __func__);
+		return -ENOMEM;
+	}
+	memset((void *)item->info->fix.smem_start, 0, item->info->fix.smem_len);
+
+	return 0;
+}
+
+static void oss288048_video_free(struct oss288048 *item)
+{
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+	kfree((void *)item->info->fix.smem_start);
+}
+
+//This routine will allocate a oss288048_page struct for each vm page in the
+//main framebuffer memory. Each struct will contain a pointer to the page
+//start, an x- and y-offset, and the length of the pagebuffer which is in the framebuffer.
+static int __init oss288048_pages_alloc(struct oss288048 *item)
+{
+	unsigned short pixels_per_page;
+	unsigned short yoffset_per_page;
+	unsigned short xoffset_per_page;
+	unsigned short index;
+	unsigned short x = 0;
+	unsigned short y = 0;
+	unsigned short *buffer;
+	unsigned short *oldbuffer;
+	unsigned int len;
+
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+	item->pages = kmalloc(item->pages_count * sizeof(struct oss288048_page),
+			      GFP_KERNEL);
+	if (!item->pages) {
+		dev_err(item->dev, "%s: unable to kmalloc for oss288048_page\n",
+			__func__);
+		return -ENOMEM;
+	}
+
+	pixels_per_page = PAGE_SIZE / (item->info->var.bits_per_pixel / 8);
+	yoffset_per_page = pixels_per_page / item->info->var.xres;
+	xoffset_per_page = pixels_per_page -
+	    (yoffset_per_page * item->info->var.xres);
+	dev_dbg(item->dev, "%s: item=0x%p pixels_per_page=%hu "
+		"yoffset_per_page=%hu xoffset_per_page=%hu\n",
+		__func__, (void *)item, pixels_per_page,
+		yoffset_per_page, xoffset_per_page);
+
+	oldbuffer = kmalloc(item->pages_count * pixels_per_page * 2,
+			      GFP_KERNEL);
+	if (!oldbuffer) {
+		dev_err(item->dev, "%s: unable to kmalloc for ili9325_page oldbuffer\n",
+			__func__);
+		return -ENOMEM;
+	}
+	buffer = (unsigned short *)item->info->fix.smem_start;
+	for (index = 0; index < item->pages_count; index++) {
+		len = (item->info->var.xres * item->info->var.yres) -
+		    (index * pixels_per_page);
+		if (len > pixels_per_page) {
+			len = pixels_per_page;
+		}
+		dev_dbg(item->dev,
+			"%s: page[%d]: x=%3hu y=%3hu buffer=0x%p len=%3hu\n",
+			__func__, index, x, y, buffer, len);
+		item->pages[index].x = x;
+		item->pages[index].y = y;
+		item->pages[index].buffer = buffer;
+		item->pages[index].oldbuffer = oldbuffer;
+		item->pages[index].len = len;
+
+		x += xoffset_per_page;
+		if (x >= item->info->var.xres) {
+			y++;
+			x -= item->info->var.xres;
+		}
+		y += yoffset_per_page;
+		buffer += pixels_per_page;
+		oldbuffer += pixels_per_page;
+	}
+
+	return 0;
+}
+
+static void oss288048_pages_free(struct oss288048 *item)
+{
+	dev_dbg(item->dev, "%s: item=0x%p\n", __func__, (void *)item);
+
+	kfree(item->pages);
+}
+
+static inline __u32 CNVT_TOHW(__u32 val, __u32 width)
+{
+	return ((val<<width) + 0x7FFF - val)>>16;
+}
+
+//This routine is needed because the console driver won't work without it.
+static int oss288048_setcolreg(unsigned regno,
+			       unsigned red, unsigned green, unsigned blue,
+			       unsigned transp, struct fb_info *info)
+{
+	int ret = 1;
+
+	/*
+	 * If greyscale is true, then we convert the RGB value
+	 * to greyscale no matter what visual we are using.
+	 */
+	if (info->var.grayscale)
+		red = green = blue = (19595 * red + 38470 * green +
+				      7471 * blue) >> 16;
+	switch (info->fix.visual) {
+	case FB_VISUAL_TRUECOLOR:
+		if (regno < 16) {
+			u32 *pal = info->pseudo_palette;
+			u32 value;
+
+			red = CNVT_TOHW(red, info->var.red.length);
+			green = CNVT_TOHW(green, info->var.green.length);
+			blue = CNVT_TOHW(blue, info->var.blue.length);
+			transp = CNVT_TOHW(transp, info->var.transp.length);
+
+			value = (red << info->var.red.offset) |
+				(green << info->var.green.offset) |
+				(blue << info->var.blue.offset) |
+				(transp << info->var.transp.offset);
+
+			pal[regno] = value;
+			ret = 0;
+		}
+		break;
+	case FB_VISUAL_STATIC_PSEUDOCOLOR:
+	case FB_VISUAL_PSEUDOCOLOR:
+		break;
+	}
+	return ret;
+}
+
+static int oss288048_blank(int blank_mode, struct fb_info *info)
+{
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	if (blank_mode == FB_BLANK_UNBLANK)
+		item->backlight=1;
+	else
+		item->backlight=0;
+	//Item->backlight won't take effect until the LCD is written to. Force that
+	//by dirty'ing a page.
+	item->pages[0].must_update=1;
+	schedule_delayed_work(&info->deferred_work, 0);
+	return 0;
+}
+
+static void oss288048_touch(struct fb_info *info, int x, int y, int w, int h) 
+{
+	struct fb_deferred_io *fbdefio = info->fbdefio;
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	int i, ystart, yend;
+	if (fbdefio) {
+		//Touch the pages the y-range hits, so the deferred io will update them.
+		for (i=0; i<item->pages_count; i++) {
+			ystart=item->pages[i].y;
+			yend=item->pages[i].y+(item->pages[i].len/info->fix.line_length)+1;
+			if (!((y+h)<ystart || y>yend)) {
+				item->pages[i].must_update=1;
+			}
+		}
+		//Schedule the deferred IO to kick in after a delay.
+		schedule_delayed_work(&info->deferred_work, fbdefio->delay);
+	}
+}
+
+static void oss288048_fillrect(struct fb_info *p, const struct fb_fillrect *rect) 
+{
+	sys_fillrect(p, rect);
+	oss288048_touch(p, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void oss288048_imageblit(struct fb_info *p, const struct fb_image *image) 
+{
+	sys_imageblit(p, image);
+	oss288048_touch(p, image->dx, image->dy, image->width, image->height);
+}
+
+static void oss288048_copyarea(struct fb_info *p, const struct fb_copyarea *area) 
+{
+	sys_copyarea(p, area);
+	oss288048_touch(p, area->dx, area->dy, area->width, area->height);
+}
+
+static ssize_t oss288048_write(struct fb_info *p, const char __user *buf, 
+				size_t count, loff_t *ppos) 
+{
+	ssize_t res;
+	res = fb_sys_write(p, buf, count, ppos);
+	oss288048_touch(p, 0, 0, p->var.xres, p->var.yres);
+	return res;
+}
+
+static struct fb_ops oss288048_fbops = {
+	.owner        = THIS_MODULE,
+	.fb_read      = fb_sys_read,
+	.fb_write     = oss288048_write,
+	.fb_fillrect  = oss288048_fillrect,
+	.fb_copyarea  = oss288048_copyarea,
+	.fb_imageblit = oss288048_imageblit,
+	.fb_setcolreg	= oss288048_setcolreg,
+	.fb_blank	= oss288048_blank,
+};
+
+static struct fb_fix_screeninfo oss288048_fix __initdata = {
+	.id          = "OSS288048",
+	.type        = FB_TYPE_PACKED_PIXELS,
+	.visual      = FB_VISUAL_TRUECOLOR,
+	.accel       = FB_ACCEL_NONE,
+	.line_length = SCREENW * 2,
+};
+
+static struct fb_var_screeninfo oss288048_var __initdata = {
+	.xres		= SCREENW,
+	.yres		= SCREENH,
+	.xres_virtual	= SCREENW,
+	.yres_virtual	= SCREENH,
+	.width		= SCREENW,
+	.height		= SCREENH,
+	.bits_per_pixel	= 16,
+	.red		= {11, 5, 0},
+	.green		= {5, 6, 0},
+	.blue		= {0, 5, 0},
+	.activate	= FB_ACTIVATE_NOW,
+	.vmode		= FB_VMODE_NONINTERLACED,
+};
+
+static struct fb_deferred_io oss288048_defio = {
+        .delay          = HZ / 50,
+        .deferred_io    = &oss288048_update,
+};
+
+static int __init oss288048_probe(struct spi_device *dev)
+{
+	int ret = 0;
+	struct oss288048 *item;
+	struct fb_info *info;
+
+	dev_dbg(&dev->dev, "%s\n", __func__);
+
+	item = kzalloc(sizeof(struct oss288048), GFP_KERNEL);
+	if (!item) {
+		dev_err(&dev->dev,
+			"%s: unable to kzalloc for oss288048\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+	item->dev = &dev->dev;
+	dev_set_drvdata(&dev->dev, item);
+	item->backlight=1;
+
+	item->spidev=dev;
+	item->dev=&dev->dev;
+	dev_set_drvdata(&dev->dev, item);
+	dev_info(&dev->dev, "spi registered, item=0x%p\n", (void *)item);
+
+	item->gpiodc=(int)dev->dev.platform_data;
+	gpio_request(item->gpiodc, "os288040 dc pin");
+	gpio_direction_output(item->gpiodc, 0);
+	dev_info(&dev->dev, "gpio %i registered\n", item->gpiodc);
+
+	info = framebuffer_alloc(sizeof(struct oss288048), &dev->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		dev_err(&dev->dev,
+			"%s: unable to framebuffer_alloc\n", __func__);
+		goto out_item;
+	}
+	info->pseudo_palette = &item->pseudo_palette;
+	item->info = info;
+	info->par = item;
+	info->dev = &dev->dev;
+	info->fbops = &oss288048_fbops;
+	info->flags = FBINFO_FLAG_DEFAULT|FBINFO_VIRTFB;
+	info->fix = oss288048_fix;
+	info->var = oss288048_var;
+
+	ret = oss288048_video_alloc(item);
+	if (ret) {
+		dev_err(&dev->dev,
+			"%s: unable to oss288048_video_alloc\n", __func__);
+		goto out_info;
+	}
+	info->screen_base = (char __iomem *)item->info->fix.smem_start;
+
+	ret = oss288048_pages_alloc(item);
+	if (ret < 0) {
+		dev_err(&dev->dev,
+			"%s: unable to oss288048_pages_init\n", __func__);
+		goto out_video;
+	}
+
+	info->fbdefio = &oss288048_defio;
+	fb_deferred_io_init(info);
+
+	ret = register_framebuffer(info);
+	if (ret < 0) {
+		dev_err(&dev->dev,
+			"%s: unable to register_frambuffer\n", __func__);
+		goto out_pages;
+	}
+
+
+	oss288048_setup(item);
+	oss288048_update_all(item);
+
+	return ret;
+
+out_pages:
+	oss288048_pages_free(item);
+out_video:
+	oss288048_video_free(item);
+out_info:
+	framebuffer_release(info);
+out_item:
+	kfree(item);
+out:
+	return ret;
+}
+
+
+static int oss288048_remove(struct spi_device *spi)
+{
+	struct fb_info *info = dev_get_drvdata(&spi->dev);
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	dev_set_drvdata(&spi->dev, NULL);
+	unregister_framebuffer(info);
+	oss288048_pages_free(item);
+	oss288048_video_free(item);
+	framebuffer_release(info);
+	kfree(item);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int oss288048_suspend(struct spi_device *spi, pm_message_t state)
+{
+	struct fb_info *info = dev_get_drvdata(&spi->dev);
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	/* enter into sleep mode */
+	oss288048_reg_set(item, OSS288048_REG_SLEEP_MODE, 0x0001);
+	return 0;
+}
+
+static int oss288048_resume(struct spi_device *spi)
+{
+	struct fb_info *info = dev_get_drvdata(&spi->dev);
+	struct oss288048 *item = (struct oss288048 *)info->par;
+	/* leave sleep mode */
+	oss288048_reg_set(item, OSS288048_REG_SLEEP_MODE, 0x0000);
+	return 0;
+}
+#else
+#define oss288048_suspend NULL
+#define oss288048_resume NULL
+#endif
+
+static struct spi_driver spi_oss288048_driver = {
+	.driver = {
+		.name	= "spi-os288048",
+		.bus	= &spi_bus_type,
+		.owner	= THIS_MODULE,
+	},
+	.probe = oss288048_probe,
+	.remove = oss288048_remove,
+	.suspend = oss288048_suspend,
+	.resume = oss288048_resume,
+};
+
+static int __init oss288048_init(void)
+{
+	int ret = 0;
+
+	pr_debug("%s\n", __func__);
+
+	ret = spi_register_driver(&spi_oss288048_driver);
+	if (ret) {
+		pr_err("%s: unable to platform_driver_register\n", __func__);
+	}
+
+	return ret;
+}
+
+module_init(oss288048_init);
+
+MODULE_DESCRIPTION("OSS288048 OLED Driver");
+MODULE_AUTHOR("Jeroen Domburg <jeroen@spritesmods.com>");
+MODULE_LICENSE("GPL");
