Re: [PATCH v4 3/3] hwmon: add ST-Ericsson ABX500 hwmon driver

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

 



On Fri, Mar 08, 2013 at 04:13:31PM +0800, Hongbo Zhang wrote:
> Each of ST-Ericsson X500 chip set series consists of both ABX500 and DBX500
> chips. This is ABX500 hwmon driver, where the abx500.c is a common layer for
> all ABX500s, and the ab8500.c is specific for AB8500 chip. Under this designed
> structure, other chip specific files can be added simply using the same common
> layer abx500.c.
> 
> Signed-off-by: Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> ---
>  Documentation/hwmon/ab8500 |  22 ++
>  Documentation/hwmon/abx500 |  28 +++
>  drivers/hwmon/Kconfig      |  13 ++
>  drivers/hwmon/Makefile     |   1 +
>  drivers/hwmon/ab8500.c     | 208 +++++++++++++++++++
>  drivers/hwmon/abx500.c     | 494 +++++++++++++++++++++++++++++++++++++++++++++
>  drivers/hwmon/abx500.h     |  69 +++++++
>  7 files changed, 835 insertions(+)
>  create mode 100644 Documentation/hwmon/ab8500
>  create mode 100644 Documentation/hwmon/abx500
>  create mode 100644 drivers/hwmon/ab8500.c
>  create mode 100644 drivers/hwmon/abx500.c
>  create mode 100644 drivers/hwmon/abx500.h
> 
> diff --git a/Documentation/hwmon/ab8500 b/Documentation/hwmon/ab8500
> new file mode 100644
> index 0000000..cf169c8
> --- /dev/null
> +++ b/Documentation/hwmon/ab8500
> @@ -0,0 +1,22 @@
> +Kernel driver ab8500
> +====================
> +
> +Supported chips:
> +  * ST-Ericsson AB8500
> +    Prefix: 'ab8500'
> +    Addresses scanned: -
> +    Datasheet: http://www.stericsson.com/developers/documentation.jsp
> +
> +Authors:
> +        Martin Persson <martin.persson@xxxxxxxxxxxxxx>
> +        Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> +
> +Description
> +-----------
> +
> +See also Documentation/hwmon/abx500. This is the ST-Ericsson AB8500 specific
> +driver.
> +
> +Currently only the AB8500 internal sensor and one external sensor for battery
> +temperature are monitored. Other GPADC channels can also be monitored if needed
> +in future.
> diff --git a/Documentation/hwmon/abx500 b/Documentation/hwmon/abx500
> new file mode 100644
> index 0000000..319a058
> --- /dev/null
> +++ b/Documentation/hwmon/abx500
> @@ -0,0 +1,28 @@
> +Kernel driver abx500
> +====================
> +
> +Supported chips:
> +  * ST-Ericsson ABx500 series
> +    Prefix: 'abx500'
> +    Addresses scanned: -
> +    Datasheet: http://www.stericsson.com/developers/documentation.jsp
> +
> +Authors:
> +        Martin Persson <martin.persson@xxxxxxxxxxxxxx>
> +        Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> +
> +Description
> +-----------
> +
> +Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically,
> +this is kernel hwmon driver for ABx500.
> +
> +There are some GPADCs inside ABx500 which are designed for connecting to
> +thermal sensors, and there is also a thermal sensor inside ABx500 too, which
> +raises interrupt when critical temperature reached.
> +
> +This abx500 is a common layer which can monitor all of the sensors, every
> +specific abx500 chip has its special configurations in its own file, e.g. some
> +sensors can be configured invisible if they are not available on that chip, and
> +the corresponding gpadc_addr should be set to 0, thus this sensor won't be
> +polled.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 32f238f..0a6fd21 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -39,6 +39,19 @@ config HWMON_DEBUG_CHIP
>  
>  comment "Native drivers"
>  
> +config SENSORS_AB8500
> +	tristate "AB8500 thermal monitoring"
> +	depends on AB8500_GPADC
> +	default n
> +	help
> +	  If you say yes here you get support for the thermal sensor part
> +	  of the AB8500 chip. The driver includes thermal management for
> +	  AB8500 die and two GPADC channels. The GPADC channel are preferably
> +	  used to access sensors outside the AB8500 chip.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called abx500-temp.
> +
>  config SENSORS_ABITUGURU
>  	tristate "Abit uGuru (rev 1 & 2)"
>  	depends on X86 && DMI
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 5da2874..06dfe85 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -19,6 +19,7 @@ obj-$(CONFIG_SENSORS_W83795)	+= w83795.o
>  obj-$(CONFIG_SENSORS_W83781D)	+= w83781d.o
>  obj-$(CONFIG_SENSORS_W83791D)	+= w83791d.o
>  
> +obj-$(CONFIG_SENSORS_AB8500)	+= abx500.o ab8500.o
>  obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
>  obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
>  obj-$(CONFIG_SENSORS_AD7314)	+= ad7314.o
> diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c
> new file mode 100644
> index 0000000..da165fa
> --- /dev/null
> +++ b/drivers/hwmon/ab8500.c
> @@ -0,0 +1,208 @@
> +/*
> + * Copyright (C) ST-Ericsson 2010 - 2013
> + * Author: Martin Persson <martin.persson@xxxxxxxxxxxxxx>
> + *         Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> + * License Terms: GNU General Public License v2
> + *
> + * When the AB8500 thermal warning temperature is reached (threshold cannot
> + * be changed by SW), an interrupt is set, and if no further action is taken
> + * within a certain time frame, pm_power off will be called.
> + *
> + * When AB8500 thermal shutdown temperature is reached a hardware shutdown of
> + * the AB8500 will occur.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mfd/abx500.h>
> +#include <linux/mfd/abx500/ab8500-bm.h>
> +#include <linux/mfd/abx500/ab8500-gpadc.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/power/ab8500.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +#include "abx500.h"
> +
> +#define DEFAULT_POWER_OFF_DELAY	(HZ * 10)
> +#define THERMAL_VCC		1800
> +#define PULL_UP_RESISTOR	47000
> +/* Number of monitored sensors should not greater than NUM_SENSORS */
> +#define NUM_MONITORED_SENSORS	4
> +
> +struct ab8500_gpadc_cfg {
> +	const struct abx500_res_to_temp *temp_tbl;
> +	int tbl_sz;
> +	int vcc;
> +	int r_up;
> +};
> +
> +struct ab8500_temp {
> +	struct ab8500_gpadc *gpadc;
> +	struct ab8500_btemp *btemp;
> +	struct delayed_work power_off_work;
> +	struct ab8500_gpadc_cfg cfg;
> +	struct abx500_temp *abx500_data;
> +};
> +
> +/*
> + * The hardware connection is like this:
> + * VCC----[ R_up ]-----[ NTC ]----GND
> + * where R_up is pull-up resistance, and GPADC measures voltage on NTC.
> + * and res_to_temp table is strictly sorted by falling resistance values.
> + */
> +static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg,
> +		int v_ntc, int *temp)
> +{
> +	int r_ntc, i = 0, tbl_sz = cfg->tbl_sz;
> +	const struct abx500_res_to_temp *tbl = cfg->temp_tbl;
> +
> +	if (cfg->vcc < 0 || v_ntc >= cfg->vcc)
> +		return -EINVAL;
> +
> +	r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc);
> +	if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist)
> +		return -EINVAL;
> +
> +	while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist)
> +		&& i < tbl_sz - 2)
> +		i++;
> +
> +	/* return milli-Celsius */
> +	*temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 *
> +		(r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
> +
> +	return 0;
> +}
> +
> +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp)
> +{
> +	int voltage, ret;
> +	struct ab8500_temp *ab8500_data = data->plat_data;
> +
> +	if (sensor == BAT_CTRL)
> +		*temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp);
> +
> +	else if (sensor == BTEMP_BALL)
> +		*temp = ab8500_btemp_get_temp(ab8500_data->btemp);
> +
> +	else {
> +		voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor);
> +		if (voltage < 0)
> +			return voltage;
> +
> +		ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
> +		if (ret < 0)
> +			return ret;
> +	}

