diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/Kconfig b/drivers/industrialio/Kconfig --- a/drivers/industrialio/Kconfig 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/Kconfig 2008-06-04 18:59:18.000000000 +0100 @@ -0,0 +1,27 @@ +# +# Industrial I/O subsytem configuration +# + +menuconfig INDUSTRIALIO + tristate "Industrial I/O support" + ---help--- + The industrial IO subsystem provides a unified framework for drivers + for many different types of embedded sensors using a number of + different phyiscal interfaces (i2c, spi etc). + + This documentation will need some work! + +if INDUSTRIALIO + +source drivers/industrialio/accelerometer/Kconfig +#source drivers/industrialio/adc/Kconfig +#source drivers/industrialio/misc/Kconfig + +config INDUSTRIALIO_DEBUG_CORE + bool "Industrial I/O Core debugging messages" + help + Say yes here if you want the Industrial I/O core to producte a bunch + of debug messages to the system log. Select this if you are having a + problem with industrial io support and want to see mor eof what is + going on. +endif diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/Makefile b/drivers/industrialio/Makefile --- a/drivers/industrialio/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/Makefile 2008-06-07 13:41:31.000000000 +0100 @@ -0,0 +1,11 @@ +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_INDUSTRIALIO) += industrial.o + +obj-y += accelerometer/ + +ifeq ($(CONFIG_INDUSTRIALIO_DEBUG_CORE),y) +EXTRA_CFLAGS += -DDEBUG +endif \ No newline at end of file diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/accelerometer/Kconfig b/drivers/industrialio/accelerometer/Kconfig --- a/drivers/industrialio/accelerometer/Kconfig 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/accelerometer/Kconfig 2008-06-04 18:59:47.000000000 +0100 @@ -0,0 +1,24 @@ +# +# Accelerometer drivers +# + +config LIS3L02DQ + tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" + help + Say yes here to build generic support for the ST microelectronics + accelerometer. You will also need to one or more of the bus specific + elements below. + +config LIS3L02DQ_SPI + depends on LIS3L02DQ && SPI + tristate "SPI support" + help + Say yes here to build support for the ST LIS3L02DQ accelerometer via + an SPI bus. + +config LIS3L02DQ_I2C + depends on LIS3L02DQ && I2C + tristate "I2C support" + help + Say yes here to build support for the ST LIS3L02DQ accelerometer via + an I2C bus. diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/accelerometer/Makefile b/drivers/industrialio/accelerometer/Makefile --- a/drivers/industrialio/accelerometer/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/accelerometer/Makefile 2008-06-05 11:35:28.000000000 +0100 @@ -0,0 +1,5 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +obj-$(CONFIG_LIS3L02DQ_SPI) += lis3l02dq.o \ No newline at end of file diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/accelerometer/lis3l02dq.c b/drivers/industrialio/accelerometer/lis3l02dq.c --- a/drivers/industrialio/accelerometer/lis3l02dq.c 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/accelerometer/lis3l02dq.c 2008-06-26 18:05:57.000000000 +0100 @@ -0,0 +1,1305 @@ +/* + * lis3l02dq.c support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * 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. + * + * FIXME: MORE DOCS + * + * Settings: + * Latch on interrupt generation enabled as it simplifies when to reenable + * the interrupt. + * 16 bit left justified mode used (makes little or no difference, but that + * is what I picked. + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "lis3l02dq.h" + +/* Read all inputs in one spi message */ +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, + unsigned char *rx_array) +{ + /* 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 = rx_array, + .bits_per_word = 16, + .len = 2, + .cs_change = 1, + }, + /* x high byte */ + { + .tx_buf = read_all_tx_array+2, + .rx_buf = rx_array + 2, + .bits_per_word = 16, + .len = 2, + .cs_change = 1, + }, + /* y low byte */ + { + .tx_buf = read_all_tx_array+4, + .rx_buf = rx_array + 4, + .bits_per_word = 16, + .len = 2, + .cs_change = 1, + }, + /* y high byte */ + { + .tx_buf = read_all_tx_array+6, + .rx_buf = rx_array + 6, + .bits_per_word = 16, + .len = 2, + .cs_change = 1, + }, + /* z low byte */ + { + .tx_buf = read_all_tx_array+8, + .rx_buf = rx_array + 8, + .bits_per_word = 16, + .len = 2, + .cs_change = 1, + }, + /* z high byte */ + { + .tx_buf = read_all_tx_array+10, + .rx_buf = rx_array + 10, + .bits_per_word = 16, + .len = 2, + .cs_change = 0, + }, + }; + struct spi_message msg; + int ret; + /* 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; +} + +static int lis3l02dq_spi_read_reg_int8_t(struct device *dev, + uint8_t reg_address, + int8_t *val) +{ + int ret; + struct spi_message msg; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = indio_dev->dev_data; + struct spi_transfer xfer = { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }; + + xfer.tx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + xfer.rx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + ((unsigned char *)(xfer.tx_buf))[1] = LIS3L02DQ_READ_REG(reg_address); + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + kfree(xfer.tx_buf); + if (ret) { + dev_err(&st->us->dev, "problem with get x offset"); + goto err_ret; + } + *val = ((unsigned char *)(xfer.rx_buf))[0]; + kfree(xfer.rx_buf); + return ret; +err_ret: + kfree(xfer.rx_buf); + return 0; +} + +/*Returns into to allow full 0/255 range with error codes in negative range */ +static int lis3l02dq_spi_read_reg_uint8_t(struct device *dev, + uint8_t reg_address) +{ + uint8_t val; + int8_t ret; + struct spi_message msg; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = indio_dev->dev_data; + /* FIXME clearer to leave these in, or use tx_buf in xfer directly? */ + unsigned char *local_tx_buf; + unsigned char *local_rx_buf; + struct spi_transfer xfer = { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }; + + local_tx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + local_rx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + + local_tx_buf[1] = LIS3L02DQ_READ_REG(reg_address); + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + kfree(local_tx_buf); + if (ret) { + dev_err(&st->us->dev, "problem with get x offset"); + goto err_ret; + } + val = local_rx_buf[0]; + kfree(local_rx_buf); + return val; +err_ret: + kfree(local_rx_buf); + return ret; +} + +static int lis3l02dq_spi_write_reg_int8_t(struct device *dev, + uint8_t reg_address, + int value) +{ + struct spi_message msg; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = indio_dev->dev_data; + unsigned char *local_tx_buf; + struct spi_transfer xfer = { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }; + int ret; + + local_tx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + xfer.tx_buf = local_tx_buf; + local_tx_buf[1] = LIS3L02DQ_WRITE_REG(reg_address); + local_tx_buf[0] = value; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + kfree(local_tx_buf); + + if (ret) { + dev_err(&st->us->dev, "problem with writing 8 bit register"); + return ret; + } + return 0; +} + +/* Relies on the MSB being one higher adress than the LSB */ +static int lis3l02dq_spi_write_reg_int16_t(struct device *dev, + uint8_t lower_reg_address, + int value) +{ + struct spi_message msg; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = indio_dev->dev_data; + unsigned char *local_tx_buf; + int ret; + struct spi_transfer xfers [] = { { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }, { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }, + }; + + local_tx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + xfers[0].tx_buf = local_tx_buf; + xfers[1].tx_buf = local_tx_buf + 2; + local_tx_buf[1] = LIS3L02DQ_WRITE_REG(lower_reg_address); + local_tx_buf[0] = (value & 0xFF); + local_tx_buf[3] = LIS3L02DQ_WRITE_REG(lower_reg_address+1); + local_tx_buf[2] = (value & 0xFF00) >> 8; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + kfree(local_tx_buf); + if (ret) { + dev_err(&st->us->dev, "problem when writing 16 bit register"); + return ret; + } + + return 0; +} + +static int lis3l02dq_spi_read_reg_int16_t(struct device *dev, + uint8_t lower_reg_address, + int16_t *val) +{ + struct spi_message msg; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = indio_dev->dev_data; + unsigned char *local_tx_buf; + unsigned char *local_rx_buf; + int ret; + uint16_t temp; + struct spi_transfer xfers [] = { { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }, { + .tx_buf = NULL, + .rx_buf = NULL, + .bits_per_word = 16, + .len = 2, + }, + }; + local_tx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + local_rx_buf = (unsigned char *)(kmalloc(4, GFP_KERNEL)); + xfers[0].tx_buf = local_tx_buf; + xfers[0].rx_buf = local_rx_buf; + xfers[1].tx_buf = local_tx_buf + 2; + xfers[1].rx_buf = local_rx_buf + 2; + local_tx_buf[0] = 0; + local_tx_buf[1] = LIS3L02DQ_READ_REG(lower_reg_address); + local_tx_buf[2] = 0; + local_tx_buf[3] = LIS3L02DQ_READ_REG(lower_reg_address+1); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + kfree(local_tx_buf); + kfree(local_rx_buf); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register"); + return ret; + } + temp = (((uint16_t)((local_rx_buf[2]))) << 8) + | (uint16_t)(local_rx_buf[0]); + *val = *((int16_t *)(&temp)); + + return 0; +} + + + +static ssize_t lis3l02dq_read_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len, ret; + int8_t val; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + + ret = lis3l02dq_spi_read_reg_int8_t(dev, + this_attr->address, + &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 device_attribute *attr, + char *buf) +{ + int val, len; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + + val = lis3l02dq_spi_read_reg_uint8_t(dev, + this_attr->address); + if (val < 0) { + dev_err(dev, "problem reading an unsigned 8 bit value"); + 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 device_attribute *attr, + const char *buf, + size_t len) +{ + long val; + int ret; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto err_ret; + + ret = lis3l02dq_spi_write_reg_int8_t(dev, + this_attr->address, + val); + if (ret) + goto err_ret; + + return len; + +err_ret: + return ret; +} + +static ssize_t lis3l02dq_write_unsigned(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + ulong val; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto err_ret; + + ret = lis3l02dq_spi_write_reg_int8_t(dev, + this_attr->address, + val); + if (ret) + goto err_ret; + + return len; +err_ret: + return ret; +} + + +static ssize_t lis3l02dq_read_16bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len, ret; + int16_t val; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + ret = lis3l02dq_spi_read_reg_int16_t(dev, + this_attr->address, + &val); + if (ret < 0) { + dev_err(dev, "problem reading a signed 16 bit value from chip"); + return ret; + } + len = sprintf(buf, "%d\n", val); + return len; +} + +/* As the ring buffer contents are device dependent this functionality + * must remain part of the driver and not the ring buffer subsystem */ +static ssize_t +lis3l02dq_read_accel_from_ring(struct industrialio_ring_buffer *ring, + int element, char *buf) +{ + int val, ret, len; + uint16_t temp; + char *data, *datalock; + + data = kmalloc(8, GFP_KERNEL); + if (data == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = industrialio_read_last_from_ring(ring, data); + datalock = data + 2*element; + kfree(data); + temp = (((uint16_t)((datalock[1]))) << 8) + | (uint16_t)(datalock[0]); + val = *((int16_t *)(&temp)); + len = sprintf(buf, "ring %d\n", val); + + return len; +error_ret: + return ret; +} + +static ssize_t lis3l02dq_read_accel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + struct industrialio_dev *dev_info = dev_get_drvdata(dev); + int element; + /* FIXME clunky*/ + if (dev_info->currentmode == INDIO_RING_DATA_RDY) { + switch (this_attr->address) { + case LIS3L02DQ_REG_OUT_X_L_ADDRESS: + element = 0; + break; + case LIS3L02DQ_REG_OUT_Y_L_ADDRESS: + element = 1; + break; + case LIS3L02DQ_REG_OUT_Z_L_ADDRESS: + element = 2; + break; + default: + return -EINVAL; + } + return lis3l02dq_read_accel_from_ring(dev_info->ring, + element, buf); + } else + return lis3l02dq_read_16bit_signed(dev, attr, buf); +} + +/* For this device this is only relevant to the threshold for interrupt + * generation */ +static ssize_t lis3l02dq_write_16bit_signed(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + long val; + struct industrialio_dev_attr *this_attr + = to_industrialio_dev_attr(attr); + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + ret = lis3l02dq_spi_write_reg_int16_t(dev, + this_attr->address, + val); + if (ret) + return ret; + return len; +} + +static ssize_t lis3l02dq_read_av_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "280 560 1120 4480\n"); +} + +static ssize_t lis3l02dq_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len; + int8_t t; + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_1_ADDRESS, + &t); + if (ret) + goto error_ret; + t &= LIS3L02DQ_DEC_MASK; + if (t == LIS3L02DQ_REG_CTRL_1_DF_128) + len = sprintf(buf, "280"); + else if (t == LIS3L02DQ_REG_CTRL_1_DF_64) + len = sprintf(buf, "560"); + else if (t == LIS3L02DQ_REG_CTRL_1_DF_32) + len = sprintf(buf, "1120"); + else + len = sprintf(buf, "4480"); + len += sprintf(buf+len, "\n"); + + return len; + +error_ret: + return ret; +} + +static ssize_t lis3l02dq_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + long val; + int ret; + int8_t t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_1_ADDRESS, + &t); + if (ret) + goto error_ret; + /* Wipe the bits clean */ + t |= ~LIS3L02DQ_DEC_MASK; + switch (val) { + case 280: + t |= LIS3L02DQ_REG_CTRL_1_DF_128; + break; + case 560: + t |= LIS3L02DQ_REG_CTRL_1_DF_64; + break; + case 1120: + t |= LIS3L02DQ_REG_CTRL_1_DF_32; + break; + case 4480: + t |= LIS3L02DQ_REG_CTRL_1_DF_8; + break; + default: + ret = -EINVAL; + goto error_ret; + }; + + ret = lis3l02dq_spi_write_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_1_ADDRESS, + t); + if (ret) + goto error_ret; + + return len; + +error_ret: + return ret; + +} + +static int lis3l02dq_initial_setup(struct lis3l02dq_state *st) +{ + int ret; + + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Write suitable defaults to ctrl1 */ + ret = lis3l02dq_spi_write_reg_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_spi_write_reg_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; + } + + ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS, + LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC); + if (ret) { + dev_err(&st->us->dev, "problem with interrupt cfg register"); + goto err_ret; + } +err_ret: + return ret; +} + + + +/* These are all a case of reading / writing directly to the chip */ + +static INDUSTRIALIO_DEV_ATTR_ACCEL_X_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_X_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Y_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_Y_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Z_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_Z_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_X_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_X_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Y_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_Y_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Z_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_Z_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_THRESH(S_IWUSR | S_IRUGO, + lis3l02dq_read_16bit_signed, + lis3l02dq_write_16bit_signed, + LIS3L02DQ_REG_THS_L_ADDRESS); + +/* Obviously the reading method for these will change depending on whether + ring buffer capture is in use. Allow specification here of alternate + function? Or take the approach used above of making this a driver issue + rather than part of industrialio */ +static INDUSTRIALIO_DEV_ATTR_ACCEL_X(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_X_L_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Y(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_Y_L_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_ACCEL_Z(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_Z_L_ADDRESS); + +static INDUSTRIALIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + lis3l02dq_read_frequency, + lis3l02dq_write_frequency); + +static INDUSTRIALIO_DEV_ATTR_AVAIL_SAMP_FREQ(lis3l02dq_read_av_freq); + +static ssize_t +lis3l02dq_read_interrupt_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len, ret, set; + int8_t val; + struct industrialio_event_attr *this_attr + = to_industrialio_event_attr(attr); + + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS, + &val); + if (ret < 0) + return ret; + set = (val & this_attr->mask) ? 1 : 0; + len = sprintf(buf, "%d\n", set); + + return len; +} + +static ssize_t +lis3l02dq_write_interrupt_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret, currentlyset, changed = 0; + int8_t valold; + int8_t controlold; + + struct industrialio_event_attr *this_attr + = to_industrialio_event_attr(attr); + long val; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + + ret = strict_strtol(buf, 10, &val); + + if (ret < 0) + return ret; + /* read current value */ + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS, + &valold); + if (ret < 0) + return ret; + + /* read current control */ + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_2_ADDRESS, + &controlold); + if (ret < 0) + return ret; + currentlyset = valold & this_attr->mask ? 1 : 0; + if (val == 0 && currentlyset) { + valold &= ~this_attr->mask; + changed = 1; + ret = industrialio_remove_event_from_list(this_attr->listel); + if (ret < 0) + return ret; + } else if (val == 1 && !currentlyset) { + changed = 1; + valold |= this_attr->mask; + /* move to a line spec rather than this? */ + ret = industrialio_add_event_to_list(&indio_dev + ->interrupts[0]->ev_list, + this_attr->listel); + if (ret < 0) + return ret; + } + + if (changed) { + ret = lis3l02dq_spi_write_reg_int8_t(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS, + valold); + if (ret < 0) + return ret; + /* This always enables the interrupt, even if we've remove the + * last thing using it. For this device we can use the reference + * count on the handler to tell us if anyone + * wants the interrupt */ + controlold = this_attr->listel->refcount ? + (controlold | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT): + (controlold & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); + ret = lis3l02dq_spi_write_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_2_ADDRESS, + controlold); + if (ret < 0) + return ret; + } + + return len; +} + +static ssize_t lis3l02dq_read_data_ready_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, set, len; + int8_t val; + + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_2_ADDRESS, + &val); + if (ret < 0) + return ret; + set = (val & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION) ? 1 : 0; + len = sprintf(buf, "%d\n", set); + + return len; +} + +/* This function must enable data ready generation - whether this is going into + a ring buffer or directly generating events is dependant on the value input. + 0 - no data ready + 1 - into ring buffer + 2 - generate events +*/ +static ssize_t lis3l02dq_write_data_ready_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret, currentlyset, changed = 0; + int8_t valold; + + struct industrialio_event_attr *this_attr + = to_industrialio_event_attr(attr); + long val; + struct industrialio_dev *indio_dev = dev_get_drvdata(dev); + + ret = strict_strtol(buf, 10, &val); + if (ret < 0) + return ret; + if (val > 2 || val < 0) + return -EINVAL; + + ret = lis3l02dq_spi_read_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_2_ADDRESS, + &valold); + if (ret < 0) + return ret; + + currentlyset + = valold + & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION + ? 1 : 0; + if (val == 0 && currentlyset) { + valold &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + changed = 1; + ret = industrialio_remove_event_from_list(this_attr->listel); + indio_dev->currentmode = INDIO_DIRECT_MODE; + } else if (val != 0 && !currentlyset) { + valold |= LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + changed = 2; + ret = industrialio_add_event_to_list(&indio_dev + ->interrupts[0]->ev_list, + this_attr->listel); + } + if (val == 1) + indio_dev->currentmode = INDIO_RING_DATA_RDY; + else if (val == 2) { + indio_dev->currentmode = INDIO_DIRECT_MODE; + valold |= LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE; + } + if (changed) { + ret = lis3l02dq_spi_write_reg_int8_t(dev, + LIS3L02DQ_REG_CTRL_2_ADDRESS, + valold); + if (ret < 0) + return ret; + /* Clear the other sources of interrupts - whilst not strictly + necessary, this does avoid the need for maintaining state + in software */ + if (changed == 2) { + ret = lis3l02dq_spi_write_reg_int8_t(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS, + LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC); + + if (ret < 0) + return ret; + } + } + + return len; +} + +static void lis3l02dq_data_rdy_bh_to_ring(struct work_struct *work_s) +{ + struct lis3l02dq_state *st + = container_of(work_s, struct lis3l02dq_state, + work_data_rdy_ring); + /* indicate we have begun to process the interrupt*/ + unsigned char *rx_array, *ring_data; + int i; + + /*FIXME both need tests for -enomem */ + rx_array = (unsigned char *)(kmalloc(12, GFP_KERNEL)); + ring_data = (unsigned char *)(kmalloc(6, GFP_KERNEL)); + + st->inter = 0; + + if (lis3l02dq_read_all(st, rx_array) >= 0) { + for (i = 0; i < 6; i++) + ring_data[i] = rx_array[i*2]; + industrialio_store_to_ring(&st->indio_dev->ring[0], + ring_data, + st->last_timestamp); + } + /* so the data should now be in the rx array */ +try_again: + while (gpio_get_value(irq_to_gpio(st->us->irq))) + if (lis3l02dq_read_all(st, rx_array) >= 0) { + for (i = 0; i < 3; i++) + ring_data[i] = rx_array[i*2]; + /* Passing a negated time stamp to indicate that + * we don't actually know when this occured! */ + industrialio_store_to_ring(&st->indio_dev->ring[0], + ring_data, + -st->last_timestamp); + } + /* push data into ring buffer before trying renable */ + enable_irq(st->us->irq); + if (gpio_get_value(irq_to_gpio(st->us->irq))) + if (st->inter == 0) { + disable_irq_nosync(st->us->irq); + goto try_again; + } + kfree(rx_array); + kfree(ring_data); + return; +} + +/* This one is odd, I need to perform a full read in order to reset the + * interrupt - hence only real thing to make sense it to send the data + * as payload of an event? */ + +static void lis3l02dq_data_rdy_bh_to_event(struct work_struct *work_s) +{ + struct lis3l02dq_state *st + = container_of(work_s, struct lis3l02dq_state, + work_data_rdy_event); + + /* send an event up to user space */ + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_DATA_RDY, + st->last_timestamp); + enable_irq(st->us->irq); + return; +} + +static int lis3l02dq_data_rdy_th(struct industrialio_dev *dev_info, + int index, + s64 timestamp, + int no_test) +{ + struct lis3l02dq_state *st = dev_info->dev_data; + + /* Stash the timestamp for the bottom half of the handler*/ + st->last_timestamp = timestamp; + + if (dev_info->currentmode == INDIO_RING_DATA_RDY) { + schedule_work(&st->work_data_rdy_ring); + st->inter = 1; + } else + schedule_work(&st->work_data_rdy_event); + + return IRQ_HANDLED; +} + +static inline int lis3l02dq_thresh_th_impl(struct industrialio_dev *dev_info, + int index, + int timestamp, + int no_test, + struct lis3l02dq_work_cont *wc) +{ + /* Find out if this condition is true */ + if (no_test == 0) { + schedule_work(&wc->ws); + /* can't do anything else yet */ + return 0; + } + schedule_work(&wc->ws_nocheck); + /* So I'm set - send event to userspace */ + +} +static int lis3l02dq_thresh_handler_th(struct industrialio_dev *dev_info, + int index, + s64 timestamp, + int no_test) +{ + struct lis3l02dq_state *st = dev_info->dev_data; + /* Stash the timestamp somewhere convenient for the bh */ + st->last_timestamp = timestamp; + /* This is somewhat missleading as for this chip these are actually + the same function */ + if (no_test == 0) + schedule_work(&st->work_cont_thresh.ws); + else + schedule_work(&st->work_cont_thresh.ws_nocheck); + return 0; +} + + +/* Unforunately it appears the interrupt won't clear unless you read from the + src register */ +/* Could move the event list adding stuff into the interrupt, and just do the + stuff necessary to reenable the interrupt in here? */ +static void lis3l02dq_thresh_handler_bh_no_check(struct work_struct *work_s) +{ + struct lis3l02dq_work_cont *wc + = container_of(work_s, struct lis3l02dq_work_cont, + ws_nocheck); + struct lis3l02dq_state *st = wc->st; + + int8_t t; + + lis3l02dq_spi_read_reg_int8_t(st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS, + &t); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_Z_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_Z_LOW, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_Y_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_Y_LOW, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_X_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW) + industrialio_put_event(st->indio_dev, 0, + INDUSTRIALIO_EVENT_CODE_ACCEL_X_LOW, + st->last_timestamp); + + /* For this chip, if this handler is running no other interrupts can + occur so this will actually enable the irq - in others it will + effectively reference count */ + enable_irq(st->us->irq); + /* Ack (and allow for new interrupts? (not clear on data sheet ) )*/ + lis3l02dq_spi_read_reg_int8_t(st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_ACK_ADDRESS, + &t); + + return; +} + +/* A shared handler for a number of threshold types */ +INDUSTRIALIO_EVENT_SH(threshold, &lis3l02dq_thresh_handler_th); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_X_HIGH_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_Y_HIGH_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_Z_HIGH_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_X_LOW_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_Y_LOW_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW); + +INDUSTRIALIO_EVENT_ATTR_ACCEL_Z_LOW_SH(industrialio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW); + +INDUSTRIALIO_EVENT_ATTR_DATA_RDY(lis3l02dq_read_data_ready_config, + lis3l02dq_write_data_ready_config, + 0, + &lis3l02dq_data_rdy_th); + + + +static struct attribute *lis3l02dq_event_attributes[] = { + &industrialio_event_attr_x_high.dev_attr.attr, + &industrialio_event_attr_y_high.dev_attr.attr, + &industrialio_event_attr_z_high.dev_attr.attr, + &industrialio_event_attr_x_low.dev_attr.attr, + &industrialio_event_attr_y_low.dev_attr.attr, + &industrialio_event_attr_z_low.dev_attr.attr, + &industrialio_event_attr_data_rdy.dev_attr.attr, + NULL +}; + +static const struct attribute_group lis3l02dq_event_attribute_group = { + .name = "event_sources", + .attrs = lis3l02dq_event_attributes, +}; + +/*standardize these attributes as much as possible*/ + +static struct attribute *lis3l02dq_attributes[] = { + &industrialio_dev_attr_x_offset.dev_attr.attr, + &industrialio_dev_attr_y_offset.dev_attr.attr, + &industrialio_dev_attr_z_offset.dev_attr.attr, + &industrialio_dev_attr_x_gain.dev_attr.attr, + &industrialio_dev_attr_y_gain.dev_attr.attr, + &industrialio_dev_attr_z_gain.dev_attr.attr, + &industrialio_dev_attr_thresh.dev_attr.attr, + &industrialio_dev_attr_x.dev_attr.attr, + &industrialio_dev_attr_y.dev_attr.attr, + &industrialio_dev_attr_z.dev_attr.attr, + &industrialio_dev_attr_sampling_frequency.dev_attr.attr, + &industrialio_dev_attr_available_sampling_frequency.dev_attr.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); + if (st == NULL) { + dev_err(&spi->dev, "Memory allocation error \n"); + return -ENOMEM; + } + st->us = spi; + + /* setup the industrialio driver allocated elements */ + st->indio_dev + = (struct industrialio_dev *) + (kzalloc(sizeof(struct industrialio_dev), GFP_KERNEL)); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_st; + } + + /* WRAP THIS LOT INTO A COUPLE OF DEFINES! */ + st->indio_dev->dev = &spi->dev; + st->indio_dev->num_interrupt_lines = 1; + st->indio_dev->event_attrs = &lis3l02dq_event_attribute_group; + st->indio_dev->attrs = &lis3l02dq_attribute_group; + st->indio_dev->dev_data = (void *)(st); + /* setup parameters of the ring buffer */ +/* CARE WITH WORD ALIGNMENT*/ + st->indio_dev->ring_dimension = 4; + st->indio_dev->ring_bytes_per_reading = 2; + st->indio_dev->ring_length = 500; + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE | INDIO_RING_DATA_RDY; + + + ret = industrialio_device_register(st->indio_dev); + if (ret < 0) + goto error_free_dev; + /* FIXME: Due to intialization order the ring is created even if the irq + is no good. Valid when polling is implemented */ + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) { + INIT_WORK(&st->work_data_rdy_ring, + lis3l02dq_data_rdy_bh_to_ring); + INIT_WORK(&st->work_data_rdy_event, + lis3l02dq_data_rdy_bh_to_event); + + /* This is a little unusual, in that the device seems + to need a full read of the interrupt source reg before + the interrupt will reset. + Hence the two handlers are the same */ + + INIT_WORK_CONT(&(st->work_cont_thresh), + lis3l02dq_thresh_handler_bh_no_check, + lis3l02dq_thresh_handler_bh_no_check, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS, + 0, + st); + st->inter = 0; + ret = industrialio_register_interrupt_line(spi->irq, + st->indio_dev, + 0, + IRQF_TRIGGER_RISING, + "lis3l02dq"); + if (ret) + goto error_unregister_dev; + } else + st->indio_dev->modes &= ~INDIO_RING_DATA_RDY; + + /* Get the device into a sane initial state */ + ret = lis3l02dq_initial_setup(st); + if (ret) + goto error_unregister_line; + + return 0; + +error_unregister_line: + if (st->indio_dev->modes & INDIO_RING_DATA_RDY) + industrialio_unregister_interrupt_line(st->indio_dev, 0); +error_unregister_dev: + industrialio_device_unregister(st->indio_dev); +error_free_dev: + kfree(st->indio_dev); +error_free_st: + kfree(st); + return ret; +} + +/* Power down the device */ +static int lis3l02dq_stop_device(struct lis3l02dq_state *st) +{ + int ret; + + ret = lis3l02dq_spi_write_reg_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_spi_write_reg_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; + } + return 0; +err_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int lis3l02dq_remove(struct spi_device *spi) +{ + int ret; + struct industrialio_dev *indio_dev = spi_get_drvdata(spi); + struct lis3l02dq_state *st = indio_dev->dev_data; + + /* stop the device + * Move into industrialio - can then automating linking + * to power type controls in sysfs? FUTURE WORK! + */ + /* Amongst other things this must ensure no more interrupts are generate + * by the device */ + ret = lis3l02dq_stop_device(st); + if (ret) + goto err_ret; + /* Fixme slightly misleading test condition - even if valid */ + if (st->indio_dev->modes & INDIO_RING_DATA_RDY) + industrialio_unregister_interrupt_line(st->indio_dev, 0); + industrialio_device_unregister(st->indio_dev); + kfree(st->indio_dev); + 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 "); +MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/accelerometer/lis3l02dq.c_oldfuncs b/drivers/industrialio/accelerometer/lis3l02dq.c_oldfuncs --- a/drivers/industrialio/accelerometer/lis3l02dq.c_oldfuncs 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/accelerometer/lis3l02dq.c_oldfuncs 2008-06-12 18:11:01.000000000 +0100 @@ -0,0 +1,265 @@ +/* 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; +} + +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(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; +} + + +/* can I make any of this generic - yes! */ +static irqreturn_t interrupthandler(int irq, void *_state) +{ + + struct lis3l02dq_state *st = _state; + struct industrialio_event_list* p; + + disable_irq_nosync(irq); + /* Could it have been us? */ + if(list_empty(&st->interrupt_pin_event_list.list)) + return IRQ_NONE; + + /* Grab and store a time stamp - might take us a while to fill in the details */ + /* Assume for now it could only have been us */ + /* If only one possible source exists don't need to identify */ + if(st->interrupt_pin_event_list.list.next->next == &st->interrupt_pin_event_list.list); + list_for_each_entry(p, &st->interrupt_pin_event_list.list, list) + { + /* fixme, need the index from somewhere safe! hang on, I'm in an interrupt, anywhere is fine */ + /* Hang on, what's the point of the index? If the time is fine, all is good */ + p->handler(_state, 1, 1); + } + /*Otherwise we need to queue a query to find out what it was and then handle later */ + + return IRQ_HANDLED; +} \ No newline at end of file diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/accelerometer/lis3l02dq.h b/drivers/industrialio/accelerometer/lis3l02dq.h --- a/drivers/industrialio/accelerometer/lis3l02dq.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/accelerometer/lis3l02dq.h 2008-06-26 18:07:34.000000000 +0100 @@ -0,0 +1,183 @@ +/* + * LISL02DQ.h -- support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * 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_DEC_MASK 0x30 +#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_INTERRUPT 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 High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH 0x02 +/* X Interupt on Low */ +#define LIS3L02DQ_REG_WAKE_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 0x2F + +#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 0 + +#define LIS3L02DQ_DIRECT_ONLY_MODE -1 +#define LIS3L02DQ_DIRECT_MODE 0 +#define LIS3L02DQ_INTERRUPT_MODE 1 + +#define LIS3L02DQ_BUFFER_LENGTH 100 + +struct lis3l02dq_work_cont { + struct work_struct ws; + struct work_struct ws_nocheck; + int address; + int mask; + struct lis3l02dq_state *st; +}; + +#define INIT_WORK_CONT(cont, _checkfunc, _nocheckfunc, _add, _mask, _st)\ + do { \ + INIT_WORK(&(cont)->ws, _checkfunc); \ + INIT_WORK(&(cont)->ws_nocheck, _nocheckfunc); \ + (cont)->address = _add; \ + (cont)->mask = _mask; \ + (cont)->st = _st; \ + } while (0) + +struct lis3l02dq_state { + struct spi_device *us; + struct work_struct work_data_rdy_ring; + struct work_struct work_data_rdy_event; + struct lis3l02dq_work_cont work_cont_thresh; + + /* Interrupt caught event - used as part of the datardy to ring bh + in ensuring interrupt line does not become locked high */ + bool inter; + s64 last_timestamp; + struct industrialio_dev *indio_dev; +}; +#endif /* SPI_LIS3L02DQ_H_ */ diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/adc/Makefile b/drivers/industrialio/adc/Makefile --- a/drivers/industrialio/adc/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/adc/Makefile 2008-06-05 11:31:16.000000000 +0100 @@ -0,0 +1,3 @@ + +# Makefile for industrial I/O ADC drivers +# diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/industrial.c b/drivers/industrialio/industrial.c --- a/drivers/industrialio/industrial.c 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/industrial.c 2008-06-26 18:26:12.000000000 +0100 @@ -0,0 +1,1172 @@ +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * 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. + * + * Based on elements of hwmon and input subsystems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Industrial I/O core"); +MODULE_LICENSE("GPL"); + +#define INDUSTRIALIO_ID_PREFIX "industrialio" +#define INDUSTRIALIO_ID_FORMAT INDUSTRIALIO_ID_PREFIX "%d" +#define INDUSTRIALIO_MAJOR 244 + +/* Integer id - used to assign each registered device a unique id*/ +static DEFINE_IDR(industrialio_idr); + +/* Single spinlock used to protect all the IDRs */ +static DEFINE_SPINLOCK(industrialio_idr_lock); + +struct class industrialio_class = { + .name = "industrialio", +}; +EXPORT_SYMBOL_GPL(industrialio_class); + +static DEFINE_SPINLOCK(industrialio_state_lock); + +/* Struct used to maintain internal state about industrialio. + * This will be used to handle the character device accesses + * and redirect them to the relevant driver. + * Will reduce this to the included table if nothing else comes + * up that should go in here! + */ +struct __industrialio_state { + /* All initially set to NULL in init */ + struct industrialio_handler *fhs[256]; +}; + + +static struct __industrialio_state industrialio_state; + +/* Used to escalate shared event. + * currently this only used with ring buffer events */ +static inline void +__industrialio_change_event(struct industrialio_detected_event_list *ev, + int ev_code, + s64 timestamp) +{ + ev->ev.id = ev_code; + ev->ev.timestamp = timestamp; +} + +/* Used both in the interrupt line put events and the ring buffer ones */ +static inline int +__industrialio_put_event(struct industrialio_event_interface *ev_int, + int ev_code, + s64 timestamp, + struct industrialio_shared_ev_pointer* + shared_pointer_p) +{ + struct industrialio_detected_event_list *ev; + int ret; + /* Does anyone care? */ + if (test_bit(INDUSTRIALIO_BUSY_BIT_POS, &ev_int->handler.flags)) { + if (ev_int->current_events + == ev_int->max_events) + return 0; + ev = (struct industrialio_detected_event_list *) + (kmalloc(sizeof(struct + industrialio_detected_event_list), + GFP_KERNEL)); + if (ev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + INIT_LIST_HEAD(&ev->list); + ev->ev.id = ev_code; + ev->ev.timestamp = timestamp; + if (shared_pointer_p != NULL) { + ev->shared_pointer = shared_pointer_p; + shared_pointer_p->ev_p = ev; + } else + ev->shared_pointer = NULL; + list_add_tail(&ev->list, &ev_int->det_events.list); + ev_int->current_events++; + wake_up_interruptible(&ev_int->wait); + } + + return 0; +error_ret: + return ret; + +} + +int industrialio_put_event(struct industrialio_dev *dev_info, + int ev_line, + int ev_code, + s64 timestamp) +{ + return __industrialio_put_event(&dev_info->event_interfaces[ev_line], + ev_code, + timestamp, NULL); +} +EXPORT_SYMBOL(industrialio_put_event); + +/* Confirming validity of supplied irq is left to drivers */ +int industrialio_register_interrupt_line(unsigned int irq, + struct industrialio_dev *dev_info, + int line_number, + unsigned long type, + const char *name) +{ + int ret; + + dev_info->interrupts[line_number] = (struct industrialio_interrupt *) + kmalloc(sizeof(struct industrialio_interrupt), GFP_KERNEL); + if (dev_info->interrupts[line_number] == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + INIT_LIST_HEAD(&dev_info->interrupts[line_number]->ev_list.list); + dev_info->interrupts[line_number]->line_number = line_number; + dev_info->interrupts[line_number]->irq = irq; + dev_info->interrupts[line_number]->dev_info = dev_info; + + /* Possibly only request on demand? + * Can see this may complicate the handling of interrupts. + * However, with this approache we end up handling lots of + * events noone cares about.*/ + ret = request_irq(irq, + &industrialio_interrupt_handler, + type, + name, + dev_info->interrupts[line_number]); + if (ret < 0) + goto error_ret; + + return 0; + +error_ret: + return ret; +} +EXPORT_SYMBOL(industrialio_register_interrupt_line); + +/* Before this runs the interrupt generator must have been disabled */ +void industrialio_unregister_interrupt_line(struct industrialio_dev *dev_info, + int line_number) +{ + /* make sure the interrupt handlers are all done */ + flush_scheduled_work(); + free_irq(dev_info->interrupts[line_number]->irq, + dev_info->interrupts[line_number]); + kfree(dev_info->interrupts[line_number]); +} +EXPORT_SYMBOL(industrialio_unregister_interrupt_line); + +/* Generic interrupt line interrupt handler */ +irqreturn_t industrialio_interrupt_handler(int irq, void *_int_info) +{ + struct industrialio_interrupt *int_info = _int_info; + struct industrialio_dev *dev_info = int_info->dev_info; + struct industrialio_event_handler_list *p; + s64 time_ns; + + if (list_empty(&int_info->ev_list.list)) + return IRQ_NONE; + + time_ns = industrialio_get_time_ns(); + /* detect single element list*/ + if (int_info->ev_list.list.next->next == &int_info->ev_list.list) { + disable_irq_nosync(irq); + p = list_first_entry(&int_info->ev_list.list, + struct industrialio_event_handler_list, + list); + p->handler(dev_info, 1, time_ns, 1); + } else + list_for_each_entry(p, &int_info->ev_list.list, list) + { + disable_irq_nosync(irq); + p->handler(dev_info, 1, time_ns, 0); + } + return IRQ_HANDLED; +} +EXPORT_SYMBOL(industrialio_interrupt_handler); + +int industrialio_add_event_to_list(struct industrialio_event_handler_list *list, + struct industrialio_event_handler_list *el) +{ + if (el->refcount == 0) + list_add(&list->list, &el->list); + el->refcount++; + return 0; +} +EXPORT_SYMBOL_GPL(industrialio_add_event_to_list); + +int industrialio_remove_event_from_list(struct industrialio_event_handler_list + *el) +{ + el->refcount--; + if (el->refcount == 0) + list_del_init(&el->list); + return 0; +} +EXPORT_SYMBOL_GPL(industrialio_remove_event_from_list); + + +static int industrialio_allocate_chrdev(struct industrialio_handler *handler) +{ + int id; + spin_lock(industrialio_state_lock); + /* Find an unused device - fixme, more efficient options?*/ + for (id = 0; id <= 256; id++) + if (industrialio_state.fhs[id] == NULL) + break; + if (id == 256) { /*FIXME incorrect error code ?*/ + spin_unlock(industrialio_state_lock); + return -ENOMEM; + } + industrialio_state.fhs[id] = handler; + spin_unlock(industrialio_state_lock); + handler->id = id; + + return 0; +} + + +static void industrialio_deallocate_chrdev(struct industrialio_handler *handler) +{ + spin_lock(industrialio_state_lock); + industrialio_state.fhs[handler->id] = NULL; + spin_unlock(industrialio_state_lock); +} + +/* Upon open, switch in the correct file ops + * lifted directly from input subsystem */ +static int industrialio_open_file(struct inode *inode, struct file *file) +{ + struct industrialio_handler *handler; + const struct file_operations *old_fops, *new_fops = NULL; + int err; + + + /* This lock needed as unlike input we are dynamically allocating + * chrdevs */ + spin_lock(industrialio_state_lock); + handler = industrialio_state.fhs[iminor(inode)]; + spin_unlock(industrialio_state_lock); + if (!handler || !(new_fops == fops_get(handler->fops))) + return -ENODEV; + /* cribbed from lp.c - not entirely certain if the fops_put is + * necessary */ + + if (test_and_set_bit(INDUSTRIALIO_BUSY_BIT_POS, &handler->flags)) { + fops_put(file->f_op); + return -EBUSY; + } + + if (!new_fops->open) { + fops_put(new_fops); + return -ENODEV; + } + old_fops = file->f_op; + file->f_op = new_fops; + /* use the private data pointer in file to give access to device + * specific stuff */ + file->private_data = handler->private; + err = new_fops->open(inode, file); + + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + + return err; +} + + +/* The main file ops structure. All open calls on the major number will + * be handled by this with fops for the actual minor number assigned by + * switching function above */ + +static const struct file_operations industrialio_fops = { + .owner = THIS_MODULE, + .open = industrialio_open_file, +}; +static int count; +/* Ring buffer related functionality */ +int industrialio_store_to_ring(struct industrialio_ring_buffer *ring, + unsigned char *data, + s64 timestamp) +{ + bool init_read = true; + int ret; + int code; + + /* initial store */ + if (unlikely(ring->write_p == 0)) { + ring->write_p = ring->data; + /* doesn't actually matter if this is out of the set + * FIXME deal with odd ring length */ + ring->half_p = ring->data - ring->length*ring->skip / 2; + init_read = false; + } + memcpy(ring->write_p, data, ring->size); + memcpy(ring->write_p + ring->size, ×tamp, sizeof(s64)); + barrier(); + ring->last_written_p = ring->write_p; + barrier(); + ring->write_p += ring->skip; + /* End of ring, back to the beginning */ + if (ring->write_p == ring->data + ring->length*ring->skip) { + ring->write_p = ring->data; + ring->loopcount++; + } + if (ring->read_p == 0) + ring->read_p = ring->data; + /* Buffer full - move the read pointer and create / escalate + * ring event */ + else if (ring->write_p == ring->read_p) { + ring->read_p += ring->skip; + if (ring->read_p == ring->data + ring->length*ring->skip) + ring->read_p = ring->data; + /* Needs protection against unexpected delete? How? */ + spin_lock(ring->shared_ev_pointer.lock); + if (ring->shared_ev_pointer.ev_p) { + /* Event escalation - probably quicker to let this + keep running than check if it is necessary */ + code = INDUSTRIALIO_EVENT_CODE_RING_100_FULL; + __industrialio_change_event(ring + ->shared_ev_pointer.ev_p, + code, + timestamp); + } else { + code = INDUSTRIALIO_EVENT_CODE_RING_100_FULL; + ret = __industrialio_put_event(&ring->ev_int, + code, + timestamp, + &ring + ->shared_ev_pointer); + if (ret) { + spin_unlock(ring->shared_ev_pointer.lock); + goto error_ret; + } + } + spin_unlock(ring->shared_ev_pointer.lock); + + } + + + /* investigate if our event barrier has been passed */ + /* There are definite 'issues' with this and chances of + * simultaneous read */ + /* Also need to use loop count to ensure this only happens once */ + ring->half_p += ring->skip; + if (ring->half_p == ring->data + ring->length*ring->skip) + ring->half_p = ring->data; + if (ring->half_p == ring->read_p) { + spin_lock(ring->shared_ev_pointer.lock); + code = INDUSTRIALIO_EVENT_CODE_RING_50_FULL; + ret = __industrialio_put_event(&ring->ev_int, + code, + timestamp, + &ring->shared_ev_pointer); + spin_unlock(ring->shared_ev_pointer.lock); + if (ret) + goto error_ret; + } + return 0; +error_ret: + return ret; +} +EXPORT_SYMBOL_GPL(industrialio_store_to_ring); + +int industrialio_read_last_from_ring(struct industrialio_ring_buffer *ring, + unsigned char *data) +{ + int loopcount_copy; + unsigned char *last_written_p_copy; +again: + loopcount_copy = ring->loopcount; + barrier(); + last_written_p_copy = ring->last_written_p; + barrier(); /*unnessecary? */ + + memcpy(data, last_written_p_copy, ring->size); + + if (unlikely(loopcount_copy != ring->loopcount)) { + if (unlikely(ring->last_written_p >= last_written_p_copy)) + goto again; + } + return 0; +} +EXPORT_SYMBOL_GPL(industrialio_read_last_from_ring); + +ssize_t industrialio_interrupt_read(struct file *filep, + char *buf, + size_t count, + loff_t *f_ps) +{ + struct industrialio_event_interface *ev_int = filep->private_data; + struct industrialio_detected_event_list *el; + int ret; + /* event interface has a list of events + * - if empty and blocking, block */ + /* need lock to protect this list ?*/ + + if (list_empty(&ev_int->det_events.list)) { + if (filep->f_flags & O_NONBLOCK) + return -EAGAIN; + /* So we are blocking on this device waiting for something + * to be there */ + /* FIXME: Take into account risk that something may have + * disappeared in meantime!*/ + ret = wait_event_interruptible(ev_int->wait, + !list_empty(&ev_int + ->det_events.list)); + if (ret) + return ret; + } + + el = list_first_entry(&ev_int->det_events.list, + struct industrialio_detected_event_list, + list); + + if (copy_to_user(buf, &(el->ev), + sizeof(struct industrialio_event_data))) + return -EFAULT; + + list_del(&el->list); + ev_int->current_events--; + /* Need to zero pointer to shared element*/ + spin_lock(el->shared_pointer->lock); + if (el->shared_pointer) + (el->shared_pointer->ev_p) = NULL; + spin_unlock(el->shared_pointer->lock); + kfree(el); + + return sizeof(struct industrialio_event_data); +} + +int industrialio_interrupt_release(struct inode *inode, struct file *filep) +{ + struct industrialio_event_interface *ev_int = filep->private_data; + + module_put(ev_int->owner); + clear_bit(INDUSTRIALIO_BUSY_BIT_POS, &ev_int->handler.flags); + + return 0; +} + +int industrialio_interrupt_open(struct inode *inode, struct file *filep) +{ + struct industrialio_event_interface *ev_int = filep->private_data; + try_module_get(ev_int->owner); + + return 0; +} +static const struct file_operations industrialio_interrupt_fileops = { + .read = industrialio_interrupt_read, + .release = industrialio_interrupt_release, + .open = industrialio_interrupt_open, + .owner = THIS_MODULE, +}; + + +static ssize_t industrialio_show_attr_minor(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len; + + struct industrialio_chrdev_minor_attr *_attr + = to_industrialio_chrdev_minor_attr(attr); + len = sprintf(buf, "%d\n", _attr->minor); + + return len; +} +static inline int +industrialio_setup_ev_int(struct industrialio_event_interface *ev_int, + const char *name, + struct module *owner, + struct device *dev) +{ + int ret; + /* FIXME MAKE THIS VARIABLE! */ + ev_int->max_events = 10; + ev_int->current_events = 0; + INIT_LIST_HEAD(&ev_int->det_events.list); + init_waitqueue_head(&ev_int->wait); + ev_int->handler.fops = &industrialio_interrupt_fileops; + ev_int->handler.private = ev_int; + ev_int->handler.flags = 0; + ret = industrialio_allocate_chrdev(&ev_int->handler); + if (ret) + goto error_ret; + ev_int->attr.dev_attr.attr.name = (const char *)(name); + ev_int->attr.dev_attr.attr.owner = owner; + ev_int->owner = owner; + ev_int->attr.minor = ev_int->handler.id; + ev_int->attr.dev_attr.attr.mode = S_IRUGO; + ev_int->attr.dev_attr.show = &industrialio_show_attr_minor; + ret = sysfs_create_file(&dev->kobj, &ev_int->attr.dev_attr.attr); + if (ret) + goto error_deallocate_chrdev; + + return 0; +error_deallocate_chrdev: + industrialio_deallocate_chrdev(&ev_int->handler); +error_ret: + return ret; +} + +static inline void +industrialio_free_ev_int(struct industrialio_event_interface *ev_int, + struct device *dev) +{ + sysfs_remove_file(&dev->kobj, &ev_int->attr.dev_attr.attr); + industrialio_deallocate_chrdev(&ev_int->handler); +} + + + + +/* + * Create a character device for reading from local ring buffer. One of + * these per device. + * Create this as part of a request for a ring buffer? Yes. If it's not + * there then the device does not have an associate ring buffer! + * How to make reading through to a hardware ring buffer transparent? + */ + + +int industrialio_ring_open(struct inode *inode, struct file *filp) +{ + /* So now we need to know where this should go !*/ + struct industrialio_ring_buffer *ring = filp->private_data; + /*Fixme MESSY - should the ring have an owner?*/ + try_module_get(ring->access_minor_attr.dev_attr.attr.owner); + + return 0; +} + +int industrialio_ring_release(struct inode *inode, struct file *filp) +{ + struct industrialio_ring_buffer *ring = filp->private_data; + + module_put(ring->access_minor_attr.dev_attr.attr.owner); + clear_bit(INDUSTRIALIO_BUSY_BIT_POS, &ring->access_handler.flags); + + return 0; +} + +/* no point in ripping more than nearest number of whole records below count */ +/* Depending on movement of pointers in the meantime this may return a lot + * less than count*/ +/* Also, we aren't going to wait for enough data to be available */ +/* NEEDS CODE REVIEW */ +ssize_t industrialio_ring_rip(struct file *filp, + char *buf, + size_t count, + loff_t *f_ps) +{ + unsigned char *initial_read_p, *initial_write_p, + *current_read_p, *end_read_p; + + struct industrialio_ring_buffer *ring = filp->private_data; + unsigned char *data_cpy; + int dead_offset; + int bytes_to_rip = 0; + int max_copied; + + bytes_to_rip = (count - count % ring->skip); + /*fixme, needed? */ + if (bytes_to_rip > ring->skip*ring->length) + bytes_to_rip = ring->skip*ring->length; + data_cpy = (unsigned char *)(kmalloc(bytes_to_rip, GFP_KERNEL)); + /* Handle -ENOMEM*/ + /* build local copy */ + initial_read_p = ring->read_p; + if (unlikely(initial_read_p == 0)) + goto err; + initial_write_p = ring->write_p; + + /* Need a consistent pair */ + while (initial_read_p != ring->read_p + || initial_write_p != ring->write_p) { + initial_read_p = ring->read_p; + initial_write_p = ring->write_p; + } + if (initial_write_p == initial_read_p) + goto err; + /* write is at later location than read */ + + if (initial_write_p > initial_read_p + bytes_to_rip) { + /* write_p is greater than necessary, all is easy */ + max_copied = bytes_to_rip; + memcpy(data_cpy, initial_read_p, max_copied); + end_read_p = initial_read_p + max_copied; + } else if (initial_write_p > initial_read_p) { + /*not enough data to cpy */ + max_copied = initial_write_p - initial_read_p; + memcpy(data_cpy, initial_read_p, max_copied); + end_read_p = initial_write_p; + } else { + max_copied = ring->data + + ring->length*ring->skip - initial_read_p; + memcpy(data_cpy, initial_read_p, max_copied); + if (initial_write_p > ring->data + bytes_to_rip - max_copied) { + /* enough data to finish */ + memcpy(data_cpy, ring->data, bytes_to_rip - max_copied); + max_copied = bytes_to_rip; + end_read_p = ring->data + (bytes_to_rip - max_copied); + } else { /* not enough data */ + memcpy(data_cpy, ring->data, + initial_write_p - ring->data); + max_copied += initial_write_p - ring->data; + end_read_p = initial_write_p; + } + } + /* Now to verify which section was cleanly copied - i.e. how far + * read pointer has been pushed */ + current_read_p = ring->read_p; + + if (initial_read_p <= current_read_p) + dead_offset = current_read_p - initial_read_p; + else + dead_offset = ring->length*ring->skip - (initial_read_p + - current_read_p); + /* setup the next read position */ + ring->read_p = end_read_p; + /* possible issue if the initial write has been lapped or indeed + * the point we were reading to has been passed */ + /* No valid data read */ + if (max_copied - dead_offset < 0) + return 0; + + if (copy_to_user(buf, data_cpy + dead_offset, + max_copied - dead_offset)) + return -EFAULT; + kfree(data_cpy); + return max_copied - dead_offset; +err: + kfree(data_cpy); + return 0; +} + +static const struct file_operations industrialio_ring_fileops = { + .read = industrialio_ring_rip, + .release = industrialio_ring_release, + .open = industrialio_ring_open, + /* true? */ + .owner = THIS_MODULE, +}; + +void industrialio_free_ring_buffer(struct industrialio_ring_buffer *ring, + struct device *dev) +{ + sysfs_remove_file(&dev->kobj, &ring->access_minor_attr.dev_attr.attr); + industrialio_deallocate_chrdev(&ring->access_handler); + kfree(ring->access_minor_name); + industrialio_free_ev_int(&ring->ev_int, dev); + kfree(ring->event_minor_name); + FREE_INDUSTRIALIO_RING_BUFFER(ring); + kfree(ring); +} +EXPORT_SYMBOL_GPL(industrialio_free_ring_buffer); + +int industrialio_request_ring_buffer(int dimension, + int bytes_per_reading, + int length, + struct industrialio_ring_buffer **ring, + int id, + struct module *owner, + struct device *dev) +{ + int ret; +/* Actually do the ring buffer initialization */ + *ring = (struct industrialio_ring_buffer *) + (kmalloc(sizeof(struct industrialio_ring_buffer), + GFP_KERNEL)); + + if (*ring == NULL) { + ret = -ENOMEM; + goto error_ret; + } + INIT_INDUSTRIALIO_RING_BUFFER(*ring, + dimension, + bytes_per_reading, + length); + if ((*ring)->data == NULL) { + ret = -ENOMEM; + goto error_free_ring; + } + +/* Create and register the event character device */ + (*ring)->event_minor_name = kmalloc(20, GFP_KERNEL); + if ((*ring)->event_minor_name == NULL) { + ret = -ENOMEM; + goto error_free_ring_data; + } + sprintf((*ring)->event_minor_name, "ring_buffer%d_ev_minor", id); + + ret = industrialio_setup_ev_int(&(*ring)->ev_int, + (const char *) + ((*ring)->event_minor_name), + owner, + dev); + if (ret) + goto error_free_event_minor_name; + (*ring)->ev_int.private = (*ring); +/* Create and register the access character device */ + (*ring)->access_minor_name = kmalloc(20, GFP_KERNEL); + if ((*ring)->access_minor_name == NULL) { + ret = -ENOMEM; + goto error_free_event_interface; + } + sprintf((*ring)->access_minor_name, "ring_buffer%d_access_minor", id); + + ret = industrialio_allocate_chrdev(&(*ring)->access_handler); + if (ret) + goto error_free_access_minor_name; + (*ring)->access_handler.fops = &industrialio_ring_fileops; + (*ring)->access_handler.private = (*ring); + (*ring)->access_minor_attr.dev_attr.attr.name + = (const char *)((*ring)->access_minor_name); + (*ring)->access_minor_attr.dev_attr.attr.owner = owner; + (*ring)->access_minor_attr.dev_attr.attr.mode = S_IRUGO; + (*ring)->access_minor_attr.minor = (*ring)->access_handler.id; + (*ring)->access_minor_attr.dev_attr.show + = &industrialio_show_attr_minor; + ret = sysfs_create_file(&dev->kobj, + &(*ring)->access_minor_attr.dev_attr.attr); + if (ret) + goto error_deallocate_chrdev; + + return 0; +error_deallocate_chrdev: + industrialio_deallocate_chrdev(&(*ring)->access_handler); +error_free_access_minor_name: + kfree((*ring)->access_minor_name); +error_free_event_interface: + industrialio_free_ev_int(&(*ring)->ev_int, + dev); +error_free_event_minor_name: + kfree((*ring)->event_minor_name); +error_free_ring_data: + FREE_INDUSTRIALIO_RING_BUFFER(*ring); +error_free_ring: + kfree(*ring); +error_ret: + return ret; +} +EXPORT_SYMBOL_GPL(industrialio_request_ring_buffer); + + +static int __init industrialio_init(void) +{ + int ret; + + memset(industrialio_state.fhs, + sizeof(struct industrialio_handler *)*256, + 0); + + /* Create sysfs class */ + ret = class_register(&industrialio_class); + if (ret < 0) { + printk(KERN_ERR + "industrialio.c: could not create sysfs class\n"); + goto error_nothing; + } + + /* Register the industrialio major number character device */ + /* Various things can then result in minor numbers being allocated + * by the subsystem + * 1) Event device - possibly more than one? + * 2) Ring buffer read device + */ + + ret = register_chrdev(INDUSTRIALIO_MAJOR, "bob", &industrialio_fops); + if (ret) { + printk(KERN_ERR + "industrialio: unable to register a char major %d", + INDUSTRIALIO_MAJOR); + goto error_unregister_class; + } + + + return 0; +error_unregister_class: + class_unregister(&industrialio_class); +error_nothing: + return ret; +} + +static void __exit industrialio_exit(void) +{ + unregister_chrdev(INDUSTRIALIO_MAJOR, "bob"); + class_unregister(&industrialio_class); +} +/* A series of functions that are effectively parts of + * device register. This structure is simpler to debug than + * a unified function. + * Each has corresponding unregister function*/ + + +int industrialio_device_register_sysfs(struct industrialio_dev *dev_info) +{ + int ret; + + dev_info->sysfs_dev = device_create(&industrialio_class, + dev_info->dev, + MKDEV(0, 0), + INDUSTRIALIO_ID_FORMAT, + dev_info->id); + + if (IS_ERR(dev_info->sysfs_dev)) { + /* what would correct error here be?*/ + ret = -EINVAL; + goto error_ret; + } + /* register attributes */ + ret = sysfs_create_group(&dev_info->dev->kobj, dev_info->attrs); + if (ret) { + dev_err(dev_info->dev, "Failed to register sysfs hooks\n"); + goto error_free_sysfs_device; + } + + return 0; +error_free_sysfs_device: + device_unregister(dev_info->dev); + +error_ret: + return ret; +} + +void industrialio_device_unregister_sysfs(struct industrialio_dev *dev_info) +{ + sysfs_remove_group(&dev_info->dev->kobj, dev_info->attrs); + device_unregister(dev_info->sysfs_dev); +} + +int industrialio_device_register_id(struct industrialio_dev *dev_info) +{ + int ret; +idr_again: + if (unlikely(idr_pre_get(&industrialio_idr, GFP_KERNEL) == 0)) + return -ENOMEM; + + spin_lock(&industrialio_idr_lock); + ret = idr_get_new(&industrialio_idr, NULL, &dev_info->id); + spin_unlock(&industrialio_idr_lock); + if (unlikely(ret == -EAGAIN)) + goto idr_again; + else if (unlikely(ret)) + return ret; + + dev_info->id = dev_info->id & MAX_ID_MASK; + return 0; +} +void industrialio_device_unregister_id(struct industrialio_dev *dev_info) +{ + /* Can I use the save id? */ + int id; + if (likely(sscanf(dev_info->sysfs_dev->bus_id, + INDUSTRIALIO_ID_FORMAT, &id) == 1)) { + spin_lock(&industrialio_idr_lock); + idr_remove(&industrialio_idr, id); + spin_unlock(&industrialio_idr_lock); + } else + dev_dbg(dev_info->dev->parent, + "indio_device_unregister() failed: bad class ID!\n"); +} + +int industrialio_device_register_eventset(struct industrialio_dev *dev_info) +{ + int ret, i; + char *name; + struct device_attribute *devattr; + struct industrialio_event_attr *indio_devattr; + /* Establish whether interrupts are actually possible */ + /*fixme - still in driver for now */ + + dev_info->event_interfaces = (struct industrialio_event_interface *) + (kzalloc(sizeof(struct industrialio_event_interface) + *dev_info->num_interrupt_lines, + GFP_KERNEL)); + if (dev_info->event_interfaces == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* assign id's to the event_interface elements */ + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + dev_info->event_interfaces[i].id = i; + /* FIXME: done in event_setup as well? */ + dev_info->event_interfaces[i].owner = dev_info->driver_module; + } + dev_info->interrupts + = (struct industrialio_interrupt **) + kmalloc(sizeof(struct industrialio_interrupt *) + *dev_info->num_interrupt_lines, + GFP_KERNEL); + if (dev_info->interrupts == NULL) { + dev_err(dev_info->dev, + "Failed to register sysfs hooks for events attributes"); + ret = -ENOMEM; + goto error_free_event_interfaces; + } + + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + name = kmalloc(20, GFP_KERNEL); + if (name == NULL) { + ret = -ENOMEM; + goto error_ret; + } + sprintf(name, "event_line%d_minor", i); + ret = industrialio_setup_ev_int(&dev_info->event_interfaces[i], + (const char *)(name), + dev_info->driver_module, + dev_info->dev); + if (ret) { + dev_err(dev_info->dev, + "Could not get chrdev interface\n"); + } + } + ret = sysfs_create_group(&dev_info->dev->kobj, dev_info->event_attrs); + if (ret) { + dev_err(dev_info->dev, + "Failed to register sysfs hooks for events attributes"); + goto error_free_interrupts; + } + /* May double initialize lists in case of shared handlers, + but other than slight overhead that isn't a problem */ + i = 0; + while (1) { + if (dev_info->event_attrs->attrs[i] == NULL) + break; + devattr = container_of(dev_info->event_attrs->attrs[i], + struct device_attribute, attr); + indio_devattr = to_industrialio_event_attr(devattr); + INIT_LIST_HEAD(&indio_devattr->listel->list); + i++; + } + return 0; + +error_free_interrupts: + kfree(dev_info->interrupts); +error_free_event_interfaces: + kfree(dev_info->event_interfaces); +error_ret: + return ret; +} + +void industrialio_device_unregister_eventset(struct industrialio_dev *dev_info) +{ + int i; + for (i = 0; i < dev_info->num_interrupt_lines; i++) + industrialio_free_ev_int(&dev_info->event_interfaces[i], + dev_info->dev); + sysfs_remove_group(&dev_info->dev->kobj, dev_info->event_attrs); + kfree(dev_info->event_interfaces); +} + +/*Simple paramater read and set for ring buffer */ +static ssize_t industrialio_read_ring_dim(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len; + struct industrialio_dev *dev_info = dev_get_drvdata(dev); + + len = sprintf(buf, "%d\n", dev_info->ring_dimension); + + return len; +} + +static ssize_t industrialio_write_ring_dim(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + /* FIXME: move to dynamically adjustable as and when ring is dynamically + allocated */ + return len; +} + +static ssize_t industrialio_read_ring_length(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len; + struct industrialio_dev *dev_info = dev_get_drvdata(dev); + + len = sprintf(buf, "%d\n", dev_info->ring_length); + + return len; + + return 0; +} + +static ssize_t industrialio_write_ring_length(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + /* FIXME: move to user adjustable as and when ring is dynamically + activated */ + return len; +} + +static ssize_t industrialio_read_ring_bps(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len; + struct industrialio_dev *dev_info = dev_get_drvdata(dev); + + len = sprintf(buf, "%d\n", dev_info->ring_bytes_per_reading); + + return len; +} + +static ssize_t industrialio_write_ring_bps(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + /*Not sure it will ever make sense to make this user adjustable!*/ + return len; +} + +DEVICE_ATTR(dimension, S_IRUGO | S_IWUSR, + industrialio_read_ring_dim, + industrialio_write_ring_dim); +DEVICE_ATTR(length, S_IRUGO | S_IWUSR, + industrialio_read_ring_length, + industrialio_write_ring_length); +DEVICE_ATTR(bps, S_IRUGO | S_IWUSR, + industrialio_read_ring_bps, + industrialio_write_ring_bps); + + +static struct attribute *industrialio_ring_attributes[] = { + &dev_attr_dimension.attr, + &dev_attr_length.attr, + &dev_attr_bps.attr, + + NULL, +}; + +static const struct attribute_group industrialio_ring_attribute_group = { + .name = "ring_buffer_attributes", + .attrs = industrialio_ring_attributes, +}; + + + +int industrialio_device_register_ring(struct industrialio_dev *dev_info, int id) +{ + int ret; + + /* FIXME: actual ring alloc should be on demand, not here + * For now I'm simply ignoring any ring buffer parameter changes!*/ + ret = industrialio_request_ring_buffer(dev_info->ring_dimension, + dev_info->ring_bytes_per_reading, + dev_info->ring_length, + &dev_info->ring, + id, + dev_info->driver_module, + dev_info->dev); + if (ret < 0) + goto error_ret; + ret = sysfs_create_group(&dev_info->dev->kobj, + &industrialio_ring_attribute_group); + if (ret < 0) + goto error_free_ring; + + return 0; + +error_free_ring: + industrialio_free_ring_buffer(dev_info->ring, dev_info->dev); +error_ret: + return ret; +} + +void industrialio_device_unregister_ring(struct industrialio_dev *dev_info) +{ + sysfs_remove_group(&dev_info->dev->kobj, + &industrialio_ring_attribute_group); + /* deallocate ring buffer related stuff */ + if (dev_info->modes & (INDIO_RING_POLLED | INDIO_RING_DATA_RDY)) + industrialio_free_ring_buffer(dev_info->ring, dev_info->dev); + +} + +int industrialio_device_register(struct industrialio_dev *dev_info) +{ + int ret; + dev_set_drvdata(dev_info->dev, (void *)(dev_info)); + +/*Get a unique id */ + ret = industrialio_device_register_id(dev_info); + if (ret) + goto error_nothing; + +/* Create sysfs device */ + ret = industrialio_device_register_sysfs(dev_info); + if (ret) + goto error_free_idr; + +/* Interrupt triggered events setup */ + ret = industrialio_device_register_eventset(dev_info); + if (ret) + goto error_free_sysfs; + +/* Ring buffer init if relevant */ + /* FIXME: multiple ring buffers? */ + if (dev_info->modes & (INDIO_RING_POLLED | INDIO_RING_DATA_RDY)) { + ret = industrialio_device_register_ring(dev_info, 0); + if (ret) + goto error_free_eventset; + } + return 0; +/* Clean up the shrapnel of any failures */ + +error_free_eventset: + industrialio_device_unregister_eventset(dev_info); + +error_free_sysfs: + industrialio_device_unregister_sysfs(dev_info); + +error_free_idr: + industrialio_device_unregister_id(dev_info); + +error_nothing: + + return ret; +} +EXPORT_SYMBOL_GPL(industrialio_device_register); + +void industrialio_device_unregister(struct industrialio_dev *dev_info) +{ + if (dev_info->modes & (INDIO_RING_POLLED | INDIO_RING_DATA_RDY)) + industrialio_device_unregister_ring(dev_info); + industrialio_device_unregister_eventset(dev_info); + industrialio_device_unregister_sysfs(dev_info); + industrialio_device_unregister_id(dev_info); + +} +EXPORT_SYMBOL_GPL(industrialio_device_unregister); + +subsys_initcall(industrialio_init); +module_exit(industrialio_exit); diff -uprN -X a/Documentation/dontdiff a/drivers/industrialio/misc/Makefile b/drivers/industrialio/misc/Makefile --- a/drivers/industrialio/misc/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/industrialio/misc/Makefile 2008-06-05 11:31:46.000000000 +0100 @@ -0,0 +1,3 @@ +# +# Makefile for industrial I/O misc drivers +# \ No newline at end of file