Re: [PATCH] hwmon: Add AHT10 Temperature and Humidity Sensor Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 12/13/20 1:48 PM, datdenkikniet wrote:
> This patch adds a hwmon driver for the AHT10 Temperature
> and Humidity sensor. It has a maxiumum sample rate, as
> the datasheet states that the chip may heat up if it is
> sampled too often.
> 
> Has been tested to work on a raspberrypi0w
> 
> Signed-off-by: Johannes Cornelis Draaijer (datdenkikniet) <jcdra1@xxxxxxxxx>

This patch didn't make it to patchwork. On top of that, my e-mail provider
tagged it as spam, based on the following information.

        1.0 TVD_RCVD_IP            Message was received from an IP address
        0.4 NO_DNS_FOR_FROM        DNS: Envelope sender has no MX or A DNS records
        0.0 SPF_NONE               SPF: sender does not publish an SPF Record
        0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different
        1.0 FORGED_GMAIL_RCVD      'From' gmail.com does not match 'Received' headers
        0.5 FREEMAIL_FROM          Sender email is commonly abused enduser mail provider [jcdra1[at]gmail.com]
        0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record
        0.0 DKIM_ADSP_CUSTOM_MED   No valid author signature, adsp_override is CUSTOM_MED
        0.0 FREEMAIL_FORGED_FROMDOMAIN 2nd level domains in From and EnvelopeFrom freemail headers are different
        0.4 RDNS_DYNAMIC           Delivered to internal network by host with dynamic-looking rDNS
        1.2 NML_ADSP_CUSTOM_MED    ADSP custom_med hit, and not from a mailing list
        0.3 KHOP_HELO_FCRDNS       Relay HELO differs from its IP's reverse DNS
        2.0 SPOOFED_FREEMAIL       No description available.
        1.0 TO_NO_BRKTS_PCNT       To: lacks brackets + percentage

I would suggest to fix that before resubmitting.

> ---
>  drivers/hwmon/Kconfig  |  10 +
>  drivers/hwmon/Makefile |   1 +
>  drivers/hwmon/aht10.c  | 405 +++++++++++++++++++++++++++++++++++++++++

Documentation is missing.

>  3 files changed, 416 insertions(+)
>  create mode 100644 drivers/hwmon/aht10.c
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 288ae9f63588..96bad243d729 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -257,6 +257,16 @@ config SENSORS_ADT7475
>  	  This driver can also be built as a module. If so, the module
>  	  will be called adt7475.
>  
> +config SENSORS_AHT10
> +	tristate "Aosong AHT10"
> +	depends on I2C
> +	help
> +	  If you say yes here, you get support for the Aosong AHT10
> +	  temperature and humidity sensors
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called aht10.
> +
>  config SENSORS_AS370
>  	tristate "Synaptics AS370 SoC hardware monitoring driver"
>  	help
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 3e32c21f5efe..6cb44d54e628 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411)	+= adt7411.o
>  obj-$(CONFIG_SENSORS_ADT7462)	+= adt7462.o
>  obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
>  obj-$(CONFIG_SENSORS_ADT7475)	+= adt7475.o
> +obj-$(CONFIG_SENSORS_AHT10)	+= aht10.o
>  obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
>  obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
>  obj-$(CONFIG_SENSORS_ARM_SCMI)	+= scmi-hwmon.o
> diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c
> new file mode 100644
> index 000000000000..1eeddce02ae9
> --- /dev/null
> +++ b/drivers/hwmon/aht10.c
> @@ -0,0 +1,405 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * aht10.c - Linux hwmon driver for AHT10 I2C Temperature and Humidity sensor
> + * Copyright (C) 2020 Johannes Cornelis Draaijer
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/i2c.h>
> +#include <linux/mutex.h>
> +#include <linux/delay.h>
> +#include <linux/ktime.h>
> +#include <linux/err.h>
> +#include <asm/div64.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>

Alphabetic include file order, please.

> +
> +#define AHT10_ADDR 0x38
> +
> +// Delays
> +
> +#define AHT10_POWERON_USEC_DELAY 40000
> +#define AHT10_MEAS_USEC_DELAY 80000
> +#define AHT10_CMD_USEC_DELAY 350000
> +#define AHT10_USEC_DELAY_OFFSET 100000

Please use

#define<space>XXX<tab>VALUE

> +
> +// Command bytes

Please no C++ style comments (except for the SPDX identifier)

