linux-spi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
@ 2008-04-25 17:11 Jonathan Cameron
       [not found] ` <481210CC.9080702-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
  0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-04-25 17:11 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

from Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

Initial support for ST Microelectronics LISS3L02DQ accelerometer via SPI

Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

---

This is my first attempt at writing a driver, so I would appreciate 
any feedback / suggestions people may wish to offer.

 drivers/spi/Kconfig           |   15
 drivers/spi/LIS3L02DQ.c       |  597 ++++++++++++++++++++++++++++++++
 include/linux/spi/LIS3L02DQ.h |  140 +++++++
 3 files changed, 752 insertions(+)

--- a/include/linux/spi/LIS3L02DQ.h	1970-01-01 01:00:00.000000000 +0100
+++ b/include/linux/spi/LIS3L02DQ.h	2008-04-25 15:55:16.000000000 +0100
@@ -0,0 +1,140 @@
+ 
+
+#ifndef _LIS3L02DQ_H_
+#define _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
+
+
+/* SPI MODE */
+#define LIS3L02DQ_SPI_MODE SPI_MODE_3
+
+#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_ENABLE_DATA_READY_GENERATION \
+	| LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE
+
+struct LIS3L02DQ_platform_data
+{
+  unsigned data_ready_gpio;
+};
+#endif /* _LIS3L02DQ_H_ */ 
--- a/drivers/spi/Kconfig	2008-04-17 03:49:44.000000000 +0100
+++ b/drivers/spi/Kconfig	2008-04-24 19:16:32.000000000 +0100
@@ -239,6 +239,21 @@ config SPI_TLE62X0
 	  sysfs interface, with each line presented as a kind of GPIO
 	  exposing both switch control and diagnostic feedback.
 
+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.
+
+config SPI_LIS3L02DQ_GPIO_INTERRUPT
+       bool "Use data ready interrupt in conjunction with a ring buffer"
+       depends on SPI_LIS3L02DQ && GENERIC_GPIO
+       help
+	 Select this option if you want to capture the maximum possible 
+	 amount of data from you accelerometer.
+       
+       
 #
 # Add new SPI protocol masters in alphabetical order above this line
 #
--- a/drivers/spi/LIS3L02DQ.c	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/spi/LIS3L02DQ.c	2008-04-25 15:55:59.000000000 +0100
@@ -0,0 +1,597 @@
+/* 
+ * 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, one is interrupt based, the other on demand */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/LIS3L02DQ.h>
+
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+
+#define LIS3L02DQ_BUFFER_LENGTH 100
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+/* Driver state including the rx / tx buffers and interrupt work structs + ring buffer pointers */
+struct LIS3L02DQ_state {
+	struct spi_device*     us;
+	unsigned char          tx_buff[2*6];
+	unsigned char          rx_buff[2*6];
+	
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	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 /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+};
+
+static const char read_all_tx_array[12] =
+{
+	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 */
+	struct spi_transfer xfers[] = {
+		/* x low byte */
+		{
+			.tx_buf = read_all_tx_array,
+			.rx_buf = st->rx_buff,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* x high byte */
+		{
+			.tx_buf = read_all_tx_array+2,
+			.rx_buf = st->rx_buff+2,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* y low byte */
+		{
+			.tx_buf = read_all_tx_array+4,
+			.rx_buf = st->rx_buff+4,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* y high byte */
+		{
+			.tx_buf = read_all_tx_array+6,
+			.rx_buf = st->rx_buff+6,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* z low byte */
+		{
+			.tx_buf = read_all_tx_array+8,
+			.rx_buf = st->rx_buff+8,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* z high byte */
+		{
+			.tx_buf = read_all_tx_array+10,
+			.rx_buf = st->rx_buff+10,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+	};
+	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;
+}
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+
+/* 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); /* No of bytes to copy */
+		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;
+}
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+
+/* 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;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	if(likely(st->last_written_pointer !=0)) {
+		/* conditions in which this may go wrong ?*/
+		uint8_t* 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)));
+		}
+	}
+#else
+	LIS3L02DQ_read_all(st);
+	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)));
+	}
+#endif /* else CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+	len += sprintf(len+buf, "\n");
+	return len;
+}
+
+static int8_t LIS3L02DQ_read_register_int8_t(struct device *dev, uint8_t reg_address)
+{
+	int8_t val, 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;
+	/* Unfortunately the result can be negative so passing error back not a good idea */
+err_ret:
+	return 0;  
+}
+
+static int LIS3L02DQ_write_register_int8_t(struct device* dev, uint8_t reg_address, int8_t 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_x_offset(struct device *dev, struct device_attribute *attr, char *buf)
+{	
+	int val, len ;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_X_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_y_offset(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Y_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_z_offset(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Z_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_x_gain(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_X_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_y_gain(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Y_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_z_gain(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Z_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_write_x_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_X_ADDRESS, val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_y_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Y_ADDRESS, val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+
+}
+
+static ssize_t LIS3L02DQ_write_z_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Z_ADDRESS, val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_x_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_X_ADDRESS, val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_y_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Y_ADDRESS, val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_z_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Z_ADDRESS, 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;
+}
+
+/* put pointers to these in a table to simplify adding / removing them? */
+static DEVICE_ATTR(scan,       S_IRUGO,           LIS3L02DQ_scan,          NULL);
+static DEVICE_ATTR(x_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_offset, LIS3L02DQ_write_x_offset);
+static DEVICE_ATTR(y_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_offset, LIS3L02DQ_write_y_offset);
+static DEVICE_ATTR(z_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_offset, LIS3L02DQ_write_z_offset);
+static DEVICE_ATTR(x_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_gain,   LIS3L02DQ_write_x_gain);
+static DEVICE_ATTR(y_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_gain,   LIS3L02DQ_write_y_gain);
+static DEVICE_ATTR(z_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_gain,   LIS3L02DQ_write_z_gain);
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+static DEVICE_ATTR(rip_buffer, S_IRUGO,           LIS3L02DQ_rip_buffer,    NULL);
+#endif 
+static struct device_attribute *LIS3L02DQ_attrs[] = {
+	&dev_attr_scan,
+	&dev_attr_x_offset,
+	&dev_attr_y_offset,
+	&dev_attr_z_offset,
+	&dev_attr_x_gain,
+	&dev_attr_y_gain,
+	&dev_attr_z_gain,
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	&dev_attr_rip_buffer,
+#endif 
+};
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+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 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);
+	struct LIS3L02DQ_platform_data* pdata = st->us->dev.platform_data;
+	
+	LIS3L02DQ_read_all(st);
+	LIS3L02DQ_store_to_ring(st);
+	st->inter = 0;
+try_again:
+	while(gpio_get_value(pdata->data_ready_gpio)) {
+		LIS3L02DQ_read_all(st);
+		LIS3L02DQ_store_to_ring(st);
+	}
+	/* If we are lucky gpio should not be set now - try renabling interrupt */
+	enable_irq(gpio_to_irq(pdata->data_ready_gpio));
+	/* verify that either the gpio has not risen or that the interrupt handler caught it */
+	if(gpio_get_value(pdata->data_ready_gpio))
+		if( st->inter == 0 ) {
+			disable_irq_nosync(gpio_to_irq(pdata->data_ready_gpio));
+			goto try_again;
+		}	
+	return;
+}
+
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+static int __devinit LIS3L02DQ_probe(struct spi_device *spi)
+{
+	struct LIS3L02DQ_state* st;
+	struct LIS3L02DQ_platform_data* pdata;
+	int ret,i ;
+	
+	pdata = spi->dev.platform_data;
+	st = kzalloc(sizeof(struct LIS3L02DQ_state), GFP_KERNEL);
+	st->us = spi;
+	
+	if(pdata == NULL) {
+		dev_err(&spi->dev, "no device data specified\n");
+		return -EINVAL;
+	}
+	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
+		ret = device_create_file(&spi->dev, LIS3L02DQ_attrs[i]);
+	if (ret) {
+		dev_err(&spi->dev, "cannot create attribute\n");
+		goto err_status;
+	}
+	
+	spi_set_drvdata(spi, st);
+	
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	INIT_WORK(&st->work, LIS3L02DQ_data_ready_work);
+	st->inter = 0;
+	/* enable an interrupt on the data ready line */
+	ret = request_irq(gpio_to_irq(pdata->data_ready_gpio),interrupthandler, IRQF_DISABLED, 
+			  "LIS3L02DQ data ready", st);
+	set_irq_type(gpio_to_irq(pdata->data_ready_gpio), IRQT_RISING);
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */	
+	/* This setup enables data ready generation (amongst other things)*/
+	ret = LIS3L02DQ_initial_setup(st);
+	
+	return ret;	
+	
+err_status:
+	device_remove_file(&spi->dev, &dev_attr_scan);
+	device_remove_file(&spi->dev, &dev_attr_x_offset);
+	device_remove_file(&spi->dev, &dev_attr_y_offset);
+	device_remove_file(&spi->dev, &dev_attr_z_offset);
+	return ret;
+}
+
+static int LIS3L02DQ_remove(struct spi_device *spi)
+{
+	device_remove_file(&spi->dev, &dev_attr_scan);
+	device_remove_file(&spi->dev, &dev_attr_x_offset);
+	device_remove_file(&spi->dev, &dev_attr_y_offset);
+	device_remove_file(&spi->dev, &dev_attr_z_offset);
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	flush_scheduled_work();
+	free_irq(gpio_to_irq(((struct LIS3L02DQ_platform_data*)(spi->dev.platform_data))->data_ready_gpio),&spi->dev);
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+	return 0;
+}
+
+static struct spi_driver LIS3L02DQ_driver = {
+	.driver = {
+		.name = "LIS3L02DQ",
+		.owner = THIS_MODULE,
+	},
+	.probe = LIS3L02DQ_probe,
+	.remove = 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);
+	return;
+}
+
+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");



-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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>
  0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-04-25 18:45 UTC (permalink / raw)
  Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

Sorry all,

I was a bit over enthusiastic when posting this, there are quite a few 
bugs in the removal code.
I'll post a working version in a day or two.

Jonathan
> from Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
>
> Initial support for ST Microelectronics LISS3L02DQ accelerometer via SPI
>
> Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
>
> ---
>
> This is my first attempt at writing a driver, so I would appreciate 
> any feedback / suggestions people may wish to offer.
>
>  drivers/spi/Kconfig           |   15
>  drivers/spi/LIS3L02DQ.c       |  597 ++++++++++++++++++++++++++++++++
>  include/linux/spi/LIS3L02DQ.h |  140 +++++++
>  3 files changed, 752 insertions(+)
>
> --- a/include/linux/spi/LIS3L02DQ.h	1970-01-01 01:00:00.000000000 +0100
> +++ b/include/linux/spi/LIS3L02DQ.h	2008-04-25 15:55:16.000000000 +0100
> @@ -0,0 +1,140 @@
> + 
> +
> +#ifndef _LIS3L02DQ_H_
> +#define _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
> +
> +
> +/* SPI MODE */
> +#define LIS3L02DQ_SPI_MODE SPI_MODE_3
> +
> +#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_ENABLE_DATA_READY_GENERATION \
> +	| LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE
> +
> +struct LIS3L02DQ_platform_data
> +{
> +  unsigned data_ready_gpio;
> +};
> +#endif /* _LIS3L02DQ_H_ */ 
> --- a/drivers/spi/Kconfig	2008-04-17 03:49:44.000000000 +0100
> +++ b/drivers/spi/Kconfig	2008-04-24 19:16:32.000000000 +0100
> @@ -239,6 +239,21 @@ config SPI_TLE62X0
>  	  sysfs interface, with each line presented as a kind of GPIO
>  	  exposing both switch control and diagnostic feedback.
>  
> +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.
> +
> +config SPI_LIS3L02DQ_GPIO_INTERRUPT
> +       bool "Use data ready interrupt in conjunction with a ring buffer"
> +       depends on SPI_LIS3L02DQ && GENERIC_GPIO
> +       help
> +	 Select this option if you want to capture the maximum possible 
> +	 amount of data from you accelerometer.
> +       
> +       
>  #
>  # Add new SPI protocol masters in alphabetical order above this line
>  #
> --- a/drivers/spi/LIS3L02DQ.c	1970-01-01 01:00:00.000000000 +0100
> +++ b/drivers/spi/LIS3L02DQ.c	2008-04-25 15:55:59.000000000 +0100
> @@ -0,0 +1,597 @@
> +/* 
> + * 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, one is interrupt based, the other on demand */
> +
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/LIS3L02DQ.h>
> +
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/gpio.h>
> +#include <linux/workqueue.h>
> +
> +#define LIS3L02DQ_BUFFER_LENGTH 100
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +
> +/* Driver state including the rx / tx buffers and interrupt work structs + ring buffer pointers */
> +struct LIS3L02DQ_state {
> +	struct spi_device*     us;
> +	unsigned char          tx_buff[2*6];
> +	unsigned char          rx_buff[2*6];
> +	
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	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 /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +};
> +
> +static const char read_all_tx_array[12] =
> +{
> +	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 */
> +	struct spi_transfer xfers[] = {
> +		/* x low byte */
> +		{
> +			.tx_buf = read_all_tx_array,
> +			.rx_buf = st->rx_buff,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* x high byte */
> +		{
> +			.tx_buf = read_all_tx_array+2,
> +			.rx_buf = st->rx_buff+2,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* y low byte */
> +		{
> +			.tx_buf = read_all_tx_array+4,
> +			.rx_buf = st->rx_buff+4,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* y high byte */
> +		{
> +			.tx_buf = read_all_tx_array+6,
> +			.rx_buf = st->rx_buff+6,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* z low byte */
> +		{
> +			.tx_buf = read_all_tx_array+8,
> +			.rx_buf = st->rx_buff+8,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* z high byte */
> +		{
> +			.tx_buf = read_all_tx_array+10,
> +			.rx_buf = st->rx_buff+10,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +	};
> +	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;
> +}
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +
> +/* 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); /* No of bytes to copy */
> +		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;
> +}
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +
> +
> +/* 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;
> +	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	if(likely(st->last_written_pointer !=0)) {
> +		/* conditions in which this may go wrong ?*/
> +		uint8_t* 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)));
> +		}
> +	}
> +#else
> +	LIS3L02DQ_read_all(st);
> +	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)));
> +	}
> +#endif /* else CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +	len += sprintf(len+buf, "\n");
> +	return len;
> +}
> +
> +static int8_t LIS3L02DQ_read_register_int8_t(struct device *dev, uint8_t reg_address)
> +{
> +	int8_t val, 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;
> +	/* Unfortunately the result can be negative so passing error back not a good idea */
> +err_ret:
> +	return 0;  
> +}
> +
> +static int LIS3L02DQ_write_register_int8_t(struct device* dev, uint8_t reg_address, int8_t 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_x_offset(struct device *dev, struct device_attribute *attr, char *buf)
> +{	
> +	int val, len ;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_X_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_read_y_offset(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	int val, len;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Y_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_read_z_offset(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	int val, len;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Z_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_read_x_gain(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	int val, len;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_X_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_read_y_gain(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	int val, len;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Y_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_read_z_gain(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	int val, len;
> +	val = LIS3L02DQ_read_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Z_ADDRESS);
> +	len = sprintf(buf, "%d\n", val);
> +	return len;
> +}
> +
> +static ssize_t LIS3L02DQ_write_x_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_X_ADDRESS, val);
> +	if( ret )
> +		goto err_ret;
> +	return len;
> +err_ret:
> +	return ret;
> +}
> +
> +static ssize_t LIS3L02DQ_write_y_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Y_ADDRESS, val);
> +	if( ret )
> +		goto err_ret;
> +	return len;
> +err_ret:
> +	return ret;
> +
> +}
> +
> +static ssize_t LIS3L02DQ_write_z_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_OFFSET_Z_ADDRESS, val);
> +	if( ret )
> +		goto err_ret;
> +	return len;
> +err_ret:
> +	return ret;
> +}
> +
> +static ssize_t LIS3L02DQ_write_x_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_X_ADDRESS, val);
> +	if( ret )
> +		goto err_ret;
> +	return len;
> +err_ret:
> +	return ret;
> +}
> +
> +static ssize_t LIS3L02DQ_write_y_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Y_ADDRESS, val);
> +	if( ret )
> +		goto err_ret;
> +	return len;
> +err_ret:
> +	return ret;
> +}
> +
> +static ssize_t LIS3L02DQ_write_z_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
> +{
> +	int ret,val;
> +	char *endp;
> +	val = simple_strtol(buf, &endp, 0);
> +	ret = LIS3L02DQ_write_register_int8_t(dev, LIS3L02DQ_REG_GAIN_Z_ADDRESS, 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;
> +}
> +
> +/* put pointers to these in a table to simplify adding / removing them? */
> +static DEVICE_ATTR(scan,       S_IRUGO,           LIS3L02DQ_scan,          NULL);
> +static DEVICE_ATTR(x_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_offset, LIS3L02DQ_write_x_offset);
> +static DEVICE_ATTR(y_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_offset, LIS3L02DQ_write_y_offset);
> +static DEVICE_ATTR(z_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_offset, LIS3L02DQ_write_z_offset);
> +static DEVICE_ATTR(x_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_gain,   LIS3L02DQ_write_x_gain);
> +static DEVICE_ATTR(y_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_gain,   LIS3L02DQ_write_y_gain);
> +static DEVICE_ATTR(z_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_gain,   LIS3L02DQ_write_z_gain);
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +static DEVICE_ATTR(rip_buffer, S_IRUGO,           LIS3L02DQ_rip_buffer,    NULL);
> +#endif 
> +static struct device_attribute *LIS3L02DQ_attrs[] = {
> +	&dev_attr_scan,
> +	&dev_attr_x_offset,
> +	&dev_attr_y_offset,
> +	&dev_attr_z_offset,
> +	&dev_attr_x_gain,
> +	&dev_attr_y_gain,
> +	&dev_attr_z_gain,
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	&dev_attr_rip_buffer,
> +#endif 
> +};
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +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 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);
> +	struct LIS3L02DQ_platform_data* pdata = st->us->dev.platform_data;
> +	
> +	LIS3L02DQ_read_all(st);
> +	LIS3L02DQ_store_to_ring(st);
> +	st->inter = 0;
> +try_again:
> +	while(gpio_get_value(pdata->data_ready_gpio)) {
> +		LIS3L02DQ_read_all(st);
> +		LIS3L02DQ_store_to_ring(st);
> +	}
> +	/* If we are lucky gpio should not be set now - try renabling interrupt */
> +	enable_irq(gpio_to_irq(pdata->data_ready_gpio));
> +	/* verify that either the gpio has not risen or that the interrupt handler caught it */
> +	if(gpio_get_value(pdata->data_ready_gpio))
> +		if( st->inter == 0 ) {
> +			disable_irq_nosync(gpio_to_irq(pdata->data_ready_gpio));
> +			goto try_again;
> +		}	
> +	return;
> +}
> +
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +
> +static int __devinit LIS3L02DQ_probe(struct spi_device *spi)
> +{
> +	struct LIS3L02DQ_state* st;
> +	struct LIS3L02DQ_platform_data* pdata;
> +	int ret,i ;
> +	
> +	pdata = spi->dev.platform_data;
> +	st = kzalloc(sizeof(struct LIS3L02DQ_state), GFP_KERNEL);
> +	st->us = spi;
> +	
> +	if(pdata == NULL) {
> +		dev_err(&spi->dev, "no device data specified\n");
> +		return -EINVAL;
> +	}
> +	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
> +		ret = device_create_file(&spi->dev, LIS3L02DQ_attrs[i]);
> +	if (ret) {
> +		dev_err(&spi->dev, "cannot create attribute\n");
> +		goto err_status;
> +	}
> +	
> +	spi_set_drvdata(spi, st);
> +	
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	INIT_WORK(&st->work, LIS3L02DQ_data_ready_work);
> +	st->inter = 0;
> +	/* enable an interrupt on the data ready line */
> +	ret = request_irq(gpio_to_irq(pdata->data_ready_gpio),interrupthandler, IRQF_DISABLED, 
> +			  "LIS3L02DQ data ready", st);
> +	set_irq_type(gpio_to_irq(pdata->data_ready_gpio), IRQT_RISING);
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */	
> +	/* This setup enables data ready generation (amongst other things)*/
> +	ret = LIS3L02DQ_initial_setup(st);
> +	
> +	return ret;	
> +	
> +err_status:
> +	device_remove_file(&spi->dev, &dev_attr_scan);
> +	device_remove_file(&spi->dev, &dev_attr_x_offset);
> +	device_remove_file(&spi->dev, &dev_attr_y_offset);
> +	device_remove_file(&spi->dev, &dev_attr_z_offset);
> +	return ret;
> +}
> +
> +static int LIS3L02DQ_remove(struct spi_device *spi)
> +{
> +	device_remove_file(&spi->dev, &dev_attr_scan);
> +	device_remove_file(&spi->dev, &dev_attr_x_offset);
> +	device_remove_file(&spi->dev, &dev_attr_y_offset);
> +	device_remove_file(&spi->dev, &dev_attr_z_offset);
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	flush_scheduled_work();
> +	free_irq(gpio_to_irq(((struct LIS3L02DQ_platform_data*)(spi->dev.platform_data))->data_ready_gpio),&spi->dev);
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +	return 0;
> +}
> +
> +static struct spi_driver LIS3L02DQ_driver = {
> +	.driver = {
> +		.name = "LIS3L02DQ",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = LIS3L02DQ_probe,
> +	.remove = 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);
> +	return;
> +}
> +
> +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");
>
>
>
> -------------------------------------------------------------------------
> 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
> _______________________________________________
> spi-devel-general mailing list
> spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
> https://lists.sourceforge.net/lists/listinfo/spi-devel-general
>
>   


-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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>
  0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-04-28 14:05 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

from Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

Initial support for ST Microelectronics LISS3L02DQ accelerometer via SPI

Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

---

This is a corrected version of the patch I posted the other day.  
All comments / feedback welcomed. Would it be preferable to submit this
as two patches, the first containing the basic functionality for directly
accessing the device and second patching against that to add ring buffer /
interrupt support?

 drivers/spi/Kconfig           |   14
 drivers/spi/LIS3L02DQ.c       |  774 ++++++++++++++++++++++++++++++++
 drivers/spi/Makefile          |    1
 include/linux/spi/LIS3L02DQ.h |  168 ++++++
 4 files changed, 957 insertions(+)


--- a/drivers/spi/Kconfig	2008-04-17 03:49:44.000000000 +0100
+++ b/drivers/spi/Kconfig	2008-04-24 19:16:32.000000000 +0100
@@ -239,6 +239,20 @@ config SPI_TLE62X0
 	  sysfs interface, with each line presented as a kind of GPIO
 	  exposing both switch control and diagnostic feedback.
 
+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.
+
+config SPI_LIS3L02DQ_GPIO_INTERRUPT
+       bool "Use data ready interrupt in conjunction with a ring buffer"
+       depends on SPI_LIS3L02DQ && GENERIC_GPIO
+       help
+	 Select this option if you want to capture the maximum possible 
+	 amount of data from you accelerometer.
+       
 #
 # Add new SPI protocol masters in alphabetical order above this line
 #
--- a/drivers/spi/Makefile	2008-04-17 03:49:44.000000000 +0100
+++ b/drivers/spi/Makefile	2008-04-25 19:13:51.000000000 +0100
@@ -34,6 +34,7 @@ obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.
 obj-$(CONFIG_SPI_AT25)		+= at25.o
 obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
 obj-$(CONFIG_SPI_TLE62X0)	+= tle62x0.o
+obj-$(CONFIG_SPI_LIS3L02DQ)     += LIS3L02DQ.o
 # 	... add above this line ...
 
 # SPI slave controller drivers (upstream link)
--- a/include/linux/spi/LIS3L02DQ.h	1970-01-01 01:00:00.000000000 +0100
+++ b/include/linux/spi/LIS3L02DQ.h	2008-04-26 12:21:25.000000000 +0100
@@ -0,0 +1,168 @@
+ 
+
+#ifndef _LIS3L02DQ_H_
+#define _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
+
+
+/* SPI MODE */
+#define LIS3L02DQ_SPI_MODE SPI_MODE_3
+
+#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_ENABLE_DATA_READY_GENERATION \
+	| LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE
+
+struct LIS3L02DQ_platform_data
+{
+  unsigned data_ready_gpio;
+};
+
+#define LIS3L02DQ_BUFFER_LENGTH 100
+
+
+struct LIS3L02DQ_state {
+	struct spi_device*     us;
+	unsigned char          tx_buff[2*6];
+	unsigned char          rx_buff[2*6];
+	
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	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 /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+};
+
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+
+#endif /* _LIS3L02DQ_H_ */ 
--- a/drivers/spi/LIS3L02DQ.c	1970-01-01 01:00:00.000000000 +0100
+++ b/drivers/spi/LIS3L02DQ.c	2008-04-28 14:52:31.000000000 +0100
@@ -0,0 +1,774 @@
+/* 
+ * 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, one is interrupt based, the other on demand */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/LIS3L02DQ.h>
+
+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,
+		},
+		/* x high byte */
+		{
+			.tx_buf = read_all_tx_array+2,
+			.rx_buf = st->rx_buff+2,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* y low byte */
+		{
+			.tx_buf = read_all_tx_array+4,
+			.rx_buf = st->rx_buff+4,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* y high byte */
+		{
+			.tx_buf = read_all_tx_array+6,
+			.rx_buf = st->rx_buff+6,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* z low byte */
+		{
+			.tx_buf = read_all_tx_array+8,
+			.rx_buf = st->rx_buff+8,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+		/* z high byte */
+		{
+			.tx_buf = read_all_tx_array+10,
+			.rx_buf = st->rx_buff+10,
+			.bits_per_word = 16,
+			.len = 2,
+		},
+	};
+	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;
+}
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+/* 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;
+}
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+
+/* 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 */
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+static ssize_t LIS3L02DQ_scan(struct device *dev, 
+			      struct device_attribute *attr, 
+			      char *buf)
+{
+	int i,len = 0;
+	uint16_t temp;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	if(likely(st->last_written_pointer !=0)) {
+		/* conditions in which this may go wrong ?*/
+		uint8_t* 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;
+}
+
+#else 
+
+static ssize_t LIS3L02DQ_scan(struct device *dev, 
+			      struct device_attribute *attr, 
+			      char *buf)
+{
+	int i,len = 0;
+	uint16_t temp;
+	struct LIS3L02DQ_state *st = dev_get_drvdata(dev);
+	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)));
+	}
+
+	len += sprintf(len+buf, "\n");
+	return len;
+err_ret:
+	return 0;
+
+}
+#endif /* else CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+static int8_t LIS3L02DQ_read_register_int8_t(struct device *dev, 
+					     uint8_t reg_address)
+{
+	int8_t val, 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;
+	/* Unfortunately the result can be negative so passing error 
+	 * back not a good idea */
+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;
+	/* Unfortunately the result can be negative so passing error 
+	 * back not a good idea */
+err_ret:
+	return ret;  
+}
+
+static int LIS3L02DQ_write_register_int8_t(struct device* dev, 
+					   uint8_t reg_address, 
+					   int8_t 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 int LIS3L02DQ_write_register_uint8_t(struct device* dev, 
+					    uint8_t reg_address, 
+					    uint8_t 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_x_offset(struct device *dev, 
+				       struct device_attribute *attr, 
+				       char *buf)
+{	
+	int val, len ;
+	val = LIS3L02DQ_read_register_int8_t(dev, 
+					     LIS3L02DQ_REG_OFFSET_X_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_y_offset(struct device *dev, 
+				       struct device_attribute *attr, 
+				       char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, 
+					     LIS3L02DQ_REG_OFFSET_Y_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_z_offset(struct device *dev, 
+				       struct device_attribute *attr, 
+				       char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_int8_t(dev, 
+					     LIS3L02DQ_REG_OFFSET_Z_ADDRESS);
+	len = sprintf(buf, "%d\n", val);
+	return len;
+}
+
+static ssize_t LIS3L02DQ_read_x_gain(struct device *dev, 
+				     struct device_attribute *attr, 
+				     char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_uint8_t(dev, 
+					     LIS3L02DQ_REG_GAIN_X_ADDRESS);
+	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_read_y_gain(struct device *dev, 
+				     struct device_attribute *attr, 
+				     char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_uint8_t(dev, 
+					     LIS3L02DQ_REG_GAIN_Y_ADDRESS);
+	if(val < 0) {
+		dev_err(dev, "problem reading y gain");
+		goto err_ret;
+		}
+	len = sprintf(buf, "%d\n", val);
+	return len;
+err_ret:
+	return val;
+}
+
+static ssize_t LIS3L02DQ_read_z_gain(struct device *dev, 
+				     struct device_attribute *attr, 
+				     char *buf)
+{
+	int val, len;
+	val = LIS3L02DQ_read_register_uint8_t(dev, 
+					     LIS3L02DQ_REG_GAIN_Z_ADDRESS);
+	if(val < 0) {
+		dev_err(dev, "problem reading y gain");
+		goto err_ret;
+	}
+	len = sprintf(buf, "%d\n", val);
+	return len;
+err_ret:
+	return val;
+}
+
+static ssize_t LIS3L02DQ_write_x_offset(struct device *dev, 
+					struct device_attribute *attr, 
+					const char *buf, 
+					size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, 
+					      LIS3L02DQ_REG_OFFSET_X_ADDRESS, 
+					      val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_y_offset(struct device *dev, 
+					struct device_attribute *attr, 
+					const char *buf, 
+					size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, 
+					      LIS3L02DQ_REG_OFFSET_Y_ADDRESS, 
+					      val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+
+}
+
+static ssize_t LIS3L02DQ_write_z_offset(struct device *dev, 
+					struct device_attribute *attr, 
+					const char *buf, 
+					size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_int8_t(dev, 
+					      LIS3L02DQ_REG_OFFSET_Z_ADDRESS, 
+					      val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_x_gain(struct device *dev, 
+				      struct device_attribute *attr, 
+				      const char *buf, 
+				      size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_uint8_t(dev, 
+					      LIS3L02DQ_REG_GAIN_X_ADDRESS, 
+					      val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_y_gain(struct device *dev, 
+				      struct device_attribute *attr, 
+				      const char *buf, 
+				      size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_uint8_t(dev, 
+					       LIS3L02DQ_REG_GAIN_Y_ADDRESS, 
+					       val);
+	if( ret )
+		goto err_ret;
+	return len;
+err_ret:
+	return ret;
+}
+
+static ssize_t LIS3L02DQ_write_z_gain(struct device *dev, 
+				      struct device_attribute *attr, 
+				      const char *buf, 
+				      size_t len)
+{
+	int ret,val;
+	char *endp;
+	val = simple_strtol(buf, &endp, 0);
+	ret = LIS3L02DQ_write_register_uint8_t(dev, 
+					       LIS3L02DQ_REG_GAIN_Z_ADDRESS, 
+					       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;
+}
+
+/* put pointers to these in a table to simplify adding / removing them? */
+static DEVICE_ATTR(scan,       S_IRUGO,           LIS3L02DQ_scan,          NULL);
+static DEVICE_ATTR(x_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_offset, LIS3L02DQ_write_x_offset);
+static DEVICE_ATTR(y_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_offset, LIS3L02DQ_write_y_offset);
+static DEVICE_ATTR(z_offset,   S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_offset, LIS3L02DQ_write_z_offset);
+static DEVICE_ATTR(x_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_x_gain,   LIS3L02DQ_write_x_gain);
+static DEVICE_ATTR(y_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_y_gain,   LIS3L02DQ_write_y_gain);
+static DEVICE_ATTR(z_gain,     S_IWUSR | S_IRUGO, LIS3L02DQ_read_z_gain,   LIS3L02DQ_write_z_gain);
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+static DEVICE_ATTR(rip_buffer, S_IRUGO,           LIS3L02DQ_rip_buffer,    NULL);
+#endif 
+static struct device_attribute *LIS3L02DQ_attrs[] = {
+	&dev_attr_scan,
+	&dev_attr_x_offset,
+	&dev_attr_y_offset,
+	&dev_attr_z_offset,
+	&dev_attr_x_gain,
+	&dev_attr_y_gain,
+	&dev_attr_z_gain,
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	&dev_attr_rip_buffer,
+#endif 
+};
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+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 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);
+	struct LIS3L02DQ_platform_data* pdata = st->us->dev.platform_data;
+	
+	st->inter = 0;
+	if( LIS3L02DQ_read_all(st) >= 0)
+		LIS3L02DQ_store_to_ring(st);
+	
+try_again:
+	while(gpio_get_value(pdata->data_ready_gpio)) 
+		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(gpio_to_irq(pdata->data_ready_gpio));
+	/* verify that either the gpio has not risen or that 
+	 * the interrupt handler caught it */
+	if(gpio_get_value(pdata->data_ready_gpio))
+		if( st->inter == 0 ) {
+			disable_irq_nosync(gpio_to_irq(pdata
+						       ->data_ready_gpio));
+			goto try_again;
+		}	
+	return;
+}
+
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+
+static int __devinit LIS3L02DQ_probe(struct spi_device *spi)
+{
+	struct LIS3L02DQ_state* st;
+	struct LIS3L02DQ_platform_data* pdata;
+	int ret,i ;
+	
+	pdata = spi->dev.platform_data;
+	st = kzalloc(sizeof(struct LIS3L02DQ_state), GFP_KERNEL);
+	st->us = spi;
+	
+	if(pdata == NULL) {
+		dev_err(&spi->dev, "no device data specified\n");
+		return -EINVAL;
+	}
+	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++) {
+		ret = device_create_file(&spi->dev, LIS3L02DQ_attrs[i]);
+		if (ret) {
+			dev_err(&spi->dev, "cannot create attribute\n");
+			goto err_status;
+		}
+	}
+	
+	spi_set_drvdata(spi, st);
+	
+
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	INIT_WORK(&st->work, LIS3L02DQ_data_ready_work);
+	st->inter = 0;
+	/* enable an interrupt on the data ready line */
+	ret = request_irq(gpio_to_irq(pdata->data_ready_gpio),
+			  interrupthandler, 
+			  IRQF_DISABLED, 
+			  "LIS3L02DQ data ready", 
+			  st);
+	
+	set_irq_type(gpio_to_irq(pdata->data_ready_gpio), IRQT_RISING);
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */	
+	/* This setup enables data ready generation (amongst other things)*/
+	ret = LIS3L02DQ_initial_setup(st);
+	
+	return ret;	
+	
+err_status:
+	
+	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
+		device_remove_file(&spi->dev, LIS3L02DQ_attrs[i]);
+	return ret;
+}
+
+static int LIS3L02DQ_remove(struct spi_device *spi)
+{
+	int i,ret;
+	struct LIS3L02DQ_state* st = spi_get_drvdata(spi);
+	struct LIS3L02DQ_platform_data* pdata = spi->dev.platform_data;
+	/* 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;
+	}
+	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
+		device_remove_file(&spi->dev, LIS3L02DQ_attrs[i]);
+#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
+	flush_scheduled_work();
+	free_irq(gpio_to_irq(pdata->data_ready_gpio),st);
+#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
+	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);
+	return;
+}
+
+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");


-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [not found]         ` <4815D9A6.4040306-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
@ 2008-04-30  1:32           ` David Brownell
  2008-05-01  1:04           ` David Brownell
  1 sibling, 0 replies; 12+ messages in thread
From: David Brownell @ 2008-04-30  1:32 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On Monday 28 April 2008, Jonathan Cameron wrote:
> 
> from Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> 
> Initial support for ST Microelectronics LISS3L02DQ accelerometer via SPI

Extra "S" there I think.  :)


> Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> 
> ---
> 
> This is a corrected version of the patch I posted the other day.  
> All comments / feedback welcomed. Would it be preferable to submit this
> as two patches, the first containing the basic functionality for directly
> accessing the device and second patching against that to add ring buffer /
> interrupt support?

One patch is fine, I think.


>  drivers/spi/Kconfig           |   14
>  drivers/spi/LIS3L02DQ.c       |  774 ++++++++++++++++++++++++++++++++
>  drivers/spi/Makefile          |    1
>  include/linux/spi/LIS3L02DQ.h |  168 ++++++
>  4 files changed, 957 insertions(+)

First technical feedback:  names in *lower case* please!  Hold
off on a new patch until I look at the code a bit.

-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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>
  1 sibling, 1 reply; 12+ messages in thread
From: David Brownell @ 2008-05-01  1:04 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On Monday 28 April 2008, Jonathan Cameron wrote:
> --- a/drivers/spi/Kconfig	2008-04-17 03:49:44.000000000 +0100
> +++ b/drivers/spi/Kconfig	2008-04-24 19:16:32.000000000 +0100
> @@ -239,6 +239,20 @@ config SPI_TLE62X0
>  	  sysfs interface, with each line presented as a kind of GPIO
>  	  exposing both switch control and diagnostic feedback.
>  
> +config SPI_LIS3L02DQ
> +       tristate "STMicroelectronics LIS3L02DQ Accelerometer" 

Accelerometer ... should this be a hwmon driver, or at least
build on some hwmon conventions?  I don't recall ever seeing
an accelerometer driver before.  After this is cleaned up a
bit more, I suggest you post this to LKML in case some folk
who have thought about how to talk to accelerometers have a
few insights to share with you.  (Shorten $SUBJECT to get
past our short attention spans too.  :)


Remove all whitespace at line-end from the stuff you add in your
patches ... that "tristate" line has that problem, as do numerous
others.  You should run "scripts/checkpatch.pl" on this patch,
and fix the ~280 little style problems (!) it tells you about.
That'll save some noise ... in fact, I'm going to avoid telling
you about things checkpatch.pl will tell you, this time around.

That LIS3L02DQ seems to be marked as obsolete on the ST site;
how close is this to the still-supported LIS3LV02DQ?


> +       depends on SPI_MASTER && SYSFS 
> +       help
> +         SPI driver for the STMicroelectrincs LIS3L02DQ 3-Axis 2g digital 
> +	 output linear accelerometer.  This provides a sysfs interface.

Documentation summarizing that interface would probably be
a Good Thing.  Here, you might summarize what functionality
it exposes to userspace.


> +
> +config SPI_LIS3L02DQ_GPIO_INTERRUPT
> +       bool "Use data ready interrupt in conjunction with a ring buffer"
> +       depends on SPI_LIS3L02DQ && GENERIC_GPIO

Looks to me like you can just #include <linux/gpio.h> and
all the other stuff you might need, an remove that config
variable ... then have your code test whether

	* gpio_is_valid(data_ready_gpio) to see if you should
	  try to use the IRQ ... you should be able to enable
	  driver support, but have it be disabled on the fly
	  if the target board doesn't provide a GPIO

	* gpio_to_irq(data_ready_gpio) is non-negative ... if not,
	  don't register the IRQ handler or expect to enable
	  that support

(I may be assuming some stuff that works best on 2.6.26-rc1;
be aware of that if you start doing these updates.)

Notice that both of those tests turn into compile-time constants
if GENERIC_GPIO isn't available.  That means you can rely on that
to eliminate big chunks of your code without all the #ifdeffery
you now have.

General policy:  don't have #ifdefs in the body of code.


> +       help
> +	 Select this option if you want to capture the maximum possible 
> +	 amount of data from you accelerometer.

Looks like your have some non-TAB indents mixed in there ...


> +       
>  #
>  # Add new SPI protocol masters in alphabetical order above this line
>  #
> --- a/drivers/spi/Makefile	2008-04-17 03:49:44.000000000 +0100
> +++ b/drivers/spi/Makefile	2008-04-25 19:13:51.000000000 +0100
> @@ -34,6 +34,7 @@ obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.
>  obj-$(CONFIG_SPI_AT25)		+= at25.o
>  obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
>  obj-$(CONFIG_SPI_TLE62X0)	+= tle62x0.o
> +obj-$(CONFIG_SPI_LIS3L02DQ)     += LIS3L02DQ.o

