11081b9d9SAndreas Klinger // SPDX-License-Identifier: GPL-2.0+
21081b9d9SAndreas Klinger /*
31081b9d9SAndreas Klinger * sgp40.c - Support for Sensirion SGP40 Gas Sensor
41081b9d9SAndreas Klinger *
51081b9d9SAndreas Klinger * Copyright (C) 2021 Andreas Klinger <ak@it-klinger.de>
61081b9d9SAndreas Klinger *
71081b9d9SAndreas Klinger * I2C slave address: 0x59
81081b9d9SAndreas Klinger *
91081b9d9SAndreas Klinger * Datasheet can be found here:
101081b9d9SAndreas Klinger * https://www.sensirion.com/file/datasheet_sgp40
111081b9d9SAndreas Klinger *
121081b9d9SAndreas Klinger * There are two functionalities supported:
131081b9d9SAndreas Klinger *
141081b9d9SAndreas Klinger * 1) read raw logarithmic resistance value from sensor
151081b9d9SAndreas Klinger * --> useful to pass it to the algorithm of the sensor vendor for
161081b9d9SAndreas Klinger * measuring deteriorations and improvements of air quality.
171081b9d9SAndreas Klinger *
181081b9d9SAndreas Klinger * 2) calculate an estimated absolute voc index (0 - 500 index points) for
191081b9d9SAndreas Klinger * measuring the air quality.
201081b9d9SAndreas Klinger * For this purpose the value of the resistance for which the voc index
211081b9d9SAndreas Klinger * will be 250 can be set up using calibbias.
221081b9d9SAndreas Klinger *
231081b9d9SAndreas Klinger * Compensation values of relative humidity and temperature can be set up
241081b9d9SAndreas Klinger * by writing to the out values of temp and humidityrelative.
251081b9d9SAndreas Klinger */
261081b9d9SAndreas Klinger
271081b9d9SAndreas Klinger #include <linux/delay.h>
281081b9d9SAndreas Klinger #include <linux/crc8.h>
291081b9d9SAndreas Klinger #include <linux/module.h>
301081b9d9SAndreas Klinger #include <linux/mutex.h>
311081b9d9SAndreas Klinger #include <linux/i2c.h>
321081b9d9SAndreas Klinger #include <linux/iio/iio.h>
331081b9d9SAndreas Klinger
341081b9d9SAndreas Klinger /*
351081b9d9SAndreas Klinger * floating point calculation of voc is done as integer
361081b9d9SAndreas Klinger * where numbers are multiplied by 1 << SGP40_CALC_POWER
371081b9d9SAndreas Klinger */
381081b9d9SAndreas Klinger #define SGP40_CALC_POWER 14
391081b9d9SAndreas Klinger
401081b9d9SAndreas Klinger #define SGP40_CRC8_POLYNOMIAL 0x31
411081b9d9SAndreas Klinger #define SGP40_CRC8_INIT 0xff
421081b9d9SAndreas Klinger
431081b9d9SAndreas Klinger DECLARE_CRC8_TABLE(sgp40_crc8_table);
441081b9d9SAndreas Klinger
451081b9d9SAndreas Klinger struct sgp40_data {
461081b9d9SAndreas Klinger struct device *dev;
471081b9d9SAndreas Klinger struct i2c_client *client;
481081b9d9SAndreas Klinger int rht;
491081b9d9SAndreas Klinger int temp;
501081b9d9SAndreas Klinger int res_calibbias;
511081b9d9SAndreas Klinger /* Prevent concurrent access to rht, tmp, calibbias */
521081b9d9SAndreas Klinger struct mutex lock;
531081b9d9SAndreas Klinger };
541081b9d9SAndreas Klinger
551081b9d9SAndreas Klinger struct sgp40_tg_measure {
561081b9d9SAndreas Klinger u8 command[2];
571081b9d9SAndreas Klinger __be16 rht_ticks;
581081b9d9SAndreas Klinger u8 rht_crc;
591081b9d9SAndreas Klinger __be16 temp_ticks;
601081b9d9SAndreas Klinger u8 temp_crc;
611081b9d9SAndreas Klinger } __packed;
621081b9d9SAndreas Klinger
631081b9d9SAndreas Klinger struct sgp40_tg_result {
641081b9d9SAndreas Klinger __be16 res_ticks;
651081b9d9SAndreas Klinger u8 res_crc;
661081b9d9SAndreas Klinger } __packed;
671081b9d9SAndreas Klinger
681081b9d9SAndreas Klinger static const struct iio_chan_spec sgp40_channels[] = {
691081b9d9SAndreas Klinger {
701081b9d9SAndreas Klinger .type = IIO_CONCENTRATION,
711081b9d9SAndreas Klinger .channel2 = IIO_MOD_VOC,
721081b9d9SAndreas Klinger .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
731081b9d9SAndreas Klinger },
741081b9d9SAndreas Klinger {
751081b9d9SAndreas Klinger .type = IIO_RESISTANCE,
761081b9d9SAndreas Klinger .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
771081b9d9SAndreas Klinger BIT(IIO_CHAN_INFO_CALIBBIAS),
781081b9d9SAndreas Klinger },
791081b9d9SAndreas Klinger {
801081b9d9SAndreas Klinger .type = IIO_TEMP,
811081b9d9SAndreas Klinger .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
821081b9d9SAndreas Klinger .output = 1,
831081b9d9SAndreas Klinger },
841081b9d9SAndreas Klinger {
851081b9d9SAndreas Klinger .type = IIO_HUMIDITYRELATIVE,
861081b9d9SAndreas Klinger .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
871081b9d9SAndreas Klinger .output = 1,
881081b9d9SAndreas Klinger },
891081b9d9SAndreas Klinger };
901081b9d9SAndreas Klinger
911081b9d9SAndreas Klinger /*
921081b9d9SAndreas Klinger * taylor approximation of e^x:
931081b9d9SAndreas Klinger * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
941081b9d9SAndreas Klinger *
951081b9d9SAndreas Klinger * Because we are calculating x real value multiplied by 2^power we get
961081b9d9SAndreas Klinger * an additional 2^power^n to divide for every element. For a reasonable
971081b9d9SAndreas Klinger * precision this would overflow after a few iterations. Therefore we
981081b9d9SAndreas Klinger * divide the x^n part whenever its about to overflow (xmax).
991081b9d9SAndreas Klinger */
1001081b9d9SAndreas Klinger
sgp40_exp(int exp,u32 power,u32 rounds)1011081b9d9SAndreas Klinger static u32 sgp40_exp(int exp, u32 power, u32 rounds)
1021081b9d9SAndreas Klinger {
1031081b9d9SAndreas Klinger u32 x, y, xp;
1041081b9d9SAndreas Klinger u32 factorial, divider, xmax;
1051081b9d9SAndreas Klinger int sign = 1;
1061081b9d9SAndreas Klinger int i;
1071081b9d9SAndreas Klinger
1081081b9d9SAndreas Klinger if (exp == 0)
1091081b9d9SAndreas Klinger return 1 << power;
1101081b9d9SAndreas Klinger else if (exp < 0) {
1111081b9d9SAndreas Klinger sign = -1;
1121081b9d9SAndreas Klinger exp *= -1;
1131081b9d9SAndreas Klinger }
1141081b9d9SAndreas Klinger
1151081b9d9SAndreas Klinger xmax = 0x7FFFFFFF / exp;
1161081b9d9SAndreas Klinger x = exp;
1171081b9d9SAndreas Klinger xp = 1;
1181081b9d9SAndreas Klinger factorial = 1;
1191081b9d9SAndreas Klinger y = 1 << power;
1201081b9d9SAndreas Klinger divider = 0;
1211081b9d9SAndreas Klinger
1221081b9d9SAndreas Klinger for (i = 1; i <= rounds; i++) {
1231081b9d9SAndreas Klinger xp *= x;
1241081b9d9SAndreas Klinger factorial *= i;
1251081b9d9SAndreas Klinger y += (xp >> divider) / factorial;
1261081b9d9SAndreas Klinger divider += power;
1271081b9d9SAndreas Klinger /* divide when next multiplication would overflow */
1281081b9d9SAndreas Klinger if (xp >= xmax) {
1291081b9d9SAndreas Klinger xp >>= power;
1301081b9d9SAndreas Klinger divider -= power;
1311081b9d9SAndreas Klinger }
1321081b9d9SAndreas Klinger }
1331081b9d9SAndreas Klinger
1341081b9d9SAndreas Klinger if (sign == -1)
1351081b9d9SAndreas Klinger return (1 << (power * 2)) / y;
1361081b9d9SAndreas Klinger else
1371081b9d9SAndreas Klinger return y;
1381081b9d9SAndreas Klinger }
1391081b9d9SAndreas Klinger
sgp40_calc_voc(struct sgp40_data * data,u16 resistance_raw,int * voc)1401081b9d9SAndreas Klinger static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
1411081b9d9SAndreas Klinger {
1421081b9d9SAndreas Klinger int x;
1431081b9d9SAndreas Klinger u32 exp = 0;
1441081b9d9SAndreas Klinger
1451081b9d9SAndreas Klinger /* we calculate as a multiple of 16384 (2^14) */
1461081b9d9SAndreas Klinger mutex_lock(&data->lock);
1471081b9d9SAndreas Klinger x = ((int)resistance_raw - data->res_calibbias) * 106;
1481081b9d9SAndreas Klinger mutex_unlock(&data->lock);
1491081b9d9SAndreas Klinger
1501081b9d9SAndreas Klinger /* voc = 500 / (1 + e^x) */
1511081b9d9SAndreas Klinger exp = sgp40_exp(x, SGP40_CALC_POWER, 18);
1521081b9d9SAndreas Klinger *voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<<SGP40_CALC_POWER) + exp));
1531081b9d9SAndreas Klinger
1541081b9d9SAndreas Klinger dev_dbg(data->dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
1551081b9d9SAndreas Klinger resistance_raw, data->res_calibbias, x, exp, *voc);
1561081b9d9SAndreas Klinger
1571081b9d9SAndreas Klinger return 0;
1581081b9d9SAndreas Klinger }
1591081b9d9SAndreas Klinger
sgp40_measure_resistance_raw(struct sgp40_data * data,u16 * resistance_raw)1601081b9d9SAndreas Klinger static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
1611081b9d9SAndreas Klinger {
1621081b9d9SAndreas Klinger int ret;
1631081b9d9SAndreas Klinger struct i2c_client *client = data->client;
1641081b9d9SAndreas Klinger u32 ticks;
1651081b9d9SAndreas Klinger u16 ticks16;
1661081b9d9SAndreas Klinger u8 crc;
1671081b9d9SAndreas Klinger struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}};
1681081b9d9SAndreas Klinger struct sgp40_tg_result tgres;
1691081b9d9SAndreas Klinger
1701081b9d9SAndreas Klinger mutex_lock(&data->lock);
1711081b9d9SAndreas Klinger
1721081b9d9SAndreas Klinger ticks = (data->rht / 10) * 65535 / 10000;
1731081b9d9SAndreas Klinger ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */
1741081b9d9SAndreas Klinger tg.rht_ticks = cpu_to_be16(ticks16);
1751081b9d9SAndreas Klinger tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT);
1761081b9d9SAndreas Klinger
1771081b9d9SAndreas Klinger ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500;
1781081b9d9SAndreas Klinger ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */
1791081b9d9SAndreas Klinger tg.temp_ticks = cpu_to_be16(ticks16);
1801081b9d9SAndreas Klinger tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT);
1811081b9d9SAndreas Klinger
1821081b9d9SAndreas Klinger mutex_unlock(&data->lock);
1831081b9d9SAndreas Klinger
1841081b9d9SAndreas Klinger ret = i2c_master_send(client, (const char *)&tg, sizeof(tg));
1851081b9d9SAndreas Klinger if (ret != sizeof(tg)) {
1861081b9d9SAndreas Klinger dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg));
1871081b9d9SAndreas Klinger return -EIO;
1881081b9d9SAndreas Klinger }
1891081b9d9SAndreas Klinger msleep(30);
1901081b9d9SAndreas Klinger
1911081b9d9SAndreas Klinger ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres));
1921081b9d9SAndreas Klinger if (ret < 0)
1931081b9d9SAndreas Klinger return ret;
1941081b9d9SAndreas Klinger if (ret != sizeof(tgres)) {
1951081b9d9SAndreas Klinger dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres));
1961081b9d9SAndreas Klinger return -EIO;
1971081b9d9SAndreas Klinger }
1981081b9d9SAndreas Klinger
1991081b9d9SAndreas Klinger crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT);
2001081b9d9SAndreas Klinger if (crc != tgres.res_crc) {
2011081b9d9SAndreas Klinger dev_err(data->dev, "CRC error while measure-raw\n");
2021081b9d9SAndreas Klinger return -EIO;
2031081b9d9SAndreas Klinger }
2041081b9d9SAndreas Klinger
2051081b9d9SAndreas Klinger *resistance_raw = be16_to_cpu(tgres.res_ticks);
2061081b9d9SAndreas Klinger
2071081b9d9SAndreas Klinger return 0;
2081081b9d9SAndreas Klinger }
2091081b9d9SAndreas Klinger
sgp40_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)2101081b9d9SAndreas Klinger static int sgp40_read_raw(struct iio_dev *indio_dev,
2111081b9d9SAndreas Klinger struct iio_chan_spec const *chan, int *val,
2121081b9d9SAndreas Klinger int *val2, long mask)
2131081b9d9SAndreas Klinger {
2141081b9d9SAndreas Klinger struct sgp40_data *data = iio_priv(indio_dev);
2151081b9d9SAndreas Klinger int ret, voc;
2161081b9d9SAndreas Klinger u16 resistance_raw;
2171081b9d9SAndreas Klinger
2181081b9d9SAndreas Klinger switch (mask) {
2191081b9d9SAndreas Klinger case IIO_CHAN_INFO_RAW:
2201081b9d9SAndreas Klinger switch (chan->type) {
2211081b9d9SAndreas Klinger case IIO_RESISTANCE:
2221081b9d9SAndreas Klinger ret = sgp40_measure_resistance_raw(data, &resistance_raw);
2231081b9d9SAndreas Klinger if (ret)
2241081b9d9SAndreas Klinger return ret;
2251081b9d9SAndreas Klinger
2261081b9d9SAndreas Klinger *val = resistance_raw;
2271081b9d9SAndreas Klinger return IIO_VAL_INT;
2281081b9d9SAndreas Klinger case IIO_TEMP:
2291081b9d9SAndreas Klinger mutex_lock(&data->lock);
2301081b9d9SAndreas Klinger *val = data->temp;
2311081b9d9SAndreas Klinger mutex_unlock(&data->lock);
2321081b9d9SAndreas Klinger return IIO_VAL_INT;
2331081b9d9SAndreas Klinger case IIO_HUMIDITYRELATIVE:
2341081b9d9SAndreas Klinger mutex_lock(&data->lock);
2351081b9d9SAndreas Klinger *val = data->rht;
2361081b9d9SAndreas Klinger mutex_unlock(&data->lock);
2371081b9d9SAndreas Klinger return IIO_VAL_INT;
2381081b9d9SAndreas Klinger default:
2391081b9d9SAndreas Klinger return -EINVAL;
2401081b9d9SAndreas Klinger }
2411081b9d9SAndreas Klinger case IIO_CHAN_INFO_PROCESSED:
2421081b9d9SAndreas Klinger ret = sgp40_measure_resistance_raw(data, &resistance_raw);
2431081b9d9SAndreas Klinger if (ret)
2441081b9d9SAndreas Klinger return ret;
2451081b9d9SAndreas Klinger
2461081b9d9SAndreas Klinger ret = sgp40_calc_voc(data, resistance_raw, &voc);
2471081b9d9SAndreas Klinger if (ret)
2481081b9d9SAndreas Klinger return ret;
2491081b9d9SAndreas Klinger
2501081b9d9SAndreas Klinger *val = voc / (1 << SGP40_CALC_POWER);
2511081b9d9SAndreas Klinger /*
2521081b9d9SAndreas Klinger * calculation should fit into integer, where:
2531081b9d9SAndreas Klinger * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
2541081b9d9SAndreas Klinger * (with SGP40_CALC_POWER = 14)
2551081b9d9SAndreas Klinger */
2561081b9d9SAndreas Klinger *val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12));
2571081b9d9SAndreas Klinger dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2);
2581081b9d9SAndreas Klinger return IIO_VAL_INT_PLUS_MICRO;
2591081b9d9SAndreas Klinger case IIO_CHAN_INFO_CALIBBIAS:
2601081b9d9SAndreas Klinger mutex_lock(&data->lock);
2611081b9d9SAndreas Klinger *val = data->res_calibbias;
2621081b9d9SAndreas Klinger mutex_unlock(&data->lock);
2631081b9d9SAndreas Klinger return IIO_VAL_INT;
2641081b9d9SAndreas Klinger default:
2651081b9d9SAndreas Klinger return -EINVAL;
2661081b9d9SAndreas Klinger }
2671081b9d9SAndreas Klinger }
2681081b9d9SAndreas Klinger
sgp40_write_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int val,int val2,long mask)2691081b9d9SAndreas Klinger static int sgp40_write_raw(struct iio_dev *indio_dev,
2701081b9d9SAndreas Klinger struct iio_chan_spec const *chan, int val,
2711081b9d9SAndreas Klinger int val2, long mask)
2721081b9d9SAndreas Klinger {
2731081b9d9SAndreas Klinger struct sgp40_data *data = iio_priv(indio_dev);
2741081b9d9SAndreas Klinger
2751081b9d9SAndreas Klinger switch (mask) {
2761081b9d9SAndreas Klinger case IIO_CHAN_INFO_RAW:
2771081b9d9SAndreas Klinger switch (chan->type) {
2781081b9d9SAndreas Klinger case IIO_TEMP:
2791081b9d9SAndreas Klinger if ((val < -45000) || (val > 130000))
2801081b9d9SAndreas Klinger return -EINVAL;
2811081b9d9SAndreas Klinger
2821081b9d9SAndreas Klinger mutex_lock(&data->lock);
2831081b9d9SAndreas Klinger data->temp = val;
2841081b9d9SAndreas Klinger mutex_unlock(&data->lock);
2851081b9d9SAndreas Klinger return 0;
2861081b9d9SAndreas Klinger case IIO_HUMIDITYRELATIVE:
2871081b9d9SAndreas Klinger if ((val < 0) || (val > 100000))
2881081b9d9SAndreas Klinger return -EINVAL;
2891081b9d9SAndreas Klinger
2901081b9d9SAndreas Klinger mutex_lock(&data->lock);
2911081b9d9SAndreas Klinger data->rht = val;
2921081b9d9SAndreas Klinger mutex_unlock(&data->lock);
2931081b9d9SAndreas Klinger return 0;
2941081b9d9SAndreas Klinger default:
2951081b9d9SAndreas Klinger return -EINVAL;
2961081b9d9SAndreas Klinger }
2971081b9d9SAndreas Klinger case IIO_CHAN_INFO_CALIBBIAS:
2981081b9d9SAndreas Klinger if ((val < 20000) || (val > 52768))
2991081b9d9SAndreas Klinger return -EINVAL;
3001081b9d9SAndreas Klinger
3011081b9d9SAndreas Klinger mutex_lock(&data->lock);
3021081b9d9SAndreas Klinger data->res_calibbias = val;
3031081b9d9SAndreas Klinger mutex_unlock(&data->lock);
3041081b9d9SAndreas Klinger return 0;
3051081b9d9SAndreas Klinger }
3061081b9d9SAndreas Klinger return -EINVAL;
3071081b9d9SAndreas Klinger }
3081081b9d9SAndreas Klinger
3091081b9d9SAndreas Klinger static const struct iio_info sgp40_info = {
3101081b9d9SAndreas Klinger .read_raw = sgp40_read_raw,
3111081b9d9SAndreas Klinger .write_raw = sgp40_write_raw,
3121081b9d9SAndreas Klinger };
3131081b9d9SAndreas Klinger
sgp40_probe(struct i2c_client * client)31407eda54dSUwe Kleine-König static int sgp40_probe(struct i2c_client *client)
3151081b9d9SAndreas Klinger {
31607eda54dSUwe Kleine-König const struct i2c_device_id *id = i2c_client_get_device_id(client);
3171081b9d9SAndreas Klinger struct device *dev = &client->dev;
3181081b9d9SAndreas Klinger struct iio_dev *indio_dev;
3191081b9d9SAndreas Klinger struct sgp40_data *data;
3201081b9d9SAndreas Klinger int ret;
3211081b9d9SAndreas Klinger
3221081b9d9SAndreas Klinger indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
3231081b9d9SAndreas Klinger if (!indio_dev)
3241081b9d9SAndreas Klinger return -ENOMEM;
3251081b9d9SAndreas Klinger
3261081b9d9SAndreas Klinger data = iio_priv(indio_dev);
3271081b9d9SAndreas Klinger data->client = client;
3281081b9d9SAndreas Klinger data->dev = dev;
3291081b9d9SAndreas Klinger
3301081b9d9SAndreas Klinger crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL);
3311081b9d9SAndreas Klinger
3321081b9d9SAndreas Klinger mutex_init(&data->lock);
3331081b9d9SAndreas Klinger
3341081b9d9SAndreas Klinger /* set default values */
3351081b9d9SAndreas Klinger data->rht = 50000; /* 50 % */
3361081b9d9SAndreas Klinger data->temp = 25000; /* 25 °C */
3371081b9d9SAndreas Klinger data->res_calibbias = 30000; /* resistance raw value for voc index of 250 */
3381081b9d9SAndreas Klinger
3391081b9d9SAndreas Klinger indio_dev->info = &sgp40_info;
3401081b9d9SAndreas Klinger indio_dev->name = id->name;
3411081b9d9SAndreas Klinger indio_dev->modes = INDIO_DIRECT_MODE;
3421081b9d9SAndreas Klinger indio_dev->channels = sgp40_channels;
3431081b9d9SAndreas Klinger indio_dev->num_channels = ARRAY_SIZE(sgp40_channels);
3441081b9d9SAndreas Klinger
3451081b9d9SAndreas Klinger ret = devm_iio_device_register(dev, indio_dev);
3461081b9d9SAndreas Klinger if (ret)
3471081b9d9SAndreas Klinger dev_err(dev, "failed to register iio device\n");
3481081b9d9SAndreas Klinger
3491081b9d9SAndreas Klinger return ret;
3501081b9d9SAndreas Klinger }
3511081b9d9SAndreas Klinger
3521081b9d9SAndreas Klinger static const struct i2c_device_id sgp40_id[] = {
3531081b9d9SAndreas Klinger { "sgp40" },
3541081b9d9SAndreas Klinger { }
3551081b9d9SAndreas Klinger };
3561081b9d9SAndreas Klinger
3571081b9d9SAndreas Klinger MODULE_DEVICE_TABLE(i2c, sgp40_id);
3581081b9d9SAndreas Klinger
3591081b9d9SAndreas Klinger static const struct of_device_id sgp40_dt_ids[] = {
3601081b9d9SAndreas Klinger { .compatible = "sensirion,sgp40" },
3611081b9d9SAndreas Klinger { }
3621081b9d9SAndreas Klinger };
3631081b9d9SAndreas Klinger
3641081b9d9SAndreas Klinger MODULE_DEVICE_TABLE(of, sgp40_dt_ids);
3651081b9d9SAndreas Klinger
3661081b9d9SAndreas Klinger static struct i2c_driver sgp40_driver = {
3671081b9d9SAndreas Klinger .driver = {
3681081b9d9SAndreas Klinger .name = "sgp40",
3691081b9d9SAndreas Klinger .of_match_table = sgp40_dt_ids,
3701081b9d9SAndreas Klinger },
371*7cf15f42SUwe Kleine-König .probe = sgp40_probe,
3721081b9d9SAndreas Klinger .id_table = sgp40_id,
3731081b9d9SAndreas Klinger };
3741081b9d9SAndreas Klinger module_i2c_driver(sgp40_driver);
3751081b9d9SAndreas Klinger
3761081b9d9SAndreas Klinger MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
3771081b9d9SAndreas Klinger MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
3781081b9d9SAndreas Klinger MODULE_LICENSE("GPL v2");
379