From e31bab42367386736c07638683acb4da3c775eaf Mon Sep 17 00:00:00 2001
From: mokopatches <mokopatches@openmoko.org>
Date: Fri, 4 Apr 2008 11:35:42 +0100
Subject: [PATCH] lis302dl.patch
 This is a Linux driver for the STmicro LIS302DL 3-axis accelerometer.

Signed-off-by: Harald Welte <laforge@openmoko.org>
---
 arch/arm/mach-s3c2440/mach-gta02.c |   46 +++-
 drivers/input/misc/Kconfig         |    9 +
 drivers/input/misc/Makefile        |    1 +
 drivers/input/misc/lis302dl.c      |  633 ++++++++++++++++++++++++++++++++++++
 include/linux/lis302dl.h           |   11 +
 5 files changed, 699 insertions(+), 1 deletions(-)
 create mode 100644 drivers/input/misc/lis302dl.c
 create mode 100644 include/linux/lis302dl.h

diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index f72a5ae..d11da10 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -46,6 +46,7 @@
 #include <linux/mtd/physmap.h>
 
 #include <linux/pcf50633.h>
+#include <linux/lis302dl.h>
 
 #include <asm/mach/arch.h>
 #include <asm/mach/map.h>
@@ -463,7 +464,7 @@ static struct s3c2410_ts_mach_info gta02_ts_cfg = {
 	.oversampling_shift = 5,
 };
 
