* [PATCH v2 3/6] Add Advantech iManager HWmon driver
@ 2016-01-10 9:11 richard.dorsch
2016-01-10 10:25 ` kbuild test robot
2016-01-10 10:25 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
0 siblings, 2 replies; 8+ messages in thread
From: richard.dorsch @ 2016-01-10 9:11 UTC (permalink / raw)
To: linux-kernel
Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
jdelvare, linux, wim, jo.sunga, Richard Vidal-Dorsch
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
Documentation/hwmon/imanager | 59 ++
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 2 +
drivers/hwmon/imanager-ec-hwmon.c | 606 +++++++++++++++++++++
drivers/hwmon/imanager-hwmon.c | 1058 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/imanager/hwmon.h | 120 ++++
6 files changed, 1857 insertions(+)
create mode 100644 Documentation/hwmon/imanager
create mode 100644 drivers/hwmon/imanager-ec-hwmon.c
create mode 100644 drivers/hwmon/imanager-hwmon.c
create mode 100644 include/linux/mfd/imanager/hwmon.h
diff --git a/Documentation/hwmon/imanager b/Documentation/hwmon/imanager
new file mode 100644
index 0000000..0705cf9
--- /dev/null
+++ b/Documentation/hwmon/imanager
@@ -0,0 +1,59 @@
+Kernel driver imanager_hwmon
+============================
+
+This platform driver provides support for iManager Hardware Monitoring
+and FAN control.
+
+This driver depends on imanager (mfd).
+
+Description
+-----------
+
+This driver provides support for the Advantech iManager Hardware Monitoring EC.
+
+The Advantech iManager supports up to 3 fan rotation speed sensors,
+3 temperature monitoring sources and up to 5 voltage sensors, VID, alarms and
+a automatic fan regulation strategy (as well as manual fan control mode).
+
+Temperatures are measured in degrees Celsius and measurement resolution is
+1 degC. An Alarm is triggered when the temperature gets higher than the high
+limit; it stays on until the temperature falls below the high limit.
+
+Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
+triggered if the rotation speed has dropped below a programmable limit. No fan
+speed divider support available.
+
+Voltage sensors (also known as IN sensors) report their values in millivolts.
+An alarm is triggered if the voltage has crossed a programmable minimum
+or maximum limit.
+
+The driver supports automatic fan control mode known as Thermal Cruise.
+In this mode, the firmware attempts to keep the measured temperature in a
+predefined temperature range. If the temperature goes out of range, fan
+is driven slower/faster to reach the predefined range again.
+
+The mode works for fan1-fan3.
+
+sysfs attributes
+----------------
+
+pwm[1-3] - this file stores PWM duty cycle or DC value (fan speed) in range:
+ 0 (lowest speed) to 255 (full)
+
+pwm[1-3]_enable - this file controls mode of fan/temperature control:
+ * 0 Fan control disabled (fans set to maximum speed)
+ * 1 Manual mode, write to pwm[1-3] any value 0-255
+ * 2 "Fan Speed Cruise" mode
+
+pwm[1-3]_mode - controls if output is PWM or DC level
+ * 0 DC output
+ * 1 PWM output
+
+Speed Cruise mode (2)
+---------------------
+
+This mode tries to keep the fan speed constant within min/max speed.
+
+fan[1-3]_min - Minimum fan speed
+fan[1-3]_max - Maximum fan speed
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 80a73bf..776bb8a 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -613,6 +613,18 @@ 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.
+ Requires mfd-core and imanager-core to function properly.
+
+ 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 12a3239..53752bc 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,8 @@ 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
+imanager_hwmon-objs := imanager-hwmon.o imanager-ec-hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER) += imanager_hwmon.o
obj-$(CONFIG_SENSORS_INA209) += ina209.o
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
diff --git a/drivers/hwmon/imanager-ec-hwmon.c b/drivers/hwmon/imanager-ec-hwmon.c
new file mode 100644
index 0000000..1920835
--- /dev/null
+++ b/drivers/hwmon/imanager-ec-hwmon.c
@@ -0,0 +1,606 @@
+/*
+ * Advantech iManager Hardware Monitoring core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/swab.h>
+#include <linux/mfd/imanager/ec.h>
+#include <linux/mfd/imanager/hwmon.h>
+
+#define HWM_STATUS_UNDEFINED_ITEM 2UL
+#define HWM_STATUS_UNDEFINED_DID 3UL
+#define HWM_STATUS_UNDEFINED_HWPIN 4UL
+
+/*
+ * FAN 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 */
+};
+
+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 */
+};
+
+struct fan_alert_limit {
+ u16 fan0_min,
+ fan0_max,
+ fan1_min,
+ fan1_max,
+ fan2_min,
+ fan2_max;
+};
+
+struct fan_alert_flag {
+ u32 fan0_min_alarm : 1,
+ fan0_max_alarm : 1,
+ fan1_min_alarm : 1,
+ fan1_max_alarm : 1,
+ fan2_min_alarm : 1,
+ fan2_max_alarm : 1,
+ dnc : 2; /* don't care */
+};
+
+/*----------------------------------------------------*
+ * FAN Control bit field *
+ * enable: 0:Disabled, 1:Enabled *
+ * type: 0:PWM, 1:RPM *
+ * pulse: 0:Undefined 1:2 Pulse 2:4 Pulse *
+ * tacho: 1:CPU FAN, 2:SYS1 FAN, 3:SYS2 FAN *
+ * mode: 0:Off, 1:Full, 2:Manual, 3:Auto *
+ *- 7 6 ---- 5 4 --- 3 2 ----- 1 -------- 0 -------*
+ * MODE | TACHO | PULSE | TYPE | ENABLE *
+ *----------------------------------------------------*/
+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 */
+};
+
+enum fan_dev_ctrl {
+ CTRL_STATE = 3,
+ OPMODE,
+ IDSENSOR,
+ ACTIVE,
+ CTRL_MODE,
+};
+
+enum fan_limit {
+ LIMIT_PWM,
+ LIMIT_RPM,
+ LIMIT_TEMP,
+};
+
+static const char * const fan_temp_label[] = {
+ "Temp CPU",
+ "Temp SYS1",
+ "Temp SYS2",
+ NULL,
+};
+
+static const struct imanager_hwmon_device *hwmon;
+
+static inline int hwm_get_adc_value(u8 did)
+{
+ return imanager_read_word(EC_CMD_HWP_RD, did);
+}
+
+static inline int hwm_get_rpm_value(u8 did)
+{
+ return imanager_read_word(EC_CMD_HWP_RD, did);
+}
+
+static inline int hwm_get_pwm_value(u8 did)
+{
+ return imanager_read_byte(EC_CMD_HWP_RD, did);
+}
+
+static inline int hwm_set_pwm_value(u8 did, u8 val)
+{
+ return imanager_write_byte(EC_CMD_HWP_WR, did, val);
+}
+
+static int hwm_read_fan_config(int num, struct fan_dev_config *cfg)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 0xff, /* use alternative message body */
+ .wlen = 0,
+ };
+ struct fan_dev_config *_cfg = (struct fan_dev_config *)&msg.u.data;
+
+ if (WARN_ON(!cfg))
+ return -EINVAL;
+
+ memset(cfg, 0, sizeof(struct fan_dev_config));
+
+ ret = imanager_msg_read(EC_CMD_FAN_CTL_RD, num, &msg);
+ if (ret)
+ return ret;
+
+ if (!_cfg->did) {
+ pr_err("Invalid FAN%d device ID - possible firmware bug\n",
+ num);
+ return -ENODEV;
+ }
+
+ memcpy(cfg, &msg.u.data, sizeof(struct fan_dev_config));
+
+ return 0;
+}
+
+static int hwm_write_fan_config(int fnum, struct fan_dev_config *cfg)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = sizeof(struct fan_dev_config),
+ };
+
+ if (!cfg->did)
+ return -ENODEV;
+
+ msg.data = (u8 *)cfg;
+
+ ret = imanager_msg_write(EC_CMD_FAN_CTL_WR, fnum, &msg);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case 0:
+ break;
+ case HWM_STATUS_UNDEFINED_ITEM:
+ case HWM_STATUS_UNDEFINED_DID:
+ case HWM_STATUS_UNDEFINED_HWPIN:
+ return -EFAULT;
+ default:
+ pr_err("Unknown error status of fan%d (%d)\n", fnum, ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static inline void hwm_set_temp_limit(struct fan_dev_config *cfg,
+ const struct hwm_fan_temp_limit *temp)
+{
+ cfg->temp_stop = temp->stop;
+ cfg->temp_min = temp->min;
+ cfg->temp_max = temp->max;
+}
+
+static inline void hwm_set_pwm_limit(struct fan_dev_config *cfg,
+ const struct hwm_fan_limit *pwm)
+{
+ cfg->pwm_min = pwm->min;
+ cfg->pwm_max = pwm->max;
+}
+
+static inline void hwm_set_rpm_limit(struct fan_dev_config *cfg,
+ const struct hwm_fan_limit *rpm)
+{
+ cfg->rpm_min = swab16(rpm->min);
+ cfg->rpm_max = swab16(rpm->max);
+}
+
+static inline void hwm_set_limit(struct fan_dev_config *cfg,
+ const struct hwm_sensors_limit *limit)
+{
+ hwm_set_temp_limit(cfg, &limit->temp);
+ hwm_set_pwm_limit(cfg, &limit->pwm);
+ hwm_set_rpm_limit(cfg, &limit->rpm);
+}
+
+static int hwm_core_get_fan_alert_flag(struct fan_alert_flag *flag)
+{
+ int ret;
+ u8 *value = (u8 *)flag;
+
+ ret = imanager_acpiram_read_byte(EC_ACPIRAM_FAN_ALERT);
+ if (ret < 0)
+ return ret;
+
+ *value = ret;
+
+ return 0;
+}
+
+static int hwm_core_get_fan_alert_limit(int fnum,
+ struct hwm_smartfan *fan)
+{
+ int ret;
+ struct fan_alert_limit limit;
+ struct fan_alert_flag flag;
+
+ ret = imanager_acpiram_read_block(EC_ACPIRAM_FAN_SPEED_LIMIT,
+ (u8 *)&limit, sizeof(limit));
+ if (ret < 0)
+ return ret;
+
+ ret = hwm_core_get_fan_alert_flag(&flag);
+ if (ret < 0)
+ return ret;
+
+ switch (fnum) {
+ case FAN_CPU:
+ fan->alert.min = swab16(limit.fan0_min);
+ fan->alert.max = swab16(limit.fan0_max);
+ fan->alert.min_alarm = flag.fan0_min_alarm;
+ fan->alert.max_alarm = flag.fan0_max_alarm;
+ break;
+ case FAN_SYS1:
+ fan->alert.min = swab16(limit.fan1_min);
+ fan->alert.max = swab16(limit.fan1_max);
+ fan->alert.min_alarm = flag.fan1_min_alarm;
+ fan->alert.max_alarm = flag.fan1_max_alarm;
+ break;
+ case FAN_SYS2:
+ fan->alert.min = swab16(limit.fan2_min);
+ fan->alert.max = swab16(limit.fan2_max);
+ fan->alert.min_alarm = flag.fan2_min_alarm;
+ fan->alert.max_alarm = flag.fan2_max_alarm;
+ break;
+ default:
+ pr_err("Unknown FAN ID %d\n", fnum);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hwm_core_fan_set_alert_limit(int fnum,
+ struct hwm_fan_alert *alert)
+{
+ int ret;
+ struct fan_alert_limit limit;
+
+ ret = imanager_acpiram_read_block(EC_ACPIRAM_FAN_SPEED_LIMIT,
+ (u8 *)&limit, sizeof(limit));
+ if (ret < 0)
+ return ret;
+
+ switch (fnum) {
+ case FAN_CPU:
+ limit.fan0_min = swab16(alert->min);
+ limit.fan0_max = swab16(alert->max);
+ break;
+ case FAN_SYS1:
+ limit.fan1_min = swab16(alert->min);
+ limit.fan1_max = swab16(alert->max);
+ break;
+ case FAN_SYS2:
+ limit.fan2_min = swab16(alert->min);
+ limit.fan2_max = swab16(alert->max);
+ break;
+ default:
+ pr_err("Unknown FAN ID %d\n", fnum);
+ return -EINVAL;
+ }
+
+ return imanager_acpiram_write_block(EC_ACPIRAM_FAN_SPEED_LIMIT,
+ (u8 *)&limit, sizeof(limit));
+}
+
+/* HWM CORE API */
+
+const char *hwm_core_adc_get_label(int num)
+{
+ if (WARN_ON(num >= hwmon->adc.num))
+ return NULL;
+
+ return hwmon->adc.attr[num].label;
+}
+
+const char *hwm_core_fan_get_label(int num)
+{
+ if (WARN_ON(num >= hwmon->fan.num))
+ return NULL;
+
+ return hwmon->fan.attr[num].label;
+}
+
+const char *hwm_core_fan_get_temp_label(int num)
+{
+ if (WARN_ON(num >= hwmon->fan.num))
+ return NULL;
+
+ return fan_temp_label[num];
+}
+
+int hwm_core_adc_is_available(int num)
+{
+ if (num >= EC_HWM_MAX_ADC)
+ return -EINVAL;
+
+ return hwmon->adc.attr[num].did ? 0 : -ENODEV;
+}
+
+int hwm_core_adc_get_value(int num, struct hwm_voltage *volt)
+{
+ int ret;
+
+ volt->valid = false;
+
+ ret = hwm_core_adc_is_available(num);
+ if (ret < 0)
+ return ret;
+
+ ret = hwm_get_adc_value(hwmon->adc.attr[num].did);
+ if (ret < 0)
+ return ret;
+
+ volt->value = ret * hwmon->adc.attr[num].scale;
+ volt->valid = true;
+
+ return 0;
+}
+
+int hwm_core_fan_get_ctrl(int num, struct hwm_smartfan *fan)
+{
+ int ret;
+ struct fan_dev_config cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg.control;
+
+ if (WARN_ON((num >= HWM_MAX_FAN) || !fan))
+ return -EINVAL;
+
+ memset(fan, 0, sizeof(struct hwm_smartfan));
+
+ ret = hwm_read_fan_config(num, &cfg);
+ if (ret < 0)
+ return ret;
+
+ fan->pulse = ctrl->pulse;
+ fan->type = ctrl->type;
+
+ /*
+ * It seems that fan->mode does not always report the correct
+ * FAN mode so the only way of reporting the current FAN mode
+ * is to read back ctrl->mode.
+ */
+ fan->mode = ctrl->mode;
+
+ ret = hwm_get_rpm_value(cfg.tachoid);
+ if (ret < 0) {
+ pr_err("Failed to read FAN speed\n");
+ return ret;
+ }
+
+ fan->speed = ret;
+
+ ret = hwm_get_pwm_value(hwmon->fan.attr[num].did);
+ if (ret < 0) {
+ pr_err("Failed to read FAN%d PWM\n", num);
+ return ret;
+ }
+
+ fan->pwm = ret;
+
+ fan->alarm = (fan->pwm && !fan->speed) ? 1 : 0;
+
+ fan->limit.temp.min = cfg.temp_min;
+ fan->limit.temp.max = cfg.temp_max;
+ fan->limit.temp.stop = cfg.temp_stop;
+ fan->limit.pwm.min = cfg.pwm_min;
+ fan->limit.pwm.max = cfg.pwm_max;
+ fan->limit.rpm.min = swab16(cfg.rpm_min);
+ fan->limit.rpm.max = swab16(cfg.rpm_max);
+
+ ret = hwm_core_get_fan_alert_limit(num, fan);
+ if (ret)
+ return ret;
+
+ fan->valid = true;
+
+ return 0;
+}
+
+int hwm_core_fan_set_ctrl(int num, int fmode, int ftype, int pwm, int pulse,
+ struct hwm_sensors_limit *limit,
+ struct hwm_fan_alert *alert)
+{
+ int ret;
+ struct fan_dev_config cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg.control;
+ struct hwm_sensors_limit _limit = { {0, 0, 0}, {0, 0}, {0, 0} };
+
+ if (WARN_ON(num >= HWM_MAX_FAN))
+ return -EINVAL;
+
+ ret = hwm_read_fan_config(num, &cfg);
+ if (ret < 0) {
+ pr_err("Failed while reading FAN %s config\n",
+ hwmon->fan.attr[num].label);
+ return ret;
+ }
+
+ if (!limit)
+ limit = &_limit;
+
+ switch (fmode) {
+ case MODE_OFF:
+ ctrl->type = CTRL_PWM;
+ ctrl->mode = MODE_OFF;
+ break;
+ case MODE_FULL:
+ ctrl->type = CTRL_PWM;
+ ctrl->mode = MODE_FULL;
+ break;
+ case MODE_MANUAL:
+ ctrl->type = CTRL_PWM;
+ ctrl->mode = MODE_MANUAL;
+ ret = hwm_set_pwm_value(hwmon->fan.attr[num].did, pwm);
+ if (ret < 0)
+ return ret;
+ break;
+ case MODE_AUTO:
+ switch (ftype) {
+ case CTRL_PWM:
+ limit->rpm.min = 0;
+ limit->rpm.max = 0;
+ ctrl->type = CTRL_PWM;
+ break;
+ case CTRL_RPM:
+ limit->pwm.min = 0;
+ limit->pwm.max = 0;
+ ctrl->type = CTRL_RPM;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ctrl->mode = MODE_AUTO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ hwm_set_limit(&cfg, limit);
+
+ ctrl->pulse = (pulse && (pulse < 3)) ? pulse : 0;
+ ctrl->enable = 1;
+
+ ret = hwm_write_fan_config(num, &cfg);
+ if (ret < 0)
+ return ret;
+
+ if (alert)
+ return hwm_core_fan_set_alert_limit(num, alert);
+
+ return 0;
+}
+
+int hwm_core_fan_is_available(int num)
+{
+ if (WARN_ON(num >= HWM_MAX_FAN))
+ return -EINVAL;
+
+ return hwmon->fan.active & (1 << num) &&
+ hwmon->fan.attr[num].did ? 0 : -ENODEV;
+}
+
+static int hwm_core_fan_set_limit(int num, int fan_limit,
+ struct hwm_sensors_limit *limit)
+{
+ struct fan_dev_config cfg;
+ int ret;
+
+ if (WARN_ON(num >= HWM_MAX_FAN))
+ return -EINVAL;
+
+ ret = hwm_read_fan_config(num, &cfg);
+ if (ret < 0) {
+ pr_err("Failed while reading FAN %s config\n",
+ hwmon->fan.attr[num].label);
+ return ret;
+ }
+
+ switch (fan_limit) {
+ case LIMIT_PWM:
+ hwm_set_pwm_limit(&cfg, &limit->pwm);
+ break;
+ case LIMIT_RPM:
+ hwm_set_rpm_limit(&cfg, &limit->rpm);
+ break;
+ case LIMIT_TEMP:
+ hwm_set_temp_limit(&cfg, &limit->temp);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return hwm_write_fan_config(num, &cfg);
+}
+
+int hwm_core_fan_set_rpm_limit(int num, int min, int max)
+{
+ struct hwm_sensors_limit limit = {
+ .rpm = {
+ .min = min,
+ .max = max,
+ },
+ };
+
+ return hwm_core_fan_set_limit(num, LIMIT_RPM, &limit);
+}
+
+int hwm_core_fan_set_pwm_limit(int num, int min, int max)
+{
+ struct hwm_sensors_limit limit = {
+ .pwm = {
+ .min = min,
+ .max = max,
+ },
+ };
+
+ return hwm_core_fan_set_limit(num, LIMIT_PWM, &limit);
+}
+
+int hwm_core_fan_set_temp_limit(int num, int stop, int min, int max)
+{
+ struct hwm_sensors_limit limit = {
+ .temp = {
+ .stop = stop,
+ .min = min,
+ .max = max,
+ },
+ };
+
+ return hwm_core_fan_set_limit(num, LIMIT_TEMP, &limit);
+}
+
+int hwm_core_adc_get_max_count(void)
+{
+ return hwmon->adc.num;
+}
+
+int hwm_core_fan_get_max_count(void)
+{
+ return hwmon->fan.num;
+}
+
+int hwm_core_init(void)
+{
+ hwmon = imanager_get_hwmon_device();
+ if (!hwmon)
+ return -ENODEV;
+
+ return 0;
+}
+
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..b8ad562
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1058 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Derived from nct6775 driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/hwmon.h>
+
+struct imanager_hwmon_data {
+ struct imanager_device_data *idev;
+ bool valid; /* if set, below values are valid */
+ struct hwm_data hwm;
+ int adc_num;
+ int fan_num;
+ unsigned long samples;
+ unsigned long last_updated;
+ const struct attribute_group *groups[3];
+};
+
+static inline u32 in_from_reg(u16 val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(u32 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);
+ int i;
+
+ mutex_lock(&data->idev->lock);
+
+ if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
+ || !data->valid) {
+ /* Measured voltages */
+ for (i = 0; i < data->adc_num; i++)
+ hwm_core_adc_get_value(i, &data->hwm.volt[i]);
+
+ /* Measured fan speeds */
+ for (i = 0; i < data->fan_num; i++)
+ hwm_core_fan_get_ctrl(i, &data->hwm.fan[i]);
+
+ data->last_updated = jiffies;
+ data->valid = true;
+ }
+
+ mutex_unlock(&data->idev->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&data->idev->lock);
+
+ adc->min = in_to_reg(val);
+
+ mutex_unlock(&data->idev->lock);
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&data->idev->lock);
+
+ adc->max = in_to_reg(val);
+
+ mutex_unlock(&data->idev->lock);
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ if (!adc->lowest)
+ adc->lowest = adc->highest = adc->value;
+ else if (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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+
+ if (!adc->highest)
+ adc->highest = adc->value;
+ else if (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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_voltage *adc = &data->hwm.volt[index];
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&data->idev->lock);
+
+ if (val == 1) {
+ adc->lowest = 0;
+ adc->highest = 0;
+ } else {
+ count = -EINVAL;
+ }
+
+ mutex_unlock(&data->idev->lock);
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+
+ return sprintf(buf, "%u\n", fan->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+
+ return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int is_alarm(const struct hwm_smartfan *fan)
+{
+ /*
+ * Do not set ALARM flag if FAN is in speed cruise mode (3)
+ * as this mode automatically turns on the FAN
+ * Set ALARM flag when pwm is set but speed is 0 as this
+ * could be a defective FAN or no FAN is present
+ */
+ return (!fan->valid ||
+ ((fan->mode == MODE_AUTO) && fan->alarm) ||
+ (fan->speed > fan->limit.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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+
+ return sprintf(buf, "%u\n", fan->valid ? is_alarm(fan) : 0);
+}
+
+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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_fan_limit *rpm = &data->hwm.fan[index - 1].limit.rpm;
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_fan_limit *rpm = &data->hwm.fan[index - 1].limit.rpm;
+
+ 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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+ struct hwm_fan_limit *rpm = &fan->limit.rpm;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in 'fan cruise mode' */
+ if (fan->mode != MODE_AUTO)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_rpm_limit(index - 1, val, rpm->max);
+ hwm_core_fan_get_ctrl(index - 1, fan); /* update */
+
+ mutex_unlock(&data->idev->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+ struct hwm_fan_limit *rpm = &fan->limit.rpm;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in 'fan cruise mode' */
+ if (fan->mode != MODE_AUTO)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_rpm_limit(index - 1, rpm->min, val);
+ hwm_core_fan_get_ctrl(index - 1, fan);
+
+ mutex_unlock(&data->idev->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 index = to_sensor_dev_attr(attr)->index;
+ u32 val = DIV_ROUND_CLOSEST(data->hwm.fan[index - 1].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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&data->idev->lock);
+
+ switch (fan->mode) {
+ case MODE_MANUAL:
+ hwm_core_fan_set_ctrl(index - 1, MODE_MANUAL, CTRL_PWM,
+ val, 0, NULL, NULL);
+ break;
+ case MODE_AUTO:
+ if (fan->type == CTRL_RPM)
+ break;
+ hwm_core_fan_set_ctrl(index - 1, MODE_AUTO, CTRL_PWM,
+ val, 0, &fan->limit, &fan->alert);
+ break;
+ }
+
+ mutex_unlock(&data->idev->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 index = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", data->hwm.fan[index - 1].limit.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 index = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", data->hwm.fan[index - 1].limit.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 hwm_smartfan *fan = &data->hwm.fan[nr];
+
+ if (fan->mode == MODE_OFF)
+ return -EINVAL;
+
+ return sprintf(buf, "%u\n", fan->mode - 1);
+}
+
+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);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct hwm_smartfan *fan = &data->hwm.fan[nr];
+ unsigned long mode = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &mode);
+ if (err < 0)
+ return err;
+
+ if (mode > MODE_AUTO)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ switch (mode) {
+ case 0:
+ if (mode != 0)
+ hwm_core_fan_set_ctrl(nr, MODE_FULL, CTRL_PWM, 100,
+ fan->pulse, NULL, NULL);
+ break;
+ case 1:
+ if (mode != 1)
+ hwm_core_fan_set_ctrl(nr, MODE_MANUAL, CTRL_PWM, 0,
+ fan->pulse, NULL, NULL);
+ break;
+ case 2:
+ if (mode != 2)
+ hwm_core_fan_set_ctrl(nr, MODE_AUTO, fan->type, 0,
+ fan->pulse, &fan->limit, NULL);
+ break;
+ }
+
+ mutex_unlock(&data->idev->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 index = to_sensor_dev_attr(attr)->index;
+ struct hwm_smartfan *fan = &data->hwm.fan[index - 1];
+
+ if (fan->mode == MODE_OFF)
+ return -EINVAL;
+
+ return sprintf(buf, "%u\n", fan->type == CTRL_PWM ? 1 : 0);
+}
+
+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);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct hwm_smartfan *fan = &data->hwm.fan[nr];
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ if (fan->mode != MODE_AUTO)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_ctrl(nr, fan->mode, val ? CTRL_RPM : CTRL_PWM,
+ fan->pwm, fan->pulse, &fan->limit, &fan->alert);
+
+ mutex_unlock(&data->idev->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;
+ int val = data->hwm.fan[nr].limit.temp.min;
+
+ return sprintf(buf, "%d\n", val * 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;
+ int val = data->hwm.fan[nr].limit.temp.max;
+
+ return sprintf(buf, "%u\n", val * 1000);
+}
+
+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 hwm_smartfan *fan = &data->hwm.fan[nr];
+ struct hwm_fan_temp_limit *temp = &fan->limit.temp;
+
+ return sprintf(buf, "%u\n", (fan->temp && (fan->temp >= temp->max)));
+}
+
+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);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct hwm_smartfan *fan = &data->hwm.fan[nr];
+ struct hwm_fan_temp_limit *temp = &fan->limit.temp;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+
+ if (val > 100)
+ return -EINVAL;
+
+ /* do not apply value if not in 'fan cruise mode' */
+ if (fan->mode != MODE_AUTO)
+ return -EINVAL;
+
+ /* The EC 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.
+ */
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_temp_limit(nr, val, val, temp->max);
+ hwm_core_fan_get_ctrl(nr, fan);
+
+ mutex_unlock(&data->idev->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);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct hwm_smartfan *fan = &data->hwm.fan[nr];
+ struct hwm_fan_temp_limit *temp = &fan->limit.temp;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+
+ if (val > 100)
+ return -EINVAL;
+
+ /* do not apply value if not in 'fan cruise mode' */
+ if (fan->mode != MODE_AUTO)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_temp_limit(nr, temp->stop, temp->min, val);
+ hwm_core_fan_get_ctrl(nr, fan);
+
+ mutex_unlock(&data->idev->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);
+ int index = to_sensor_dev_attr(attr)->index;
+ struct hwm_fan_limit *pwm = &data->hwm.fan[index - 1].limit.pwm;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ if (val > 100)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_pwm_limit(index - 1, val, pwm->max);
+
+ mutex_unlock(&data->idev->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);
+ int index = to_sensor_dev_attr(attr)->index;
+ struct hwm_fan_limit *pwm = &data->hwm.fan[index - 1].limit.pwm;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ if (val > 100)
+ return -EINVAL;
+
+ mutex_lock(&data->idev->lock);
+
+ hwm_core_fan_set_pwm_limit(index - 1, pwm->min, val);
+
+ mutex_unlock(&data->idev->lock);
+
+ return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int index = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwm_core_adc_get_label(index));
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int index = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwm_core_fan_get_temp_label(index - 1));
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int index = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwm_core_fan_get_label(index - 1));
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min,
+ store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max,
+ store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min,
+ store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max,
+ store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min,
+ store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max,
+ store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, show_temp_min,
+ store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max,
+ store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, show_temp_min,
+ store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max,
+ store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp_min,
+ store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max,
+ store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
+ store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IWUSR | S_IRUGO, show_fan_max,
+ store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
+ store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, S_IWUSR | S_IRUGO, show_fan_max,
+ store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, S_IRUGO, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min,
+ store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, S_IWUSR | S_IRUGO, show_fan_max,
+ store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, S_IWUSR | S_IRUGO, show_pwm_min,
+ store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, S_IWUSR | S_IRUGO, show_pwm_max,
+ store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable,
+ store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, show_pwm_mode,
+ store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, S_IWUSR | S_IRUGO, show_pwm_min,
+ store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, S_IWUSR | S_IRUGO, show_pwm_max,
+ store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable,
+ store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, S_IWUSR | S_IRUGO, show_pwm_mode,
+ store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, S_IWUSR | S_IRUGO, show_pwm_min,
+ store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, S_IWUSR | S_IRUGO, show_pwm_max,
+ store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_enable,
+ store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, S_IWUSR | S_IRUGO, show_pwm_mode,
+ store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, S_IWUSR | S_IRUGO, show_in_min,
+ store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, S_IWUSR | S_IRUGO, show_in_max,
+ store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, S_IRUGO, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, S_IRUGO, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, S_IRUGO, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, S_IRUGO, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, S_IWUSR, NULL,
+ store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, S_IRUGO, 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
+};
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ int max_count = hwm_core_adc_get_max_count();
+
+ if (max_count >= 3)
+ 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)
+{
+ int max_count = hwm_core_adc_get_max_count();
+
+ /*
+ * There are either 3 or 5 VINs
+ * vin3 is current monitoring
+ * vin4 is CPU VID
+ */
+ if (max_count > 3)
+ 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)
+{
+ int err;
+
+ if ((index >= 0) && (index <= 14)) { /* fan */
+ err = hwm_core_fan_is_available(index / 5);
+ if (err < 0)
+ return 0;
+ } else if ((index >= 15) && (index <= 29)) { /* temp */
+ err = hwm_core_fan_is_available((index - 15) / 5);
+ if (err < 0)
+ return 0;
+ } else if ((index >= 30) && (index <= 34)) { /* pwm */
+ err = hwm_core_fan_is_available((index - 30) / 5);
+ if (err < 0)
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+ .attrs = imanager_fan_attributes,
+ .is_visible = imanager_fan_is_visible,
+};
+
+/*
+ * Module stuff
+ */
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+ struct imanager_hwmon_data *data;
+ struct device *hwmon_dev;
+ int err, i, num_attr_groups = 0;
+
+ if (!idev) {
+ dev_err(dev, "Invalid platform data\n");
+ return -EINVAL;
+ }
+
+ err = hwm_core_init();
+ if (err) {
+ dev_err(dev, "Hwmon core init failed\n");
+ return -EIO;
+ }
+
+ data = devm_kzalloc(dev, sizeof(struct imanager_hwmon_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->idev = idev;
+ platform_set_drvdata(pdev, data);
+
+ data->adc_num = hwm_core_adc_get_max_count();
+ data->fan_num = hwm_core_fan_get_max_count();
+
+ for (i = 0; i < data->fan_num; i++) {
+ /* set active fan to automatic speed control */
+ hwm_core_fan_set_ctrl(i, MODE_AUTO, CTRL_RPM, 0, 0,
+ NULL, NULL);
+ hwm_core_fan_get_ctrl(i, &data->hwm.fan[i]);
+ }
+
+ data->groups[num_attr_groups++] = &imanager_group_in;
+
+ if (data->adc_num > 3)
+ data->groups[num_attr_groups++] = &imanager_group_other;
+
+ if (data->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 = {
+ .owner = THIS_MODULE,
+ .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");
diff --git a/include/linux/mfd/imanager/hwmon.h b/include/linux/mfd/imanager/hwmon.h
new file mode 100644
index 0000000..2a7e191
--- /dev/null
+++ b/include/linux/mfd/imanager/hwmon.h
@@ -0,0 +1,120 @@
+/*
+ * Advantech iManager Hardware Monitoring core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __HWMON_H__
+#define __HWMON_H__
+
+#include <linux/types.h>
+
+#define HWM_MAX_ADC 5
+#define HWM_MAX_FAN 3
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN 2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+/* Default Voltage Sensors */
+struct hwm_voltage {
+ bool valid; /* if set, below values are valid */
+
+ int value;
+ int min;
+ int max;
+ int average;
+ int lowest;
+ int highest;
+
+};
+
+struct hwm_fan_temp_limit {
+ int stop;
+ int min;
+ int max;
+};
+
+struct hwm_fan_limit {
+ int min;
+ int max;
+};
+
+struct hwm_fan_alert {
+ int min;
+ int max;
+ int min_alarm;
+ int max_alarm;
+};
+
+struct hwm_sensors_limit {
+ struct hwm_fan_temp_limit temp;
+ struct hwm_fan_limit pwm;
+ struct hwm_fan_limit rpm;
+};
+
+struct hwm_smartfan {
+ bool valid; /* if set, below values are valid */
+
+ int mode;
+ int type;
+ int pwm;
+ int speed;
+ int pulse;
+ int alarm;
+ int temp;
+
+ struct hwm_sensors_limit limit;
+ struct hwm_fan_alert alert;
+};
+
+struct hwm_data {
+ struct hwm_voltage volt[HWM_MAX_ADC];
+ struct hwm_smartfan fan[HWM_MAX_FAN];
+};
+
+enum fan_unit {
+ FAN_CPU,
+ FAN_SYS1,
+ FAN_SYS2,
+};
+
+enum fan_ctrl_type {
+ CTRL_PWM,
+ CTRL_RPM,
+};
+
+enum fan_mode {
+ MODE_OFF,
+ MODE_FULL,
+ MODE_MANUAL,
+ MODE_AUTO,
+};
+
+int hwm_core_init(void);
+
+int hwm_core_adc_is_available(int num);
+int hwm_core_adc_get_max_count(void);
+int hwm_core_adc_get_value(int num, struct hwm_voltage *volt);
+const char *hwm_core_adc_get_label(int num);
+
+int hwm_core_fan_is_available(int num);
+int hwm_core_fan_get_max_count(void);
+int hwm_core_fan_get_ctrl(int num, struct hwm_smartfan *fan);
+int hwm_core_fan_set_ctrl(int num, int fmode, int ftype, int pwm, int pulse,
+ struct hwm_sensors_limit *limit,
+ struct hwm_fan_alert *alert);
+
+int hwm_core_fan_set_rpm_limit(int num, int min, int max);
+int hwm_core_fan_set_pwm_limit(int num, int min, int max);
+int hwm_core_fan_set_temp_limit(int num, int stop, int min, int max);
+
+const char *hwm_core_fan_get_label(int num);
+const char *hwm_core_fan_get_temp_label(int num);
+
+#endif
--
2.6.4
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/6] Add Advantech iManager HWmon driver
2016-01-10 9:11 [PATCH v2 3/6] Add Advantech iManager HWmon driver richard.dorsch
@ 2016-01-10 10:25 ` kbuild test robot
2016-01-10 10:25 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
1 sibling, 0 replies; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:25 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160110-171635
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/hwmon/imanager-hwmon.c:1047:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH] fix platform_no_drv_owner.cocci warnings
2016-01-10 9:11 [PATCH v2 3/6] Add Advantech iManager HWmon driver richard.dorsch
2016-01-10 10:25 ` kbuild test robot
@ 2016-01-10 10:25 ` kbuild test robot
1 sibling, 0 replies; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:25 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
drivers/hwmon/imanager-hwmon.c:1047:3-8: No need to set .owner here. The core will do it.
Remove .owner field if calls are used which set it automatically
Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci
CC: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---
imanager-hwmon.c | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/hwmon/imanager-hwmon.c
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -1044,7 +1044,6 @@ static int imanager_hwmon_probe(struct p
static struct platform_driver imanager_hwmon_driver = {
.driver = {
- .owner = THIS_MODULE,
.name = "imanager_hwmon",
},
.probe = imanager_hwmon_probe,
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 5/6] Add Advantech iManager Backlight driver
@ 2016-01-10 10:44 kbuild test robot
[not found] ` <1452417098-28667-1-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:44 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160110-171635
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/video/backlight/imanager-bl.c:187:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 4/6] Add Advantech iManager I2C driver
2016-01-10 9:11 ` richard.dorsch
@ 2016-01-10 10:34 kbuild test robot
2016-01-10 9:11 ` richard.dorsch
0 siblings, 1 reply; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:34 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160110-171635
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/i2c/busses/imanager-i2c.c:228:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 4/6] Add Advantech iManager I2C driver
@ 2016-01-10 9:11 ` richard.dorsch
2016-01-10 10:34 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
0 siblings, 1 reply; 8+ messages in thread
From: richard.dorsch @ 2016-01-10 9:11 UTC (permalink / raw)
To: linux-kernel
Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
jdelvare, linux, wim, jo.sunga, Richard Vidal-Dorsch
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
Documentation/i2c/busses/i2c-imanager | 48 ++++
drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 2 +
drivers/i2c/busses/imanager-ec-i2c.c | 466 ++++++++++++++++++++++++++++++++++
drivers/i2c/busses/imanager-i2c.c | 240 +++++++++++++++++
include/linux/mfd/imanager/i2c.h | 55 ++++
6 files changed, 822 insertions(+)
create mode 100644 Documentation/i2c/busses/i2c-imanager
create mode 100644 drivers/i2c/busses/imanager-ec-i2c.c
create mode 100644 drivers/i2c/busses/imanager-i2c.c
create mode 100644 include/linux/mfd/imanager/i2c.h
diff --git a/Documentation/i2c/busses/i2c-imanager b/Documentation/i2c/busses/i2c-imanager
new file mode 100644
index 0000000..d149fbf
--- /dev/null
+++ b/Documentation/i2c/busses/i2c-imanager
@@ -0,0 +1,48 @@
+Kernel driver imanager_i2c
+==========================
+
+This platform driver provides support for iManager I2C/SMBus.
+
+This driver depends on imanager (mfd).
+
+Module Parameters
+-----------------
+
+* bus_frequency (unsigned short)
+Set desired bus frequency. Valid values (kHz) are:
+ 50 Slow
+ 100 Standard (default)
+ 400 Fast
+
+
+Description
+-----------
+
+The Advantech iManager provides up to four SMBus controllers. One of them
+is configured for I2C compatibility.
+
+
+Process Call Support
+--------------------
+
+Not supported.
+
+
+I2C Block Read Support
+----------------------
+
+I2C block read is supported.
+
+
+SMBus 2.0 Support
+-----------------
+
+Several SMBus 2.0 features are supported.
+No PEC support.
+
+
+Interrupt Support
+-----------------
+
+No interrupt support available
+
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7b0aa82..4c401a4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -42,6 +42,17 @@ config I2C_ALI15X3
This driver can also be built as a module. If so, the module
will be called i2c-ali15x3.
+config I2C_IMANAGER
+ tristate "Advantech iManager I2C Interface"
+ depends on MFD_IMANAGER
+ help
+ This enables support for Advantech iManager I2C of some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards.
+ Requires mfd-core and imanager-core to function properly.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-imanager.
+
config I2C_AMD756
tristate "AMD 756/766/768/8111 and nVidia nForce"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..da76404 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -15,6 +15,8 @@ obj-$(CONFIG_I2C_AMD8111) += i2c-amd8111.o
obj-$(CONFIG_I2C_I801) += i2c-i801.o
obj-$(CONFIG_I2C_ISCH) += i2c-isch.o
obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o
+i2c-imanager-objs := imanager-i2c.o imanager-ec-i2c.o
+obj-$(CONFIG_I2C_IMANAGER) += i2c-imanager.o
obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o
obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
diff --git a/drivers/i2c/busses/imanager-ec-i2c.c b/drivers/i2c/busses/imanager-ec-i2c.c
new file mode 100644
index 0000000..f8839dc
--- /dev/null
+++ b/drivers/i2c/busses/imanager-ec-i2c.c
@@ -0,0 +1,466 @@
+/*
+ * Advantech iManager I2C bus core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/mfd/imanager/ec.h>
+#include <linux/mfd/imanager/i2c.h>
+
+#define I2C_SMBUS_BLOCK_SIZE 32
+
+#define EVAL_WR_SIZE(x) \
+ (x < I2C_SMBUS_BLOCK_SIZE ? x : I2C_SMBUS_BLOCK_SIZE - 1)
+#define EVAL_RD_SIZE(x) \
+ (x && (x <= I2C_SMBUS_BLOCK_SIZE) ? x : I2C_SMBUS_BLOCK_SIZE)
+
+#define EC_HWRAM_OFFSET_STATUS 0UL
+
+#define I2C_ERR_PROTO 0x19UL
+#define I2C_ERR_TIMEOUT 0x18UL
+#define I2C_ERR_ACCESS 0x17UL
+#define I2C_ERR_UNKNOWN 0x13UL
+#define I2C_ERR_ADDR_NACK 0x10UL
+
+#define EC_SMB_DID(N) (0x28 + N)
+
+struct ec_i2c_status {
+ u32 error : 7;
+ u32 complete : 1;
+};
+
+static const struct imanager_i2c_device *i2c;
+
+static int i2c_core_eval_status(u8 _status)
+{
+ struct ec_i2c_status *status = (struct ec_i2c_status *)&_status;
+ int err = 0;
+
+ switch (status->error) {
+ case 0:
+ break;
+ case I2C_ERR_ADDR_NACK:
+ err = -ENODEV;
+ break;
+ case I2C_ERR_ACCESS:
+ case I2C_ERR_UNKNOWN:
+ err = -EAGAIN;
+ break;
+ case I2C_ERR_TIMEOUT:
+ err = -ETIME;
+ break;
+ case I2C_ERR_PROTO:
+ err = -EPROTO;
+ break;
+ default:
+ pr_err("Undefined status code 0x%02X\n", status->error);
+ err = -EIO;
+ break;
+ }
+
+ return err;
+}
+
+static inline int i2c_send_message(u8 cmd, u8 param, struct ec_message *msg)
+{
+ int err;
+
+ err = imanager_msg_write(cmd, param, msg);
+ if (err)
+ return i2c_core_eval_status(err);
+
+ return 0;
+}
+
+static int i2c_core_blk_wr_rw_combined(u8 proto, struct ec_message *msg)
+{
+ int err;
+
+ if (WARN_ON(!msg))
+ return -EINVAL;
+
+ err = imanager_wait_proc_complete(EC_HWRAM_OFFSET_STATUS, 0);
+ if (err)
+ return err;
+
+ err = i2c_send_message(proto, i2c->i2coem->did, msg);
+ if (err)
+ return err;
+
+ if (msg->rlen) {
+ if (msg->rlen == 1)
+ return msg->u.data[0];
+ else if (msg->rlen == 2)
+ return (msg->u.data[1] << 8) | msg->u.data[0];
+ else
+ return msg->rlen;
+ }
+
+ return 0;
+}
+
+/* Write-Read and Read-Write wrappers */
+static inline int i2c_core_wr_combined(struct ec_message *msg)
+{
+ return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_WR, msg);
+}
+
+static inline int i2c_core_rw_combined(struct ec_message *msg)
+{
+ return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_RW, msg);
+}
+
+/*
+ * iManager I2C core API
+ */
+int i2c_core_write_quick(u16 addr)
+{
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 0,
+ .wlen = 1,
+ },
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_byte(u16 addr)
+{
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 1,
+ .wlen = 0,
+ },
+ },
+ };
+
+ return i2c_core_rw_combined(&msg);
+}
+
+int i2c_core_write_byte(u16 addr, u8 cmd)
+{
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 1,
+ .wlen = 1,
+ .cmd = cmd,
+ },
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_byte_data(u16 addr, u8 cmd)
+{
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 1,
+ .wlen = 1,
+ .cmd = cmd,
+ },
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value)
+{
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header) + 1,
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 0,
+ .wlen = 2,
+ .cmd = cmd,
+ },
+ .smb.data[0] = value,
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_word_data(u16 addr, u8 cmd)
+{
+ struct ec_message msg = {
+ .rlen = 2,
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 2,
+ .wlen = 1,
+ .cmd = cmd,
+ },
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value)
+{
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header) + 2,
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 0,
+ .wlen = 3,
+ .cmd = cmd,
+ },
+ .smb.data[0] = LOBYTE16(value),
+ .smb.data[1] = HIBYTE16(value),
+ },
+ };
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+ int i;
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = sizeof(struct ec_message_header) +
+ EVAL_WR_SIZE(buf[0]),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = 0,
+ .wlen = 1 + EVAL_WR_SIZE(buf[0]),
+ .cmd = cmd,
+ },
+ },
+ };
+
+ if ((buf[0] == 0) || (buf[0] >= I2C_MAX_WRITE_BYTES)) {
+ pr_err("Invalid I2C write length %d\n", buf[0]);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < EVAL_WR_SIZE(buf[0]); i++)
+ msg.u.data[i + sizeof(struct ec_message_header)] = buf[i + 1];
+
+ return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+ int i;
+ int ret;
+ struct ec_message msg = {
+ .rlen = EVAL_RD_SIZE(buf[0]),
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = EVAL_RD_SIZE(buf[0]),
+ .wlen = 1,
+ .cmd = cmd,
+ },
+ },
+ };
+
+ /*
+ * If buf[0] == 0 EC will read I2C_MAX_READ_BYTES
+ */
+ ret = i2c_core_wr_combined(&msg);
+ if (ret < 0) {
+ pr_err("I2C transaction failed\n");
+ return ret;
+ }
+
+ buf[0] = ret;
+ for (i = 0; i < ret; i++)
+ buf[i + 1] = msg.u.data[i];
+
+ return 0;
+}
+
+int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+ int i;
+ int ret;
+ struct ec_message msg = {
+ .rlen = EVAL_RD_SIZE(buf[0]),
+ .wlen = sizeof(struct ec_message_header),
+ .u = {
+ .smb.hdr = {
+ .addr_low = LOADDR16(addr),
+ .addr_high = HIADDR16(addr),
+ .rlen = EVAL_RD_SIZE(buf[0]),
+ .wlen = 1,
+ .cmd = cmd,
+ },
+ },
+ };
+
+ if ((buf[0] == 0) || (buf[0] > I2C_MAX_READ_BYTES)) {
+ pr_err("Invalid I2C read length\n");
+ return -EINVAL;
+ }
+
+ ret = i2c_core_wr_combined(&msg);
+ if (ret < 0) {
+ pr_err("I2C transaction failed\n");
+ return ret;
+ }
+
+ buf[0] = ret;
+ for (i = 0; i < ret; i++)
+ buf[i + 1] = msg.u.data[i];
+
+ return 0;
+}
+
+int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+ if (WARN_ON(!buf))
+ return -EINVAL;
+
+ return i2c_core_write_block_data(addr, cmd, buf);
+}
+
+int i2c_core_smb_get_freq(u32 bus_id)
+{
+ int ret = 0, f;
+ int freq_id, freq;
+
+ if (WARN_ON(bus_id > I2C_OEM_1))
+ return -EINVAL;
+
+ switch (i2c->ecdev->id) {
+ case IT8518:
+ case IT8528:
+ ret = imanager_read_word(EC_CMD_SMB_FREQ_RD,
+ EC_SMB_DID(bus_id));
+ if (ret < 0) {
+ pr_err("Failed to get bus frequency\n");
+ return ret;
+ }
+
+ freq_id = HIBYTE16(ret);
+ f = LOBYTE16(ret);
+ switch (freq_id) {
+ case 0:
+ freq = f;
+ break;
+ case 1:
+ freq = 50;
+ break;
+ case 2:
+ freq = 100;
+ break;
+ case 3:
+ freq = 400;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ pr_err("EC version not supported!\n");
+ return -ENODEV;
+ }
+
+ return freq;
+}
+
+int i2c_core_smb_set_freq(u32 bus_id, u32 freq)
+{
+ int err;
+ u16 val;
+
+ if (WARN_ON(bus_id > I2C_OEM_1))
+ return -EINVAL;
+
+ switch (i2c->ecdev->id) {
+ case IT8518:
+ case IT8528:
+ switch (freq) {
+ case 50:
+ val = 0x0100;
+ break;
+ case 100:
+ val = 0x0200;
+ break;
+ case 400:
+ val = 0x0300;
+ break;
+ default:
+ if (freq < 50)
+ val = freq;
+ else
+ return -EINVAL;
+ }
+
+ err = imanager_write_word(EC_CMD_SMB_FREQ_WR,
+ EC_SMB_DID(bus_id), val);
+ if (err) {
+ pr_err("Failed to set I2C bus frequency\n");
+ return err;
+ }
+ break;
+ default:
+ pr_err("EC version not supported!\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+int i2c_core_init(void)
+{
+ i2c = imanager_get_i2c_device();
+ if (!i2c)
+ return -ENODEV;
+
+ return 0;
+}
+
diff --git a/drivers/i2c/busses/imanager-i2c.c b/drivers/i2c/busses/imanager-i2c.c
new file mode 100644
index 0000000..bbfd453
--- /dev/null
+++ b/drivers/i2c/busses/imanager-i2c.c
@@ -0,0 +1,240 @@
+/*
+ * Advantech iManager I2C bus driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/i2c.h>
+
+static uint bus_frequency = 100;
+module_param(bus_frequency, uint, 0);
+MODULE_PARM_DESC(bus_frequency,
+ "I2C bus frequency [50, 100, 400]kHz (defaults to 100kHz)");
+
+struct imanager_i2c_data {
+ struct imanager_device_data *idev;
+ struct i2c_adapter adapter;
+};
+
+static int imanager_smb_access(struct i2c_adapter *adap, u16 addr,
+ unsigned short flags, char read_write, u8 command,
+ int size, union i2c_smbus_data *smb_data)
+{
+ struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+ struct device *dev = data->adapter.dev.parent;
+ int ret = 0;
+ int val = 0;
+
+ if (!data)
+ return -ENODEV;
+
+ addr <<= 1;
+
+ mutex_lock(&data->idev->lock);
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ ret = i2c_core_write_quick(addr);
+ break;
+ case I2C_SMBUS_BYTE:
+ if (read_write == I2C_SMBUS_WRITE) /* NOT tested */
+ val = i2c_core_write_byte(addr, command);
+ else
+ val = i2c_core_read_byte(addr);
+
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ if (read_write == I2C_SMBUS_WRITE)
+ val = i2c_core_write_byte_data(addr, command,
+ smb_data->byte);
+ else
+ val = i2c_core_read_byte_data(addr, command);
+
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ case I2C_SMBUS_WORD_DATA:
+ if (read_write == I2C_SMBUS_WRITE)
+ val = i2c_core_write_word_data(addr, command,
+ smb_data->word);
+ else
+ val = i2c_core_read_word_data(addr, command);
+
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->word = val;
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ if (read_write == I2C_SMBUS_WRITE)
+ ret = i2c_core_write_block_data(addr, command,
+ smb_data->block);
+ else
+ ret = i2c_core_read_block_data(addr, command,
+ smb_data->block);
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ if (read_write == I2C_SMBUS_WRITE)
+ ret = i2c_core_write_i2c_block_data(addr, command,
+ smb_data->block);
+ else
+ ret = i2c_core_read_i2c_block_data(addr, command,
+ smb_data->block);
+ break;
+ default:
+ dev_err(dev, "Unsupported SMB transaction %d\n", size);
+ ret = -EOPNOTSUPP;
+ }
+
+ mutex_unlock(&data->idev->lock);
+
+ return ret;
+}
+
+static int imanager_i2c_access(struct i2c_adapter *adap, struct i2c_msg *msg,
+ int num)
+{
+ struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+ struct device *dev = data->adapter.dev.parent;
+
+ /*
+ * To be implemented
+ */
+
+ dev_info(dev, "i2c_access() is not yet implemented. msg=%p, num=%d\n",
+ msg, num);
+
+ return 0;
+}
+
+static u32 imanager_smb_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm imanager_algorithm = {
+ .smbus_xfer = imanager_smb_access,
+ .master_xfer = imanager_i2c_access,
+ .functionality = imanager_smb_i2c_func,
+};
+
+static int imanager_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+ struct imanager_i2c_data *i2c;
+ int ret;
+
+ if (!idev) {
+ dev_err(dev, "Invalid platform data\n");
+ return -EINVAL;
+ }
+
+ ret = i2c_core_init();
+ if (ret) {
+ dev_err(dev, "Failed to initialize I2C core\n");
+ return -EIO;
+ }
+
+ if (bus_frequency > 100)
+ bus_frequency = 400;
+ else if (bus_frequency < 50)
+ bus_frequency = 50;
+ else
+ bus_frequency = 100;
+
+ ret = i2c_core_smb_set_freq(I2C_OEM_1, bus_frequency);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set I2C bus frequency to %d kHz\n",
+ bus_frequency);
+ return ret;
+ }
+
+ ret = i2c_core_smb_get_freq(I2C_OEM_1);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get I2C bus frequency\n");
+ return ret;
+ }
+ bus_frequency = ret;
+ dev_info(dev, "Bus frequency: %d kHz\n", bus_frequency);
+
+ i2c = devm_kzalloc(dev, sizeof(struct imanager_i2c_data), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, i2c);
+ i2c_set_adapdata(&i2c->adapter, i2c);
+
+ i2c->idev = idev;
+
+ i2c->adapter.owner = THIS_MODULE;
+ i2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ i2c->adapter.algo = &imanager_algorithm;
+
+ /* set up the sysfs linkage to our parent device */
+ i2c->adapter.dev.parent = dev;
+
+ /* Retry up to 3 times on lost arbitration */
+ i2c->adapter.retries = 3;
+
+ snprintf(i2c->adapter.name, sizeof(i2c->adapter.name),
+ "iManager I2C driver");
+
+ ret = i2c_add_adapter(&i2c->adapter);
+ if (ret) {
+ dev_err(dev, "Failed to add SMBus adapter\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imanager_i2c_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_i2c_data *i2c = dev_get_drvdata(dev);
+
+ i2c_del_adapter(&i2c->adapter);
+ i2c_set_adapdata(&i2c->adapter, NULL);
+
+ return 0;
+}
+
+static struct platform_driver imanager_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imanager_i2c",
+ },
+ .probe = imanager_i2c_probe,
+ .remove = imanager_i2c_remove,
+};
+
+module_platform_driver(imanager_i2c_driver);
+
+MODULE_DESCRIPTION("Advantech iManager I2C Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager_i2c");
diff --git a/include/linux/mfd/imanager/i2c.h b/include/linux/mfd/imanager/i2c.h
new file mode 100644
index 0000000..a8ef6c2
--- /dev/null
+++ b/include/linux/mfd/imanager/i2c.h
@@ -0,0 +1,55 @@
+/*
+ * Advantech iManager I2C bus core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __I2C_H__
+#define __I2C_H__
+
+#include <linux/types.h>
+
+#define I2C_MAX_READ_BYTES 32
+#define I2C_MAX_WRITE_BYTES 32
+
+/* Only for setting SMBus frequency */
+enum smb_bus_id {
+ SMB_OEM_0,
+ SMB_OEM_1,
+ SMB_OEM_2,
+ SMB_EEPROM,
+ SMB_TH_0,
+ SMB_TH_1,
+ SMB_SECURITY_EEPROM,
+ I2C_OEM_1,
+};
+
+int i2c_core_init(void);
+
+int i2c_core_write_quick(u16 addr);
+
+int i2c_core_read_byte(u16 addr);
+int i2c_core_write_byte(u16 addr, u8 cmd);
+
+int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value);
+int i2c_core_read_byte_data(u16 addr, u8 cmd);
+
+int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value);
+int i2c_core_read_word_data(u16 addr, u8 cmd);
+
+int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf);
+int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf);
+
+int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf);
+int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf);
+
+int i2c_core_smb_get_freq(u32 bus_id);
+int i2c_core_smb_set_freq(u32 bus_id, u32 freq);
+
+#endif
--
2.6.4
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH] fix platform_no_drv_owner.cocci warnings
2016-01-10 9:11 ` richard.dorsch
@ 2016-01-10 10:34 ` kbuild test robot
0 siblings, 0 replies; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:34 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
drivers/i2c/busses/imanager-i2c.c:228:3-8: No need to set .owner here. The core will do it.
Remove .owner field if calls are used which set it automatically
Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci
CC: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---
imanager-i2c.c | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/i2c/busses/imanager-i2c.c
+++ b/drivers/i2c/busses/imanager-i2c.c
@@ -225,7 +225,6 @@ static int imanager_i2c_remove(struct pl
static struct platform_driver imanager_i2c_driver = {
.driver = {
- .owner = THIS_MODULE,
.name = "imanager_i2c",
},
.probe = imanager_i2c_probe,
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 1/6] Add Advantech iManager MFD core driver
2016-01-10 9:10 ` richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
@ 2016-01-10 10:11 kbuild test robot
2016-01-10 9:10 ` richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
0 siblings, 1 reply; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:11 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160110-171635
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/mfd/imanager-core.c:248:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/6] Add Advantech iManager MFD core driver
@ 2016-01-10 9:10 ` richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
2016-01-10 10:11 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
0 siblings, 1 reply; 8+ messages in thread
From: richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w @ 2016-01-10 9:10 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: lm-sensors-GZX6beZjE8VD60Wz+7aTrA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
linux-gpio-u79uwXL29TY76Z2rM5mHXA,
lee.jones-QSEj5FYQhm4dnm+yROfE0A, jdelvare-IBi9RG/b67k,
linux-0h96xk9xTtrk1uMJSBkQmQ, wim-IQzOog9fTRqzQB+pC5nmwQ,
jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg, Richard Vidal-Dorsch
From: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
This patch adds Advantech iManager Embedded Controller MFD core driver.
This mfd core dirver provides an interface to GPIO, I2C, HWmon,
Watchdog, and Backlight/Brightness control.
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
Documentation/devicetree/bindings/mfd/imanager.txt | 33 +
MAINTAINERS | 13 +
drivers/mfd/Kconfig | 20 +
drivers/mfd/Makefile | 2 +
drivers/mfd/imanager-core.c | 288 +++++
drivers/mfd/imanager-ec.c | 1345 ++++++++++++++++++++
include/linux/mfd/imanager/core.h | 31 +
include/linux/mfd/imanager/ec.h | 210 +++
8 files changed, 1942 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/imanager.txt
create mode 100644 drivers/mfd/imanager-core.c
create mode 100644 drivers/mfd/imanager-ec.c
create mode 100644 include/linux/mfd/imanager/core.h
create mode 100644 include/linux/mfd/imanager/ec.h
diff --git a/Documentation/devicetree/bindings/mfd/imanager.txt b/Documentation/devicetree/bindings/mfd/imanager.txt
new file mode 100644
index 0000000..bf58a96
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/imanager.txt
@@ -0,0 +1,33 @@
+Note
+====
+
+This is a set of platform drivers which provide support for multiple
+embedded features such as GPIO, I2C/SMBus, Hardware Monitoring, Watchdog,
+and Backlight/Brightness control. Those features are available on Advantech
+Embedded boards such as SOM, MIO, AIMB, and PCM.
+Datasheets of each product line can be downloaded from www.advantech.com
+
+Author:
+ Richard Vidal-Dorsch <richard.dorsch-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org>
+
+
+Kernel driver imanager
+======================
+
+This driver provides a communication layer to the Advantech iManager EC
+firmware which is factory loaded onto ITE IT8518/28 chips. The type of
+communication is message based. Clients (gpio, i2c, hwmon etc. drivers)
+request data from Advantech iManager (polling, not interrupt driven). If
+a response is been received within a time frame, the data is been extracted
+from the message and then passed to the caller (clients).
+
+ Supported chips:
+ * Advantech EC based on ITE IT8518
+ Prefix: imanager
+ Addresses: 0x029e/0x029f
+ Datasheet: Available from ITE upon request
+ * Advantech EC based on ITE IT8528
+ Prefix: imanager
+ Addresses: 0x0299/0x29a
+ Datasheet: Available from ITE upon request
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 233f834..8d25fdc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5391,6 +5391,19 @@ M: Stanislaw Gruszka <stf_xl-5tc4TXWwyLM@public.gmane.org>
S: Maintained
F: drivers/usb/atm/ueagle-atm.c
+IMANAGER ADVANTECH EC DRIVER
+M: Richard Vidal-Dorsch <richard.dorsch-ELdSlb/RfAS1Z/+hSey0Gg@public.gmane.org>
+S: Maintained
+F: Documentation/devicetree/bindings/mfd/imanager.txt
+F: Documentation/hwmon/imanager
+F: Documentation/i2c/busses/i2c-imanager
+F: drivers/mfd/imanager-core.c
+F: drivers/gpio/imanager-gpio.c
+F: drivers/hwmon/imanager-hwmon.c
+F: drivers/i2c/busses/imanager-i2c.c
+F: drivers/video/backlight/imanager-bl.c
+F: drivers/watchdog/imanager-wdt.c
+
INA209 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org>
L: lm-sensors-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4d92df6..4dc7c13 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -322,6 +322,26 @@ config MFD_INTEL_QUARK_I2C_GPIO
their respective IO driver.
The GPIO exports a total amount of 8 interrupt-capable GPIOs.
+config MFD_IMANAGER
+ tristate "Advantech iManager Embedded Controller"
+ depends on PCI
+ depends on X86
+ select MFD_CORE
+ help
+ This is the core driver for Advantech iManager EC as found on some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards. The EC may provide
+ functions like GPIO, I2C interface, HW monitoring, Watchdog, and
+ backlight/brightness control.
+
+ The following Advantech boards are supported:
+ * All SOM modules newer than SOM-5788
+ * MIO-5250/5251/5270/5271 and newer
+ * PCM-9388
+ * AIMB-274/231
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager.
+
config LPC_ICH
tristate "Intel ICH LPC"
depends on PCI
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a8b76b8..c9c63d9 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -142,6 +142,8 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
+imanager-objs := imanager-core.o imanager-ec.o
+obj-$(CONFIG_MFD_IMANAGER) += imanager.o
obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o
obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
diff --git a/drivers/mfd/imanager-core.c b/drivers/mfd/imanager-core.c
new file mode 100644
index 0000000..9697c144
--- /dev/null
+++ b/drivers/mfd/imanager-core.c
@@ -0,0 +1,288 @@
+/*
+ * Advantech iManager MFD core driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/ec.h>
+
+static struct platform_device *pdev;
+
+enum imanager_cells {
+ IMANAGER_BL,
+ IMANAGER_GPIO,
+ IMANAGER_HWMON,
+ IMANAGER_I2C,
+ IMANAGER_WDT,
+};
+
+static const char * const chip_names[] = {
+ "it8516",
+ "it8518",
+ "it8528",
+ NULL
+};
+
+static struct resource imanager_ioresource = {
+ .start = IT8516_DAT_PORT,
+ .end = IT8518_DAT_PORT,
+ .flags = IORESOURCE_IO,
+};
+
+/*
+ * Devices which are part of the iManager and are available via firmware.
+ */
+static struct mfd_cell imanager_devs[] = {
+ [IMANAGER_BL] = {
+ .name = "imanager_backlight",
+ },
+ [IMANAGER_GPIO] = {
+ .name = "imanager_gpio",
+ },
+ [IMANAGER_HWMON] = {
+ .name = "imanager_hwmon",
+ },
+ [IMANAGER_I2C] = {
+ .name = "imanager_i2c",
+ },
+ [IMANAGER_WDT] = {
+ .name = "imanager_wdt",
+ },
+};
+
+const char *project_type_to_str(int type)
+{
+ const char *version_type;
+
+ switch (type) {
+ case 'V':
+ version_type = "Release";
+ break;
+ case 'X':
+ version_type = "Engineering Sample";
+ break;
+ case 'A' ... 'U':
+ version_type = "Custom";
+ break;
+ default:
+ version_type = "Unknown";
+ break;
+ }
+
+ return version_type;
+}
+
+static ssize_t imanager_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", info->pcb_name);
+}
+
+static ssize_t imanager_kversion_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n",
+ info->version.kernel_major,
+ info->version.kernel_minor);
+}
+
+static ssize_t imanager_fwversion_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n",
+ info->version.firmware_major,
+ info->version.firmware_minor);
+}
+
+static ssize_t imanager_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ project_type_to_str(info->version.type));
+}
+
+static ssize_t imanager_chip_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", pdata->chip_name);
+}
+
+static DEVICE_ATTR(imanager_name, S_IRUGO, imanager_name_show, NULL);
+static DEVICE_ATTR(imanager_kversion, S_IRUGO, imanager_kversion_show, NULL);
+static DEVICE_ATTR(imanager_fwversion, S_IRUGO, imanager_fwversion_show, NULL);
+static DEVICE_ATTR(imanager_type, S_IRUGO, imanager_type_show, NULL);
+static DEVICE_ATTR(imanager_chip_name, S_IRUGO, imanager_chip_name_show, NULL);
+
+static struct attribute *imanager_core_attributes[] = {
+ &dev_attr_imanager_name.attr,
+ &dev_attr_imanager_kversion.attr,
+ &dev_attr_imanager_fwversion.attr,
+ &dev_attr_imanager_type.attr,
+ &dev_attr_imanager_chip_name.attr,
+ NULL
+};
+
+static const struct attribute_group imanager_core_attr_group = {
+ .attrs = imanager_core_attributes,
+};
+
+static int imanager_platform_create(void)
+{
+ struct device *dev;
+ struct imanager_platform_data platdata;
+ int err;
+
+ pdev = platform_device_alloc("imanager-core", -1);
+ if (!pdev)
+ return -ENOMEM;
+
+ dev = &pdev->dev;
+
+ err = platform_device_add_data(pdev, &platdata, sizeof(platdata));
+ if (err)
+ goto exit_device_put;
+
+ err = platform_device_add_resources(pdev, &imanager_ioresource, 1);
+ if (err)
+ goto exit_device_put;
+
+ err = platform_device_add(pdev);
+ if (err)
+ goto exit_device_put;
+
+ err = mfd_add_devices(dev, pdev->id, imanager_devs,
+ ARRAY_SIZE(imanager_devs), NULL, -1, NULL);
+ if (err)
+ goto exit_device_unregister;
+
+ return 0;
+
+exit_device_unregister:
+ platform_device_unregister(pdev);
+exit_device_put:
+ platform_device_put(pdev);
+
+ return err;
+}
+
+static int imanager_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ struct imanager_device_data *imanager;
+ int ret;
+
+ if (!pdev)
+ return -EINVAL;
+
+ imanager = devm_kzalloc(dev, sizeof(*imanager), GFP_KERNEL);
+ if (!imanager)
+ return -ENOMEM;
+
+ imanager->dev = dev;
+ mutex_init(&imanager->lock);
+
+ platform_set_drvdata(pdev, imanager);
+
+ pdata->dev = imanager_get_ec_device();
+ pdata->chip_name = chip_names[pdata->dev->id];
+
+ dev_info(dev, "Found Advantech iManager %s - %s %d.%d/%d.%d (%s)\n",
+ pdata->chip_name,
+ pdata->dev->info.pcb_name,
+ pdata->dev->info.version.kernel_major,
+ pdata->dev->info.version.kernel_minor,
+ pdata->dev->info.version.firmware_major,
+ pdata->dev->info.version.firmware_minor,
+ project_type_to_str(pdata->dev->info.version.type));
+
+ ret = sysfs_create_group(&dev->kobj, &imanager_core_attr_group);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imanager_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ sysfs_remove_group(&dev->kobj, &imanager_core_attr_group);
+
+ mfd_remove_devices(dev);
+
+ return 0;
+}
+
+static struct platform_driver imanager_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imanager-core",
+ },
+ .probe = imanager_probe,
+ .remove = imanager_remove,
+};
+
+static int __init imanager_init(void)
+{
+ int ret;
+
+ ret = imanager_ec_probe();
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_platform_create();
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&imanager_driver);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void __exit imanager_exit(void)
+{
+ if (pdev)
+ platform_device_unregister(pdev);
+
+ platform_driver_unregister(&imanager_driver);
+}
+
+module_init(imanager_init);
+module_exit(imanager_exit);
+
+MODULE_DESCRIPTION("Advantech iManager Core Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-core");
diff --git a/drivers/mfd/imanager-ec.c b/drivers/mfd/imanager-ec.c
new file mode 100644
index 0000000..be554ba
--- /dev/null
+++ b/drivers/mfd/imanager-ec.c
@@ -0,0 +1,1345 @@
+/*
+ * Advantech iManager Core - Firmware Interface
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/module.h>
+#include <linux/swab.h>
+#include <linux/mfd/imanager/ec.h>
+
+/**
+ * This is the delay time between two EC transactions.
+ * Values lower than 200us are not encouraged and may
+ * cause I/O errors
+ */
+#define EC_MICRO_DELAY 200
+#define EC_MAX_RETRY 1000
+
+#define EC_MSG_OFFSET_CMD 0UL
+#define EC_MSG_OFFSET_STATUS 1UL
+#define EC_MSG_OFFSET_PARAM 2UL
+#define EC_MSG_OFFSET_DATA(N) (3UL + N)
+#define EC_MSG_OFFSET_PAYLOAD(N) (7UL + N)
+
+/* The Device ID registers - 16 bit */
+#define DEVID_REG_MSB 0x20
+#define DEVID_REG_LSB 0x21
+
+/*
+ * IT8528 based firmware require a read/write command offset.
+ */
+#define EC_CMD_OFFSET_READ 0xA0UL
+#define EC_CMD_OFFSET_WRITE 0x50UL
+
+#define EC_STATUS_SUCCESS BIT(0)
+#define EC_STATUS_CMD_COMPLETE BIT(7)
+
+#define PCB_NAME_MAX_SIZE 8UL
+#define EC_I2C_BLOCK_SIZE 32
+#define EC_MAX_DID 32UL
+
+#define DID_LABEL_SIZE 24UL
+#define DID_DESC_SIZE 32UL
+
+#define EC_KERNEL_MINOR(x) (LOBYTE16(x))
+#define EC_KERNEL_MAJOR(x) ({ \
+ typeof(x) __x = (HIBYTE16(x)); \
+ ((__x >> 4) * 10 + (__x & 0x000f)); })
+#define EC_FIRMWARE_MINOR(x) (LOBYTE16(x))
+#define EC_FIRMWARE_MAJOR(x) EC_KERNEL_MAJOR(x)
+#define EC_PROJECT_CODE(x) ((char)(LOBYTE16(x)))
+
+enum ec_device_type {
+ ADC = 1,
+ DAC,
+ GPIO,
+ IRQ,
+ PWM,
+ SMB,
+ TACH
+};
+
+enum ec_device_id {
+ /* GPIO */
+ ALTGPIO0 = 0x10,
+ ALTGPIO1,
+ ALTGPIO2,
+ ALTGPIO3,
+ ALTGPIO4,
+ ALTGPIO5,
+ ALTGPIO6,
+ ALTGPIO7,
+ /* Button (GPIO) */
+ BUTTON0,
+ BUTTON1,
+ BUTTON2,
+ BUTTON3,
+ BUTTON4,
+ BUTTON5,
+ BUTTON6,
+ BUTTON7,
+ /* FAN */
+ CPUFAN_2P,
+ CPUFAN_4P,
+ SYSFAN1_2P,
+ SYSFAN1_4P,
+ SYSFAN2_2P,
+ SYSFAN2_4P,
+ /* Brightness Control */
+ BRIGHTNESS,
+ /* System Speaker */
+ PCBEEP,
+ /* SMBus */
+ SMBOEM0,
+ SMBOEM1,
+ SMBOEM2,
+ SMBEEPROM,
+ SMBTHERMAL0,
+ SMBTHERMAL1,
+ SMBSECEEP,
+ I2COEM,
+ /* Speaker */
+ SPEAKER = 0x30,
+ /* SMBus */
+ SMBEEP2K = 0x38,
+ OEMEEP,
+ OEMEEP2K,
+ PECI,
+ SMBOEM3,
+ SMLINK,
+ SMBSLV,
+ /* LED */
+ POWERLED = 0x40,
+ BATLEDG,
+ OEMLED0,
+ OEMLED1,
+ OEMLED2,
+ BATLEDR,
+ /* Smart Battery */
+ SMARTBAT1 = 0x48,
+ SMARTBAT2,
+ /* ADC */
+ CMOSBAT = 0x50,
+ CMOSBATx2,
+ CMOSBATx10,
+ LIBAT,
+ LIBATx2,
+ LIBATx10,
+ ADC5VS0,
+ ADC5VS0x2,
+ ADC5VS0x10,
+ ADC5VS5,
+ ADC5VS5x2,
+ ADC5VS5x10,
+ ADC33VS0,
+ ADC33VS0x2,
+ ADC33VS0x10,
+ ADC33VS5,
+ ADC33VS5x2, /* 0x60 */
+ ADC33VS5x10,
+ ADC12VS0,
+ ADC12VS0x2,
+ ADC12VS0x10,
+ VCOREA,
+ VCOREAx2,
+ VCOREAx10,
+ VCOREB,
+ VCOREBx2,
+ VCOREBx10,
+ ADCDC,
+ ADCDCx2,
+ ADCDCx10,
+ VSTBY,
+ VSTBYx2,
+ VSTBYx10, /* 0x70 */
+ VAUX,
+ VAUXx2,
+ VAUXx10,
+ CURRENT,
+ /* Watchdog */
+ WDIRQ = 0x78,
+ WDNMI,
+ /* FAN Tacho */
+ TACHO0 = 0x80,
+ TACHO1,
+ TACHO2,
+ /* Brightness/Backlight Control */
+ BRIGHTNESS2 = 0x88,
+ BACKLIGHT1,
+ BACKLIGHT2
+};
+
+enum ec_dynamic_table_type {
+ EC_DYN_DID,
+ EC_DYN_HWP,
+ EC_DYN_POL
+};
+
+struct ec_devtbl {
+ int id;
+ int type;
+ int scale;
+ const char label[DID_LABEL_SIZE];
+ const char description[DID_DESC_SIZE];
+};
+
+struct ec_dyn_devtbl {
+ int did; /* Device ID */
+ int hwp; /* Hardware Pin */
+ int pol; /* Polarity */
+ const struct ec_devtbl *devtbl; /* Device table Entry */
+};
+
+struct imanager_data {
+ int (*read)(int cmd);
+ int (*write)(int cmd, int value);
+
+ struct ec_dyn_devtbl dyn[EC_MAX_DID];
+
+ struct imanager_ec_device dev;
+ struct imanager_hwmon_device sensors;
+ struct imanager_gpio_device gpio;
+ struct imanager_i2c_device i2c;
+ struct imanager_watchdog_device wdt;
+ struct imanager_backlight_device blc;
+};
+
+struct ec_version_raw {
+ u16 kernel,
+ chipid,
+ project_code,
+ firmware;
+};
+
+enum ec_ram_type {
+ EC_RAM_ACPI = 1,
+ EC_RAM_HW,
+ EC_RAM_EXT
+};
+
+static struct imanager_data ec;
+
+static const struct ec_devtbl devtbl[] = {
+ { ALTGPIO0, GPIO, -1, "gpio0" },
+ { ALTGPIO1, GPIO, -1, "gpio1" },
+ { ALTGPIO2, GPIO, -1, "gpio2" },
+ { ALTGPIO3, GPIO, -1, "gpio3" },
+ { ALTGPIO4, GPIO, -1, "gpio4" },
+ { ALTGPIO5, GPIO, -1, "gpio5" },
+ { ALTGPIO6, GPIO, -1, "gpio6" },
+ { ALTGPIO7, GPIO, -1, "gpio7" },
+ { BUTTON0, GPIO, -1, "button0" },
+ { BUTTON1, GPIO, -1, "button1" },
+ { BUTTON2, GPIO, -1, "button2" },
+ { BUTTON3, GPIO, -1, "button3" },
+ { BUTTON4, GPIO, -1, "button4" },
+ { BUTTON5, GPIO, -1, "button4" },
+ { BUTTON6, GPIO, -1, "button4" },
+ { BUTTON7, GPIO, -1, "button4" },
+ { CPUFAN_2P, PWM, 2, "FAN CPU" },
+ { CPUFAN_4P, PWM, 4, "FAN CPU" },
+ { SYSFAN1_2P, PWM, 2, "FAN SYS1" },
+ { SYSFAN1_4P, PWM, 4, "FAN SYS1" },
+ { SYSFAN2_2P, PWM, 2, "FAN SYS2" },
+ { SYSFAN2_4P, PWM, 4, "FAN SYS2" },
+ { BRIGHTNESS, PWM, -1, "Brightness1" },
+ { PCBEEP, PWM, -1, "Beep" },
+ { SMBOEM0, SMB, -1, "SMB1" },
+ { SMBOEM1, SMB, -1, "SMB2" },
+ { SMBOEM2, SMB, -1, "SMB3" },
+ { SMBEEPROM, SMB, -1, "SMBEEP" },
+ { SMBTHERMAL0, SMB, -1, "SMBTHERM0" },
+ { SMBTHERMAL1, SMB, -1, "SMBTHERM1" },
+ { SMBSECEEP, SMB, -1, "SMBSECEEP" },
+ { I2COEM, SMB, -1, "I2COEM" },
+ { SPEAKER, DAC, -1, "Speaker" },
+ { SMBEEP2K, SMB, -1, "SMBEEP2K" },
+ { OEMEEP, SMB, -1, "OEMEEP" },
+ { OEMEEP2K, SMB, -1, "OEMEEP2K" },
+ { PECI, SMB, -1, "SMB_PECI" },
+ { SMBOEM3, SMB, -1, "SMBOEM3" },
+ { SMLINK, SMB, -1, "SMLINK" },
+ { SMBSLV, SMB, -1, "SMBSLV" },
+ { POWERLED, GPIO, -1, "Power LED" },
+ { BATLEDG, GPIO, -1, "BATLEDG" },
+ { OEMLED0, GPIO, -1, "OEMLED0" },
+ { OEMLED1, GPIO, -1, "OEMLED1" },
+ { OEMLED2, GPIO, -1, "OEMLED2" },
+ { BATLEDR, GPIO, -1, "OEMLEDR" },
+ { SMARTBAT1, SMB, -1, "SmartBat1" },
+ { SMARTBAT2, SMB, -1, "SmartBat2" },
+ { CMOSBAT, ADC, 1, "VBat" },
+ { CMOSBATx2, ADC, 2, "VBat" },
+ { CMOSBATx10, ADC, 10, "VBat" },
+ { LIBAT, ADC, 1, "VBat2" },
+ { LIBATx2, ADC, 2, "VBat2" },
+ { LIBATx10, ADC, 10, "VBat2" },
+ { ADC5VS0, ADC, 1, "+5V" },
+ { ADC5VS0x2, ADC, 2, "+5V" },
+ { ADC5VS0x10, ADC, 10, "+5V" },
+ { ADC5VS5, ADC, 1, "+5V" },
+ { ADC5VS5x2, ADC, 2, "+5V" },
+ { ADC5VS5x10, ADC, 10, "+5V" },
+ { ADC33VS0, ADC, 1, "+3.3V" },
+ { ADC33VS0x2, ADC, 2, "+3.3V" },
+ { ADC33VS0x10, ADC, 10, "+3.3V" },
+ { ADC33VS5, ADC, 1, "+3.3V" },
+ { ADC33VS5x2, ADC, 2, "+3.3V" },
+ { ADC33VS5x10, ADC, 10, "+3.3V" },
+ { ADC12VS0, ADC, 1, "+12V" },
+ { ADC12VS0x2, ADC, 2, "+12V" },
+ { ADC12VS0x10, ADC, 10, "+12V" },
+ { VCOREA, ADC, 1, "VCore" },
+ { VCOREAx2, ADC, 2, "VCore" },
+ { VCOREAx10, ADC, 10, "VCore" },
+ { VCOREB, ADC, 1, "VCore2" },
+ { VCOREBx2, ADC, 2, "VCore2" },
+ { VCOREBx10, ADC, 10, "VCore2" },
+ { ADCDC, ADC, 1, "ADCDC" },
+ { ADCDCx2, ADC, 2, "ADCDCx2" },
+ { ADCDCx10, ADC, 10, "ADCDCx10" },
+ { VSTBY, ADC, 1, "Vsb" },
+ { VSTBYx2, ADC, 2, "Vsb" },
+ { VSTBYx10, ADC, 10, "Vsb" },
+ { VAUX, ADC, 1, "VAUX" },
+ { VAUXx2, ADC, 2, "VAUX" },
+ { VAUXx10, ADC, 10, "VAUX" },
+ { CURRENT, ADC, 1, "Imon" },
+ { WDIRQ, IRQ, -1, "WDIRQ" },
+ { WDNMI, GPIO, -1, "WDNMI" },
+ { TACHO0, TACH, -1, "Tacho1" },
+ { TACHO1, TACH, -1, "Tacho2" },
+ { TACHO2, TACH, -1, "Tacho3" },
+ { BRIGHTNESS2, PWM, -1, "Brightness2" },
+ { BACKLIGHT1, GPIO, -1, "Backlight1" },
+ { BACKLIGHT2, GPIO, -1, "Backlight2" },
+ { 0, 0, 0, "" }
+};
+
+/**
+ * EC I/O
+ */
+
+static inline void imanager_delay(void)
+{
+ udelay(EC_MICRO_DELAY);
+}
+
+static int wait_ibf_cleared(void)
+{
+ int i = 0;
+
+ do {
+ if (!(inb(IT8516_CMD_PORT) & BIT(1)))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+static int wait_obf_set(void)
+{
+ int i = 0;
+
+ do {
+ if (inb(IT8516_CMD_PORT) & BIT(0))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+static inline int ec_inb(int addr, int reg)
+{
+ outb(reg, addr);
+ return inb(addr + 1);
+}
+
+static inline void ec_outb(int addr, int reg, int val)
+{
+ outb(reg, addr);
+ outb(val, addr + 1);
+}
+
+static inline int ec_io28_inb(int addr, int reg)
+{
+ int ret;
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ /* clear data to prevent lock */
+ inb(addr - 1);
+
+ outb(reg, addr);
+
+ ret = wait_obf_set();
+ if (ret)
+ return ret;
+
+ return inb(addr - 1);
+}
+
+static inline int ec_io28_outb(int addr, int reg, int val)
+{
+ int ret;
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ outb(reg, addr);
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ outb(val, addr - 1);
+
+ return 0;
+}
+
+static int ec_io18_read(int cmd)
+{
+ return ec_inb(IT8518_CMD_PORT, cmd);
+}
+
+static int ec_io18_write(int cmd, int value)
+{
+ ec_outb(IT8518_CMD_PORT, cmd, value);
+
+ return 0;
+}
+
+static int ec_io28_read(int cmd)
+{
+ return ec_io28_inb(IT8516_CMD_PORT, cmd + EC_CMD_OFFSET_READ);
+}
+
+static int ec_io28_write(int cmd, int value)
+{
+ return ec_io28_outb(IT8516_CMD_PORT, cmd + EC_CMD_OFFSET_WRITE, value);
+}
+
+/* Prevent FW lock */
+static void ec_clear_ports(void)
+{
+ inb(IT8516_DAT_PORT);
+ inb(IT8518_DAT_PORT);
+}
+
+static inline u16 ec_read_chipid(u16 addr)
+{
+ return (ec_inb(addr, DEVID_REG_MSB) << 8 |
+ ec_inb(addr, DEVID_REG_LSB));
+}
+
+static int ec_wait_cmd_clear(void)
+{
+ int i = 0;
+
+ do {
+ if (!ec.read(0))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ pr_err("No respons from EC (timeout)\n");
+
+ return -ETIME;
+}
+
+static int ec_read_ram(u8 bank, u8 offset, u8 len, u8 *buf, u8 bufsz)
+{
+ int i;
+ int ret;
+
+ if (WARN_ON(!buf))
+ return -EINVAL;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, bank);
+ ec.write(EC_MSG_OFFSET_DATA(0), offset);
+ ec.write(EC_MSG_OFFSET_DATA(0x2C), len);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (i = 0; (i < len) && (len < EC_MSG_SIZE) && (len <= bufsz); i++)
+ buf[i] = ec.read(EC_MSG_OFFSET_DATA(i + 1));
+
+ return 0;
+}
+
+static int ec_write_ram(u8 bank, u8 offset, u8 len, u8 *buf)
+{
+ int i;
+ int ret;
+
+ if (WARN_ON(!buf))
+ return -EINVAL;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, bank);
+ ec.write(EC_MSG_OFFSET_DATA(0), offset);
+ ec.write(EC_MSG_OFFSET_DATA(0x2C), len);
+
+ for (i = 0; (i < len) && (len < EC_MSG_SIZE); i++)
+ ec.write(EC_MSG_OFFSET_DATA(i + 1), buf[i]);
+
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_WR);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ return 0;
+}
+
+static int ec_read_dynamic_devtbl(struct imanager_data *ec)
+{
+ u32 i, j;
+ int ret;
+ struct ec_message did = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_message hwp = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_message pol = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_dyn_devtbl *dyn;
+
+ memset(ec->dyn, 0, sizeof(ec->dyn));
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_DID, &did);
+ if (ret)
+ return -EIO;
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_HWP, &hwp);
+ if (ret)
+ return -EIO;
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_POL, &pol);
+ if (ret)
+ return -EIO;
+
+ for (i = 0; (i < EC_MAX_DID) && did.u.data[i]; i++) {
+ dyn = &ec->dyn[i];
+ for (j = 0; j < ARRAY_SIZE(devtbl); j++) {
+ if (devtbl[j].id == did.u.data[i]) {
+ dyn->did = did.u.data[i];
+ dyn->hwp = hwp.u.data[i];
+ dyn->pol = pol.u.data[i];
+ dyn->devtbl = &devtbl[j];
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_read_buffer(u8 *data, int rlen)
+{
+ int ret, i, j;
+ int pages = rlen % EC_I2C_BLOCK_SIZE;
+ int rem = rlen / EC_I2C_BLOCK_SIZE;
+
+ /* pre-condition: rlen <= 256 */
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ for (i = 0; i < pages; i++) {
+ ec.write(EC_MSG_OFFSET_PARAM, i);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_BUF_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (j = 0; j < EC_I2C_BLOCK_SIZE; j++)
+ data[i * EC_I2C_BLOCK_SIZE + j] =
+ ec.read(EC_MSG_OFFSET_DATA(j));
+ }
+
+ if (rem) {
+ ec.write(EC_MSG_OFFSET_PARAM, pages);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_BUF_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (j = 0; j < rem; j++)
+ data[pages * EC_I2C_BLOCK_SIZE + j] =
+ ec.read(EC_MSG_OFFSET_DATA(j));
+ }
+
+ return 0;
+}
+
+static int
+imanager_msg_trans(u8 cmd, u8 param, struct ec_message *msg, bool payload)
+{
+ int ret, i, len;
+ u32 offset;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, param);
+
+ if (msg && msg->wlen) {
+ if (!msg->data) {
+ for (i = 0; i < msg->wlen; i++)
+ ec.write(EC_MSG_OFFSET_DATA(i),
+ msg->u.data[i]);
+ } else {
+ for (i = 0; i < msg->wlen; i++)
+ ec.write(EC_MSG_OFFSET_DATA(i), msg->data[i]);
+ ec.write(EC_MSG_OFFSET_DATA(0x2c), msg->wlen);
+ }
+ }
+
+ ec.write(EC_MSG_OFFSET_CMD, cmd);
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ /* GPIO and I2C have different success return values */
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if ((ret != EC_STATUS_SUCCESS) && !(ret & EC_STATUS_CMD_COMPLETE))
+ return -EIO;
+ /*
+ * EC I2C may return an error code which we need to hand-off
+ * to the caller
+ */
+ else if (ret & 0x07e)
+ return ret;
+
+ if (msg && msg->data) {
+ ret = ec_read_buffer(msg->data, msg->rlen);
+ if (ret < 0)
+ return ret;
+ } else if (msg && msg->rlen) {
+ if (msg->rlen == 0xff)
+ /* Use alternate message body for hwmon */
+ len = ec.read(EC_MSG_OFFSET_DATA(0x2C));
+ else
+ len = (msg->rlen > EC_MSG_SIZE ? EC_MSG_SIZE :
+ msg->rlen);
+ offset = payload ? EC_MSG_OFFSET_PAYLOAD(0) :
+ EC_MSG_OFFSET_DATA(0);
+ for (i = 0; i < len; i++)
+ msg->u.data[i] = ec.read(offset + i);
+ }
+
+ return 0;
+}
+
+int imanager_msg_read(u8 cmd, u8 param, struct ec_message *msg)
+{
+ return imanager_msg_trans(cmd, param, msg, false);
+}
+EXPORT_SYMBOL_GPL(imanager_msg_read);
+
+int imanager_msg_write(u8 cmd, u8 param, struct ec_message *msg)
+{
+ return imanager_msg_trans(cmd, param, msg, true);
+}
+EXPORT_SYMBOL_GPL(imanager_msg_write);
+
+int imanager_read_byte(u8 cmd, u8 param)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = 0,
+ };
+
+ ret = imanager_msg_read(cmd, param, &msg);
+ if (ret)
+ return ret;
+
+ return msg.u.data[0];
+}
+EXPORT_SYMBOL_GPL(imanager_read_byte);
+
+int imanager_read_word(u8 cmd, u8 param)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 2,
+ .wlen = 0,
+ };
+
+ ret = imanager_msg_read(cmd, param, &msg);
+ if (ret)
+ return ret;
+
+ return (msg.u.data[0] << 8 | msg.u.data[1]);
+}
+EXPORT_SYMBOL_GPL(imanager_read_word);
+
+int imanager_write_byte(u8 cmd, u8 param, u8 byte)
+{
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = 1,
+ .u = {
+ .data = { byte, 0 },
+ },
+ };
+
+ return imanager_msg_write(cmd, param, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write_byte);
+
+int imanager_write_word(u8 cmd, u8 param, u16 word)
+{
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = 2,
+ .u = {
+ .data = { HIBYTE16(word), LOBYTE16(word), 0 },
+ },
+ };
+
+ return imanager_msg_write(cmd, param, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write_word);
+
+static int ec_hwram_read_byte(u8 offset)
+{
+ int ret;
+ u8 val;
+
+ ret = ec_read_ram(EC_RAM_HW, offset, sizeof(val), &val, sizeof(val));
+ if (ret < 0) {
+ pr_err("Failed to read from HWRAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return val;
+}
+
+int imanager_acpiram_read_byte(u8 offset)
+{
+ int ret;
+ u8 value;
+
+ ret = ec_read_ram(EC_RAM_ACPI, offset, sizeof(value), (u8 *)&value,
+ sizeof(value));
+ if (ret < 0) {
+ pr_err("Failed to read from ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return value;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_read_byte);
+
+int imanager_acpiram_write_byte(u8 offset, u8 value)
+{
+ int ret;
+
+ ret = ec_write_ram(EC_RAM_ACPI, offset, sizeof(value), &value);
+ if (ret) {
+ pr_err("Failed to write to ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_write_byte);
+
+int imanager_acpiram_read_block(u8 offset, u8 *buf, u8 len)
+{
+ int ret;
+
+ ret = ec_read_ram(EC_RAM_ACPI, offset, len, buf, len);
+ if (ret < 0) {
+ pr_err("Failed to read from ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_read_block);
+
+int imanager_acpiram_write_block(u8 offset, u8 *buf, u8 len)
+{
+ int ret;
+
+ ret = ec_write_ram(EC_RAM_ACPI, offset, len, buf);
+ if (ret) {
+ pr_err("Failed to write to ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_write_block);
+
+int imanager_wait_proc_complete(u8 offset, int cond)
+{
+ int ret, i;
+
+ for (i = 0; i < EC_MAX_RETRY; i++) {
+ ret = ec_hwram_read_byte(offset);
+ if (ret < 0)
+ return ret;
+
+ if (ret == cond)
+ return 0;
+
+ imanager_delay();
+ }
+
+ return -EIO;
+}
+EXPORT_SYMBOL_GPL(imanager_wait_proc_complete);
+
+static inline void ec_get_dev_attr(struct ec_dev_attr *attr,
+ const struct ec_dyn_devtbl *tbl)
+{
+ attr->did = tbl->did;
+ attr->hwp = tbl->hwp;
+ attr->pol = tbl->pol;
+ attr->scale = tbl->devtbl->scale;
+ attr->label = tbl->devtbl->label;
+}
+
+static int ec_get_dev_gpio(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_gpio_device *gpio = &ec->gpio;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == GPIO)) {
+ switch (dyn->did) {
+ case ALTGPIO0:
+ ec_get_dev_attr(&gpio->attr[0], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO1:
+ ec_get_dev_attr(&gpio->attr[1], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO2:
+ ec_get_dev_attr(&gpio->attr[2], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO3:
+ ec_get_dev_attr(&gpio->attr[3], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO4:
+ ec_get_dev_attr(&gpio->attr[4], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO5:
+ ec_get_dev_attr(&gpio->attr[5], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO6:
+ ec_get_dev_attr(&gpio->attr[6], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO7:
+ ec_get_dev_attr(&gpio->attr[7], dyn);
+ gpio->num++;
+ break;
+ case BUTTON0:
+ case BUTTON1:
+ case BUTTON2:
+ case BUTTON3:
+ case BUTTON4:
+ case BUTTON5:
+ case BUTTON6:
+ case BUTTON7:
+ case POWERLED:
+ case BATLEDG:
+ case OEMLED0:
+ case OEMLED1:
+ case OEMLED2:
+ case BATLEDR:
+ case WDNMI:
+ case BACKLIGHT1:
+ case BACKLIGHT2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ gpio->info = &ec->dev.info;
+
+ return 0;
+}
+
+static int ec_get_dev_adc(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct dev_adc *adc = &ec->sensors.adc;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == ADC)) {
+ switch (dyn->did) {
+ case ADC12VS0:
+ case ADC12VS0x2:
+ case ADC12VS0x10:
+ ec_get_dev_attr(&adc->attr[0], dyn);
+ adc->num++;
+ break;
+ case ADC5VS5:
+ case ADC5VS5x2:
+ case ADC5VS5x10:
+ ec_get_dev_attr(&adc->attr[1], dyn);
+ adc->num++;
+ break;
+ case CMOSBAT:
+ case CMOSBATx2:
+ case CMOSBATx10:
+ ec_get_dev_attr(&adc->attr[2], dyn);
+ adc->num++;
+ break;
+ case VCOREA:
+ case ADC5VS0:
+ case ADC5VS0x2:
+ case ADC5VS0x10:
+ ec_get_dev_attr(&adc->attr[3], dyn);
+ adc->num++;
+ break;
+ case CURRENT:
+ case ADC33VS0:
+ case ADC33VS0x2:
+ case ADC33VS0x10:
+ ec_get_dev_attr(&adc->attr[4], dyn);
+ adc->num++;
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_get_dev_fan(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct dev_fan *fan = &ec->sensors.fan;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && ((dyn->devtbl->type == TACH) ||
+ (dyn->devtbl->type == PWM))) {
+ switch (dyn->did) {
+ case CPUFAN_2P:
+ case CPUFAN_4P:
+ ec_get_dev_attr(&fan->attr[0], dyn);
+ fan->num++;
+ fan->active |= 1 << 0;
+ break;
+ case SYSFAN1_2P:
+ case SYSFAN1_4P:
+ ec_get_dev_attr(&fan->attr[1], dyn);
+ fan->num++;
+ fan->active |= 1 << 1;
+ break;
+ case SYSFAN2_2P:
+ case SYSFAN2_4P:
+ ec_get_dev_attr(&fan->attr[2], dyn);
+ fan->num++;
+ fan->active |= 1 << 2;
+ break;
+ case TACHO0:
+ case TACHO1:
+ case TACHO2:
+ case BRIGHTNESS:
+ case BRIGHTNESS2:
+ case PCBEEP:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_get_dev_hwmon(struct imanager_data *ec)
+{
+ int ret;
+
+ ret = ec_get_dev_adc(ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_fan(ec);
+ if (ret < 0)
+ return ret;
+
+ ec->sensors.ecdev = &ec->dev;
+
+ return 0;
+}
+
+static int ec_get_dev_i2c(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_i2c_device *i2c = &ec->i2c;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == SMB)) {
+ switch (dyn->did) {
+ case SMBEEPROM:
+ ec_get_dev_attr(&i2c->attr[0], dyn);
+ i2c->eeprom = &i2c->attr[0];
+ i2c->num++;
+ break;
+ case I2COEM:
+ ec_get_dev_attr(&i2c->attr[1], dyn);
+ i2c->i2coem = &i2c->attr[1];
+ i2c->num++;
+ break;
+ case SMBOEM0:
+ case SMBOEM1:
+ case SMBOEM2:
+ case SMBTHERMAL0:
+ case SMBTHERMAL1:
+ case SMBSECEEP:
+ case SMBEEP2K:
+ case OEMEEP:
+ case OEMEEP2K:
+ case PECI:
+ case SMBOEM3:
+ case SMLINK:
+ case SMBSLV:
+ case SMARTBAT1:
+ case SMARTBAT2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ i2c->ecdev = &ec->dev;
+
+ return 0;
+}
+
+static int ec_get_dev_blc(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_backlight_device *blc = &ec->blc;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == PWM)) {
+ switch (dyn->did) {
+ case BRIGHTNESS:
+ ec_get_dev_attr(&blc->attr[0], dyn);
+ blc->brightness[0] = EC_ACPIRAM_BRIGHTNESS1;
+ blc->num++;
+ break;
+ case BRIGHTNESS2:
+ ec_get_dev_attr(&blc->attr[1], dyn);
+ blc->brightness[1] = EC_ACPIRAM_BRIGHTNESS2;
+ blc->num++;
+ break;
+ case CPUFAN_2P:
+ case CPUFAN_4P:
+ case SYSFAN1_2P:
+ case SYSFAN1_4P:
+ case SYSFAN2_2P:
+ case SYSFAN2_4P:
+ case PCBEEP:
+ case TACHO0:
+ case TACHO1:
+ case TACHO2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ blc->info = &ec->dev.info;
+
+ return 0;
+}
+
+static int ec_get_dev_wdt(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_watchdog_device *wdt = &ec->wdt;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == IRQ)) {
+ switch (dyn->did) {
+ case WDIRQ:
+ ec_get_dev_attr(&wdt->attr[0], dyn);
+ wdt->irq = &wdt->attr[0];
+ wdt->num++;
+ break;
+ case WDNMI:
+ ec_get_dev_attr(&wdt->attr[1], dyn);
+ wdt->nmi = &wdt->attr[1];
+ wdt->num++;
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ wdt->info = &ec->dev.info;
+
+ return 0;
+}
+
+const struct imanager_ec_device *imanager_get_ec_device(void)
+{
+ return &ec.dev;
+};
+EXPORT_SYMBOL_GPL(imanager_get_ec_device);
+
+const struct imanager_hwmon_device *imanager_get_hwmon_device(void)
+{
+ return &ec.sensors;
+}
+EXPORT_SYMBOL_GPL(imanager_get_hwmon_device);
+
+const struct imanager_gpio_device *imanager_get_gpio_device(void)
+{
+ return &ec.gpio;
+}
+EXPORT_SYMBOL_GPL(imanager_get_gpio_device);
+
+const struct imanager_i2c_device *imanager_get_i2c_device(void)
+{
+ return &ec.i2c;
+}
+EXPORT_SYMBOL_GPL(imanager_get_i2c_device);
+
+const struct imanager_backlight_device *imanager_get_backlight_device(void)
+{
+ return &ec.blc;
+}
+EXPORT_SYMBOL_GPL(imanager_get_backlight_device);
+
+const struct imanager_watchdog_device *imanager_get_watchdog_device(void)
+{
+ return &ec.wdt;
+}
+EXPORT_SYMBOL_GPL(imanager_get_watchdog_device);
+
+static int ec_get_version(struct ec_version *version)
+{
+ int ret;
+ u16 raw;
+ struct ec_version_raw ver;
+
+ if (WARN_ON(!version))
+ return -EINVAL;
+
+ ret = ec_read_ram(EC_RAM_ACPI, EC_ACPIRAM_FW_RELEASE_RD, sizeof(ver),
+ (u8 *)&ver, sizeof(ver));
+ if (ret < 0)
+ return ret;
+
+ raw = swab16(ver.kernel);
+ version->kernel_major = EC_KERNEL_MAJOR(raw);
+ version->kernel_minor = EC_KERNEL_MINOR(raw);
+
+ raw = swab16(ver.firmware);
+ version->firmware_major = EC_FIRMWARE_MAJOR(raw);
+ version->firmware_minor = EC_FIRMWARE_MINOR(raw);
+
+ raw = swab16(ver.project_code);
+ version->type = EC_PROJECT_CODE(raw);
+
+ return 0;
+}
+
+static int ec_get_pcb_name(struct ec_info *info)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = ARRAY_SIZE(info->pcb_name),
+ .wlen = 0,
+ };
+
+ if (WARN_ON(!info))
+ return -EINVAL;
+
+ ret = imanager_msg_read(EC_CMD_FW_INFO_RD, 0, &msg);
+ if (ret)
+ return ret;
+
+ /*
+ * Sadly, the string is not Null-terminated so we will need to read a
+ * fixed amount of chars. There is, apparently, no exact definition
+ * of board name (SOM6867 vs. MIO-5271).
+ */
+ memset(info->pcb_name, 0, ARRAY_SIZE(info->pcb_name));
+ strncpy(info->pcb_name, (const char *)msg.u.data, PCB_NAME_MAX_SIZE);
+
+ if (strchr(info->pcb_name, '-') == NULL)
+ info->pcb_name[PCB_NAME_MAX_SIZE - 1] = '\0';
+
+ return 0;
+}
+
+static int ec_get_fw_info(struct ec_info *info)
+{
+ int ret;
+
+ if (WARN_ON(!info))
+ return -EINVAL;
+
+ ret = ec_get_version(&info->version);
+ if (ret)
+ return ret;
+
+ return ec_get_pcb_name(info);
+}
+
+static int ec_init(void)
+{
+ int ret;
+
+ ec_clear_ports();
+
+ ret = ec_read_dynamic_devtbl(&ec);
+ if (ret)
+ return ret;
+
+ ret = ec_get_fw_info(&ec.dev.info);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_gpio(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_hwmon(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_i2c(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_blc(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_wdt(&ec);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int imanager_ec_probe(void)
+{
+ int chipid = ec_read_chipid(EC_BASE_ADDR);
+
+ memset((void *)&ec, 0, sizeof(ec));
+
+ switch (chipid) {
+ case EC_DEVID_IT8516:
+ pr_err("EC IT8516 not supported\n");
+ ec.dev.id = IT8516;
+ return -ENODEV;
+ case EC_DEVID_IT8518:
+ ec.read = ec_io18_read;
+ ec.write = ec_io18_write;
+ ec.dev.id = IT8518;
+ break;
+ case EC_DEVID_IT8528:
+ ec.read = ec_io28_read;
+ ec.write = ec_io28_write;
+ ec.dev.id = IT8528;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ec.dev.addr = EC_BASE_ADDR;
+
+ return ec_init();
+}
+
diff --git a/include/linux/mfd/imanager/core.h b/include/linux/mfd/imanager/core.h
new file mode 100644
index 0000000..05c77fa
--- /dev/null
+++ b/include/linux/mfd/imanager/core.h
@@ -0,0 +1,31 @@
+/*
+ * Advantech iManager MFD core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __CORE_H__
+#define __CORE_H__
+
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/mfd/imanager/ec.h>
+
+struct imanager_platform_data {
+ const struct imanager_ec_device *dev;
+ const char *chip_name;
+};
+
+struct imanager_device_data {
+ struct device *dev;
+ struct mutex lock;
+};
+
+#endif
diff --git a/include/linux/mfd/imanager/ec.h b/include/linux/mfd/imanager/ec.h
new file mode 100644
index 0000000..783f268
--- /dev/null
+++ b/include/linux/mfd/imanager/ec.h
@@ -0,0 +1,210 @@
+/*
+ * Advantech iManager core - firmware interface
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __EC_H__
+#define __EC_H__
+
+#include <linux/types.h>
+
+#define EC_DEVID_IT8516 0x8516
+#define EC_DEVID_IT8518 0x8518
+#define EC_DEVID_IT8528 0x8528
+
+#define EC_BASE_ADDR 0x029C
+
+#define IT8516_CMD_PORT 0x029A /* it8528 */
+#define IT8516_DAT_PORT 0x0299
+
+#define IT8518_CMD_PORT 0x029E /* it8518 */
+#define IT8518_DAT_PORT 0x029F
+
+#define EC_GPIO_MAX_NUM 8
+#define EC_HWM_MAX_ADC 5
+#define EC_HWM_MAX_FAN 3
+#define EC_BLC_MAX_NUM 2
+#define EC_SMB_MAX_NUM 4
+#define EC_WDT_MAX_NUM 2
+
+#define PCB_NAME_SIZE 32
+#define EC_PAYLOAD_SIZE 40
+#define EC_MSG_SIZE sizeof(struct ec_smb_message)
+
+#define LOBYTE16(x) (x & 0x00FF)
+#define HIBYTE16(x) (LOBYTE16(x >> 8))
+#define LOADDR16(x) LOBYTE16(x)
+#define HIADDR16(x) (x >= 0xF000 ? LOBYTE16(x >> 8) : 0)
+
+/*
+ * iManager commands
+ */
+#define EC_CMD_HWP_RD 0x11UL
+#define EC_CMD_HWP_WR 0x12UL
+#define EC_CMD_GPIO_DIR_RD 0x30UL
+#define EC_CMD_GPIO_DIR_WR 0x31UL
+#define EC_CMD_PWM_FREQ_RD 0x36UL
+#define EC_CMD_PWM_FREQ_WR 0x32UL
+#define EC_CMD_PWM_POL_RD 0x37UL
+#define EC_CMD_PWM_POL_WR 0x33UL
+#define EC_CMD_SMB_FREQ_RD 0x34UL
+#define EC_CMD_SMB_FREQ_WR 0x35UL
+#define EC_CMD_FAN_CTL_RD 0x40UL
+#define EC_CMD_FAN_CTL_WR 0x41UL
+#define EC_CMD_THZ_RD 0x42UL
+#define EC_CMD_DYN_TBL_RD 0x20UL
+#define EC_CMD_FW_INFO_RD 0xF0UL
+#define EC_CMD_BUF_CLR 0xC0UL
+#define EC_CMD_BUF_RD 0xC1UL
+#define EC_CMD_BUF_WR 0xC2UL
+#define EC_CMD_RAM_RD 0x1EUL
+#define EC_CMD_RAM_WR 0x1FUL
+#define EC_CMD_I2C_RW 0x0EUL
+#define EC_CMD_I2C_WR 0x0FUL
+#define EC_CMD_WDT_CTRL 0x28UL
+
+/*
+ * ACPI and HW RAM offsets
+ */
+#define EC_ACPIRAM_FAN_ALERT 0x6FUL
+#define EC_ACPIRAM_FAN_SPEED_LIMIT 0x76UL
+#define EC_ACPIRAM_BRIGHTNESS1 0x50UL
+#define EC_ACPIRAM_BRIGHTNESS2 0x52UL
+#define EC_ACPIRAM_BLC_CTRL 0x99UL
+#define EC_ACPIRAM_FW_RELEASE_RD 0xF8UL
+
+enum chips { IT8516, IT8518, IT8528 };
+
+struct ec_message_header {
+ u8 addr_low; /* SMB low-byte address or data low-byte */
+ /* of byte-/word-transaction */
+ u8 addr_high; /* SMB high-byte address or data high-byte */
+ /* of word-transaction */
+ u8 rlen; /* SMB read length */
+ u8 wlen; /* SMB write length */
+ u8 cmd; /* SMB command */
+};
+
+struct ec_smb_message {
+ struct ec_message_header hdr;
+ u8 data[EC_PAYLOAD_SIZE];
+};
+
+struct ec_message {
+ u8 rlen; /* EC message read length */
+ u8 wlen; /* EC message write length */
+ union {
+ struct ec_smb_message smb;
+ u8 data[EC_MSG_SIZE];
+ } u;
+
+ u8 *data;
+};
+
+struct ec_version {
+ u32 kernel_major;
+ u32 kernel_minor;
+ u32 firmware_major;
+ u32 firmware_minor;
+ u32 type;
+};
+
+struct ec_info {
+ struct ec_version version;
+ char pcb_name[PCB_NAME_SIZE];
+};
+
+struct imanager_ec_device {
+ int id; /* enum chip */
+ u16 addr;
+ struct ec_info info;
+};
+
+struct ec_dev_attr {
+ int did; /* Device ID */
+ int hwp; /* Hardware Pin number */
+ int pol; /* Polarity */
+ int scale; /* Scaling factor */
+ const char *label;
+};
+
+struct imanager_gpio_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_GPIO_MAX_NUM];
+ struct ec_info *info;
+};
+
+struct dev_adc {
+ u32 num;
+ struct ec_dev_attr attr[EC_HWM_MAX_ADC];
+};
+
+struct dev_fan {
+ u32 num;
+ u32 active;
+ struct ec_dev_attr attr[EC_HWM_MAX_FAN];
+};
+
+struct imanager_hwmon_device {
+ struct dev_adc adc;
+ struct dev_fan fan;
+ struct imanager_ec_device *ecdev;
+};
+
+struct imanager_i2c_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_SMB_MAX_NUM];
+ struct ec_dev_attr *eeprom;
+ struct ec_dev_attr *i2coem;
+ struct imanager_ec_device *ecdev;
+};
+
+struct imanager_backlight_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_BLC_MAX_NUM];
+ u8 brightness[EC_BLC_MAX_NUM];
+ struct ec_info *info;
+};
+
+struct imanager_watchdog_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_WDT_MAX_NUM];
+ struct ec_dev_attr *irq;
+ struct ec_dev_attr *nmi;
+ struct ec_info *info;
+};
+
+/* Must be called first, obviously */
+int imanager_ec_probe(void);
+
+int imanager_msg_write(u8 cmd, u8 param, struct ec_message *msg);
+int imanager_msg_read(u8 cmd, u8 param, struct ec_message *msg);
+
+int imanager_read_byte(u8 cmd, u8 param);
+int imanager_read_word(u8 cmd, u8 param);
+
+int imanager_write_byte(u8 cmd, u8 param, u8 byte);
+int imanager_write_word(u8 cmd, u8 param, u16 word);
+
+int imanager_acpiram_read_byte(u8 offset);
+int imanager_acpiram_write_byte(u8 offset, u8 value);
+int imanager_acpiram_read_block(u8 offset, u8 *buf, u8 len);
+int imanager_acpiram_write_block(u8 offset, u8 *buf, u8 len);
+
+int imanager_wait_proc_complete(u8 offset, int cond);
+
+const struct imanager_ec_device *imanager_get_ec_device(void);
+const struct imanager_hwmon_device *imanager_get_hwmon_device(void);
+const struct imanager_gpio_device *imanager_get_gpio_device(void);
+const struct imanager_i2c_device *imanager_get_i2c_device(void);
+const struct imanager_watchdog_device *imanager_get_watchdog_device(void);
+const struct imanager_backlight_device *imanager_get_backlight_device(void);
+
+#endif
--
2.6.4
--
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
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH] fix platform_no_drv_owner.cocci warnings
2016-01-10 9:10 ` richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
@ 2016-01-10 10:11 ` kbuild test robot
0 siblings, 0 replies; 8+ messages in thread
From: kbuild test robot @ 2016-01-10 10:11 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
drivers/mfd/imanager-core.c:248:3-8: No need to set .owner here. The core will do it.
Remove .owner field if calls are used which set it automatically
Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci
CC: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---
imanager-core.c | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/mfd/imanager-core.c
+++ b/drivers/mfd/imanager-core.c
@@ -245,7 +245,6 @@ static int imanager_remove(struct platfo
static struct platform_driver imanager_driver = {
.driver = {
- .owner = THIS_MODULE,
.name = "imanager-core",
},
.probe = imanager_probe,
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 6/6] Add Advantech iManager Watchdog driver
@ 2016-01-09 2:02 kbuild test robot
[not found] ` <1452292166-20118-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
0 siblings, 1 reply; 8+ messages in thread
From: kbuild test robot @ 2016-01-09 2:02 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160109-063329
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/watchdog/imanager-wdt.c:322:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/6] Add Advantech iManager GPIO driver
2016-01-08 22:29 ` richard.dorsch
@ 2016-01-09 0:50 kbuild test robot
2016-01-08 22:29 ` richard.dorsch
0 siblings, 1 reply; 8+ messages in thread
From: kbuild test robot @ 2016-01-09 0:50 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
Hi Richard,
[auto build test WARNING on hwmon/hwmon-next]
[also build test WARNING on v4.4-rc8 next-20160108]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/richard-dorsch-gmail-com/Add-Advantech-iManager-EC-driver-set/20160109-063329
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
coccinelle warnings: (new ones prefixed by >>)
>> drivers/gpio/imanager-gpio.c:170:3-8: No need to set .owner here. The core will do it.
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/6] Add Advantech iManager GPIO driver
@ 2016-01-08 22:29 ` richard.dorsch
2016-01-09 0:50 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
0 siblings, 1 reply; 8+ messages in thread
From: richard.dorsch @ 2016-01-08 22:29 UTC (permalink / raw)
To: linux-kernel
Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
jdelvare, linux, wim, jo.sunga, Richard Vidal-Dorsch
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/gpio/Kconfig | 8 ++
drivers/gpio/Makefile | 2 +
drivers/gpio/imanager-ec-gpio.c | 98 ++++++++++++++++++++
drivers/gpio/imanager-gpio.c | 182 ++++++++++++++++++++++++++++++++++++++
include/linux/mfd/imanager/gpio.h | 27 ++++++
5 files changed, 317 insertions(+)
create mode 100644 drivers/gpio/imanager-ec-gpio.c
create mode 100644 drivers/gpio/imanager-gpio.c
create mode 100644 include/linux/mfd/imanager/gpio.h
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index b18bea0..0f80947 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -765,6 +765,14 @@ config GPIO_DLN2
This driver can also be built as a module. If so, the module
will be called gpio-dln2.
+config GPIO_IMANAGER
+ tristate "Advantech iManager GPIO support"
+ depends on MFD_IMANAGER
+ help
+ Say yes here to support Advantech iManager GPIO functionality
+ of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+ Requires mfd-core and imanager-core to function properly.
+
config GPIO_JANZ_TTL
tristate "Janz VMOD-TTL Digital IO Module"
depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 986dbd8..0df55e4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -41,6 +41,8 @@ obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
+gpio-imanager-objs := imanager-gpio.o imanager-ec-gpio.o
+obj-$(CONFIG_GPIO_IMANAGER) += gpio-imanager.o
obj-$(CONFIG_GPIO_IOP) += gpio-iop.o
obj-$(CONFIG_GPIO_IT87) += gpio-it87.o
obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
diff --git a/drivers/gpio/imanager-ec-gpio.c b/drivers/gpio/imanager-ec-gpio.c
new file mode 100644
index 0000000..cefc44d
--- /dev/null
+++ b/drivers/gpio/imanager-ec-gpio.c
@@ -0,0 +1,98 @@
+/*
+ * Advantech iManager GPIO core
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/mfd/imanager/ec.h>
+#include <linux/mfd/imanager/gpio.h>
+
+#define EC_GPIOF_DIR_OUT (1 << 6)
+#define EC_GPIOF_DIR_IN (1 << 7)
+#define EC_GPIOF_LOW (0 << 0)
+#define EC_GPIOF_HIGH (1 << 0)
+
+/*
+ * Power-on default:
+ * GPIO[7..4] := Input
+ * GPIO[3..0] := Output
+ */
+
+static const struct imanager_gpio_device *gpio;
+
+int gpio_core_get_state(u32 num)
+{
+ int ret;
+
+ if (WARN_ON(num >= gpio->num))
+ return -EINVAL;
+
+ ret = imanager_read_byte(EC_CMD_HWP_RD, gpio->attr[num].did);
+ if (ret < 0)
+ pr_err("Failed to get GPIO pin state (%x)\n", num);
+
+ return ret;
+}
+
+int gpio_core_set_state(u32 num, bool state)
+{
+ int ret;
+
+ if (WARN_ON(num >= gpio->num))
+ return -EINVAL;
+
+ ret = imanager_write_byte(EC_CMD_HWP_WR, gpio->attr[num].did,
+ state ? EC_GPIOF_HIGH : EC_GPIOF_LOW);
+ if (ret) {
+ pr_err("Failed to set GPIO pin state (%x)\n", num);
+ return ret;
+ }
+
+ return 0;
+}
+
+int gpio_core_set_direction(u32 num, int dir)
+{
+ int ret;
+
+ if (WARN_ON(num >= gpio->num))
+ return -EINVAL;
+
+ ret = imanager_write_byte(EC_CMD_GPIO_DIR_WR, gpio->attr[num].did,
+ dir ? EC_GPIOF_DIR_IN : EC_GPIOF_DIR_OUT);
+ if (ret) {
+ pr_err("Failed to set GPIO direction (%x, '%s')\n", num,
+ dir == GPIOF_DIR_OUT ? "OUT" : "IN");
+ return ret;
+ }
+
+ return 0;
+}
+
+int gpio_core_get_max_count(void)
+{
+ return gpio->num;
+}
+
+int gpio_core_init(void)
+{
+ gpio = imanager_get_gpio_device();
+ if (!gpio)
+ return -ENODEV;
+
+ return 0;
+}
+
diff --git a/drivers/gpio/imanager-gpio.c b/drivers/gpio/imanager-gpio.c
new file mode 100644
index 0000000..3939c0f
--- /dev/null
+++ b/drivers/gpio/imanager-gpio.c
@@ -0,0 +1,182 @@
+/*
+ * Advantech iManager GPIO driver
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/gpio.h>
+
+struct imanager_gpio_data {
+ struct imanager_device_data *idev;
+ struct gpio_chip chip;
+};
+
+static inline struct imanager_gpio_data *
+to_imanager_gpio_data(struct gpio_chip *chip)
+{
+ return container_of(chip, struct imanager_gpio_data, chip);
+}
+
+static int imanager_direction_in(struct gpio_chip *chip, u32 gpio_num)
+{
+ struct imanager_gpio_data *data = to_imanager_gpio_data(chip);
+ int ret;
+
+ mutex_lock(&data->idev->lock);
+
+ ret = gpio_core_set_direction(gpio_num, GPIOF_DIR_IN);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set direction to 'in' (%d)\n",
+ gpio_num);
+ ret = -EIO;
+ }
+
+ mutex_unlock(&data->idev->lock);
+
+ return ret;
+}
+
+static int
+imanager_direction_out(struct gpio_chip *chip, u32 gpio_num, int val)
+{
+ struct imanager_gpio_data *data = to_imanager_gpio_data(chip);
+ int ret;
+
+ mutex_lock(&data->idev->lock);
+
+ ret = gpio_core_set_direction(gpio_num, GPIOF_DIR_OUT);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set direction to 'out' (%d)\n",
+ gpio_num);
+ ret = -EIO;
+ }
+
+ mutex_unlock(&data->idev->lock);
+
+ return ret;
+}
+
+static int imanager_get(struct gpio_chip *chip, u32 gpio_num)
+{
+ struct imanager_gpio_data *data = to_imanager_gpio_data(chip);
+ int ret;
+
+ mutex_lock(&data->idev->lock);
+
+ ret = gpio_core_get_state(gpio_num);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to get status (%d)\n", gpio_num);
+ ret = -EIO;
+ }
+
+ mutex_unlock(&data->idev->lock);
+
+ return ret;
+}
+
+static void imanager_set(struct gpio_chip *chip, u32 gpio_num,
+ int val)
+{
+ struct imanager_gpio_data *data = to_imanager_gpio_data(chip);
+ int ret;
+
+ mutex_lock(&data->idev->lock);
+
+ ret = gpio_core_set_state(gpio_num, val);
+ if (ret < 0)
+ dev_err(chip->dev, "Failed to set status (%d)\n", gpio_num);
+
+ mutex_unlock(&data->idev->lock);
+}
+
+static int imanager_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+ struct imanager_gpio_data *data;
+ struct gpio_chip *chip;
+ int ret;
+
+ if (!idev) {
+ dev_err(dev, "Invalid platform data\n");
+ return -EINVAL;
+ }
+
+ ret = gpio_core_init();
+ if (ret) {
+ dev_err(dev, "Failed initializing GPIO core\n");
+ return ret;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->idev = idev;
+
+ platform_set_drvdata(pdev, data);
+
+ chip = &data->chip;
+
+ chip->owner = THIS_MODULE;
+ chip->dev = dev;
+ chip->label = "imanager_gpio";
+
+ chip->base = -1;
+ chip->ngpio = gpio_core_get_max_count();
+
+ chip->get = imanager_get;
+ chip->set = imanager_set;
+
+ chip->can_sleep = 1;
+
+ chip->direction_input = imanager_direction_in;
+ chip->direction_output = imanager_direction_out;
+
+ ret = gpiochip_add(chip);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register driver\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imanager_remove(struct platform_device *pdev)
+{
+ struct imanager_gpio_data *data = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&data->chip);
+
+ return 0;
+}
+
+static struct platform_driver imanager_gpio_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imanager_gpio",
+ },
+ .probe = imanager_gpio_probe,
+ .remove = imanager_remove,
+};
+
+module_platform_driver(imanager_gpio_driver);
+
+MODULE_DESCRIPTION("Advantech iManager GPIO Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager_gpio");
diff --git a/include/linux/mfd/imanager/gpio.h b/include/linux/mfd/imanager/gpio.h
new file mode 100644
index 0000000..7b76727
--- /dev/null
+++ b/include/linux/mfd/imanager/gpio.h
@@ -0,0 +1,27 @@
+/*
+ * Advantech iManager GPIO core
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __GPIO_H__
+#define __GPIO_H__
+
+#include <linux/gpio.h>
+#include <linux/types.h>
+
+int gpio_core_init(void);
+
+int gpio_core_get_max_count(void);
+
+int gpio_core_get_state(u32 num);
+int gpio_core_set_state(u32 num, bool state);
+int gpio_core_set_direction(u32 num, int dir);
+
+#endif
--
2.6.4
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH] fix platform_no_drv_owner.cocci warnings
2016-01-08 22:29 ` richard.dorsch
@ 2016-01-09 0:50 ` kbuild test robot
0 siblings, 0 replies; 8+ messages in thread
From: kbuild test robot @ 2016-01-09 0:50 UTC (permalink / raw)
Cc: kbuild-all, linux-kernel, lm-sensors, linux-i2c, linux-watchdog,
linux-gpio, lee.jones, jdelvare, linux, wim, jo.sunga,
Richard Vidal-Dorsch
drivers/gpio/imanager-gpio.c:170:3-8: No need to set .owner here. The core will do it.
Remove .owner field if calls are used which set it automatically
Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci
CC: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---
imanager-gpio.c | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/gpio/imanager-gpio.c
+++ b/drivers/gpio/imanager-gpio.c
@@ -167,7 +167,6 @@ static int imanager_remove(struct platfo
static struct platform_driver imanager_gpio_driver = {
.driver = {
- .owner = THIS_MODULE,
.name = "imanager_gpio",
},
.probe = imanager_gpio_probe,
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2016-01-10 10:44 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-01-10 9:11 [PATCH v2 3/6] Add Advantech iManager HWmon driver richard.dorsch
2016-01-10 10:25 ` kbuild test robot
2016-01-10 10:25 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
-- strict thread matches above, loose matches on Subject: below --
2016-01-10 10:44 [PATCH v2 5/6] Add Advantech iManager Backlight driver kbuild test robot
[not found] ` <1452417098-28667-1-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2016-01-10 10:44 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
2016-01-10 10:34 [PATCH v2 4/6] Add Advantech iManager I2C driver kbuild test robot
2016-01-10 9:11 ` richard.dorsch
2016-01-10 10:34 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
2016-01-10 10:11 [PATCH v2 1/6] Add Advantech iManager MFD core driver kbuild test robot
2016-01-10 9:10 ` richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
2016-01-10 10:11 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
2016-01-09 2:02 [PATCH 6/6] Add Advantech iManager Watchdog driver kbuild test robot
[not found] ` <1452292166-20118-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2016-01-09 2:02 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
2016-01-09 0:50 [PATCH 2/6] Add Advantech iManager GPIO driver kbuild test robot
2016-01-08 22:29 ` richard.dorsch
2016-01-09 0:50 ` [PATCH] fix platform_no_drv_owner.cocci warnings kbuild test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).