[PATCH] input: New driver for Analog Devices ADXL362 Three-Axis Digital Accelerometer

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Michael Hennerich <michael.hennerich@xxxxxxxxxx>

This patch adds support for Analog Devices ADXL362 SPI-Bus Three-Axis Digital
Accelerometer.

Signed-off-by: Michael Hennerich <michael.hennerich@xxxxxxxxxx>
---
 Documentation/ABI/testing/sysfs-driver-adxl362 |   14 +
 drivers/input/misc/Kconfig                     |   12 +
 drivers/input/misc/Makefile                    |    1 +
 drivers/input/misc/adxl362.c                   |  817 ++++++++++++++++++++++++
 include/linux/input/adxl362.h                  |  125 ++++
 5 files changed, 969 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-adxl362
 create mode 100644 drivers/input/misc/adxl362.c
 create mode 100644 include/linux/input/adxl362.h

diff --git a/Documentation/ABI/testing/sysfs-driver-adxl362 b/Documentation/ABI/testing/sysfs-driver-adxl362
new file mode 100644
index 0000000..a8d2bfc
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-adxl362
@@ -0,0 +1,14 @@
+What:		/sys/bus/spi/devices/<busnum>-<devaddr>/rate
+Date:		June 2012
+Contact:	Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx>
+Description:	Sets the output data rate (ODR) in Hz. Valid values are in the
+		range of 12..400.
+		Reading returns the current output data rate in Hz.
+
+What:		/sys/bus/spi/devices/<busnum>-<devaddr>/autosleep
+Date:		June 2012
+Contact:	Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx>
+Description:	Writing '1' puts the device in autosleep mode. Which will cause
+		the device to autonomously enter an low power mode mode when
+		inactivity is detected.
+		Reading returns '1' for autosleep or '0' for normal operation.
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7faf4a7..f6671ff 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -553,6 +553,18 @@ config INPUT_ADXL34X_SPI
 	  To compile this driver as a module, choose M here: the
 	  module will be called adxl34x-spi.
 
+config INPUT_ADXL362
+	tristate "Analog Devices ADXL362 Three-Axis Digital Accelerometer"
+	depends on SPI
+	help
+	  Say Y here if you have a Accelerometer interface using the
+	  ADXL362 controller, and your board-specific initialization
+	  code includes that in its table of devices.
+	  If unsure, say N (but it's safe to say "Y").
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called adxl362.
+
 config INPUT_CMA3000
 	tristate "VTI CMA3000 Tri-axis accelerometer"
 	help
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index f55cdf4..091a338 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI)		+= ad714x-spi.o
 obj-$(CONFIG_INPUT_ADXL34X)		+= adxl34x.o
 obj-$(CONFIG_INPUT_ADXL34X_I2C)		+= adxl34x-i2c.o
 obj-$(CONFIG_INPUT_ADXL34X_SPI)		+= adxl34x-spi.o
+obj-$(CONFIG_INPUT_ADXL362)		+= adxl362.o
 obj-$(CONFIG_INPUT_APANEL)		+= apanel.o
 obj-$(CONFIG_INPUT_ATI_REMOTE2)		+= ati_remote2.o
 obj-$(CONFIG_INPUT_ATLAS_BTNS)		+= atlas_btns.o