-/* SPI */
+/* SPI: LCM control interface attached to Glamo3362 */
 
 static struct spi_board_info gta02_spi_board_info[] = {
 	{
@@ -504,6 +505,48 @@ static struct platform_device gta01_led_dev = {
 	.resource	= gta01_led_resources,
 };
 
+/* SPI: Accelerometers attached to SPI of s3c244x */
+
+static void gta02_spi_acc_set_cs(struct s3c2410_spi_info *spi, int cs, int pol)
+{
+	s3c2410_gpio_setpin(cs, pol);
+}
+
+static const struct lis302dl_platform_data lis302_pdata[] = {
+	{
+		.name		= "lis302-1 (top)"
+	}, {
+		.name		= "lis302-2 (bottom)"
+	},
+};
+
+static struct spi_board_info gta02_spi_acc_bdinfo[] = {
+	{
+		.modalias	= "lis302dl",
+		.platform_data	= &lis302_pdata[0],
+		.irq		= GTA02_IRQ_GSENSOR_1,
+		.max_speed_hz	= 400 * 1000,
+		.bus_num	= 1,
+		.chip_select	= S3C2410_GPD12,
+		.mode		= SPI_MODE_3,
+	},
+	{
+		.modalias	= "lis302dl",
+		.platform_data	= &lis302_pdata[1],
+		.irq		= GTA02_IRQ_GSENSOR_2,
+		.max_speed_hz	= 400 * 1000,
+		.bus_num	= 1,
+		.chip_select	= S3C2410_GPD13,
+		.mode		= SPI_MODE_3,
+	},
+};
+
+static struct s3c2410_spi_info gta02_spi_acc_cfg = {
+	.set_cs		= gta02_spi_acc_set_cs,
+	.board_size	= ARRAY_SIZE(gta02_spi_acc_bdinfo),
+	.board_info	= gta02_spi_acc_bdinfo,
+};
+
 static struct resource gta02_led_resources[] = {
 	{
 		.name	= "gta02-power:orange",
@@ -746,6 +789,7 @@ static void __init gta02_machine_init(void)
 	s3c_device_usb.dev.platform_data = &gta02_usb_info;
 	s3c_device_nand.dev.platform_data = &gta02_nand_info;
 	s3c_device_sdi.dev.platform_data = &gta02_mmc_cfg;
+	s3c_device_spi1.dev.platform_data = &gta02_spi_acc_cfg;
 
 	/* Only GTA02v1 has a SD_DETECT GPIO.  Since the slot is not
 	 * hot-pluggable, this is not required anyway */
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 8f5c7b9..5a15edd 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -183,4 +183,13 @@ config HP_SDC_RTC
 	  Say Y here if you want to support the built-in real time clock
 	  of the HP SDC controller.
 
+config INPUT_LIS302DL
+	tristate "STmicro LIS302DL 3-axis accelerometer"
+	depends on SPI_MASTER
+	help
+	  SPI driver for the STmicro LIS302DL 3-axis accelerometer.
+
+	  The userspece interface is a 3-axis (X/Y/Z) relative movement
+	  Linux input device, reporting REL_[XYZ] events.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 3585b50..f197a67 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
 obj-$(CONFIG_HP_SDC_RTC)		+= hp_sdc_rtc.o
 obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
+obj-$(CONFIG_INPUT_LIS302DL)		+= lis302dl.o
diff --git a/drivers/input/misc/lis302dl.c b/drivers/input/misc/lis302dl.c
new file mode 100644
index 0000000..45c41c8
--- /dev/null
+++ b/drivers/input/misc/lis302dl.c
@@ -0,0 +1,633 @@
+/* Linux kernel driver for the ST LIS302D 3-axis accelerometer
+ *
+ * Copyright (C) 2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ * All rights reserved.
+ *
+ * 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 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * TODO
+ * 	* statistics for overflow events
+ * 	* configuration interface (sysfs) for
+ * 		* enable/disable x/y/z axis data ready
+ * 		* enable/disable resume from freee fall / click
+ * 		* free fall / click parameters
+ * 		* high pass filter parameters
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/sysfs.h>
+
+#include <linux/lis302dl.h>
+
+#include <linux/spi/spi.h>
+
+#define LIS302DL_WHO_AM_I_MAGIC		0x3b
+
+enum lis302dl_reg {
+	LIS302DL_REG_WHO_AM_I		= 0x0f,
+	LIS302DL_REG_CTRL1		= 0x20,
+	LIS302DL_REG_CTRL2		= 0x21,
+	LIS302DL_REG_CTRL3		= 0x22,
+	LIS302DL_REG_HP_FILTER_RESET	= 0x23,
+	LIS302DL_REG_STATUS		= 0x27,
+	LIS302DL_REG_OUT_X		= 0x29,
+	LIS302DL_REG_OUT_Y		= 0x2b,
+	LIS302DL_REG_OUT_Z		= 0x2d,
+	LIS302DL_REG_FF_WU_CFG_1	= 0x30,
+	LIS302DL_REG_FF_WU_SRC_1	= 0x31,
+	LIS302DL_REG_FF_WU_THS_1	= 0x32,
+	LIS302DL_REG_FF_WU_DURATION_1	= 0x33,
+	LIS302DL_REG_FF_WU_CFG_2	= 0x34,
+	LIS302DL_REG_FF_WU_SRC_2	= 0x35,
+	LIS302DL_REG_FF_WU_THS_2	= 0x36,
+	LIS302DL_REG_FF_WU_DURATION_2	= 0x37,
+	LIS302DL_REG_CLICK_CFG		= 0x38,
+	LIS302DL_REG_CLICK_SRC		= 0x39,
+	LIS302DL_REG_CLICK_THSY_X	= 0x3b,
+	LIS302DL_REG_CLICK_THSZ		= 0x3c,
+	LIS302DL_REG_CLICK_TIME_LIMIT	= 0x3d,
+	LIS302DL_REG_CLICK_LATENCY	= 0x3e,
+	LIS302DL_REG_CLICK_WINDOW	= 0x3f,
+};
+
+enum lis302dl_reg_ctrl1 {
+	LIS302DL_CTRL1_Xen		= 0x01,
+	LIS302DL_CTRL1_Yen		= 0x02,
+	LIS302DL_CTRL1_Zen		= 0x04,
+	LIS302DL_CTRL1_STM		= 0x08,
+	LIS302DL_CTRL1_STP		= 0x10,
+	LIS302DL_CTRL1_FS		= 0x20,
+	LIS302DL_CTRL1_PD		= 0x40,
+	LIS302DL_CTRL1_DR		= 0x80,
+};
+
+enum lis302dl_reg_ctrl3 {
+	LIS302DL_CTRL3_PP_OD		= 0x40,
+};
+
+enum lis302dl_reg_status {
+	LIS302DL_STATUS_XDA		= 0x01,
+	LIS302DL_STATUS_YDA		= 0x02,
+	LIS302DL_STATUS_ZDA		= 0x04,
+	LIS302DL_STATUS_XYZDA		= 0x08,
+	LIS302DL_STATUS_XOR		= 0x10,
+	LIS302DL_STATUS_YOR		= 0x20,
+	LIS302DL_STATUS_ZOR		= 0x40,
+	LIS302DL_STATUS_XYZOR		= 0x80,
+};
+
+enum lis302dl_reg_ffwusrc1 {
+	LIS302DL_FFWUSRC1_XL		= 0x01,
+	LIS302DL_FFWUSRC1_XH		= 0x02,
+	LIS302DL_FFWUSRC1_YL		= 0x04,
+	LIS302DL_FFWUSRC1_YH		= 0x08,
+	LIS302DL_FFWUSRC1_ZL		= 0x10,
+	LIS302DL_FFWUSRC1_ZH		= 0x20,
+	LIS302DL_FFWUSRC1_IA		= 0x40,
+};
+
+enum lis302dl_reg_cloik_src {
+	LIS302DL_CLICKSRC_SINGLE_X	= 0x01,
+	LIS302DL_CLICKSRC_DOUBLE_X	= 0x02,
+	LIS302DL_CLICKSRC_SINGLE_Y	= 0x04,
+	LIS302DL_CLICKSRC_DOUBLE_Y	= 0x08,
+	LIS302DL_CLICKSRC_SINGLE_Z	= 0x10,
+	LIS302DL_CLICKSRC_DOUBLE_Z	= 0x20,
+	LIS302DL_CLICKSRC_IA		= 0x40,
+};
+
+struct lis302dl_info {
+	struct spi_device *spi_dev;
+	struct input_dev *input_dev;
+	struct mutex lock;
+	struct work_struct work;
+	unsigned int flags;
+	unsigned int working;
+	u_int8_t regs[0x40];
+};
+
+#define LIS302DL_F_WUP_FF		0x0001	/* wake up from free fall */
+#define LIS302DL_F_WUP_CLICK		0x0002
+#define LIS302DL_F_POWER		0x0010
+#define LIS302DL_F_FS			0x0020 	/* ADC full scale */
+
+/* lowlevel register access functions */
+
+#define READ_BIT	0x01
+#define MS_BIT		0x02
+#define ADDR_SHIFT	2
+
+static inline u_int8_t __reg_read(struct lis302dl_info *lis, u_int8_t reg)
+{
+	int rc;
+	u_int8_t cmd;
+
+	cmd = (reg << ADDR_SHIFT) | READ_BIT;
+
+	rc = spi_w8r8(lis->spi_dev, cmd);
+
+	return rc;
+}
+
+static u_int8_t reg_read(struct lis302dl_info *lis, u_int8_t reg)
+{
+	u_int8_t ret;
+
+	mutex_lock(&lis->lock);
+	ret = __reg_read(lis, reg);
+	mutex_unlock(&lis->lock);
+
+	return ret;
+}
+
+static inline int __reg_write(struct lis302dl_info *lis, u_int8_t reg, u_int8_t val)
+{
+	u_int8_t buf[2];
+
+	buf[0] = (reg << ADDR_SHIFT);
+	buf[1] = val;
+
+	return spi_write(lis->spi_dev, buf, sizeof(buf));
+}
+
+static int reg_write(struct lis302dl_info *lis, u_int8_t reg, u_int8_t val)
+{
+	int ret;
+
+	mutex_lock(&lis->lock);
+	ret = __reg_write(lis, reg, val);
+	mutex_unlock(&lis->lock);
+
+	return ret;
+}
+
+static int reg_set_bit_mask(struct lis302dl_info *lis,
+			    u_int8_t reg, u_int8_t mask, u_int8_t val)
+{
+	int ret;
+	u_int8_t tmp;
+
+	val &= mask;
+
+	mutex_lock(&lis->lock);
+
+	tmp = __reg_read(lis, reg);
+	tmp &= ~mask;
+	tmp |= val;
+	ret = __reg_write(lis, reg, tmp);
+
+	mutex_unlock(&lis->lock);
+
+	return ret;
+}
+
+/* interrupt handling related */
+
+enum lis302dl_intmode {
+	LIS302DL_INTMODE_GND		= 0x00,
+	LIS302DL_INTMODE_FF_WU_1	= 0x01,
+	LIX302DL_INTMODE_FF_WU_2	= 0x02,
+	LIX302DL_INTMODE_FF_WU_12	= 0x03,
+	LIX302DL_INTMODE_DATA_READY	= 0x04,
+	LIX302DL_INTMODE_CLICK		= 0x07,
+};
+
+static void lis302dl_int_mode(struct spi_device *spi, int int_pin,
+			      enum lis302dl_intmode mode)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
+
+	if (int_pin == 1)
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x07, mode);
+	else if (int_pin == 2)
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x38, mode << 3);
+}
+
+static void _report_btn_single(struct input_dev *inp, int btn)
+{
+	input_report_key(inp, btn, 1);
+	input_sync(inp);
+	input_report_key(inp, btn, 0);
+}
+
+static void _report_btn_double(struct input_dev *inp, int btn)
+{
+	input_report_key(inp, btn, 1);
+	input_sync(inp);
+	input_report_key(inp, btn, 0);
+	input_sync(inp);
+	input_report_key(inp, btn, 1);
+	input_sync(inp);
+	input_report_key(inp, btn, 0);
+}
+
+static void lis302dl_work(struct work_struct *work)
+{
+	struct lis302dl_info *lis =
+			container_of(work, struct lis302dl_info, work);
+
+	u_int8_t status, ff_wu_src_1, click_src;
+	u_int8_t val;
+
+	lis->working = 1;
+
+ 	status = reg_read(lis, LIS302DL_REG_STATUS);
+	ff_wu_src_1 = reg_read(lis, LIS302DL_REG_FF_WU_SRC_1);
+	click_src = reg_read(lis, LIS302DL_REG_CLICK_SRC);
+
+	if (status & LIS302DL_STATUS_XDA) {
+		val = reg_read(lis, LIS302DL_REG_OUT_X);
+		if (lis->flags & LIS302DL_F_FS)
+			val = val << 2;
+		input_report_rel(lis->input_dev, REL_X, val);
+	}
+
+	if (status & LIS302DL_STATUS_YDA) {
+		val = reg_read(lis, LIS302DL_REG_OUT_Y);
+		if (lis->flags & LIS302DL_F_FS)
+			val = val << 2;
+		input_report_rel(lis->input_dev, REL_Y, val);
+	}
+
+	if (status & LIS302DL_STATUS_ZDA) {
+		val = reg_read(lis, LIS302DL_REG_OUT_Z);
+		if (lis->flags & LIS302DL_F_FS)
+			val = val << 2;
+		input_report_rel(lis->input_dev, REL_Z, val);
+	}
+
+	if (status & 0xf0)
+		dev_dbg(&lis->spi_dev->dev, "overrun!\n");
+
+	/* FIXME: implement overrun statistics */
+
+	if (ff_wu_src_1 & LIS302DL_FFWUSRC1_IA) {
+		/* FIXME: free fall interrupt handling */
+	}
+
+	if (click_src & LIS302DL_CLICKSRC_IA) {
+		if (click_src & LIS302DL_CLICKSRC_SINGLE_X)
+			_report_btn_single(lis->input_dev, BTN_X);
+		if (click_src & LIS302DL_CLICKSRC_DOUBLE_X)
+			_report_btn_double(lis->input_dev, BTN_X);
+
+		if (click_src & LIS302DL_CLICKSRC_SINGLE_Y)
+			_report_btn_single(lis->input_dev, BTN_Y);
+		if (click_src & LIS302DL_CLICKSRC_DOUBLE_Y)
+			_report_btn_double(lis->input_dev, BTN_Y);
+
+		if (click_src & LIS302DL_CLICKSRC_SINGLE_Z)
+			_report_btn_single(lis->input_dev, BTN_Z);
+		if (click_src & LIS302DL_CLICKSRC_DOUBLE_Z)
+			_report_btn_double(lis->input_dev, BTN_Z);
+	}
+
+	lis->working = 0;
+	input_sync(lis->input_dev);
+	put_device(&lis->spi_dev->dev);
+
+	enable_irq(lis->spi_dev->irq);
+}
+
+static void lis302dl_schedule_work(struct lis302dl_info *lis)
+{
+	int status;
+
+	get_device(&lis->spi_dev->dev);
+	status = schedule_work(&lis->work);
+	if (!status && !lis->working)
+		dev_dbg(&lis->spi_dev->dev, "work item may be lost\n");
+}
+
+static irqreturn_t lis302dl_interrupt(int irq, void *_lis)
+{
+	struct lis302dl_info *lis = _lis;
+
+	lis302dl_schedule_work(lis);
+
+	/* Disable any further interrupts until we have processed
+	 * the current one */
+	disable_irq(lis->spi_dev->irq);
+
+	return IRQ_HANDLED;
+}
+
+/* sysfs */
+
+static ssize_t show_rate(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(dev);
+	u_int8_t ctrl1 = reg_read(lis, LIS302DL_REG_CTRL1);
+
+	return sprintf(buf, "%d\n", ctrl1 & LIS302DL_CTRL1_DR ? 400 : 100);
+}
+
+static ssize_t set_rate(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(dev);
+
+	if (!strcmp(buf, "400\n"))
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR,
+				 LIS302DL_CTRL1_DR);
+	else
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR, 0);
+
+	return count;
+}
+
+static DEVICE_ATTR(sample_rate, S_IRUGO | S_IWUSR, show_rate, set_rate);
+
+static ssize_t show_scale(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(dev);
+	u_int8_t ctrl1 = reg_read(lis, LIS302DL_REG_CTRL1);
+
+	return sprintf(buf, "%s\n", ctrl1 & LIS302DL_CTRL1_FS ? "9.2" : "2.3");
+}
+
+static ssize_t set_scale(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(dev);
+
+	if (!strcmp(buf, "9.2\n"))
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS,
+				 LIS302DL_CTRL1_FS);
+	else
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS, 0);
+
+	return count;
+}
+
+static DEVICE_ATTR(full_scale, S_IRUGO | S_IWUSR, show_scale, set_scale);
+
+static struct attribute *lis302dl_sysfs_entries[] = {
+	&dev_attr_sample_rate.attr,
+	&dev_attr_full_scale.attr,
+};
+
+static struct attribute_group lis302dl_attr_group = {
+	.name	= NULL,
+	.attrs	= lis302dl_sysfs_entries,
+};
+
+/* input device handling and driver core interaction */
+
+static int lis302dl_input_open(struct input_dev *inp)
+{
+	struct lis302dl_info *lis = inp->private;
+	u_int8_t ctrl1 = LIS302DL_CTRL1_PD | LIS302DL_CTRL1_Xen |
+			 LIS302DL_CTRL1_Yen | LIS302DL_CTRL1_Zen;
+
+	/* make sure we're powered up and generate data ready */
+	reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, ctrl1);
+
+	return 0;
+}
+
+static void lis302dl_input_close(struct input_dev *inp)
+{
+	struct lis302dl_info *lis = inp->private;
+	u_int8_t ctrl1 = LIS302DL_CTRL1_Xen | LIS302DL_CTRL1_Yen |
+			 LIS302DL_CTRL1_Zen;
+
+	/* since the input core already serializes access and makes sure we
+	 * only see close() for the close of the lastre user, we can safely
+	 * disable the data ready events */
+	reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, 0x00);
+
+	/* however, don't power down the whole device if still needed */
+	if (!(lis->flags & LIS302DL_F_WUP_FF ||
+	      lis->flags & LIS302DL_F_WUP_CLICK)) {
+		reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD,
+				 0x00);
+	}
+}
+
+static int __devinit lis302dl_probe(struct spi_device *spi)
+{
+	int rc;
+	struct lis302dl_info *lis;
+	u_int8_t wai;
+
+	lis = kzalloc(sizeof(*lis), GFP_KERNEL);
+	if (!lis)
+		return -ENOMEM;
+
+	mutex_init(&lis->lock);
+	INIT_WORK(&lis->work, lis302dl_work);
+	lis->spi_dev = spi;
+
+	spi_set_drvdata(spi, lis);
+
+	rc = spi_setup(spi);
+	if (rc < 0) {
+		printk(KERN_ERR "error durign spi_setup of lis302dl driver\n");
+		dev_set_drvdata(&spi->dev, NULL);
+		kfree(lis);
+		return rc;
+	}
+
+	wai = reg_read(lis, LIS302DL_REG_WHO_AM_I);
+	if (wai != LIS302DL_WHO_AM_I_MAGIC) {
+		printk(KERN_ERR "unknown who_am_i signature 0x%02x\n", wai);
+		dev_set_drvdata(&spi->dev, NULL);
+		kfree(lis);
+		return -ENODEV;
+	}
+
+	/* switch interrupt to open collector */
+	reg_write(lis, LIS302DL_CTRL3_PP_OD, 0x7c);
+
+	rc = request_irq(lis->spi_dev->irq, lis302dl_interrupt, IRQF_DISABLED,
+			 "lis302dl", NULL);
+	if (rc < 0) {
+		dev_err(&spi->dev, "error requesting IRQ %d\n",
+			lis->spi_dev->irq);
+		/* FIXME */
+		return rc;
+	}
+
+	rc = sysfs_create_group(&spi->dev.kobj, &lis302dl_attr_group);
+	if (rc) {
+		dev_err(&spi->dev, "error creating sysfs group\n");
+		/* FIXME */
+		return rc;
+	}
+
+	/* initialize input layer details */
+	lis->input_dev = input_allocate_device();
+	if (!lis->input_dev) {
+		dev_err(&spi->dev, "Unable to allocate input device\n");
+		/* FIXME */
+	}
+
+	set_bit(EV_REL, lis->input_dev->evbit);
+	set_bit(EV_KEY, lis->input_dev->evbit);
+	set_bit(BTN_X, lis->input_dev->keybit);
+	set_bit(BTN_Y, lis->input_dev->keybit);
+	set_bit(BTN_Z, lis->input_dev->keybit);
+
+	lis->input_dev->private = lis;
+	lis->input_dev->name = "lis302dl"; /* FIXME: platform data */
+	lis->input_dev->id.bustype = BUS_I2C; /* FIXME: SPI Bus */
+	lis->input_dev->open = lis302dl_input_open;
+	lis->input_dev->close = lis302dl_input_close;
+
+	input_register_device(lis->input_dev);
+
+	return 0;
+}
+
+static int __devexit lis302dl_remove(struct spi_device *spi)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
+
+	/* power down the device */
+	reg_write(lis, LIS302DL_REG_CTRL1, 0x00);
+	sysfs_remove_group(&spi->dev.kobj, &lis302dl_attr_group);
+	input_unregister_device(lis->input_dev);
+	dev_set_drvdata(&spi->dev, NULL);
+	kfree(lis);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lis302dl_suspend(struct spi_device *spi, pm_message_t state)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
+
+	/* save registers */
+	lis->regs[LIS302DL_REG_CTRL1] = reg_read(lis, LIS302DL_REG_CTRL1);
+	lis->regs[LIS302DL_REG_CTRL2] = reg_read(lis, LIS302DL_REG_CTRL2);
+	lis->regs[LIS302DL_REG_CTRL3] = reg_read(lis, LIS302DL_REG_CTRL3);
+	lis->regs[LIS302DL_REG_FF_WU_CFG_1] =
+				reg_read(lis, LIS302DL_REG_FF_WU_CFG_1);
+	lis->regs[LIS302DL_REG_FF_WU_THS_1] =
+				reg_read(lis, LIS302DL_REG_FF_WU_THS_1);
+	lis->regs[LIS302DL_REG_FF_WU_DURATION_1] =
+				reg_read(lis, LIS302DL_REG_FF_WU_DURATION_1);
+	lis->regs[LIS302DL_REG_FF_WU_CFG_2] =
+				reg_read(lis, LIS302DL_REG_FF_WU_CFG_2);
+	lis->regs[LIS302DL_REG_FF_WU_THS_2] =
+				reg_read(lis, LIS302DL_REG_FF_WU_THS_2);
+	lis->regs[LIS302DL_REG_FF_WU_DURATION_2] =
+				reg_read(lis, LIS302DL_REG_FF_WU_DURATION_2);
+	lis->regs[LIS302DL_REG_CLICK_CFG] =
+				reg_read(lis, LIS302DL_REG_CLICK_CFG);
+	lis->regs[LIS302DL_REG_CLICK_THSY_X] =
+				reg_read(lis, LIS302DL_REG_CLICK_THSY_X);
+	lis->regs[LIS302DL_REG_CLICK_THSZ] =
+				reg_read(lis, LIS302DL_REG_CLICK_THSZ);
+	lis->regs[LIS302DL_REG_CLICK_TIME_LIMIT] =
+				reg_read(lis, LIS302DL_REG_CLICK_TIME_LIMIT);
+	lis->regs[LIS302DL_REG_CLICK_LATENCY] =
+				reg_read(lis, LIS302DL_REG_CLICK_LATENCY);
+	lis->regs[LIS302DL_REG_CLICK_WINDOW] =
+				reg_read(lis, LIS302DL_REG_CLICK_WINDOW);
+
+	/* determine if we want to wake up from the accel. */
+	if (!(lis->flags & LIS302DL_F_WUP_FF ||
+	      lis->flags & LIS302DL_F_WUP_CLICK)) {
+		/* power down */
+		u_int8_t tmp;
+		tmp = reg_read(lis, LIS302DL_REG_CTRL1);
+		tmp &= ~LIS302DL_CTRL1_PD;
+		reg_write(lis, LIS302DL_REG_CTRL1, tmp);
+	}
+
+	return 0;
+}
+
+static int lis302dl_resume(struct spi_device *spi)
+{
+	struct lis302dl_info *lis = dev_get_drvdata(&spi->dev);
+
+	/* restore registers after resume */
+	reg_write(lis, LIS302DL_REG_CTRL1, lis->regs[LIS302DL_REG_CTRL1]);
+	reg_write(lis, LIS302DL_REG_CTRL2, lis->regs[LIS302DL_REG_CTRL2]);
+	reg_write(lis, LIS302DL_REG_CTRL3, lis->regs[LIS302DL_REG_CTRL3]);
+	reg_write(lis, LIS302DL_REG_FF_WU_CFG_1,
+		  lis->regs[LIS302DL_REG_FF_WU_CFG_1]);
+	reg_write(lis, LIS302DL_REG_FF_WU_THS_1,
+		  lis->regs[LIS302DL_REG_FF_WU_THS_1]);
+	reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1,
+		  lis->regs[LIS302DL_REG_FF_WU_DURATION_1]);
+	reg_write(lis, LIS302DL_REG_FF_WU_CFG_2,
+		  lis->regs[LIS302DL_REG_FF_WU_CFG_2]);
+	reg_write(lis, LIS302DL_REG_FF_WU_THS_2,
+		  lis->regs[LIS302DL_REG_FF_WU_THS_2]);
+	reg_write(lis, LIS302DL_REG_FF_WU_DURATION_2,
+		  lis->regs[LIS302DL_REG_FF_WU_DURATION_2]);
+	reg_write(lis, LIS302DL_REG_CLICK_CFG,
+		  lis->regs[LIS302DL_REG_CLICK_CFG]);
+	reg_write(lis, LIS302DL_REG_CLICK_THSY_X,
+		  lis->regs[LIS302DL_REG_CLICK_THSY_X]);
+	reg_write(lis, LIS302DL_REG_CLICK_THSZ,
+		  lis->regs[LIS302DL_REG_CLICK_THSZ]);
+	reg_write(lis, LIS302DL_REG_CLICK_TIME_LIMIT,
+		  lis->regs[LIS302DL_REG_CLICK_TIME_LIMIT]);
+	reg_write(lis, LIS302DL_REG_CLICK_LATENCY,
+		  lis->regs[LIS302DL_REG_CLICK_LATENCY]);
+	reg_write(lis, LIS302DL_REG_CLICK_WINDOW,
+		  lis->regs[LIS302DL_REG_CLICK_WINDOW]);
+
+	return 0;
+}
+#else
+#define lis302dl_suspend	NULL
+#define lis302dl_resume		NULL
+#endif
+
+static struct spi_driver lis302dl_driver = {
+	.driver = {
+		.name	= "lis302dl",
+		.owner	= THIS_MODULE,
+	},
+
+	.probe	 = lis302dl_probe,
+	.remove	 = __devexit_p(lis302dl_remove),
+	.suspend = lis302dl_suspend,
+	.resume	 = lis302dl_resume,
+};
+
+static int __init lis302dl_init(void)
+{
+	return spi_register_driver(&lis302dl_driver);
+}
+
+static void __exit lis302dl_exit(void)
+{
+	spi_unregister_driver(&lis302dl_driver);
+}
+
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
+MODULE_LICENSE("GPL");
+
+module_init(lis302dl_init);
+module_exit(lis302dl_exit);
diff --git a/include/linux/lis302dl.h b/include/linux/lis302dl.h
new file mode 100644
index 0000000..d0f31be
--- /dev/null
+++ b/include/linux/lis302dl.h
@@ -0,0 +1,11 @@
+#ifndef _LINUX_LIS302DL_H
+#define _LINUX_LIS302DL_H
+
+#include <linux/types.h>
+
+struct lis302dl_platform_data {
+	char *name;
+};
+
+#endif /* _LINUX_LIS302DL_H */
+
-- 
1.5.6.5