Re: [PATCH 1/2] iio: humidity: Add support for Sensirion SHTC3 sensor

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Jonathan,

Le dim. 14 nov. 2021 à 17:44, Jonathan Cameron <jic23@xxxxxxxxxx> a écrit :
>
> On Sun, 14 Nov 2021 15:58:15 +0100
> Gilles Talis <gilles.talis@xxxxxxxxx> wrote:
>
> > Hi Lars,
> >
> > Le dim. 14 nov. 2021 à 14:47, Lars-Peter Clausen <lars@xxxxxxxxxx> a écrit :
> >
> > > On 11/14/21 2:23 PM, Gilles Talis wrote:
> > > > The SHTC3 is a digital humidity and temperature sensor.
> > > > It covers humidity measurement range from 0 to 100% relative humidity
> > > > and temperature measurement range from -45 to 125 deg C.
> > > >
> > > > Datasheet: https://www.sensirion.com/file/datasheet_shtc3
> > > >
> > > > Signed-off-by: Gilles Talis <gilles.talis@xxxxxxxxx>
> > >
> > > Hi,
> > >
> > > Thanks for the path. This looks really good, very well written driver.
> > >
> > > But we already have support for the shtc3 in the Linux kernel as part of
> > > the hwmon framework, see drivers/hwmon/shtc1.c.
> > >
> > > - Lars
> > >
> > Thanks for the review. Oops, I should have been more careful. Next time, I
> > will try to be.
> > Sorry for the spamming.
>
> The fun question of whether humidity drivers should be in IIO or HWMON was my
> fault many years ago.  Pre IIO being anywhere near ready for mainline (and mostly
> a concept rather than code) I wanted to get at least one of the drivers I was
> working with upstream and the characteristics of humidity drivers were rather
> different from ADCs and Accelerometers etc so I asked if Humidty counted as
> hardware monitoring and got something like "sure it could be" as a reply so
> upstreamed one driver in hwmon (sht15).
>
> Ever since it's been a bit random on where humidity drivers end up based on
> who is submitting them and their usecase + most similar drivers already upstream.
>
> Sorry you fell into this historical quirk!
No problem. Thanks for taking the time to explain the history. Next
time, I will make sure I also look in other subsystems before
submitting such a driver.

>
> I took a quick look and agree with Lars: nice clean driver, but as you can guess
> we don't really want 2 drivers and there isn't a strong reason to propose
> moving this one between subsystems.
This obviously makes no sense having 2 drivers for this device. Thanks
for the review and the kind words.

>
> Thanks,
>
> Jonathan
Thanks,

Gilles.

>
> >
> > Thanks!
> > Gilles.
> >
> >
> >
> > >
> > > > ---
> > > >   drivers/iio/humidity/Kconfig  |  11 ++
> > > >   drivers/iio/humidity/Makefile |   1 +
> > > >   drivers/iio/humidity/shtc3.c  | 286 ++++++++++++++++++++++++++++++++++
> > > >   3 files changed, 298 insertions(+)
> > > >   create mode 100644 drivers/iio/humidity/shtc3.c
> > > >
> > > > diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
> > > > index 2de5494e7c22..7aab6141c64b 100644
> > > > --- a/drivers/iio/humidity/Kconfig
> > > > +++ b/drivers/iio/humidity/Kconfig
> > > > @@ -98,6 +98,17 @@ config HTU21
> > > >         This driver can also be built as a module. If so, the module will
> > > >         be called htu21.
> > > >
> > > > +config SHTC3
> > > > +     tristate "Sensirion SHTC3 humidity and temperature sensor"
> > > > +     depends on I2C
> > > > +     select CRC8
> > > > +     help
> > > > +       Say yes here to build support for the Sensirion SHTC3
> > > > +       humidity and temperature sensor.
> > > > +
> > > > +       To compile this driver as a module, choose M here: the module
> > > > +       will be called shtc3.
> > > > +
> > > >   config SI7005
> > > >       tristate "SI7005 relative humidity and temperature sensor"
> > > >       depends on I2C
> > > > diff --git a/drivers/iio/humidity/Makefile
> > > b/drivers/iio/humidity/Makefile
> > > > index f19ff3de97c5..13020dfad1b3 100644
> > > > --- a/drivers/iio/humidity/Makefile
> > > > +++ b/drivers/iio/humidity/Makefile
> > > > @@ -16,6 +16,7 @@ obj-$(CONFIG_HTS221_I2C) += hts221_i2c.o
> > > >   obj-$(CONFIG_HTS221_SPI) += hts221_spi.o
> > > >
> > > >   obj-$(CONFIG_HTU21) += htu21.o
> > > > +obj-$(CONFIG_SHTC3) += shtc3.o
> > > >   obj-$(CONFIG_SI7005) += si7005.o
> > > >   obj-$(CONFIG_SI7020) += si7020.o
> > > >
> > > > diff --git a/drivers/iio/humidity/shtc3.c b/drivers/iio/humidity/shtc3.c
> > > > new file mode 100644
> > > > index 000000000000..ec3d7215e378
> > > > --- /dev/null
> > > > +++ b/drivers/iio/humidity/shtc3.c
> > > > @@ -0,0 +1,286 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-only
> > > > +/*
> > > > + * Sensirion SHTC3 Humidity and Temperature Sensor
> > > > + *
> > > > + * Copyright (c) 2021 Gilles Talis <gilles.talis@xxxxxxxxx>
> > > > + *
> > > > + * Datasheet: https://www.sensirion.com/file/datasheet_shtc3
> > > > + *
> > > > + * I2C slave address: 0x70
> > > > + */
> > > > +
> > > > +#include <linux/crc8.h>
> > > > +#include <linux/delay.h>
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/mod_devicetable.h>
> > > > +
> > > > +#include <linux/iio/iio.h>
> > > > +
> > > > +#define SHTC3_CMD(cmd_word)          cpu_to_be16(cmd_word)
> > > > +#define SHTC3_CMD_LEN                        2
> > > > +
> > > > +#define SHTC3_ID_MASK                        0x083F
> > > > +#define SHTC3_ID                     0x0807
> > > > +
> > > > +#define SHTC3_CRC8_POLYNOMIAL                0x31
> > > > +
> > > > +enum shtc3_cmd {
> > > > +     SHTC3_CMD_GET_ID                = SHTC3_CMD(0xEFC8),
> > > > +     SHTC3_CMD_SOFT_RESET            = SHTC3_CMD(0x805D),
> > > > +     SHTC3_CMD_SLEEP                 = SHTC3_CMD(0xB098),
> > > > +     SHTC3_CMD_WAKEUP                = SHTC3_CMD(0x3517),
> > > > +     /*
> > > > +      * Run measurement, low-power mode, clock stretching
> > > > +      * temperature first
> > > > +      */
> > > > +     SHTC3_CMD_TEMP_MEAS_LP_CS       = SHTC3_CMD(0x6458),
> > > > +     /*
> > > > +      * Run measurement, low-power mode, clock stretching
> > > > +      * relative humidity first
> > > > +      */
> > > > +     SHTC3_CMD_RH_MEAS_LP_CS         = SHTC3_CMD(0x44DE),
> > > > +};
> > > > +
> > > > +DECLARE_CRC8_TABLE(shtc3_crc8_tbl);
> > > > +
> > > > +struct shtc3_rx_data {
> > > > +     __be16  data;
> > > > +     u8      crc;
> > > > +} __packed;
> > > > +
> > > > +static int shtc3_send_cmd(struct i2c_client *client, u16 cmd, u16 *data)
> > > > +{
> > > > +     int ret;
> > > > +     struct shtc3_rx_data rx_data;
> > > > +     u8 crc;
> > > > +
> > > > +     ret = i2c_master_send(client, (const char *)&cmd, SHTC3_CMD_LEN);
> > > > +     if (ret != SHTC3_CMD_LEN)
>
> That might eat another error, so convention is to check ret < 0 first and then
> check the length. That way you can return a more informative error if there
> is one.   I see you do that on the recv below.
>
> > > > +             return -EIO;
> > > > +
> > > > +     /*
> > > > +      * This is used to read temperature and humidity measurements
> > > > +      * as well as the sensor ID.
> > > > +      * Sensor sends 2 bytes of data followed by one byte of CRC
> > > > +      */
> > > > +     if (data) {
> > > > +             ret = i2c_master_recv(client, (u8 *) &rx_data,
> > > > +                                     sizeof(struct shtc3_rx_data));
> > > > +             if (ret < 0)
> > > > +                     return ret;
> > > > +             if (ret != sizeof(struct shtc3_rx_data))
> > > > +                     return -EIO;
> > > > +
> > > > +             crc = crc8(shtc3_crc8_tbl, (u8 *)&rx_data.data,
> > > > +                         2, CRC8_INIT_VALUE);
> > > > +             if (crc != rx_data.crc)
> > > > +                     return -EIO;
> > > > +
> > > > +             *data = be16_to_cpu(rx_data.data);
> > > > +     }
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +static int shtc3_sleep(struct i2c_client *client)
> > > > +{
> > > > +     return shtc3_send_cmd(client, SHTC3_CMD_SLEEP, 0);
> > > > +}
> > > > +
> > > > +static int shtc3_wakeup(struct i2c_client *client)
> > > > +{
> > > > +     if (shtc3_send_cmd(client, SHTC3_CMD_WAKEUP, 0) < 0)
> > > > +             return -EIO;
> > > > +
> > > > +     /* Wait for device to wake up */
> > > > +     usleep_range(180, 240);
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +static int shtc3_read_channel(struct i2c_client *client, bool temp)
> > > > +{
> > > > +     int ret;
> > > > +     u16 cmd;
> > > > +     u16 meas;
> > > > +
> > > > +     ret = shtc3_wakeup(client);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     /*
> > > > +      * Sensor sends back measurement results after measurement command
> > > > +      * has been issued by the host.
> > > > +      * Sensor sends 3 bytes (2 bytes of data + 1 byte of CRC) for each
> > > > +      * channel sequentially
> > > > +      * The command issued by the host determines the channel for which
> > > > +      * the sensor will first send the data.
> > > > +      * We select the channel for which we need the results
> > > > +      * then only read back the 2 bytes corresponding to this channel.
> > > > +      */
> > > > +     cmd = temp ? SHTC3_CMD_TEMP_MEAS_LP_CS : SHTC3_CMD_RH_MEAS_LP_CS;
> > > > +     ret = shtc3_send_cmd(client, cmd, &meas);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     /* Go back to sleep */
> > > > +     shtc3_sleep(client);
> > > > +
> > > > +     return meas;
> > > > +}
> > > > +
> > > > +static int shtc3_read_raw(struct iio_dev *indio_dev,
> > > > +                        struct iio_chan_spec const *chan, int *val,
> > > > +                        int *val2, long mask)
> > > > +{
> > > > +     struct i2c_client **client = iio_priv(indio_dev);
> > > > +     int ret;
> > > > +
> > > > +     switch (mask) {
> > > > +     case IIO_CHAN_INFO_RAW:
> > > > +             ret = shtc3_read_channel(*client, (chan->type ==
> > > IIO_TEMP));
> > > > +             if (ret < 0)
> > > > +                     return ret;
> > > > +             *val = ret;
> > > > +             return IIO_VAL_INT;
> > > > +     case IIO_CHAN_INFO_SCALE:
> > > > +             if (chan->type == IIO_TEMP) {
> > > > +                     *val = 2;
> > > > +                     *val2 = 670000;
> > > > +             } else {
> > > > +                     *val = 0;
> > > > +                     *val2 = 1525;
> > > > +             }
> > > > +             return IIO_VAL_INT_PLUS_MICRO;
> > > > +     case IIO_CHAN_INFO_OFFSET:
> > > > +             *val = -16852;
> > > > +             return IIO_VAL_INT;
> > > > +     default:
> > > > +             break;
> > > > +     }
> > > > +
> > > > +     return -EINVAL;
>
> I'd move this into the default: statement as saves a few lines and makes it
> slightly easier to read.
>
> > > > +}
> > > > +
> > > > +static const struct iio_chan_spec shtc3_channels[] = {
> > > > +     {
> > > > +             .type = IIO_HUMIDITYRELATIVE,
> > > > +             .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > > > +                     BIT(IIO_CHAN_INFO_SCALE),
> > > > +     },
> > > > +     {
> > > > +             .type = IIO_TEMP,
> > > > +             .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > > > +                     BIT(IIO_CHAN_INFO_SCALE) |
> > > BIT(IIO_CHAN_INFO_OFFSET),
> > > > +     }
> > > > +};
> > > > +
> > > > +static const struct iio_info shtc3_info = {
> > > > +     .read_raw = shtc3_read_raw,
> > > > +};
> > > > +
> > > > +static int shtc3_verify_id(struct i2c_client *client)
> > > > +{
> > > > +     int ret;
> > > > +     u16 device_id;
> > > > +     u16 reg_val;
> > > > +
> > > > +     ret = shtc3_send_cmd(client, SHTC3_CMD_GET_ID, &reg_val);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     device_id = reg_val & SHTC3_ID_MASK;
> > > > +     if (device_id != SHTC3_ID)
> > > > +             return -ENODEV;
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +static int shtc3_reset(struct i2c_client *client)
> > > > +{
> > > > +     int ret;
> > > > +
> > > > +     ret = shtc3_send_cmd(client, SHTC3_CMD_SOFT_RESET, 0);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     /* Wait for device to enter idle state */
> > > > +     usleep_range(180, 240);
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +static int shtc3_setup(struct i2c_client *client)
> > > > +{
> > > > +     int ret;
> > > > +
> > > > +     ret = shtc3_verify_id(client);
> > > > +     if (ret < 0) {
> > > > +             dev_err(&client->dev, "SHTC3 not found\n");
> > > > +             return -ENODEV;
> > > > +     }
> > > > +
> > > > +     ret = shtc3_reset(client);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     return shtc3_sleep(client);
> > > > +}
> > > > +
> > > > +static int shtc3_probe(struct i2c_client *client,
> > > > +                     const struct i2c_device_id *id)
> > > > +{
> > > > +     struct iio_dev *indio_dev;
> > > > +     struct i2c_client **data;
> > > > +     int ret;
> > > > +
> > > > +     indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
> > > > +     if (!indio_dev)
> > > > +             return -ENOMEM;
> > > > +
> > > > +     data = iio_priv(indio_dev);
> > > > +     *data = client;
>
> It's normally better to put a structure in there because the chances
> of a later change requiring more data is rather high...
>
> Obviously for now it will only have one element so will end up
> compiling to pretty much the same you have here.
>
> > > > +
> > > > +     indio_dev->name = dev_name(&client->dev);
> > > > +     indio_dev->modes = INDIO_DIRECT_MODE;
> > > > +     indio_dev->info = &shtc3_info;
> > > > +     indio_dev->channels = shtc3_channels;
> > > > +     indio_dev->num_channels = ARRAY_SIZE(shtc3_channels);
> > > > +
> > > > +     crc8_populate_msb(shtc3_crc8_tbl, SHTC3_CRC8_POLYNOMIAL);
> > > > +
> > > > +     ret = shtc3_setup(client);
> > > > +     if (ret < 0) {
> > > > +             dev_err(&client->dev, "SHTC3 setup failed\n");
> > > > +             return ret;
> > > > +     }
> > > > +
> > > > +     return devm_iio_device_register(&client->dev, indio_dev);
> > > > +}
> > > > +
> > > > +static const struct i2c_device_id shtc3_id[] = {
> > > > +     { "shtc3", 0 },
> > > > +     { }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(i2c, shtc3_id);
> > > > +
> > > > +static const struct of_device_id shtc3_dt_ids[] = {
> > > > +     { .compatible = "sensirion,shtc3" },
> > > > +     { }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, shtc3_dt_ids);
> > > > +
> > > > +static struct i2c_driver shtc3_driver = {
> > > > +     .driver = {
> > > > +             .name = "shtc3",
> > > > +             .of_match_table = shtc3_dt_ids,
> > > > +     },
> > > > +     .probe          = shtc3_probe,
> > > > +     .id_table       = shtc3_id,
> > > > +};
> > > > +
> > > > +module_i2c_driver(shtc3_driver);
> > > > +MODULE_DESCRIPTION("Sensirion SHTC3 Humidity and Temperature Sensor");
> > > > +MODULE_AUTHOR("Gilles Talis <gilles.talis@xxxxxxxxx>");
> > > > +MODULE_LICENSE("GPL");
> > >
> > >
> > >
>




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux