lkml.org 
[lkml]   [2019]   [Mar]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
From
Date
SubjectRe: [PATCH v2] iio: Add driver for TLA202x ADCs
Thanks for the review. I shall make these changes in v3. One comment below.

On Sat, Mar 16, 2019 at 3:48 PM Jonathan Cameron <jic23@kernel.org> wrote:
>
> On Wed, 13 Mar 2019 12:14:03 +0100
> Ibtsam Ul-Haq <ibtsam.haq.0x01@gmail.com> wrote:
>
> > Basic driver for Texas Instruments TLA202x series ADCs. Input
> > channels are configurable from the device tree. Input scaling
> > is supported. Trigger buffer support is not yet implemented.
> >
> > Signed-off-by: Ibtsam Ul-Haq <ibtsam.haq.0x01@gmail.com>
> One quick process thing - and this varies a bit by subsystem
> so worth checking for each one what happened for previous drivers..
>
> Please don't post new versions in replies to old threads.
> They become buried in most email clients and it's not always
> obvious whether different branches of the thread are talking
> about particular versions of the driver.
>
> Much better to just start a fresh thread and rely on the
> consistent naming to make it obvious that it's a new version
> of the same patch.
>
> Anyhow, on to the review.
>
> This needs a devicetree binding document.
>
> Various minor bits inline.
>
> Jonathan
> > ---
> > Changes in v2:
> > - changed commit message
> > - Added mutex to protect channel selection
> > - Removed redundant mutex around i2c transfers
> > - Removed PROCESSED channel output
> > - Added SCALE info
> > - Removed Continuous Mode since continuous read not supported
> > - Removed SAMP_FREQ info since continuous read not supported
> > ---
> > drivers/iio/adc/Kconfig | 11 +
> > drivers/iio/adc/Makefile | 1 +
> > drivers/iio/adc/ti-tla2024.c | 463 +++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 475 insertions(+)
> > create mode 100644 drivers/iio/adc/ti-tla2024.c
> >
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> > index 5762565..8c214c8 100644
> > --- a/drivers/iio/adc/Kconfig
> > +++ b/drivers/iio/adc/Kconfig
> > @@ -816,6 +816,17 @@ config TI_AM335X_ADC
> > To compile this driver as a module, choose M here: the module will be
> > called ti_am335x_adc.
> >
> > +config TI_TLA2024
> > + tristate "Texas Instruments TLA2024/TLA2022/TLA2021 ADC driver"
> > + depends on OF
> > + depends on I2C
> > + help
> > + Say yes here to build support for Texas Instruments TLA2024,
> > + TLA2022 or TLA2021 I2C Analog to Digital Converters.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called ti-tla2024.
> > +
> > config TI_TLC4541
> > tristate "Texas Instruments TLC4541 ADC driver"
> > depends on SPI
> > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> > index 9874e05..819f35b 100644
> > --- a/drivers/iio/adc/Makefile
> > +++ b/drivers/iio/adc/Makefile
> > @@ -75,6 +75,7 @@ obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o
> > obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
> > obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> > obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
> > +obj-$(CONFIG_TI_TLA2024) += ti-tla2024.o
> > obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
> > obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
> > obj-$(CONFIG_VF610_ADC) += vf610_adc.o
> > diff --git a/drivers/iio/adc/ti-tla2024.c b/drivers/iio/adc/ti-tla2024.c
> > new file mode 100644
> > index 0000000..e902eb8
> > --- /dev/null
> > +++ b/drivers/iio/adc/ti-tla2024.c
> > @@ -0,0 +1,463 @@
> > +/* SPDX-License-Identifier: GPL-2.0
> > + *
> > + * Texas Instruments TLA2021/TLA2022/TLA2024 12-bit ADC driver
> > + *
> > + * Copyright (C) 2019 Koninklijke Philips N.V.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/i2c.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +
> > +#define TLA2024_DATA 0x00
> > +#define TLA2024_DATA_RES_MASK GENMASK(15, 4)
> > +#define TLA2024_DATA_RES_SHIFT 4
> > +
> > +#define TLA2024_CONF 0x01
> > +#define TLA2024_CONF_OS_MASK BIT(15)
> > +#define TLA2024_CONF_OS_SHIFT 15
> > +#define TLA2024_CONF_MUX_MASK GENMASK(14, 12)
> > +#define TLA2024_CONF_MUX_SHIFT 12
> > +#define TLA2024_CONF_PGA_MASK GENMASK(11, 9)
> > +#define TLA2024_CONF_PGA_SHIFT 9
> > +#define TLA2024_CONF_MODE_MASK BIT(8)
> > +#define TLA2024_CONF_MODE_SHIFT 8
> > +#define TLA2024_CONF_DR_MASK GENMASK(7, 5)
> > +#define TLA2024_CONF_DR_SHIFT 5
> > +
> > +#define TLA2024_CONV_RETRY 10
> > +
> > +struct tla202x_model {
> > + unsigned int mux_available;
> > + unsigned int pga_available;
> > +};
> > +
> > +struct tla2024 {
> > + struct i2c_client *i2c;
> > + struct tla202x_model *model;
> > + struct mutex lock; /* protect channel selection */
> > + u8 used_mux_channels;
> > +};
> > +
> > +struct tla2024_channel {
> > + int ainp;
> > + int ainn;
> > + const char *datasheet_name;
> > + bool differential;
> > +};
> > +
> > +static const struct tla2024_channel tla2024_all_channels[] = {
> > + {0, 1, "AIN0-AIN1", 1},
> > + {0, 3, "AIN0-AIN3", 1},
> > + {1, 3, "AIN1-AIN3", 1},
> > + {2, 3, "AIN2-AIN3", 1},
> > + {0, -1, "AIN0-GND", 0},
>
> Single ended, so normal convention would just be AIN0
>
> > + {1, -1, "AIN1-GND", 0},
> > + {2, -1, "AIN2-GND", 0},
> > + {3, -1, "AIN3-GND", 0},
> > +};
> > +
> > +static int tla2024_find_chan_idx(int ainp_in, int ainn_in, u16 *idx)
> > +{
> > + u16 i;
> > +
> > + for (i = 0; i < ARRAY_SIZE(tla2024_all_channels); i++) {
> > + if ((tla2024_all_channels[i].ainp == ainp_in) &&
> > + (tla2024_all_channels[i].ainn == ainn_in)) {
> > + *idx = i;
> > + return 0;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +#define TLA202x_MODEL(_mux, _pga) \
> > + { \
> > + .mux_available = (_mux), \
> > + .pga_available = (_pga), \
> > + }
> > +
> > +enum tla2024_model_id {
> > + TLA2024 = 0,
> > + TLA2022 = 1,
> > + TLA2021 = 2,
> > +};
> > +
> > +static struct tla202x_model tla202x_models[] = {
> > + TLA202x_MODEL(1, 1), // TLA2024
> [TLA2024] = TLA202x_MODEL(1, 1),
>
> Makes it clear which is which and avoids the ugly c++ comments.
>
> > + TLA202x_MODEL(0, 1), // TLA2022
> > + TLA202x_MODEL(0, 0), // TLA2021
> > +};
> > +
> > +static const int tla2024_scale[] = { /* scale, int plus micro */
> > + 3, 0, 2, 0, 1, 0, 0, 500000, 0, 250000, 0, 125000 };
> > +
> > +static int tla2024_get(struct tla2024 *priv, u8 addr, u16 mask,
> > + u16 shift, u16 *val)
> > +{
> > + int ret;
> > + u16 data;
> > +
> > + ret = i2c_smbus_read_word_swapped(priv->i2c, addr);
> > + if (ret < 0)
> > + return ret;
> > +
> > + data = ret;
> > + *val = (mask & data) >> shift;
> I'm going to guess that your shift is always just that needed
> to move the mask to 0? As such we have the really neat
> FIELD_GET macros to avoid the need to pass both shifts and
> masks around (it computes the shift from the mask).
>

yes but my understanding was that FIELD_GET/FIELD_PREP
require the "mask" to be a compile-time constant, so I could not
use them in the somewhat generic get/set.

I could still use them if I just remove the get/set. Then I could use
i2c read/write and apply FIELD_GET/PREP on the output/input with
a context-specific static mask. Is this the preferred way? Sorry if I
am missing something completely...

> > +
> > + return 0;
> > +}
> > +
> > +static int tla2024_set(struct tla2024 *priv, u8 addr, u16 mask,
> > + u16 shift, u16 val)
> > +{
> > + int ret;
> > + u16 data;
> > +
> > + ret = i2c_smbus_read_word_swapped(priv->i2c, addr);
> > + if (ret < 0)
> > + return ret;
> > +
> > + data = ret;
> > + data &= ~mask;
> > + data |= mask & (val << shift);
> > +
> > + ret = i2c_smbus_write_word_swapped(priv->i2c, addr, data);
> > +
> > + return ret;
> > +}
> > +
> > +static int tla2024_read_avail(struct iio_dev *idev,
> > + struct iio_chan_spec const *chan,
> > + const int **vals, int *type, int *length,
> > + long mask)
> > +{
> > + switch (mask) {
> > + case IIO_CHAN_INFO_SCALE:
> > +
> > + *length = ARRAY_SIZE(tla2024_scale);
> > + *vals = tla2024_scale;
> > +
> > + *type = IIO_VAL_INT_PLUS_MICRO;
> > + return IIO_AVAIL_LIST;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int tla2024_of_find_chan(struct tla2024 *priv, struct device_node *ch)
> > +{
> > + u16 chan_idx = 0;
> > + u32 tmp_p, tmp_n;
> > + int ainp, ainn;
> > + int ret;
> > +
> > + ret = of_property_read_u32_index(ch, "single-channel", 0, &tmp_p);
>
> This property isn't currently in the dt docs (I think...)
> Hence the generic binding doc needs amending.
>
> > + if (ret) {
> > + ret = of_property_read_u32_index(ch,
> > + "diff-channels", 0, &tmp_p);
> > + if (ret)
> > + return ret;
> > +
> > + ret = of_property_read_u32_index(ch,
> > + "diff-channels", 1, &tmp_n);
> > + if (ret)
> > + return ret;
> > +
> > + ainp = (int)tmp_p;
> > + ainn = (int)tmp_n;
> > + } else {
> > + ainp = (int)tmp_p;
> > + ainn = -1;
>
> Given this value is 'magic' anyway just use MAX_UINT instead and avoid
> the need for negative handling? That gets rid of the need for tmp_p, tmp_n
> above.
>
> > + }
> > +
> > + ret = tla2024_find_chan_idx(ainp, ainn, &chan_idx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* if model doesn"t have mux then only channel 0 is allowed */
> > + if (!priv->model->mux_available && chan_idx)
> > + return -EINVAL;
> > +
> > + /* if already used */
> > + if ((priv->used_mux_channels) & (1 << chan_idx))
> > + return -EINVAL;
> > +
> > + return chan_idx;
> > +}
> > +
> > +static int tla2024_init_chan(struct iio_dev *idev, struct device_node *node,
> > + struct iio_chan_spec *chan)
> > +{
> > + struct tla2024 *priv = iio_priv(idev);
> > + u16 chan_idx;
> > + int ret;
> > +
> > + ret = tla2024_of_find_chan(priv, node);
> > + if (ret < 0)
> > + return ret;
> > +
> > + chan_idx = ret;
> > + priv->used_mux_channels |= BIT(chan_idx);
> > + chan->type = IIO_VOLTAGE;
> > + chan->channel = tla2024_all_channels[chan_idx].ainp;
> > + chan->channel2 = tla2024_all_channels[chan_idx].ainn;
> > + chan->differential = tla2024_all_channels[chan_idx].differential;
> > + chan->extend_name = node->name;
> This is a bad idea. You've just created a device specific userspace ABI
> so all generic tools cannot be used. What is the intended purpose of
> doing this?
>
> > + chan->datasheet_name = tla2024_all_channels[chan_idx].datasheet_name;
> > + chan->indexed = 1;
> > + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> > + chan->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE);
> > + chan->info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE);
> > +
> > + return 0;
> > +}
> > +
> > +static int tla2024_wait(struct tla2024 *priv)
> > +{
> > + int ret;
> > + unsigned int retry = TLA2024_CONV_RETRY;
> > + u16 status;
> > +
> > + do {
> > + if (!--retry)
> > + return -EIO;
> > + ret = tla2024_get(priv, TLA2024_CONF, TLA2024_CONF_OS_MASK,
> > + TLA2024_CONF_OS_SHIFT, &status);
> > + if (ret < 0)
> > + return ret;
> > + if (!status)
> > + usleep_range(25, 1000);
> > + } while (!status);
> > +
> > + return ret;
> > +}
> > +
> > +static int tla2024_select_channel(struct tla2024 *priv,
> > + struct iio_chan_spec const *chan)
> > +{
> > + int ret;
> > + int ainp = chan->channel;
> > + int ainn = chan->channel2;
> > + u16 chan_id = 0;
> > +
> > + ret = tla2024_find_chan_idx(ainp, ainn, &chan_id);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return tla2024_set(priv, TLA2024_CONF, TLA2024_CONF_MUX_MASK,
> > + TLA2024_CONF_MUX_SHIFT, chan_id);
> > +}
> > +
> > +static int tla2024_convert(struct tla2024 *priv)
> > +{
> > + int ret = 0;
> > +
> > + ret = tla2024_set(priv, TLA2024_CONF, TLA2024_CONF_OS_MASK,
> > + TLA2024_CONF_OS_SHIFT, 1);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return tla2024_wait(priv);
> > +}
> > +
> > +static int tla2024_read_raw(struct iio_dev *idev,
> > + struct iio_chan_spec const *channel, int *val,
> > + int *val2, long mask)
> > +{
> > + struct tla2024 *priv = iio_priv(idev);
> > + int ret;
> > + u16 data;
> > + s16 tmp;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_RAW:
> > + mutex_lock(&priv->lock);
> > + ret = tla2024_select_channel(priv, channel);
> > + if (ret < 0)
> > + goto return_unlock;
> > +
> > + ret = tla2024_convert(priv);
> > + if (ret < 0)
> > + goto return_unlock;
> > +
> > + ret = tla2024_get(priv, TLA2024_DATA, TLA2024_DATA_RES_MASK,
> > + TLA2024_DATA_RES_SHIFT, &data);
> > + if (ret < 0)
> > + goto return_unlock;
> > +
> > + tmp = (s16)(data << TLA2024_DATA_RES_SHIFT);
> > + *val = tmp >> TLA2024_DATA_RES_SHIFT;
> > + ret = IIO_VAL_INT;
> > +
> The moment we get a deeply indented goto target like this it
> always suggests to me that we should consider pulling the code
> being protected by the lock out to a separate function, leaving
> the lock external so you can have a simple
>
> lock
> ret = functioncall
> unlock
> if (ret)... or just return ret;
>
> > +return_unlock:
> > + mutex_unlock(&priv->lock);
> > + return ret;
> > +
> > + case IIO_CHAN_INFO_SCALE:
> > + if (!(priv->model->pga_available)) {
> > + *val = 1; /* Scale always 1 mV when no PGA */
> > + return IIO_VAL_INT;
> > + }
> > + ret = tla2024_get(priv, TLA2024_CONF, TLA2024_CONF_PGA_MASK,
> > + TLA2024_CONF_PGA_SHIFT, &data);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* gain for the 3bit pga values 6 and 7 is same as at 5 */
> > + if (data >= (ARRAY_SIZE(tla2024_scale) >> 1))
> > + data = (ARRAY_SIZE(tla2024_scale) >> 1) - 1;
> > +
> > + *val = tla2024_scale[data << 1];
> > + *val2 = tla2024_scale[(data << 1) + 1];
> > + return IIO_VAL_INT_PLUS_MICRO;
> > +
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int tla2024_write_raw(struct iio_dev *idev,
> > + struct iio_chan_spec const *chan,
> > + int val, int val2, long mask)
> > +{
> > + struct tla2024 *priv = iio_priv(idev);
> > + int i;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_SCALE:
> > + if (!(priv->model->pga_available))
> > + return -EINVAL; /* scale can't be changed if no pga */
> > +
> > + for (i = 0; i < ARRAY_SIZE(tla2024_scale); i = i + 2) {
> > + if ((tla2024_scale[i] == val) &&
> > + (tla2024_scale[i + 1] == val2))
> > + break;
> > + }
> > +
> > + if (i == ARRAY_SIZE(tla2024_scale))
> > + return -EINVAL;
> > +
>
> This need a lock I think...
>
> > + return tla2024_set(priv, TLA2024_CONF, TLA2024_CONF_PGA_MASK,
> > ++ TLA2024_CONF_PGA_SHIFT, i >> 1);
> > +
> > + default:
> > + break;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int tla2024_of_chan_init(struct iio_dev *idev)
> > +{
> > + struct device_node *node = idev->dev.of_node;
> > + struct device_node *child;
> > + struct iio_chan_spec *channels;
> > + int ret, i, num_channels;
>
> > +
> > + num_channels = of_get_available_child_count(node);
> > + if (!num_channels) {
> > + dev_err(&idev->dev, "no channels configured\n");
> > + return -ENODEV;
> > + }
> > +
> > + channels = devm_kcalloc(&idev->dev, num_channels,
> > + sizeof(struct iio_chan_spec), GFP_KERNEL);
> > + if (!channels)
> > + return -ENOMEM;
> > +
> > + i = 0;
> > + for_each_available_child_of_node(node, child) {
> > + ret = tla2024_init_chan(idev, child, &channels[i]);
> > + if (ret) {
> > + of_node_put(child);
> > + return ret;
> > + }
> > + i++;
> > + }
> > +
> > + idev->channels = channels;
> > + idev->num_channels = num_channels;
> > +
> > + return 0;
> > +}
> > +
> > +static const struct iio_info tla2024_info = {
> > + .read_raw = tla2024_read_raw,
> > + .write_raw = tla2024_write_raw,
> > + .read_avail = tla2024_read_avail,
> > +};
> > +
> > +static int tla2024_probe(struct i2c_client *client,
> > + const struct i2c_device_id *id)
> > +{
> > + struct iio_dev *iio;
> > + struct tla2024 *priv;
> > + struct tla202x_model *model;
> > + int ret;
> > +
> > + if (!i2c_check_functionality(client->adapter,
> > + I2C_FUNC_SMBUS_WORD_DATA))
> > + return -EOPNOTSUPP;
> > +
> > + model = &tla202x_models[id->driver_data];
>
> What is the point in this local variable? I would just
> assign this directly below.
>
> > +
> > + iio = devm_iio_device_alloc(&client->dev, sizeof(*priv));
> > + if (!iio)
> > + return -ENOMEM;
> > +
> > + priv = iio_priv(iio);
> > + priv->i2c = client;
> > + priv->model = model;
> > + mutex_init(&priv->lock);
> > +
> > + iio->dev.parent = &client->dev;
> > + iio->dev.of_node = client->dev.of_node;
> > + iio->name = client->name;
> > + iio->modes = INDIO_DIRECT_MODE;
> > + iio->info = &tla2024_info;
> > +
> > + ret = tla2024_of_chan_init(iio);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = tla2024_set(priv, TLA2024_CONF, TLA2024_CONF_MODE_MASK,
> > + TLA2024_CONF_MODE_SHIFT, 1);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return devm_iio_device_register(&client->dev, iio);
> > +}
> > +
> > +static const struct i2c_device_id tla2024_id[] = {
> > + { "tla2024", TLA2024 },
> > + { "tla2022", TLA2022 },
> > + { "tla2021", TLA2021 },
>
> Really minor point, but reverse numerical order is 'unusual'..
>
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, tla2024_id);
> > +
> > +static const struct of_device_id tla2024_of_match[] = {
> > + { .compatible = "ti,tla2024" },
> > + { .compatible = "ti,tla2022" },
> > + { .compatible = "ti,tla2021" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, tla2024_of_match);
> > +
> > +static struct i2c_driver tla2024_driver = {
> > + .driver = {
> > + .name = "tla2024",
> > + .of_match_table = of_match_ptr(tla2024_of_match),
> > + },
> > + .probe = tla2024_probe,
> > + .id_table = tla2024_id,
> > +};
> > +module_i2c_driver(tla2024_driver);
> > +
> > +MODULE_AUTHOR("Ibtsam Haq <ibtsam.haq@philips.com>");
> > +MODULE_DESCRIPTION("Texas Instruments TLA2021/TLA2022/TLA2024 ADC driver");
> > +MODULE_LICENSE("GPL v2");
>

\
 
 \ /
  Last update: 2019-03-22 10:24    [W:0.138 / U:0.016 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site