mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-28 14:07:12 +08:00
285 lines
6.4 KiB
C
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");
|