Please follow coding style: if you use { } anywhere in an if statement, use it
everywhere.

> +
> +	return 0;
> +}
> +
> +static void ab8500_thermal_power_off(struct work_struct *work)
> +{
> +	struct ab8500_temp *ab8500_data = container_of(work,
> +				struct ab8500_temp, power_off_work.work);
> +	struct abx500_temp *abx500_data = ab8500_data->abx500_data;
> +
> +	dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n");
> +
> +	pm_power_off();
> +}
> +
> +static ssize_t ab8500_show_name(struct device *dev,
> +		struct device_attribute *devattr, char *buf)
> +{
> +	return sprintf(buf, "ab8500\n");
> +}
> +
> +static ssize_t ab8500_show_label(struct device *dev,
> +		struct device_attribute *devattr, char *buf)
> +{
> +	char *label;
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	int index = attr->index;
> +
> +	switch (index) {
> +	case 1:
> +		label = "ext_adc1";
> +		break;
> +	case 2:
> +		label = "ext_adc2";
> +		break;
> +	case 3:
> +		label = "bat_temp";
> +		break;
> +	case 4:
> +		label = "bat_ctrl";
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return sprintf(buf, "%s\n", label);
> +}
> +
> +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data)
> +{
> +	struct ab8500_temp *ab8500_data = data->plat_data;
> +
> +	dev_warn(&data->pdev->dev, "Power off in %d s\n",
> +		DEFAULT_POWER_OFF_DELAY / HZ);
> +
> +	schedule_delayed_work(&ab8500_data->power_off_work,
> +		DEFAULT_POWER_OFF_DELAY);
> +	return 0;
> +}
> +
> +int abx500_hwmon_init(struct abx500_temp *data)
> +{
> +	struct ab8500_temp *ab8500_data;
> +
> +	ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data),
> +		GFP_KERNEL);
> +	if (!ab8500_data)
> +		return -ENOMEM;
> +
> +	ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
> +	if (IS_ERR(ab8500_data->gpadc))
> +		return PTR_ERR(ab8500_data->gpadc);
> +
> +	ab8500_data->btemp = ab8500_btemp_get();
> +	if (IS_ERR(ab8500_data->btemp))
> +		return PTR_ERR(ab8500_data->btemp);
> +
> +	INIT_DELAYED_WORK(&ab8500_data->power_off_work,
> +		ab8500_thermal_power_off);
> +
> +	ab8500_data->cfg.vcc = THERMAL_VCC;
> +	ab8500_data->cfg.r_up = PULL_UP_RESISTOR;
> +	ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a;
> +	ab8500_data->cfg.tbl_sz = ARRAY_SIZE(ab8500_temp_tbl_a);
> +
> +	data->plat_data = ab8500_data;
> +
> +	/*
> +	 * ADC_AUX1 and ADC_AUX2, connected to external NTC
> +	 * BTEMP_BALL and BAT_CTRL, fixed usage
> +	 */
> +	data->gpadc_addr[0] = ADC_AUX1;
> +	data->gpadc_addr[1] = ADC_AUX2;
> +	data->gpadc_addr[2] = BTEMP_BALL;
> +	data->gpadc_addr[3] = BAT_CTRL;
> +	data->monitored_sensors = NUM_MONITORED_SENSORS;
> +
> +	data->ops.read_sensor = ab8500_read_sensor;
> +	data->ops.irq_handler = ab8500_temp_irq_handler;
> +	data->ops.show_name = ab8500_show_name;
> +	data->ops.show_label = ab8500_show_label;
> +	data->ops.is_visible = NULL;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(abx500_hwmon_init);
> +
> +MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("AB8500 temperature driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c
> new file mode 100644
> index 0000000..1da5910
> --- /dev/null
> +++ b/drivers/hwmon/abx500.c
> @@ -0,0 +1,494 @@
> +/*
> + * Copyright (C) ST-Ericsson 2010 - 2013
> + * Author: Martin Persson <martin.persson@xxxxxxxxxxxxxx>
> + *         Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> + * License Terms: GNU General Public License v2
> + *
> + * ABX500 does not provide auto ADC, so to monitor the required temperatures,
> + * a periodic work is used. It is more important to not wake up the CPU than
> + * to perform this job, hence the use of a deferred delay.
> + *
> + * A deferred delay for thermal monitor is considered safe because:
> + * If the chip gets too hot during a sleep state it's most likely due to
> + * external factors, such as the surrounding temperature. I.e. no SW decisions
> + * will make any difference.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/interrupt.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +#include <linux/workqueue.h>
> +#include "abx500.h"
> +
> +#define DEFAULT_MONITOR_DELAY	HZ
> +#define DEFAULT_MAX_TEMP	130
> +
> +static inline void schedule_monitor(struct abx500_temp *data)
> +{
> +	data->work_active = true;
> +	schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY);
> +}
> +
> +static void threshold_updated(struct abx500_temp *data)
> +{
> +	int i;
> +	for (i = 0; i < data->monitored_sensors; i++)
> +		if (data->max[i] != 0 || data->min[i] != 0) {
> +			schedule_monitor(data);
> +			return;
> +		}
> +
> +	dev_dbg(&data->pdev->dev, "No active thresholds.\n");
> +	cancel_delayed_work_sync(&data->work);
> +	data->work_active = false;
> +}
> +
> +static void gpadc_monitor(struct work_struct *work)
> +{
> +	int temp, i, ret;
> +	char alarm_node[30];
> +	bool updated_min_alarm = false;
> +	bool updated_max_alarm = false;
> +	struct abx500_temp *data;
> +
> +	data = container_of(work, struct abx500_temp, work.work);
> +	mutex_lock(&data->lock);
> +
> +	for (i = 0; i < data->monitored_sensors; i++) {
> +		/* Thresholds are considered inactive if set to 0 */
> +		if (data->max[i] == 0 && data->min[i] == 0)
> +			continue;
> +
> +		if (data->max[i] < data->min[i])
> +			continue;
> +
> +		ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp);
> +		if (ret < 0) {
> +			dev_err(&data->pdev->dev, "GPADC read failed\n");
> +			continue;
> +		}
> +
> +		if (data->min[i] != 0) {
> +			if (temp < data->min[i]) {
> +				if (data->min_alarm[i] == false) {
> +					data->min_alarm[i] = true;
> +					updated_min_alarm = true;
> +				}
> +			} else {
> +				if (data->min_alarm[i] == true) {
> +					data->min_alarm[i] = false;
> +					updated_min_alarm = true;
> +				}
> +			}
> +		}
> +		if (data->max[i] != 0) {
> +			if (temp > data->max[i]) {
> +				if (data->max_alarm[i] == false) {
> +					data->max_alarm[i] = true;
> +					updated_max_alarm = true;
> +				}
> +			} else if (temp < data->max[i] - data->max_hyst[i]) {
> +				if (data->max_alarm[i] == true) {
> +					data->max_alarm[i] = false;
> +					updated_max_alarm = true;
> +				}
> +			}
> +		}
> +
> +		if (updated_min_alarm) {
> +			ret = sprintf(alarm_node, "temp%d_min_alarm", i);
> +			sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
> +		}
> +		if (updated_max_alarm) {
> +			ret = sprintf(alarm_node, "temp%d_max_alarm", i);

I am almost sure this is wrong. Loop index starts with 0, tempX index starts
with 1.

> +			sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
> +		}
> +
> +		updated_min_alarm = false;
> +		updated_max_alarm = false;

