linux-spi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
To: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
Cc: David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
Subject: Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
Date: Fri, 02 May 2008 10:13:29 +0100	[thread overview]
Message-ID: <481ADB39.40408@cam.ac.uk> (raw)
In-Reply-To: <481AD6E9.1090201-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

[-- Attachment #1: Type: text/plain, Size: 989 bytes --]

Jonathan Cameron wrote:
> Initial support for ST Microelectronics LIS3L02DQ accelerometer via SPI
> 
> Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> 
> ---
> Resend of patch. Hopefully my email client shouldn't mangle it this time.
> 
> This patch includes a number of changes suggested by David Brownell.
> Provides sysfs interface to set calibration parameters and read last value.
> Two modes available, first gives direct on demand access to chip, second
> utilizes a ring buffer and provides a rip_buffer sysfs element which gets
> the new buffer contents. This is intended for use by a userspace program
> that will check often enough that no data is lost.
> 
> The interrupt driver mode requires that the interrupt is from a gpioline
> due to the nature of the data_ready signal from the chip.
> 
> Requires patch to fix missing gpio_is_valid() definitions.
> (now in mainline since Monday)
> 

Sorry about this, please see attachment for patch.

[-- Attachment #2: lis3l02dq.patch --]
[-- Type: text/x-patch, Size: 28318 bytes --]

--- a/drivers/spi/Kconfig	2008-04-17 03:49:44.000000000 +0100
+++ b/drivers/spi/Kconfig	2008-05-01 18:12:04.000000000 +0100
@@ -221,6 +221,18 @@ config SPI_AT25
 	  This driver can also be built as a module.  If so, the module
 	  will be called at25.
 
+config SPI_LIS3L02DQ
+       tristate "STMicroelectronics LIS3L02DQ Accelerometer" 
+       depends on SPI_MASTER && SYSFS 
+       help    
+		SPI driver for the STMicroelectrincs LIS3L02DQ 3-Axis 2g digital 
+		output linear accelerometer.  This provides a sysfs interface.
+	 	Calibration parameters may be read and controlled (gain / offset).
+	 	Last 3d accel reading obtained via scan attribute and a ring buffer
+	 	interrupt mode enabled allowing high frequency data to be obtained
+	 	via rip_buffer attribute.	
+
+       
 config SPI_SPIDEV
 	tristate "User mode SPI device driver support"
 	depends on SPI_MASTER && EXPERIMENTAL
--- a/drivers/spi/Makefile	2008-04-17 03:49:44.000000000 +0100
+++ b/drivers/spi/Makefile	2008-05-01 11:55:56.000000000 +0100
@@ -32,8 +32,9 @@ obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.
 
 # SPI protocol drivers (device/link on bus)
 obj-$(CONFIG_SPI_AT25)		+= at25.o
+obj-$(CONFIG_SPI_LIS3L02DQ)     += lis3l02dq.o
 obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
 obj-$(CONFIG_SPI_TLE62X0)	+= tle62x0.o
 # 	... add above this line ...
 
 # SPI slave controller drivers (upstream link)
--- a/drivers/spi/lis3l02dq.h	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/spi/lis3l02dq.h	2008-05-01 18:30:20.000000000 +0100
@@ -0,0 +1,168 @@
+/*
+ * LISL02DQ.h -- support STMicroelectronics LISD02DQ
+ *               3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
+ *
+ * Loosely based upon tle62x0.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This driver has two modes, one is interrupt based, the other on demand
+ */
+#ifndef SPI_LIS3L02DQ_H_
+#define SPI_LIS3L02DQ_H_
+#define LIS3L02DQ_READ_REG(a) a | 0x80
+#define LIS3L02DQ_WRITE_REG(a) a
+
+/* Calibration parameters */
+#define LIS3L02DQ_REG_OFFSET_X_ADDRESS 0x16
+#define LIS3L02DQ_REG_OFFSET_Y_ADDRESS 0x17
+#define LIS3L02DQ_REG_OFFSET_Z_ADDRESS 0x18
+
+#define LIS3L02DQ_REG_GAIN_X_ADDRESS 0x19
+#define LIS3L02DQ_REG_GAIN_Y_ADDRESS 0x1A
+#define LIS3L02DQ_REG_GAIN_Z_ADDRESS 0x1B
+
+/* Control Register (1 of 2) */
+#define LIS3L02DQ_REG_CTRL_1_ADDRESS 0x20
+/* Power ctrl - either bit set corresponds to on*/
+#define LIS3L02DQ_REG_CTRL_1_PD_ON 0xC0
+
+/* Decimation Factor  */
+#define LIS3L02DQ_REG_CTRL_1_DF_128 0x00
+#define LIS3L02DQ_REG_CTRL_1_DF_64  0x10
+#define LIS3L02DQ_REG_CTRL_1_DF_32  0x20
+#define LIS3L02DQ_REG_CTRL_1_DF_8   0x10 | 0x20
+
+/* Self Test Enable */
+#define LIS3L02DQ_REG_CTRL_1_SELF_TEST_ON 0x08
+
+/* Axes enable ctrls */
+#define LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE  0x04
+#define LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE  0x02
+#define LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE  0x01
+
+/* Control Register (2 of 2) */
+#define LIS3L02DQ_REG_CTRL_2_ADDRESS 0x21
+
+/* Block Data Update only after MSB and LSB read */
+#define LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE 0x40
+
+/* Set to big endian output */
+#define LIS3L02DQ_REG_CTRL_2_BIG_ENDIAN   0x20
+
+/* Reboot memory content */
+#define LIS3L02DQ_REG_CTRL_2_REBOOT_MEMORY 0x10
+
+/* Interupt Enable - applies data ready to the RDY pad */
+#define LIS3L02DQ_REG_CTRL_2_ENABLE_INTERUPT 0x08
+
+/* Enable Data Ready Generation - relationship with previous unclear in docs */
+#define LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION 0x04
+
+/* SPI 3 wire mode */
+#define LIS3L02DQ_REG_CTRL_2_THREE_WIRE_SPI_MODE 0x02
+
+/* Data alignment, default is 12 bit right justified
+ * - option for 16 bit left justified */
+#define LIS3L02DQ_REG_CTRL_2_DATA_ALIGNMENT_16_BIT_LEFT_JUSTIFIED 0x01
+
+/* Interupt related stuff */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS 0x23
+
+/* Switch from or combination fo conditions to and */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_BOOLEAN_AND 0x80
+
+/* Latch interupt request,
+ * if on ack must be given by reading the ack register */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC 0x40
+
+/* Z Interupt on High (above threshold)*/
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH 0x20
+/* Z Interupt on Low */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW 0x10
+/* Y Interupt on High */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH 0x08
+/* Y Interupt on Low */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW 0x04
+/* X Interupt on Hight */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HGIH 0x02
+/* X Interupt on Low */
+#define LIS3L02DQ_REG_WAKT_UP_CFG_INTERRUPT_X_LOW 0x01
+
+/* Register that gives description of what caused interupt
+ * - latched if set in CFG_ADDRES */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS 0x24
+/* top bit ignored */
+/* Interupt Active */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_ACTIVATED 0x40
+/* Interupts that have been triggered */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH 0x20
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW 0x10
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH 0x08
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW 0x04
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH 0x02
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW 0x01
+
+#define LIS3L02DQ_REG_WAKE_UP_ACK_ADDRESS 0x25
+
+/* Status register */
+#define LIS3L02DQ_REG_STATUS_ADDRESS 0x27
+/* XYZ axis data overrun - first is all overrun? */
+#define LIS3L02DQ_REG_STATUS_XYZ_OVERRUN 0x80
+#define LIS3L02DQ_REG_STATUS_Z_OVERRUN 0x40
+#define LIS3L02DQ_REG_STATUS_Y_OVERRUN 0x20
+#define LIS3L02DQ_REG_STATUS_X_OVERRUN 0x10
+/* XYZ new data available - first is all 3 available? */
+#define LIS3L02DQ_REG_STATUS_XYZ_NEW_DATA 0x08
+#define LIS3L02DQ_REG_STATUS_Z_NEW_DATA 0x04
+#define LIS3L02DQ_REG_STATUS_Y_NEW_DATA 0x02
+#define LIS3L02DQ_REG_STATUS_X_NEW_DATA 0x01
+
+/* The accelerometer readings - low and high bytes.
+Form of high byte dependant on justification set in ctrl reg */
+#define LIS3L02DQ_REG_OUT_X_L_ADDRESS 0x28
+#define LIS3L02DQ_REG_OUT_X_H_ADDRESS 0x29
+#define LIS3L02DQ_REG_OUT_Y_L_ADDRESS 0x2A
+#define LIS3L02DQ_REG_OUT_Y_H_ADDRESS 0x2B
+#define LIS3L02DQ_REG_OUT_Z_L_ADDRESS 0x2C
+#define LIS3L02DQ_REG_OUT_Z_H_ADDRESS 0x2D
+
+/* Threshold values for all axes and both above and below thresholds
+ * - i.e. there is only one value */
+#define LIS3L02DQ_REG_THS_L_ADDRESS 0x2E
+#define LIS3L02DQ_REG_THS_H_ADDRESS 0x2D
+
+#define LIS3L02DQ_DEFAULT_CTRL1 (LIS3L02DQ_REG_CTRL_1_PD_ON	      \
+				 | LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE \
+				 | LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE \
+				 | LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE \
+				 | LIS3L02DQ_REG_CTRL_1_DF_128)
+
+#define LIS3L02DQ_DEFAULT_CTRL2			\
+	(LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE)
+#define LIS3L02DQ_DIRECT_ONLY_MODE -1
+#define LIS3L02DQ_DIRECT_MODE       0
+#define LIS3L02DQ_INTERRUPT_MODE    1
+
+#define LIS3L02DQ_BUFFER_LENGTH 100
+
+
+struct LIS3L02DQ_state {
+	struct spi_device      *us;
+	unsigned char          *tx_buff;
+	unsigned char          *rx_buff;
+	int                     mode;
+	/* interrupt / ring buffer related elements */
+	struct work_struct     work;
+	bool                   inter;
+	uint8_t                ring_buffer[LIS3L02DQ_BUFFER_LENGTH*6];
+	uint8_t                *read_pointer;
+	uint8_t                *write_pointer;
+	uint8_t                *last_written_pointer;
+
+};
+#endif /* SPI_LIS3L02DQ_H_ */
--- a/drivers/spi/lis3l02dq.c	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/spi/lis3l02dq.c	2008-05-01 18:24:48.000000000 +0100
@@ -0,0 +1,790 @@
+/*
+ * LISL02DQ.c -- support STMicroelectronics LISD02DQ
+ *               3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
+ *
+ * Loosely based upon tle62x0.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This driver has two modes - INTERRUPT_MODE is only available if
+ * a gpio interrupt has been specified in the spi config.
+ *
+ * In DIRECT_MODE all of the sysfs interfaces communicate directly
+ * with the chip.
+ *
+ * In INTERRUPT_MODE the data ready interrupt of the chip is turned
+ * on and an interrupt handler (via workqueue) is used to save each
+ * reading from the chip into a local ring buffer.
+ *
+ * Two sysfs interfaces behave differently depending on the mode:
+ *
+ * scan - In direct mode a read will cause the chip to be queried
+ * whilst in interrupt mode the last fully stored reading
+ * will be retrieved from the ring buffer.
+ *
+ * rip_buffer - Designed to allow a userspace program to pull a copy
+ * of the ring buffer (new elements). In direct mode any left over
+ * elements from the last use of interrupt mode will be output.
+ * At the moment there is no overflow labeling of the ring buffer,
+ * with the userspace program assumed to check sufficiently often that
+ * the buffer does not overflow.
+ *
+ * Additional fairly self explanatory sysfs attributes are
+ * gain_x, gain_y, gain_z, offset_x, offset_y, offset_z
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include "lis3l02dq.h"
+
+/* Equivalent of device_attribute with addition of address value */
+struct lis3l02dq_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct device *dev, struct attribute *attr,
+			char *buf);
+	ssize_t (*store)(struct device *dev, struct attribute *attr,
+			 const char *buf, size_t count);
+	uint8_t addr;
+};
+
+#define LIS3L02DQ_ATTR(_name, _mode, _show, _store, _address)		\
+	struct lis3l02dq_attribute lis3l02dq_attr_##_name = {		\
+		.attr = {.name = __stringify(_name), .mode = _mode },	\
+		.show	= _show,					\
+		.store	= _store,					\
+		.addr = _address,					\
+	}
+
+static const char read_all_tx_array[] =
+{
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_L_ADDRESS),
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_H_ADDRESS),
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_L_ADDRESS),
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_H_ADDRESS),
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_L_ADDRESS),
+	0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_H_ADDRESS),
+};
+
+static int LIS3L02DQ_read_all(struct LIS3L02DQ_state *st)
+{
+	/* Sadly the device appears to require deselection between
+	 * reading the different registers - hence the somewhat
+	 * convoluted nature of this transfer */
+	struct spi_transfer xfers[] = {
+		/* x low byte */
+		{
+			.tx_buf = read_all_tx_array,
+			.rx_buf = st->rx_buff,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 1,
+		},
+		/* x high byte */
+		{
+			.tx_buf = read_all_tx_array+2,
+			.rx_buf = st->rx_buff+2,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 1,
+		},
+		/* y low byte */
+		{
+			.tx_buf = read_all_tx_array+4,
+			.rx_buf = st->rx_buff+4,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 1,
+		},
+		/* y high byte */
+		{
+			.tx_buf = read_all_tx_array+6,
+			.rx_buf = st->rx_buff+6,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 1,
+		},
+		/* z low byte */
+		{
+			.tx_buf = read_all_tx_array+8,
+			.rx_buf = st->rx_buff+8,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 1,
+		},
+		/* z high byte */
+		{
+			.tx_buf = read_all_tx_array+10,
+			.rx_buf = st->rx_buff+10,
+			.bits_per_word = 16,
+			.len = 2,
+			.cs_change = 0,
+		},
+	};
+	struct spi_message msg;
+	int ret;
+
+	memset(st->rx_buff, 0, sizeof st->rx_buff);
+	/* After these are trasmitted, the rx_buff should have
+	 * values in alternate bytes */
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfers[0], &msg);
+	spi_message_add_tail(&xfers[2], &msg);
+	spi_message_add_tail(&xfers[4], &msg);
+	spi_message_add_tail(&xfers[1], &msg);
+	spi_message_add_tail(&xfers[3], &msg);
+	spi_message_add_tail(&xfers[5], &msg);
+	ret = spi_sync(st->us, &msg);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with get all accels");
+		goto err_ret;
+	}
+
+err_ret:
+	return ret;
+}
+
+/* A fairly inellegant way of ripping the contents of the
+ * ring buffer and ensuring only a valid set of readings are output */
+static ssize_t LIS3L02DQ_rip_buffer(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	int len = 0, elements, i, dead_offset = 0;
+	uint8_t data_dump[LIS3L02DQ_BUFFER_LENGTH*6];
+	uint8_t *initial_read_p, *initial_write_p,
+		*current_read_p, *end_read_p;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	uint16_t temp;
+
+	/* Get a consistent pair of read and write pointers */
+	initial_read_p = st->read_pointer;
+
+	/* Occurs if nothing has yet been placed in the ring buffer */
+	if (unlikely(initial_read_p == 0))
+		goto err;
+
+	initial_write_p = st->write_pointer;
+	while (initial_read_p != st->read_pointer
+	       || initial_write_p != st->write_pointer) {
+		initial_read_p = st->read_pointer;
+		initial_write_p = st->write_pointer;
+	}
+	if (initial_write_p > initial_read_p) {
+		elements = (initial_write_p - initial_read_p);
+		memcpy(data_dump, initial_read_p, elements);
+	} else {
+		elements = st->ring_buffer
+			+ LIS3L02DQ_BUFFER_LENGTH*6
+			- initial_read_p;
+
+		memcpy(data_dump, initial_read_p, elements);
+		memcpy(data_dump+elements,
+		       st->ring_buffer,
+		       initial_write_p - st->ring_buffer);
+		elements += initial_write_p - st->ring_buffer;
+	}
+
+	end_read_p = st->read_pointer;
+
+	if (initial_read_p <= end_read_p)
+		dead_offset = end_read_p - initial_read_p;
+	else
+		dead_offset = st->ring_buffer
+			+ LIS3L02DQ_BUFFER_LENGTH*6 - initial_read_p
+			+ end_read_p - st->ring_buffer;
+
+	/* Possible issue here is the readpointer may have changed.
+	 * It could in theory have passed the initial write pointer.*/
+	st->read_pointer = initial_write_p;
+
+	for (current_read_p = data_dump + dead_offset;
+	    current_read_p < data_dump + elements;
+	    current_read_p += 6) {
+		for (i = 0; i < 3; i++) {
+			temp = (((uint16_t)((current_read_p[2*i+1]))) << 8)
+				| (uint16_t)(current_read_p[2*i]);
+			len += sprintf(len+buf, "%d ", *((int16_t *)(&temp)));
+		}
+	}
+	len += sprintf(len+buf, "\n");
+
+	return len;
+
+err:
+	return 0;
+}
+
+
+
+/* If in interrupt triggered mode this sysfs function will output the latest
+ * finished element from the ringbuffer.
+ *
+ * If in direct access mode it will simply read the output registers of the
+ * device.
+ * Be aware that the device may be in blocking mode so results are a little
+ * unpredictable */
+
+static ssize_t LIS3L02DQ_scan(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	int i, len = 0;
+	uint16_t temp;
+	uint8_t *written_p;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+
+	if (st->mode != LIS3L02DQ_INTERRUPT_MODE) {
+		if (LIS3L02DQ_read_all(st) < 0)
+			goto err_ret;
+
+		for (i = 0; i < 3; i++) {
+			temp = (((uint16_t)((st->rx_buff[4*i+2]))) << 8)
+				| (uint16_t)(st->rx_buff[4*i]);
+			len += sprintf(len+buf, "%d ", *((int16_t *)(&temp)));
+		}
+	} else 	if (likely(st->last_written_pointer != 0)) {
+		written_p = st->last_written_pointer;
+		for (i = 0; i < 3; i++) {
+			temp = (((uint16_t)((written_p[2*i+1]))) << 8)
+				| (uint16_t)(written_p[2*i]);
+			len += sprintf(len+buf, "%d ", *((int16_t *)(&temp)));
+		}
+	}
+	len += sprintf(len+buf, "\n");
+
+	return len;
+err_ret:
+	return 0;
+}
+
+
+static int LIS3L02DQ_read_register_int8_t(struct device *dev,
+					  uint8_t reg_address,
+					  int8_t *val)
+{
+	int ret;
+	struct spi_message msg;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	struct spi_transfer xfer = {
+		.tx_buf = st->tx_buff,
+		.rx_buf = st->rx_buff,
+		.bits_per_word = 16,
+		.len = 2,
+	};
+
+	st->tx_buff[1] = LIS3L02DQ_READ_REG(reg_address);
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+	ret = spi_sync(st->us, &msg);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with get x offset");
+		goto err_ret;
+	}
+	*val = st->rx_buff[0];
+
+	return ret;
+err_ret:
+	return 0;
+}
+
+/*Returns into to allow full 0/255 range with error codes in negative range */
+static int LIS3L02DQ_read_register_uint8_t(struct device *dev,
+					       uint8_t reg_address)
+{
+	uint8_t val;
+	int8_t ret;
+	struct spi_message msg;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	struct spi_transfer xfer = {
+		.tx_buf = st->tx_buff,
+		.rx_buf = st->rx_buff,
+		.bits_per_word = 16,
+		.len = 2,
+	};
+
+	st->tx_buff[1] = LIS3L02DQ_READ_REG(reg_address);
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+	ret = spi_sync(st->us, &msg);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with get x offset");
+		goto err_ret;
+	}
+	val = st->rx_buff[0];
+
+	return val;
+err_ret:
+	return ret;
+}
+
+static int LIS3L02DQ_write_register_int8_t(struct device *dev,
+					   uint8_t reg_address,
+					   int value)
+{
+	struct spi_message msg;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	struct spi_transfer xfer = {
+		.tx_buf = st->tx_buff,
+		.rx_buf = NULL,
+		.bits_per_word = 16,
+		.len = 2,
+	};
+	int ret;
+
+	st->tx_buff[1] = LIS3L02DQ_WRITE_REG(reg_address);
+	st->tx_buff[0] = value;
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+	ret = spi_sync(st->us, &msg);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with writing 8 bit register");
+		goto err_ret;
+	}
+	return 0;
+err_ret:
+	return ret;
+}
+
+
+
+static ssize_t LIS3L02DQ_read_signed(struct device *dev,
+				     struct attribute *attr,
+				     char *buf)
+{
+	int len, ret;
+	int8_t val;
+	struct lis3l02dq_attribute *this_attr
+		= container_of(attr,
+			       struct lis3l02dq_attribute,
+			       attr);
+
+	ret = LIS3L02DQ_read_register_int8_t(dev,
+					     this_attr->addr,
+					     &val);
+	if (ret < 0)
+		goto err_ret;
+	len = sprintf(buf, "%d\n", val);
+
+	return len;
+
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_read_unsigned(struct device *dev,
+				       struct attribute *attr,
+				       char *buf)
+{
+	int val, len;
+	struct lis3l02dq_attribute *this_attr
+		= container_of(attr,
+			       struct lis3l02dq_attribute,
+			       attr);
+
+	val = LIS3L02DQ_read_register_uint8_t(dev,
+					      this_attr->addr);
+	if (val < 0) {
+		dev_err(dev, "problem reading x gain");
+		goto err_ret;
+	}
+
+	len = sprintf(buf, "%d\n", val);
+	return len;
+err_ret:
+	return val;
+}
+
+static ssize_t LIS3L02DQ_write_signed(struct device *dev,
+				      struct attribute *attr,
+				      const char *buf,
+				      size_t len)
+{
+	long val;
+	int ret;
+	struct lis3l02dq_attribute *this_attr
+		= container_of(attr,
+			       struct lis3l02dq_attribute,
+			       attr);
+
+	ret = strict_strtol(buf, 10, &val);
+	if (ret)
+		goto err_ret;
+
+	ret = LIS3L02DQ_write_register_int8_t(dev,
+					      this_attr->addr,
+					      val);
+	if (ret)
+		goto err_ret;
+
+	return len;
+
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_unsigned(struct device *dev,
+					struct attribute *attr,
+					const char *buf,
+					size_t len)
+{
+	int ret;
+	ulong val;
+	struct lis3l02dq_attribute *this_attr
+		= container_of(attr,
+			       struct lis3l02dq_attribute,
+			       attr);
+
+	val = strict_strtoul(buf, 10, &val);
+	ret = LIS3L02DQ_write_register_int8_t(dev,
+					      this_attr->addr,
+					      val);
+	if (ret)
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static int LIS3L02DQ_initial_setup(struct LIS3L02DQ_state *st)
+{
+	int ret;
+
+	memset(st->tx_buff, 0, sizeof st->tx_buff);
+	memset(st->rx_buff, 0, sizeof st->rx_buff);
+	st->us->mode = SPI_MODE_3;
+	spi_setup(st->us);
+
+	/* Write suitable defaults to ctrl1 */
+	ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+					      LIS3L02DQ_REG_CTRL_1_ADDRESS,
+					      LIS3L02DQ_DEFAULT_CTRL1);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with setup control register 1");
+		goto err_ret;
+	}
+	ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+					      LIS3L02DQ_REG_CTRL_2_ADDRESS,
+					      LIS3L02DQ_DEFAULT_CTRL2);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with setup control register 2");
+		goto err_ret;
+	}
+err_ret:
+	return ret;
+}
+
+static irqreturn_t interrupthandler(int irq, void *_state)
+{
+	struct LIS3L02DQ_state *st = _state;
+
+	disable_irq_nosync(irq);
+	schedule_work(&st->work);
+	st->inter = 1;
+	return IRQ_HANDLED;
+}
+
+static int LIS3L02DQ_set_mode(struct device *dev, int val)
+{
+	int ret;
+	uint8_t tmp;
+	const uint8_t addr_ctrl2 = LIS3L02DQ_REG_CTRL_2_ADDRESS;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+
+	if (st->mode == val
+	    || st->mode == LIS3L02DQ_DIRECT_ONLY_MODE)
+		return 0;
+
+	switch (val) {
+	case 0:
+		/* disable interrupt generation */
+		ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+						      addr_ctrl2,
+						      LIS3L02DQ_DEFAULT_CTRL2);
+		if (ret)	{
+			dev_err(&st->us->dev,
+				"problem with setup control register 2");
+			goto err_ret;
+		}
+
+		flush_scheduled_work();
+		free_irq(st->us->irq, st);
+		break;
+
+	case 1:
+		/* quick read to ensure that the interrupt line is low */
+		LIS3L02DQ_read_all(st);
+		ret = request_irq(st->us->irq,
+				  interrupthandler,
+				  IRQF_TRIGGER_RISING,
+				  "lis3l02dq",
+				  st);
+		if (ret < 0) {
+			dev_err(&st->us->dev, "Could not obtain irq ");
+			goto err_ret;
+		}
+		/* enable interrupt generation */
+	tmp =  LIS3L02DQ_DEFAULT_CTRL2
+			|  LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION;
+		ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+						      addr_ctrl2,
+						      tmp);
+		if (ret) {
+			dev_err(&st->us->dev,
+				"problem with setup control register 2");
+			goto err_ret;
+		}
+		break;
+	default:
+		ret = -EINVAL;
+		goto err_ret;
+		break;
+	};
+	st->mode = val;
+
+	return 0;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_read_mode(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	int len = 0;
+
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	len += sprintf(buf + len, "%d\n", st->mode);
+
+	return len;
+}
+
+static ssize_t LIS3L02DQ_write_mode(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf,
+				    size_t len)
+{
+	int ret;
+	long val;
+
+	ret = strict_strtol(buf, 10, &val);
+	if (ret < 0)
+		goto err_ret;
+	ret = LIS3L02DQ_set_mode(dev, val);
+	if (ret < 0)
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+
+
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO, LIS3L02DQ_read_mode,
+		   LIS3L02DQ_write_mode);
+
+static DEVICE_ATTR(scan, S_IRUGO,           LIS3L02DQ_scan,          NULL);
+
+static LIS3L02DQ_ATTR(x_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_signed,
+		      LIS3L02DQ_write_signed, LIS3L02DQ_REG_OFFSET_X_ADDRESS);
+
+static LIS3L02DQ_ATTR(y_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_signed,
+		      LIS3L02DQ_write_signed, LIS3L02DQ_REG_OFFSET_Y_ADDRESS);
+
+static LIS3L02DQ_ATTR(z_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_signed,
+		      LIS3L02DQ_write_signed, LIS3L02DQ_REG_OFFSET_Z_ADDRESS);
+
+static LIS3L02DQ_ATTR(x_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_unsigned,
+		      LIS3L02DQ_write_unsigned, LIS3L02DQ_REG_GAIN_X_ADDRESS);
+
+static LIS3L02DQ_ATTR(y_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_unsigned,
+		      LIS3L02DQ_write_unsigned, LIS3L02DQ_REG_GAIN_Y_ADDRESS);
+
+static LIS3L02DQ_ATTR(z_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_unsigned,
+		      LIS3L02DQ_write_unsigned, LIS3L02DQ_REG_GAIN_Z_ADDRESS);
+
+static DEVICE_ATTR(rip_buffer, S_IRUGO, LIS3L02DQ_rip_buffer, NULL);
+
+static void LIS3L02DQ_store_to_ring(struct LIS3L02DQ_state *st)
+{
+	int i;
+	bool initread = true;
+
+	/* First use of ring */
+	if (unlikely(st->write_pointer == 0)) {
+		st->write_pointer = st->ring_buffer;
+		initread = false;
+	}
+	/*probably unnecessary */
+	barrier();
+	/*save data */
+	for (i = 0; i < 3; i++) {
+		st->write_pointer[i*2] = st->rx_buff[i*4];
+		st->write_pointer[i*2+1] = st->rx_buff[i*4+2];
+	}
+	barrier();
+	st->last_written_pointer = st->write_pointer;
+	barrier();
+	st->write_pointer += 6;
+	if (unlikely(st->write_pointer
+		     == st->ring_buffer + 6*LIS3L02DQ_BUFFER_LENGTH))
+		st->write_pointer = st->ring_buffer;
+
+	if (unlikely(st->read_pointer == 0))
+		st->read_pointer = st->ring_buffer;
+	else if (st->write_pointer == st->read_pointer)	{
+		if (unlikely((st->read_pointer+6
+			      == st->ring_buffer + 6*LIS3L02DQ_BUFFER_LENGTH)))
+			st->read_pointer = st->ring_buffer;
+		else
+			st->read_pointer += 6;
+	}
+	return;
+}
+
+static void LIS3L02DQ_data_ready_work(struct work_struct *work_s)
+{
+	struct LIS3L02DQ_state *st
+		= container_of(work_s, struct LIS3L02DQ_state, work);
+
+	st->inter = 0;
+	if (LIS3L02DQ_read_all(st) >= 0)
+		LIS3L02DQ_store_to_ring(st);
+
+try_again:
+	while (gpio_get_value(irq_to_gpio(st->us->irq)))
+		if (LIS3L02DQ_read_all(st) >= 0)
+			LIS3L02DQ_store_to_ring(st);
+	/* If we are lucky gpio should not be set now
+	 * Try reenabling interrupt. */
+	enable_irq(st->us->irq);
+	/* verify that either the gpio has not risen or that
+	 * the interrupt handler caught it */
+	if (gpio_get_value(irq_to_gpio(st->us->irq)))
+		if (st->inter == 0) {
+			disable_irq_nosync(st->us->irq);
+			goto try_again;
+		}
+	return;
+}
+
+static struct attribute *lis3l02dq_attributes[] = {
+	&lis3l02dq_attr_x_offset.attr,
+	&lis3l02dq_attr_y_offset.attr,
+	&lis3l02dq_attr_z_offset.attr,
+	&lis3l02dq_attr_x_gain.attr,
+	&lis3l02dq_attr_y_gain.attr,
+	&lis3l02dq_attr_z_gain.attr,
+	&dev_attr_scan.attr,
+	&dev_attr_rip_buffer.attr,
+	&dev_attr_mode.attr,
+	NULL
+};
+
+static const struct attribute_group lis3l02dq_attribute_group = {
+	.attrs = lis3l02dq_attributes,
+};
+
+static int __devinit LIS3L02DQ_probe(struct spi_device *spi)
+{
+	struct LIS3L02DQ_state *st;
+	int ret;
+
+
+	st = kzalloc(sizeof(struct LIS3L02DQ_state), GFP_KERNEL);
+	st->tx_buff = kmalloc(12, GFP_KERNEL);
+	st->rx_buff = kmalloc(12, GFP_KERNEL);
+	st->us = spi;
+
+	ret = sysfs_create_group(&spi->dev.kobj,  &lis3l02dq_attribute_group);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to register sysfs hooks\n");
+		return -EINVAL;
+	}
+	spi_set_drvdata(spi, st);
+	/* establish whether interrupt mode possible */
+	if (spi->irq) {
+		if (gpio_is_valid(irq_to_gpio(spi->irq)) > 0) {
+			INIT_WORK(&st->work, LIS3L02DQ_data_ready_work);
+			st->inter = 0;
+		} else
+			st->mode = LIS3L02DQ_DIRECT_ONLY_MODE;
+	} else
+		st->mode = LIS3L02DQ_DIRECT_ONLY_MODE;
+
+	/* This setup enables data ready generation (amongst other things)*/
+	ret = LIS3L02DQ_initial_setup(st);
+
+	return ret;
+}
+
+static int LIS3L02DQ_remove(struct spi_device *spi)
+{
+	int ret;
+	struct LIS3L02DQ_state *st = spi_get_drvdata(spi);
+	kfree(st->tx_buff);
+	kfree(st->rx_buff);
+	/* stop the device */
+	ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+					      LIS3L02DQ_REG_CTRL_1_ADDRESS,
+					      0);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with turning device off: ctrl1");
+		goto err_ret;
+	}
+	ret = LIS3L02DQ_write_register_int8_t(&st->us->dev,
+					      LIS3L02DQ_REG_CTRL_2_ADDRESS,
+					      0);
+	if (ret) {
+		dev_err(&st->us->dev, "problem with turning device off: ctrl2");
+		goto err_ret;
+	}
+	sysfs_remove_group(&spi->dev.kobj, &lis3l02dq_attribute_group);
+
+	if (st->mode == LIS3L02DQ_INTERRUPT_MODE) {
+		flush_scheduled_work();
+		free_irq(st->us->irq, st);
+	}
+
+	kfree(st);
+	return 0;
+
+err_ret:
+	return ret;
+}
+
+static struct spi_driver LIS3L02DQ_driver = {
+	.driver = {
+		.name = "lis3l02dq",
+		.owner = THIS_MODULE,
+	},
+	.probe = LIS3L02DQ_probe,
+	.remove = __devexit_p(LIS3L02DQ_remove),
+};
+
+static __init int LIS3L02DQ_init(void)
+{
+	return spi_register_driver(&LIS3L02DQ_driver);
+}
+
+static __exit void LIS3L02DQ_exit(void)
+{
+	spi_unregister_driver(&LIS3L02DQ_driver);
+}
+
+module_init(LIS3L02DQ_init);
+module_exit(LIS3L02DQ_exit);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>");
+MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver");
+MODULE_LICENSE("GPL v2");

