190 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors4 *5 * Copyright (C) 2015, 20186 * Author: Matt Ranostay <matt.ranostay@konsulko.com>7 */8 9#include <linux/module.h>10#include <linux/mod_devicetable.h>11#include <linux/mutex.h>12#include <linux/init.h>13#include <linux/i2c.h>14#include <linux/iio/iio.h>15 16#define AMS_IAQCORE_DATA_SIZE 917 18#define AMS_IAQCORE_VOC_CO2_IDX 019#define AMS_IAQCORE_VOC_RESISTANCE_IDX 120#define AMS_IAQCORE_VOC_TVOC_IDX 221 22struct ams_iaqcore_reading {23 __be16 co2_ppm;24 u8 status;25 __be32 resistance;26 __be16 voc_ppb;27} __packed;28 29struct ams_iaqcore_data {30 struct i2c_client *client;31 struct mutex lock;32 unsigned long last_update;33 34 struct ams_iaqcore_reading buffer;35};36 37static const struct iio_chan_spec ams_iaqcore_channels[] = {38 {39 .type = IIO_CONCENTRATION,40 .channel2 = IIO_MOD_CO2,41 .modified = 1,42 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),43 .address = AMS_IAQCORE_VOC_CO2_IDX,44 },45 {46 .type = IIO_RESISTANCE,47 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),48 .address = AMS_IAQCORE_VOC_RESISTANCE_IDX,49 },50 {51 .type = IIO_CONCENTRATION,52 .channel2 = IIO_MOD_VOC,53 .modified = 1,54 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),55 .address = AMS_IAQCORE_VOC_TVOC_IDX,56 },57};58 59static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data)60{61 struct i2c_client *client = data->client;62 int ret;63 64 struct i2c_msg msg = {65 .addr = client->addr,66 .flags = client->flags | I2C_M_RD,67 .len = AMS_IAQCORE_DATA_SIZE,68 .buf = (char *) &data->buffer,69 };70 71 ret = i2c_transfer(client->adapter, &msg, 1);72 73 return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret;74}75 76static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data)77{78 int ret;79 80 /* sensor can only be polled once a second max per datasheet */81 if (!time_after(jiffies, data->last_update + HZ))82 return 0;83 84 ret = ams_iaqcore_read_measurement(data);85 if (ret < 0)86 return ret;87 88 data->last_update = jiffies;89 90 return 0;91}92 93static int ams_iaqcore_read_raw(struct iio_dev *indio_dev,94 struct iio_chan_spec const *chan, int *val,95 int *val2, long mask)96{97 struct ams_iaqcore_data *data = iio_priv(indio_dev);98 int ret;99 100 if (mask != IIO_CHAN_INFO_PROCESSED)101 return -EINVAL;102 103 mutex_lock(&data->lock);104 ret = ams_iaqcore_get_measurement(data);105 106 if (ret)107 goto err_out;108 109 switch (chan->address) {110 case AMS_IAQCORE_VOC_CO2_IDX:111 *val = 0;112 *val2 = be16_to_cpu(data->buffer.co2_ppm);113 ret = IIO_VAL_INT_PLUS_MICRO;114 break;115 case AMS_IAQCORE_VOC_RESISTANCE_IDX:116 *val = be32_to_cpu(data->buffer.resistance);117 ret = IIO_VAL_INT;118 break;119 case AMS_IAQCORE_VOC_TVOC_IDX:120 *val = 0;121 *val2 = be16_to_cpu(data->buffer.voc_ppb);122 ret = IIO_VAL_INT_PLUS_NANO;123 break;124 default:125 ret = -EINVAL;126 }127 128err_out:129 mutex_unlock(&data->lock);130 131 return ret;132}133 134static const struct iio_info ams_iaqcore_info = {135 .read_raw = ams_iaqcore_read_raw,136};137 138static int ams_iaqcore_probe(struct i2c_client *client)139{140 struct iio_dev *indio_dev;141 struct ams_iaqcore_data *data;142 143 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));144 if (!indio_dev)145 return -ENOMEM;146 147 data = iio_priv(indio_dev);148 i2c_set_clientdata(client, indio_dev);149 data->client = client;150 151 /* so initial reading will complete */152 data->last_update = jiffies - HZ;153 mutex_init(&data->lock);154 155 indio_dev->info = &ams_iaqcore_info;156 indio_dev->name = dev_name(&client->dev);157 indio_dev->modes = INDIO_DIRECT_MODE;158 159 indio_dev->channels = ams_iaqcore_channels;160 indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels);161 162 return devm_iio_device_register(&client->dev, indio_dev);163}164 165static const struct i2c_device_id ams_iaqcore_id[] = {166 { "ams-iaq-core" },167 { }168};169MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id);170 171static const struct of_device_id ams_iaqcore_dt_ids[] = {172 { .compatible = "ams,iaq-core" },173 { }174};175MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids);176 177static struct i2c_driver ams_iaqcore_driver = {178 .driver = {179 .name = "ams-iaq-core",180 .of_match_table = ams_iaqcore_dt_ids,181 },182 .probe = ams_iaqcore_probe,183 .id_table = ams_iaqcore_id,184};185module_i2c_driver(ams_iaqcore_driver);186 187MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");188MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");189MODULE_LICENSE("GPL v2");190