Files
openloongarch-linux-6.12/drivers/iio/humidity/aht20.c
T
2026-02-04 16:40:54 +08:00

781 lines
18 KiB
C

/*
* 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);