Re: [PATCH v2 2/2] iio: distance: srf08: add IIO driver for us ranger

From: Jonathan Cameron
Date: Sun Jan 15 2017 - 08:13:02 EST


On 14/01/17 17:48, Andreas Klinger wrote:
> Hi Jonathan,
>
> see comments below.
>
> Andreas
>
>
> Jonathan Cameron <jic23@xxxxxxxxxx> schrieb am Sat, 14. Jan 12:17:
>> On 10/01/17 18:48, Andreas Klinger wrote:
>>> This is the IIO driver for devantech srf08 ultrasonic ranger which can be
>>> used to measure the distances to an object.
>>>
>>> The sensor supports I2C with some registers.
>>>
>>> Supported Features include:
>>> - read the distance in ranging mode in centimeters
>>> - output of the driver is directly the read value
>>> - together with the scale the driver delivers the distance in meters
>>> - only the first echo of the nearest object is delivered
>>> - set max gain register; userspace enters analogue value
>>> - set range registers; userspace enters range in millimeters in 43 mm steps
>>>
>>> Features not supported by this driver:
>>> - ranging mode in inches or in microseconds
>>> - ANN mode
>>> - change I2C address through this driver
>>> - light sensor
>>>
>>> The driver was added in the directory "proximity" of the iio subsystem
>>> in absence of another directory named "distance".
>>> There is also a new submenu "distance"
>> Hi Andreas,
>>
>> Sorry it took me a while to get to this!
>>
>> I'd not bother with the new submenu. Perhaps we should rename the
>> proximity menu to proximity/distance.
>>
>> We already the lightening detector in there which is definitely not
>> measuring proximity in the convetional sense!
>>
>> Anyhow, the actual code is fine, but we need to think about how the
>> userspace ABI fits within the wider IIO ABI. Naming and approaches
>> that make sense in a single class of drivers can end up meaining
>> very different things for other drivers. Various suggestions inline.
>>
>> Jonathan
>>>
>>> Signed-off-by: Andreas Klinger <ak@xxxxxxxxxxxxx>
>>> ---
>>> drivers/iio/proximity/Kconfig | 15 ++
>>> drivers/iio/proximity/Makefile | 1 +
>>> drivers/iio/proximity/srf08.c | 362 +++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 378 insertions(+)
>>> create mode 100644 drivers/iio/proximity/srf08.c
>>>
>>> diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
>>> index ef4c73db5b53..7b10a137702b 100644
>>> --- a/drivers/iio/proximity/Kconfig
>>> +++ b/drivers/iio/proximity/Kconfig
>>> @@ -46,3 +46,18 @@ config SX9500
>>> module will be called sx9500.
>>>
>>> endmenu
>>> +
>>> +menu "Distance sensors"
>>> +
>>> +config SRF08
>>> + tristate "Devantech SRF08 ultrasonic ranger sensor"
>>> + depends on I2C
>>> + help
>>> + Say Y here to build a driver for Devantech SRF08 ultrasonic
>>> + ranger sensor. This driver can be used to measure the distance
>>> + of objects.
>>> +
>>> + To compile this driver as a module, choose M here: the
>>> + module will be called srf08.
>>> +
>>> +endmenu
>>> diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
>>> index 9aadd9a8ee99..e914c2a5dd49 100644
>>> --- a/drivers/iio/proximity/Makefile
>>> +++ b/drivers/iio/proximity/Makefile
>>> @@ -5,4 +5,5 @@
>>> # When adding new entries keep the list in alphabetical order
>>> obj-$(CONFIG_AS3935) += as3935.o
>>> obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o
>>> +obj-$(CONFIG_SRF08) += srf08.o
>>> obj-$(CONFIG_SX9500) += sx9500.o
>>> diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c
>>> new file mode 100644
>>> index 000000000000..f38c74ed0933
>>> --- /dev/null
>>> +++ b/drivers/iio/proximity/srf08.c
>>> @@ -0,0 +1,362 @@
>>> +/*
>>> + * srf08.c - Support for Devantech SRF08 ultrasonic ranger
>>> + *
>>> + * Copyright (c) 2016 Andreas Klinger <ak@xxxxxxxxxxxxx>
>>> + *
>>> + * This file is subject to the terms and conditions of version 2 of
>>> + * the GNU General Public License. See the file COPYING in the main
>>> + * directory of this archive for more details.
>>> + *
>>> + * For details about the device see:
>>> + * http://www.robot-electronics.co.uk/htm/srf08tech.html
>>> + */
>>> +
>>> +#include <linux/err.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/module.h>
>>> +#include <linux/bitops.h>
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/sysfs.h>
>>> +
>>> +/* registers of SRF08 device */
>>> +#define SRF08_WRITE_COMMAND 0x00 /* Command Register */
>>> +#define SRF08_WRITE_MAX_GAIN 0x01 /* Max Gain Register: 0 .. 31 */
>>> +#define SRF08_WRITE_RANGE 0x02 /* Range Register: 0 .. 255 */
>>> +#define SRF08_READ_SW_REVISION 0x00 /* Software Revision */
>>> +#define SRF08_READ_LIGHT 0x01 /* Light Sensor during last echo */
>>> +#define SRF08_READ_ECHO_1_HIGH 0x02 /* Range of first echo received */
>>> +#define SRF08_READ_ECHO_1_LOW 0x03 /* Range of first echo received */
>>> +
>>> +#define SRF08_CMD_RANGING_CM 0x51 /* Ranging Mode - Result in cm */
>>> +
>>> +#define SRF08_DEFAULT_GAIN 1025 /* max. analogue value of Gain */
>>> +#define SRF08_DEFAULT_RANGE 11008 /* max. value of Range in mm */
>>> +
>>> +struct srf08_data {
>>> + struct i2c_client *client;
>>> + int gain; /* Max Gain */
>>> + int range_mm; /* Range in mm */
>>> + struct mutex lock;
>>> +};
>>> +
>>> +static const int srf08_gain[] = {
>>> + 94, 97, 100, 103, 107, 110, 114, 118,
>>> + 123, 128, 133, 139, 145, 152, 159, 168,
>>> + 177, 187, 199, 212, 227, 245, 265, 288,
>>> + 317, 352, 395, 450, 524, 626, 777, 1025 };
>>> +
>>> +static int srf08_read_ranging(struct srf08_data *data)
>>> +{
>>> + struct i2c_client *client = data->client;
>>> + int ret, i;
>>> +
>>> + mutex_lock(&data->lock);
>>> +
>>> + ret = i2c_smbus_write_byte_data(data->client,
>>> + SRF08_WRITE_COMMAND, SRF08_CMD_RANGING_CM);
>>> + if (ret < 0) {
>>> + dev_err(&client->dev, "write command - err: %d\n", ret);
>>> + mutex_unlock(&data->lock);
>>> + return ret;
>>> + }
>>> +
>>> + /*
>>> + * normally after 65 ms the device should have the read value
>>> + * we round it up to 100 ms
>> I'd suggest this should be adapted so that it takes advantage of knowing
>> roughly how long it is going to take as the 'range' maximum is changed.
>> So perhaps in the basic case, sleep for 65 msecs, then poll at 5msec
>> intervals. If we know it's going to be a lot faster, then poll it from
>> an earlier time.
>>> + *
>>> + * we read here until a correct version number shows up as
>>> + * suggested by the documentation
>>> + */
>>> + for (i = 0; i < 5; i++) {
>>> + ret = i2c_smbus_read_byte_data(data->client,
>>> + SRF08_READ_SW_REVISION);
>>> +
>>> + /* check if a valid version number is read */
>>> + if (ret < 255 && ret > 0)
>>> + break;
>>> + msleep(20);
>>> + }
>>> +
>>> + if (ret >= 255 || ret <= 0) {
>>> + dev_err(&client->dev, "device not ready\n");
>>> + mutex_unlock(&data->lock);
>>> + return -EIO;
>>> + }
>>> +
>>> + ret = i2c_smbus_read_word_swapped(data->client,
>>> + SRF08_READ_ECHO_1_HIGH);
>>> + if (ret < 0) {
>>> + dev_err(&client->dev, "cannot read distance: ret=%d\n", ret);
>>> + mutex_unlock(&data->lock);
>>> + return ret;
>>> + }
>>> +
>>> + mutex_unlock(&data->lock);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int srf08_read_raw(struct iio_dev *indio_dev,
>>> + struct iio_chan_spec const *channel, int *val,
>>> + int *val2, long mask)
>>> +{
>>> + struct srf08_data *data = iio_priv(indio_dev);
>>> + int ret;
>>> +
>>> + if (channel->type != IIO_DISTANCE)
>>> + return -EINVAL;
>>> +
>>> + switch (mask) {
>>> + case IIO_CHAN_INFO_RAW:
>>> + ret = srf08_read_ranging(data);
>>> + if (ret < 0)
>>> + return ret;
>>> + *val = ret;
>>> + return IIO_VAL_INT;
>>> + case IIO_CHAN_INFO_SCALE:
>>> + /* 1 LSB is 1 cm */
>>> + *val = 0;
>>> + *val2 = 10000;
>>> + return IIO_VAL_INT_PLUS_MICRO;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +}
>>> +
>>> +static ssize_t srf08_show_range_mm_available(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + int i, len = 0;
>>> +
>>> + for (i = 0; i < 256; i++)
>>> + len += scnprintf(buf + len, PAGE_SIZE - len,
>>> + "%d ", (i + 1) * 43);
>>> +
>>> + buf[len - 1] = '\n';
>>> +
>>> + return len;
>>> +}
>>> +
>>> +static IIO_DEVICE_ATTR(range_mm_available, S_IRUGO,
>>> + srf08_show_range_mm_available, NULL, 0);
>>> +
>>> +static ssize_t srf08_show_range_mm(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct srf08_data *data = iio_priv(indio_dev);
>>> +
>>> + return sprintf(buf, "%d\n", data->range_mm);
>>> +}
>>> +
>>> +/*
>>> + * set the range of the sensor to an even multiple of 43 mm
>>> + * which corresponds to 1 LSB in the register
>>> + *
>>> + * register value corresponding range
>>> + * 0x00 43 mm
>>> + * 0x01 86 mm
>>> + * 0x02 129 mm
>>> + * ...
>>> + * 0xFF 11008 mm
>>> + */
>>> +static ssize_t srf08_write_range_mm(struct device *dev,
>>> + struct device_attribute *attr,
>>> + const char *buf, size_t len)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct srf08_data *data = iio_priv(indio_dev);
>>> + struct i2c_client *client = data->client;
>>> + int ret;
>>> + unsigned int val, mod;
>>> + u8 regval;
>>> +
>>> + ret = kstrtouint(buf, 10, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = val / 43 - 1;
>>> + mod = val % 43;
>>> +
>>> + if (mod || (ret < 0) || (ret > 255))
>>> + return -EINVAL;
>>> +
>>> + regval = ret;
>>> +
>>> + mutex_lock(&data->lock);
>>> +
>>> + ret = i2c_smbus_write_byte_data(data->client,
>>> + SRF08_WRITE_RANGE, regval);
>>> + if (ret < 0) {
>>> + dev_err(&client->dev, "write_range - err: %d\n", ret);
>>> + mutex_unlock(&data->lock);
>>> + return ret;
>>> + }
>>> +
>>> + data->range_mm = val;
>>> +
>>> + mutex_unlock(&data->lock);
>>> +
>>> + return len;
>>> +}
>>> +
>>> +static IIO_DEVICE_ATTR(range_mm, S_IRUGO | S_IWUSR,
>>> + srf08_show_range_mm, srf08_write_range_mm, 0);
>>> +
>>> +static ssize_t srf08_show_gain_available(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + int i, len = 0;
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(srf08_gain); i++)
>>> + len += sprintf(buf + len, "%d ", srf08_gain[i]);
>>> +
>>> + len += sprintf(buf + len, "\n");
>>> +
>>> + return len;
>>> +}
>>> +
>>> +static IIO_DEVICE_ATTR(gain_available, S_IRUGO,
>>> + srf08_show_gain_available, NULL, 0);
>>> +
>>> +static ssize_t srf08_show_gain(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct srf08_data *data = iio_priv(indio_dev);
>>> + int len;
>>> +
>>> + len = sprintf(buf, "%d\n", data->gain);
>>> +
>>> + return len;
>>> +}
>>> +
>>> +static ssize_t srf08_write_gain(struct device *dev,
>>> + struct device_attribute *attr,
>>> + const char *buf, size_t len)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct srf08_data *data = iio_priv(indio_dev);
>>> + struct i2c_client *client = data->client;
>>> + int ret, i;
>>> + unsigned int val;
>>> + u8 regval;
>>> +
>>> + ret = kstrtouint(buf, 10, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(srf08_gain); i++)
>>> + if (val == srf08_gain[i]) {
>>> + regval = i;
>>> + break;
>>> + }
>>> +
>>> + if (i >= ARRAY_SIZE(srf08_gain))
>>> + return -EINVAL;
>>> +
>>> + mutex_lock(&data->lock);
>>> +
>>> + ret = i2c_smbus_write_byte_data(data->client,
>>> + SRF08_WRITE_MAX_GAIN, regval);
>>> + if (ret < 0) {
>>> + dev_err(&client->dev, "write_gain - err: %d\n", ret);
>>> + mutex_unlock(&data->lock);
>>> + return ret;
>>> + }
>>> +
>>> + data->gain = val;
>>> +
>>> + mutex_unlock(&data->lock);
>>> +
>>> + return len;
>>> +}
>>> +
>>> +static IIO_DEVICE_ATTR(gain, S_IRUGO | S_IWUSR,
>>> + srf08_show_gain, srf08_write_gain, 0);
>>> +
>>> +static struct attribute *srf08_attributes[] = {
>>> + &iio_dev_attr_range_mm.dev_attr.attr,
>>> + &iio_dev_attr_range_mm_available.dev_attr.attr,
>>> + &iio_dev_attr_gain.dev_attr.attr,
>>> + &iio_dev_attr_gain_available.dev_attr.attr,
>> Hmm. Custom attributes always give us issues. The primary point of IIO
>> is to enforce (more or less) standard interfaces.
>>
>> If you do need to add something new then that is fine (and I do think
>> you need to here!).
>>
>> They need to be formally proposed as an addition to the ABI with
>> docs in /Documentation/ABI/testing/sysfs-bus-iio*
>>
>> Once we take one driver using it it becomes part of our ABI that
>> userspace will need to handle, hence we consider these very
>> carefully.
>>
>> My gut feeling would be that gain needs to be more specific as it's
>> a term that can mean very different things.. Here we are talking
>> about an amplifier on a signal that we are then looking at the timing
>> of. It might otherwise be interpretted as another term for what
>> we term 'scale' in IIO.
>>
>> So what to call it... Perhaps afegain for Analog front end gain?
>> We might want to add this to the core supported attrs, but lets
>> not do so until we see if we have this on a number of devices.
>>
>
> In /Documentation/ABI/testing/sysfs-bus-iio-proximity-as3935 there is also a
> gain used in a similar situation and it's called there "sensor_sensitivity"
>
> What it we also use this name here?
It's not ideal as it's not linked to a particular channel or anything,
but lets go with that as at least we will be consistent between drivers.
>
>> The description would need to make it explicit that this gain is
>> for cases where we aren't measuring the magnitude of what is
>> being amplified.
>>
>> For the range, it's an interesting one. Again the term range could
>> mean too many things within the wider ABI. We need to make it more
>> specific.
>>
>> Actually reading the datasheet, I think this is fundamentally about the
>> maximum sampling frequency rather than directly about the range.
>> The only reason you'd reduce the range is to speed that up. It doesn't
>> improve the resolution, the device simply answers quicker.
>>
>> So I'd support this as sampling_frequency. You could then use
>> the the iio_info_mask_*_available and relevant callback to provide
>> info on what it then restricts the possible output values to
>> (rather than controlling it directly).
>>
>
> By changing the range one cannot influence the sampling frequency directly. I
> have seen on the oszilloscope that the telegrams arrive almost at the same time
> with different settings of range and the same gain.
>
> Only if the gain is also adjusted the sensor works faster and a higher frequency
> can be used. But the gain is also used to adjust the sensitivity of the sensor.
That's rather weird and not what the datasheet suggests. Ah well.
>
> What about calling it "sensor_domain" or "sensor_max_range"?
hmm. Not sure - propose that with appropriate Docs and we can think more on it.
>
>
>>> + NULL,
>>> +};
>>> +
>>> +static const struct attribute_group srf08_attribute_group = {
>>> + .attrs = srf08_attributes,
>>> +};
>>> +
>>> +static const struct iio_chan_spec srf08_channels[] = {
>>> + {
>>> + .type = IIO_DISTANCE,
>>> + .info_mask_separate =
>>> + BIT(IIO_CHAN_INFO_RAW) |
>>> + BIT(IIO_CHAN_INFO_SCALE),
>>> + },
>>> +};
>>> +
>>> +static const struct iio_info srf08_info = {
>>> + .read_raw = srf08_read_raw,
>>> + .attrs = &srf08_attribute_group,
>>> + .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static int srf08_probe(struct i2c_client *client,
>>> + const struct i2c_device_id *id)
>>> +{
>>> + struct iio_dev *indio_dev;
>>> + struct srf08_data *data;
>>> +
>>> + if (!i2c_check_functionality(client->adapter,
>>> + I2C_FUNC_SMBUS_READ_BYTE_DATA |
>>> + I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
>>> + I2C_FUNC_SMBUS_READ_WORD_DATA))
>>> + return -ENODEV;
>>> +
>>> + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> +
>>> + data = iio_priv(indio_dev);
>>> + i2c_set_clientdata(client, indio_dev);
>>> + data->client = client;
>>> +
>>> + /*
>>> + * set default values of device here
>>> + * these values are already set on the hardware after power on
>>> + */
>>> + data->gain = SRF08_DEFAULT_GAIN;
>>> + data->range_mm = SRF08_DEFAULT_RANGE;
>> We should be a little careful with assumptions about the device having
>> just been powered on. The driver might simply have been removed and
>> reprobed. So I'd sugest rewriting them whatever to be sure we have
>> what we expect. Either that or if they can be read back, then just
>> always retrieve them from the device.
>
> You are right.
> Then i'll set the default value at the sensor, because it cannot be read.
>
>>> +
>>> + indio_dev->name = dev_name(&client->dev);
>>> + indio_dev->dev.parent = &client->dev;
>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>> + indio_dev->info = &srf08_info;
>>> + indio_dev->channels = srf08_channels;
>>> + indio_dev->num_channels = ARRAY_SIZE(srf08_channels);
>>> +
>>> + mutex_init(&data->lock);
>>> +
>>> + return devm_iio_device_register(&client->dev, indio_dev);
>>> +}
>>> +
>>> +static const struct i2c_device_id srf08_id[] = {
>>> + { "srf08", 0 },
>>> + { }
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, srf08_id);
>>> +
>>> +static struct i2c_driver srf08_driver = {
>>> + .driver = {
>>> + .name = "srf08",
>>> + },
>>> + .probe = srf08_probe,
>>> + .id_table = srf08_id,
>>> +};
>>> +module_i2c_driver(srf08_driver);
>>> +
>>> +MODULE_AUTHOR("Andreas Klinger <ak@xxxxxxxxxxxxx>");
>>> +MODULE_DESCRIPTION("Devantech SRF08 ultrasonic ranger driver");
>>> +MODULE_LICENSE("GPL");
>>>
>>
>