Files
2024-06-17 20:18:01 +08:00

285 lines
6.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/* Sensirion AHT21 humidity and temperature sensor driver
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/device.h>
/**
* struct aht21 - AHT21 device specific data
* @client: I2C client device
* @lock: mutex to protect measurement values
* @temperature: cached temperature measurement value
* @humidity: cached humidity measurement value
*/
struct aht21 {
struct i2c_client *client;
struct mutex lock;
int temperature;
int humidity;
};
static int aht21_reset_reg(struct i2c_client *client, char reg)
{
int ret;
u8 buf[3] = { 0 };
buf[0] = reg;
buf[1] = 0;
buf[2] = 0;
ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "failed to send read request\n");
goto exit;
}
usleep_range(5000, 6000);
/* Do a bulk data read, then pick out what we need. */
ret = i2c_master_recv(client, buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "failed to read sensor data\n");
goto exit;
}
usleep_range(10000, 11000);
buf[0] = 0xb0 | reg;
ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "failed to send read request\n");
goto exit;
}
return 0;
exit:
return ret;
}
static int aht21_hw_init(struct i2c_client *client)
{
char val;
int ret;
u8 buf[3] = { 0 };
val = i2c_smbus_read_byte(client);
if ((val & 0x18) != 0x18) {
buf[0] = 0xa8;
buf[1] = 0;
buf[2] = 0;
ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "failed to send read request\n");
goto out;
}
usleep_range(10000, 11000);
buf[0] = 0xbe;
buf[1] = 0x08;
buf[2] = 0;
ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "failed to send read request\n");
goto out;
}
usleep_range(10000, 11000);
aht21_reset_reg(client, 0x1b);
aht21_reset_reg(client, 0x1c);
aht21_reset_reg(client, 0x1e);
}
return 0;
out:
return ret;
}
static int aht21_crc(u8 *val, int num)
{
u8 i, byte, crc = 0xff;
for (byte=0; byte<num; byte++) {
crc ^= val[byte];
for (i=8; i>0; --i) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc = (crc << 1);
}
}
return crc;
}
/**
* aht21_update_measurements() - get updated measurements from device
* @dev: device
*
* Returns 0 on success, else negative errno.
*/
static int aht21_update_measurements(struct device *dev)
{
int ret = 0;
struct aht21 *aht21 = dev_get_drvdata(dev);
struct i2c_client *client = aht21->client;
u8 buf[8] = { 0 };
int timeout;
int temperature = 0, humidity = 0;
mutex_lock(&aht21->lock);
buf[0] = 0xac;
buf[1] = 0x33;
buf[2] = 0;
ret = i2c_master_send(client, buf, 3);
if (ret < 0) {
dev_err(&client->dev, "failed to send read request\n");
goto out;
}
usleep_range(80000, 90000);
while ((i2c_smbus_read_byte(client) & 0x80) == 0x80) {
usleep_range(100, 200);
if (timeout++ > 100)
break;
}
ret = i2c_master_recv(client, buf, 7);
if (ret < 0) {
dev_err(&client->dev, "failed to read sensor data\n");
goto out;
}
humidity = (humidity | buf[1]) << 8;
humidity = (humidity | buf[2]) << 8;
humidity = (humidity | buf[3]);
humidity = humidity >> 4;
temperature = (temperature | buf[3]) << 8;
temperature = (temperature | buf[4]) << 8;
temperature = (temperature | buf[5]);
temperature = temperature & 0xfffff;
if (aht21_crc(buf, 6) != buf[6]) {
dev_err(&client->dev, "sensor data crc error\n");
}
aht21->temperature = (((temperature * 200) / 1024) * 1000) / 1024 - 50 * 1000;
aht21->humidity = (((humidity * 100) / 1024) * 1000) / 1024;
out:
mutex_unlock(&aht21->lock);
return ret >= 0 ? 0 : ret;
}
/**
* aht21_show_temperature() - show temperature measurement value in sysfs
* @dev: device
* @attr: device attribute
* @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to
*
* Will be called on read access to temp1_input sysfs attribute.
* Returns number of bytes written into buffer, negative errno on error.
*/
static ssize_t aht21_temperature_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct aht21 *aht21 = dev_get_drvdata(dev);
int ret;
ret = aht21_update_measurements(dev);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", aht21->temperature);
}
/**
* aht21_show_humidity() - show humidity measurement value in sysfs
* @dev: device
* @attr: device attribute
* @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to
*
* Will be called on read access to humidity1_input sysfs attribute.
* Returns number of bytes written into buffer, negative errno on error.
*/
static ssize_t aht21_humidity_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct aht21 *aht21 = dev_get_drvdata(dev);
int ret;
ret = aht21_update_measurements(dev);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", aht21->humidity);
}
/* sysfs attributes */
static SENSOR_DEVICE_ATTR_RO(temp1_input, aht21_temperature, 0);
static SENSOR_DEVICE_ATTR_RO(humidity1_input, aht21_humidity, 0);
static struct attribute *aht21_attrs[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_humidity1_input.dev_attr.attr,
NULL
};
ATTRIBUTE_GROUPS(aht21);
static int aht21_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct aht21 *aht21;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(&client->dev,
"adapter does not support SMBus word transactions\n");
return -ENODEV;
}
aht21 = devm_kzalloc(dev, sizeof(*aht21), GFP_KERNEL);
if (!aht21)
return -ENOMEM;
aht21->client = client;
aht21_hw_init(client);
mutex_init(&aht21->lock);
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
aht21, aht21_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
/* Device ID table */
static const struct i2c_device_id aht21_id[] = {
{ "aht21", 0 },
{ "aht20", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, aht21_id);
static struct i2c_driver aht21_driver = {
.driver.name = "aht21",
.probe_new = aht21_probe,
.id_table = aht21_id,
};
module_i2c_driver(aht21_driver);
MODULE_AUTHOR("TangHaifeng <tanghaifeng-gz@loongson.cn>");
MODULE_DESCRIPTION("Aosong AHT21 humidity and temperature sensor driver");
MODULE_LICENSE("GPL");