mirror of
https://gitee.com/open-loongarch/linux-6.12.git
synced 2026-10-25 12:37:11 +08:00
iio:humidity:Add aht20 support
Signed-off-by: maoxiaochuan <maoxiaochuan@loongson.cn>
This commit is contained in:
@@ -143,4 +143,14 @@ config SI7020
|
|||||||
To compile this driver as a module, choose M here: the module
|
To compile this driver as a module, choose M here: the module
|
||||||
will be called si7020.
|
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
|
endmenu
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ obj-$(CONFIG_HTS221_SPI) += hts221_spi.o
|
|||||||
obj-$(CONFIG_HTU21) += htu21.o
|
obj-$(CONFIG_HTU21) += htu21.o
|
||||||
obj-$(CONFIG_SI7005) += si7005.o
|
obj-$(CONFIG_SI7005) += si7005.o
|
||||||
obj-$(CONFIG_SI7020) += si7020.o
|
obj-$(CONFIG_SI7020) += si7020.o
|
||||||
|
obj-$(CONFIG_AHT20) += aht20.o
|
||||||
|
|
||||||
ccflags-y += -I$(srctree)/drivers/iio/common/hid-sensors
|
ccflags-y += -I$(srctree)/drivers/iio/common/hid-sensors
|
||||||
|
|||||||
@@ -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);
|
||||||
Reference in New Issue
Block a user