浏览代码

Merge tag 'staging-4.6-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging

Pull staging and IIO driver fixes from Greg KH:
 "Here are some IIO driver fixes, along with two staging driver fixes
  for 4.6-rc3.

  One staging driver patch reverts the deletion of a driver that
  happened in 4.6-rc1.  We thought that laptop.org was dead, but it's
  still alive and kicking, and has users that were mad we broke their
  hardware by deleting a driver for their machines.  So that driver is
  added back and everyone is happy again.

  All of these have been in linux-next for a while with no reported
  issues"

* tag 'staging-4.6-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging:
  Revert "Staging: olpc_dcon: Remove obsolete driver"
  staging/rdma/hfi1: select CRC32
  iio: gyro: bmg160: fix buffer read values
  iio: gyro: bmg160: fix endianness when reading axes
  iio: accel: bmc150: fix endianness when reading axes
  iio: st_magn: always define ST_MAGN_TRIGGER_SET_STATE
  iio: fix config watermark initial value
  iio: health: max30100: correct FIFO check condition
  iio: imu: Fix inv_mpu6050 dependencies
  iio: adc: Fix build error of missing devm_ioremap_resource on UM
  iio: light: apds9960: correct FIFO check condition
  iio: adc: max1363: correct reference voltage
  iio: adc: max1363: add missing adc to max1363_id
Linus Torvalds 9 年之前
父节点
当前提交
c6e6e58cc8

+ 8 - 0
MAINTAINERS

@@ -10595,6 +10595,14 @@ L:	linux-tegra@vger.kernel.org
 S:	Maintained
 F:	drivers/staging/nvec/
 
+STAGING - OLPC SECONDARY DISPLAY CONTROLLER (DCON)
+M:	Jens Frederich <jfrederich@gmail.com>
+M:	Daniel Drake <dsd@laptop.org>
+M:	Jon Nettleton <jon.nettleton@gmail.com>
+W:	http://wiki.laptop.org/go/DCON
+S:	Maintained
+F:	drivers/staging/olpc_dcon/
+
 STAGING - REALTEK RTL8712U DRIVERS
 M:	Larry Finger <Larry.Finger@lwfinger.net>
 M:	Florian Schilhabel <florian.c.schilhabel@googlemail.com>.

+ 4 - 3
drivers/iio/accel/bmc150-accel-core.c

@@ -547,7 +547,7 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data,
 {
 	int ret;
 	int axis = chan->scan_index;
-	unsigned int raw_val;
+	__le16 raw_val;
 
 	mutex_lock(&data->mutex);
 	ret = bmc150_accel_set_power_state(data, true);
@@ -557,14 +557,14 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data,
 	}
 
 	ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_AXIS_TO_REG(axis),
-			       &raw_val, 2);
+			       &raw_val, sizeof(raw_val));
 	if (ret < 0) {
 		dev_err(data->dev, "Error reading axis %d\n", axis);
 		bmc150_accel_set_power_state(data, false);
 		mutex_unlock(&data->mutex);
 		return ret;
 	}
-	*val = sign_extend32(raw_val >> chan->scan_type.shift,
+	*val = sign_extend32(le16_to_cpu(raw_val) >> chan->scan_type.shift,
 			     chan->scan_type.realbits - 1);
 	ret = bmc150_accel_set_power_state(data, false);
 	mutex_unlock(&data->mutex);
@@ -988,6 +988,7 @@ static const struct iio_event_spec bmc150_accel_event = {
 		.realbits = (bits),					\
 		.storagebits = 16,					\
 		.shift = 16 - (bits),					\
+		.endianness = IIO_LE,					\
 	},								\
 	.event_spec = &bmc150_accel_event,				\
 	.num_event_specs = 1						\

+ 1 - 0
drivers/iio/adc/Kconfig

@@ -134,6 +134,7 @@ config AT91_ADC
 config AT91_SAMA5D2_ADC
 	tristate "Atmel AT91 SAMA5D2 ADC"
 	depends on ARCH_AT91 || COMPILE_TEST
+	depends on HAS_IOMEM
 	help
 	  Say yes here to build support for Atmel SAMA5D2 ADC which is
 	  available on SAMA5D2 SoC family.

+ 8 - 4
drivers/iio/adc/max1363.c

@@ -1386,7 +1386,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
 	},
 	[max11644] = {
 		.bits = 12,
-		.int_vref_mv = 2048,
+		.int_vref_mv = 4096,
 		.mode_list = max11644_mode_list,
 		.num_modes = ARRAY_SIZE(max11644_mode_list),
 		.default_mode = s0to1,
@@ -1396,7 +1396,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
 	},
 	[max11645] = {
 		.bits = 12,
-		.int_vref_mv = 4096,
+		.int_vref_mv = 2048,
 		.mode_list = max11644_mode_list,
 		.num_modes = ARRAY_SIZE(max11644_mode_list),
 		.default_mode = s0to1,
@@ -1406,7 +1406,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
 	},
 	[max11646] = {
 		.bits = 10,
-		.int_vref_mv = 2048,
+		.int_vref_mv = 4096,
 		.mode_list = max11644_mode_list,
 		.num_modes = ARRAY_SIZE(max11644_mode_list),
 		.default_mode = s0to1,
@@ -1416,7 +1416,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
 	},
 	[max11647] = {
 		.bits = 10,
-		.int_vref_mv = 4096,
+		.int_vref_mv = 2048,
 		.mode_list = max11644_mode_list,
 		.num_modes = ARRAY_SIZE(max11644_mode_list),
 		.default_mode = s0to1,
@@ -1680,6 +1680,10 @@ static const struct i2c_device_id max1363_id[] = {
 	{ "max11615", max11615 },
 	{ "max11616", max11616 },
 	{ "max11617", max11617 },
+	{ "max11644", max11644 },
+	{ "max11645", max11645 },
+	{ "max11646", max11646 },
+	{ "max11647", max11647 },
 	{}
 };
 

+ 5 - 4
drivers/iio/gyro/bmg160_core.c

@@ -452,7 +452,7 @@ static int bmg160_get_temp(struct bmg160_data *data, int *val)
 static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
 {
 	int ret;
-	unsigned int raw_val;
+	__le16 raw_val;
 
 	mutex_lock(&data->mutex);
 	ret = bmg160_set_power_state(data, true);
@@ -462,7 +462,7 @@ static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
 	}
 
 	ret = regmap_bulk_read(data->regmap, BMG160_AXIS_TO_REG(axis), &raw_val,
-			       2);
+			       sizeof(raw_val));
 	if (ret < 0) {
 		dev_err(data->dev, "Error reading axis %d\n", axis);
 		bmg160_set_power_state(data, false);
@@ -470,7 +470,7 @@ static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
 		return ret;
 	}
 