We had this before: Move this to the beginning of the loop, and you safe one set
of initializations.

> +	}
> +
> +	schedule_monitor(data);
> +	mutex_unlock(&data->lock);
> +}
> +
> +/* HWMON sysfs interfaces */
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> +			 char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	/* Show chip name */
> +	return data->ops.show_name(dev, devattr, buf);
> +}
> +
> +static ssize_t show_label(struct device *dev,
> +			  struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	/* Show each sensor label */
> +	return data->ops.show_label(dev, devattr, buf);
> +}
> +
> +static ssize_t show_input(struct device *dev,
> +			  struct device_attribute *devattr, char *buf)
> +{
> +	int ret, temp;
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	u8 gpadc_addr = data->gpadc_addr[attr->index];
> +
> +	ret = data->ops.read_sensor(data, gpadc_addr, &temp);
> +	if (ret < 0)
> +		dev_err(&data->pdev->dev, "GPADC read failed\n");

	... and temp is uninitialized. Needs
		return err;
	Not sure if the error message is really helpful; the caller will notice.

> +
> +	return sprintf(buf, "%d\n", temp);
> +}
> +
> +/* Set functions (RW nodes) */
> +static ssize_t set_min(struct device *dev, struct device_attribute *devattr,
> +		       const char *buf, size_t count)
> +{
> +	unsigned long val;
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	int res = kstrtol(buf, 10, &val);
> +	if (res < 0)
> +		return res;
> +
> +	clamp_val(val, 0, data->max[attr->index]);
> +

You should clamp against fixed limits, ie [0, DEFAULT_MAX_TEMP].
Otherwise updating limits gets tricky and has to be done in a fixed order,
which would be unexpected behavior. The code using the values should be able
to handle situations where min > max.

> +	mutex_lock(&data->lock);
> +	data->min[attr->index] = val;
> +	threshold_updated(data);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t set_max(struct device *dev, struct device_attribute *devattr,
> +		       const char *buf, size_t count)
> +{
> +	unsigned long val;
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	int res = kstrtol(buf, 10, &val);
> +	if (res < 0)
> +		return res;
> +
> +	clamp_val(val, data->min[attr->index], DEFAULT_MAX_TEMP);
> +
Same here.

> +	mutex_lock(&data->lock);
> +	data->max[attr->index] = val;
> +	threshold_updated(data);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t set_max_hyst(struct device *dev,
> +			    struct device_attribute *devattr,
> +			    const char *buf, size_t count)
> +{
> +	unsigned long val;
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	int res = kstrtoul(buf, 10, &val);
> +	if (res < 0)
> +		return res;
> +
> +	mutex_lock(&data->lock);
> +	data->max_hyst[attr->index] = val;

No clamp here ?

> +	threshold_updated(data);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +/* Show functions (RO nodes) */
> +static ssize_t show_min(struct device *dev,
> +			struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +
> +	return sprintf(buf, "%ld\n", data->min[attr->index]);
> +}
> +
> +static ssize_t show_max(struct device *dev,
> +			struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +
> +	return sprintf(buf, "%ld\n", data->max[attr->index]);
> +}
> +
> +static ssize_t show_max_hyst(struct device *dev,
> +			     struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +
> +	return sprintf(buf, "%ld\n", data->max_hyst[attr->index]);
> +}
> +
> +static ssize_t show_min_alarm(struct device *dev,
> +			      struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +
> +	return sprintf(buf, "%d\n", data->min_alarm[attr->index]);
> +}
> +
> +static ssize_t show_max_alarm(struct device *dev,
> +			      struct device_attribute *devattr, char *buf)
> +{
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +
> +	return sprintf(buf, "%d\n", data->max_alarm[attr->index]);
> +}
> +
> +static mode_t abx500_attrs_visible(struct kobject *kobj,
> +				   struct attribute *attr, int n)
> +{
> +	struct device *dev = container_of(kobj, struct device, kobj);
> +	struct abx500_temp *data = dev_get_drvdata(dev);
> +
> +	if (data->ops.is_visible)
> +		return data->ops.is_visible(attr, n);
> +	else

Unnecessary else

> +		return attr->mode;
> +}
> +
> +/* Chip name, required by hwmon */
> +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
> +
> +/* GPADC - SENSOR1 */
> +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0);
> +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0);
> +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
> +			  show_max_hyst, set_max_hyst, 0);
> +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0);
> +
> +/* GPADC - SENSOR2 */
> +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1);
> +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1);
> +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1);
> +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1);
> +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO,
> +			  show_max_hyst, set_max_hyst, 1);
> +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1);
> +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1);
> +
> +/* GPADC - SENSOR3 */
> +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2);
> +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2);
> +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2);
> +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2);
> +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO,
> +			  show_max_hyst, set_max_hyst, 2);
> +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2);
> +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2);
> +
> +/* GPADC - SENSOR4 */
> +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3);
> +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3);
> +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO,
> +			  show_max_hyst, set_max_hyst, 3);
> +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3);
> +
> +struct attribute *abx500_temp_attributes[] = {
> +	&sensor_dev_attr_name.dev_attr.attr,
> +
> +	&sensor_dev_attr_temp1_label.dev_attr.attr,
> +	&sensor_dev_attr_temp1_input.dev_attr.attr,
> +	&sensor_dev_attr_temp1_min.dev_attr.attr,
> +	&sensor_dev_attr_temp1_max.dev_attr.attr,
> +	&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
> +	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_temp2_label.dev_attr.attr,
> +	&sensor_dev_attr_temp2_input.dev_attr.attr,
> +	&sensor_dev_attr_temp2_min.dev_attr.attr,
> +	&sensor_dev_attr_temp2_max.dev_attr.attr,
> +	&sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
> +	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_temp3_label.dev_attr.attr,
> +	&sensor_dev_attr_temp3_input.dev_attr.attr,
> +	&sensor_dev_attr_temp3_min.dev_attr.attr,
> +	&sensor_dev_attr_temp3_max.dev_attr.attr,
> +	&sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
> +	&sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_temp4_label.dev_attr.attr,
> +	&sensor_dev_attr_temp4_input.dev_attr.attr,
> +	&sensor_dev_attr_temp4_min.dev_attr.attr,
> +	&sensor_dev_attr_temp4_max.dev_attr.attr,
> +	&sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
> +	&sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group abx500_temp_group = {
> +	.attrs = abx500_temp_attributes,
> +	.is_visible = abx500_attrs_visible,
> +};
> +
> +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data)
> +{
> +	struct platform_device *pdev = irq_data;
> +	struct abx500_temp *data = platform_get_drvdata(pdev);
> +
> +	data->ops.irq_handler(irq, data);
> +	return IRQ_HANDLED;
> +}
> +
> +static int setup_irqs(struct platform_device *pdev)
> +{
> +	int ret;
> +	int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM");
> +
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "Get irq by name failed\n");
> +		return irq;
> +	}
> +
> +	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
> +		abx500_temp_irq_handler, IRQF_NO_SUSPEND, "abx500-temp", pdev);
> +	if (ret < 0)
> +		dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
> +
You display error messages here and in the calling code ... once should be
enough.