Repeating:  don't use uppercase in source file names.  Or
in the driver name, either!  And keep the Kconfig and Makefile
symbols in alphabetical order too, please...


>  # 	... add above this line ...
>  
>  # SPI slave controller drivers (upstream link)
> --- a/include/linux/spi/LIS3L02DQ.h	1970-01-01 01:00:00.000000000 +0100
> +++ b/include/linux/spi/LIS3L02DQ.h	2008-04-26 12:21:25.000000000 +0100
> @@ -0,0 +1,168 @@
> 	...

Almost all the definitions in that header belong elsewhere,
like register symbols in a drivers/spi/lis3l02dq.h header.
The reason to have something in a <linux/...> header is to
share it with code outside the driver.


> +/* SPI MODE */
> +#define LIS3L02DQ_SPI_MODE SPI_MODE_3

That hardly needs to be abstracted like that, does it?


> +struct LIS3L02DQ_platform_data
> +{
> +  unsigned data_ready_gpio;
> +};

At a quick glance, this is the only thing in this file that
seems like it *should* go into a <linux/spi/...> header.

Everthing else is driver-internal stuff.


> +
> +#define LIS3L02DQ_BUFFER_LENGTH 100
> +
> +
> +struct LIS3L02DQ_state {
> +	struct spi_device*     us;
> +	unsigned char          tx_buff[2*6];
> +	unsigned char          rx_buff[2*6];

I know I've been guilty of that idiom in the past, but
it's best to avoid it.  Instead of allocating DMA
buffers like that, just kmalloc() them separately.

(If you were using a pure PIO driver it wouldn't matter.
But with DMA, on systems without cache-coherent main
memory -- e.g. on most non-x86 systems! -- this can make
for data corruption problems.)


> +	
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	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 /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +};