-	*val = sign_extend32(raw_val, 15);
+	*val = sign_extend32(le16_to_cpu(raw_val), 15);
 	ret = bmg160_set_power_state(data, false);
 	mutex_unlock(&data->mutex);
 	if (ret < 0)
@@ -733,6 +733,7 @@ static const struct iio_event_spec bmg160_event = {
 		.sign = 's',						\
 		.realbits = 16,					\
 		.storagebits = 16,					\
+		.endianness = IIO_LE,					\
 	},								\
 	.event_spec = &bmg160_event,					\
 	.num_event_specs = 1						\
@@ -780,7 +781,7 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p)
 			mutex_unlock(&data->mutex);
 			goto err;
 		}
-		data->buffer[i++] = ret;
+		data->buffer[i++] = val;
 	}
 	mutex_unlock(&data->mutex);
 

+ 2 - 1
drivers/iio/health/max30100.c

@@ -238,12 +238,13 @@ static irqreturn_t max30100_interrupt_handler(int irq, void *private)
 
 	mutex_lock(&data->lock);
 
-	while (cnt-- || (cnt = max30100_fifo_count(data) > 0)) {
+	while (cnt || (cnt = max30100_fifo_count(data) > 0)) {
 		ret = max30100_read_measurement(data);
 		if (ret)
 			break;
 
 		iio_push_to_buffers(data->indio_dev, data->buffer);
+		cnt--;
 	}
 
 	mutex_unlock(&data->lock);

+ 1 - 2
drivers/iio/imu/inv_mpu6050/Kconfig

@@ -9,9 +9,8 @@ config INV_MPU6050_IIO
 
 config INV_MPU6050_I2C
 	tristate "Invensense MPU6050 devices (I2C)"
-	depends on I2C
+	depends on I2C_MUX
 	select INV_MPU6050_IIO
-	select I2C_MUX
 	select REGMAP_I2C
 	help
 	  This driver supports the Invensense MPU6050 devices.

+ 1 - 0
drivers/iio/industrialio-buffer.c

@@ -653,6 +653,7 @@ static int iio_verify_update(struct iio_dev *indio_dev,
 	unsigned int modes;
 
 	memset(config, 0, sizeof(*config));
+	config->watermark = ~0;
 
 	/*
 	 * If there is just one buffer and we are removing it there is nothing

+ 2 - 1
drivers/iio/light/apds9960.c

@@ -769,7 +769,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data)
 	mutex_lock(&data->lock);
 	data->gesture_mode_running = 1;
 
-	while (cnt-- || (cnt = apds9660_fifo_is_empty(data) > 0)) {
+	while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) {
 		ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE,
 				      &data->buffer, 4);
 
@@ -777,6 +777,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data)
 			goto err_read;
 
 		iio_push_to_buffers(data->indio_dev, data->buffer);
+		cnt--;
 	}
 
 err_read:

+ 1 - 0
drivers/iio/magnetometer/st_magn.h

@@ -44,6 +44,7 @@ static inline int st_magn_allocate_ring(struct iio_dev *indio_dev)
 static inline void st_magn_deallocate_ring(struct iio_dev *indio_dev)
 {
 }
+#define ST_MAGN_TRIGGER_SET_STATE NULL
 #endif /* CONFIG_IIO_BUFFER */
 
 #endif /* ST_MAGN_H */

+ 2 - 0
drivers/staging/Kconfig

@@ -30,6 +30,8 @@ source "drivers/staging/wlan-ng/Kconfig"
 
 source "drivers/staging/comedi/Kconfig"
 
+source "drivers/staging/olpc_dcon/Kconfig"
+
 source "drivers/staging/rtl8192u/Kconfig"
 
 source "drivers/staging/rtl8192e/Kconfig"

+ 1 - 0
drivers/staging/Makefile

@@ -4,6 +4,7 @@ obj-y				+= media/
 obj-$(CONFIG_SLICOSS)		+= slicoss/
 obj-$(CONFIG_PRISM2_USB)	+= wlan-ng/
 obj-$(CONFIG_COMEDI)		+= comedi/
+obj-$(CONFIG_FB_OLPC_DCON)	+= olpc_dcon/
 obj-$(CONFIG_RTL8192U)		+= rtl8192u/
 obj-$(CONFIG_RTL8192E)		+= rtl8192e/
 obj-$(CONFIG_R8712U)		+= rtl8712/

+ 35 - 0
drivers/staging/olpc_dcon/Kconfig

@@ -0,0 +1,35 @@
+config FB_OLPC_DCON
+	tristate "One Laptop Per Child Display CONtroller support"
+	depends on OLPC && FB
+	depends on I2C
+	depends on (GPIO_CS5535 || GPIO_CS5535=n)
+	select BACKLIGHT_CLASS_DEVICE
+	---help---
+	  In order to support very low power operation, the XO laptop uses a
+	  secondary Display CONtroller, or DCON.  This secondary controller
+	  is present in the video pipeline between the primary display
+	  controller (integrate into the processor or chipset) and the LCD
+	  panel.  It allows the main processor/display controller to be
+	  completely powered off while still retaining an image on the display.
+	  This controller is only available on OLPC platforms.  Unless you have
+	  one of these platforms, you will want to say 'N'.
+
+config FB_OLPC_DCON_1
+	bool "OLPC XO-1 DCON support"
+	depends on FB_OLPC_DCON && GPIO_CS5535
+	default y
+	---help---
+	  Enable support for the DCON in XO-1 model laptops.  The kernel
+	  communicates with the DCON using model-specific code.  If you
+	  have an XO-1 (or if you're unsure what model you have), you should
+	  say 'Y'.
+
+config FB_OLPC_DCON_1_5
+	bool "OLPC XO-1.5 DCON support"
+	depends on FB_OLPC_DCON && ACPI
+	default y
+	---help---
+	  Enable support for the DCON in XO-1.5 model laptops.  The kernel
+	  communicates with the DCON using model-specific code.  If you
+	  have an XO-1.5 (or if you're unsure what model you have), you
+	  should say 'Y'.

+ 6 - 0
drivers/staging/olpc_dcon/Makefile

@@ -0,0 +1,6 @@
+olpc-dcon-objs += olpc_dcon.o
+olpc-dcon-$(CONFIG_FB_OLPC_DCON_1)	+= olpc_dcon_xo_1.o
+olpc-dcon-$(CONFIG_FB_OLPC_DCON_1_5)	+= olpc_dcon_xo_1_5.o
+obj-$(CONFIG_FB_OLPC_DCON)	+= olpc-dcon.o
+
+

+ 9 - 0
drivers/staging/olpc_dcon/TODO

@@ -0,0 +1,9 @@
+TODO:
+	- see if vx855 gpio API can be made similar enough to cs5535 so we can
+	  share more code
+	- allow simultaneous XO-1 and XO-1.5 support
+
+Please send patches to Greg Kroah-Hartman <greg@kroah.com> and
+copy:
+	Daniel Drake <dsd@laptop.org>
+	Jens Frederich <jfrederich@gmail.com>

+ 813 - 0
drivers/staging/olpc_dcon/olpc_dcon.c

@@ -0,0 +1,813 @@
+/*
+ * Mainly by David Woodhouse, somewhat modified by Jordan Crouse
+ *
+ * Copyright © 2006-2007  Red Hat, Inc.
+ * Copyright © 2006-2007  Advanced Micro Devices, Inc.
+ * Copyright © 2009       VIA Technology, Inc.
+ * Copyright (c) 2010-2011  Andres Salomon <dilinger@queued.net>
+ *
+ * This program is free software.  You can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/fb.h>
+#include <linux/console.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/backlight.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/reboot.h>
+#include <linux/olpc-ec.h>
+#include <asm/tsc.h>
+#include <asm/olpc.h>
+
+#include "olpc_dcon.h"
+
+/* Module definitions */
+
+static ushort resumeline = 898;
+module_param(resumeline, ushort, 0444);
+
+static struct dcon_platform_data *pdata;
+
+/* I2C structures */
+
+/* Platform devices */
+static struct platform_device *dcon_device;
+
+static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END };
+
+static s32 dcon_write(struct dcon_priv *dcon, u8 reg, u16 val)
+{
+	return i2c_smbus_write_word_data(dcon->client, reg, val);
+}
+
+static s32 dcon_read(struct dcon_priv *dcon, u8 reg)
+{
+	return i2c_smbus_read_word_data(dcon->client, reg);
+}
+
+/* ===== API functions - these are called by a variety of users ==== */
+
+static int dcon_hw_init(struct dcon_priv *dcon, int is_init)
+{
+	u16 ver;
+	int rc = 0;
+
+	ver = dcon_read(dcon, DCON_REG_ID);
+	if ((ver >> 8) != 0xDC) {
+		pr_err("DCON ID not 0xDCxx: 0x%04x instead.\n", ver);
+		rc = -ENXIO;
+		goto err;
+	}
+
+	if (is_init) {
+		pr_info("Discovered DCON version %x\n", ver & 0xFF);
+		rc = pdata->init(dcon);
+		if (rc != 0) {
+			pr_err("Unable to init.\n");
+			goto err;
+		}
+	}
+
+	if (ver < 0xdc02) {
+		dev_err(&dcon->client->dev,
+				"DCON v1 is unsupported, giving up..\n");
+		rc = -ENODEV;
+		goto err;
+	}
+
+	/* SDRAM setup/hold time */
+	dcon_write(dcon, 0x3a, 0xc040);
+	dcon_write(dcon, DCON_REG_MEM_OPT_A, 0x0000);  /* clear option bits */
+	dcon_write(dcon, DCON_REG_MEM_OPT_A,
+				MEM_DLL_CLOCK_DELAY | MEM_POWER_DOWN);
+	dcon_write(dcon, DCON_REG_MEM_OPT_B, MEM_SOFT_RESET);
+
+	/* Colour swizzle, AA, no passthrough, backlight */
+	if (is_init) {
+		dcon->disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE |
+				MODE_CSWIZZLE | MODE_COL_AA;
+	}
+	dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
+
+	/* Set the scanline to interrupt on during resume */
+	dcon_write(dcon, DCON_REG_SCAN_INT, resumeline);
+
+err:
+	return rc;
+}
+
+/*
+ * The smbus doesn't always come back due to what is believed to be
+ * hardware (power rail) bugs.  For older models where this is known to
+ * occur, our solution is to attempt to wait for the bus to stabilize;
+ * if it doesn't happen, cut power to the dcon, repower it, and wait
+ * for the bus to stabilize.  Rinse, repeat until we have a working
+ * smbus.  For newer models, we simply BUG(); we want to know if this
+ * still happens despite the power fixes that have been made!
+ */
+static int dcon_bus_stabilize(struct dcon_priv *dcon, int is_powered_down)
+{
+	unsigned long timeout;
+	u8 pm;
+	int x;
+
+power_up:
+	if (is_powered_down) {
+		pm = 1;
+		x = olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
+		if (x) {
+			pr_warn("unable to force dcon to power up: %d!\n", x);
+			return x;
+		}
+		usleep_range(10000, 11000);  /* we'll be conservative */
+	}
+
+	pdata->bus_stabilize_wiggle();
+
+	for (x = -1, timeout = 50; timeout && x < 0; timeout--) {
+		usleep_range(1000, 1100);
+		x = dcon_read(dcon, DCON_REG_ID);
+	}
+	if (x < 0) {
+		pr_err("unable to stabilize dcon's smbus, reasserting power and praying.\n");
+		BUG_ON(olpc_board_at_least(olpc_board(0xc2)));
+		pm = 0;
+		olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
+		msleep(100);
+		is_powered_down = 1;
+		goto power_up;	/* argh, stupid hardware.. */
+	}
+
+	if (is_powered_down)
+		return dcon_hw_init(dcon, 0);
+	return 0;
+}
+
+static void dcon_set_backlight(struct dcon_priv *dcon, u8 level)
+{
+	dcon->bl_val = level;
+	dcon_write(dcon, DCON_REG_BRIGHT, dcon->bl_val);
+
+	/* Purposely turn off the backlight when we go to level 0 */
+	if (dcon->bl_val == 0) {
+		dcon->disp_mode &= ~MODE_BL_ENABLE;
+		dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
+	} else if (!(dcon->disp_mode & MODE_BL_ENABLE)) {
+		dcon->disp_mode |= MODE_BL_ENABLE;
+		dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
+	}
+}
+
+/* Set the output type to either color or mono */
+static int dcon_set_mono_mode(struct dcon_priv *dcon, bool enable_mono)
+{
+	if (dcon->mono == enable_mono)
+		return 0;
+
+	dcon->mono = enable_mono;
+
+	if (enable_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;
+	}
+
+	dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
+	return 0;
+}
+
+/* For now, this will be really stupid - we need to address how
+ * DCONLOAD works in a sleep and account for it accordingly
+ */
+
+static void dcon_sleep(struct dcon_priv *dcon, bool sleep)
+{
+	int x;
+
+	/* Turn off the backlight and put the DCON to sleep */
+
+	if (dcon->asleep == sleep)
+		return;
+
+	if (!olpc_board_at_least(olpc_board(0xc2)))
+		return;
+
+	if (sleep) {
+		u8 pm = 0;
+
+		x = olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
+		if (x)
+			pr_warn("unable to force dcon to power down: %d!\n", x);
+		else
+			dcon->asleep = sleep;
+	} else {
+		/* Only re-enable the backlight if the backlight value is set */
+		if (dcon->bl_val != 0)
+			dcon->disp_mode |= MODE_BL_ENABLE;
+		x = dcon_bus_stabilize(dcon, 1);
+		if (x)
+			pr_warn("unable to reinit dcon hardware: %d!\n", x);
+		else
+			dcon->asleep = sleep;
+
+		/* Restore backlight */
+		dcon_set_backlight(dcon, dcon->bl_val);
+	}
+
+	/* We should turn off some stuff in the framebuffer - but what? */
+}
+
+/* the DCON seems to get confused if we change DCONLOAD too
+ * frequently -- i.e., approximately faster than frame time.
+ * normally we don't change it this fast, so in general we won't
+ * delay here.
+ */
+static void dcon_load_holdoff(struct dcon_priv *dcon)
+{
+	ktime_t delta_t, now;
+
+	while (1) {
+		now = ktime_get();
+		delta_t = ktime_sub(now, dcon->load_time);
+		if (ktime_to_ns(delta_t) > NSEC_PER_MSEC * 20)
+			break;
+		mdelay(4);
+	}
+}
+
+static bool dcon_blank_fb(struct dcon_priv *dcon, bool blank)
+{
+	int err;
+
+	console_lock();
+	if (!lock_fb_info(dcon->fbinfo)) {
+		console_unlock();
+		dev_err(&dcon->client->dev, "unable to lock framebuffer\n");
+		return false;
+	}
+
+	dcon->ignore_fb_events = true;
+	err = fb_blank(dcon->fbinfo,
+			blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+	dcon->ignore_fb_events = false;
+	unlock_fb_info(dcon->fbinfo);
+	console_unlock();
+
+	if (err) {
+		dev_err(&dcon->client->dev, "couldn't %sblank framebuffer\n",
+				blank ? "" : "un");
+		return false;
+	}
+	return true;
+}
+
+/* Set the source of the display (CPU or DCON) */
+static void dcon_source_switch(struct work_struct *work)
+{
+	struct dcon_priv *dcon = container_of(work, struct dcon_priv,
+			switch_source);
+	int source = dcon->pending_src;
+
+	if (dcon->curr_src == source)
+		return;
+
+	dcon_load_holdoff(dcon);
+
+	dcon->switched = false;
+
+	switch (source) {
+	case DCON_SOURCE_CPU:
+		pr_info("dcon_source_switch to CPU\n");
+		/* Enable the scanline interrupt bit */
+		if (dcon_write(dcon, DCON_REG_MODE,
+				dcon->disp_mode | MODE_SCAN_INT))
+			pr_err("couldn't enable scanline interrupt!\n");
+		else
+			/* Wait up to one second for the scanline interrupt */
+			wait_event_timeout(dcon->waitq, dcon->switched, HZ);
+
+		if (!dcon->switched)
+			pr_err("Timeout entering CPU mode; expect a screen glitch.\n");
+
+		/* Turn off the scanline interrupt */
+		if (dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode))
+			pr_err("couldn't disable scanline interrupt!\n");
+
+		/*
+		 * Ideally we'd like to disable interrupts here so that the
+		 * fb unblanking and DCON turn on happen at a known time value;
+		 * however, we can't do that right now with fb_blank
+		 * messing with semaphores.
+		 *
+		 * For now, we just hope..
+		 */
+		if (!dcon_blank_fb(dcon, false)) {
+			pr_err("Failed to enter CPU mode\n");
+			dcon->pending_src = DCON_SOURCE_DCON;
+			return;
+		}
+
+		/* And turn off the DCON */
+		pdata->set_dconload(1);
+		dcon->load_time = ktime_get();
+
+		pr_info("The CPU has control\n");
+		break;
+	case DCON_SOURCE_DCON:
+	{
+		ktime_t delta_t;
+
+		pr_info("dcon_source_switch to DCON\n");
+
+		/* Clear DCONLOAD - this implies that the DCON is in control */
+		pdata->set_dconload(0);
+		dcon->load_time = ktime_get();
+
+		wait_event_timeout(dcon->waitq, dcon->switched, HZ/2);
+
+		if (!dcon->switched) {
+			pr_err("Timeout entering DCON mode; expect a screen glitch.\n");
+		} else {
+			/* sometimes the DCON doesn't follow its own rules,
+			 * and doesn't wait for two vsync pulses before
+			 * ack'ing the frame load with an IRQ.  the result
+			 * is that the display shows the *previously*
+			 * loaded frame.  we can detect this by looking at
+			 * the time between asserting DCONLOAD and the IRQ --
+			 * if it's less than 20msec, then the DCON couldn't
+			 * have seen two VSYNC pulses.  in that case we
+			 * deassert and reassert, and hope for the best.
+			 * see http://dev.laptop.org/ticket/9664
+			 */
+			delta_t = ktime_sub(dcon->irq_time, dcon->load_time);
+			if (dcon->switched && ktime_to_ns(delta_t)
+			    < NSEC_PER_MSEC * 20) {
+				pr_err("missed loading, retrying\n");
+				pdata->set_dconload(1);
+				mdelay(41);
+				pdata->set_dconload(0);
+				dcon->load_time = ktime_get();
+				mdelay(41);
+			}
+		}
+
+		dcon_blank_fb(dcon, true);
+		pr_info("The DCON has control\n");
+		break;
+	}
+	default:
+		BUG();
+	}
+
+	dcon->curr_src = source;
+}
+
+static void dcon_set_source(struct dcon_priv *dcon, int arg)
+{
+	if (dcon->pending_src == arg)
+		return;
+
+	dcon->pending_src = arg;
+
+	if (dcon->curr_src != arg)
+		schedule_work(&dcon->switch_source);
+}
+
+static void dcon_set_source_sync(struct dcon_priv *dcon, int arg)
+{
+	dcon_set_source(dcon, arg);
+	flush_scheduled_work();
+}
+
+static ssize_t dcon_mode_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct dcon_priv *dcon = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%4.4X\n", dcon->disp_mode);
+}
+
+static ssize_t dcon_sleep_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct dcon_priv *dcon = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", dcon->asleep);
+}
+
+static ssize_t dcon_freeze_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct dcon_priv *dcon = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", dcon->curr_src == DCON_SOURCE_DCON ? 1 : 0);
+}
+
+static ssize_t dcon_mono_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct dcon_priv *dcon = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", dcon->mono);
+}
+
+static ssize_t dcon_resumeline_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", resumeline);
+}
+
+static ssize_t dcon_mono_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long enable_mono;
+	int rc;
+
+	rc = kstrtoul(buf, 10, &enable_mono);
+	if (rc)
+		return rc;
+
+	dcon_set_mono_mode(dev_get_drvdata(dev), enable_mono ? true : false);
+
+	return count;
+}
+
+static ssize_t dcon_freeze_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct dcon_priv *dcon = dev_get_drvdata(dev);
+	unsigned long output;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &output);
+	if (ret)
+		return ret;
+
+	pr_info("dcon_freeze_store: %lu\n", output);
+
+	switch (output) {
+	case 0:
+		dcon_set_source(dcon, DCON_SOURCE_CPU);
+		break;
+	case 1:
+		dcon_set_source_sync(dcon, DCON_SOURCE_DCON);
+		break;
+	case 2:  /* normally unused */
+		dcon_set_source(dcon, DCON_SOURCE_DCON);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static ssize_t dcon_resumeline_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned short rl;
+	int rc;
+
+	rc = kstrtou16(buf, 10, &rl);
+	if (rc)
+		return rc;
+
+	resumeline = rl;
+	dcon_write(dev_get_drvdata(dev), DCON_REG_SCAN_INT, resumeline);
+
+	return count;
+}
+
+static ssize_t dcon_sleep_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long output;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &output);
+	if (ret)
+		return ret;
+
+	dcon_sleep(dev_get_drvdata(dev), output ? true : false);
+	return count;
+}
+
+static struct device_attribute dcon_device_files[] = {
+	__ATTR(mode, 0444, dcon_mode_show, NULL),
+	__ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store),
+	__ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store),
+	__ATTR(monochrome, 0644, dcon_mono_show, dcon_mono_store),
+	__ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store),
+};
+
+static int dcon_bl_update(struct backlight_device *dev)
+{
+	struct dcon_priv *dcon = bl_get_data(dev);
+	u8 level = dev->props.brightness & 0x0F;
+
+	if (dev->props.power != FB_BLANK_UNBLANK)
+		level = 0;
+
+	if (level != dcon->bl_val)
+		dcon_set_backlight(dcon, level);
+
+	/* power down the DCON when the screen is blanked */
+	if (!dcon->ignore_fb_events)
+		dcon_sleep(dcon, !!(dev->props.state & BL_CORE_FBBLANK));
+
+	return 0;
+}
+
+static int dcon_bl_get(struct backlight_device *dev)
+{
+	struct dcon_priv *dcon = bl_get_data(dev);
+
+	return dcon->bl_val;
+}
+
+static const struct backlight_ops dcon_bl_ops = {
+	.update_status = dcon_bl_update,
+	.get_brightness = dcon_bl_get,
+};
+
+static struct backlight_properties dcon_bl_props = {
+	.max_brightness = 15,
+	.type = BACKLIGHT_RAW,
+	.power = FB_BLANK_UNBLANK,
+};
+
+static int dcon_reboot_notify(struct notifier_block *nb,
+			      unsigned long foo, void *bar)
+{
+	struct dcon_priv *dcon = container_of(nb, struct dcon_priv, reboot_nb);
+
+	if (!dcon || !dcon->client)
+		return NOTIFY_DONE;
+
+	/* Turn off the DCON. Entirely. */
+	dcon_write(dcon, DCON_REG_MODE, 0x39);
+	dcon_write(dcon, DCON_REG_MODE, 0x32);
+	return NOTIFY_DONE;
+}
+
+static int unfreeze_on_panic(struct notifier_block *nb,
+			     unsigned long e, void *p)
+{
+	pdata->set_dconload(1);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block dcon_panic_nb = {
+	.notifier_call = unfreeze_on_panic,
+};
+
+static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+	strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct dcon_priv *dcon;
+	int rc, i, j;
+
+	if (!pdata)
+		return -ENXIO;
+
+	dcon = kzalloc(sizeof(*dcon), GFP_KERNEL);
+	if (!dcon)
+		return -ENOMEM;
+
+	dcon->client = client;
+	init_waitqueue_head(&dcon->waitq);
+	INIT_WORK(&dcon->switch_source, dcon_source_switch);
+	dcon->reboot_nb.notifier_call = dcon_reboot_notify;
+	dcon->reboot_nb.priority = -1;
+
+	i2c_set_clientdata(client, dcon);
+
+	if (num_registered_fb < 1) {
+		dev_err(&client->dev, "DCON driver requires a registered fb\n");
+		rc = -EIO;
+		goto einit;
+	}
+	dcon->fbinfo = registered_fb[0];
+
+	rc = dcon_hw_init(dcon, 1);
+	if (rc)
+		goto einit;
+
+	/* Add the DCON device */
+
+	dcon_device = platform_device_alloc("dcon", -1);
+
+	if (!dcon_device) {
+		pr_err("Unable to create the DCON device\n");
+		rc = -ENOMEM;
+		goto eirq;
+	}
+	rc = platform_device_add(dcon_device);
+	platform_set_drvdata(dcon_device, dcon);
+
+	if (rc) {
+		pr_err("Unable to add the DCON device\n");
+		goto edev;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dcon_device_files); i++) {
+		rc = device_create_file(&dcon_device->dev,
+					&dcon_device_files[i]);
+		if (rc) {
+			dev_err(&dcon_device->dev, "Cannot create sysfs file\n");
+			goto ecreate;
+		}
+	}
+
+	dcon->bl_val = dcon_read(dcon, DCON_REG_BRIGHT) & 0x0F;
+
+	/* Add the backlight device for the DCON */
+	dcon_bl_props.brightness = dcon->bl_val;
+	dcon->bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev,
+		dcon, &dcon_bl_ops, &dcon_bl_props);
+	if (IS_ERR(dcon->bl_dev)) {
+		dev_err(&client->dev, "cannot register backlight dev (%ld)\n",
+				PTR_ERR(dcon->bl_dev));
+		dcon->bl_dev = NULL;
+	}
+
+	register_reboot_notifier(&dcon->reboot_nb);
+	atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb);
+
+	return 0;
+
+ ecreate:
+	for (j = 0; j < i; j++)
+		device_remove_file(&dcon_device->dev, &dcon_device_files[j]);
+ edev:
+	platform_device_unregister(dcon_device);
+	dcon_device = NULL;
+ eirq:
+	free_irq(DCON_IRQ, dcon);
+ einit:
+	kfree(dcon);
+	return rc;
+}
+
+static int dcon_remove(struct i2c_client *client)
+{
+	struct dcon_priv *dcon = i2c_get_clientdata(client);
+
+	unregister_reboot_notifier(&dcon->reboot_nb);
+	atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb);
+
+	free_irq(DCON_IRQ, dcon);
+
+	backlight_device_unregister(dcon->bl_dev);
+
+	if (dcon_device)
+		platform_device_unregister(dcon_device);
+	cancel_work_sync(&dcon->switch_source);
+
+	kfree(dcon);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int dcon_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct dcon_priv *dcon = i2c_get_clientdata(client);
+
+	if (!dcon->asleep) {
+		/* Set up the DCON to have the source */
+		dcon_set_source_sync(dcon, DCON_SOURCE_DCON);
+	}
+
+	return 0;
+}
+
+static int dcon_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct dcon_priv *dcon = i2c_get_clientdata(client);
+
+	if (!dcon->asleep) {
+		dcon_bus_stabilize(dcon, 0);
+		dcon_set_source(dcon, DCON_SOURCE_CPU);
+	}
+
+	return 0;
+}
+
+#else
+
+#define dcon_suspend NULL
+#define dcon_resume NULL
+
+#endif /* CONFIG_PM */
+
+irqreturn_t dcon_interrupt(int irq, void *id)
+{
+	struct dcon_priv *dcon = id;
+	u8 status;
+
+	if (pdata->read_status(&status))
+		return IRQ_NONE;
+
+	switch (status & 3) {
+	case 3:
+		pr_debug("DCONLOAD_MISSED interrupt\n");
+		break;
+
+	case 2:	/* switch to DCON mode */
+	case 1: /* switch to CPU mode */
+		dcon->switched = true;
+		dcon->irq_time = ktime_get();
+		wake_up(&dcon->waitq);
+		break;
+
+	case 0:
+		/* workaround resume case:  the DCON (on 1.5) doesn't
+		 * ever assert status 0x01 when switching to CPU mode
+		 * during resume.  this is because DCONLOAD is de-asserted
+		 * _immediately_ upon exiting S3, so the actual release
+		 * of the DCON happened long before this point.
+		 * see http://dev.laptop.org/ticket/9869
+		 */
+		if (dcon->curr_src != dcon->pending_src && !dcon->switched) {
+			dcon->switched = true;
+			dcon->irq_time = ktime_get();
+			wake_up(&dcon->waitq);
+			pr_debug("switching w/ status 0/0\n");
+		} else {
+			pr_debug("scanline interrupt w/CPU\n");
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static const struct dev_pm_ops dcon_pm_ops = {
+	.suspend = dcon_suspend,
+	.resume = dcon_resume,
+};
+
+static const struct i2c_device_id dcon_idtable[] = {
+	{ "olpc_dcon",  0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, dcon_idtable);
+
+static struct i2c_driver dcon_driver = {
+	.driver = {
+		.name	= "olpc_dcon",
+		.pm = &dcon_pm_ops,
+	},
+	.class = I2C_CLASS_DDC | I2C_CLASS_HWMON,
+	.id_table = dcon_idtable,
+	.probe = dcon_probe,
+	.remove = dcon_remove,
+	.detect = dcon_detect,
+	.address_list = normal_i2c,
+};
+
+static int __init olpc_dcon_init(void)
+{
+#ifdef CONFIG_FB_OLPC_DCON_1_5
+	/* XO-1.5 */
+	if (olpc_board_at_least(olpc_board(0xd0)))
+		pdata = &dcon_pdata_xo_1_5;
+#endif
+#ifdef CONFIG_FB_OLPC_DCON_1
+	if (!pdata)
+		pdata = &dcon_pdata_xo_1;
+#endif
+
+	return i2c_add_driver(&dcon_driver);
+}
+
+static void __exit olpc_dcon_exit(void)
+{
+	i2c_del_driver(&dcon_driver);
+}
+
+module_init(olpc_dcon_init);
+module_exit(olpc_dcon_exit);
+
+MODULE_LICENSE("GPL");

+ 111 - 0
drivers/staging/olpc_dcon/olpc_dcon.h

@@ -0,0 +1,111 @@
+#ifndef OLPC_DCON_H_
+#define OLPC_DCON_H_
+
+#include <linux/notifier.h>
+#include <linux/workqueue.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		0x2
+#define DCON_REG_HTOTAL		0x3
+#define DCON_REG_HSYNC_WIDTH	0x4
+#define DCON_REG_VRES		0x5
+#define DCON_REG_VTOTAL		0x6
+#define DCON_REG_VSYNC_WIDTH	0x7
+#define DCON_REG_TIMEOUT	0x8
+#define DCON_REG_SCAN_INT	0x9
+#define DCON_REG_BRIGHT		0xa
+#define DCON_REG_MEM_OPT_A	0x41
+#define DCON_REG_MEM_OPT_B	0x42
+
+/* Load Delay Locked Loop (DLL) settings for clock delay */
+#define MEM_DLL_CLOCK_DELAY	(1<<0)
+/* Memory controller power down function */
+#define MEM_POWER_DOWN		(1<<8)
+/* Memory controller software reset */
+#define MEM_SOFT_RESET		(1<<0)
+
+/* 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
+
+/* Interrupt */
+#define DCON_IRQ                6
+
+struct dcon_priv {
+	struct i2c_client *client;
+	struct fb_info *fbinfo;
+	struct backlight_device *bl_dev;
+
+	wait_queue_head_t waitq;
+	struct work_struct switch_source;
+	struct notifier_block reboot_nb;
+
+	/* Shadow register for the DCON_REG_MODE register */
+	u8 disp_mode;
+
+	/* The current backlight value - this saves us some smbus traffic */
+	u8 bl_val;
+
+	/* Current source, initialized at probe time */
+	int curr_src;
+
+	/* Desired source */
+	int pending_src;
+
+	/* Variables used during switches */
+	bool switched;
+	ktime_t irq_time;
+	ktime_t load_time;
+
+	/* Current output type; true == mono, false == color */
+	bool mono;
+	bool asleep;
+	/* This get set while controlling fb blank state from the driver */
+	bool ignore_fb_events;
+};
+
+struct dcon_platform_data {
+	int (*init)(struct dcon_priv *);
+	void (*bus_stabilize_wiggle)(void);
+	void (*set_dconload)(int);
+	int (*read_status)(u8 *);
+};
+
+#include <linux/interrupt.h>
+
+irqreturn_t dcon_interrupt(int irq, void *id);
+
+#ifdef CONFIG_FB_OLPC_DCON_1
+extern struct dcon_platform_data dcon_pdata_xo_1;
+#endif
+
+#ifdef CONFIG_FB_OLPC_DCON_1_5
+extern struct dcon_platform_data dcon_pdata_xo_1_5;
+#endif
+
+#endif

+ 205 - 0
drivers/staging/olpc_dcon/olpc_dcon_xo_1.c

@@ -0,0 +1,205 @@
+/*
+ * Mainly by David Woodhouse, somewhat modified by Jordan Crouse
+ *
+ * Copyright © 2006-2007  Red Hat, Inc.
+ * Copyright © 2006-2007  Advanced Micro Devices, Inc.
+ * Copyright © 2009       VIA Technology, Inc.
+ * Copyright (c) 2010  Andres Salomon <dilinger@queued.net>
+ *
+ * This program is free software.  You can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cs5535.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <asm/olpc.h>
+
+#include "olpc_dcon.h"
+
+static int dcon_init_xo_1(struct dcon_priv *dcon)
+{
+	unsigned char lob;
+
+	if (gpio_request(OLPC_GPIO_DCON_STAT0, "OLPC-DCON")) {
+		pr_err("failed to request STAT0 GPIO\n");
+		return -EIO;
+	}
+	if (gpio_request(OLPC_GPIO_DCON_STAT1, "OLPC-DCON")) {
+		pr_err("failed to request STAT1 GPIO\n");
+		goto err_gp_stat1;
+	}
+	if (gpio_request(OLPC_GPIO_DCON_IRQ, "OLPC-DCON")) {
+		pr_err("failed to request IRQ GPIO\n");
+		goto err_gp_irq;
+	}
+	if (gpio_request(OLPC_GPIO_DCON_LOAD, "OLPC-DCON")) {
+		pr_err("failed to request LOAD GPIO\n");
+		goto err_gp_load;
+	}
+	if (gpio_request(OLPC_GPIO_DCON_BLANK, "OLPC-DCON")) {
+		pr_err("failed to request BLANK GPIO\n");
+		goto err_gp_blank;
+	}
+
+	/* Turn off the event enable for GPIO7 just to be safe */
+	cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE);
+
+	/*
+	 * Determine the current state by reading the GPIO bit; earlier
+	 * stages of the boot process have established the state.
+	 *
+	 * Note that we read GPIO_OUTPUT_VAL rather than GPIO_READ_BACK here;
+	 * this is because OFW will disable input for the pin and set a value..
+	 * READ_BACK will only contain a valid value if input is enabled and
+	 * then a value is set.  So, future readings of the pin can use
+	 * READ_BACK, but the first one cannot.  Awesome, huh?
+	 */
+	dcon->curr_src = cs5535_gpio_isset(OLPC_GPIO_DCON_LOAD, GPIO_OUTPUT_VAL)
+		? DCON_SOURCE_CPU
+		: DCON_SOURCE_DCON;
+	dcon->pending_src = dcon->curr_src;
+
+	/* Set the directions for the GPIO pins */
+	gpio_direction_input(OLPC_GPIO_DCON_STAT0);
+	gpio_direction_input(OLPC_GPIO_DCON_STAT1);
+	gpio_direction_input(OLPC_GPIO_DCON_IRQ);
+	gpio_direction_input(OLPC_GPIO_DCON_BLANK);
+	gpio_direction_output(OLPC_GPIO_DCON_LOAD,
+			dcon->curr_src == DCON_SOURCE_CPU);
+
+	/* Set up the interrupt mappings */
+
+	/* Set the IRQ to pair 2 */
+	cs5535_gpio_setup_event(OLPC_GPIO_DCON_IRQ, 2, 0);
+
+	/* Enable group 2 to trigger the DCON interrupt */
+	cs5535_gpio_set_irq(2, DCON_IRQ);
+
+	/* Select edge level for interrupt (in PIC) */
+	lob = inb(0x4d0);
+	lob &= ~(1 << DCON_IRQ);
+	outb(lob, 0x4d0);
+
+	/* Register the interrupt handler */
+	if (request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", dcon)) {
+		pr_err("failed to request DCON's irq\n");
+		goto err_req_irq;
+	}
+
+	/* Clear INV_EN for GPIO7 (DCONIRQ) */
+	cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_INVERT);
+
+	/* Enable filter for GPIO12 (DCONBLANK) */
+	cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_FILTER);
+
+	/* Disable filter for GPIO7 */
+	cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_FILTER);
+
+	/* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
+	cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_EVENT_COUNT);
+	cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_EVENT_COUNT);
+
+	/* Add GPIO12 to the Filter Event Pair #7 */
+	cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_FE7_SEL);
+
+	/* Turn off negative Edge Enable for GPIO12 */
+	cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_EN);
+
+	/* Enable negative Edge Enable for GPIO7 */
+	cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_EN);
+
+	/* Zero the filter amount for Filter Event Pair #7 */
+	cs5535_gpio_set(0, GPIO_FLTR7_AMOUNT);
+
+	/* Clear the negative edge status for GPIO7 and GPIO12 */
+	cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS);
+	cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_STS);
+
+	/* FIXME:  Clear the positive status as well, just to be sure */
+	cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_POSITIVE_EDGE_STS);
+	cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_POSITIVE_EDGE_STS);
+
+	/* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
+	cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE);
+	cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_EVENTS_ENABLE);
+
+	return 0;
+
+err_req_irq:
+	gpio_free(OLPC_GPIO_DCON_BLANK);
+err_gp_blank:
+	gpio_free(OLPC_GPIO_DCON_LOAD);
+err_gp_load:
+	gpio_free(OLPC_GPIO_DCON_IRQ);
+err_gp_irq:
+	gpio_free(OLPC_GPIO_DCON_STAT1);
+err_gp_stat1:
+	gpio_free(OLPC_GPIO_DCON_STAT0);
+	return -EIO;
+}
+
+static void dcon_wiggle_xo_1(void)
+{
+	int x;
+
+	/*
+	 * According to HiMax, when powering the DCON up we should hold
+	 * SMB_DATA high for 8 SMB_CLK cycles.  This will force the DCON
+	 * state machine to reset to a (sane) initial state.  Mitch Bradley
+	 * did some testing and discovered that holding for 16 SMB_CLK cycles
+	 * worked a lot more reliably, so that's what we do here.
+	 *
+	 * According to the cs5536 spec, to set GPIO14 to SMB_CLK we must
+	 * simultaneously set AUX1 IN/OUT to GPIO14; ditto for SMB_DATA and
+	 * GPIO15.
+	 */
+	cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
+	cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_VAL);
+	cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_ENABLE);
+	cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_ENABLE);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX2);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX2);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1);
+	cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
+
+	for (x = 0; x < 16; x++) {
+		udelay(5);
+		cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
+		udelay(5);
+		cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
+	}
+	udelay(5);
+	cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1);
+	cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
+	cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1);
+	cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
+}
+
+static void dcon_set_dconload_1(int val)
+{
+	gpio_set_value(OLPC_GPIO_DCON_LOAD, val);
+}
+
+static int dcon_read_status_xo_1(u8 *status)
+{
+	*status = gpio_get_value(OLPC_GPIO_DCON_STAT0);
+	*status |= gpio_get_value(OLPC_GPIO_DCON_STAT1) << 1;
+
+	/* Clear the negative edge status for GPIO7 */
+	cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS);
+
+	return 0;
+}
+
+struct dcon_platform_data dcon_pdata_xo_1 = {
+	.init = dcon_init_xo_1,
+	.bus_stabilize_wiggle = dcon_wiggle_xo_1,
+	.set_dconload = dcon_set_dconload_1,
+	.read_status = dcon_read_status_xo_1,
+};

+ 161 - 0
drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c

@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2009,2010       One Laptop per Child
+ *
+ * This program is free software.  You can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <asm/olpc.h>
+
+/* TODO: this eventually belongs in linux/vx855.h */
+#define NR_VX855_GPI    14
+#define NR_VX855_GPO    13
+#define NR_VX855_GPIO   15
+
+#define VX855_GPI(n)    (n)
+#define VX855_GPO(n)    (NR_VX855_GPI + (n))
+#define VX855_GPIO(n)   (NR_VX855_GPI + NR_VX855_GPO + (n))
+
+#include "olpc_dcon.h"
+
+/* Hardware setup on the XO 1.5:
+ *	DCONLOAD connects to VX855_GPIO1 (not SMBCK2)
+ *	DCONBLANK connects to VX855_GPIO8 (not SSPICLK)  unused in driver
+ *	DCONSTAT0 connects to VX855_GPI10 (not SSPISDI)
+ *	DCONSTAT1 connects to VX855_GPI11 (not nSSPISS)
+ *	DCONIRQ connects to VX855_GPIO12
+ *	DCONSMBDATA connects to VX855 graphics CRTSPD
+ *	DCONSMBCLK connects to VX855 graphics CRTSPCLK
+ */
+
+#define VX855_GENL_PURPOSE_OUTPUT 0x44c /* PMIO_Rx4c-4f */
+#define VX855_GPI_STATUS_CHG 0x450  /* PMIO_Rx50 */
+#define VX855_GPI_SCI_SMI 0x452  /* PMIO_Rx52 */
+#define BIT_GPIO12 0x40
+
+#define PREFIX "OLPC DCON:"
+
+static void dcon_clear_irq(void)
+{
+	/* irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12 */
+	outb(BIT_GPIO12, VX855_GPI_STATUS_CHG);
+}
+
+static int dcon_was_irq(void)
+{
+	u_int8_t tmp;
+
+	/* irq status will appear in PMIO_Rx50[6] on gpio12 */
+	tmp = inb(VX855_GPI_STATUS_CHG);
+	return !!(tmp & BIT_GPIO12);
+
+	return 0;
+}
+
+static int dcon_init_xo_1_5(struct dcon_priv *dcon)
+{
+	unsigned int irq;
+
+	dcon_clear_irq();
+
+	/* set   PMIO_Rx52[6] to enable SCI/SMI on gpio12 */
+	outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
+
+	/* Determine the current state of DCONLOAD, likely set by firmware */
+	/* GPIO1 */
+	dcon->curr_src = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ?
+			DCON_SOURCE_CPU : DCON_SOURCE_DCON;
+	dcon->pending_src = dcon->curr_src;
+
+	/* we're sharing the IRQ with ACPI */
+	irq = acpi_gbl_FADT.sci_interrupt;
+	if (request_irq(irq, &dcon_interrupt, IRQF_SHARED, "DCON", dcon)) {
+		pr_err("DCON (IRQ%d) allocation failed\n", irq);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void set_i2c_line(int sda, int scl)
+{
+	unsigned char tmp;
+	unsigned int port = 0x26;
+
+	/* FIXME: This directly accesses the CRT GPIO controller !!! */
+	outb(port, 0x3c4);
+	tmp = inb(0x3c5);
+
+	if (scl)
+		tmp |= 0x20;
+	else
+		tmp &= ~0x20;
+
+	if (sda)
+		tmp |= 0x10;
+	else
+		tmp &= ~0x10;
+
+	tmp |= 0x01;
+
+	outb(port, 0x3c4);
+	outb(tmp, 0x3c5);
+}
+
+
+static void dcon_wiggle_xo_1_5(void)
+{
+	int x;
+
+	/*
+	 * According to HiMax, when powering the DCON up we should hold
+	 * SMB_DATA high for 8 SMB_CLK cycles.  This will force the DCON
+	 * state machine to reset to a (sane) initial state.  Mitch Bradley
+	 * did some testing and discovered that holding for 16 SMB_CLK cycles
+	 * worked a lot more reliably, so that's what we do here.
+	 */
+	set_i2c_line(1, 1);
+
+	for (x = 0; x < 16; x++) {
+		udelay(5);
+		set_i2c_line(1, 0);
+		udelay(5);
+		set_i2c_line(1, 1);
+	}
+	udelay(5);
+
+	/* set   PMIO_Rx52[6] to enable SCI/SMI on gpio12 */
+	outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
+}
+
+static void dcon_set_dconload_xo_1_5(int val)
+{
+	gpio_set_value(VX855_GPIO(1), val);
+}
+
+static int dcon_read_status_xo_1_5(u8 *status)
+{
+	if (!dcon_was_irq())
+		return -1;
+
+	/* i believe this is the same as "inb(0x44b) & 3" */
+	*status = gpio_get_value(VX855_GPI(10));
+	*status |= gpio_get_value(VX855_GPI(11)) << 1;
+
+	dcon_clear_irq();
+
+	return 0;
+}
+
+struct dcon_platform_data dcon_pdata_xo_1_5 = {
+	.init = dcon_init_xo_1_5,
+	.bus_stabilize_wiggle = dcon_wiggle_xo_1_5,
+	.set_dconload = dcon_set_dconload_xo_1_5,
+	.read_status = dcon_read_status_xo_1_5,
+};

+ 1 - 0
drivers/staging/rdma/hfi1/Kconfig

@@ -2,6 +2,7 @@ config INFINIBAND_HFI1
 	tristate "Intel OPA Gen1 support"
 	depends on X86_64 && INFINIBAND_RDMAVT
 	select MMU_NOTIFIER
+	select CRC32
 	default m
 	---help---
 	This is a low-level driver for Intel OPA Gen1 adapter.