From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
To: linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
gnurou-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
jdelvare-IBi9RG/b67k@public.gmane.org,
linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org,
wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org,
lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
tomi.valkeinen-l0cyMroinI0@public.gmane.org,
wim-IQzOog9fTRqzQB+pC5nmwQ@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-hwmon-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-watchdog-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org
Cc: Richard Vidal-Dorsch
<richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org,
weilun.huang-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org,
andrew.chou-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org
Subject: [PATCH v4 3/6] Add Advantech iManager HWmon driver
Date: Wed, 02 Nov 2016 08:37:48 +0000 [thread overview]
Message-ID: <20161102083751.6335-4-richard.dorsch@gmail.com> (raw)
In-Reply-To: <20161102083751.6335-1-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/imanager-hwmon.c | 1226 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 1238 insertions(+)
create mode 100644 drivers/hwmon/imanager-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..7a9db12 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -625,6 +625,17 @@ config SENSORS_CORETEMP
sensor inside your CPU. Most of the family 6 CPUs
are supported. Check Documentation/hwmon/coretemp for details.
+config SENSORS_IMANAGER
+ tristate "Advantech iManager Hardware Monitoring"
+ depends on MFD_IMANAGER
+ select HWMON_VID
+ help
+ This enables support for Advantech iManager hardware monitoring
+ of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager-hwmon.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..3f489ae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER) += imanager-hwmon.o
obj-$(CONFIG_SENSORS_INA209) += ina209.o
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..af72c35
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1226 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Partially derived from nct6775
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN 2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+#define HWM_STATUS_UNDEFINED_ITEM 2UL
+#define HWM_STATUS_UNDEFINED_DID 3UL
+#define HWM_STATUS_UNDEFINED_HWPIN 4UL
+
+/* iManager EC FW pwm[1-*]_mode values are switched */
+enum imanager_pwm_mode { CTRL_PWM, CTRL_RPM };
+/* EC FW defines pwm_enable mode 'full speed' besides mode 'off' */
+enum imanager_pwm_enable { MODE_OFF, MODE_FULL, MODE_MANUAL, MODE_AUTO };
+
+/*
+ * iManager FAN device defs
+ */
+struct fan_dev_config {
+ u8 did,
+ hwpin,
+ tachoid,
+ status,
+ control,
+ temp_max,
+ temp_min,
+ temp_stop,
+ pwm_max,
+ pwm_min;
+ u16 rpm_max;
+ u16 rpm_min;
+ u8 debounce; /* debounce time, not used */
+ u8 temp; /* Current Thermal Zone Temperature */
+ u16 rpm_target; /* RPM Target Speed, not used */
+} __attribute__((__packed__));
+
+struct fan_alert_limit {
+ u16 min,
+ max;
+} __attribute__((__packed__));
+
+struct fan_status {
+ u32 sysctl : 1, /* System Control flag */
+ tacho : 1, /* FAN tacho source defined */
+ pulse : 1, /* FAN pulse type defined */
+ thermal : 1, /* Thermal zone init */
+ i2clink : 1, /* I2C protocol fail flag (thermal sensor) */
+ dnc : 1, /* don't care */
+ mode : 2; /* FAN Control mode */
+};
+
+/**
+ * FAN Control bit field
+ * enable: 0:Disabled, 1:Enabled
+ * type: 0:PWM, 1:RPM
+ * pulse: 0:Undefined 1:2 Pulses 2:4 Pulses
+ * tacho: 1:CPU FAN, 2:SYS FAN1, 3:SYS FAN2
+ * mode: 0:Off, 1:Full, 2:Manual, 3:Auto
+ */
+struct fan_ctrl {
+ u32 enable : 1, /* SmartFAN control on/off */
+ type : 1, /* FAN control type [0, 1] PWM/RPM */
+ pulse : 2, /* FAN pulse [0..2] */
+ tacho : 2, /* FAN Tacho Input [1..3] */
+ mode : 2; /* off/full/manual/auto */
+};
+
+/* Default Voltage Sensors */
+struct imanager_hwmon_adc {
+ bool valid; /* if set, below values are valid */
+ unsigned int value;
+ unsigned int min;
+ unsigned int max;
+ unsigned int average;
+ unsigned int lowest;
+ unsigned int highest;
+};
+
+struct imanager_hwmon_smartfan {
+ bool valid; /* if set, below values are valid */
+ unsigned int pwm;
+ unsigned int speed;
+ bool speed_min_alarm;
+ bool speed_max_alarm;
+ struct fan_dev_config cfg;
+};
+
+struct imanager_hwmon_data {
+ struct imanager_device_data *imgr;
+ const struct attribute_group *groups[3];
+ unsigned long samples;
+ unsigned long last_updated;
+ struct imanager_hwmon_adc adc[EC_MAX_ADC_NUM];
+ struct imanager_hwmon_smartfan fan[EC_MAX_FAN_NUM];
+};
+
+static int imanager_hwmon_read_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(EC_F_HWMON_MSG, 0, num, (u8 *)&fan->cfg)
+ };
+ int ret;
+
+ ret = imanager_read(ec, EC_CMD_FAN_CTL_RD, &msg);
+ if (ret)
+ return ret;
+
+ return fan->cfg.did ? 0 : -ENODEV;
+}
+
+static int
+imanager_hwmon_write_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, sizeof(fan->cfg), num, (u8 *)&fan->cfg)
+ };
+ int err;
+
+ err = imanager_write(ec, EC_CMD_FAN_CTL_WR, &msg);
+ if (err < 0) {
+ return err;
+ } else if (err) {
+ switch (err) {
+ case HWM_STATUS_UNDEFINED_ITEM:
+ case HWM_STATUS_UNDEFINED_DID:
+ case HWM_STATUS_UNDEFINED_HWPIN:
+ return -ENODEV;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * FAN max/min alert bits are stored as bit pairs in a 8-bit register.
+ * FAN0~2: 2:{max2, min2}, 1:{max1, min1}, 0:{max0, min0}
+ */
+#define IS_FAN_ALERT_MIN(fan_num, var) test_bit(BIT((fan_num) << 1), var)
+#define IS_FAN_ALERT_MAX(fan_num, var) test_bit(BIT(((fan_num) << 1) + 1), var)
+
+static int imanager_hwmon_read_fan_alert(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ const ulong alert_flags = 0;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT,
+ (u8 *)&alert_flags, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ fan->speed_min_alarm = IS_FAN_ALERT_MIN(num, &alert_flags);
+ fan->speed_max_alarm = IS_FAN_ALERT_MAX(num, &alert_flags);
+
+ return 0;
+}
+
+static int imanager_hwmon_write_fan_alert(struct imanager_ec_data *ec, int fnum,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct fan_alert_limit limits[EC_MAX_FAN_NUM];
+ struct fan_alert_limit *limit = &limits[fnum];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+ if (ret < 0)
+ return ret;
+
+ limit->min = fan->cfg.rpm_min;
+ limit->max = fan->cfg.rpm_max;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+}
+
+static int imanager_hwmon_read_adc(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_adc *adc)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+ int ret;
+
+ adc->valid = false;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ adc->value = ret * attr->ecdev->scale;
+ adc->valid = true;
+
+ return 0;
+}
+
+static int imanager_hwmon_read_fan_ctrl(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+
+ int ret;
+
+ fan->valid = false;
+
+ ret = imanager_hwmon_read_fan_config(ec, num, fan);
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, fan->cfg.tachoid);
+ if (ret < 0)
+ return ret;
+
+ fan->speed = ret;
+
+ ret = imanager_read8(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ fan->pwm = ret;
+
+ ret = imanager_hwmon_read_fan_alert(ec, num, fan);
+ if (ret)
+ return ret;
+
+ fan->valid = true;
+
+ return 0;
+}
+
+static inline uint in_from_reg(u16 val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(uint val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * 1000, SCALE_IN), 0, 65535);
+}
+
+static struct imanager_hwmon_data *
+imanager_hwmon_update_device(struct device *dev)
+{
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ int i;
+
+ mutex_lock(&imgr->lock);
+
+ if (time_after(jiffies, data->last_updated + HZ + HZ / 2)) {
+ /* Measured voltages */
+ for (i = 0; i < hwmon->adc.num; i++)
+ imanager_hwmon_read_adc(ec, i, &data->adc[i]);
+
+ /* Measured fan speeds */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->last_updated = jiffies;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return data;
+}
+
+static ssize_t
+show_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].value));
+}
+
+static ssize_t
+show_in_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].min));
+}
+
+static ssize_t
+show_in_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].max));
+}
+
+static ssize_t
+store_in_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].min = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+store_in_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].max = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+show_in_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+ int val = 0;
+
+ if (adc->valid)
+ val = (adc->value < adc->min) || (adc->value > adc->max);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+show_in_average(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (adc->average) {
+ adc->average = DIV_ROUND_CLOSEST(adc->average * data->samples +
+ adc->value, ++data->samples);
+ } else {
+ adc->average = adc->value;
+ data->samples = 1;
+ }
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->average));
+}
+
+static ssize_t
+show_in_lowest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->lowest || (adc->value < adc->lowest))
+ adc->lowest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->lowest));
+}
+
+static ssize_t
+show_in_highest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->highest || (adc->value > adc->highest))
+ adc->highest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->highest));
+}
+
+static ssize_t
+store_in_reset_history(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long reset;
+ int err;
+
+ err = kstrtoul(buf, 10, &reset);
+ if (err < 0)
+ return err;
+
+ if (reset) {
+ data->adc[nr].lowest = 0;
+ data->adc[nr].highest = 0;
+ data->adc[nr].average = 0;
+ data->samples = 0;
+ }
+
+ return count;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp * 1000);
+}
+
+static ssize_t
+show_fan_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+
+ return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int check_fan_alarm(const struct imanager_hwmon_smartfan *fan)
+{
+ int rpm_min = cpu_to_be16(fan->cfg.rpm_min);
+ int rpm_max = cpu_to_be16(fan->cfg.rpm_max);
+
+ return !fan->valid || fan->speed_min_alarm || fan->speed_max_alarm ||
+ (!fan->speed && fan->pwm) || (fan->speed < rpm_min) ||
+ (fan->speed > rpm_max);
+}
+
+static ssize_t
+show_fan_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ bool is_alarm = (ctrl->mode = MODE_AUTO) ? 0 : check_fan_alarm(fan);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_min = cpu_to_be16(data->fan[nr].cfg.rpm_min);
+
+ return sprintf(buf, "%u\n", rpm_min);
+}
+
+static ssize_t
+show_fan_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_max = cpu_to_be16(data->fan[nr].cfg.rpm_max);
+
+ return sprintf(buf, "%u\n", rpm_max);
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_min = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_fan_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_max = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ u32 val = DIV_ROUND_CLOSEST(data->fan[nr].pwm * 255, 100);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+store_pwm(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long pwm = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &pwm);
+ if (err < 0)
+ return err;
+
+ pwm = DIV_ROUND_CLOSEST(pwm * 100, 255);
+
+ mutex_lock(&imgr->lock);
+
+ if (ctrl->mode = MODE_MANUAL) {
+ ctrl->type = CTRL_PWM;
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ } else if ((ctrl->mode = MODE_AUTO) && (ctrl->type = CTRL_PWM)) {
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ fan->cfg.rpm_min = 0;
+ fan->cfg.rpm_max = 0;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ imanager_hwmon_write_fan_alert(&imgr->ec, nr, fan);
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_min);
+}
+
+static ssize_t
+show_pwm_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_max);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = ctrl->mode - 1;
+
+ if (ctrl->mode = MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+enum pwm_enable { OFF, MANUAL, THERMAL_CRUISE };
+
+static ssize_t
+store_pwm_enable(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long mode = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &mode);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&imgr->lock);
+
+ switch (mode) {
+ case OFF:
+ ctrl->mode = MODE_FULL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 100);
+ break;
+ case MANUAL:
+ ctrl->mode = MODE_MANUAL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ case THERMAL_CRUISE:
+ ctrl->mode = MODE_AUTO;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = (ctrl->type = CTRL_PWM) ? 1 : 0;
+
+ if (ctrl->mode = MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+static ssize_t
+store_pwm_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ ctrl->type = val ? CTRL_RPM : CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%d\n", data->fan[nr].cfg.temp_min * 1000);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp_max * 1000);
+}
+
+static inline bool is_outside(int min, int max, int value)
+{
+ return (value < min) || (value > max);
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct fan_dev_config *cfg = &data->fan[nr].cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg->control;
+ bool is_alarm = (ctrl->mode = MODE_AUTO) ? 0 :
+ is_outside(cfg->temp_min, cfg->temp_max, cfg->temp);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* temperature in 1/10 degC */
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ /*
+ * The iManager provides three different temperature limit values
+ * (stop, min, and max) where stop indicates a minimum temp value
+ * (threshold) from which the FAN will turn off. We are setting
+ * temp_stop to the same value as temp_min since it cannot be mapped
+ * to anything else.
+ */
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_stop = val;
+ fan->cfg.temp_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_temp_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwmon->adc.label[nr]);
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.temp_label[nr]);
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.label[nr]);
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, 0444, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, 0644, show_in_min, store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, 0644, show_in_max, store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, 0444, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, 0644, show_in_min, store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, 0644, show_in_max, store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, 0444, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, 0644, show_in_min, store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, 0644, show_in_max, store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, 0444, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_temp_min, store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, 0644, show_temp_min, store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, 0644, show_temp_min, store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, 0444, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, 0644, show_fan_min, store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, 0644, show_fan_max, store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, 0444, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, 0644, show_fan_min, store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, 0644, show_fan_max, store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, 0444, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, 0644, show_fan_min, store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, 0644, show_fan_max, store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644, show_pwm_min, store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644, show_pwm_max, store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, 0644, show_pwm_mode, store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, 0644, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, 0644, show_pwm_min, store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, 0644, show_pwm_max, store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, 0644, show_pwm_mode, store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, 0644, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, 0644, show_pwm_min, store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, 0644, show_pwm_max, store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, 0644, show_pwm_mode, store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, 0444, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, 0644, show_in_min, store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, 0644, show_in_max, store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, 0444, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, 0444, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, 0444, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, 0444, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, 0200, NULL,
+ store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, 0444, show_in, NULL, 3);
+
+static struct attribute *imanager_in_attributes[] = {
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in0_min.dev_attr.attr,
+ &sensor_dev_attr_in0_max.dev_attr.attr,
+ &sensor_dev_attr_in0_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_min.dev_attr.attr,
+ &sensor_dev_attr_in1_max.dev_attr.attr,
+ &sensor_dev_attr_in1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_min.dev_attr.attr,
+ &sensor_dev_attr_in2_max.dev_attr.attr,
+ &sensor_dev_attr_in2_alarm.dev_attr.attr,
+
+ NULL
+};
+
+#define to_dev(obj) container_of(obj, struct device, kobj)
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ if (hwmon->adc.num <= EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_in = {
+ .attrs = imanager_in_attributes,
+ .is_visible = imanager_in_is_visible,
+};
+
+static struct attribute *imanager_other_attributes[] = {
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_min.dev_attr.attr,
+ &sensor_dev_attr_curr1_max.dev_attr.attr,
+ &sensor_dev_attr_curr1_alarm.dev_attr.attr,
+ &sensor_dev_attr_curr1_average.dev_attr.attr,
+ &sensor_dev_attr_curr1_lowest.dev_attr.attr,
+ &sensor_dev_attr_curr1_highest.dev_attr.attr,
+ &sensor_dev_attr_curr1_reset_history.dev_attr.attr,
+
+ &sensor_dev_attr_cpu0_vid.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t imanager_other_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ /*
+ * There are either 3 or 5 VINs available
+ * vin3 is current monitoring
+ * vin4 is CPU VID
+ */
+ if (hwmon->adc.num = EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_other = {
+ .attrs = imanager_other_attributes,
+ .is_visible = imanager_other_is_visible,
+};
+
+static struct attribute *imanager_fan_attributes[] = {
+ &sensor_dev_attr_fan1_label.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan1_min.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan2_label.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_min.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan3_label.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_min.dev_attr.attr,
+ &sensor_dev_attr_fan3_max.dev_attr.attr,
+ &sensor_dev_attr_fan3_alarm.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_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_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_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm1_min.dev_attr.attr,
+ &sensor_dev_attr_pwm1_max.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm1_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm2_min.dev_attr.attr,
+ &sensor_dev_attr_pwm2_max.dev_attr.attr,
+ &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm2_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm3_min.dev_attr.attr,
+ &sensor_dev_attr_pwm3_max.dev_attr.attr,
+ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm3_mode.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t
+imanager_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_fan_device *fan = &data->imgr->ec.hwmon.fan;
+
+ if ((index >= 0) && (index <= 14)) { /* fan */
+ if (!fan->attr[index / 5])
+ return 0;
+ } else if ((index >= 15) && (index <= 29)) { /* temp */
+ if (!fan->attr[(index - 15) / 5])
+ return 0;
+ } else if ((index >= 30) && (index <= 34)) { /* pwm */
+ if (!fan->attr[(index - 30) / 5])
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+ .attrs = imanager_fan_attributes,
+ .is_visible = imanager_fan_is_visible,
+};
+
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ struct imanager_hwmon_data *data;
+ struct device *hwmon_dev;
+ int i, num_attr_groups = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ platform_set_drvdata(pdev, data);
+
+ /* read fan control settings */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->groups[num_attr_groups++] = &imanager_group_in;
+
+ if (hwmon->adc.num > 3)
+ data->groups[num_attr_groups++] = &imanager_group_other;
+
+ if (hwmon->fan.num)
+ data->groups[num_attr_groups++] = &imanager_group_fan;
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+ "imanager_hwmon",
+ data, data->groups);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver imanager_hwmon_driver = {
+ .driver = {
+ .name = "imanager-hwmon",
+ },
+ .probe = imanager_hwmon_probe,
+};
+
+module_platform_driver(imanager_hwmon_driver);
+
+MODULE_DESCRIPTION("Advantech iManager HWmon Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-hwmon");
--
2.10.1
WARNING: multiple messages have this Message-ID (diff)
From: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
To: linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
gnurou-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
jdelvare-IBi9RG/b67k@public.gmane.org,
linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org,
wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org,
lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
tomi.valkeinen-l0cyMroinI0@public.gmane.org,
wim-IQzOog9fTRqzQB+pC5nmwQ@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-hwmon-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-watchdog-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org
Cc: Richard Vidal-Dorsch
<richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org,
weilun.huang-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org,
andrew.chou-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org
Subject: [PATCH v4 3/6] Add Advantech iManager HWmon driver
Date: Wed, 2 Nov 2016 01:37:48 -0700 [thread overview]
Message-ID: <20161102083751.6335-4-richard.dorsch@gmail.com> (raw)
In-Reply-To: <20161102083751.6335-1-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/imanager-hwmon.c | 1226 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 1238 insertions(+)
create mode 100644 drivers/hwmon/imanager-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..7a9db12 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -625,6 +625,17 @@ config SENSORS_CORETEMP
sensor inside your CPU. Most of the family 6 CPUs
are supported. Check Documentation/hwmon/coretemp for details.
+config SENSORS_IMANAGER
+ tristate "Advantech iManager Hardware Monitoring"
+ depends on MFD_IMANAGER
+ select HWMON_VID
+ help
+ This enables support for Advantech iManager hardware monitoring
+ of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager-hwmon.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..3f489ae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER) += imanager-hwmon.o
obj-$(CONFIG_SENSORS_INA209) += ina209.o
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..af72c35
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1226 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Partially derived from nct6775
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN 2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+#define HWM_STATUS_UNDEFINED_ITEM 2UL
+#define HWM_STATUS_UNDEFINED_DID 3UL
+#define HWM_STATUS_UNDEFINED_HWPIN 4UL
+
+/* iManager EC FW pwm[1-*]_mode values are switched */
+enum imanager_pwm_mode { CTRL_PWM, CTRL_RPM };
+/* EC FW defines pwm_enable mode 'full speed' besides mode 'off' */
+enum imanager_pwm_enable { MODE_OFF, MODE_FULL, MODE_MANUAL, MODE_AUTO };
+
+/*
+ * iManager FAN device defs
+ */
+struct fan_dev_config {
+ u8 did,
+ hwpin,
+ tachoid,
+ status,
+ control,
+ temp_max,
+ temp_min,
+ temp_stop,
+ pwm_max,
+ pwm_min;
+ u16 rpm_max;
+ u16 rpm_min;
+ u8 debounce; /* debounce time, not used */
+ u8 temp; /* Current Thermal Zone Temperature */
+ u16 rpm_target; /* RPM Target Speed, not used */
+} __attribute__((__packed__));
+
+struct fan_alert_limit {
+ u16 min,
+ max;
+} __attribute__((__packed__));
+
+struct fan_status {
+ u32 sysctl : 1, /* System Control flag */
+ tacho : 1, /* FAN tacho source defined */
+ pulse : 1, /* FAN pulse type defined */
+ thermal : 1, /* Thermal zone init */
+ i2clink : 1, /* I2C protocol fail flag (thermal sensor) */
+ dnc : 1, /* don't care */
+ mode : 2; /* FAN Control mode */
+};
+
+/**
+ * FAN Control bit field
+ * enable: 0:Disabled, 1:Enabled
+ * type: 0:PWM, 1:RPM
+ * pulse: 0:Undefined 1:2 Pulses 2:4 Pulses
+ * tacho: 1:CPU FAN, 2:SYS FAN1, 3:SYS FAN2
+ * mode: 0:Off, 1:Full, 2:Manual, 3:Auto
+ */
+struct fan_ctrl {
+ u32 enable : 1, /* SmartFAN control on/off */
+ type : 1, /* FAN control type [0, 1] PWM/RPM */
+ pulse : 2, /* FAN pulse [0..2] */
+ tacho : 2, /* FAN Tacho Input [1..3] */
+ mode : 2; /* off/full/manual/auto */
+};
+
+/* Default Voltage Sensors */
+struct imanager_hwmon_adc {
+ bool valid; /* if set, below values are valid */
+ unsigned int value;
+ unsigned int min;
+ unsigned int max;
+ unsigned int average;
+ unsigned int lowest;
+ unsigned int highest;
+};
+
+struct imanager_hwmon_smartfan {
+ bool valid; /* if set, below values are valid */
+ unsigned int pwm;
+ unsigned int speed;
+ bool speed_min_alarm;
+ bool speed_max_alarm;
+ struct fan_dev_config cfg;
+};
+
+struct imanager_hwmon_data {
+ struct imanager_device_data *imgr;
+ const struct attribute_group *groups[3];
+ unsigned long samples;
+ unsigned long last_updated;
+ struct imanager_hwmon_adc adc[EC_MAX_ADC_NUM];
+ struct imanager_hwmon_smartfan fan[EC_MAX_FAN_NUM];
+};
+
+static int imanager_hwmon_read_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(EC_F_HWMON_MSG, 0, num, (u8 *)&fan->cfg)
+ };
+ int ret;
+
+ ret = imanager_read(ec, EC_CMD_FAN_CTL_RD, &msg);
+ if (ret)
+ return ret;
+
+ return fan->cfg.did ? 0 : -ENODEV;
+}
+
+static int
+imanager_hwmon_write_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, sizeof(fan->cfg), num, (u8 *)&fan->cfg)
+ };
+ int err;
+
+ err = imanager_write(ec, EC_CMD_FAN_CTL_WR, &msg);
+ if (err < 0) {
+ return err;
+ } else if (err) {
+ switch (err) {
+ case HWM_STATUS_UNDEFINED_ITEM:
+ case HWM_STATUS_UNDEFINED_DID:
+ case HWM_STATUS_UNDEFINED_HWPIN:
+ return -ENODEV;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * FAN max/min alert bits are stored as bit pairs in a 8-bit register.
+ * FAN0~2: 2:{max2, min2}, 1:{max1, min1}, 0:{max0, min0}
+ */
+#define IS_FAN_ALERT_MIN(fan_num, var) test_bit(BIT((fan_num) << 1), var)
+#define IS_FAN_ALERT_MAX(fan_num, var) test_bit(BIT(((fan_num) << 1) + 1), var)
+
+static int imanager_hwmon_read_fan_alert(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ const ulong alert_flags = 0;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT,
+ (u8 *)&alert_flags, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ fan->speed_min_alarm = IS_FAN_ALERT_MIN(num, &alert_flags);
+ fan->speed_max_alarm = IS_FAN_ALERT_MAX(num, &alert_flags);
+
+ return 0;
+}
+
+static int imanager_hwmon_write_fan_alert(struct imanager_ec_data *ec, int fnum,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct fan_alert_limit limits[EC_MAX_FAN_NUM];
+ struct fan_alert_limit *limit = &limits[fnum];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+ if (ret < 0)
+ return ret;
+
+ limit->min = fan->cfg.rpm_min;
+ limit->max = fan->cfg.rpm_max;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+}
+
+static int imanager_hwmon_read_adc(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_adc *adc)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+ int ret;
+
+ adc->valid = false;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ adc->value = ret * attr->ecdev->scale;
+ adc->valid = true;
+
+ return 0;
+}
+
+static int imanager_hwmon_read_fan_ctrl(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+
+ int ret;
+
+ fan->valid = false;
+
+ ret = imanager_hwmon_read_fan_config(ec, num, fan);
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, fan->cfg.tachoid);
+ if (ret < 0)
+ return ret;
+
+ fan->speed = ret;
+
+ ret = imanager_read8(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ fan->pwm = ret;
+
+ ret = imanager_hwmon_read_fan_alert(ec, num, fan);
+ if (ret)
+ return ret;
+
+ fan->valid = true;
+
+ return 0;
+}
+
+static inline uint in_from_reg(u16 val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(uint val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * 1000, SCALE_IN), 0, 65535);
+}
+
+static struct imanager_hwmon_data *
+imanager_hwmon_update_device(struct device *dev)
+{
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ int i;
+
+ mutex_lock(&imgr->lock);
+
+ if (time_after(jiffies, data->last_updated + HZ + HZ / 2)) {
+ /* Measured voltages */
+ for (i = 0; i < hwmon->adc.num; i++)
+ imanager_hwmon_read_adc(ec, i, &data->adc[i]);
+
+ /* Measured fan speeds */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->last_updated = jiffies;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return data;
+}
+
+static ssize_t
+show_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].value));
+}
+
+static ssize_t
+show_in_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].min));
+}
+
+static ssize_t
+show_in_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].max));
+}
+
+static ssize_t
+store_in_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].min = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+store_in_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].max = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+show_in_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+ int val = 0;
+
+ if (adc->valid)
+ val = (adc->value < adc->min) || (adc->value > adc->max);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+show_in_average(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (adc->average) {
+ adc->average = DIV_ROUND_CLOSEST(adc->average * data->samples +
+ adc->value, ++data->samples);
+ } else {
+ adc->average = adc->value;
+ data->samples = 1;
+ }
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->average));
+}
+
+static ssize_t
+show_in_lowest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->lowest || (adc->value < adc->lowest))
+ adc->lowest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->lowest));
+}
+
+static ssize_t
+show_in_highest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->highest || (adc->value > adc->highest))
+ adc->highest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->highest));
+}
+
+static ssize_t
+store_in_reset_history(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long reset;
+ int err;
+
+ err = kstrtoul(buf, 10, &reset);
+ if (err < 0)
+ return err;
+
+ if (reset) {
+ data->adc[nr].lowest = 0;
+ data->adc[nr].highest = 0;
+ data->adc[nr].average = 0;
+ data->samples = 0;
+ }
+
+ return count;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp * 1000);
+}
+
+static ssize_t
+show_fan_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+
+ return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int check_fan_alarm(const struct imanager_hwmon_smartfan *fan)
+{
+ int rpm_min = cpu_to_be16(fan->cfg.rpm_min);
+ int rpm_max = cpu_to_be16(fan->cfg.rpm_max);
+
+ return !fan->valid || fan->speed_min_alarm || fan->speed_max_alarm ||
+ (!fan->speed && fan->pwm) || (fan->speed < rpm_min) ||
+ (fan->speed > rpm_max);
+}
+
+static ssize_t
+show_fan_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 : check_fan_alarm(fan);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_min = cpu_to_be16(data->fan[nr].cfg.rpm_min);
+
+ return sprintf(buf, "%u\n", rpm_min);
+}
+
+static ssize_t
+show_fan_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_max = cpu_to_be16(data->fan[nr].cfg.rpm_max);
+
+ return sprintf(buf, "%u\n", rpm_max);
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_min = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_fan_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_max = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ u32 val = DIV_ROUND_CLOSEST(data->fan[nr].pwm * 255, 100);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+store_pwm(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long pwm = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &pwm);
+ if (err < 0)
+ return err;
+
+ pwm = DIV_ROUND_CLOSEST(pwm * 100, 255);
+
+ mutex_lock(&imgr->lock);
+
+ if (ctrl->mode == MODE_MANUAL) {
+ ctrl->type = CTRL_PWM;
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ } else if ((ctrl->mode == MODE_AUTO) && (ctrl->type == CTRL_PWM)) {
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ fan->cfg.rpm_min = 0;
+ fan->cfg.rpm_max = 0;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ imanager_hwmon_write_fan_alert(&imgr->ec, nr, fan);
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_min);
+}
+
+static ssize_t
+show_pwm_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_max);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = ctrl->mode - 1;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+enum pwm_enable { OFF, MANUAL, THERMAL_CRUISE };
+
+static ssize_t
+store_pwm_enable(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long mode = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &mode);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&imgr->lock);
+
+ switch (mode) {
+ case OFF:
+ ctrl->mode = MODE_FULL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 100);
+ break;
+ case MANUAL:
+ ctrl->mode = MODE_MANUAL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ case THERMAL_CRUISE:
+ ctrl->mode = MODE_AUTO;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = (ctrl->type == CTRL_PWM) ? 1 : 0;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+static ssize_t
+store_pwm_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ ctrl->type = val ? CTRL_RPM : CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%d\n", data->fan[nr].cfg.temp_min * 1000);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp_max * 1000);
+}
+
+static inline bool is_outside(int min, int max, int value)
+{
+ return (value < min) || (value > max);
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct fan_dev_config *cfg = &data->fan[nr].cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg->control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 :
+ is_outside(cfg->temp_min, cfg->temp_max, cfg->temp);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* temperature in 1/10 degC */
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ /*
+ * The iManager provides three different temperature limit values
+ * (stop, min, and max) where stop indicates a minimum temp value
+ * (threshold) from which the FAN will turn off. We are setting
+ * temp_stop to the same value as temp_min since it cannot be mapped
+ * to anything else.
+ */
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_stop = val;
+ fan->cfg.temp_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_temp_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwmon->adc.label[nr]);
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.temp_label[nr]);
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.label[nr]);
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, 0444, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, 0644, show_in_min, store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, 0644, show_in_max, store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, 0444, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, 0644, show_in_min, store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, 0644, show_in_max, store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, 0444, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, 0644, show_in_min, store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, 0644, show_in_max, store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, 0444, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_temp_min, store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, 0644, show_temp_min, store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, 0644, show_temp_min, store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, 0444, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, 0644, show_fan_min, store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, 0644, show_fan_max, store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, 0444, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, 0644, show_fan_min, store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, 0644, show_fan_max, store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, 0444, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, 0644, show_fan_min, store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, 0644, show_fan_max, store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644, show_pwm_min, store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644, show_pwm_max, store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, 0644, show_pwm_mode, store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, 0644, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, 0644, show_pwm_min, store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, 0644, show_pwm_max, store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, 0644, show_pwm_mode, store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, 0644, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, 0644, show_pwm_min, store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, 0644, show_pwm_max, store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, 0644, show_pwm_mode, store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, 0444, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, 0644, show_in_min, store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, 0644, show_in_max, store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, 0444, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, 0444, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, 0444, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, 0444, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, 0200, NULL,
+ store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, 0444, show_in, NULL, 3);
+
+static struct attribute *imanager_in_attributes[] = {
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in0_min.dev_attr.attr,
+ &sensor_dev_attr_in0_max.dev_attr.attr,
+ &sensor_dev_attr_in0_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_min.dev_attr.attr,
+ &sensor_dev_attr_in1_max.dev_attr.attr,
+ &sensor_dev_attr_in1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_min.dev_attr.attr,
+ &sensor_dev_attr_in2_max.dev_attr.attr,
+ &sensor_dev_attr_in2_alarm.dev_attr.attr,
+
+ NULL
+};
+
+#define to_dev(obj) container_of(obj, struct device, kobj)
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ if (hwmon->adc.num <= EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_in = {
+ .attrs = imanager_in_attributes,
+ .is_visible = imanager_in_is_visible,
+};
+
+static struct attribute *imanager_other_attributes[] = {
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_min.dev_attr.attr,
+ &sensor_dev_attr_curr1_max.dev_attr.attr,
+ &sensor_dev_attr_curr1_alarm.dev_attr.attr,
+ &sensor_dev_attr_curr1_average.dev_attr.attr,
+ &sensor_dev_attr_curr1_lowest.dev_attr.attr,
+ &sensor_dev_attr_curr1_highest.dev_attr.attr,
+ &sensor_dev_attr_curr1_reset_history.dev_attr.attr,
+
+ &sensor_dev_attr_cpu0_vid.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t imanager_other_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ /*
+ * There are either 3 or 5 VINs available
+ * vin3 is current monitoring
+ * vin4 is CPU VID
+ */
+ if (hwmon->adc.num == EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_other = {
+ .attrs = imanager_other_attributes,
+ .is_visible = imanager_other_is_visible,
+};
+
+static struct attribute *imanager_fan_attributes[] = {
+ &sensor_dev_attr_fan1_label.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan1_min.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan2_label.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_min.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan3_label.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_min.dev_attr.attr,
+ &sensor_dev_attr_fan3_max.dev_attr.attr,
+ &sensor_dev_attr_fan3_alarm.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_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_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_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm1_min.dev_attr.attr,
+ &sensor_dev_attr_pwm1_max.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm1_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm2_min.dev_attr.attr,
+ &sensor_dev_attr_pwm2_max.dev_attr.attr,
+ &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm2_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm3_min.dev_attr.attr,
+ &sensor_dev_attr_pwm3_max.dev_attr.attr,
+ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm3_mode.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t
+imanager_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_fan_device *fan = &data->imgr->ec.hwmon.fan;
+
+ if ((index >= 0) && (index <= 14)) { /* fan */
+ if (!fan->attr[index / 5])
+ return 0;
+ } else if ((index >= 15) && (index <= 29)) { /* temp */
+ if (!fan->attr[(index - 15) / 5])
+ return 0;
+ } else if ((index >= 30) && (index <= 34)) { /* pwm */
+ if (!fan->attr[(index - 30) / 5])
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+ .attrs = imanager_fan_attributes,
+ .is_visible = imanager_fan_is_visible,
+};
+
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ struct imanager_hwmon_data *data;
+ struct device *hwmon_dev;
+ int i, num_attr_groups = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ platform_set_drvdata(pdev, data);
+
+ /* read fan control settings */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->groups[num_attr_groups++] = &imanager_group_in;
+
+ if (hwmon->adc.num > 3)
+ data->groups[num_attr_groups++] = &imanager_group_other;
+
+ if (hwmon->fan.num)
+ data->groups[num_attr_groups++] = &imanager_group_fan;
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+ "imanager_hwmon",
+ data, data->groups);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver imanager_hwmon_driver = {
+ .driver = {
+ .name = "imanager-hwmon",
+ },
+ .probe = imanager_hwmon_probe,
+};
+
+module_platform_driver(imanager_hwmon_driver);
+
+MODULE_DESCRIPTION("Advantech iManager HWmon Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-hwmon");
--
2.10.1
--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
WARNING: multiple messages have this Message-ID (diff)
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
To: linus.walleij@linaro.org, gnurou@gmail.com, jdelvare@suse.com,
linux@roeck-us.net, wsa@the-dreams.de, lee.jones@linaro.org,
jingoohan1@gmail.com, tomi.valkeinen@ti.com, wim@iguana.be,
linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org,
linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org,
linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org,
k.kozlowski@samsung.com
Cc: Richard Vidal-Dorsch <richard.dorsch@gmail.com>,
jo.sunga@advantech.com, weilun.huang@advantech.com,
andrew.chou@advantech.com
Subject: [PATCH v4 3/6] Add Advantech iManager HWmon driver
Date: Wed, 2 Nov 2016 01:37:48 -0700 [thread overview]
Message-ID: <20161102083751.6335-4-richard.dorsch@gmail.com> (raw)
In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/imanager-hwmon.c | 1226 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 1238 insertions(+)
create mode 100644 drivers/hwmon/imanager-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..7a9db12 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -625,6 +625,17 @@ config SENSORS_CORETEMP
sensor inside your CPU. Most of the family 6 CPUs
are supported. Check Documentation/hwmon/coretemp for details.
+config SENSORS_IMANAGER
+ tristate "Advantech iManager Hardware Monitoring"
+ depends on MFD_IMANAGER
+ select HWMON_VID
+ help
+ This enables support for Advantech iManager hardware monitoring
+ of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager-hwmon.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..3f489ae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER) += imanager-hwmon.o
obj-$(CONFIG_SENSORS_INA209) += ina209.o
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..af72c35
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1226 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Partially derived from nct6775
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN 2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+#define HWM_STATUS_UNDEFINED_ITEM 2UL
+#define HWM_STATUS_UNDEFINED_DID 3UL
+#define HWM_STATUS_UNDEFINED_HWPIN 4UL
+
+/* iManager EC FW pwm[1-*]_mode values are switched */
+enum imanager_pwm_mode { CTRL_PWM, CTRL_RPM };
+/* EC FW defines pwm_enable mode 'full speed' besides mode 'off' */
+enum imanager_pwm_enable { MODE_OFF, MODE_FULL, MODE_MANUAL, MODE_AUTO };
+
+/*
+ * iManager FAN device defs
+ */
+struct fan_dev_config {
+ u8 did,
+ hwpin,
+ tachoid,
+ status,
+ control,
+ temp_max,
+ temp_min,
+ temp_stop,
+ pwm_max,
+ pwm_min;
+ u16 rpm_max;
+ u16 rpm_min;
+ u8 debounce; /* debounce time, not used */
+ u8 temp; /* Current Thermal Zone Temperature */
+ u16 rpm_target; /* RPM Target Speed, not used */
+} __attribute__((__packed__));
+
+struct fan_alert_limit {
+ u16 min,
+ max;
+} __attribute__((__packed__));
+
+struct fan_status {
+ u32 sysctl : 1, /* System Control flag */
+ tacho : 1, /* FAN tacho source defined */
+ pulse : 1, /* FAN pulse type defined */
+ thermal : 1, /* Thermal zone init */
+ i2clink : 1, /* I2C protocol fail flag (thermal sensor) */
+ dnc : 1, /* don't care */
+ mode : 2; /* FAN Control mode */
+};
+
+/**
+ * FAN Control bit field
+ * enable: 0:Disabled, 1:Enabled
+ * type: 0:PWM, 1:RPM
+ * pulse: 0:Undefined 1:2 Pulses 2:4 Pulses
+ * tacho: 1:CPU FAN, 2:SYS FAN1, 3:SYS FAN2
+ * mode: 0:Off, 1:Full, 2:Manual, 3:Auto
+ */
+struct fan_ctrl {
+ u32 enable : 1, /* SmartFAN control on/off */
+ type : 1, /* FAN control type [0, 1] PWM/RPM */
+ pulse : 2, /* FAN pulse [0..2] */
+ tacho : 2, /* FAN Tacho Input [1..3] */
+ mode : 2; /* off/full/manual/auto */
+};
+
+/* Default Voltage Sensors */
+struct imanager_hwmon_adc {
+ bool valid; /* if set, below values are valid */
+ unsigned int value;
+ unsigned int min;
+ unsigned int max;
+ unsigned int average;
+ unsigned int lowest;
+ unsigned int highest;
+};
+
+struct imanager_hwmon_smartfan {
+ bool valid; /* if set, below values are valid */
+ unsigned int pwm;
+ unsigned int speed;
+ bool speed_min_alarm;
+ bool speed_max_alarm;
+ struct fan_dev_config cfg;
+};
+
+struct imanager_hwmon_data {
+ struct imanager_device_data *imgr;
+ const struct attribute_group *groups[3];
+ unsigned long samples;
+ unsigned long last_updated;
+ struct imanager_hwmon_adc adc[EC_MAX_ADC_NUM];
+ struct imanager_hwmon_smartfan fan[EC_MAX_FAN_NUM];
+};
+
+static int imanager_hwmon_read_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(EC_F_HWMON_MSG, 0, num, (u8 *)&fan->cfg)
+ };
+ int ret;
+
+ ret = imanager_read(ec, EC_CMD_FAN_CTL_RD, &msg);
+ if (ret)
+ return ret;
+
+ return fan->cfg.did ? 0 : -ENODEV;
+}
+
+static int
+imanager_hwmon_write_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, sizeof(fan->cfg), num, (u8 *)&fan->cfg)
+ };
+ int err;
+
+ err = imanager_write(ec, EC_CMD_FAN_CTL_WR, &msg);
+ if (err < 0) {
+ return err;
+ } else if (err) {
+ switch (err) {
+ case HWM_STATUS_UNDEFINED_ITEM:
+ case HWM_STATUS_UNDEFINED_DID:
+ case HWM_STATUS_UNDEFINED_HWPIN:
+ return -ENODEV;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * FAN max/min alert bits are stored as bit pairs in a 8-bit register.
+ * FAN0~2: 2:{max2, min2}, 1:{max1, min1}, 0:{max0, min0}
+ */
+#define IS_FAN_ALERT_MIN(fan_num, var) test_bit(BIT((fan_num) << 1), var)
+#define IS_FAN_ALERT_MAX(fan_num, var) test_bit(BIT(((fan_num) << 1) + 1), var)
+
+static int imanager_hwmon_read_fan_alert(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ const ulong alert_flags = 0;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT,
+ (u8 *)&alert_flags, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ fan->speed_min_alarm = IS_FAN_ALERT_MIN(num, &alert_flags);
+ fan->speed_max_alarm = IS_FAN_ALERT_MAX(num, &alert_flags);
+
+ return 0;
+}
+
+static int imanager_hwmon_write_fan_alert(struct imanager_ec_data *ec, int fnum,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct fan_alert_limit limits[EC_MAX_FAN_NUM];
+ struct fan_alert_limit *limit = &limits[fnum];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+ if (ret < 0)
+ return ret;
+
+ limit->min = fan->cfg.rpm_min;
+ limit->max = fan->cfg.rpm_max;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+}
+
+static int imanager_hwmon_read_adc(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_adc *adc)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+ int ret;
+
+ adc->valid = false;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ adc->value = ret * attr->ecdev->scale;
+ adc->valid = true;
+
+ return 0;
+}
+
+static int imanager_hwmon_read_fan_ctrl(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+
+ int ret;
+
+ fan->valid = false;
+
+ ret = imanager_hwmon_read_fan_config(ec, num, fan);
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, fan->cfg.tachoid);
+ if (ret < 0)
+ return ret;
+
+ fan->speed = ret;
+
+ ret = imanager_read8(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ fan->pwm = ret;
+
+ ret = imanager_hwmon_read_fan_alert(ec, num, fan);
+ if (ret)
+ return ret;
+
+ fan->valid = true;
+
+ return 0;
+}
+
+static inline uint in_from_reg(u16 val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(uint val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * 1000, SCALE_IN), 0, 65535);
+}
+
+static struct imanager_hwmon_data *
+imanager_hwmon_update_device(struct device *dev)
+{
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ int i;
+
+ mutex_lock(&imgr->lock);
+
+ if (time_after(jiffies, data->last_updated + HZ + HZ / 2)) {
+ /* Measured voltages */
+ for (i = 0; i < hwmon->adc.num; i++)
+ imanager_hwmon_read_adc(ec, i, &data->adc[i]);
+
+ /* Measured fan speeds */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->last_updated = jiffies;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return data;
+}
+
+static ssize_t
+show_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].value));
+}
+
+static ssize_t
+show_in_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].min));
+}
+
+static ssize_t
+show_in_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].max));
+}
+
+static ssize_t
+store_in_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].min = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+store_in_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].max = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+show_in_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+ int val = 0;
+
+ if (adc->valid)
+ val = (adc->value < adc->min) || (adc->value > adc->max);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+show_in_average(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (adc->average) {
+ adc->average = DIV_ROUND_CLOSEST(adc->average * data->samples +
+ adc->value, ++data->samples);
+ } else {
+ adc->average = adc->value;
+ data->samples = 1;
+ }
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->average));
+}
+
+static ssize_t
+show_in_lowest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->lowest || (adc->value < adc->lowest))
+ adc->lowest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->lowest));
+}
+
+static ssize_t
+show_in_highest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->highest || (adc->value > adc->highest))
+ adc->highest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->highest));
+}
+
+static ssize_t
+store_in_reset_history(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long reset;
+ int err;
+
+ err = kstrtoul(buf, 10, &reset);
+ if (err < 0)
+ return err;
+
+ if (reset) {
+ data->adc[nr].lowest = 0;
+ data->adc[nr].highest = 0;
+ data->adc[nr].average = 0;
+ data->samples = 0;
+ }
+
+ return count;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp * 1000);
+}
+
+static ssize_t
+show_fan_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+
+ return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int check_fan_alarm(const struct imanager_hwmon_smartfan *fan)
+{
+ int rpm_min = cpu_to_be16(fan->cfg.rpm_min);
+ int rpm_max = cpu_to_be16(fan->cfg.rpm_max);
+
+ return !fan->valid || fan->speed_min_alarm || fan->speed_max_alarm ||
+ (!fan->speed && fan->pwm) || (fan->speed < rpm_min) ||
+ (fan->speed > rpm_max);
+}
+
+static ssize_t
+show_fan_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 : check_fan_alarm(fan);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_min = cpu_to_be16(data->fan[nr].cfg.rpm_min);
+
+ return sprintf(buf, "%u\n", rpm_min);
+}
+
+static ssize_t
+show_fan_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_max = cpu_to_be16(data->fan[nr].cfg.rpm_max);
+
+ return sprintf(buf, "%u\n", rpm_max);
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_min = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_fan_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_max = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ u32 val = DIV_ROUND_CLOSEST(data->fan[nr].pwm * 255, 100);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+store_pwm(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long pwm = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &pwm);
+ if (err < 0)
+ return err;
+
+ pwm = DIV_ROUND_CLOSEST(pwm * 100, 255);
+
+ mutex_lock(&imgr->lock);
+
+ if (ctrl->mode == MODE_MANUAL) {
+ ctrl->type = CTRL_PWM;
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ } else if ((ctrl->mode == MODE_AUTO) && (ctrl->type == CTRL_PWM)) {
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ fan->cfg.rpm_min = 0;
+ fan->cfg.rpm_max = 0;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ imanager_hwmon_write_fan_alert(&imgr->ec, nr, fan);
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_min);
+}
+
+static ssize_t
+show_pwm_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_max);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = ctrl->mode - 1;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+enum pwm_enable { OFF, MANUAL, THERMAL_CRUISE };
+
+static ssize_t
+store_pwm_enable(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long mode = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &mode);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&imgr->lock);
+
+ switch (mode) {
+ case OFF:
+ ctrl->mode = MODE_FULL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 100);
+ break;
+ case MANUAL:
+ ctrl->mode = MODE_MANUAL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ case THERMAL_CRUISE:
+ ctrl->mode = MODE_AUTO;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = (ctrl->type == CTRL_PWM) ? 1 : 0;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+static ssize_t
+store_pwm_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ ctrl->type = val ? CTRL_RPM : CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%d\n", data->fan[nr].cfg.temp_min * 1000);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp_max * 1000);
+}
+
+static inline bool is_outside(int min, int max, int value)
+{
+ return (value < min) || (value > max);
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct fan_dev_config *cfg = &data->fan[nr].cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg->control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 :
+ is_outside(cfg->temp_min, cfg->temp_max, cfg->temp);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* temperature in 1/10 degC */
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ /*
+ * The iManager provides three different temperature limit values
+ * (stop, min, and max) where stop indicates a minimum temp value
+ * (threshold) from which the FAN will turn off. We are setting
+ * temp_stop to the same value as temp_min since it cannot be mapped
+ * to anything else.
+ */
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_stop = val;
+ fan->cfg.temp_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_temp_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwmon->adc.label[nr]);
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.temp_label[nr]);
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.label[nr]);
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, 0444, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, 0644, show_in_min, store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, 0644, show_in_max, store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, 0444, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, 0644, show_in_min, store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, 0644, show_in_max, store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, 0444, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, 0644, show_in_min, store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, 0644, show_in_max, store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, 0444, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_temp_min, store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, 0644, show_temp_min, store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, 0644, show_temp_min, store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, 0444, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, 0644, show_fan_min, store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, 0644, show_fan_max, store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, 0444, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, 0644, show_fan_min, store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, 0644, show_fan_max, store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, 0444, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, 0644, show_fan_min, store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, 0644, show_fan_max, store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644, show_pwm_min, store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644, show_pwm_max, store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, 0644, show_pwm_mode, store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, 0644, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, 0644, show_pwm_min, store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, 0644, show_pwm_max, store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, 0644, show_pwm_mode, store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, 0644, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, 0644, show_pwm_min, store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, 0644, show_pwm_max, store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, 0644, show_pwm_mode, store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, 0444, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, 0644, show_in_min, store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, 0644, show_in_max, store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, 0444, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, 0444, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, 0444, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, 0444, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, 0200, NULL,
+ store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, 0444, show_in, NULL, 3);
+
+static struct attribute *imanager_in_attributes[] = {
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in0_min.dev_attr.attr,
+ &sensor_dev_attr_in0_max.dev_attr.attr,
+ &sensor_dev_attr_in0_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_min.dev_attr.attr,
+ &sensor_dev_attr_in1_max.dev_attr.attr,
+ &sensor_dev_attr_in1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_min.dev_attr.attr,
+ &sensor_dev_attr_in2_max.dev_attr.attr,
+ &sensor_dev_attr_in2_alarm.dev_attr.attr,
+
+ NULL
+};
+
+#define to_dev(obj) container_of(obj, struct device, kobj)
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ if (hwmon->adc.num <= EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_in = {
+ .attrs = imanager_in_attributes,
+ .is_visible = imanager_in_is_visible,
+};
+
+static struct attribute *imanager_other_attributes[] = {
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_min.dev_attr.attr,
+ &sensor_dev_attr_curr1_max.dev_attr.attr,
+ &sensor_dev_attr_curr1_alarm.dev_attr.attr,
+ &sensor_dev_attr_curr1_average.dev_attr.attr,
+ &sensor_dev_attr_curr1_lowest.dev_attr.attr,
+ &sensor_dev_attr_curr1_highest.dev_attr.attr,
+ &sensor_dev_attr_curr1_reset_history.dev_attr.attr,
+
+ &sensor_dev_attr_cpu0_vid.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t imanager_other_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ /*
+ * There are either 3 or 5 VINs available
+ * vin3 is current monitoring
+ * vin4 is CPU VID
+ */
+ if (hwmon->adc.num == EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_other = {
+ .attrs = imanager_other_attributes,
+ .is_visible = imanager_other_is_visible,
+};
+
+static struct attribute *imanager_fan_attributes[] = {
+ &sensor_dev_attr_fan1_label.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan1_min.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan2_label.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_min.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan3_label.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_min.dev_attr.attr,
+ &sensor_dev_attr_fan3_max.dev_attr.attr,
+ &sensor_dev_attr_fan3_alarm.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_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_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_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm1_min.dev_attr.attr,
+ &sensor_dev_attr_pwm1_max.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm1_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm2_min.dev_attr.attr,
+ &sensor_dev_attr_pwm2_max.dev_attr.attr,
+ &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm2_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm3_min.dev_attr.attr,
+ &sensor_dev_attr_pwm3_max.dev_attr.attr,
+ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm3_mode.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t
+imanager_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_fan_device *fan = &data->imgr->ec.hwmon.fan;
+
+ if ((index >= 0) && (index <= 14)) { /* fan */
+ if (!fan->attr[index / 5])
+ return 0;
+ } else if ((index >= 15) && (index <= 29)) { /* temp */
+ if (!fan->attr[(index - 15) / 5])
+ return 0;
+ } else if ((index >= 30) && (index <= 34)) { /* pwm */
+ if (!fan->attr[(index - 30) / 5])
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+ .attrs = imanager_fan_attributes,
+ .is_visible = imanager_fan_is_visible,
+};
+
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ struct imanager_hwmon_data *data;
+ struct device *hwmon_dev;
+ int i, num_attr_groups = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ platform_set_drvdata(pdev, data);
+
+ /* read fan control settings */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->groups[num_attr_groups++] = &imanager_group_in;
+
+ if (hwmon->adc.num > 3)
+ data->groups[num_attr_groups++] = &imanager_group_other;
+
+ if (hwmon->fan.num)
+ data->groups[num_attr_groups++] = &imanager_group_fan;
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+ "imanager_hwmon",
+ data, data->groups);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver imanager_hwmon_driver = {
+ .driver = {
+ .name = "imanager-hwmon",
+ },
+ .probe = imanager_hwmon_probe,
+};
+
+module_platform_driver(imanager_hwmon_driver);
+
+MODULE_DESCRIPTION("Advantech iManager HWmon Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-hwmon");
--
2.10.1
next prev parent reply other threads:[~2016-11-02 8:37 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-11-02 8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-02 8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-03 8:41 ` Lee Jones
2016-11-03 8:41 ` Lee Jones
2016-11-03 8:41 ` Lee Jones
2016-11-02 8:37 ` [PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-05 8:29 ` Linus Walleij
2016-11-05 8:29 ` Linus Walleij
2016-11-02 8:37 ` [PATCH v4 4/6] Add Advantech iManager I2C driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-02 8:37 ` [PATCH v4 5/6] Add Advantech iManager Backlight driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
[not found] ` <20161102083751.6335-1-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2016-11-02 8:37 ` Richard Vidal-Dorsch [this message]
2016-11-02 8:37 ` [PATCH v4 3/6] Add Advantech iManager HWmon driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-02 8:37 ` [PATCH v4 6/6] Add Advantech iManager Watchdog driver Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
2016-11-02 8:37 ` Richard Vidal-Dorsch
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20161102083751.6335-4-richard.dorsch@gmail.com \
--to=richard.dorsch@gmail.com \
--cc=andrew.chou-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org \
--cc=gnurou-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
--cc=jdelvare-IBi9RG/b67k@public.gmane.org \
--cc=jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
--cc=jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org \
--cc=k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org \
--cc=lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
--cc=linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
--cc=linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org \
--cc=linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-hwmon-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-watchdog-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
--cc=tomi.valkeinen-l0cyMroinI0@public.gmane.org \
--cc=weilun.huang-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org \
--cc=wim-IQzOog9fTRqzQB+pC5nmwQ@public.gmane.org \
--cc=wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.