> +
> +#define AHT10_CMD_INIT 0b11100001
> +#define AHT10_CMD_MEAS 0b10101100
> +#define AHT10_CMD_RST  0b10111010
> +
> +// Flags in the answer byte/command
> +
> +#define AHT10_RESP_ERROR 0xFF
> +
> +#define AHT10_CAL_ENABLED (1u << 3u)
> +#define AHT10_BUSY        (1u << 7u)
> +#define AHT10_MODE_NOR    (0b11u << 5u)
> +#define AHT10_MODE_CYC    (0b01u << 5u)
> +#define AHT10_MODE_CMD    (0b10u << 5u)
> +

Please use bit operations where possible.,

> +#define AHT10_MAX_POLL_INTERVAL_LEN 30
> +
> +// Full commands
> +
> +const u8 cmd_init[] = {AHT10_CMD_INIT, AHT10_CAL_ENABLED | AHT10_MODE_CYC,
> +		0x00};
> +const u8 cmd_meas[] = {AHT10_CMD_MEAS, 0x33, 0x00};
> +const u8 cmd_rst[] = {AHT10_CMD_RST, 0x00, 0x00};
> +
> +struct aht10_measurement {
> +	u8 data[6];

Only used within a function and thus pointless.

> +	u8 status;

Not used at all.

> +	int temperature;
> +	int humidity;

Fold into struct aht10_data.

> +};
> +
> +/**
> + *   struct aht10_data - All the data required to operate an AHT10 chip
> + *   @client: the i2c client associated with the AHT10
> + *   @lock: a mutex that is used to prevent parallel access to the
> + *          i2c client
> + *   @initialized: whether or not the AHT10 has been initialized
> + *   @current_measurement: the last-measured values of the AHT10
> + *   @poll_interval: the minimum poll interval
> + *                   While the poll rate is not 100% necessary,
> + *                   the datasheet recommends that a measurement
> + *                   is not performed more too often to prevent
> + *                   the chip from "heating up". If it's
> + *                   unwanted, it can be ignored by setting
> + *                   it to 0.
> + */
> +
> +struct aht10_data {
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	int initialized;

Only set and never used, and thus pointless.

> +	struct aht10_measurement current_measurement;
> +	ktime_t poll_interval;
> +	ktime_t previous_poll_time;
> +};
> +
> +
Please no double empty lines. Having said that, checkpatch --strict reports:

total: 0 errors, 5 warnings, 17 checks, 428 lines checked

Please fix.

