From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753030AbbKAUws (ORCPT ); Sun, 1 Nov 2015 15:52:48 -0500 Received: from saturn.retrosnub.co.uk ([178.18.118.26]:48186 "EHLO saturn.retrosnub.co.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752698AbbKAUwn (ORCPT ); Sun, 1 Nov 2015 15:52:43 -0500 Subject: Re: [PATCH 2/2] iio: health: Add driver for the TI AFE4404 heart monitor To: "Andrew F. Davis" , Rob Herring , Pawel Moll , Mark Rutland , Ian Campbell , Kumar Gala , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald References: <1446309089-21094-1-git-send-email-afd@ti.com> <1446309089-21094-3-git-send-email-afd@ti.com> Cc: devicetree@vger.kernel.org, linux-iio@vger.kernel.org, linux-api@vger.kernel.org, linux-kernel@vger.kernel.org From: Jonathan Cameron Message-ID: <56367B95.3030105@kernel.org> Date: Sun, 1 Nov 2015 20:52:37 +0000 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.2.0 MIME-Version: 1.0 In-Reply-To: <1446309089-21094-3-git-send-email-afd@ti.com> Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On 31/10/15 16:31, Andrew F. Davis wrote: > Add driver for the TI AFE4404 heart rate monitor and pulse oximeter. > This device detects reflected LED light fluctuations and presents an ADC > value to the user space for further signal processing. > > Data sheet located here: > http://www.ti.com/product/AFE4404/datasheet > > Signed-off-by: Andrew F. Davis Hi Andrew, Good to see this resurface. It's a fascinating little device. Anyhow, most of the interesting bit in here is unsuprisingly concerned with the interface. I know we went round a few loops of this before but it still feels like we haven't worked out to handle it well. I would like as much input as we can get on this as inevitablly it will have repercussions outside this driver. Your approach of hammering out descriptive sysfs attributes is a good starting point but we need to work towards a formal description that can be generalised. Whilst there are not many similar devices out there to this one, I suspect there are a few and more may well show up in future. The escence of my rather roundabout response inline is that I'm suggesting adding a new channel type to represent light transmission, taking the analogous case of proximity devices in which we are looking at light reflection. I've also taken the justification we use for illuminance vs intensity readings for two sensor ALS sensors as a precident for having compound channels of a different type to the 'raw' data that feeds them. Hence I propose something along the lines of: in_intensityX_raw (raw channel value with the led on) in_intensityX_ambient_raw (raw channel value with the led off) in_intensitytransmittedX_raw the differential signal which is actually just measuring the proportion of light that got through the finger or similar. (other naming suggestions welcome!) Lots more detail inline, but I wanted anyone at a quick glance to know what we are discussing. Perhaps my suggestion is bonkers so feel free to pick it to shreds. The average channels are also unusual to handle. When would a user want to get both the average and non average channel via the triggered buffer? I propose that we might handle these generically by treating the averaging process as a filter and extending the filter interface to describe it. I also do feel that the modularity you have driven for to support your other part has perhaps come at the expense of some false complexity (I think there are easier to follow ways of keeping thing modular without as many duplicate copies of pointers as you pass around here). Jonathan p.s. Seemed like a good idea to look at this on a Sunday evening to avoid some truely terrible TV... Now for the TV I think! > --- > .../ABI/testing/sysfs-bus-iio-health-afe4404 | 70 +++ > drivers/iio/Kconfig | 1 + > drivers/iio/Makefile | 1 + > drivers/iio/health/Kconfig | 24 + > drivers/iio/health/Makefile | 6 + > drivers/iio/health/afe4404.c | 526 +++++++++++++++++++++ > drivers/iio/health/afe440x.h | 159 +++++++ > 7 files changed, 787 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-health-afe4404 > create mode 100644 drivers/iio/health/Kconfig > create mode 100644 drivers/iio/health/Makefile > create mode 100644 drivers/iio/health/afe4404.c > create mode 100644 drivers/iio/health/afe440x.h > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-health-afe4404 b/Documentation/ABI/testing/sysfs-bus-iio-health-afe4404 > new file mode 100644 > index 0000000..c67748b > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-bus-iio-health-afe4404 > @@ -0,0 +1,70 @@ > +What: /sys/bus/iio/devices/iio:deviceX/out_resistanceY_tia_raw > + /sys/bus/iio/devices/iio:deviceX/out_capacitanceY_tia_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get and set the resistance and the capacitance settings for the > + Transimpedance Amplifier. Y is 1 for Rf1 and Cf1, Y is 2 for > + Rf2 and Cf2 values. > + Resistance setting is from 0 -> 7 > + Capcitance setting is from 0 -> 15 These are defined types, so need to be in the relevant defined base units. I know it is a pain to map real world values directly to the driver units but if it actually makes sense to expose these to userspace they need to defined in the same units as every other element of that type is - so if you don't provide a scale for the 'channels' then it should be nanofarads and ohms. Could be handled as an output channel internaly with and extended_name - this sort of internally handled value is exactly what the extended_name stuff is meant to be used to identify. These are really controlling a front end analog filter, so another option would be to use the filter description attributes to handle this. They are however somewhat 'minimalist' for this case so would need extending. (this will also get complex if we start describing the average channel as a filtered channel as well). > + > +What: /sys/bus/iio/devices/iio:deviceX/tia_separate_enable > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Enable or disable separate settings for the TransImpedance > + Amplifier above, when disabled both values are set by the > + first channel. This is an 'interesting' one. Might be cleaner to just have both values exposed and switch this on and off depending on whether they are equal? That way we don't need the custom interface. Perhaps we need a brief description of why one might not want separate control? (i.e. if we have separate control and set them to the same value, what do we lose?) > + > +What: /sys/bus/iio/devices/iio:deviceX/out_current_ledY_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get and set the LED current for the specified LED. > + Y is the specific LED number. > + Values range from 0 -> 63. Current is calculated by > + current = value * 0.8mA Existence of this attribute is fine, but you need to do the relevant handling to allow it to be a standard current channel. You can't have the mysterious * 0.8mA. Also it should be called out_currentX_led_raw (so led is an extended name) IIRC we do this for some proximity sensors. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_ledY_raw > + /sys/bus/iio/devices/iio:deviceX/in_intensity_ledY_ambient_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get measured values from the ADC for these stages. Y is the > + specific LED number. The values are expressed in 24-bit twos > + complement for the specified LEDs. These are getting a little bit 'clunky' in naming. Could map them back to the simpler in_intensityX_raw and in_intensityX_ambient_raw and rely on the channel index to associate them with an LED. The led version of ambient strikes me as odd to start with given I think the LED is turned off during that measurement? This is merely to do with when they occur in the sequence? What we are really dealing with here is a single photodiode and an led sequencer. Perhaps we need a modifier that simply means the source is an led driven at the same instance? (this is the same as for proximity sensors, but there the signal is explicitly proximity). Maybe, we should be treating these as a different type entirely? They are measuring light levels, but in common with proximity sensors the 'interesting' bit is what is effecting those levels. Perhaps a new type would make sense. How about: in_intensitytransmittedX_raw in_intensityX_raw This makes a mess of the differential channels however, as suddenly they are taking the difference of two signals of different types. Ah well there goes a good plan. I'd neglected that the transmitted version is the combination of the ambient and the transmitted. This is irritatingly hard to map onto anything generic. Perhaps the next thing is to think of these a bit like the ALS sensors that use two sensors to work out what the illuminance is and do it similar to that (somewhat hiding the relationship). In that case we'd have in_intensityX_ambient_raw in_intensityX_raw (transmitted and ambient - maybe some modifier to indicate this - like we do for the hideous 'both' modifier for the visible + infrared sensitive element is some ALSs? - note both seemed sensible at the time, now the name seems bonkers - oops.) and the differential would become in_intensitytransmittedX_raw In the ALS analogy the transform is often horribly non linear so could never be represented as a differential channel (unlike here). Maybe from a userspace interface point of view the best bet here is to not represent it as a differential channel... Are all such light sensors even linear - here the assumption is the connected diode is. Perhaps that won't always hold true in future. (this is my favourite option at the moment) > + > +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_led1-led1_ambient_raw > + /sys/bus/iio/devices/iio:deviceX/in_intensity_led2-led2_ambient_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get differential values from the ADC for these stages. The > + values are expressed in 24-bit twos complement for the > + specified LEDs. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_led1-led1_ambient_mean_raw > + /sys/bus/iio/devices/iio:deviceX/in_intensity_led2-led2_ambient_mean_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get average values from the ADC for these stages. The > + values are expressed in 24-bit twos complement for the > + specified LEDs. Oh goody another weird one ;) I note in the current proposal, it's not obvious that the mean acutally applies to the differential. Another element in favour of the new channel type option. We do have a chan info element for average raw that would do the job for the sysfs attribute. However, as you are pushing this into the buffer, it doesn't work for us here. This is basically a low pass filtered version of the signal, so one option would be a duplicate channel that has the addition of filter attributes to describe that the filter applied (similar to the existing low pass filter controls). The challenge would be making it clear that the channel is infact the same as the led2 channel but with the filter. Actually, odd question, but why would someone want both the unfiltered and filtered versions via the buffered interface? > + > +What: /sys/bus/iio/devices/iio:deviceX/out_current_offdac_ledY_raw > + /sys/bus/iio/devices/iio:deviceX/out_current_offdac_ledY_ambient_raw > +Date: October 2015 > +KernelVersion: > +Contact: Andrew F. Davis > +Description: > + Get and set the offset cancellation DAC setting for these > + stages. > + Values range from 0 -> 15 Are these in mA? Not sure I like the naming here. You could either treat them as explicit output channels, or (and I'd be tempted to favour this) as calibration offsets for the in_intensitytransmitted_ channel described above (or maybe the straight intensity channels - I'm now confused on what is what here!). > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig > index 4011eff..53e1892 100644 > --- a/drivers/iio/Kconfig > +++ b/drivers/iio/Kconfig > @@ -65,6 +65,7 @@ source "drivers/iio/common/Kconfig" > source "drivers/iio/dac/Kconfig" > source "drivers/iio/frequency/Kconfig" > source "drivers/iio/gyro/Kconfig" > +source "drivers/iio/health/Kconfig" > 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..d350cb3 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 += health/ > obj-y += humidity/ > obj-y += imu/ > obj-y += light/ > diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig > new file mode 100644 > index 0000000..f5e5d82 > --- /dev/null > +++ b/drivers/iio/health/Kconfig > @@ -0,0 +1,24 @@ > +# > +# Health drivers > +# > +# When adding new entries keep the list in alphabetical order > + > +menu "Health" > + > +menu "Heart Rate Monitors" > + > +config AFE4404 > + tristate "TI AFE4404 Heart Rate Monitor" > + depends on I2C > + select IIO_BUFFER > + select IIO_TRIGGERED_BUFFER > + help > + Say yes to choose the Texas Instruments AFE4404 > + heart rate monitor and low-cost pulse oximeter. > + > + To compile this driver as a module, choose M here: the > + module will be called afe4404. > + > +endmenu > + > +endmenu > diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile > new file mode 100644 > index 0000000..c108c8d > --- /dev/null > +++ b/drivers/iio/health/Makefile > @@ -0,0 +1,6 @@ > +# > +# Makefile for IIO Health drivers > +# > +# When adding new entries keep the list in alphabetical order > + > +obj-$(CONFIG_AFE4404) += afe4404.o > diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c > new file mode 100644 > index 0000000..af65f30 > --- /dev/null > +++ b/drivers/iio/health/afe4404.c > @@ -0,0 +1,526 @@ > +/* > + * AFE4404 Heart Rate Monitors and Low-Cost Pulse Oximeters > + * > + * Author: Andrew F. Davis > + * > + * Copyright: (C) 2015 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "afe440x.h" > + > +#define AFE4404_DRIVER_NAME "afe4404" > + > +/* AFE4404 registers */ Maybe say 'AFE4404 specific registers' to make it clear there are others in the header. > +#define AFE4404_TIA_GAIN_SEP 0x20 > +#define AFE4404_TIA_GAIN 0x21 > +#define AFE4404_PROG_TG_STC 0x34 > +#define AFE4404_PROG_TG_ENDC 0x35 > +#define AFE4404_LED3LEDSTC 0x36 > +#define AFE4404_LED3LEDENDC 0x37 > +#define AFE4404_CLKDIV_PRF 0x39 > +#define AFE4404_OFFDAC 0x3a > +#define AFE4404_DEC 0x3d > +#define AFE4404_AVG_LED2_ALED2VAL 0x3f > +#define AFE4404_AVG_LED1_ALED1VAL 0x40 > + > +/* AFE4404 GAIN register fields */ Is it worth considering the regmap field stuff? That way all this could go into the field defintions, and perhaps then give a cleaner driver? > +#define AFE4404_TIA_GAIN_RES_MASK GENMASK(2, 0) > +#define AFE4404_TIA_GAIN_RES_SHIFT 0 > +#define AFE4404_TIA_GAIN_CAP_MASK GENMASK(5, 3) > +#define AFE4404_TIA_GAIN_CAP_SHIFT 3 > + > +/* AFE4404 LEDCNTRL register fields */ > +#define AFE4404_LEDCNTRL_ILED1_MASK GENMASK(5, 0) > +#define AFE4404_LEDCNTRL_ILED1_SHIFT 0 > +#define AFE4404_LEDCNTRL_ILED2_MASK GENMASK(11, 6) > +#define AFE4404_LEDCNTRL_ILED2_SHIFT 6 > +#define AFE4404_LEDCNTRL_ILED3_MASK GENMASK(17, 12) > +#define AFE4404_LEDCNTRL_ILED3_SHIFT 12 > + > +/* AFE4404 CONTROL3 register fields */ > +#define AFE440X_CONTROL3_OSC_ENABLE BIT(9) > + > +/* AFE4404 OFFDAC register current fields */ > +#define AFE4404_OFFDAC_CURR_LED1_MASK GENMASK(8, 5) > +#define AFE4404_OFFDAC_CURR_LED1_SHIFT 5 > +#define AFE4404_OFFDAC_CURR_LED2_MASK GENMASK(18, 15) > +#define AFE4404_OFFDAC_CURR_LED2_SHIFT 15 > +#define AFE4404_OFFDAC_CURR_LED3_MASK GENMASK(3, 0) > +#define AFE4404_OFFDAC_CURR_LED3_SHIFT 0 > +#define AFE4404_OFFDAC_CURR_AMB1_MASK GENMASK(13, 10) > +#define AFE4404_OFFDAC_CURR_AMB1_SHIFT 10 > +#define AFE4404_OFFDAC_CURR_AMB2_MASK GENMASK(3, 0) > +#define AFE4404_OFFDAC_CURR_AMB2_SHIFT 0 > + > +/* AFE4404 OFFDAC register polarity fields */ > +#define AFE4404_OFFDAC_POL_LED1_MASK BIT(9) > +#define AFE4404_OFFDAC_POL_LED1_SHIFT 9 > +#define AFE4404_OFFDAC_POL_LED2_MASK BIT(19) > +#define AFE4404_OFFDAC_POL_LED2_SHIFT 19 > +#define AFE4404_OFFDAC_POL_LED3_MASK BIT(4) > +#define AFE4404_OFFDAC_POL_LED3_SHIFT 4 > +#define AFE4404_OFFDAC_POL_AMB1_MASK BIT(14) > +#define AFE4404_OFFDAC_POL_AMB1_SHIFT 14 > +#define AFE4404_OFFDAC_POL_AMB2_MASK BIT(4) > +#define AFE4404_OFFDAC_POL_AMB2_SHIFT 4 > + > +/* AFE4404 TIA_GAIN_CAP values */ > +#define AFE4404_TIA_GAIN_CAP_5_P 0x0 > +#define AFE4404_TIA_GAIN_CAP_2_5_P 0x1 > +#define AFE4404_TIA_GAIN_CAP_10_P 0x2 > +#define AFE4404_TIA_GAIN_CAP_7_5_P 0x3 > +#define AFE4404_TIA_GAIN_CAP_20_P 0x4 > +#define AFE4404_TIA_GAIN_CAP_17_5_P 0x5 > +#define AFE4404_TIA_GAIN_CAP_25_P 0x6 > +#define AFE4404_TIA_GAIN_CAP_22_5_P 0x7 I'd be tempted to represent this as a lookup table instead of this set of defines. This is particular true in light of the fact the sysfs attribute needs to map them to standard units. Given that will need to be in nano farads and these are pico you only need the 'val2 part' and use the int_plus_micro form. The search will be rather more iritating as these are in a non obvious order, but such is life. Hence (I'd use the C99 asignments stuff to make it obvious that the index is important!) static const int afe4404_tia_gain_caps_femto_farads[] = { [0] = 5000, [1] = 2500, [2] = 10000, [3] = 7500, [4] = 20000, [5] = 17500, [6] = 25000, [7] = 22500 }; > + > +/* AFE4404 TIA_GAIN_RES values */ > +#define AFE4404_TIA_GAIN_RES_500_K 0x0 > +#define AFE4404_TIA_GAIN_RES_250_K 0x1 > +#define AFE4404_TIA_GAIN_RES_100_K 0x2 > +#define AFE4404_TIA_GAIN_RES_50_K 0x3 > +#define AFE4404_TIA_GAIN_RES_25_K 0x4 > +#define AFE4404_TIA_GAIN_RES_10_K 0x5 > +#define AFE4404_TIA_GAIN_RES_1_M 0x6 > +#define AFE4404_TIA_GAIN_RES_2_M 0x7 Same as for the capacitances. > + > +enum afe4404_chan_id { > + LED1VAL, > + ALED1VAL, > + LED2VAL, > + ALED2VAL, > + LED3VAL, > + LED1_ALED1VAL, > + LED2_ALED2VAL, > + AVG_LED1_ALED1VAL, > + AVG_LED2_ALED2VAL, > +}; > + > +static const struct iio_chan_spec afe4404_channels[] = { > + /* ADC values from the IC */ > + AFE440X_READ_CHAN(LED1VAL, "led1", AFE440X_LED1VAL), > + AFE440X_READ_CHAN(ALED1VAL, "led1_ambient", AFE440X_ALED1VAL), > + AFE440X_READ_CHAN(LED2VAL, "led2", AFE440X_LED2VAL), > + AFE440X_READ_CHAN(ALED2VAL, "led2_ambient", AFE440X_ALED2VAL), > + AFE440X_READ_CHAN(LED3VAL, "led3", AFE440X_ALED2VAL), > + AFE440X_READ_CHAN(LED1_ALED1VAL, "led1-led1_ambient", AFE440X_LED1_ALED1VAL), > + AFE440X_READ_CHAN(LED2_ALED2VAL, "led2-led2_ambient", AFE440X_LED2_ALED2VAL), > + AFE440X_READ_CHAN(AVG_LED1_ALED1VAL, "led1-led1_ambient_mean", AFE4404_AVG_LED1_ALED1VAL), > + AFE440X_READ_CHAN(AVG_LED2_ALED2VAL, "led2-led2_ambient_mean", AFE4404_AVG_LED2_ALED2VAL), > +}; > + > +static ssize_t afe440x_show_register(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct iio_dev *indio_dev = dev_to_iio_dev(dev); > + struct afe440x_data *afe440x = iio_device_get_drvdata(indio_dev); > + struct afe440x_reg *afe440x_reg = to_afe440x_reg(attr); > + int reg_val; > + int ret; > + > + ret = regmap_read(afe440x->regmap, afe440x_reg->reg, ®_val); > + if (ret) > + return ret; > + > + reg_val >>= afe440x_reg->shift; > + reg_val &= afe440x_reg->mask; > + > + return scnprintf(buf, PAGE_SIZE, "%u\n", reg_val); > +} > + > +static ssize_t afe440x_store_register(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct iio_dev *indio_dev = dev_to_iio_dev(dev); > + struct afe440x_data *afe440x = iio_device_get_drvdata(indio_dev); > + struct afe440x_reg *afe440x_reg = to_afe440x_reg(attr); > + unsigned val; > + int reg_val; > + int ret; > + > + if (kstrtoint(buf, 0, &val)) > + return -EINVAL; > + > + ret = regmap_read(afe440x->regmap, afe440x_reg->reg, ®_val); > + if (ret) > + return ret; > + > + reg_val &= ~afe440x_reg->mask; > + reg_val |= ((val << afe440x_reg->shift) & afe440x_reg->mask); > + > + ret = regmap_write(afe440x->regmap, afe440x_reg->reg, reg_val); > + if (ret) > + return ret; > + > + return count; > +} > + > +AFE440X_ATTR(tia_separate_enable, AFE4404_TIA_GAIN_SEP, AFE440X_TIAGAIN_ENSEPGAIN); > + > +AFE440X_ATTR(out_resistance1_tia_raw, AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_RES); > +AFE440X_ATTR(out_capacitance1_tia_raw, AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_CAP); > + > +AFE440X_ATTR(out_resistance2_tia_raw, AFE4404_TIA_GAIN_SEP, AFE4404_TIA_GAIN_RES); > +AFE440X_ATTR(out_capacitance2_tia_raw, AFE4404_TIA_GAIN_SEP, AFE4404_TIA_GAIN_CAP); I talk about these above. They look to me like they should either be treated as output channels or possibly as controls on a filter (which is what they really are) > + > +AFE440X_ATTR(out_current_offdac_led1_raw, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_LED1); > +AFE440X_ATTR(out_current_offdac_led2_raw, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_LED2); > +AFE440X_ATTR(out_current_offdac_led3_raw, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_LED3); Again, talked about earlier. These could be treated as calibration offsets (similar to the ones applied to trim gyros in high end IMUs for example). This stuff makes me wonder whether we are anywhere near descriptive enough of the analog front ends to devices we support. Probably not I guess! > + > +AFE440X_ATTR(out_current_offdac_led1_ambient_raw, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_AMB1); > +AFE440X_ATTR(out_current_offdac_led2_ambient_raw, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_AMB2); > + > +AFE440X_ATTR(out_current_led1_raw, AFE440X_LEDCNTRL, AFE4404_LEDCNTRL_ILED1); > +AFE440X_ATTR(out_current_led2_raw, AFE440X_LEDCNTRL, AFE4404_LEDCNTRL_ILED2); > +AFE440X_ATTR(out_current_led3_raw, AFE440X_LEDCNTRL, AFE4404_LEDCNTRL_ILED3); We already do this for some proximity sensors - just might be better to represent them as straight forward output channels. I suppose if we really get into it they are output intensity channels, but perhaps best to ignore that. > + > +static struct attribute *afe4404_attributes[] = { > + &afe440x_reg_tia_separate_enable.dev_attr.attr, > + &afe440x_reg_out_resistance1_tia_raw.dev_attr.attr, > + &afe440x_reg_out_capacitance1_tia_raw.dev_attr.attr, > + &afe440x_reg_out_resistance2_tia_raw.dev_attr.attr, > + &afe440x_reg_out_capacitance2_tia_raw.dev_attr.attr, > + &afe440x_reg_out_current_offdac_led1_raw.dev_attr.attr, > + &afe440x_reg_out_current_offdac_led2_raw.dev_attr.attr, > + &afe440x_reg_out_current_offdac_led3_raw.dev_attr.attr, > + &afe440x_reg_out_current_offdac_led1_ambient_raw.dev_attr.attr, > + &afe440x_reg_out_current_offdac_led2_ambient_raw.dev_attr.attr, > + &afe440x_reg_out_current_led1_raw.dev_attr.attr, > + &afe440x_reg_out_current_led2_raw.dev_attr.attr, > + &afe440x_reg_out_current_led3_raw.dev_attr.attr, > + NULL > +}; > + > +static const struct attribute_group afe4404_attribute_group = { > + .attrs = afe4404_attributes > +}; > + > +static int afe440x_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct afe440x_data *afe440x = iio_device_get_drvdata(indio_dev); > + int ret; > + > + ret = regmap_read(afe440x->regmap, chan->address, val); > + if (ret) > + return ret; > + > + *val2 = 0; There should be no need to set *val2 as it's never read if the return is IIO_VAL_INT. Nothing wrong with paranoia however ;) > + > + return IIO_VAL_INT; > +} > + > +static const struct iio_info afe4404_iio_info = { > + .attrs = &afe4404_attribute_group, > + .read_raw = afe440x_read_raw, > + .driver_module = THIS_MODULE, > +}; > + > +static irqreturn_t afe440x_trigger_handler(int irq, void *private) > +{ > + struct iio_poll_func *pf = (struct iio_poll_func *)private; Shouldn't be any need to explicitly cast when coming from a void * > + struct iio_dev *indio_dev = pf->indio_dev; > + struct afe440x_data *afe440x = iio_device_get_drvdata(indio_dev); > + int ret, bit, reg, i = 0; > + s32 buffer[10]; So there are 9 channels? Then you need space for the timestamp that needs to be 8 byte aligned. Hence this needs to be s32 buffer[12]. (iio_push_to_buffers_with_timestamp is rather odd - see the documentation) > + > + for_each_set_bit(bit, indio_dev->active_scan_mask, > + indio_dev->masklength) { > + reg = afe440x->channels[bit].address; Why using the version in afe440x (which arguably shouldn't be there) rather than the one in indio_dev->channels[bit].address? > + ret = regmap_read(afe440x->regmap, reg, &buffer[i++]); > + if (ret) > + goto err; > + } > + > + iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); > +err: > + iio_trigger_notify_done(indio_dev->trig); > + > + return IRQ_HANDLED; > +} > + > +static const struct iio_trigger_ops afe440x_trigger_ops = { > + .owner = THIS_MODULE, > +}; > + > +/* Default timings from data-sheet */ > +#define AFE4404_TIMING_PAIRS \ > + { AFE440X_PRPCOUNT, 39999 }, \ > + { AFE440X_LED2LEDSTC, 0 }, \ > + { AFE440X_LED2LEDENDC, 398 }, \ > + { AFE440X_LED2STC, 80 }, \ > + { AFE440X_LED2ENDC, 398 }, \ > + { AFE440X_ADCRSTSTCT0, 5600 }, \ > + { AFE440X_ADCRSTENDCT0, 5606 }, \ > + { AFE440X_LED2CONVST, 5607 }, \ > + { AFE440X_LED2CONVEND, 6066 }, \ > + { AFE4404_LED3LEDSTC, 400 }, \ > + { AFE4404_LED3LEDENDC, 798 }, \ > + { AFE440X_ALED2STC, 480 }, \ > + { AFE440X_ALED2ENDC, 798 }, \ > + { AFE440X_ADCRSTSTCT1, 6068 }, \ > + { AFE440X_ADCRSTENDCT1, 6074 }, \ > + { AFE440X_ALED2CONVST, 6075 }, \ > + { AFE440X_ALED2CONVEND, 6534 }, \ > + { AFE440X_LED1LEDSTC, 800 }, \ > + { AFE440X_LED1LEDENDC, 1198 }, \ > + { AFE440X_LED1STC, 880 }, \ > + { AFE440X_LED1ENDC, 1198 }, \ > + { AFE440X_ADCRSTSTCT2, 6536 }, \ > + { AFE440X_ADCRSTENDCT2, 6542 }, \ > + { AFE440X_LED1CONVST, 6543 }, \ > + { AFE440X_LED1CONVEND, 7003 }, \ > + { AFE440X_ALED1STC, 1280 }, \ > + { AFE440X_ALED1ENDC, 1598 }, \ > + { AFE440X_ADCRSTSTCT3, 7005 }, \ > + { AFE440X_ADCRSTENDCT3, 7011 }, \ > + { AFE440X_ALED1CONVST, 7012 }, \ > + { AFE440X_ALED1CONVEND, 7471 }, \ > + { AFE440X_PDNCYCLESTC, 7671 }, \ > + { AFE440X_PDNCYCLEENDC, 39199 } > + > +static const struct reg_sequence afe4404_reg_sequences[] = { > + AFE4404_TIMING_PAIRS, > + { AFE440X_CONTROL1, AFE440X_CONTROL1_TIMEREN }, > + { AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_RES_50_K }, > + { AFE440X_LEDCNTRL, (0xf << AFE4404_LEDCNTRL_ILED1_SHIFT) | > + (0x3 << AFE4404_LEDCNTRL_ILED2_SHIFT) | > + (0x3 << AFE4404_LEDCNTRL_ILED3_SHIFT) }, > + { AFE440X_CONTROL2, AFE440X_CONTROL3_OSC_ENABLE }, > +}; > + > +static const struct regmap_range afe4404_yes_ranges[] = { > + regmap_reg_range(AFE440X_LED2VAL, AFE440X_LED1_ALED1VAL), > + regmap_reg_range(AFE4404_AVG_LED2_ALED2VAL, AFE4404_AVG_LED1_ALED1VAL), > +}; > + > +static const struct regmap_access_table afe4404_volatile_table = { > + .yes_ranges = afe4404_yes_ranges, > + .n_yes_ranges = ARRAY_SIZE(afe4404_yes_ranges), > +}; > + > +static const struct regmap_config afe4404_regmap_config = { > + .reg_bits = 8, > + .val_bits = 24, > + > + .max_register = AFE4404_AVG_LED1_ALED1VAL, > + .cache_type = REGCACHE_RBTREE, > + .volatile_table = &afe4404_volatile_table, > +}; > + > +#ifdef CONFIG_OF > +static const struct of_device_id afe4404_of_match[] = { > + { .compatible = "ti,afe4404", }, > + { /* sentinel */ }, > +}; > +MODULE_DEVICE_TABLE(of, afe4404_of_match); > +#endif > + > +static int afe440x_suspend(struct device *dev) > +{ > + struct afe440x_data *afe440x = dev_get_drvdata(dev); > + int ret; > + > + ret = regmap_update_bits(afe440x->regmap, AFE440X_CONTROL2, > + AFE440X_CONTROL2_PDN_AFE, > + AFE440X_CONTROL2_PDN_AFE); > + if (ret) > + return ret; > + > + ret = regulator_disable(afe440x->regulator); > + if (ret) { > + dev_err(dev, "Failed to disable regulator\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int afe440x_resume(struct device *dev) > +{ > + struct afe440x_data *afe440x = dev_get_drvdata(dev); > + int ret; > + > + ret = regmap_update_bits(afe440x->regmap, AFE440X_CONTROL2, > + AFE440X_CONTROL2_PDN_AFE, 0); > + if (ret) > + return ret; > + > + ret = regulator_enable(afe440x->regulator); > + if (ret) { > + dev_err(dev, "Failed to enable regulator\n"); > + return ret; > + } > + > + return 0; > +} > + > +SIMPLE_DEV_PM_OPS(afe440x_pm_ops, afe440x_suspend, afe440x_resume); > + > +static int afe440x_iio_setup(struct afe440x_data *afe440x) > +{ > + struct iio_dev *indio_dev; > + int ret; > + > + indio_dev = devm_iio_device_alloc(afe440x->dev, 0); > + if (indio_dev == NULL) { > + dev_err(afe440x->dev, "Unable to allocate IIO device\n"); > + return -ENOMEM; > + } > + > + iio_device_set_drvdata(indio_dev, afe440x); > + > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->dev.parent = afe440x->dev; > + indio_dev->channels = afe440x->channels; > + indio_dev->num_channels = afe440x->num_channels; > + indio_dev->name = afe440x->name; > + indio_dev->info = afe440x->info; > + > + if (afe440x->irq > 0) { > + afe440x->trig = devm_iio_trigger_alloc(afe440x->dev, "%s-dev%d", > + indio_dev->name, > + indio_dev->id); > + if (afe440x->trig == NULL) { > + dev_err(afe440x->dev, "Unable to allocate IIO trigger\n"); > + return -ENOMEM; > + } > + > + iio_trigger_set_drvdata(afe440x->trig, indio_dev); > + > + afe440x->trig->ops = &afe440x_trigger_ops; > + afe440x->trig->dev.parent = afe440x->dev; > + > + ret = iio_trigger_register(afe440x->trig); > + if (ret) { > + dev_err(afe440x->dev, "Unable to register IIO trigger\n"); > + return ret; > + } > + > + ret = devm_request_threaded_irq(afe440x->dev, afe440x->irq, > + iio_trigger_generic_data_rdy_poll, > + NULL, IRQF_ONESHOT, > + "afe4404", afe440x->trig); > + if (ret) { > + dev_err(afe440x->dev, "Unable to request IRQ\n"); > + return ret; > + } > + } > + > + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, > + &afe440x_trigger_handler, NULL); > + if (ret) { > + dev_err(afe440x->dev, "Unable to setup buffer\n"); > + return ret; > + } > + > + ret = devm_iio_device_register(afe440x->dev, indio_dev); > + if (ret) { > + dev_err(afe440x->dev, "Unable to register IIO device\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int afe4404_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct afe440x_data *afe440x; > + int ret; > + > + afe440x = devm_kzalloc(&client->dev, sizeof(*afe440x), GFP_KERNEL); > + if (!afe440x) > + return -ENOMEM; Hmm. I guess this more of the stuff from this being a highly modular driver. Right now that is really hurting the organisation of the code. You'd be better off just using the devm_iio_device_alloc call to allocate both your private data and the iio device data. You could then pass the iio_dev to your iio_setup function if you really want to. Not bouncing through having copies of everything in your afe440x structure would also make it a lot cleaner without hurting your modularity substantially. I can see that you were aiming to completely separate the iio side from the more generic driver parts, but that division is all a bit blured and leads to more complex code. > + > + i2c_set_clientdata(client, afe440x); > + > + afe440x->dev = &client->dev; > + afe440x->irq = client->irq; > + > + afe440x->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config); > + if (IS_ERR(afe440x->regmap)) { > + dev_err(afe440x->dev, "Unable to allocate register map\n"); > + return PTR_ERR(afe440x->regmap); > + } > + > + afe440x->regulator = devm_regulator_get(afe440x->dev, "led"); > + if (IS_ERR(afe440x->regulator)) { > + dev_err(afe440x->dev, "Unable to get regulator\n"); > + return PTR_ERR(afe440x->regulator); > + } > + > + ret = regmap_write(afe440x->regmap, AFE440X_CONTROL0, > + AFE440X_CONTROL0_SW_RESET); > + if (ret) { > + dev_err(afe440x->dev, "Unable to reset device\n"); > + return ret; > + } > + > + ret = regmap_register_patch(afe440x->regmap, afe4404_reg_sequences, > + ARRAY_SIZE(afe4404_reg_sequences)); Cool. Never knew that one existed before... > + if (ret) { > + dev_err(afe440x->dev, "Unable to set register defaults\n"); > + return ret; > + } > + > + afe440x->channels = afe4404_channels; > + afe440x->num_channels = ARRAY_SIZE(afe4404_channels); > + afe440x->name = AFE4404_DRIVER_NAME; > + afe440x->info = &afe4404_iio_info; > + > + return afe440x_iio_setup(afe440x); > +} > + > +static const struct i2c_device_id afe4404_ids[] = { > + { "afe4404", 0 }, > + { /* sentinel */ }, > +}; > +MODULE_DEVICE_TABLE(i2c, afe4404_ids); > + > +static struct i2c_driver afe4404_i2c_driver = { > + .driver = { > + .name = AFE4404_DRIVER_NAME, > + .of_match_table = of_match_ptr(afe4404_of_match), > + .pm = &afe440x_pm_ops, > + }, > + .probe = afe4404_probe, > + .id_table = afe4404_ids, > +}; > +module_i2c_driver(afe4404_i2c_driver); > + > +MODULE_AUTHOR("Andrew F. Davis "); > +MODULE_DESCRIPTION("TI AFE4404 Heart Rate and Pulse Oximeter"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/iio/health/afe440x.h b/drivers/iio/health/afe440x.h > new file mode 100644 > index 0000000..2d98a20 > --- /dev/null > +++ b/drivers/iio/health/afe440x.h > @@ -0,0 +1,159 @@ > +/* > + * AFE440X Heart Rate Monitors and Low-Cost Pulse Oximeters > + * > + * Author: Andrew F. Davis > + * > + * Copyright: (C) 2015 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. > + */ > + > +#ifndef _AFE440X_H > +#define _AFE440X_H > + > +/* AFE440X registers */ > +#define AFE440X_CONTROL0 0x00 > +#define AFE440X_LED2STC 0x01 > +#define AFE440X_LED2ENDC 0x02 > +#define AFE440X_LED1LEDSTC 0x03 > +#define AFE440X_LED1LEDENDC 0x04 > +#define AFE440X_ALED2STC 0x05 > +#define AFE440X_ALED2ENDC 0x06 > +#define AFE440X_LED1STC 0x07 > +#define AFE440X_LED1ENDC 0x08 > +#define AFE440X_LED2LEDSTC 0x09 > +#define AFE440X_LED2LEDENDC 0x0a > +#define AFE440X_ALED1STC 0x0b > +#define AFE440X_ALED1ENDC 0x0c > +#define AFE440X_LED2CONVST 0x0d > +#define AFE440X_LED2CONVEND 0x0e > +#define AFE440X_ALED2CONVST 0x0f > +#define AFE440X_ALED2CONVEND 0x10 > +#define AFE440X_LED1CONVST 0x11 > +#define AFE440X_LED1CONVEND 0x12 > +#define AFE440X_ALED1CONVST 0x13 > +#define AFE440X_ALED1CONVEND 0x14 > +#define AFE440X_ADCRSTSTCT0 0x15 > +#define AFE440X_ADCRSTENDCT0 0x16 > +#define AFE440X_ADCRSTSTCT1 0x17 > +#define AFE440X_ADCRSTENDCT1 0x18 > +#define AFE440X_ADCRSTSTCT2 0x19 > +#define AFE440X_ADCRSTENDCT2 0x1a > +#define AFE440X_ADCRSTSTCT3 0x1b > +#define AFE440X_ADCRSTENDCT3 0x1c > +#define AFE440X_PRPCOUNT 0x1d > +#define AFE440X_CONTROL1 0x1e > +#define AFE440X_TIAGAIN 0x20 > +#define AFE440X_TIA_AMB_GAIN 0x21 > +#define AFE440X_LEDCNTRL 0x22 > +#define AFE440X_CONTROL2 0x23 > +#define AFE440X_ALARM 0x29 > +#define AFE440X_LED2VAL 0x2a > +#define AFE440X_ALED2VAL 0x2b > +#define AFE440X_LED1VAL 0x2c > +#define AFE440X_ALED1VAL 0x2d > +#define AFE440X_LED2_ALED2VAL 0x2e > +#define AFE440X_LED1_ALED1VAL 0x2f > +#define AFE440X_CONTROL3 0x31 > +#define AFE440X_PDNCYCLESTC 0x32 > +#define AFE440X_PDNCYCLEENDC 0x33 > + > +/* CONTROL0 register fields */ > +#define AFE440X_CONTROL0_REG_READ BIT(0) > +#define AFE440X_CONTROL0_TM_COUNT_RST BIT(1) > +#define AFE440X_CONTROL0_SW_RESET BIT(3) > + > +/* CONTROL1 register fields */ > +#define AFE440X_CONTROL1_TIMEREN BIT(8) > + > +/* TIAGAIN register fields */ > +#define AFE440X_TIAGAIN_ENSEPGAIN_MASK BIT(15) > +#define AFE440X_TIAGAIN_ENSEPGAIN_SHIFT 15 > + > +/* CONTROL2 register fields */ > +#define AFE440X_CONTROL2_PDN_AFE BIT(0) > +#define AFE440X_CONTROL2_PDN_RX BIT(1) > +#define AFE440X_CONTROL2_DYNAMIC4 BIT(3) > +#define AFE440X_CONTROL2_DYNAMIC3 BIT(4) > +#define AFE440X_CONTROL2_DYNAMIC2 BIT(14) > +#define AFE440X_CONTROL2_DYNAMIC1 BIT(20) > + > +/* CONTROL3 register fields */ > +#define AFE440X_CONTROL3_CLKDIV GENMASK(2, 0) > + > +/* CONTROL0 values */ > +#define AFE440X_CONTROL0_WRITE 0x0 > +#define AFE440X_CONTROL0_READ 0x1 > + > +#define AFE440X_READ_CHAN(_index, _name, _address) \ > + { \ > + .type = IIO_INTENSITY, \ > + .channel = _index, \ > + .address = _address, \ > + .scan_index = _index, \ > + .scan_type = { \ > + .sign = 's', \ > + .realbits = 24, \ > + .storagebits = 32, \ > + .endianness = IIO_CPU, \ > + }, \ > + .extend_name = _name, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > + } > + > +struct afe440x_reg { > + struct device_attribute dev_attr; > + u8 reg; > + unsigned long shift; > + unsigned long mask; > +}; > + > +#define to_afe440x_reg(_dev_attr) \ > + container_of(_dev_attr, struct afe440x_reg, dev_attr) > + > +#define AFE440X_ATTR(_name, _reg, _field) \ > + struct afe440x_reg afe440x_reg_##_name = { \ > + .dev_attr = __ATTR(_name, (S_IRUGO | S_IWUSR), \ > + afe440x_show_register, \ > + afe440x_store_register), \ > + .reg = _reg, \ > + .shift = _field ## _SHIFT, \ > + .mask = _field ## _MASK, \ > + } > + > +/** > + * struct afe440x_data > + * @dev - Device structure > + * @name - Device name > + * @spi - SPI device pointer the driver is attached to > + * @iolock - Read/Write mutex > + * @regmap - Register map of the device > + * @regulator - Pointer to the regulator for the IC > + * @channels - IIO channels > + * @num_channels - Number of IIO channels > + * @info - IIO info for device > + * @trig - IIO trigger for this device > + * @irq - ADC_RDY line interrupt number > +**/ > +struct afe440x_data { > + struct device *dev; This is used in remarkably few places and those are all effectively in the device probe. Perhaps just passing it directly would make sense. > + const char *name; Why have another instance of name given it's held in the iio_dev anyway? > + struct spi_device *spi; Not used anywhere that I can find. > + struct mutex iolock; Another one not used anywhere (left-overs from pre regmap?) > + struct regmap *regmap; > + struct regulator *regulator; > + struct iio_chan_spec const *channels; > + int num_channels; Having the above in here as well makes limited sense given the versions in iio_dev are identical. I'm guessing some of this was due to how the modular stuff you refer to in the cover letter was done. However, I'm suspecting that was done less than cleanly to end up with this mixture of constant and non constant elements in here. There should have been a chip_info structure consisting of entirely constant elements (such as channels etc) rather than this combined structure. > + const struct iio_info *info; Again, can't see any reason to ever have this directly in here. > + struct iio_trigger *trig; > + int irq; > +}; > + > +#endif /* _AFE440X_H */ >