iio:humidity:Add aht20 support

Signed-off-by: maoxiaochuan <maoxiaochuan@loongson.cn>
This commit is contained in:
maoxiaochuan
2026-02-04 16:40:54 +08:00
parent 505444594c
commit b604c407e2
3 changed files with 791 additions and 0 deletions
+10
View File
@@ -143,4 +143,14 @@ config SI7020
To compile this driver as a module, choose M here: the module
will be called si7020.
config AHT20
tristate "ASAIR AHT20 temperature and humidity sensor"
depends on I2C
help
If you say yes here you get support for the ASAIR AHT20
temperature and humidity sensor.
This driver can also be built as a module. If so, the module
will be called aht20.
endmenu
+1
View File
@@ -20,5 +20,6 @@ obj-$(CONFIG_HTS221_SPI) += hts221_spi.o
obj-$(CONFIG_HTU21) += htu21.o
obj-$(CONFIG_SI7005) += si7005.o
obj-$(CONFIG_SI7020) += si7020.o
obj-$(CONFIG_AHT20) += aht20.o
ccflags-y += -I$(srctree)/drivers/iio/common/hid-sensors
+780
View File
@@ -0,0 +1,780 @@
/*
* AHT20 I2C temperature and humidity sensor driver
*
* Copyright (c) 2025
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/regulator/consumer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/version.h>
#include <linux/bitfield.h>
#include <linux/of.h>
#include <linux/of_device.h>
#define DRIVER_NAME "aht20"
#define DRIVER_VERSION "1.0.0"
/* Default I2C address (can be overridden by device tree) */
#define AHT20_DEFAULT_ADDR 0x70
/* Commands */
#define AHT20_CMD_INIT 0xBE
#define AHT20_CMD_MEASURE 0xAC
#define AHT20_CMD_SOFTRESET 0xBA
#define AHT20_CMD_STATUS 0x71
/* Status bits */
#define AHT20_STATUS_BUSY BIT(7)
#define AHT20_STATUS_CALIBRATED BIT(3)
/* Timing constants */
#define AHT20_POWER_ON_DELAY_MS 20
#define AHT20_SOFT_RESET_DELAY_MS 20
#define AHT20_MEASURE_DELAY_MS 80
#define AHT20_CALIBRATION_DELAY_MS 10
/* Data length */
#define AHT20_DATA_LEN 7
/* Channel definitions */
#define AHT20_CHANNEL_TEMP 0
#define AHT20_CHANNEL_HUMIDITY 1
#define AHT20_CHANNEL_COUNT 2
/* Scale factors */
#define AHT20_TEMP_SCALE_NANO 244141 /* 200 / 2^20 * 10^9 */
#define AHT20_TEMP_OFFSET (-50 * 1000000000LL)
#define AHT20_HUMIDITY_SCALE_NANO 95367 /* 100 / 2^20 * 10^9 */
struct aht20_data {
struct i2c_client *client;
struct mutex lock;
struct regulator *vdd;
struct iio_trigger *trig;
struct delayed_work measure_work;
bool triggered_measurement;
/* Last reading */
u32 raw_temp;
u32 raw_humidity;
u8 status;
bool valid;
/* Calibration data */
bool calibrated;
int64_t timestamp;
/* For triggered buffer */
struct {
s32 temp;
s32 humidity;
s64 ts;
} scan;
/* Device specific data */
u8 i2c_addr; /* Store the actual I2C address */
};
/**
* aht20_read_data() - read raw data from sensor
*/
static int aht20_read_data(struct i2c_client *client, u8 *buf, int len)
{
// struct aht20_data *data = i2c_get_clientdata(client);
struct i2c_msg msgs[2] = {
{
.addr = client->addr,
.flags = 0,
.len = 0,
.buf = NULL,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
int ret;
/* Use the stored address for debugging/logging purposes */
dev_info(&client->dev, "Reading from I2C address 0x%02x\n", client->addr);
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0) {
dev_err(&client->dev, "I2C read failed: %d\n", ret);
return ret;
}
return (ret == 2) ? 0 : -EIO;
}
/**
* aht20_write_cmd() - write command to sensor
*/
static int aht20_write_cmd(struct i2c_client *client, u8 *buf, int len)
{
//struct aht20_data *data = i2c_get_clientdata(client);
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = len,
.buf = buf,
};
int ret;
dev_info(&client->dev, "Writing to I2C address 0x%02x\n", client->addr);
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "I2C write failed: %d\n", ret);
return ret;
}
return (ret == 1) ? 0 : -EIO;
}
/**
* aht20_soft_reset() - perform soft reset
*/
static int aht20_soft_reset(struct i2c_client *client)
{
u8 cmd = AHT20_CMD_SOFTRESET;
int ret;
dev_info(&client->dev, "Sending soft reset command\n");
ret = aht20_write_cmd(client, &cmd, 1);
if (ret < 0)
return ret;
msleep(AHT20_SOFT_RESET_DELAY_MS);
return 0;
}
/**
* aht20_read_status() - read status register
*/
static int aht20_read_status(struct i2c_client *client, u8 *status)
{
int ret;
ret = aht20_read_data(client, status, 1);
if (ret < 0)
return ret;
dev_dbg(&client->dev, "Status register: 0x%02x\n", *status);
return 0;
}
/**
* aht20_init_sensor() - initialize sensor
*/
static int aht20_init_sensor(struct i2c_client *client)
{
struct aht20_data *data = i2c_get_clientdata(client);
u8 cmd[3] = {AHT20_CMD_INIT, 0x08, 0x00};
u8 status;
int ret;
dev_info(&client->dev, "Initializing AHT20 at I2C address 0x%02x\n",
client->addr);
/* Power on delay */
msleep(AHT20_POWER_ON_DELAY_MS);
/* Soft reset */
ret = aht20_soft_reset(client);
if (ret < 0) {
dev_err(&client->dev, "aht20 soft reset fail\n");
return ret;
}
/* Check calibration status */
ret = aht20_read_status(client, &status);
if (ret < 0)
return ret;
if (!(status & AHT20_STATUS_CALIBRATED)) {
dev_info(&client->dev, "Calibrating sensor\n");
/* Send calibration command */
ret = aht20_write_cmd(client, cmd, 3);
if (ret < 0)
return ret;
msleep(AHT20_CALIBRATION_DELAY_MS);
data->calibrated = true;
dev_info(&client->dev, "Calibration complete\n");
} else {
dev_info(&client->dev, "Sensor already calibrated\n");
data->calibrated = true;
}
return 0;
}
/**
* aht20_calc_crc() - calculate CRC8
*/
static u8 aht20_calc_crc(const u8 *data, u8 len)
{
u8 i, bit;
u8 crc = 0xFF;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc <<= 1;
}
}
return crc;
}
/**
* aht20_read_measurement() - read temperature and humidity
*/
static int aht20_read_measurement(struct i2c_client *client,
u32 *raw_temp, u32 *raw_humidity)
{
// struct aht20_data *data = i2c_get_clientdata(client);
u8 cmd[3] = {AHT20_CMD_MEASURE, 0x33, 0x00};
u8 data_buf[AHT20_DATA_LEN];
u8 crc;
int ret;
dev_dbg(&client->dev, "Starting measurement\n");
/* Send measurement command */
ret = aht20_write_cmd(client, cmd, 3);
if (ret < 0)
return ret;
/* Wait for measurement to complete */
msleep(AHT20_MEASURE_DELAY_MS);
/* Read measurement data */
ret = aht20_read_data(client, data_buf, AHT20_DATA_LEN);
if (ret < 0)
return ret;
/* Check if sensor is busy */
if (data_buf[0] & AHT20_STATUS_BUSY) {
dev_warn(&client->dev, "Sensor busy, retrying\n");
return -EAGAIN;
}
/* Verify CRC */
crc = aht20_calc_crc(data_buf, 6);
if (crc != data_buf[6]) {
dev_err(&client->dev, "CRC error: expected 0x%02x, got 0x%02x\n",
crc, data_buf[6]);
return -EIO;
}
/* Extract humidity data (20 bits) */
*raw_humidity = ((u32)data_buf[1] << 16) |
((u32)data_buf[2] << 8) |
data_buf[3];
*raw_humidity >>= 4;
/* Extract temperature data (20 bits) */
*raw_temp = ((u32)data_buf[3] & 0x0F) << 16 |
((u32)data_buf[4] << 8) |
data_buf[5];
dev_info(&client->dev, "Raw humidity: 0x%05x, Raw temperature: 0x%05x\n",
*raw_humidity, *raw_temp);
return 0;
}
/**
* aht20_read_raw() - IIO read_raw callback
*/
static int aht20_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct aht20_data *data = iio_priv(indio_dev);
struct i2c_client *client = data->client;
int ret = 0;
dev_dbg(&client->dev, "aht20_read_raw enter ... %ld, type:%d\n", mask, chan->type);
mutex_lock(&data->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
/* Trigger a new measurement */
ret = aht20_read_measurement(client,
&data->raw_temp,
&data->raw_humidity);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
}
data->valid = true;
data->timestamp = iio_get_time_ns(indio_dev);
switch (chan->type) {
case IIO_TEMP:
*val = (s32)data->raw_temp;
ret = IIO_VAL_INT;
dev_dbg(&client->dev, "Temperature raw: %d (0x%x)\n",
*val, data->raw_temp);
break;
case IIO_HUMIDITYRELATIVE:
*val = (s32)data->raw_humidity;
ret = IIO_VAL_INT;
dev_dbg(&client->dev, "Humidity raw: %d (0x%x)\n",
*val, data->raw_humidity);
break;
default:
ret = -EINVAL;
break;
}
break;
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_TEMP:
*val = AHT20_TEMP_SCALE_NANO / 1000;
*val2 = AHT20_TEMP_SCALE_NANO % 1000 * 1000;
ret = IIO_VAL_INT_PLUS_NANO;
break;
case IIO_HUMIDITYRELATIVE:
*val = 0;
*val2 = AHT20_HUMIDITY_SCALE_NANO;
ret = IIO_VAL_INT_PLUS_NANO;
break;
default:
ret = -EINVAL;
break;
}
break;
case IIO_CHAN_INFO_OFFSET:
if (chan->type == IIO_TEMP) {
*val = AHT20_TEMP_OFFSET / 1000000000LL;
*val2 = AHT20_TEMP_OFFSET % 1000000000LL;
ret = IIO_VAL_INT_PLUS_NANO;
} else {
ret = -EINVAL;
}
break;
case IIO_CHAN_INFO_SAMP_FREQ:
/* Fixed measurement time ~80ms */
*val = 12; /* ~12 Hz */
*val2 = 0;
ret = IIO_VAL_INT;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&data->lock);
if (ret >= 0 && mask == IIO_CHAN_INFO_RAW) {
dev_dbg(&client->dev, "Successfully read channel type %d: %d\n",
chan->type, *val);
}
return ret;
}
/**
* aht20_write_raw() - IIO write_raw callback
*/
static int aht20_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
/* This sensor doesn't support configuration */
return -EINVAL;
}
/**
* aht20_read_avail() - IIO read_avail callback
*/
static int aht20_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
static const int temp_scale_avail[] = { AHT20_TEMP_SCALE_NANO };
static const int humidity_scale_avail[] = { AHT20_HUMIDITY_SCALE_NANO };
static const int sample_freq_avail[] = { 12 };
switch (mask) {
case IIO_CHAN_INFO_SCALE:
*type = IIO_VAL_INT_PLUS_NANO;
*length = 1;
if (chan->type == IIO_TEMP) {
*vals = temp_scale_avail;
} else {
*vals = humidity_scale_avail;
}
return IIO_AVAIL_LIST;
case IIO_CHAN_INFO_SAMP_FREQ:
*type = IIO_VAL_INT;
*length = 1;
*vals = sample_freq_avail;
return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
}
/**
* aht20_validate_trigger() - Validate trigger
*/
static int aht20_validate_trigger(struct iio_dev *indio_dev,
struct iio_trigger *trig)
{
struct aht20_data *data = iio_priv(indio_dev);
if (data->trig != trig)
return -EINVAL;
return 0;
}
/**
* aht20_trigger_handler() - Trigger handler for buffer
*/
static irqreturn_t aht20_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct aht20_data *data = iio_priv(indio_dev);
int ret;
mutex_lock(&data->lock);
/* Perform measurement */
ret = aht20_read_measurement(data->client,
&data->raw_temp,
&data->raw_humidity);
if (ret < 0) {
mutex_unlock(&data->lock);
goto out;
}
data->valid = true;
data->timestamp = iio_get_time_ns(indio_dev);
/* Convert raw values to processed values */
/* Temperature: T = raw * 200 / 2^20 - 50 */
data->scan.temp = ((s64)data->raw_temp * 200000000000LL >> 20) - 50000000000LL;
/* Humidity: H = raw * 100 / 2^20 */
data->scan.humidity = (s64)data->raw_humidity * 100000000000LL >> 20;
data->scan.ts = data->timestamp;
/* Push to buffer */
iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
data->timestamp);
mutex_unlock(&data->lock);
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
/**
* aht20_measurement_work() - Work function for periodic measurements
*/
static void aht20_measurement_work(struct work_struct *work)
{
struct aht20_data *data = container_of(to_delayed_work(work),
struct aht20_data,
measure_work);
struct iio_dev *indio_dev = i2c_get_clientdata(data->client);
int ret;
mutex_lock(&data->lock);
ret = aht20_read_measurement(data->client,
&data->raw_temp,
&data->raw_humidity);
if (ret == 0) {
data->valid = true;
data->timestamp = iio_get_time_ns(indio_dev);
/* Store in buffer if triggered */
if (data->triggered_measurement) {
data->scan.temp = ((s64)data->raw_temp * 200000000000LL >> 20) - 50000000000LL;
data->scan.humidity = (s64)data->raw_humidity * 100000000000LL >> 20;
data->scan.ts = data->timestamp;
iio_push_to_buffers_with_timestamp(indio_dev,
&data->scan,
data->timestamp);
}
}
mutex_unlock(&data->lock);
/* Schedule next measurement */
if (data->triggered_measurement) {
schedule_delayed_work(&data->measure_work,
msecs_to_jiffies(AHT20_MEASURE_DELAY_MS));
}
}
/**
* aht20_buffer_preenable() - Buffer preenable callback
*/
static int aht20_buffer_preenable(struct iio_dev *indio_dev)
{
struct aht20_data *data = iio_priv(indio_dev);
data->triggered_measurement = true;
schedule_delayed_work(&data->measure_work,
msecs_to_jiffies(AHT20_MEASURE_DELAY_MS));
return 0;
}
/**
* aht20_buffer_postdisable() - Buffer postdisable callback
*/
static int aht20_buffer_postdisable(struct iio_dev *indio_dev)
{
struct aht20_data *data = iio_priv(indio_dev);
data->triggered_measurement = false;
cancel_delayed_work_sync(&data->measure_work);
return 0;
}
static const struct iio_buffer_setup_ops aht20_buffer_setup_ops = {
.preenable = aht20_buffer_preenable,
// .postenable = iio_triggered_buffer_postenable,
// .predisable = iio_triggered_buffer_predisable,
.postdisable = aht20_buffer_postdisable,
};
/* Channel definitions */
static const struct iio_chan_spec aht20_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET),
.info_mask_shared_by_type_available =
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = AHT20_CHANNEL_TEMP,
.scan_type = {
.sign = 's',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_CPU,
},
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_type_available =
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = AHT20_CHANNEL_HUMIDITY,
.scan_type = {
.sign = 's',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_CPU,
},
},
IIO_CHAN_SOFT_TIMESTAMP(2),
};
/* IIO info structure */
static const struct iio_info aht20_info = {
.read_raw = aht20_read_raw,
.write_raw = aht20_write_raw,
.read_avail = aht20_read_avail,
.validate_trigger = aht20_validate_trigger,
};
/* Device tree match table */
static const struct of_device_id aht20_of_match[] = {
{ .compatible = "asair,aht20" },
{ }
};
MODULE_DEVICE_TABLE(of, aht20_of_match);
/* I2C device ID table */
static const struct i2c_device_id aht20_id[] = {
{ "aht20", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, aht20_id);
/**
* aht20_probe() - probe function
*/
static int aht20_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct aht20_data *data;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
/* Get I2C address from device tree */
if (!client->addr) {
dev_err(dev, "I2C address not specified\n");
return -EINVAL;
}
dev_info(dev, "Probing AHT20 at I2C address 0x%02x\n", client->addr);
/* Allocate IIO device */
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
data->i2c_addr = client->addr; /* Store the address */
mutex_init(&data->lock);
i2c_set_clientdata(client, indio_dev);
/* Get power supply (optional) */
data->vdd = devm_regulator_get_optional(dev, "vdd");
if (IS_ERR(data->vdd)) {
if (PTR_ERR(data->vdd) != -ENODEV) {
ret = PTR_ERR(data->vdd);
dev_err(dev, "Failed to get regulator: %d\n", ret);
return ret;
}
/* No regulator specified, assume always powered */
data->vdd = NULL;
dev_info(dev, "No regulator specified, assuming always powered\n");
}
/* Enable power if regulator exists */
if (data->vdd) {
ret = regulator_enable(data->vdd);
if (ret < 0) {
dev_err(dev, "Failed to enable regulator: %d\n", ret);
return ret;
}
dev_info(dev, "Regulator enabled\n");
}
/* Initialize sensor */
ret = aht20_init_sensor(client);
if (ret < 0) {
dev_err(dev, "Failed to initialize AHT20: %d\n", ret);
goto err_disable_reg;
}
/* Initialize delayed work */
INIT_DELAYED_WORK(&data->measure_work, aht20_measurement_work);
/* Setup IIO device */
indio_dev->name = dev_name(dev);
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &aht20_info;
indio_dev->channels = aht20_channels;
indio_dev->num_channels = ARRAY_SIZE(aht20_channels);
/* Setup buffer */
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
aht20_trigger_handler,
&aht20_buffer_setup_ops);
if (ret < 0) {
dev_err(dev, "Failed to setup buffer: %d\n", ret);
goto err_disable_reg;
}
/* Register IIO device */
ret = devm_iio_device_register(dev, indio_dev);
if (ret < 0) {
dev_err(dev, "Failed to register IIO device: %d\n", ret);
goto err_disable_reg;
}
dev_info(dev, "AHT20 temperature/humidity sensor registered at 0x%02x\n",
client->addr);
return 0;
err_disable_reg:
regulator_disable(data->vdd);
return ret;
}
/**
* aht20_remove() - remove function
*/
static void aht20_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct aht20_data *data = iio_priv(indio_dev);
/* Cancel any pending work */
cancel_delayed_work_sync(&data->measure_work);
/* Disable power */
regulator_disable(data->vdd);
dev_info(&client->dev, "AHT20 removed from I2C address 0x%02x\n",
client->addr);
}
static struct i2c_driver aht20_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = aht20_of_match,
},
.probe = aht20_probe,
.remove = aht20_remove,
.id_table = aht20_id,
};
module_i2c_driver(aht20_driver);
MODULE_AUTHOR("Xiaochuan Mao<maoxiaochuan@loongson.cn>");
MODULE_DESCRIPTION("AHT20 I2C temperature and humidity sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRIVER_VERSION);