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