That's very much driver-internal.  :)

> +
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/gpio.h>
> +#include <linux/workqueue.h>
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */

Avoid #ifdeffing #includes; they can mask compilation problems
that show up only on particular platforms.  Likewise, avoid
code that's #ifdeffed ... if you make it so the compiler's dead
code removal does its job, you will make sure that most of the
driver gets compiled every time, and reduce opportunities for
configuration specific compilation problems to appear.


> +
> +
> +#endif /* _LIS3L02DQ_H_ */ 
> --- a/drivers/spi/LIS3L02DQ.c	1970-01-01 01:00:00.000000000 +0100
> +++ b/drivers/spi/LIS3L02DQ.c	2008-04-28 14:52:31.000000000 +0100
> @@ -0,0 +1,774 @@
> +/* 
> + * 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, one is interrupt based, the other on demand */

I strongly prefer to see the terminal "*/" on a line by itself.
Otherwise I need a double (or triple) take before I can find it
on the end of some previous line.  ;)

Some more explanation of the "two modes" would be useful...


> +
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/LIS3L02DQ.h>
> +
> +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 */

But you don't set the .cs_change flag, so it's staying
selected ... what looks odd to me is that you *could* do
this all in one shot, with spi_dev.bits_per_word == 16,
while instead you're using a much higher overhead scheme
with multiple transfers...