> +	return ret;
> +}
> +
> +static int abx500_temp_probe(struct platform_device *pdev)
> +{
> +	struct abx500_temp *data;
> +	int err;
> +
> +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->pdev = pdev;
> +	mutex_init(&data->lock);
> +
> +	/* Chip specific initialization */
> +	err = abx500_hwmon_init(data);
> +	if (err	< 0 || !data->ops.read_sensor || !data->ops.show_name
> +	    || !data->ops.show_label) {
> +		dev_err(&pdev->dev, "ABx500 hwmon init failed");

	"ABx500 hwmon" is redundant here.

> +		return -EINVAL;

You'll want to return the original error code, which for example can be ENOMEM
where an error message is not needed. Also, I suspect abx500_hwmon_init might
at some point return -EPROBE_DEFER. If that ever happens, you definitely want
to fail silently and return the correct error code.

> +	}
> +
> +	INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor);
> +
> +	platform_set_drvdata(pdev, data);
> +
> +	err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err);
> +		return err;
> +	}
> +
> +	data->hwmon_dev = hwmon_device_register(&pdev->dev);
> +	if (IS_ERR(data->hwmon_dev)) {
> +		err = PTR_ERR(data->hwmon_dev);
> +		dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
> +		goto exit_sysfs_group;
> +	}
> +
> +	if (data->ops.irq_handler) {
> +		err = setup_irqs(pdev);
> +		if (err < 0) {
> +			dev_err(&pdev->dev, "irq setup failed (%d)\n", err);
> +			goto exit_hwmon_reg;
> +		}
> +	}
> +	return 0;
> +
> +exit_hwmon_reg:
> +	hwmon_device_unregister(data->hwmon_dev);
> +exit_sysfs_group:
> +	sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
> +	return err;
> +}
> +
> +static int abx500_temp_remove(struct platform_device *pdev)
> +{
> +	struct abx500_temp *data = platform_get_drvdata(pdev);
> +
> +	cancel_delayed_work_sync(&data->work);
> +	hwmon_device_unregister(data->hwmon_dev);
> +	sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
> +
> +	return 0;
> +}
> +
> +static int abx500_temp_suspend(struct platform_device *pdev,
> +			       pm_message_t state)
> +{
> +	struct abx500_temp *data = platform_get_drvdata(pdev);
> +
> +	if (data->work_active)
> +		cancel_delayed_work_sync(&data->work);
> +
> +	return 0;
> +}
> +
> +static int abx500_temp_resume(struct platform_device *pdev)
> +{
> +	struct abx500_temp *data = platform_get_drvdata(pdev);
> +
> +	if (data->work_active)
> +		schedule_monitor(data);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id abx500_temp_match[] = {
> +	{ .compatible = "stericsson,abx500-temp" },
> +	{},
> +};
> +#endif
> +
> +static struct platform_driver abx500_temp_driver = {
> +	.driver = {
> +		.owner = THIS_MODULE,
> +		.name = "abx500-temp",
> +		.of_match_table = of_match_ptr(abx500_temp_match),
> +	},
> +	.suspend = abx500_temp_suspend,
> +	.resume = abx500_temp_resume,
> +	.probe = abx500_temp_probe,
> +	.remove = abx500_temp_remove,
> +};
> +
> +module_platform_driver(abx500_temp_driver);
> +
> +MODULE_AUTHOR("Martin Persson <martin.persson@xxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("ABX500 temperature driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h
> new file mode 100644
> index 0000000..9b295e6
> --- /dev/null
> +++ b/drivers/hwmon/abx500.h
> @@ -0,0 +1,69 @@
> +/*
> + * Copyright (C) ST-Ericsson 2010 - 2013
> + * License terms: GNU General Public License v2
> + * Author: Martin Persson <martin.persson@xxxxxxxxxxxxxx>
> + *         Hongbo Zhang <hongbo.zhang@xxxxxxxxxx>
> + */
> +
> +#ifndef _ABX500_H
> +#define _ABX500_H
> +
> +#define NUM_SENSORS 5
> +
> +struct abx500_temp;
> +
> +/*
> + * struct abx500_temp_ops - abx500 chip specific ops
> + * @read_sensor: reads gpadc output
> + * @irq_handler: irq handler
> + * @show_name: hwmon device name
> + * @show_label: hwmon attribute label
> + * @is_visible: is attribute visible
> + */
> +struct abx500_temp_ops {
> +	int (*read_sensor)(struct abx500_temp *, u8, int *);
> +	int (*irq_handler)(int, struct abx500_temp *);
> +	ssize_t (*show_name)(struct device *,
> +			struct device_attribute *, char *);
> +	ssize_t (*show_label) (struct device *,
> +			struct device_attribute *, char *);
> +	int (*is_visible)(struct attribute *, int);
> +};
> +
> +/*
> + * struct abx500_temp - representation of temp mon device
> + * @pdev: platform device
> + * @hwmon_dev: hwmon device
> + * @ops: abx500 chip specific ops
> + * @gpadc_addr: gpadc channel address
> + * @min: sensor temperature min value
> + * @max: sensor temperature max value
> + * @max_hyst: sensor temperature hysteresis value for max limit
> + * @min_alarm: sensor temperature min alarm
> + * @max_alarm: sensor temperature max alarm
> + * @work: delayed work scheduled to monitor temperature periodically
> + * @work_active: True if work is active
> + * @lock: mutex
> + * @monitored_sensors: number of monitored sensors
> + * @plat_data: private usage, usually points to platform specific data
> + */
> +struct abx500_temp {
> +	struct platform_device *pdev;
> +	struct device *hwmon_dev;
> +	struct abx500_temp_ops ops;
> +	u8 gpadc_addr[NUM_SENSORS];
> +	unsigned long min[NUM_SENSORS];
> +	unsigned long max[NUM_SENSORS];
> +	unsigned long max_hyst[NUM_SENSORS];
> +	bool min_alarm[NUM_SENSORS];
> +	bool max_alarm[NUM_SENSORS];
> +	struct delayed_work work;
> +	bool work_active;
> +	struct mutex lock;
> +	int monitored_sensors;
> +	void *plat_data;
> +};
> +
> +int abx500_hwmon_init(struct abx500_temp *data);
> +
> +#endif /* _ABX500_H */
> -- 
> 1.8.0
> 
> 

_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux