// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2023 Anshul Dalal * * Driver for Aosong AGS02MA * * Datasheet: * https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf * Product Page: * http://www.aosong.com/m/en/products-33.html */ #include #include #include #include #include #define AGS02MA_TVOC_READ_REG 0x00 #define AGS02MA_VERSION_REG 0x11 #define AGS02MA_VERSION_PROCESSING_DELAY 30 #define AGS02MA_TVOC_READ_PROCESSING_DELAY 1500 #define AGS02MA_CRC8_INIT 0xff #define AGS02MA_CRC8_POLYNOMIAL 0x31 DECLARE_CRC8_TABLE(ags02ma_crc8_table); struct ags02ma_data { struct i2c_client *client; }; struct ags02ma_reading { __be32 data; u8 crc; } __packed; static int ags02ma_register_read(struct i2c_client *client, u8 reg, u16 delay, u32 *val) { int ret; u8 crc; struct ags02ma_reading read_buffer; ret = i2c_master_send(client, ®, sizeof(reg)); if (ret < 0) { dev_err(&client->dev, "Failed to send data to register 0x%x: %d", reg, ret); return ret; } /* Processing Delay, Check Table 7.7 in the datasheet */ msleep_interruptible(delay); ret = i2c_master_recv(client, (u8 *)&read_buffer, sizeof(read_buffer)); if (ret < 0) { dev_err(&client->dev, "Failed to receive from register 0x%x: %d", reg, ret); return ret; } crc = crc8(ags02ma_crc8_table, (u8 *)&read_buffer.data, sizeof(read_buffer.data), AGS02MA_CRC8_INIT); if (crc != read_buffer.crc) { dev_err(&client->dev, "CRC error\n"); return -EIO; } *val = be32_to_cpu(read_buffer.data); return 0; } static int ags02ma_read_raw(struct iio_dev *iio_device, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { int ret; struct ags02ma_data *data = iio_priv(iio_device); switch (mask) { case IIO_CHAN_INFO_RAW: ret = ags02ma_register_read(data->client, AGS02MA_TVOC_READ_REG, AGS02MA_TVOC_READ_PROCESSING_DELAY, val); if (ret < 0) return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* The sensor reads data as ppb */ *val = 0; *val2 = 100; return IIO_VAL_INT_PLUS_NANO; default: return -EINVAL; } } static const struct iio_info ags02ma_info = { .read_raw = ags02ma_read_raw, }; static const struct iio_chan_spec ags02ma_channel = { .type = IIO_CONCENTRATION, .channel2 = IIO_MOD_VOC, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }; static int ags02ma_probe(struct i2c_client *client) { int ret; struct ags02ma_data *data; struct iio_dev *indio_dev; u32 version; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; crc8_populate_msb(ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL); ret = ags02ma_register_read(client, AGS02MA_VERSION_REG, AGS02MA_VERSION_PROCESSING_DELAY, &version); if (ret < 0) return dev_err_probe(&client->dev, ret, "Failed to read device version\n"); dev_dbg(&client->dev, "Aosong AGS02MA, Version: 0x%x", version); data = iio_priv(indio_dev); data->client = client; indio_dev->info = &ags02ma_info; indio_dev->channels = &ags02ma_channel; indio_dev->num_channels = 1; indio_dev->name = "ags02ma"; return devm_iio_device_register(&client->dev, indio_dev); } static const struct i2c_device_id ags02ma_id_table[] = { { "ags02ma" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(i2c, ags02ma_id_table); static const struct of_device_id ags02ma_of_table[] = { { .compatible = "aosong,ags02ma" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, ags02ma_of_table); static struct i2c_driver ags02ma_driver = { .driver = { .name = "ags02ma", .of_match_table = ags02ma_of_table, }, .id_table = ags02ma_id_table, .probe = ags02ma_probe, }; module_i2c_driver(ags02ma_driver); MODULE_AUTHOR("Anshul Dalal "); MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver"); MODULE_LICENSE("GPL");