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