> +/**
> + * aht10_init() - Initialize an AHT10 chip
> + * @client: the i2c client associated with the AHT10
> + * @data: the data associated with this AHT10 chip
> + * Return: 0 if succesfull, 1 if not
> + */
> +static int aht10_init(struct i2c_client *client, struct aht10_data *data)
> +{
> +	struct mutex *mutex = &data->lock;

Unnecessary variable.

> +
> +	int res;
> +	u8 status;
> +
> +	mutex_lock(mutex);
> +

Unnecessary lock. This is the init function. It won't be called
multiple times in parallel.

> +	usleep_range(AHT10_POWERON_USEC_DELAY, AHT10_POWERON_USEC_DELAY +
> +		AHT10_USEC_DELAY_OFFSET);

Pointless delay.

> +
> +	i2c_master_send(client, cmd_init, 3);
> +
> +	usleep_range(AHT10_CMD_USEC_DELAY, AHT10_CMD_USEC_DELAY +
> +		AHT10_USEC_DELAY_OFFSET);
> +
> +	res = i2c_master_recv(client, &status, 1);
> +
> +	if (res != 1) {
> +		mutex_unlock(mutex);
> +		return 1;

Return standard error codes. Everywhere.

> +	}
> +
> +	data->initialized = 1;
> +
> +	if (status & AHT10_BUSY)
> +		pr_warn("AHT10 busy flag is enabled! Is another program already using the AHT10?\n");

If this is a concern, return -EBUSY and exit with error.

> +
> +	mutex_unlock(mutex);
> +	return 0;
> +}
> +
> +/**
> + * aht10_read_data() - read and parse the raw data from the AHT10
> + * @client: the i2c client associated with the AHT10
> + * @aht10_data: the struct aht10_data to use for the lock
> + * @aht10_measurement: the struct aht10_measurement to store the raw
> + *                     data and parsed values in
> + * Return: 0 if succesfull, 1 if not
> + */
> +static int aht10_read_data(struct i2c_client *client,
> +			struct aht10_data *aht10_data,
> +			struct aht10_measurement *measurement)
> +{
> +	u32 temp, hum;
> +	int hum_i, temp_i;
> +	int res;
> +	struct mutex *mutex = &aht10_data->lock;
> +	int was_locked = mutex_is_locked(mutex);
> +	u8 *raw_data = measurement->data;
> +
> +	mutex_lock(mutex);
> +	if (!was_locked) {

This is both unnecessary and unsafe. Check and update
previous_poll_time from within the lock instead.

> +		i2c_master_send(client, cmd_meas, 3);
> +		usleep_range(AHT10_MEAS_USEC_DELAY,
> +			AHT10_MEAS_USEC_DELAY + AHT10_USEC_DELAY_OFFSET);
> +
> +		res = i2c_master_recv(client, raw_data, 6);
> +
> +		if (res != 6) {
> +			mutex_unlock(mutex);
> +			pr_warn("Did not receive 6 bytes from AHT10!\n");

Please no such noise.

> +			return 1;
> +		}
> +
> +		temp = ((u32) (raw_data[3] & 0x0Fu) << 16u) | ((u32) raw_data[4] << 8u) | raw_data[5];
> +		hum = ((u32) raw_data[1] << 12u) | ((u32) raw_data[2] << 4u) | (raw_data[3] & 0xF0u >> 4u);
> +
> +		/*
> +		 * Avoid doing float arithmetic, while trying to preserve
> +		 * precision. There must be a better way to do this (or
> +		 * by using 64 bit values)
> +		 */

Pointless comment. Then implement it.

> +
> +		temp = temp * 200;
> +		temp = temp >> 10u;
> +		temp = temp * 100;
> +		temp = temp >> 10u;
> +
> +		hum = hum * 100;
> +		hum = hum >> 10u;
> +		hum = hum * 100;
> +		hum = hum >> 10u;
> +
> +		temp_i = temp - 5000;
> +		hum_i = hum;
> +
> +		measurement->temperature = temp_i;
> +		measurement->humidity = hum_i;
> +	}
> +	mutex_unlock(mutex);
> +	return 0;
> +}
> +
> +/**
> + * aht10_check_and_set_polltime() - check if the minimum poll interval has
> + *                                  expired, and if so set the previous
> + *                                  poll time
> + * @data: what time to compare (and possibly set)
> + * Return: 1 if the minimum poll interval has expired, 0 if not
> + */
> +static int aht10_check_and_set_polltime(struct aht10_data *data)
> +{
> +	ktime_t current_time = ktime_get_boottime();
> +	ktime_t difference = ktime_sub(current_time,
> +				data->previous_poll_time);
> +	if (ktime_to_us(difference) >=
> +	ktime_to_us(data->poll_interval)) {

Unnecessary line split, and really bad alignment. Also way too complex.
Use ktime_after() or similar instead.

> +		data->previous_poll_time = current_time;
> +		return 1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * temperature_show() - show the temperature in Celcius
> + */
> +static ssize_t temperature_show(struct device *dev,
> +				struct device_attribute *attr,
> +				char *buf)
> +{
> +	int bytes_written;
> +	struct aht10_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	struct aht10_measurement *measurement = &data->current_measurement;
> +
> +	if (aht10_check_and_set_polltime(data))
> +		aht10_read_data(client, data, measurement);
> +
> +	bytes_written = sprintf(buf, "%d", measurement->temperature * 10);
> +	return bytes_written;
> +}
> +
> +
> +/**
> + * humidity_show() - show the relative humidity in %H
> + */
> +static ssize_t humidity_show(struct device *dev,
> +			struct device_attribute *attr,
> +			char *buf)
> +{
> +	int bytes_written;
> +	struct aht10_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	struct aht10_measurement *measurement = &data->current_measurement;
> +
> +	if (aht10_check_and_set_polltime(data))
> +		aht10_read_data(client, data, measurement);
> +
> +	bytes_written = sprintf(buf, "%d", measurement->humidity * 10);
> +	return bytes_written;
> +}
> +
> +/**
> + * reset_store() - reset the ATH10
> + */
> +static ssize_t reset_store(struct device *dev,
> +			struct device_attribute *attr,
> +			const char *buf, size_t count)
> +{
> +	// TODO
> +	return count;

Not acceptable.

> +}
> +
> +/**
> + * min_poll_interval_show() - show the minimum poll interval
> + *                            in milliseconds
> + */
> +static ssize_t min_poll_interval_show(struct device *dev,
> +				struct device_attribute *attr,
> +				char *buf)
> +{
> +	struct aht10_data *data = dev_get_drvdata(dev);
> +	int bytes_written;
> +	u64 usec = ktime_to_us(data->poll_interval);
> +
> +	do_div(usec, USEC_PER_MSEC);
> +	bytes_written = sprintf(buf, "%lld", usec);
> +	return bytes_written;
> +}
> +
> +/**
> + * min_poll_interval_store() - store the given minimum poll interval.
> + * Input in milliseconds
> + */
> +static ssize_t min_poll_interval_store(struct device *dev,
> +			struct device_attribute *attr,
> +			const char *buf, size_t count)
> +{
> +	struct aht10_data *data = dev_get_drvdata(dev);
> +	int i;
> +	u64 msecs;
> +	int res;
> +
> +	char null_terminated[AHT10_MAX_POLL_INTERVAL_LEN + 1];
> +
> +	if (count > AHT10_MAX_POLL_INTERVAL_LEN) {
> +		pr_warn("AHT10: Warning! Input too long. Max characters: %d\n",
> +		AHT10_MAX_POLL_INTERVAL_LEN);
> +		return count;
> +	}
> +
> +	for (i = 0; i < count && i < AHT10_MAX_POLL_INTERVAL_LEN; i++)
> +		null_terminated[i] = buf[i];
> +
> +	null_terminated[i] = 0;
> +
> +	res = kstrtoull(null_terminated, 10, &msecs);

What is the point of this ? kstrtoull() works directly on buf.

> +
> +	if (res) {
> +		pr_warn("AHT10: Warning! Invalid input.\n");

Please no such error messages. They can be used to clog the log.
Return standard error codes....

> +		return count;

... and don't ignore errors.

> +	}
> +
> +	data->poll_interval = ms_to_ktime(msecs);
> +	return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(reset, 0200, NULL, reset_store, 0);
> +static SENSOR_DEVICE_ATTR(temp1_input, 0444, temperature_show, NULL, 0);
> +static SENSOR_DEVICE_ATTR(humidity1_input, 0444, humidity_show, NULL, 0);
> +static SENSOR_DEVICE_ATTR(min_poll_interval, 0644, min_poll_interval_show,
> +						  min_poll_interval_store, 0);

Please use standard attributes.

> +
> +static struct attribute *aht10_attrs[] = {
> +	&sensor_dev_attr_reset.dev_attr.attr,
> +	&sensor_dev_attr_temp1_input.dev_attr.attr,
> +	&sensor_dev_attr_humidity1_input.dev_attr.attr,
> +	&sensor_dev_attr_min_poll_interval.dev_attr.attr,
> +	NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(aht10);
> +
> +static int aht10_probe(struct i2c_client *client,
> +		const struct i2c_device_id *aht10_id)
> +{
> +	struct device *device = &client->dev;
> +	struct device *hwmon_dev;
> +	struct i2c_adapter *adapter = client->adapter;
> +	struct aht10_data *data;
> +	const struct attribute_group **attribute_groups = aht10_groups;
> +	int res = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
> +		return 0;
> +
> +	if (client->addr != AHT10_ADDR)
> +		return 0;
> +
> +	data = devm_kzalloc(device, sizeof(*data), GFP_KERNEL);
> +
Unnecessary empty line. In general, please no empty line between assignments
and value checks.

> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->poll_interval = ns_to_ktime((u64) 10000 * NSEC_PER_MSEC);
> +	data->previous_poll_time = ns_to_ktime(0);
> +	data->client = client;
> +
> +	i2c_set_clientdata(client, data);
> +
> +	mutex_init(&data->lock);
> +
> +	res = aht10_init(client, data);
> +
> +	if (res)
> +		return 2;
> +
Please use standard error codes.

> +	hwmon_dev = devm_hwmon_device_register_with_groups(device,
> +							client->name,
> +							data,
> +							attribute_groups);

New drivers shall use devm_hwmon_device_register_with_info().

> +
> +	pr_info("AHT10 was detected and registered\n");
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static int aht10_remove(struct i2c_client *client)
> +{
> +	if (client->addr != AHT10_ADDR)
> +		return 0;
> +
> +	pr_info("AHT10 was removed\n");
> +	return 0;
> +}

Pointless remove function.

> +
> +static const struct i2c_device_id aht10_id[] = {
> +	{ "aht10", 0 },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(i2c, aht10_id);
> +
> +static const struct of_device_id aht10_of_match[] = {
> +	{ .compatible = "aht10", },

Needs to be aosong,aht10 or similar (ie vendor prefix needed),
and needs to be documented.

> +};
> +> +static struct i2c_driver aht10_driver = {
> +	.driver = {
> +		.name = "aht10",
> +		.of_match_table = aht10_of_match,

This implies (unnecessary) dependency on devicetree. Please
use of_match_ptr().

> +	},
> +	.probe      = aht10_probe,
> +	.remove     = aht10_remove,
> +	.id_table   = aht10_id,
> +};
> +
> +module_i2c_driver(aht10_driver);
> +
> +MODULE_AUTHOR("Johannes Draaijer <jcdra1@xxxxxxxxx>");
> +MODULE_DESCRIPTION("AHT10 Temperature and Humidity sensor driver");
> +MODULE_VERSION("1.0");
> +MODULE_LICENSE("GPL v2");
> 




[Index of Archives]     [LM Sensors]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux