from Jonathan Cameron <[EMAIL PROTECTED]>

Initial support for ST Microelectronics LISS3L02DQ accelerometer via SPI

Signed-off-by: Jonathan Cameron <[EMAIL PROTECTED]>

---

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 <[EMAIL PROTECTED]>
+ *
+ * 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 <[EMAIL PROTECTED]>");
+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
[email protected]
https://lists.sourceforge.net/lists/listinfo/spi-devel-general

Reply via email to