diff --git a/drivers/input/misc/adxl362.c b/drivers/input/misc/adxl362.c
new file mode 100644
index 0000000..c77409d
--- /dev/null
+++ b/drivers/input/misc/adxl362.c
@@ -0,0 +1,817 @@
+/*
+ * ADXL362 Three-Axis Digital Accelerometers
+ *
+ * Copyright (C) 2012 Michael Hennerich, Analog Devices Inc.
+ * Licensed under the GPL-2.
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/spi/spi.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+
+#include <linux/input/adxl362.h>
+
+#define REG_2B			(1 << 9) /* 16-bit register */
+#define ADXL_REG_FIFO		(1 << 8) /* dummy register */
+
+/* ADXL362 Register Map */
+#define ADXL_REG_DEVID_AD	0x00
+#define ADXL_REG_DEVID_MST	0x01
+#define ADXL_REG_PARTID		0x02
+#define ADXL_REG_REVID		0x03
+#define ADXL_REG_XDATA		0x08
+#define ADXL_REG_YDATA		0x09
+#define ADXL_REG_ZDATA		0x0A
+#define ADXL_REG_STATUS		0x0B
+#define ADXL_REG_FIFO_ENTRIES_L	(0x0C | REG_2B)
+#define ADXL_REG_FIFO_ENTRIES_H	0x0D
+#define ADXL_REG_XDATA_L	(0x0E | REG_2B)
+#define ADXL_REG_XDATA_H	0x0F
+#define ADXL_REG_YDATA_L	(0x10 | REG_2B)
+#define ADXL_REG_YDATA_H	0x11
+#define ADXL_REG_ZDATA_L	(0x12 | REG_2B)
+#define ADXL_REG_ZDATA_H	0x13
+#define ADXL_REG_TEMP_L		(0x14 | REG_2B)
+#define ADXL_REG_TEMP_H		0x15
+#define ADXL_REG_SOFT_RESET	0x1F
+#define ADXL_REG_THRESH_ACT_L	(0x20 | REG_2B)
+#define ADXL_REG_THRESH_ACT_H	0x21
+#define ADXL_REG_TIME_ACT	0x22
+#define ADXL_REG_THRESH_INACT_L	(0x23 | REG_2B)
+#define ADXL_REG_THRESH_INACT_H	0x24
+#define ADXL_REG_TIME_INACT_L	(0x25 | REG_2B)
+#define ADXL_REG_TIME_INACT_H	0x26
+#define ADXL_REG_ACT_INACT_CTL	0x27
+#define ADXL_REG_FIFO_CTL	0x28
+#define ADXL_REG_FIFO_SAMPLES	0x29
+#define ADXL_REG_INTMAP1	0x2A
+#define ADXL_REG_INTMAP2	0x2B
+#define ADXL_REG_FILTER_CTL	0x2C
+#define ADXL_REG_POWER_CTL	0x2D
+#define ADXL_REG_SELF_TEST	0x2E
+
+/* STATUS */
+#define ADXL_ERR_USER_REGS	(1 << 7)
+#define ADXL_AWAKE		(1 << 6)
+#define ADXL_INACT		(1 << 5)
+#define ADXL_ACT		(1 << 4)
+#define ADXL_FIFO_OVERRUN	(1 << 3)
+#define ADXL_FIFO_WATERMARK	(1 << 2)
+#define ADXL_FIFO_READY		(1 << 1)
+#define ADXL_DATA_READY		(1 << 0)
+
+/* ACT_INACT_CTL */
+#define ADXL_LINK_LOOP(x)	(((x) & 0x3) << 4)
+#define ADXL_DEFA_MODE		0
+#define ADXL_LINK_MODE		2
+#define ADXL_LOOP_MODE		3
+#define ADXL_INACT_REF		(1 << 3)
+#define ADXL_INACT_EN		(1 << 2)
+#define ADXL_ACT_REF		(1 << 1)
+#define ADXL_ACT_EN		(1 << 0)
+
+/* FILTER_CTL */
+#define ADXL_RANGE(x)		((x) << 6)
+#define ADXL_HALF_BW		(1 << 4)
+#define ADXL_EXT_SAMPLE		(1 << 3)
+#define ADXL_ODR(x)		((x) & 0x7)
+
+/* FIFO_CTL */
+#define ADXL_FIFO_AH		(1 << 3)
+#define ADXL_FIFO_TEMP_EN	(1 << 2)
+#define ADXL_FIFO_MODE(x)	(((x) & 0x3) << 0)
+#define ADXL_FIFO_MODE_DIS	0
+#define ADXL_FIFO_MODE_OLDEST	1
+#define ADXL_FIFO_MODE_STREAM	2
+#define ADXL_FIFO_MODE_TRIG	3
+
+/* INTMAP1/INTMAP2 */
+#define ADXL_INT_LOW_ACTIVE	(1 << 7)
+#define ADXL_INT_AWAKE_EN	(1 << 6)
+#define ADXL_INT_INACT_EN	(1 << 5)
+#define ADXL_INT_ACT_EN		(1 << 4)
+#define ADXL_INT_FIFO_OVERRUN_EN (1 << 3)
+#define ADXL_INT_FIFO_WATERMARK_EN (1 << 2)
+#define ADXL_INT_FIFO_READY_EN	(1 << 1)
+#define ADXL_INT_DATA_READY_EN	(1 << 0)
+
+/* POWER_CTL */
+#define ADXL_EXT_CLK		(1 << 6)
+#define ADXL_LOW_NOISE(x)	((x) << 4)
+#define ADXL_WAKE_UP		(1 << 3)
+#define ADXL_AUTOSLEEP		(1 << 2)
+#define ADXL_MEASUREMENT_MODE	(1 << 1)
+
+/* FIFO Buffer */
+#define X_AXIS			0
+#define Y_AXIS			1
+#define Z_AXIS			2
+#define TEMP			3
+#define FIFO_ITEM(x)		((x) >> 14)
+#define FIFO_ITEM_MASK		(3 << 14)
+
+/* device specifics */
+#define MAN_ID_AD		0xAD
+#define PART_ID_ADXL362		0xF2 /* octal 362 */
+#define MIN_FIFO_SETS		1
+#define MAX_T_FIFO_SETS		170
+
+/* SPI specifics */
+#define MAX_SPI_FREQ_HZ		5000000
+#define ADXL34X_CMD_FIFO	0xD
+#define ADXL34X_CMD_READ	0xB
+#define ADXL34X_CMD_WRITE	0xA
+
+/* internal math helpers */
+#define SIGN(x)			((x) < 0 ? -1 : 1)
+#define ODR_TO_HZ(x)		((125 << (x)) / 10)
+#define HZ_TO_ODR(x)		(ilog2(((x) * 10) / 125))
+#define CLAMP_WM(x)		(clamp_t(u8, x, MIN_FIFO_SETS, MAX_T_FIFO_SETS))
+#define CLAMP_ACT(x)		(clamp_t(u8, x, 1, 0xFF))
+#define CLAMP_INACT(x)		(clamp_t(u16, x, 1, 0xFFFF))
+
+struct adxl362_axis_event {
+	unsigned code;
+	int scale;
+};
+
+struct adxl362_state {
+	struct input_dev *input;
+	struct spi_device *spi;
+	struct mutex mutex;	/* reentrant protection for struct */
+	struct adxl362_platform_data *pdata;
+	struct spi_message msg;
+	struct spi_transfer xfers[2];
+	struct adxl362_axis_event axis_event[3];
+	struct delayed_work work; /* !irq */
+	unsigned long delay; /* !irq */
+	unsigned irq;
+	bool opened;	/* P: mutex */
+	bool suspended;	/* P: mutex */
+	char phys[32];
+	unsigned char int_mask;
+	unsigned char power_ctl;
+	unsigned char filter_ctl;
+	unsigned char fifo_ctl;
+	unsigned char act_inact_ctl;
+	unsigned char watermarks_odr[6];
+
+	/*
+	 * DMA (thus cache coherency maintenance) requires the
+	 * transfer buffers to live in their own cache lines.
+	 */
+	__le16 data[512] ____cacheline_aligned;
+};
+
+static struct adxl362_platform_data adxl362_default_init = {
+	.data_rate = ADXL_ODR_100HZ,	/* 100Hz */
+	.data_range = ADXL_RANGE_PM_2g,	/* +/- 2000mg */
+	.activity_threshold = 70,	/* 70mg (referenced) */
+	.inactivity_threshold = 30,	/* 30mg (referenced) */
+	.inactivity_time = 10000,	/* 10s */
+	.activity_time = 1,		/* 1ms */
+	.referenced_activity_en = true, /* cancel static accel. of gravity */
+	.referenced_inactivity_en = true, /* cancel static accel. of gravity */
+	.watermark_odr_12Hz = 1,
+	.watermark_odr_25Hz = 1,
+	.watermark_odr_50Hz = 1,
+	.watermark_odr_100Hz = 1,
+	.watermark_odr_200Hz = 2, /* limit irq/poll interval to 10ms */
+	.watermark_odr_400Hz = 4, /* limit irq/poll interval to 10ms */
+	.ev_code_x = ABS_X,		/* default mapping */
+	.ev_code_y = ABS_Y,
+	.ev_code_z = ABS_Z,
+};
+
+static int adxl362_read(struct spi_device *spi, unsigned reg)
+{
+	unsigned char buf[4];
+	unsigned rxcnt;
+	ssize_t status;
+
+	buf[0] = ADXL34X_CMD_READ;
+	buf[1] = reg;
+
+	if (reg & REG_2B) {
+		rxcnt = 2;
+	} else {
+		rxcnt = 1;
+		buf[3] = 0;
+	}
+
+	status = spi_write_then_read(spi, &buf[0], 2, &buf[2], rxcnt);
+
+	return (status < 0) ? status : get_unaligned_le16(&buf[2]);
+}
+
+static int adxl362_write(struct spi_device *spi,
+			     unsigned reg, unsigned val)
+{
+	unsigned char buf[4];
+	unsigned txcnt = 3;
+
+	buf[0] = ADXL34X_CMD_WRITE;
+	buf[1] = reg;
+	buf[2] = val;
+
+	if (reg & REG_2B) {
+		buf[3] = val >> 8; /* high byte last */
+		txcnt++;
+	}
+
+	return spi_write_then_read(spi, &buf[0], txcnt, NULL, 0);
+}
+
+/*
+ * Speed path spi access function:
+ * Users call sequentially from the irq threaded/poll handler,
+ * no additional buffer locks required.
+ */
+
+static int adxl362_read_block(struct spi_device *spi,
+				  unsigned reg, unsigned count,
+				  void *buf)
+{
+	struct adxl362_state *ac = dev_get_drvdata(&spi->dev);
+	unsigned char *txbuf = buf;
+	int ret;
+
+	if (reg == ADXL_REG_FIFO) {
+		txbuf[0] = ADXL34X_CMD_FIFO;
+		ac->xfers[0].len = 1;
+	} else {
+		txbuf[0] = ADXL34X_CMD_READ;
+		txbuf[1] = reg;
+		ac->xfers[0].len = 2;
+	}
+
+	ac->xfers[0].tx_buf = buf;
+	ac->xfers[1].len = count;
+	ac->xfers[1].rx_buf = ac->data;
+
+	ret = spi_sync(spi, &ac->msg);
+	if (ret)
+		dev_err(&spi->dev, "block read failure");
+
+	return ret;
+}
+
+static int adxl362_fifo_watermark(struct adxl362_state *ac, unsigned odr_val)
+{
+	unsigned char val, tmp;
+	unsigned short samples;
+	int ret;
+
+	val = ac->watermarks_odr[odr_val];
+
+	if (!ac->irq) /* poll mode */
+		return val; /* return value to calculate delayed work */
+
+	samples = val * 3; /* 512 entry fifo counts in samples not sets */
+	tmp = ac->fifo_ctl;
+
+	if (samples > 255) /* MSB is stored in ADXL_REG_FIFO_CTL */
+		ac->fifo_ctl |= ADXL_FIFO_AH;
+	else
+		ac->fifo_ctl &= ~ADXL_FIFO_AH;
+
+	if (tmp != ac->fifo_ctl) { /* avoid surplus writes */
+		ret = adxl362_write(ac->spi, ADXL_REG_FIFO_CTL, ac->fifo_ctl);
+		if (ret < 0)
+			return ret;
+	}
+
+	return adxl362_write(ac->spi, ADXL_REG_FIFO_SAMPLES, samples & 0xFF);
+}
+
+static int adxl362_get_status(struct adxl362_state *ac,
+			      unsigned *stat, unsigned *samples)
+{
+	unsigned char *buf = (u8 *)ac->data;
+	int ret;
+
+	/* read status and fifo count in one shot to reduce SPI overhead */
+
+	ret = adxl362_read_block(ac->spi, ADXL_REG_STATUS, 3, buf);
+	if (ret < 0)
+		dev_err(&ac->spi->dev, "failed to query int stat\n");
+
+	*stat = buf[0];
+	*samples = get_unaligned_le16(&buf[1]); /* ADXL_REG_FIFO_ENTRIES_L */
+
+	return ret;
+}
+
+static void adxl362_service_ev_fifo(struct adxl362_state *ac, unsigned cnt)
+{
+	unsigned short fifo_val;
+	int i, ret;
+
+	ret = adxl362_read_block(ac->spi, ADXL_REG_FIFO, cnt * 2, ac->data);
+	if (ret < 0)
+		dev_err(&ac->spi->dev, "failed to read fifo\n");
+
+	for (i = 0; i < cnt; i++) {
+		fifo_val = le16_to_cpu(ac->data[i]);
+		input_report_abs(ac->input,
+				 ac->axis_event[FIFO_ITEM(fifo_val)].code,
+				 sign_extend32(fifo_val, 12) *
+				 ac->axis_event[FIFO_ITEM(fifo_val)].scale);
+		if (FIFO_ITEM(fifo_val) == Z_AXIS)
+			input_sync(ac->input);
+	}
+}
+
+static int adxl362_service(struct adxl362_state *ac)
+{
+	unsigned stat, samples;
+	int ret;
+
+	ret = adxl362_get_status(ac, &stat, &samples);
+	if (stat & ADXL_FIFO_OVERRUN)
+		dev_err(&ac->spi->dev, "FIFO_OVERRUN\n");
+
+	if (stat & ADXL_ERR_USER_REGS)
+		dev_err(&ac->spi->dev, "ERR_USER_REGS\n");
+
+	if (stat & (ADXL_FIFO_READY | ADXL_FIFO_WATERMARK))
+		adxl362_service_ev_fifo(ac, samples);
+
+	return ret;
+}
+
+static unsigned long adxl362_calc_poll_rate(unsigned short rate,
+					    unsigned short wmark)
+{
+	unsigned long delay = msecs_to_jiffies((1000 * wmark) / rate);
+
+	if (delay >= HZ)
+		delay = round_jiffies_relative(delay);
+
+	return delay;
+}
+
+static inline void adxl362_queue_work(struct adxl362_state *ac)
+{
+	queue_delayed_work(system_freezable_wq, &ac->work, ac->delay);
+}
+
+static void adxl362_work(struct work_struct *work)
+{
+	struct adxl362_state *ac =
+		container_of(work, struct adxl362_state, work.work);
+
+	adxl362_service(ac);
+	adxl362_queue_work(ac);
+}
+
+static irqreturn_t adxl362_irq(int irq, void *handle)
+{
+	struct adxl362_state *ac = handle;
+
+	adxl362_service(ac);
+
+	return IRQ_HANDLED;
+}
+
+static inline void __adxl362_disable(struct adxl362_state *ac)
+{
+	adxl362_write(ac->spi, ADXL_REG_POWER_CTL, 0);
+}
+
+static inline void __adxl362_enable(struct adxl362_state *ac)
+{
+	adxl362_write(ac->spi, ADXL_REG_POWER_CTL,
+		      ac->power_ctl | ADXL_MEASUREMENT_MODE);
+}
+
+static ssize_t adxl362_rate_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct adxl362_state *ac = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", ODR_TO_HZ(ADXL_ODR(ac->filter_ctl)));
+}
+
+static ssize_t adxl362_rate_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct adxl362_state *ac = dev_get_drvdata(dev);
+	unsigned short rate, wmark, odr_val;
+	int ret;
+
+	ret = kstrtou16(buf, 10, &rate);
+	if (ret)
+		return ret;
+
+	rate = clamp_t(u16, rate, 13, 400);
+
+	mutex_lock(&ac->mutex);
+
+	odr_val = ADXL_ODR(HZ_TO_ODR(rate));
+	ac->filter_ctl &= ~ADXL_ODR(~0);
+	ac->filter_ctl |= odr_val;
+
+	adxl362_write(ac->spi, ADXL_REG_FILTER_CTL, ac->filter_ctl);
+	wmark = adxl362_fifo_watermark(ac, odr_val);
+
+	/* update [in]activity timers with new rate */
+	adxl362_write(ac->spi, ADXL_REG_TIME_INACT_L,
+		CLAMP_INACT((ac->pdata->inactivity_time * rate) / 1000));
+	adxl362_write(ac->spi, ADXL_REG_TIME_ACT,
+		CLAMP_ACT((ac->pdata->activity_time * rate) / 1000));
+
+	if (!ac->irq) { /* poll mode */
+		ac->delay = adxl362_calc_poll_rate(rate, wmark);
+		if (ac->opened && !ac->suspended) {
+			cancel_delayed_work_sync(&ac->work);
+			adxl362_queue_work(ac);
+		}
+	}
+
+	mutex_unlock(&ac->mutex);
+
+	return count;
+}
+
+static DEVICE_ATTR(rate, 0664, adxl362_rate_show, adxl362_rate_store);
+
+static ssize_t adxl362_autosleep_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct adxl362_state *ac = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !!(ac->power_ctl & ADXL_AUTOSLEEP));
+}
+
+static ssize_t adxl362_autosleep_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct adxl362_state *ac = dev_get_drvdata(dev);
+	bool val;
+	int error;
+
+	error = strtobool(buf, &val);
+	if (error)
+		return error;
+
+	mutex_lock(&ac->mutex);
+
+	if (val)
+		ac->power_ctl |= ADXL_AUTOSLEEP;
+	else
+		ac->power_ctl &= ~ADXL_AUTOSLEEP;
+
+	adxl362_write(ac->spi, ADXL_REG_POWER_CTL, ac->power_ctl);
+
+	mutex_unlock(&ac->mutex);
+
+	return count;
+}
+
+static DEVICE_ATTR(autosleep, 0664,
+		   adxl362_autosleep_show, adxl362_autosleep_store);
+
+static struct attribute *adxl362_attributes[] = {
+	&dev_attr_rate.attr,
+	&dev_attr_autosleep.attr,
+	NULL
+};
+
+static const struct attribute_group adxl362_attr_group = {
+	.attrs = adxl362_attributes,
+};
+
+static int __devinit adxl362_setup(struct adxl362_state *ac)
+{
+	struct adxl362_platform_data *pdata = ac->pdata;
+	unsigned char scale = pdata->data_range / 2; /* scale div 1, 2, 4 */
+	unsigned odr_val;
+	int ret;
+
+	ac->axis_event[X_AXIS].code = abs(pdata->ev_code_x);
+	ac->axis_event[Y_AXIS].code = abs(pdata->ev_code_y);
+	ac->axis_event[Z_AXIS].code = abs(pdata->ev_code_z);
+	ac->axis_event[X_AXIS].scale = SIGN(pdata->ev_code_x) * scale;
+	ac->axis_event[Y_AXIS].scale = SIGN(pdata->ev_code_y) * scale;
+	ac->axis_event[Z_AXIS].scale = SIGN(pdata->ev_code_z) * scale;
+
+	odr_val = ADXL_ODR(HZ_TO_ODR(pdata->data_rate));
+	ac->power_ctl = ADXL_LOW_NOISE(pdata->low_power_mode);
+	ac->filter_ctl = ADXL_RANGE(pdata->data_range) |
+			 (pdata->half_bw ? ADXL_HALF_BW : 0) |
+			  odr_val;
+	ac->act_inact_ctl = ADXL_LINK_LOOP(ADXL_LOOP_MODE) |
+			(pdata->referenced_activity_en ? ADXL_ACT_REF : 0) |
+			(pdata->referenced_inactivity_en ? ADXL_INACT_REF : 0) |
+			ADXL_INACT_EN | ADXL_ACT_EN;
+
+	ac->fifo_ctl = ADXL_FIFO_MODE(ADXL_FIFO_MODE_STREAM);
+
+	ac->watermarks_odr[0] = CLAMP_WM(pdata->watermark_odr_12Hz);
+	ac->watermarks_odr[1] = CLAMP_WM(pdata->watermark_odr_25Hz);
+	ac->watermarks_odr[2] = CLAMP_WM(pdata->watermark_odr_50Hz);
+	ac->watermarks_odr[3] = CLAMP_WM(pdata->watermark_odr_100Hz);
+	ac->watermarks_odr[4] = CLAMP_WM(pdata->watermark_odr_200Hz);
+	ac->watermarks_odr[5] = CLAMP_WM(pdata->watermark_odr_400Hz);
+
+	ret = adxl362_write(ac->spi, ADXL_REG_THRESH_ACT_L,
+			    pdata->activity_threshold / scale);
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_write(ac->spi, ADXL_REG_TIME_ACT,
+			    CLAMP_ACT((pdata->activity_time *
+			    ODR_TO_HZ(odr_val)) / 1000));
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_write(ac->spi, ADXL_REG_THRESH_INACT_L,
+			    pdata->inactivity_threshold / scale);
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_write(ac->spi, ADXL_REG_TIME_INACT_L,
+			    CLAMP_INACT((pdata->inactivity_time *
+			    ODR_TO_HZ(odr_val)) / 1000));
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_write(ac->spi, ADXL_REG_ACT_INACT_CTL,
+			    ac->act_inact_ctl);
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_write(ac->spi, ADXL_REG_FIFO_CTL, ac->fifo_ctl);
+	if (ret < 0)
+		return ret;
+
+	ret = adxl362_fifo_watermark(ac, odr_val);
+	if (ret < 0)
+		return ret;
+
+	if (!ac->irq) { /* poll mode */
+		ac->delay = adxl362_calc_poll_rate(
+			ODR_TO_HZ(ADXL_ODR(ac->filter_ctl)), ret);
+	} else {
+		ret = adxl362_write(ac->spi, pdata->use_int2 ?
+			ADXL_REG_INTMAP2 : ADXL_REG_INTMAP1, ac->int_mask);
+		if (ret < 0)
+			return ret;
+	}
+
+	return adxl362_write(ac->spi, ADXL_REG_FILTER_CTL, ac->filter_ctl);
+}
+
+static int adxl362_input_open(struct input_dev *input)
+{
+	struct adxl362_state *ac = input_get_drvdata(input);
+
+	mutex_lock(&ac->mutex);
+
+	if (!ac->suspended)
+		__adxl362_enable(ac);
+
+	ac->opened = true;
+
+	if (!ac->irq) { /* poll mode */
+		adxl362_service(ac);
+		adxl362_queue_work(ac);
+	}
+
+	mutex_unlock(&ac->mutex);
+
+	return 0;
+}
+
+static void adxl362_input_close(struct input_dev *input)
+{
+	struct adxl362_state *ac = input_get_drvdata(input);
+
+	mutex_lock(&ac->mutex);
+
+	if (!ac->suspended)
+		__adxl362_disable(ac);
+
+	ac->opened = false;
+
+	if (!ac->irq) /* poll mode */
+		cancel_delayed_work_sync(&ac->work);
+
+	mutex_unlock(&ac->mutex);
+}
+
+static int __devinit adxl362_probe(struct spi_device *spi)
+{
+	struct adxl362_platform_data *pdata = spi->dev.platform_data;
+	struct input_dev *input_dev;
+	struct adxl362_state *ac;
+	struct device *dev = &spi->dev;
+	int ret, man_id, part_id, range;
+	unsigned long irqflags;
+
+	/* don't exceed max specified SPI CLK frequency */
+	if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
+		dev_err(dev, "SPI CLK %d Hz too fast\n", spi->max_speed_hz);
+		return -EINVAL;
+	}
+
+	ac = devm_kzalloc(dev, sizeof(*ac), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!ac || !input_dev) {
+		ret = -ENOMEM;
+		goto err_free_mem;
+	}
+
+	if (!pdata) {
+		dev_dbg(dev,
+			"No platform data: Using default initialization\n");
+		pdata = &adxl362_default_init;
+	}
+
+	spi_set_drvdata(spi, ac);
+	mutex_init(&ac->mutex);
+
+	ac->input = input_dev;
+	ac->pdata = pdata;
+	ac->spi = spi;
+	ac->irq = spi->irq;
+
+	/* query device presence */
+	man_id = adxl362_read(spi, ADXL_REG_DEVID_AD);
+	part_id = adxl362_read(spi, ADXL_REG_PARTID);
+
+	if (man_id != MAN_ID_AD || part_id != PART_ID_ADXL362) {
+		dev_err(dev, "Failed to probe (0x%X:0x%X)\n", man_id, part_id);
+		ret = -ENODEV;
+		goto err_free_mem;
+	}
+
+	/* now set to a known state */
+	adxl362_write(ac->spi, ADXL_REG_SOFT_RESET, 'R'); /* reset */
+
+	/* setup speed path default message */
+	spi_message_init(&ac->msg);
+	spi_message_add_tail(&ac->xfers[0], &ac->msg);
+	spi_message_add_tail(&ac->xfers[1], &ac->msg);
+	ac->xfers[0].bits_per_word = 8;
+	ac->xfers[1].bits_per_word = 8;
+
+	/* setup input device */
+	snprintf(ac->phys, sizeof(ac->phys), "%s/input0", dev_name(dev));
+	input_dev->name = "ADXL362 accelerometer";
+	input_dev->phys = ac->phys;
+	input_dev->dev.parent = dev;
+	input_dev->id.bustype = BUS_SPI;
+	input_dev->id.vendor = man_id;
+	input_dev->id.product = part_id;
+	input_dev->id.version = adxl362_read(spi, ADXL_REG_REVID);
+	input_dev->open = adxl362_input_open;
+	input_dev->close = adxl362_input_close;
+
+	input_set_drvdata(input_dev, ac);
+
+	__set_bit(EV_ABS, input_dev->evbit);
+
+	range = pdata->data_range * 1000; /* +/- 2000, 4000, 8000 mg */
+
+	input_set_abs_params(input_dev, ABS_X, -range, range,
+			     pdata->abs_fuzz, 0);
+	input_set_abs_params(input_dev, ABS_Y, -range, range,
+			     pdata->abs_fuzz, 0);
+	input_set_abs_params(input_dev, ABS_Z, -range, range,
+			     pdata->abs_fuzz, 0);
+
+	/* request irq or init polled mode if desired */
+	if (spi->irq) {
+		ac->int_mask = ADXL_INT_FIFO_WATERMARK_EN |
+			       ADXL_INT_FIFO_OVERRUN_EN;
+
+		if (pdata->irqflags)
+			irqflags = pdata->irqflags & IRQF_TRIGGER_MASK;
+		else
+			irqflags = IRQF_TRIGGER_HIGH;
+
+		if (irqflags & (IRQF_TRIGGER_LOW | IRQF_TRIGGER_FALLING))
+			ac->int_mask |= ADXL_INT_LOW_ACTIVE;
+
+		ret = request_threaded_irq(spi->irq, NULL, adxl362_irq,
+					irqflags | IRQF_ONESHOT,
+					dev_name(dev), ac);
+		if (ret) {
+			dev_err(dev, "irq %d busy?\n", spi->irq);
+			goto err_free_mem;
+		}
+	} else {
+		INIT_DELAYED_WORK(&ac->work, adxl362_work);
+	}
+
+	ret = sysfs_create_group(&dev->kobj, &adxl362_attr_group);
+	if (ret)
+		goto err_free_irq;
+
+	/* init hardware */
+	ret = adxl362_setup(ac);
+	if (ret)
+		goto err_remove_attr;
+
+	ret = input_register_device(input_dev);
+	if (ret)
+		goto err_remove_attr;
+
+	return 0;
+
+err_remove_attr:
+	sysfs_remove_group(&dev->kobj, &adxl362_attr_group);
+err_free_irq:
+	if (spi->irq)
+		free_irq(spi->irq, ac);
+err_free_mem:
+	input_free_device(input_dev);
+
+	return ret;
+}
+
+static int __devexit adxl362_remove(struct spi_device *spi)
+{
+	struct adxl362_state *ac = dev_get_drvdata(&spi->dev);
+
+	if (spi->irq)
+		free_irq(spi->irq, ac);
+	else
+		cancel_delayed_work_sync(&ac->work);
+
+	__adxl362_disable(ac);
+	sysfs_remove_group(&spi->dev.kobj, &adxl362_attr_group);
+	input_unregister_device(ac->input);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int adxl362_suspend(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct adxl362_state *ac = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ac->mutex);
+
+	if (!ac->suspended && ac->opened)
+		__adxl362_disable(ac);
+
+	ac->suspended = true;
+
+	mutex_unlock(&ac->mutex);
+
+	return 0;
+}
+
+static int adxl362_resume(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct adxl362_state *ac = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ac->mutex);
+
+	if (ac->suspended && ac->opened)
+		__adxl362_enable(ac);
+
+	ac->suspended = false;
+
+	mutex_unlock(&ac->mutex);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(adxl362_pm, adxl362_suspend,
+			 adxl362_resume);
+
+static struct spi_driver adxl362_driver = {
+	.driver = {
+		.name = "adxl362",
+		.owner = THIS_MODULE,
+		.pm = &adxl362_pm,
+	},
+	.probe   = adxl362_probe,
+	.remove  = __devexit_p(adxl362_remove),
+};
+
+module_spi_driver(adxl362_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("ADXL362 Three-Axis Digital Accelerometer");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/adxl362.h b/include/linux/input/adxl362.h
new file mode 100644
index 0000000..e77447a
--- /dev/null
+++ b/include/linux/input/adxl362.h
@@ -0,0 +1,125 @@
+/*
+ * include/linux/input/adxl362.h
+ *
+ * Digital Accelerometer characteristics are highly application specific
+ * and may vary between boards and models. The platform_data for the
+ * device's "struct device" holds this information.
+ *
+ * Copyright 2012 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __LINUX_INPUT_ADXL362_H__
+#define __LINUX_INPUT_ADXL362_H__
+
+enum adxl_odr {
+	ADXL_ODR_12_5HZ	= 13,
+	ADXL_ODR_25HZ	= 25,
+	ADXL_ODR_50HZ	= 50,
+	ADXL_ODR_100HZ	= 100,
+	ADXL_ODR_200HZ	= 200,
+	ADXL_ODR_400HZ	= 400,
+};
+
+enum adxl_g_range {
+	ADXL_RANGE_PM_2g = 2,
+	ADXL_RANGE_PM_4g = 4,
+	ADXL_RANGE_PM_8g = 8,
+};
+
+enum adxl_power_mode {
+	ADXL_NORM_OPERATION	= 0,
+	ADXL_LOW_NOISE_MODE	= 1,
+	ADXL_ULTRA_LOW_NOISE_MODE = 2,
+};
+
+struct adxl362_platform_data {
+	/*
+	 * data_range:
+	 * Measurement range selection +/- 2,4,8 g
+	 */
+	enum adxl_g_range data_range;
+
+	/*
+	 * low_power_mode:
+	 * Power versus noise tradeoff.
+	 */
+	enum adxl_power_mode low_power_mode;
+
+	/*
+	 * data_rate:
+	 * Selects the output data rate (ODR).
+	 */
+	enum adxl_odr data_rate;
+
+	/*
+	 * half_bw:
+	 * Sets the anti-aliasing filter to 1/4 of the output data rate (ODR)
+	 */
+	bool half_bw;
+
+	/*
+	 * watermark_odr:
+	 * The Watermark feature can be used to reduce the interrupt/poll load
+	 * of the system. The FIFO fills up to watermark value in sample sets
+	 * [1..170] and then generates an interrupt. Each ODR can have it's
+	 * own watermark.
+	 */
+	u8 watermark_odr_12Hz;
+	u8 watermark_odr_25Hz;
+	u8 watermark_odr_50Hz;
+	u8 watermark_odr_100Hz;
+	u8 watermark_odr_200Hz;
+	u8 watermark_odr_400Hz;
+
+	/*
+	 * When acceleration measurements are received from the ADXL362
+	 * events are sent to the input event subsystem. The following settings
+	 * select the event code for ABS x, y and z axis data
+	 * respectively. The event codes can also be negated to further account
+	 * for sensor orientation.
+	 */
+	s32 ev_code_x;	/* (+/-)ABS_X,Y,Z */
+	s32 ev_code_y;	/* (+/-)ABS_X,Y,Z */
+	s32 ev_code_z;	/* (+/-)ABS_X,Y,Z */
+	s32 abs_fuzz;	/* input fuzz val */
+
+	/*
+	 * [in]activity_threshold:
+	 * holds the threshold value for activity detection.
+	 * The data format is unsigned. The scale factor is
+	 * 1mg/LSB.
+	 */
+	u16 activity_threshold;
+	u16 inactivity_threshold;
+
+	/*
+	 * [in]activity_time:
+	 * is an unsigned time value representing the
+	 * amount of time that acceleration must be [below]/above the value in
+	 * [in]activity_threshold for [in]activity to be declared.
+	 * The scale factor is 1ms/LSB.
+	 */
+	u32 inactivity_time;
+	u32 activity_time;
+
+	/*
+	 * referenced_[in]activity_en:
+	 * Sets [in]activity detection to operate in referenced mode opposed to
+	 * absolute mode.
+	 */
+	bool referenced_activity_en;
+	bool referenced_inactivity_en;
+
+	/*
+	 * Use ADXL362 INT2 pin instead of INT1 pin for interrupt output
+	 */
+	bool use_int2;
+
+	/*
+	 * Optional IRQ flags
+	 */
+	unsigned irqflags;
+};
+#endif
-- 
1.7.0.4


--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux