> Add the TI afe4403 heart rate monitor. > This device detects reflected LED > wave length fluctuations and presents an ADC > value to the user space to be converted to a > heart rate. > Data sheet located here: > http://www.ti.com/product/AFE4403/datasheet s/heart_monitor/heart_monitors you need to update Documentation/ABI/testing/sysfs-bus-iio more minor comments below > Signed-off-by: Dan Murphy <dmurphy@xxxxxx> > --- > drivers/iio/Kconfig | 1 + > drivers/iio/Makefile | 1 + > drivers/iio/heart_monitors/Kconfig | 16 + > drivers/iio/heart_monitors/Makefile | 6 + > drivers/iio/heart_monitors/afe4403.c | 610 ++++++++++++++++++++++++++++++++++ > 5 files changed, 634 insertions(+) > create mode 100644 drivers/iio/heart_monitors/Kconfig > create mode 100644 drivers/iio/heart_monitors/Makefile > create mode 100644 drivers/iio/heart_monitors/afe4403.c > > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig > index 345395e..e3a7945 100644 > --- a/drivers/iio/Kconfig > +++ b/drivers/iio/Kconfig > @@ -66,6 +66,7 @@ source "drivers/iio/common/Kconfig" > source "drivers/iio/dac/Kconfig" > source "drivers/iio/frequency/Kconfig" > source "drivers/iio/gyro/Kconfig" > +source "drivers/iio/heart_monitors/Kconfig" heartrate would be my preference to name the category (feel free to ignore) and use heartrate / HEARTRATE consistently, not HEART_, not HEARTBEAT_ > source "drivers/iio/humidity/Kconfig" > source "drivers/iio/imu/Kconfig" > source "drivers/iio/light/Kconfig" > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile > index 698afc2..46c0960 100644 > --- a/drivers/iio/Makefile > +++ b/drivers/iio/Makefile > @@ -18,6 +18,7 @@ obj-y += common/ > obj-y += dac/ > obj-y += gyro/ > obj-y += frequency/ > +obj-y += heart_monitors/ > obj-y += humidity/ > obj-y += imu/ > obj-y += light/ > diff --git a/drivers/iio/heart_monitors/Kconfig b/drivers/iio/heart_monitors/Kconfig > new file mode 100644 > index 0000000..7060694 > --- /dev/null > +++ b/drivers/iio/heart_monitors/Kconfig > @@ -0,0 +1,16 @@ > +# > +# Heart Rate Monitor drivers > +# > +# When adding new entries keep the list in alphabetical order > + > +menu "Heart Rate Monitors" > + > +config AFE4403 > + tristate "TI AFE4403 Heart Rate Monitor" > + depends on SPI > + select IIO_BUFFER > + select IIO_TRIGGERED_BUFFER > + select SYSFS other drivers 'depend' on SYSFS rather than 'select', and use SPI_MASTER > + help > + > +endmenu > diff --git a/drivers/iio/heart_monitors/Makefile b/drivers/iio/heart_monitors/Makefile > new file mode 100644 > index 0000000..77cc5c5 > --- /dev/null > +++ b/drivers/iio/heart_monitors/Makefile > @@ -0,0 +1,6 @@ > +# > +# Makefile for IIO Heart Rate Monitor drivers > +# > + > +# When adding new entries keep the list in alphabetical order > +obj-$(CONFIG_AFE4403) += afe4403.o > diff --git a/drivers/iio/heart_monitors/afe4403.c b/drivers/iio/heart_monitors/afe4403.c > new file mode 100644 > index 0000000..d285769 > --- /dev/null > +++ b/drivers/iio/heart_monitors/afe4403.c > @@ -0,0 +1,610 @@ > +/* > + * AFE4403 Heart Rate Monitors and Low-Cost Pulse Oximeters > + * > + * Author: Dan Murphy <dmurphy@xxxxxx> > + * > + * Copyright: (C) 2014 Texas Instruments, Inc. > + * > + * 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 program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#include <linux/device.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/interrupt.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of_gpio.h> > +#include <linux/spi/spi.h> > +#include <linux/slab.h> > +#include <linux/sysfs.h> > +#include <linux/regulator/consumer.h> > + > +#include <linux/iio/iio.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/driver.h> > +#include <linux/iio/machine.h> > +#include <linux/iio/events.h> > +#include <linux/iio/kfifo_buf.h> > + > +#define AFE4403_CONTROL0 0x00 > +#define AFE4403_LED2STC 0x01 > +#define AFE4403_LED2ENDC 0x02 > +#define AFE4403_LED2LEDSTC 0x03 > +#define AFE4403_LED2LEDENDC 0x04 > +#define AFE4403_ALED2STC 0x05 > +#define AFE4403_ALED2ENDC 0x06 > +#define AFE4403_LED1STC 0x07 > +#define AFE4403_LED1ENDC 0x08 > +#define AFE4403_LED1LEDSTC 0x09 > +#define AFE4403_LED1LEDENDC 0x0a > +#define AFE4403_ALED1STC 0x0b > +#define AFE4403_ALED1ENDC 0x0c > +#define AFE4403_LED2CONVST 0x0d > +#define AFE4403_LED2CONVEND 0x0e > +#define AFE4403_ALED2CONVST 0x0f > +#define AFE4403_ALED2CONVEND 0x10 > +#define AFE4403_LED1CONVST 0x11 > +#define AFE4403_LED1CONVEND 0x12 > +#define AFE4403_ALED1CONVST 0x13 > +#define AFE4403_ALED1CONVEND 0x14 > +#define AFE4403_ADCRSTSTCT0 0x15 > +#define AFE4403_ADCRSTENDCT0 0x16 > +#define AFE4403_ADCRSTSTCT1 0x17 > +#define AFE4403_ADCRSTENDCT1 0x18 > +#define AFE4403_ADCRSTSTCT2 0x19 > +#define AFE4403_ADCRSTENDCT2 0x1a > +#define AFE4403_ADCRSTSTCT3 0x1b > +#define AFE4403_ADCRSTENDCT3 0x1c > +#define AFE4403_PRPCOUNT 0x1d > +#define AFE4403_CONTROL1 0x1e > +#define AFE4403_SPARE1 0x1f > +#define AFE4403_TIAGAIN 0x20 > +#define AFE4403_TIA_AMB_GAIN 0x21 > +#define AFE4403_LEDCNTRL 0x22 > +#define AFE4403_CONTROL2 0x23 > +#define AFE4403_SPARE2 0x24 > +#define AFE4403_SPARE3 0x25 > +#define AFE4403_SPARE4 0x26 > +#define AFE4403_ALARM 0x29 > +#define AFE4403_LED2VAL 0x2A > +#define AFE4403_ALED2VAL 0x2B > +#define AFE4403_LED1VAL 0x2C > +#define AFE4403_ALED1VAL 0x2D > +#define AFE4403_LED2_ALED2VAL 0x2E > +#define AFE4403_LED1_ALED1VAL 0x2F > +#define AFE4403_DIAG 0x30 > +#define AFE4403_CONTROL3 0x31 > +#define AFE4403_PDNCYCLESTC 0x32 > +#define AFE4403_PDNCYCLEENDC 0x33 > + > +#define AFE4403_SPI_READ (1 << 0) > +#define AFE4403_SW_RESET (1 << 3) > +#define AFE4403_PWR_DWN (1 << 0) can use BIT() > + > +/** > + * struct afe4403_data - > + * @work - Work item used to off load the enable/disable of the vibration > + * @indio_dev - > + * @map - > + * @spi - > + * @regulator - Pointer to the regulator for the IC > + * @ste_gpio - > + * @data_gpio - > + * @reset_gpio - > + * @data_buffer - > + * @irq - AFE4403 interrupt number > +**/ > +struct afe4403_data { > + struct work_struct work; > + struct iio_dev *indio_dev; > + struct iio_map *map; > + struct spi_device *spi; > + struct regulator *regulator; > + struct gpio_desc *ste_gpio; > + struct gpio_desc *data_gpio; > + struct gpio_desc *reset_gpio; > + int data_buffer[5]; magic 5 > + int irq; > +}; > + > +enum afe4403_reg_id { > + LED1VAL, > + ALED1VAL, > + LED2VAL, > + ALED2VAL, > + DIAG, > + TIAGAIN, > + TIA_AMB_GAIN, > + LEDCNTRL, > + CONTROL3, > +}; > + > +static const struct iio_event_spec afe4403_events[] = { > + { > + .type = IIO_EV_TYPE_MAG, > + .dir = IIO_EV_DIR_EITHER, > + .mask_separate = BIT(IIO_EV_INFO_VALUE) | > + BIT(IIO_EV_INFO_ENABLE), > + }, > +}; > + > +#define AFE4403_COMMON_CHAN(index, name) \ > + { \ > + .type = IIO_HEARTRATE, \ > + .indexed = 1, \ > + .channel = index, \ > + .datasheet_name = name, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ > + .scan_index = index + 1, \ could do (index), but doesn't matter why +1? > + .scan_type = { \ > + .sign = 'u', \ > + .realbits = 24, \ top 2 bits are ignored > + .storagebits = 24, \ .endianness = IIO_BE > + }, \ > + .event_spec = afe4403_events, \ > + .num_event_specs = ARRAY_SIZE(afe4403_events), \ > + } > + > +static const struct iio_chan_spec afe4403_channels[] = { > + /* Read only values from the IC */ > + [LED1VAL] = AFE4403_COMMON_CHAN(0, "LED1VAL"), > + [ALED1VAL] = AFE4403_COMMON_CHAN(1, "ALED1VAL"), > + [LED2VAL] = AFE4403_COMMON_CHAN(2, "LED2VAL"), > + [ALED1VAL] = AFE4403_COMMON_CHAN(3, "ALED2VAL"), > + [DIAG] = AFE4403_COMMON_CHAN(4, "DIAG"), > + > + /* Required writing for calibration */ > + [TIAGAIN] = AFE4403_COMMON_CHAN(5, "TIAGAIN"), > + [TIA_AMB_GAIN] = AFE4403_COMMON_CHAN(6, "TIA_AMB_GAIN"), > + [LEDCNTRL] = AFE4403_COMMON_CHAN(7, "LEDCNTRL"), > + [CONTROL3] = AFE4403_COMMON_CHAN(8, "CONTROL3"), > +}; > + > +static struct iio_map afe4403_default_iio_maps[] = { > + { > + .consumer_dev_name = "afe4403_heartrate", > + .consumer_channel = "afe4403_led1value", > + .adc_channel_label = "LED1VAL", > + }, > + { > + .consumer_dev_name = "afe4403_heartrate", > + .consumer_channel = "afe4403_aled1value", > + .adc_channel_label = "ALED1VAL", > + }, > + { > + .consumer_dev_name = "afe4403_heartrate", > + .consumer_channel = "afe4403_led2value", > + .adc_channel_label = "LED2VAL", > + }, > + { > + .consumer_dev_name = "afe4403_heartrate", > + .consumer_channel = "afe4403_aled2value", > + .adc_channel_label = "ALED2VAL", > + }, > + { > + .consumer_dev_name = "afe4403_heartrate", > + .consumer_channel = "afe4403_diagnostics", > + .adc_channel_label = "DIAG", > + }, > +}; > + > +static void afe4403_toggle_ste(struct afe4403_data *afe4403) > +{ > + gpiod_set_value(afe4403->ste_gpio, 1); > + udelay(800); > + gpiod_set_value(afe4403->ste_gpio, 0); > +} > + > +static int afe4403_read(struct afe4403_data *afe4403, u8 reg, > + unsigned int *data) > +{ > + int ret; > + > + ret = spi_write_then_read(afe4403->spi, (u8 *)®, 1, (u8 *)data, 3); > + if (ret) > + return -EIO; > + > + afe4403_toggle_ste(afe4403); > + > + return ret; > +}; > + > +static int afe4403_write(struct afe4403_data *afe4403, u8 reg, > + unsigned int data) > +{ > + int ret; > + u8 tx[4] = {0x0, 0x0, 0x0, 0x0}; > + > + /* Enable write to the device */ > + > + tx[0] = AFE4403_CONTROL0; > + tx[3] = 0x0; > + ret = spi_write(afe4403->spi, (u8 *)tx, 4); > + if (ret) > + return -EIO; > + > + afe4403_toggle_ste(afe4403); > + > + tx[0] = reg; > + tx[1] = (data & 0x0f0000) >> 16; > + tx[2] = (data & 0x00ff00) >> 8; > + tx[3] = data & 0xff; > + ret = spi_write(afe4403->spi, (u8 *)tx, 4); > + if (ret) > + return -EIO; > + > + afe4403_toggle_ste(afe4403); > + > + /* Re-Enable reading from the device */ > + tx[0] = AFE4403_CONTROL0; > + tx[1] = 0x0; > + tx[2] = 0x0; > + tx[3] = AFE4403_SPI_READ; > + ret = spi_write(afe4403->spi, (u8 *)tx, 4); > + if (ret) > + return -EIO; > + > + afe4403_toggle_ste(afe4403); > + > + return ret; > +}; > + > +static int afe4403_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct afe4403_data *data = iio_priv(indio_dev); > + u8 reg; > + > + if (chan->channel > ARRAY_SIZE(afe4403_channels)) > + return -EINVAL; > + check for channel < 5? check for val2 == 0 this is not how to control iio channels can't _SCALE be used? > + if (chan->channel == 5) > + reg = AFE4403_TIAGAIN; > + else if (chan->channel == 6) > + reg = AFE4403_TIA_AMB_GAIN; > + else if (chan->channel == 7) > + reg = AFE4403_LEDCNTRL; > + else if (chan->channel == 8) > + reg = AFE4403_CONTROL2; > + else > + return -EINVAL; > + > + return afe4403_write(data, reg, val); > +} > + > +static int afe4403_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct afe4403_data *data = iio_priv(indio_dev); > + > + if (iio_buffer_enabled(indio_dev)) > + return -EBUSY; > + > + if (chan->channel > ARRAY_SIZE(afe4403_channels)) >= and it is data_buffer[5]; 5 is smaller than ARRAY_SIZE(afe4003_channels) > + return -EINVAL; > + > + switch (mask) { > + case IIO_CHAN_INFO_PROCESSED: > + *val = data->data_buffer[chan->channel]; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > +} > + > +static int afe4403_write_event(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + int state) > +{ > + struct afe4403_data *afe4403 = iio_priv(indio_dev); > + int ret; > + int control_val; > + > + ret = afe4403_read(afe4403, AFE4403_CONTROL2, &control_val); _read() only read 3 bytes, leaving the remaining bytes of control_val uninitialized; control_val should be unsigned int wondering about endianness... > + if (ret) > + return ret; > + > + printk("***state 0x%X control 0x%X 0x%X\n", state, control_val, ~AFE4403_PWR_DWN); no printk()s please > + if (state) > + control_val &= ~AFE4403_PWR_DWN; > + else > + control_val |= AFE4403_PWR_DWN; > + > + printk("***after control 0x%X\n", control_val); > + > + ret = afe4403_write(afe4403, AFE4403_CONTROL2, control_val); > + if (ret) > + return ret; > + > + ret = afe4403_read(afe4403, AFE4403_CONTROL2, &control_val); > + if (ret) > + return ret; > + > + printk("***control 0x%X\n", control_val); > + > + return 0; > +} > + > +static const struct iio_info afe4403_iio_info = { > + .read_raw = &afe4403_read_raw, > + .write_raw = &afe4403_write_raw, > + .write_event_config = &afe4403_write_event, > + .driver_module = THIS_MODULE, > +}; > + > +static irqreturn_t afe4403_event_handler(int irq, void *data) > +{ > + struct iio_dev *indio_dev = data; > + struct afe4403_data *afe4403 = iio_priv(indio_dev); > + int ret; > + > + ret = afe4403_read(afe4403, AFE4403_LED1VAL, &afe4403->data_buffer[0]); > + if (ret) > + goto done; can't this be done all in one go? > + > + ret = afe4403_read(afe4403, AFE4403_ALED1VAL, &afe4403->data_buffer[1]); > + if (ret) > + goto done; > + > + ret = afe4403_read(afe4403, AFE4403_LED2VAL, &afe4403->data_buffer[2]); > + if (ret) > + goto done; > + > + ret = afe4403_read(afe4403, AFE4403_ALED2VAL, &afe4403->data_buffer[3]); > + if (ret) > + goto done; > + > + ret = afe4403_read(afe4403, AFE4403_DIAG, &afe4403->data_buffer[4]); > + if (ret) > + goto done; > + > + iio_push_to_buffers_with_timestamp(indio_dev, &afe4403->data_buffer, > + iio_get_time_ns()); > +done: > + return IRQ_HANDLED; > +} > + > +struct afe4403_reg { > + uint8_t reg; > + u32 value; > +} afe4403_init_regs[] = { > + { AFE4403_LED2STC, 0x0820}, > + { AFE4403_LED2ENDC, 0x0f9e }, > + { AFE4403_LED2LEDSTC, 0x07d0 }, > + { AFE4403_LED2LEDENDC, 0x0f9f }, > + { AFE4403_ALED2STC, 0x0050 }, > + { AFE4403_ALED2ENDC, 0x07ce }, > + { AFE4403_LED1STC, 0xc350 }, > + { AFE4403_LED1ENDC, 0xc350 }, > + { AFE4403_LED1LEDSTC, 0xc350 }, > + { AFE4403_LED1LEDENDC, 0xc350 }, > + { AFE4403_ALED1STC, 0x0ff0 }, > + { AFE4403_ALED1ENDC, 0x176e }, > + { AFE4403_LED2CONVST, 0x1775 }, > + { AFE4403_LED2CONVEND, 0x1f3f }, > + { AFE4403_ALED2CONVST, 0x1f45 }, > + { AFE4403_ALED2CONVEND, 0x270f }, > + { AFE4403_LED1CONVST, 0x2715 }, > + { AFE4403_LED1CONVEND, 0x2edf }, > + { AFE4403_ALED1CONVST, 0x2ee5 }, > + { AFE4403_ALED1CONVEND, 0x36af }, > + { AFE4403_ADCRSTSTCT0, 0x1770 }, > + { AFE4403_ADCRSTENDCT0, 0x1774 }, > + { AFE4403_ADCRSTSTCT1, 0x1f40 }, > + { AFE4403_ADCRSTENDCT1, 0x1f44 }, > + { AFE4403_ADCRSTSTCT2, 0x2710 }, > + { AFE4403_ADCRSTENDCT2, 0x2714 }, > + { AFE4403_ADCRSTSTCT3, 0x2ee0 }, > + { AFE4403_ADCRSTENDCT3, 0x2ee4 }, > + { AFE4403_PRPCOUNT, 0x09c3f }, > + { AFE4403_CONTROL1, 0x0107 }, > + { AFE4403_TIAGAIN, 0x8006 }, > + { AFE4403_TIA_AMB_GAIN, 0x06 }, > + { AFE4403_LEDCNTRL, 0x11414 }, > + { AFE4403_CONTROL2, 0x20000 }, > +}; > + > +static int afe4403_init(struct afe4403_data *afe4403) > +{ > + int ret, i, reg_count; > + > + /* Hard reset the device */ > + if (afe4403->reset_gpio) { > + gpiod_set_value(afe4403->reset_gpio, 0); > + udelay(800); > + gpiod_set_value(afe4403->reset_gpio, 1); > + } else { > + /* Soft reset the device */ > + ret = afe4403_write(afe4403, AFE4403_CONTROL0, AFE4403_SW_RESET); > + if (ret) > + return ret; > + } > + > + udelay(800); > + > + reg_count = sizeof(afe4403_init_regs) / sizeof(afe4403_init_regs[0]); ARRAY_SIZE() > + for (i = 0; i < reg_count; i++) { > + ret = afe4403_write(afe4403, afe4403_init_regs[i].reg, > + afe4403_init_regs[i].value); > + if (ret) > + return ret; > + } > + > + return ret; > +}; > + > +static int afe4403_iio_buffered_hardware_setup(struct iio_dev *indio_dev) > +{ > + struct iio_buffer *buffer; > + int ret; > + > + buffer = iio_kfifo_allocate(indio_dev); > + if (!buffer) > + return -ENOMEM; > + > + iio_device_attach_buffer(indio_dev, buffer); > + > + ret = iio_buffer_register(indio_dev, > + indio_dev->channels, > + indio_dev->num_channels); > + if (ret) > + goto error_kfifo_free; > + > + return 0; > + > +error_kfifo_free: > + iio_kfifo_free(indio_dev->buffer); > + return ret; > +} > + > +static int afe4403_spi_probe(struct spi_device *spi) > +{ > + struct afe4403_data *afe4403; > + struct iio_dev *indio_dev; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*afe4403)); > + if (indio_dev == NULL) { > + dev_err(&spi->dev, "Failed to allocate iio device\n"); > + return -ENOMEM; > + } > + > + spi_set_drvdata(spi, indio_dev); > + > + afe4403 = iio_priv(indio_dev); > + afe4403->spi = spi; > + > + ret = iio_map_array_register(indio_dev, afe4403_default_iio_maps); > + if (ret) { > + dev_err(&indio_dev->dev, "iio map err: %d\n", ret); > + return ret; > + } > + afe4403->map = afe4403_default_iio_maps; > + > + indio_dev->dev.parent = &spi->dev; > + indio_dev->name = "afe4403"; > + indio_dev->info = &afe4403_iio_info; > + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE; > + indio_dev->channels = afe4403_channels; > + indio_dev->num_channels = ARRAY_SIZE(afe4403_channels); > + > + afe4403->ste_gpio = devm_gpiod_get(&spi->dev, "ste"); > + if (IS_ERR(afe4403->ste_gpio)) { > + ret = PTR_ERR(afe4403->ste_gpio); > + if (ret != -ENOENT && ret != -ENOSYS) { > + dev_err(&spi->dev, "Failed to allocate spi gpio\n"); > + return ret; > + } > + afe4403->ste_gpio = NULL; > + } else { > + gpiod_direction_output(afe4403->ste_gpio, 0); > + } > + > + afe4403->data_gpio = devm_gpiod_get(&spi->dev, "data-ready"); > + if (IS_ERR(afe4403->data_gpio)) { > + ret = PTR_ERR(afe4403->data_gpio); > + if (ret != -ENOENT && ret != -ENOSYS) { > + dev_err(&spi->dev, "Failed to allocate data_ready gpio\n"); > + return ret; > + } > + afe4403->data_gpio = NULL; > + } else { > + gpiod_direction_input(afe4403->data_gpio); > + afe4403->irq = gpiod_to_irq(afe4403->data_gpio); > + } > + > + afe4403->reset_gpio = devm_gpiod_get(&spi->dev, "reset"); > + if (IS_ERR(afe4403->reset_gpio)) { > + ret = PTR_ERR(afe4403->reset_gpio); > + if (ret != -ENOENT && ret != -ENOSYS) { > + dev_err(&spi->dev, "Failed to allocate reset gpio\n"); > + return ret; > + } > + afe4403->reset_gpio = NULL; > + } else { > + gpiod_direction_output(afe4403->reset_gpio, 1); > + } > + > + ret = afe4403_iio_buffered_hardware_setup(indio_dev); > + if (ret) { > + dev_err(&spi->dev, "Failed to allocate iio buffers %i\n", ret); > + return ret; > + } > + > + ret = devm_request_threaded_irq(&spi->dev, afe4403->irq, > + NULL, > + afe4403_event_handler, > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, > + "afe4403 int", > + indio_dev); > + if (ret) { > + dev_err(&spi->dev, "Failed to allocate thread %i\n", ret); threaded irq? > + return ret; > + } > + > + ret = devm_iio_device_register(&spi->dev, indio_dev); > + if (ret < 0) > + return ret; > + > + ret = afe4403_init(afe4403); > + if (ret) { > + dev_err(&spi->dev, "Failed to init device\n"); and now what? no cleanup? > + return ret; > + } > + > + return 0; > + > +} > + > +static int afe4403_spi_remove(struct spi_device *spi) > +{ > + struct afe4403_data *afe4403 = dev_get_drvdata(&spi->dev); > + > + iio_kfifo_free(afe4403->indio_dev->buffer); > + iio_buffer_unregister(afe4403->indio_dev); > + > + if (!IS_ERR(afe4403->regulator)) > + regulator_disable(afe4403->regulator); regulator never enabled? > + > + return 0; > +} > + > +#if IS_ENABLED(CONFIG_OF) > +static const struct of_device_id afe4403_of_match[] = { > + { .compatible = "ti,afe4403", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, afe4403_of_match); > +#endif > + > +static struct spi_driver afe4403_spi_driver = { > + .driver = { > + .name = "afe4403", > + .owner = THIS_MODULE, > + .of_match_table = of_match_ptr(afe4403_of_match), > + }, > + .probe = afe4403_spi_probe, > + .remove = afe4403_spi_remove, > +}; > + > +static int __init afe4403_spi_init(void) > +{ > + return spi_register_driver(&afe4403_spi_driver); > +} > +module_init(afe4403_spi_init); > + > +MODULE_AUTHOR("Dan Murphy <dmurphy@xxxxxx>"); > +MODULE_DESCRIPTION("TI AFE4403 Heart Rate and Pulse Oximeter"); > +MODULE_LICENSE("GPL"); > -- Peter Meerwald +43-664-2444418 (mobile) -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html