> +	struct spi_transfer xfers[] = {
> +		/* x low byte */
> +		{
> +			.tx_buf = read_all_tx_array,
> +			.rx_buf = st->rx_buff,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* x high byte */
> +		{
> +			.tx_buf = read_all_tx_array+2,
> +			.rx_buf = st->rx_buff+2,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* y low byte */
> +		{
> +			.tx_buf = read_all_tx_array+4,
> +			.rx_buf = st->rx_buff+4,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* y high byte */
> +		{
> +			.tx_buf = read_all_tx_array+6,
> +			.rx_buf = st->rx_buff+6,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* z low byte */
> +		{
> +			.tx_buf = read_all_tx_array+8,
> +			.rx_buf = st->rx_buff+8,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +		/* z high byte */
> +		{
> +			.tx_buf = read_all_tx_array+10,
> +			.rx_buf = st->rx_buff+10,
> +			.bits_per_word = 16,
> +			.len = 2,
> +		},
> +	};
> +	struct spi_message msg;
> +	int ret;
> +	memset(st->rx_buff, 0, sizeof st->rx_buff);

Have a blank line between local variables and code...

> +	/* 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;
> +}
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +/* 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)

I'd have to read the chip spec to see what this is doing.

Failing that, some description of how the data shows up
in sysfs would be a good thing.  :)



> +/* 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 */

Then you should probably have a sysfs attribute exposing which
mode the device is in ... and probably letting the userspace
code change to the mode it wants.


> +static int8_t LIS3L02DQ_read_register_int8_t(struct device *dev, 
> +					     uint8_t reg_address)
> +{
> +	int8_t val, 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;
> +	/* Unfortunately the result can be negative so passing error 
> +	 * back not a good idea */

If the result is signed, then you should have some alternate calling
convention...


> +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)

> +static int LIS3L02DQ_write_register_int8_t(struct device* dev, 
> +					   uint8_t reg_address, 
> +					   int8_t value)

... I think you only need one function to write an 8 bit value,
not separate ones for signed and unsigned values.  Just pass
the value as "int", and the 8 bits get stuffed into a buffer ...



> +static int LIS3L02DQ_write_register_uint8_t(struct device* dev, 
> +					    uint8_t reg_address, 
> +					    uint8_t value)



> +static ssize_t LIS3L02DQ_read_x_gain(struct device *dev, 
> +				     struct device_attribute *attr, 
> +				     char *buf)


> +static ssize_t LIS3L02DQ_read_y_gain(struct device *dev, 
> +				     struct device_attribute *attr, 
> +				     char *buf)


> +static ssize_t LIS3L02DQ_read_z_gain(struct device *dev, 
> +				     struct device_attribute *attr, 
> +				     char *buf)

You can save some code here by wrapping your attributes in a
struct that holds the relevant "read register" commands.  The
output code is the same ... the only difference is that command.

So my_attribute = container_of(attr, ...) and read that command
from that field.  You will eliminate at least two copies of each
read routine.  (One works for signed values, one unsigned.)

The same should be true on the write side too.

The minor cost:  you can't use the DEVICE_ATTR macros to declare
things; you'd use __ATTR() and friends directly.  No prob ... and
a lot less needless code duplication!



> +static struct device_attribute *LIS3L02DQ_attrs[] = {
> +	&dev_attr_scan,
> +	&dev_attr_x_offset,
> +	&dev_attr_y_offset,
> +	&dev_attr_z_offset,
> +	&dev_attr_x_gain,
> +	&dev_attr_y_gain,
> +	&dev_attr_z_gain,
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	&dev_attr_rip_buffer,
> +#endif 
> +};

Then what you should do is use an attribute_group here,
which lets you register (then later, unregister) all the
attributes in one call ... with no funky partial cleanup
error recovery needed.


> +static int __devinit LIS3L02DQ_probe(struct spi_device *spi)
> +{
> +	struct LIS3L02DQ_state* st;
> +	struct LIS3L02DQ_platform_data* pdata;
> +	int ret,i ;
> +	
> +	pdata = spi->dev.platform_data;
> +	st = kzalloc(sizeof(struct LIS3L02DQ_state), GFP_KERNEL);
> +	st->us = spi;
> +	
> +	if(pdata == NULL) {
> +		dev_err(&spi->dev, "no device data specified\n");
> +		return -EINVAL;
> +	}
> +	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++) {
> +		ret = device_create_file(&spi->dev, LIS3L02DQ_attrs[i]);
> +		if (ret) {
> +			dev_err(&spi->dev, "cannot create attribute\n");
> +			goto err_status;
> +		}
> +	}

That could become just a simple sysfs_create_group() call..
.
> +	
> +	spi_set_drvdata(spi, st);
> +	
> +
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	INIT_WORK(&st->work, LIS3L02DQ_data_ready_work);
> +	st->inter = 0;
> +	/* enable an interrupt on the data ready line */
> +	ret = request_irq(gpio_to_irq(pdata->data_ready_gpio),
> +			  interrupthandler, 
> +			  IRQF_DISABLED, 
> +			  "LIS3L02DQ data ready", 
> +			  st);
> +	
> +	set_irq_type(gpio_to_irq(pdata->data_ready_gpio), IRQT_RISING);

Instead of calling set_irq_type, pass the right IRQF_* flag
into request_irq().  And as a general policy, please avoid
using IRQ labels with spaces in them.  Plain "lis3l02dq" is
just fine here (i.e. driver->name).


> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */	
> +	/* This setup enables data ready generation (amongst other things)*/
> +	ret = LIS3L02DQ_initial_setup(st);
> +	
> +	return ret;	
> +	
> +err_status:
> +	
> +	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
> +		device_remove_file(&spi->dev, LIS3L02DQ_attrs[i]);
> +	return ret;
> +}
> +
> +static int LIS3L02DQ_remove(struct spi_device *spi)
> +{
> +	int i,ret;
> +	struct LIS3L02DQ_state* st = spi_get_drvdata(spi);
> +	struct LIS3L02DQ_platform_data* pdata = spi->dev.platform_data;
> +	/* stop the device */

Conventionally, there should be a blank line after all local
variable declarations and before any code (including comments).

I've omitted that comment in a *LOT* of places it should apply...


> +	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;
> +	}
> +	for(i = 0; i < ARRAY_SIZE(LIS3L02DQ_attrs); i++)
> +		device_remove_file(&spi->dev, LIS3L02DQ_attrs[i]);
> +#ifdef CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT
> +	flush_scheduled_work();
> +	free_irq(gpio_to_irq(pdata->data_ready_gpio),st);
> +#endif /* CONFIG_SPI_LIS3L02DQ_GPIO_INTERRUPT */
> +	kfree(st);
> +	return 0;
> +
> +err_ret:
> +	return ret;
> +
> +}
> +
> +static struct spi_driver LIS3L02DQ_driver = {
> +	.driver = {
> +		.name = "LIS3L02DQ",

Lowercase ...

> +		.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);
> +	return;

Strike that needless return.


> +}
> +
> +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");
> 

-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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
  1 sibling, 0 replies; 12+ messages in thread
From: David Brownell @ 2008-05-01  2:18 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Jonathan Cameron

On Wednesday 30 April 2008, David Brownell wrote:
>         * gpio_to_irq(data_ready_gpio) is non-negative ... if not,
>           don't register the IRQ handler or expect to enable
>           that support

For that matter, you don't actually need to package that in
platform_data.  Just check spi_device.irq as provided in the
device setup logic.  Your code doesn't seem to care if that
signal is a GPIO or not...

- Dave


-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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>
  1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-05-01 17:25 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f; +Cc: David Brownell


> Accelerometer ... should this be a hwmon driver, or at least
> build on some hwmon conventions? 
I did wonder that myself. Guess your suggestion of posting to lkml
should get me a few opinions.
> That LIS3L02DQ seems to be marked as obsolete on the ST site;
> how close is this to the still-supported LIS3LV02DQ?
>   
Based on the data sheet, I think this driver should work with a subset 
of the functionality
of that chip, but without one to actually test against I'd rather not 
commit to it doing so.

The reason I'm interested in the older chip is that it's what crossbow 
(xbow.com) have
used on their imote2 sensor board, which means there are quite a few out 
there
+ importantly a reasonable number of linux users.

I'm actually interested in writing quite a few similar drivers for other 
inertial sensors,
but as I had to start somewhere I went with this one.
> (I may be assuming some stuff that works best on 2.6.26-rc1;
> be aware of that if you start doing these updates.)
>   
Indeed, I ran into gpio_is_valid not being defined for the pxa arch. The 
relevant
is in the main tree by 2.6.25-git17 (I haven't tracked down exactly when 
it was fixed).
> Notice that both of those tests turn into compile-time constants
> if GENERIC_GPIO isn't available.  That means you can rely on that
> to eliminate big chunks of your code without all the #ifdeffery
> you now have.
>
> General policy:  don't have #ifdefs in the body of code.
>   
The new patch does exactly that.
>> +struct LIS3L02DQ_platform_data
>> +{
>> +  unsigned data_ready_gpio;
>> +};
>>     
>
> At a quick glance, this is the only thing in this file that
> seems like it *should* go into a <linux/spi/...> header.
>
> Everthing else is driver-internal stuff.
>
>   
I've removed the necessity for any platform data (via you spi->irq 
suggestion)
However, I do require that the irq is a gpio. This is because the chip 
raises and holds
high the data ready line until a read has occurred. Thus it is possible 
for the interrupt
bottom half to be delayed past the next data_ready signal. This doesn't 
happen often,
but it will freeze the driver it does. Hence, the need to verify that 
the line is either
low immediately after re enabling interrupts or that the interrupt 
handler caught it.

Thus I'm using irq_to_gpio to get the gpio number to check.
>> +
>> +#define LIS3L02DQ_BUFFER_LENGTH 100
>> +
>> +
>> +struct LIS3L02DQ_state {
>> +	struct spi_device*     us;
>> +	unsigned char          tx_buff[2*6];
>> +	unsigned char          rx_buff[2*6];
>>     
>
> I know I've been guilty of that idiom in the past, but
> it's best to avoid it.  Instead of allocating DMA
> buffers like that, just kmalloc() them separately.
>
> (If you were using a pure PIO driver it wouldn't matter.
> But with DMA, on systems without cache-coherent main
> memory -- e.g. on most non-x86 systems! -- this can make
> for data corruption problems.)
>   
I'm afraid I don't fully understand this (not having much familiarity 
with dma),
is this fixed by the patch? (which will follow - basically allocates 
tx_buff and rx_buff
dynamically)
>   
>> +static ssize_t LIS3L02DQ_read_z_gain(struct device *dev, 
>> +				     struct device_attribute *attr, 
>> +				     char *buf)
>>     
>
> You can save some code here by wrapping your attributes in a
> struct that holds the relevant "read register" commands.  The
> output code is the same ... the only difference is that command.
>
> So my_attribute = container_of(attr, ...) and read that command
> from that field.  You will eliminate at least two copies of each
> read routine.  (One works for signed values, one unsigned.)
>
> The same should be true on the write side too.
>
> The minor cost:  you can't use the DEVICE_ATTR macros to declare
> things; you'd use __ATTR() and friends directly.  No prob ... and
> a lot less needless code duplication!
>   
I've had a go at this, but didn't end up with terribly elegant code.
Any suggestions how how to clean it up would be welcome.

I have implemented all your other suggestions.

Patch to follow shortly

Thanks again for your help,

--

Jonathan Cameron


-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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-01 18:48                   ` David Brownell
  1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-05-01 17:46 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f; +Cc: David Brownell

http://lkml.org/lkml/2008/4/26/47from Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

Initial support for ST Microelectronics LIS3L02DQ accelerometer via SPI

Signed-off-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

---

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)

--- 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,5 +221,17 @@ 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");



-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [not found]                 ` <4819FD27.7070600-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
  2008-05-01 17:46                   ` Jonathan Cameron
@ 2008-05-01 18:48                   ` David Brownell
  1 sibling, 0 replies; 12+ messages in thread
From: David Brownell @ 2008-05-01 18:48 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

By the way, your last patch seems to have badly mangled by
some mail software:  I see no tabs anywhere... could you
resend?


On Thursday 01 May 2008, Jonathan Cameron wrote:

> >> +struct LIS3L02DQ_state {
> >> +	struct spi_device*     us;
> >> +	unsigned char          tx_buff[2*6];
> >> +	unsigned char          rx_buff[2*6];
> >>     
> >
> > I know I've been guilty of that idiom in the past, but
> > it's best to avoid it.  Instead of allocating DMA
> > buffers like that, just kmalloc() them separately.
> >
> > (If you were using a pure PIO driver it wouldn't matter.
> > But with DMA, on systems without cache-coherent main
> > memory -- e.g. on most non-x86 systems! -- this can make
> > for data corruption problems.)
> >   
> I'm afraid I don't fully understand this (not having much familiarity 
> with dma), is this fixed by the patch? (which will follow - basically
> allocates tx_buff and rx_buff dynamically)

It should be fixed, yes.  The issue is "cache line sharing".

The buffers shown above share cache lines with data that the
Linux driver may be using during the DMA.  So while the DMA
mapping calls invalidate/flush those lines before DMA starts,
the CPU could reload them shortly afterwards.

Imagine the DMA writes that rx_buff with some data.  Question:
when the CPU reads that rx_buf, what value will it get?  Probably
the value in the cache.  Maybe that value gets written back and
clobbers the "right" rx_buf data.  Or maybe that cacheline
never gets dirtied, and is discarded, so the "right" rx_buf
values come back.  You can't really know ...

- Dave

-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [not found]                     ` <481A01E2.8030309-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
@ 2008-05-02  8:48                       ` Jonathan Cameron
  2008-05-02  8:55                       ` Jonathan Cameron
  1 sibling, 0 replies; 12+ messages in thread
From: Jonathan Cameron @ 2008-05-02  8:48 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f; +Cc: David Brownell

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)



--- 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,5 +221,17 @@ 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 inelegant 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");


-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [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>
  1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2008-05-02  8:55 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f; +Cc: David Brownell

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)



--- 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,5 +221,17 @@ 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");







-------------------------------------------------------------------------
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

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/1] Initial support for ST Microelectronics lis3l02dq accelerometer via SPI
       [not found]                         ` <481AD6E9.1090201-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
@ 2008-05-02  9:13                           ` Jonathan Cameron
  0 siblings, 0 replies; 12+ messages in thread
From: Jonathan Cameron @ 2008-05-02  9:13 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Brownell, spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

[-- 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

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2008-05-02  9:13 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
2008-05-01 18:48                   ` David Brownell

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).