Peter Sorry for the delayed reply I was working on the code and comments On 10/15/2014 04:52 AM, Peter Meerwald wrote: >> 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 Will update > >> 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_ Noted but not changed unless there are are other objections > >> 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 Fixed > >> + 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() Fixed. I need to update these defines per the data sheet to get rid of the magic numbers below. I am wondering if a header file may be in order thoughts? > >> + >> +/** >> + * 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 I can #define this but this is the number of registers we read and report from the IC > >> + 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? Fixed > >> + .scan_type = { \ >> + .sign = 'u', \ >> + .realbits = 24, \ > top 2 bits are ignored Not sure what you mean here. How are the top two bits ignored? > >> + .storagebits = 24, \ > .endianness = IIO_BE Fixed > >> + }, \ >> + .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? I will need to investigate this. The device needs to be calibrated and these are the registers we need to write. I did struggle a little with understanding how these things are put together. > >> + 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) It is meant to be smaller we only read and report 5 registers to the user space. The other registers are written to during calibration. > >> + 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... Fixed > >> + if (ret) >> + return ret; >> + >> + printk("***state 0x%X control 0x%X 0x%X\n", state, control_val, ~AFE4403_PWR_DWN); > no printk()s please My bad. This was an artifact from debugging. > >> + 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? I was thinking this I just need to adjust the read api in the code to take an argument for the number of registers to read. > >> + >> + 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() Fixed > >> + 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? Yes. What is the objection? Would there be a preference that I read the data from the IC in an IRQ context? > >> + 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? What is it that needs cleanup? I was careful to use devm calls which should self clean up on probe failure. Since the init is all spi writes should the spi write fail then the probe fails and all subsequent devm calls self cleanup. The only call made that is not devm is iio_map_array_register > >> + 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? Fixed > >> + >> + 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"); >> -- ------------------ Dan Murphy -- 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