[-- Attachment #3: Type: text/plain, Size: 320 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by the 2008 JavaOne(SM) Conference 
Don't miss this year's exciting event. There's still time to save $100. 
Use priority code J8TL2D2. 
http://ad.doubleclick.net/clk;198757673;13503038;p?http://java.sun.com/javaone

[-- Attachment #4: Type: text/plain, Size: 210 bytes --]

_______________________________________________
spi-devel-general mailing list
spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/spi-devel-general

  parent reply	other threads:[~2008-05-02  9:13 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-04-25 17:11 [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI Jonathan Cameron
     [not found] ` <481210CC.9080702-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-04-25 18:45   ` Jonathan Cameron
     [not found]     ` <481226C4.9010806-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-04-28 14:05       ` Jonathan Cameron
     [not found]         ` <4815D9A6.4040306-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-04-30  1:32           ` David Brownell
2008-05-01  1:04           ` David Brownell
     [not found]             ` <200804301804.24924.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-05-01  2:18               ` David Brownell
2008-05-01 17:25               ` Jonathan Cameron
     [not found]                 ` <4819FD27.7070600-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-05-01 17:46                   ` Jonathan Cameron
     [not found]                     ` <481A01E2.8030309-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-05-02  8:48                       ` Jonathan Cameron
2008-05-02  8:55                       ` Jonathan Cameron
     [not found]                         ` <481AD6E9.1090201-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2008-05-02  9:13                           ` Jonathan Cameron [this message]
2008-05-01 18:48                   ` David Brownell

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=481ADB39.40408@cam.ac.uk \
    --to=jic23-kwpb1pkirijaa/9udqfwiw@public.gmane.org \
    --cc=david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org \
    --cc=spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).