18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015-2018 68c2ecf20Sopenharmony_ci * Author: Matt Ranostay <matt.ranostay@konsulko.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/mutex.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/iio/iio.h> 168c2ecf20Sopenharmony_ci#include <linux/iio/sysfs.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define VZ89X_REG_MEASUREMENT 0x09 198c2ecf20Sopenharmony_ci#define VZ89X_REG_MEASUREMENT_RD_SIZE 6 208c2ecf20Sopenharmony_ci#define VZ89X_REG_MEASUREMENT_WR_SIZE 3 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define VZ89X_VOC_CO2_IDX 0 238c2ecf20Sopenharmony_ci#define VZ89X_VOC_SHORT_IDX 1 248c2ecf20Sopenharmony_ci#define VZ89X_VOC_TVOC_IDX 2 258c2ecf20Sopenharmony_ci#define VZ89X_VOC_RESISTANCE_IDX 3 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define VZ89TE_REG_MEASUREMENT 0x0c 288c2ecf20Sopenharmony_ci#define VZ89TE_REG_MEASUREMENT_RD_SIZE 7 298c2ecf20Sopenharmony_ci#define VZ89TE_REG_MEASUREMENT_WR_SIZE 6 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define VZ89TE_VOC_TVOC_IDX 0 328c2ecf20Sopenharmony_ci#define VZ89TE_VOC_CO2_IDX 1 338c2ecf20Sopenharmony_ci#define VZ89TE_VOC_RESISTANCE_IDX 2 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cienum { 368c2ecf20Sopenharmony_ci VZ89X, 378c2ecf20Sopenharmony_ci VZ89TE, 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistruct vz89x_chip_data; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistruct vz89x_data { 438c2ecf20Sopenharmony_ci struct i2c_client *client; 448c2ecf20Sopenharmony_ci const struct vz89x_chip_data *chip; 458c2ecf20Sopenharmony_ci struct mutex lock; 468c2ecf20Sopenharmony_ci int (*xfer)(struct vz89x_data *data, u8 cmd); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci bool is_valid; 498c2ecf20Sopenharmony_ci unsigned long last_update; 508c2ecf20Sopenharmony_ci u8 buffer[VZ89TE_REG_MEASUREMENT_RD_SIZE]; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct vz89x_chip_data { 548c2ecf20Sopenharmony_ci bool (*valid)(struct vz89x_data *data); 558c2ecf20Sopenharmony_ci const struct iio_chan_spec *channels; 568c2ecf20Sopenharmony_ci u8 num_channels; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci u8 cmd; 598c2ecf20Sopenharmony_ci u8 read_size; 608c2ecf20Sopenharmony_ci u8 write_size; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic const struct iio_chan_spec vz89x_channels[] = { 648c2ecf20Sopenharmony_ci { 658c2ecf20Sopenharmony_ci .type = IIO_CONCENTRATION, 668c2ecf20Sopenharmony_ci .channel2 = IIO_MOD_CO2, 678c2ecf20Sopenharmony_ci .modified = 1, 688c2ecf20Sopenharmony_ci .info_mask_separate = 698c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 708c2ecf20Sopenharmony_ci .address = VZ89X_VOC_CO2_IDX, 718c2ecf20Sopenharmony_ci }, 728c2ecf20Sopenharmony_ci { 738c2ecf20Sopenharmony_ci .type = IIO_CONCENTRATION, 748c2ecf20Sopenharmony_ci .channel2 = IIO_MOD_VOC, 758c2ecf20Sopenharmony_ci .modified = 1, 768c2ecf20Sopenharmony_ci .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 778c2ecf20Sopenharmony_ci .address = VZ89X_VOC_SHORT_IDX, 788c2ecf20Sopenharmony_ci .extend_name = "short", 798c2ecf20Sopenharmony_ci }, 808c2ecf20Sopenharmony_ci { 818c2ecf20Sopenharmony_ci .type = IIO_CONCENTRATION, 828c2ecf20Sopenharmony_ci .channel2 = IIO_MOD_VOC, 838c2ecf20Sopenharmony_ci .modified = 1, 848c2ecf20Sopenharmony_ci .info_mask_separate = 858c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 868c2ecf20Sopenharmony_ci .address = VZ89X_VOC_TVOC_IDX, 878c2ecf20Sopenharmony_ci }, 888c2ecf20Sopenharmony_ci { 898c2ecf20Sopenharmony_ci .type = IIO_RESISTANCE, 908c2ecf20Sopenharmony_ci .info_mask_separate = 918c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), 928c2ecf20Sopenharmony_ci .address = VZ89X_VOC_RESISTANCE_IDX, 938c2ecf20Sopenharmony_ci .scan_index = -1, 948c2ecf20Sopenharmony_ci .scan_type = { 958c2ecf20Sopenharmony_ci .endianness = IIO_LE, 968c2ecf20Sopenharmony_ci }, 978c2ecf20Sopenharmony_ci }, 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic const struct iio_chan_spec vz89te_channels[] = { 1018c2ecf20Sopenharmony_ci { 1028c2ecf20Sopenharmony_ci .type = IIO_CONCENTRATION, 1038c2ecf20Sopenharmony_ci .channel2 = IIO_MOD_VOC, 1048c2ecf20Sopenharmony_ci .modified = 1, 1058c2ecf20Sopenharmony_ci .info_mask_separate = 1068c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 1078c2ecf20Sopenharmony_ci .address = VZ89TE_VOC_TVOC_IDX, 1088c2ecf20Sopenharmony_ci }, 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci { 1118c2ecf20Sopenharmony_ci .type = IIO_CONCENTRATION, 1128c2ecf20Sopenharmony_ci .channel2 = IIO_MOD_CO2, 1138c2ecf20Sopenharmony_ci .modified = 1, 1148c2ecf20Sopenharmony_ci .info_mask_separate = 1158c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 1168c2ecf20Sopenharmony_ci .address = VZ89TE_VOC_CO2_IDX, 1178c2ecf20Sopenharmony_ci }, 1188c2ecf20Sopenharmony_ci { 1198c2ecf20Sopenharmony_ci .type = IIO_RESISTANCE, 1208c2ecf20Sopenharmony_ci .info_mask_separate = 1218c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), 1228c2ecf20Sopenharmony_ci .address = VZ89TE_VOC_RESISTANCE_IDX, 1238c2ecf20Sopenharmony_ci .scan_index = -1, 1248c2ecf20Sopenharmony_ci .scan_type = { 1258c2ecf20Sopenharmony_ci .endianness = IIO_BE, 1268c2ecf20Sopenharmony_ci }, 1278c2ecf20Sopenharmony_ci }, 1288c2ecf20Sopenharmony_ci}; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689"); 1318c2ecf20Sopenharmony_cistatic IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223"); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic struct attribute *vz89x_attributes[] = { 1348c2ecf20Sopenharmony_ci &iio_const_attr_in_concentration_co2_scale.dev_attr.attr, 1358c2ecf20Sopenharmony_ci &iio_const_attr_in_concentration_voc_scale.dev_attr.attr, 1368c2ecf20Sopenharmony_ci NULL, 1378c2ecf20Sopenharmony_ci}; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic const struct attribute_group vz89x_attrs_group = { 1408c2ecf20Sopenharmony_ci .attrs = vz89x_attributes, 1418c2ecf20Sopenharmony_ci}; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci/* 1448c2ecf20Sopenharmony_ci * Chipset sometime updates in the middle of a reading causing it to reset the 1458c2ecf20Sopenharmony_ci * data pointer, and causing invalid reading of previous data. 1468c2ecf20Sopenharmony_ci * We can check for this by reading MSB of the resistance reading that is 1478c2ecf20Sopenharmony_ci * always zero, and by also confirming the VOC_short isn't zero. 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic bool vz89x_measurement_is_valid(struct vz89x_data *data) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0) 1538c2ecf20Sopenharmony_ci return true; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci return !!(data->buffer[data->chip->read_size - 1] > 0); 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci/* VZ89TE device has a modified CRC-8 two complement check */ 1598c2ecf20Sopenharmony_cistatic bool vz89te_measurement_is_valid(struct vz89x_data *data) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci u8 crc = 0; 1628c2ecf20Sopenharmony_ci int i, sum = 0; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci for (i = 0; i < (data->chip->read_size - 1); i++) { 1658c2ecf20Sopenharmony_ci sum = crc + data->buffer[i]; 1668c2ecf20Sopenharmony_ci crc = sum; 1678c2ecf20Sopenharmony_ci crc += sum / 256; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci return !((0xff - crc) == data->buffer[data->chip->read_size - 1]); 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci const struct vz89x_chip_data *chip = data->chip; 1768c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 1778c2ecf20Sopenharmony_ci struct i2c_msg msg[2]; 1788c2ecf20Sopenharmony_ci int ret; 1798c2ecf20Sopenharmony_ci u8 buf[6] = { cmd, 0, 0, 0, 0, 0xf3 }; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci msg[0].addr = client->addr; 1828c2ecf20Sopenharmony_ci msg[0].flags = client->flags; 1838c2ecf20Sopenharmony_ci msg[0].len = chip->write_size; 1848c2ecf20Sopenharmony_ci msg[0].buf = (char *) &buf; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci msg[1].addr = client->addr; 1878c2ecf20Sopenharmony_ci msg[1].flags = client->flags | I2C_M_RD; 1888c2ecf20Sopenharmony_ci msg[1].len = chip->read_size; 1898c2ecf20Sopenharmony_ci msg[1].buf = (char *) &data->buffer; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ret = i2c_transfer(client->adapter, msg, 2); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return (ret == 2) ? 0 : ret; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 1998c2ecf20Sopenharmony_ci int ret; 2008c2ecf20Sopenharmony_ci int i; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci ret = i2c_smbus_write_word_data(client, cmd, 0); 2038c2ecf20Sopenharmony_ci if (ret < 0) 2048c2ecf20Sopenharmony_ci return ret; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci for (i = 0; i < data->chip->read_size; i++) { 2078c2ecf20Sopenharmony_ci ret = i2c_smbus_read_byte(client); 2088c2ecf20Sopenharmony_ci if (ret < 0) 2098c2ecf20Sopenharmony_ci return ret; 2108c2ecf20Sopenharmony_ci data->buffer[i] = ret; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic int vz89x_get_measurement(struct vz89x_data *data) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci const struct vz89x_chip_data *chip = data->chip; 2198c2ecf20Sopenharmony_ci int ret; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* sensor can only be polled once a second max per datasheet */ 2228c2ecf20Sopenharmony_ci if (!time_after(jiffies, data->last_update + HZ)) 2238c2ecf20Sopenharmony_ci return data->is_valid ? 0 : -EAGAIN; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci data->is_valid = false; 2268c2ecf20Sopenharmony_ci data->last_update = jiffies; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci ret = data->xfer(data, chip->cmd); 2298c2ecf20Sopenharmony_ci if (ret < 0) 2308c2ecf20Sopenharmony_ci return ret; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci ret = chip->valid(data); 2338c2ecf20Sopenharmony_ci if (ret) 2348c2ecf20Sopenharmony_ci return -EAGAIN; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci data->is_valid = true; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic int vz89x_get_resistance_reading(struct vz89x_data *data, 2428c2ecf20Sopenharmony_ci struct iio_chan_spec const *chan, 2438c2ecf20Sopenharmony_ci int *val) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci u8 *tmp = (u8 *) &data->buffer[chan->address]; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci switch (chan->scan_type.endianness) { 2488c2ecf20Sopenharmony_ci case IIO_LE: 2498c2ecf20Sopenharmony_ci *val = le32_to_cpup((__le32 *) tmp) & GENMASK(23, 0); 2508c2ecf20Sopenharmony_ci break; 2518c2ecf20Sopenharmony_ci case IIO_BE: 2528c2ecf20Sopenharmony_ci *val = be32_to_cpup((__be32 *) tmp) >> 8; 2538c2ecf20Sopenharmony_ci break; 2548c2ecf20Sopenharmony_ci default: 2558c2ecf20Sopenharmony_ci return -EINVAL; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return 0; 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic int vz89x_read_raw(struct iio_dev *indio_dev, 2628c2ecf20Sopenharmony_ci struct iio_chan_spec const *chan, int *val, 2638c2ecf20Sopenharmony_ci int *val2, long mask) 2648c2ecf20Sopenharmony_ci{ 2658c2ecf20Sopenharmony_ci struct vz89x_data *data = iio_priv(indio_dev); 2668c2ecf20Sopenharmony_ci int ret = -EINVAL; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci switch (mask) { 2698c2ecf20Sopenharmony_ci case IIO_CHAN_INFO_RAW: 2708c2ecf20Sopenharmony_ci mutex_lock(&data->lock); 2718c2ecf20Sopenharmony_ci ret = vz89x_get_measurement(data); 2728c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (ret) 2758c2ecf20Sopenharmony_ci return ret; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci switch (chan->type) { 2788c2ecf20Sopenharmony_ci case IIO_CONCENTRATION: 2798c2ecf20Sopenharmony_ci *val = data->buffer[chan->address]; 2808c2ecf20Sopenharmony_ci return IIO_VAL_INT; 2818c2ecf20Sopenharmony_ci case IIO_RESISTANCE: 2828c2ecf20Sopenharmony_ci ret = vz89x_get_resistance_reading(data, chan, val); 2838c2ecf20Sopenharmony_ci if (!ret) 2848c2ecf20Sopenharmony_ci return IIO_VAL_INT; 2858c2ecf20Sopenharmony_ci break; 2868c2ecf20Sopenharmony_ci default: 2878c2ecf20Sopenharmony_ci return -EINVAL; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci break; 2908c2ecf20Sopenharmony_ci case IIO_CHAN_INFO_SCALE: 2918c2ecf20Sopenharmony_ci switch (chan->type) { 2928c2ecf20Sopenharmony_ci case IIO_RESISTANCE: 2938c2ecf20Sopenharmony_ci *val = 10; 2948c2ecf20Sopenharmony_ci return IIO_VAL_INT; 2958c2ecf20Sopenharmony_ci default: 2968c2ecf20Sopenharmony_ci return -EINVAL; 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci break; 2998c2ecf20Sopenharmony_ci case IIO_CHAN_INFO_OFFSET: 3008c2ecf20Sopenharmony_ci switch (chan->channel2) { 3018c2ecf20Sopenharmony_ci case IIO_MOD_CO2: 3028c2ecf20Sopenharmony_ci *val = 44; 3038c2ecf20Sopenharmony_ci *val2 = 250000; 3048c2ecf20Sopenharmony_ci return IIO_VAL_INT_PLUS_MICRO; 3058c2ecf20Sopenharmony_ci case IIO_MOD_VOC: 3068c2ecf20Sopenharmony_ci *val = -13; 3078c2ecf20Sopenharmony_ci return IIO_VAL_INT; 3088c2ecf20Sopenharmony_ci default: 3098c2ecf20Sopenharmony_ci return -EINVAL; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci } 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci return ret; 3148c2ecf20Sopenharmony_ci} 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cistatic const struct iio_info vz89x_info = { 3178c2ecf20Sopenharmony_ci .attrs = &vz89x_attrs_group, 3188c2ecf20Sopenharmony_ci .read_raw = vz89x_read_raw, 3198c2ecf20Sopenharmony_ci}; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cistatic const struct vz89x_chip_data vz89x_chips[] = { 3228c2ecf20Sopenharmony_ci { 3238c2ecf20Sopenharmony_ci .valid = vz89x_measurement_is_valid, 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci .cmd = VZ89X_REG_MEASUREMENT, 3268c2ecf20Sopenharmony_ci .read_size = VZ89X_REG_MEASUREMENT_RD_SIZE, 3278c2ecf20Sopenharmony_ci .write_size = VZ89X_REG_MEASUREMENT_WR_SIZE, 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci .channels = vz89x_channels, 3308c2ecf20Sopenharmony_ci .num_channels = ARRAY_SIZE(vz89x_channels), 3318c2ecf20Sopenharmony_ci }, 3328c2ecf20Sopenharmony_ci { 3338c2ecf20Sopenharmony_ci .valid = vz89te_measurement_is_valid, 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci .cmd = VZ89TE_REG_MEASUREMENT, 3368c2ecf20Sopenharmony_ci .read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE, 3378c2ecf20Sopenharmony_ci .write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE, 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci .channels = vz89te_channels, 3408c2ecf20Sopenharmony_ci .num_channels = ARRAY_SIZE(vz89te_channels), 3418c2ecf20Sopenharmony_ci }, 3428c2ecf20Sopenharmony_ci}; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_cistatic const struct of_device_id vz89x_dt_ids[] = { 3458c2ecf20Sopenharmony_ci { .compatible = "sgx,vz89x", .data = (void *) VZ89X }, 3468c2ecf20Sopenharmony_ci { .compatible = "sgx,vz89te", .data = (void *) VZ89TE }, 3478c2ecf20Sopenharmony_ci { } 3488c2ecf20Sopenharmony_ci}; 3498c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, vz89x_dt_ids); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_cistatic int vz89x_probe(struct i2c_client *client, 3528c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 3538c2ecf20Sopenharmony_ci{ 3548c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 3558c2ecf20Sopenharmony_ci struct iio_dev *indio_dev; 3568c2ecf20Sopenharmony_ci struct vz89x_data *data; 3578c2ecf20Sopenharmony_ci int chip_id; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); 3608c2ecf20Sopenharmony_ci if (!indio_dev) 3618c2ecf20Sopenharmony_ci return -ENOMEM; 3628c2ecf20Sopenharmony_ci data = iio_priv(indio_dev); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) 3658c2ecf20Sopenharmony_ci data->xfer = vz89x_i2c_xfer; 3668c2ecf20Sopenharmony_ci else if (i2c_check_functionality(client->adapter, 3678c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) 3688c2ecf20Sopenharmony_ci data->xfer = vz89x_smbus_xfer; 3698c2ecf20Sopenharmony_ci else 3708c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci if (!dev_fwnode(dev)) 3738c2ecf20Sopenharmony_ci chip_id = id->driver_data; 3748c2ecf20Sopenharmony_ci else 3758c2ecf20Sopenharmony_ci chip_id = (unsigned long)device_get_match_data(dev); 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci i2c_set_clientdata(client, indio_dev); 3788c2ecf20Sopenharmony_ci data->client = client; 3798c2ecf20Sopenharmony_ci data->chip = &vz89x_chips[chip_id]; 3808c2ecf20Sopenharmony_ci data->last_update = jiffies - HZ; 3818c2ecf20Sopenharmony_ci mutex_init(&data->lock); 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci indio_dev->info = &vz89x_info; 3848c2ecf20Sopenharmony_ci indio_dev->name = dev_name(dev); 3858c2ecf20Sopenharmony_ci indio_dev->modes = INDIO_DIRECT_MODE; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci indio_dev->channels = data->chip->channels; 3888c2ecf20Sopenharmony_ci indio_dev->num_channels = data->chip->num_channels; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci return devm_iio_device_register(dev, indio_dev); 3918c2ecf20Sopenharmony_ci} 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_cistatic const struct i2c_device_id vz89x_id[] = { 3948c2ecf20Sopenharmony_ci { "vz89x", VZ89X }, 3958c2ecf20Sopenharmony_ci { "vz89te", VZ89TE }, 3968c2ecf20Sopenharmony_ci { } 3978c2ecf20Sopenharmony_ci}; 3988c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, vz89x_id); 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic struct i2c_driver vz89x_driver = { 4018c2ecf20Sopenharmony_ci .driver = { 4028c2ecf20Sopenharmony_ci .name = "vz89x", 4038c2ecf20Sopenharmony_ci .of_match_table = vz89x_dt_ids, 4048c2ecf20Sopenharmony_ci }, 4058c2ecf20Sopenharmony_ci .probe = vz89x_probe, 4068c2ecf20Sopenharmony_ci .id_table = vz89x_id, 4078c2ecf20Sopenharmony_ci}; 4088c2ecf20Sopenharmony_cimodule_i2c_driver(vz89x_driver); 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>"); 4118c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors"); 4128c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 413