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