All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/6] Add Advantech iManager EC driver set
@ 2016-01-10 23:31 ` richard.dorsch
  0 siblings, 0 replies; 22+ messages in thread
From: richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w @ 2016-01-10 23:31 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>

The Advantech iManager is an embedded controller (depending on board type,
ITE it8518/it8528) on which a custom firmware runs. All drivers (GPIO, I2C,
HWmon, Watchdog, and backlight) are being managed by the imanager mfd driver.
The imanager mfd driver acts as a 'gateway' and handles all communications
between the EC and sub-drivers (GPIO, I2C, ...). The feature specific
communication portions are split up into core files such as
imanager-ec-[gpio, i2c, ...].

Changes from v2:
I forgot to remove .owner from other drivers too... silly me!
- Remove .owner from platform_driver in:
  drivers/gpio/imanager-bl
  drivers/gpio/imanager-core.c
  drivers/gpio/imanager-i2c.c
  drivers/gpio/imanager-hwmon.c

Changes from v1:
- Remove .owner from platform_driver in drivers/gpio/imanager-gpio.c
- Remove .owner from platform_driver in drivers/gpio/imanager-wdt.c
- Replace 2015 by 2016 in all files

Richard Vidal-Dorsch (6):
  Add Advantech iManager MFD core driver
  Add Advantech iManager GPIO driver
  Add Advantech iManager HWmon driver
  Add Advantech iManager I2C driver
  Add Advantech iManager Backlight driver
  Add Advantech iManager Watchdog driver

 Documentation/devicetree/bindings/mfd/imanager.txt |   33 +
 Documentation/hwmon/imanager                       |   59 +
 Documentation/i2c/busses/i2c-imanager              |   48 +
 MAINTAINERS                                        |   13 +
 drivers/gpio/Kconfig                               |    8 +
 drivers/gpio/Makefile                              |    2 +
 drivers/gpio/imanager-ec-gpio.c                    |   98 ++
 drivers/gpio/imanager-gpio.c                       |  181 +++
 drivers/hwmon/Kconfig                              |   12 +
 drivers/hwmon/Makefile                             |    2 +
 drivers/hwmon/imanager-ec-hwmon.c                  |  606 +++++++++
 drivers/hwmon/imanager-hwmon.c                     | 1057 +++++++++++++++
 drivers/i2c/busses/Kconfig                         |   11 +
 drivers/i2c/busses/Makefile                        |    2 +
 drivers/i2c/busses/imanager-ec-i2c.c               |  466 +++++++
 drivers/i2c/busses/imanager-i2c.c                  |  239 ++++
 drivers/mfd/Kconfig                                |   20 +
 drivers/mfd/Makefile                               |    2 +
 drivers/mfd/imanager-core.c                        |  287 +++++
 drivers/mfd/imanager-ec.c                          | 1345 ++++++++++++++++++++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    2 +
 drivers/video/backlight/imanager-bl.c              |  198 +++
 drivers/video/backlight/imanager-ec-bl.c           |  118 ++
 drivers/watchdog/Kconfig                           |   12 +
 drivers/watchdog/Makefile                          |    2 +
 drivers/watchdog/imanager-ec-wdt.c                 |  170 +++
 drivers/watchdog/imanager-wdt.c                    |  333 +++++
 include/linux/mfd/imanager/backlight.h             |   37 +
 include/linux/mfd/imanager/core.h                  |   31 +
 include/linux/mfd/imanager/ec.h                    |  210 +++
 include/linux/mfd/imanager/gpio.h                  |   27 +
 include/linux/mfd/imanager/hwmon.h                 |  120 ++
 include/linux/mfd/imanager/i2c.h                   |   55 +
 include/linux/mfd/imanager/wdt.h                   |   37 +
 35 files changed, 5855 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/imanager.txt
 create mode 100644 Documentation/hwmon/imanager
 create mode 100644 Documentation/i2c/busses/i2c-imanager
 create mode 100644 drivers/gpio/imanager-ec-gpio.c
 create mode 100644 drivers/gpio/imanager-gpio.c
 create mode 100644 drivers/hwmon/imanager-ec-hwmon.c
 create mode 100644 drivers/hwmon/imanager-hwmon.c
 create mode 100644 drivers/i2c/busses/imanager-ec-i2c.c
 create mode 100644 drivers/i2c/busses/imanager-i2c.c
 create mode 100644 drivers/mfd/imanager-core.c
 create mode 100644 drivers/mfd/imanager-ec.c
 create mode 100644 drivers/video/backlight/imanager-bl.c
 create mode 100644 drivers/video/backlight/imanager-ec-bl.c
 create mode 100644 drivers/watchdog/imanager-ec-wdt.c
 create mode 100644 drivers/watchdog/imanager-wdt.c
 create mode 100644 include/linux/mfd/imanager/backlight.h
 create mode 100644 include/linux/mfd/imanager/core.h
 create mode 100644 include/linux/mfd/imanager/ec.h
 create mode 100644 include/linux/mfd/imanager/gpio.h
 create mode 100644 include/linux/mfd/imanager/hwmon.h
 create mode 100644 include/linux/mfd/imanager/i2c.h
 create mode 100644 include/linux/mfd/imanager/wdt.h

-- 
2.7.0

--
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	[flat|nested] 22+ messages in thread

* [PATCH v3 0/6] Add Advantech iManager EC driver set
@ 2016-01-10 23:31 ` richard.dorsch
  0 siblings, 0 replies; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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>

The Advantech iManager is an embedded controller (depending on board type,
ITE it8518/it8528) on which a custom firmware runs. All drivers (GPIO, I2C,
HWmon, Watchdog, and backlight) are being managed by the imanager mfd driver.
The imanager mfd driver acts as a 'gateway' and handles all communications
between the EC and sub-drivers (GPIO, I2C, ...). The feature specific
communication portions are split up into core files such as
imanager-ec-[gpio, i2c, ...].

Changes from v2:
I forgot to remove .owner from other drivers too... silly me!
- Remove .owner from platform_driver in:
  drivers/gpio/imanager-bl
  drivers/gpio/imanager-core.c
  drivers/gpio/imanager-i2c.c
  drivers/gpio/imanager-hwmon.c

Changes from v1:
- Remove .owner from platform_driver in drivers/gpio/imanager-gpio.c
- Remove .owner from platform_driver in drivers/gpio/imanager-wdt.c
- Replace 2015 by 2016 in all files

Richard Vidal-Dorsch (6):
  Add Advantech iManager MFD core driver
  Add Advantech iManager GPIO driver
  Add Advantech iManager HWmon driver
  Add Advantech iManager I2C driver
  Add Advantech iManager Backlight driver
  Add Advantech iManager Watchdog driver

 Documentation/devicetree/bindings/mfd/imanager.txt |   33 +
 Documentation/hwmon/imanager                       |   59 +
 Documentation/i2c/busses/i2c-imanager              |   48 +
 MAINTAINERS                                        |   13 +
 drivers/gpio/Kconfig                               |    8 +
 drivers/gpio/Makefile                              |    2 +
 drivers/gpio/imanager-ec-gpio.c                    |   98 ++
 drivers/gpio/imanager-gpio.c                       |  181 +++
 drivers/hwmon/Kconfig                              |   12 +
 drivers/hwmon/Makefile                             |    2 +
 drivers/hwmon/imanager-ec-hwmon.c                  |  606 +++++++++
 drivers/hwmon/imanager-hwmon.c                     | 1057 +++++++++++++++
 drivers/i2c/busses/Kconfig                         |   11 +
 drivers/i2c/busses/Makefile                        |    2 +
 drivers/i2c/busses/imanager-ec-i2c.c               |  466 +++++++
 drivers/i2c/busses/imanager-i2c.c                  |  239 ++++
 drivers/mfd/Kconfig                                |   20 +
 drivers/mfd/Makefile                               |    2 +
 drivers/mfd/imanager-core.c                        |  287 +++++
 drivers/mfd/imanager-ec.c                          | 1345 ++++++++++++++++++++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    2 +
 drivers/video/backlight/imanager-bl.c              |  198 +++
 drivers/video/backlight/imanager-ec-bl.c           |  118 ++
 drivers/watchdog/Kconfig                           |   12 +
 drivers/watchdog/Makefile                          |    2 +
 drivers/watchdog/imanager-ec-wdt.c                 |  170 +++
 drivers/watchdog/imanager-wdt.c                    |  333 +++++
 include/linux/mfd/imanager/backlight.h             |   37 +
 include/linux/mfd/imanager/core.h                  |   31 +
 include/linux/mfd/imanager/ec.h                    |  210 +++
 include/linux/mfd/imanager/gpio.h                  |   27 +
 include/linux/mfd/imanager/hwmon.h                 |  120 ++
 include/linux/mfd/imanager/i2c.h                   |   55 +
 include/linux/mfd/imanager/wdt.h                   |   37 +
 35 files changed, 5855 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/imanager.txt
 create mode 100644 Documentation/hwmon/imanager
 create mode 100644 Documentation/i2c/busses/i2c-imanager
 create mode 100644 drivers/gpio/imanager-ec-gpio.c
 create mode 100644 drivers/gpio/imanager-gpio.c
 create mode 100644 drivers/hwmon/imanager-ec-hwmon.c
 create mode 100644 drivers/hwmon/imanager-hwmon.c
 create mode 100644 drivers/i2c/busses/imanager-ec-i2c.c
 create mode 100644 drivers/i2c/busses/imanager-i2c.c
 create mode 100644 drivers/mfd/imanager-core.c
 create mode 100644 drivers/mfd/imanager-ec.c
 create mode 100644 drivers/video/backlight/imanager-bl.c
 create mode 100644 drivers/video/backlight/imanager-ec-bl.c
 create mode 100644 drivers/watchdog/imanager-ec-wdt.c
 create mode 100644 drivers/watchdog/imanager-wdt.c
 create mode 100644 include/linux/mfd/imanager/backlight.h
 create mode 100644 include/linux/mfd/imanager/core.h
 create mode 100644 include/linux/mfd/imanager/ec.h
 create mode 100644 include/linux/mfd/imanager/gpio.h
 create mode 100644 include/linux/mfd/imanager/hwmon.h
 create mode 100644 include/linux/mfd/imanager/i2c.h
 create mode 100644 include/linux/mfd/imanager/wdt.h

-- 
2.7.0

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 1/6] Add Advantech iManager MFD core driver
  2016-01-10 23:31 ` richard.dorsch
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
  2016-01-11  0:54   ` Krzysztof Kozlowski
  -1 siblings, 1 reply; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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>

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@gmail.com>
---
 Documentation/devicetree/bindings/mfd/imanager.txt |   33 +
 MAINTAINERS                                        |   13 +
 drivers/mfd/Kconfig                                |   20 +
 drivers/mfd/Makefile                               |    2 +
 drivers/mfd/imanager-core.c                        |  287 +++++
 drivers/mfd/imanager-ec.c                          | 1345 ++++++++++++++++++++
 include/linux/mfd/imanager/core.h                  |   31 +
 include/linux/mfd/imanager/ec.h                    |  210 +++
 8 files changed, 1941 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@advantech.com>
+
+
+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@wp.pl>
 S:	Maintained
 F:	drivers/usb/atm/ueagle-atm.c
 
+IMANAGER ADVANTECH EC DRIVER
+M:	Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+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@roeck-us.net>
 L:	lm-sensors@lm-sensors.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..d573ecb
--- /dev/null
+++ b/drivers/mfd/imanager-core.c
@@ -0,0 +1,287 @@
+/*
+ * Advantech iManager MFD core 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/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 = {
+		.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@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/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@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 __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@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 __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.7.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 2/6] Add Advantech iManager GPIO driver
  2016-01-10 23:31 ` richard.dorsch
  (?)
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
  2016-01-18  7:42     ` Lee Jones
  -1 siblings, 1 reply; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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>
---
 drivers/gpio/Kconfig              |   8 ++
 drivers/gpio/Makefile             |   2 +
 drivers/gpio/imanager-ec-gpio.c   |  98 +++++++++++++++++++++
 drivers/gpio/imanager-gpio.c      | 181 ++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/imanager/gpio.h |  27 ++++++
 5 files changed, 316 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..c448666
--- /dev/null
+++ b/drivers/gpio/imanager-ec-gpio.c
@@ -0,0 +1,98 @@
+/*
+ * Advantech iManager GPIO 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/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..d4a2b30
--- /dev/null
+++ b/drivers/gpio/imanager-gpio.c
@@ -0,0 +1,181 @@
+/*
+ * Advantech iManager GPIO 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/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 = {
+		.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..dfc849f
--- /dev/null
+++ b/include/linux/mfd/imanager/gpio.h
@@ -0,0 +1,27 @@
+/*
+ * Advantech iManager GPIO 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 __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.7.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 3/6] Add Advantech iManager HWmon driver
  2016-01-10 23:31 ` richard.dorsch
                   ` (2 preceding siblings ...)
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
  2016-01-17 18:53   ` Guenter Roeck
  -1 siblings, 1 reply; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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     | 1057 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/imanager/hwmon.h |  120 ++++
 6 files changed, 1856 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..f836c7e
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1057 @@
+/*
+ * 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 = {
+		.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.7.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 4/6] Add Advantech iManager I2C driver
  2016-01-10 23:31 ` richard.dorsch
                   ` (3 preceding siblings ...)
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
       [not found]   ` <1452468675-5827-5-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  -1 siblings, 1 reply; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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     | 239 +++++++++++++++++
 include/linux/mfd/imanager/i2c.h      |  55 ++++
 6 files changed, 821 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..932cba2
--- /dev/null
+++ b/drivers/i2c/busses/imanager-i2c.c
@@ -0,0 +1,239 @@
+/*
+ * 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 = {
+		.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.7.0

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 5/6] Add Advantech iManager Backlight driver
  2016-01-10 23:31 ` richard.dorsch
                   ` (4 preceding siblings ...)
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
  2016-01-11 10:23   ` Lee Jones
  -1 siblings, 1 reply; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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>
---
 drivers/video/backlight/Kconfig          |  12 ++
 drivers/video/backlight/Makefile         |   2 +
 drivers/video/backlight/imanager-bl.c    | 198 +++++++++++++++++++++++++++++++
 drivers/video/backlight/imanager-ec-bl.c | 118 ++++++++++++++++++
 include/linux/mfd/imanager/backlight.h   |  37 ++++++
 5 files changed, 367 insertions(+)
 create mode 100644 drivers/video/backlight/imanager-bl.c
 create mode 100644 drivers/video/backlight/imanager-ec-bl.c
 create mode 100644 include/linux/mfd/imanager/backlight.h

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5ffa4b4..8003573 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -251,6 +251,18 @@ config BACKLIGHT_HP700
 	  If you have an HP Jornada 700 series,
 	  say Y to include backlight control driver.
 
+config BACKLIGHT_IMANAGER
+	tristate "Advantech iManager backlight/brightness"
+	depends on MFD_IMANAGER
+	help
+	  This enables support for Advantech iManager Backlight and
+	  Brightness control 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_bl.
+
 config BACKLIGHT_CARILLO_RANCH
 	tristate "Intel Carillo Ranch Backlight Driver"
 	depends on LCD_CLASS_DEVICE && PCI && X86 && FB_LE80578
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 16ec534..15bf136 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -26,6 +26,8 @@ obj-$(CONFIG_BACKLIGHT_ADP8870)		+= adp8870_bl.o
 obj-$(CONFIG_BACKLIGHT_APPLE)		+= apple_bl.o
 obj-$(CONFIG_BACKLIGHT_AS3711)		+= as3711_bl.o
 obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.o
+imanager_bl-objs			:= imanager-bl.o imanager-ec-bl.o
+obj-$(CONFIG_BACKLIGHT_IMANAGER)	+= imanager_bl.o
 obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH)	+= cr_bllcd.o
 obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE)	+= backlight.o
 obj-$(CONFIG_BACKLIGHT_DA903X)		+= da903x_bl.o
diff --git a/drivers/video/backlight/imanager-bl.c b/drivers/video/backlight/imanager-bl.c
new file mode 100644
index 0000000..9950fec
--- /dev/null
+++ b/drivers/video/backlight/imanager-bl.c
@@ -0,0 +1,198 @@
+/*
+ * Advantech iManager Backlight driver
+ * Partially derived from wm831x_bl
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/pwm.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/backlight.h>
+
+#define BL_MAX_BRIGHTNESS	100
+
+static bool polarity = PWM_POLARITY_NORMAL;
+module_param(polarity, bool, 0);
+MODULE_PARM_DESC(polarity, "Select backlight polarity (inverted := 1)");
+
+static ushort unit = UNIT_1;
+module_param(unit, ushort, 0);
+MODULE_PARM_DESC(unit, "Select backlight control unit [0, 1] (defaults to 0)");
+
+struct imanager_backlight_data {
+	struct imanager_device_data *idev;
+	struct backlight_device *bl;
+};
+
+static int get_brightness(struct backlight_device *dev)
+{
+	struct imanager_backlight_data *data = bl_get_data(dev);
+	int ret;
+
+	mutex_lock(&data->idev->lock);
+
+	ret = bl_core_get_pulse_width(unit);
+	/* Reverse percentage if polarity is set */
+	if (polarity)
+		ret = 100 - ret;
+
+	mutex_unlock(&data->idev->lock);
+
+	return ret;
+}
+
+static int set_brightness(struct backlight_device *dev)
+{
+	struct imanager_backlight_data *data = bl_get_data(dev);
+	u8 brightness = (u8) dev->props.brightness;
+	int ret;
+
+	if (brightness > BL_MAX_BRIGHTNESS)
+		return -EINVAL;
+
+	if (dev->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (dev->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (dev->props.state & BL_CORE_SUSPENDED)
+		brightness = 0;
+
+	mutex_lock(&data->idev->lock);
+
+	/* Inversed percentage if polarity is set */
+	if (polarity)
+		brightness = 100 - brightness;
+	ret = bl_core_set_pulse_width(unit, brightness);
+
+	mutex_unlock(&data->idev->lock);
+
+	return ret;
+}
+
+static const struct backlight_ops imanager_bl_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.get_brightness = get_brightness,
+	.update_status  = set_brightness,
+};
+
+static int imanager_backlight_init(struct device *dev,
+				   struct imanager_backlight_data *data)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = BL_MAX_BRIGHTNESS;
+	bd = backlight_device_register("imanager_backlight", dev, data,
+					&imanager_bl_ops, &props);
+
+	if (IS_ERR(bd)) {
+		data->bl = NULL;
+		dev_err(dev, "Unable to register backlight device\n");
+		return PTR_ERR(bd);
+	}
+
+	data->bl = bd;
+
+	bd->props.brightness = get_brightness(bd);
+	bd->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(bd);
+
+	return 0;
+}
+
+static int imanager_backlight_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+	struct imanager_backlight_data *data;
+	int ret;
+
+	if (!idev) {
+		dev_err(dev, "Invalid platform data\n");
+		return -EINVAL;
+	}
+
+	ret = bl_core_init();
+	if (ret) {
+		dev_err(dev, "Failed to initialize backlight core\n");
+		return -EIO;
+	}
+
+	ret = bl_core_set_state(unit, BL_CTRL_ENABLE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable backlight control (%d)\n",
+				unit);
+		return -EIO;
+	}
+
+	if (polarity)
+		ret = bl_core_set_polarity(PWM_POLARITY_INVERSED);
+	else
+		ret = bl_core_set_polarity(PWM_POLARITY_NORMAL);
+	if (ret < 0) {
+		dev_err(dev, "Failed to set backlight polarity\n");
+		return -EIO;
+	}
+
+	/* init brightness to 60% */
+	bl_core_set_pulse_width(unit, 60);
+
+	data = devm_kzalloc(dev, sizeof(struct imanager_backlight_data),
+			GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->idev = idev;
+
+	ret = imanager_backlight_init(dev, data);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int imanager_backlight_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_backlight_data *data = dev_get_drvdata(dev);
+
+	backlight_device_unregister(data->bl);
+
+	return 0;
+}
+
+static struct platform_driver imanager_backlight_driver = {
+	.driver = {
+		.name	= "imanager_backlight",
+	},
+	.probe	= imanager_backlight_probe,
+	.remove	= imanager_backlight_remove,
+};
+
+module_platform_driver(imanager_backlight_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Backlight driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager_backlight");
diff --git a/drivers/video/backlight/imanager-ec-bl.c b/drivers/video/backlight/imanager-ec-bl.c
new file mode 100644
index 0000000..04d2478
--- /dev/null
+++ b/drivers/video/backlight/imanager-ec-bl.c
@@ -0,0 +1,118 @@
+/*
+ * Advantech iManager Backlight/Brightness 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/backlight.h>
+
+struct brightness_level {
+	u32	value	: 7,	/* Brightness Value  - LSB [6..0] */
+		enable	: 1;	/* Brightness Enable - MSB [7] */
+};
+
+struct backlight_ctrl {
+	u32	enable	: 1,	/* Backlight Control Enable - LSB [0] */
+		pwmpol	: 1,	/* PWM Polarity		    - bit [1] */
+		blpol	: 1,	/* Backlight Polarity	    - bit [2] */
+		dnc	: 5;	/* Don't care		    - bit [7..3] */
+};
+
+static const struct imanager_backlight_device *bl;
+
+int bl_core_get_pulse_width(u32 unit)
+{
+	int ret;
+
+	if (WARN_ON(unit >= EC_BLC_MAX_NUM))
+		return -EINVAL;
+
+	ret = imanager_read_byte(EC_CMD_HWP_RD, bl->attr[unit].did);
+	if (ret < 0)
+		pr_err("Failed reading PWM (unit=%d)\n", unit);
+
+	return ret;
+}
+
+int bl_core_set_pulse_width(u32 unit, u32 pwm)
+{
+	int ret;
+
+	if (WARN_ON(unit >= EC_BLC_MAX_NUM))
+		return -EINVAL;
+
+	pwm = pwm > 100 ? 100 : pwm;
+
+	ret = imanager_write_byte(EC_CMD_HWP_WR, bl->attr[unit].did, pwm);
+	if (ret < 0)
+		pr_err("Failed writing PWM (val=%d, unit=%d)\n", pwm, unit);
+
+	return ret;
+}
+
+int bl_core_set_state(u32 unit, bool enable)
+{
+	int ret;
+	u8 val8;
+	struct brightness_level *pl = (struct brightness_level *)&val8;
+
+	if (WARN_ON(unit >= EC_BLC_MAX_NUM))
+		return -EINVAL;
+
+	ret = imanager_acpiram_read_byte(bl->brightness[unit]);
+	if (ret < 0)
+		return ret;
+	val8 = ret;
+
+	pl->enable = enable ? 1 : 0;
+
+	ret = imanager_acpiram_write_byte(bl->attr[unit].did, val8);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int bl_core_set_polarity(u32 polarity)
+{
+	int ret;
+	u8 val8;
+	struct backlight_ctrl *ctrl = (struct backlight_ctrl *)&val8;
+
+	ret = imanager_acpiram_read_byte(EC_ACPIRAM_BLC_CTRL);
+	if (ret < 0)
+		return ret;
+	val8 = ret;
+
+	ctrl->blpol = polarity ? 1 : 0;
+
+	ret = imanager_acpiram_write_byte(EC_ACPIRAM_BLC_CTRL, val8);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int bl_core_init(void)
+{
+	bl = imanager_get_backlight_device();
+	if (!bl)
+		return -ENODEV;
+
+	return 0;
+}
+
diff --git a/include/linux/mfd/imanager/backlight.h b/include/linux/mfd/imanager/backlight.h
new file mode 100644
index 0000000..93ea7cf5
--- /dev/null
+++ b/include/linux/mfd/imanager/backlight.h
@@ -0,0 +1,37 @@
+/*
+ * Advantech iManager Backlight 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 __BACKLIGHT_H__
+#define __BACKLIGHT_H__
+
+#include <linux/types.h>
+
+enum backlight_control {
+	BL_CTRL_DISABLE,
+	BL_CTRL_ENABLE,
+};
+
+enum backlight_unit {
+	UNIT_1,
+	UNIT_2,
+};
+
+int bl_core_init(void);
+
+int bl_core_set_state(u32 unit, bool enable);
+
+int bl_core_set_polarity(u32 polarity);
+
+int bl_core_get_pulse_width(u32 unit);
+int bl_core_set_pulse_width(u32 unit, u32 pwm);
+
+#endif
-- 
2.7.0

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 6/6] Add Advantech iManager Watchdog driver
  2016-01-10 23:31 ` richard.dorsch
                   ` (5 preceding siblings ...)
  (?)
@ 2016-01-10 23:31 ` richard.dorsch
  2016-01-17 19:15   ` Guenter Roeck
       [not found]   ` <1452468675-5827-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  -1 siblings, 2 replies; 22+ messages in thread
From: richard.dorsch @ 2016-01-10 23:31 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>
---
 drivers/watchdog/Kconfig           |  12 ++
 drivers/watchdog/Makefile          |   2 +
 drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
 drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/imanager/wdt.h   |  37 +++++
 5 files changed, 554 insertions(+)
 create mode 100644 drivers/watchdog/imanager-ec-wdt.c
 create mode 100644 drivers/watchdog/imanager-wdt.c
 create mode 100644 include/linux/mfd/imanager/wdt.h

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 1c427be..e555127 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -846,6 +846,18 @@ config ITCO_VENDOR_SUPPORT
 	  devices. At this moment we only have additional support for some
 	  SuperMicro Inc. motherboards.
 
+config IMANAGER_WDT
+	tristate "Advantech iManager Watchdog"
+	depends on MFD_IMANAGER
+	select WATCHDOG_CORE
+	help
+	  This driver provides support for Advantech iManager watchdog
+	  of 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_wdt.
+
 config IT8712F_WDT
 	tristate "IT8712F (Smart Guardian) Watchdog Timer"
 	depends on X86
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 53d4827..647eca87 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -100,6 +100,8 @@ obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
 ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
 obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
 endif
+imanager_wdt-objs := imanager-wdt.o imanager-ec-wdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
diff --git a/drivers/watchdog/imanager-ec-wdt.c b/drivers/watchdog/imanager-ec-wdt.c
new file mode 100644
index 0000000..9c94b21
--- /dev/null
+++ b/drivers/watchdog/imanager-ec-wdt.c
@@ -0,0 +1,170 @@
+/*
+ * Advantech iManager Watchdog 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.
+ *
+ * Note: Current release only implements RESET of the EC WDT.
+ *       In other words, no PWR button, NMI, SCI, IRQ, or WDPin support yet!
+ */
+
+#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/wdt.h>
+
+/* Timer resolution */
+#define WDT_FREQ	10 /* Hz */
+
+enum wdt_ctrl {
+	START = 1,
+	STOP,
+	RST,
+	GET_TIMEOUT,
+	SET_TIMEOUT,
+	STOPBOOT = 8,
+};
+
+struct wdt_event_delay {
+	u16	delay,
+		pwrbtn,
+		nmi,
+		reset,
+		wdpin,
+		sci,
+		dummy;
+};
+
+static const struct imanager_watchdog_device *wd;
+
+static inline int set_timer(enum wdt_ctrl ctrl)
+{
+	if (WARN_ON(ctrl == SET_TIMEOUT))
+		return -EINVAL;
+
+	return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
+}
+
+static int
+wdt_ctrl(enum wdt_ctrl ctrl, enum wdt_event type, unsigned int timeout)
+{
+	u16 val;
+	int ret;
+	struct ec_message msg = {
+		.rlen = 0,
+		.wlen = 0,
+	};
+	u8 *fevent = &msg.u.data[0];
+	struct wdt_event_delay *event =
+				(struct wdt_event_delay *)&msg.u.data[1];
+
+	switch (ctrl) {
+	case SET_TIMEOUT:
+		memset(event, 0xff, sizeof(*event));
+		msg.wlen = sizeof(*event);
+		*fevent = 0;
+		val = (!timeout) ? 0xffff : swab16(timeout * WDT_FREQ);
+
+		switch (type) {
+		case DELAY:
+			event->delay = val;
+			break;
+		case PWRBTN:
+			event->pwrbtn = val;
+			break;
+		case NMI:
+			event->nmi = val;
+			break;
+		case RESET:
+			event->reset = val;
+			break;
+		case WDPIN:
+			event->wdpin = val;
+			break;
+		case SCI:
+			event->sci = val;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg);
+		if (ret < 0) {
+			pr_err("Failed to set timeout\n");
+			return ret;
+		}
+		break;
+	case START:
+	case STOP:
+	case RST:
+	case STOPBOOT:
+		/* simple command, no data */
+		return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
+	default:
+		return -EINVAL;
+	}
+
+	return timeout;
+}
+
+int wdt_core_start_timer(void)
+{
+	return set_timer(START);
+}
+
+int wdt_core_stop_timer(void)
+{
+	return set_timer(STOP);
+}
+
+int wdt_core_reset_timer(void)
+{
+	return set_timer(RST);
+}
+
+int wdt_core_stop_boot_timer(void)
+{
+	return set_timer(STOPBOOT);
+}
+
+inline int wdt_core_set_timeout(enum wdt_event type, u32 timeout)
+{
+	return wdt_ctrl(SET_TIMEOUT, type, timeout);
+}
+
+int wdt_core_disable_all(void)
+{
+	struct ec_message msg = {
+		.rlen = 0,
+		.wlen = sizeof(struct wdt_event_delay),
+	};
+	struct wdt_event_delay *event =
+				(struct wdt_event_delay *)&msg.u.data[1];
+
+	memset(event, 0xff, sizeof(*event));
+
+	return  (wdt_core_stop_timer() ||
+		 wdt_core_stop_boot_timer() ||
+		 imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg));
+}
+
+int wdt_core_init(void)
+{
+	wd = imanager_get_watchdog_device();
+	if (!wd)
+		return -ENODEV;
+
+	return 0;
+}
+
diff --git a/drivers/watchdog/imanager-wdt.c b/drivers/watchdog/imanager-wdt.c
new file mode 100644
index 0000000..10f8e22
--- /dev/null
+++ b/drivers/watchdog/imanager-wdt.c
@@ -0,0 +1,333 @@
+/*
+ * Advantech iManager Watchdog 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/wdt.h>
+
+#define WATCHDOG_TIMEOUT 30 /* in seconds */
+
+static unsigned long last_updated = -1;
+
+static uint timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+	"Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
+	__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+	"Watchdog cannot be stopped once started (default="
+	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int wdt_start_timer(void)
+{
+	int ret;
+
+	ret = wdt_core_start_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = jiffies;
+
+	return 0;
+}
+
+static int wdt_stop_timer(void)
+{
+	int ret;
+
+	ret = wdt_core_stop_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = 0;
+
+	return 0;
+}
+
+static int wdt_ping(void)
+{
+	int ret;
+
+	ret = wdt_core_reset_timer();
+	if (ret < 0)
+		return ret;
+
+	last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set(unsigned int _timeout)
+{
+	int ret;
+
+	if (timeout != _timeout)
+		timeout = _timeout;
+
+	ret = wdt_core_set_timeout(PWRBTN, timeout);
+	if (ret < 0)
+		return ret;
+
+	if (last_updated != 0)
+		last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt_dev,
+				    unsigned int timeout)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = imanager_wdt_set(timeout);
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to set timeout\n");
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static unsigned int imanager_wdt_get_timeleft(struct watchdog_device *wdt_dev)
+{
+	unsigned int timeleft = 0;
+	unsigned long time_diff = ((jiffies - last_updated) / HZ);
+
+	if (last_updated && (timeout > time_diff))
+		timeleft = timeout - time_diff;
+
+	return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_start_timer();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to start timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_stop_timer();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to stop timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt_dev)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	int ret = 0;
+
+	mutex_lock(&idev->lock);
+
+	ret = wdt_ping();
+	if (ret < 0)
+		dev_err(wdt_dev->dev, "Failed to reset timer\n");
+
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static long imanager_wdt_ioctl(struct watchdog_device *wdt_dev,
+			       unsigned int cmd, unsigned long arg)
+{
+	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int ret = 0;
+	int timeval, options;
+	static const struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING |
+			   WDIOF_SETTIMEOUT |
+			   WDIOF_MAGICCLOSE,
+		.firmware_version = 0,
+		.identity = "imanager_wdt",
+	};
+
+	mutex_lock(&idev->lock);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &ident, sizeof(ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		if (options & WDIOS_DISABLECARD)
+			wdt_stop_timer();
+		if (options & WDIOS_ENABLECARD) {
+			wdt_ping();
+			wdt_start_timer();
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		wdt_ping();
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(timeval, p)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		if (imanager_wdt_set(timeval)) {
+			ret = -EINVAL;
+			goto out;
+		}
+		wdt_ping();
+		/* Fall through */
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(timeout, p);
+		break;
+	case WDIOC_GETTIMELEFT:
+		timeval = imanager_wdt_get_timeleft(wdt_dev);
+		ret = put_user(timeval, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+out:
+	mutex_unlock(&idev->lock);
+
+	return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+	.options		= WDIOF_SETTIMEOUT |
+				  WDIOF_KEEPALIVEPING |
+				  WDIOF_MAGICCLOSE,
+	.identity		= "imanager_wdt",
+	.firmware_version	= 0,
+};
+
+static const struct watchdog_ops imanager_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= imanager_wdt_start,
+	.stop		= imanager_wdt_stop,
+	.ping		= imanager_wdt_ping,
+	.set_timeout	= imanager_wdt_set_timeout,
+	.get_timeleft	= imanager_wdt_get_timeleft,
+	.ioctl		= imanager_wdt_ioctl,
+};
+
+static struct watchdog_device imanager_wdt_dev = {
+	.info		= &imanager_wdt_info,
+	.ops		= &imanager_wdt_ops,
+	.timeout	= WATCHDOG_TIMEOUT,
+	.min_timeout	= 1,
+	.max_timeout	= 0xfffe,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+	int ret;
+
+	if (!idev) {
+		dev_err(dev, "Invalid platform data\n");
+		return -EINVAL;
+	}
+
+	watchdog_set_nowayout(&imanager_wdt_dev, nowayout);
+	watchdog_set_drvdata(&imanager_wdt_dev, idev);
+
+	ret = watchdog_register_device(&imanager_wdt_dev);
+	if (ret) {
+		dev_err(dev, "Failed to register watchdog device\n");
+		return ret;
+	}
+
+	ret = wdt_core_init();
+	if (ret) {
+		dev_err(dev, "Failed to initialize watchdog core\n");
+		goto unregister_driver;
+	}
+
+	wdt_core_disable_all();
+
+	imanager_wdt_set_timeout(&imanager_wdt_dev, timeout);
+
+	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+	return 0;
+
+unregister_driver:
+	watchdog_unregister_device(&imanager_wdt_dev);
+	platform_set_drvdata(pdev, NULL);
+
+	return ret;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+	wdt_core_disable_all();
+
+	watchdog_unregister_device(&imanager_wdt_dev);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver imanager_wdt_driver = {
+	.driver = {
+		.name	= "imanager_wdt",
+	},
+	.probe	= imanager_wdt_probe,
+	.remove	= imanager_wdt_remove,
+};
+
+module_platform_driver(imanager_wdt_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Watchdog Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager_wdt");
diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
new file mode 100644
index 0000000..eb709b7
--- /dev/null
+++ b/include/linux/mfd/imanager/wdt.h
@@ -0,0 +1,37 @@
+/*
+ * Advantech iManager Watchdog 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 __WDT_H__
+#define __WDT_H__
+
+#include <linux/types.h>
+
+enum wdt_event {
+	DELAY,
+	PWRBTN,
+	NMI,
+	RESET,
+	WDPIN,
+	SCI,
+};
+
+int wdt_core_init(void);
+
+int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
+
+int wdt_core_disable_all(void);
+int wdt_core_start_timer(void);
+int wdt_core_stop_timer(void);
+int wdt_core_reset_timer(void);
+int wdt_core_stop_boot_timer(void);
+
+#endif
-- 
2.7.0

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 1/6] Add Advantech iManager MFD core driver
  2016-01-10 23:31 ` [PATCH v3 1/6] Add Advantech iManager MFD core driver richard.dorsch
@ 2016-01-11  0:54   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 22+ messages in thread
From: Krzysztof Kozlowski @ 2016-01-11  0:54 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	lee.jones, jdelvare, linux, wim, jo.sunga

2016-01-11 8:31 GMT+09:00  <richard.dorsch@gmail.com>:
> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
>
> 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@gmail.com>
> ---
>  Documentation/devicetree/bindings/mfd/imanager.txt |   33 +
>  MAINTAINERS                                        |   13 +
>  drivers/mfd/Kconfig                                |   20 +
>  drivers/mfd/Makefile                               |    2 +
>  drivers/mfd/imanager-core.c                        |  287 +++++
>  drivers/mfd/imanager-ec.c                          | 1345 ++++++++++++++++++++
>  include/linux/mfd/imanager/core.h                  |   31 +
>  include/linux/mfd/imanager/ec.h                    |  210 +++
>  8 files changed, 1941 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@advantech.com>
> +
> +
> +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

Hi,

This does not describe the bindings. It is not the place for it. If
you have bindings please put the them as separate patch (first one).

> +
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 233f834..8d25fdc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5391,6 +5391,19 @@ M:       Stanislaw Gruszka <stf_xl@wp.pl>
>  S:     Maintained
>  F:     drivers/usb/atm/ueagle-atm.c
>
> +IMANAGER ADVANTECH EC DRIVER
> +M:     Richard Vidal-Dorsch <richard.dorsch@advantech.com>
> +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@roeck-us.net>
>  L:     lm-sensors@lm-sensors.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..d573ecb
> --- /dev/null
> +++ b/drivers/mfd/imanager-core.c
> @@ -0,0 +1,287 @@
> +/*
> + * Advantech iManager MFD core 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/module.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/ioport.h>

Do you need ioport?

> +#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;

Wait, why? Why you create a platform device?

> +
> +enum imanager_cells {
> +       IMANAGER_BL,
> +       IMANAGER_GPIO,
> +       IMANAGER_HWMON,
> +       IMANAGER_I2C,
> +       IMANAGER_WDT,
> +};

Is the enum uses beside of the index in mfd_cell array? If not, remove it.

> +
> +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)

static

> +{
> +       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);

You made so many of these just to print the name/version... What is
the purpose of exposing this to user-space? Who will use it and for
what purpose? What need does it fulfill?

For debugging purposes usually dev_info() is sufficient. If not (why
not?) I would expect at most one sysfs entry.

Anyway you are making this as part of ABI so it requires
documentation: Documentation/ABI.

> +
> +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);

This is part of probe, not __init. Beside that I don't have a clue why
entire imanager_platform_create() exists. :)

> +       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;

Wait? Which pdev? Local or file-scope? Device cannot be probed for a
NULL device!

> +
> +       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);

If devices were created in __init then they must be removed in __exit,
not in remove()... so this is also a good hint that you shouldn't put
mfd_add_devices() in __init.

> +
> +       return 0;
> +}
> +
> +static struct platform_driver imanager_driver = {
> +       .driver = {
> +               .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;

This confuses me even more: probing before register?

> +
> +       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);

The driver does not support DT... what is the purpose of DT bindings document?

> +
> +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@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/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;

Why this is file-scope, not stored in device state?

> +
> +static const struct ec_devtbl devtbl[] = {

I didn't quite get the purpose of this array.

> +       { 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);
> +}

Factoring it to inline does not help. Just use udelay() where you need to.

> +
> +static int wait_ibf_cleared(void)
> +{
> +       int i = 0;
> +
> +       do {
> +               if (!(inb(IT8516_CMD_PORT) & BIT(1)))

Please define a developer-friendly name for 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))

ditto

> +                       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);

Here and in other places - this should be dev_err.

> +               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);

Here and in rest of file: I am not sure whether mfd tree is suitable
for this. I assume you want to share it between all of the drivers as
a part of common transport layer?

> +
> +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)

Naming of this and other similar functions is confusing to me. It
suggests that some device will be GET and returned. If not returned
directly then put under argument (like **). But it behaves completely
differently.

> +{
> +       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);

dev_err()... but is it really an error? Critical error? Maybe dev_info
or dev_warn?

> +                               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);

ditto

> +                               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);

1. All of these helpers should have rather a format of:
to_blablabla(pointer) {
  container_of(...)
}

Don't use the globals, don't rely on them. If you have to rely, then why?

2. You are mixing the order of declarations - static, export, static.
Please be consistent. First static, last exported.

3. Do you really need all of these exported functions? I didn't check
rest of patches so I am just guessing - all of them are used?

> +
> +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;

So this can be called with NULL? Or cannot? If cannot, then no warn,
just return... but this is a static function so it has only local
users which means that you control the users. How can the info be NULL
then?

> +
> +       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));

Isn't info allocated with kzalloc()? Why you need memset?

> +       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';

If there is no '-' and contents exceeded PCB_NAME_MAX_SIZE then no
need of NULL-terminating?

> +
> +       return 0;
> +}
> +
> +static int ec_get_fw_info(struct ec_info *info)
> +{
> +       int ret;
> +
> +       if (WARN_ON(!info))
> +               return -EINVAL;

The same weird pattern. Just no.

> +
> +       ret = ec_get_version(&info->version);
> +       if (ret)
> +               return ret;
> +
> +       return ec_get_pcb_name(info);
> +}
> +
> +static int ec_init(void)

init not marked as __init looks a little weird. It also does not
accept any arguments so again you depend on some magic
globals/file-scope variables.

> +{
> +       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)

This is not a probe. Or it is seriously broken for a probe. :)

> +{
> +       int chipid = ec_read_chipid(EC_BASE_ADDR);
> +
> +       memset((void *)&ec, 0, sizeof(ec));

Why? The driver should dynamically allocate its state during a probe
(real probe) and use kzalloc so no memset needed.

> +
> +       switch (chipid) {
> +       case EC_DEVID_IT8516:
> +               pr_err("EC IT8516 not supported\n");

dev_err.
The fact that you cannot refer to a device here is also interesting...
What is the context for this function?

> +               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@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 __CORE_H__
> +#define __CORE_H__

Bigger protection - like __LINUX_MFD_IMANAGER_CORE_H__ (probably LINUX
is too much... but anyway...).

> +
> +#include <linux/mutex.h>
> +#include <linux/io.h>
> +#include <linux/types.h>

Why do you need io.h and 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@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 __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 };

Is the order anyhow related to order of chip_names array?

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 0/6] Add Advantech iManager EC driver set
  2016-01-10 23:31 ` richard.dorsch
                   ` (6 preceding siblings ...)
  (?)
@ 2016-01-11  0:54 ` Guenter Roeck
  -1 siblings, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2016-01-11  0:54 UTC (permalink / raw)
  To: richard.dorsch, linux-kernel
  Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
	jdelvare, wim, jo.sunga

On 01/10/2016 03:31 PM, richard.dorsch@gmail.com wrote:
> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
>
> The Advantech iManager is an embedded controller (depending on board type,
> ITE it8518/it8528) on which a custom firmware runs. All drivers (GPIO, I2C,
> HWmon, Watchdog, and backlight) are being managed by the imanager mfd driver.
> The imanager mfd driver acts as a 'gateway' and handles all communications
> between the EC and sub-drivers (GPIO, I2C, ...). The feature specific
> communication portions are split up into core files such as
> imanager-ec-[gpio, i2c, ...].
>
> Changes from v2:
> I forgot to remove .owner from other drivers too... silly me!
> - Remove .owner from platform_driver in:
>    drivers/gpio/imanager-bl
>    drivers/gpio/imanager-core.c
>    drivers/gpio/imanager-i2c.c
>    drivers/gpio/imanager-hwmon.c
>

Hi Richard,

in case there are more alerts from the 0day build system, or minor feedback
from others, please give us time to actually review the code before
resending the series. I have not even had time to look into v1 (and I bet
no one else had either), and you already sent v3, just to remove various
.owner fields.

Also, please be a bit patient. The commit window just opened, meaning
all maintainers will be busy preparing to send pull requests to Linus.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 5/6] Add Advantech iManager Backlight driver
  2016-01-10 23:31 ` [PATCH v3 5/6] Add Advantech iManager Backlight driver richard.dorsch
@ 2016-01-11 10:23   ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-11 10:23 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	jdelvare, linux, wim, jo.sunga

On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> 
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>  drivers/video/backlight/Kconfig          |  12 ++
>  drivers/video/backlight/Makefile         |   2 +
>  drivers/video/backlight/imanager-bl.c    | 198 +++++++++++++++++++++++++++++++
>  drivers/video/backlight/imanager-ec-bl.c | 118 ++++++++++++++++++
>  include/linux/mfd/imanager/backlight.h   |  37 ++++++
>  5 files changed, 367 insertions(+)
>  create mode 100644 drivers/video/backlight/imanager-bl.c
>  create mode 100644 drivers/video/backlight/imanager-ec-bl.c
>  create mode 100644 include/linux/mfd/imanager/backlight.h

How do you expect this to be reviewed if you don't send it to the
reviewer?

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 3/6] Add Advantech iManager HWmon driver
  2016-01-10 23:31 ` [PATCH v3 3/6] Add Advantech iManager HWmon driver richard.dorsch
@ 2016-01-17 18:53   ` Guenter Roeck
  2016-01-18  7:40     ` Lee Jones
  0 siblings, 1 reply; 22+ messages in thread
From: Guenter Roeck @ 2016-01-17 18:53 UTC (permalink / raw)
  To: richard.dorsch, linux-kernel
  Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
	jdelvare, wim, jo.sunga

On 01/10/2016 03:31 PM, richard.dorsch@gmail.com wrote:
> 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     | 1057 ++++++++++++++++++++++++++++++++++++
>   include/linux/mfd/imanager/hwmon.h |  120 ++++
>   6 files changed, 1856 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
>

Please run the patch)es) through checkpatch --strict.
In many cases continuation line alignment is off by one character.

> 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>

Please list include files in alphabetic order.

> +
> +#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 */

Please consider using BIT() here instead. The above makes the code very hardware
and possibly compiler dependent.

> +};
> +
> +struct fan_alert_limit {
> +	u16	fan0_min,
> +		fan0_max,
> +		fan1_min,
> +		fan1_max,
> +		fan2_min,
> +		fan2_max;
> +};

You probably want to add __packed here.

> +
> +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 */

Same here. I don't see a value in those structures,
except that they make the code more difficult to read and more error prone
(see below).

> +};
> +
> +/*----------------------------------------------------*
> + * 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 */

Same here.

> +};
> +
> +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;
> +

cfg can never be NULL.

Also, the use of both cfg and _cfg is confusing. Can you find a better name for _cfg ?

> +	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;

Please use ENXIO. We already know that the device is present.

> +	}
> +
> +	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;
> +
You are checking this value already when reading the configuration.
No need to check it again. Also, ENODEV would be wrong at this point.

> +	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;

EFAULT is supposed to mean a bad memory address. It is not appropriate here.
EIO, maybe, or ENXIO.


> +	default:
> +		pr_err("Unknown error status of fan%d (%d)\n", fnum, ret);

What distinguishes this error from the other errors that it warrants
an extra error message to the console ?

> +		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);

be16_to_cpu()

> +}
> +
> +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;
> +
This is really not a good idea. It writes an 8-bit value into a 32-bit pointer.
Given that the return value can never be negative, I would suggest to return
both error and return value in the function return code. This would reduce the
code to

	return imanager_acpiram_read_byte(EC_ACPIRAM_FAN_ALERT);

> +	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);

This assumes a specific endianness on both ends. You probably want
be16_to_cpu() here.

> +		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);

It would be desirable if you could use dev_err here and elsewhere,
ie pass the hwmon device around. Possibly keep it in struct
imanager_hwmon_data.

> +		return -EINVAL;

EINVAL is supposed to mean "Invalid Argument" from user space.
ENXIO, maybe ?

> +	}
> +
> +	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);

cpu_to_be16()

> +		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));

I think those functions need mutex protection. I _think_
the calling code does provide that. Please add a note to the function header
indicating that the function must be called with a lock held, and which one.

> +}
> +
> +/* HWM CORE API */
> +
> +const char *hwm_core_adc_get_label(int num)
> +{
> +	if (WARN_ON(num >= hwmon->adc.num))
> +		return NULL;

Please no unnecessary error checks. This should never happen
unless your code is buggy, and then we _want_ it to crash.

> +
> +	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)
> +{

Return value is never checked.

> +	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));
> +

sizeof(*fan) would be better and less error prone in those situations.

> +	ret = hwm_read_fan_config(num, &cfg);
> +	if (ret < 0)
> +		return ret;
> +

It is really hard to find a consistency in error messages.
Any chance you can just drop all of them ?

> +	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.
> +	 */

Why ? Is this a bug ?

> +	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;

Overall I would prefer less noisyness on errors. User space will be alerted
of the errors with function return values. If the entire kernel
would be as noisy as this driver, the log would be all but unusable.

> +	}
> +
> +	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)
> +{

Why return an int if the calling code doesn't check it ?

> +	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;

Your error messages are inconsistent. In some cases, you print an error if
hwm_read_fan_config() returns an error, sometimes not. And hwm_read_fan_config()
sometimes alreasdy prints an error message, sometimes not.

Please make it consistent. Only one error message for a given failure,
and if you do print an error message

> +	}
> +
> +	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..f836c7e
> --- /dev/null
> +++ b/drivers/hwmon/imanager-hwmon.c
> @@ -0,0 +1,1057 @@
> +/*
> + * 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);
> +

That isn't the idea of alarm attributes; user space can do that calculation.
Please drop the attribute unless the EC can report it. It would make much more
sense to report an alarm if valid is false (presumably that _does_ indicate
a problem).

> +	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;
> +	}
> +

Another attribute which should only be implemented if provided by the hardware.
Please don't implement alarm, lowest, highest, average etc in software.

> +	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;
> +}

lowest/highest attributes are meant to be used if the chip provides the values.
That is not the case here, meaning that the attributes do not return
real lowest/highest values but "the highest/lowest reported value", which can
as well be calculated and maintained by user space. Please drop the attributes.

> +
> +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));

Please no unnecessary ( ).

> +}
> +
> +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);

Please use NULL to pass NULL pointers.

> +		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);

Why not just set index to [0..n-1] instead of always recalculating it ?

> +}
> +
> +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)

Why would mode ever be != 0 here ? Either this is unnecessary, or
you want to do something else.

> +			hwm_core_fan_set_ctrl(nr, MODE_FULL, CTRL_PWM, 100,
> +					      fan->pulse, NULL, NULL);
> +		break;
> +	case 1:
> +		if (mode != 1)

Same here.

> +			hwm_core_fan_set_ctrl(nr, MODE_MANUAL, CTRL_PWM, 0,
> +					      fan->pulse, NULL, NULL);
> +		break;
> +	case 2:
> +		if (mode != 2)

Same here.

> +			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;
> +
Please no. That messes up user space code for no good reason.
Why not just return the previously stored type ?

Also, a show function should not return -EINVAL. The user did not provide
an invalid value, and the query is not unreasonable

> +	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;
> +
Please explain. DC/PWM mode should be settable and readable under all circumstances.

> +	mutex_lock(&data->idev->lock);
> +
> +	hwm_core_fan_set_ctrl(nr, fan->mode, val ? CTRL_RPM : CTRL_PWM,

Ok, I am getting confused. The mode attribute is supposed to set DC / PWM mode,
not PWM/RPM mode.

> +			      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.
> +	 */

/*
  * Please use non-networking-subsystem multi-line comments.
  */

> +
> +	mutex_lock(&data->idev->lock);
> +
> +	hwm_core_fan_set_temp_limit(nr, val, val, temp->max);

This sounds like the temp limit attributes are not used as temperature
limits, but as fan control limits. Wrong attributes in that case.

You should use auto_point attributes if the limits are used for fan control.

> +	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;
> +

This is highly questionable. What if the user wants to switch modes ?
In what order do attributes have to be set for this to work ?

It would be better to accept attributes in any oder.

> +	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);
> +

Are you really setting pwm_min and pwm_max here, or are these again
pwm set points ? In the latter case, auto_point attributes would make
more sense.

> +	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;

So if max_count is 2 or below you don't have any voltage attributes ? Or only 1/2 ?

The comments below suggest that max_count is always >= 3.
If so, this function would be unnecessary.

> +
> +	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 */

Please no unnecessary ( ).

> +		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;

Invalid ? Seems to be missing. Please return -ENODEV in this case.
I also wonder if this really needs an extra error message.

> +	}
> +
> +	err = hwm_core_init();
> +	if (err) {
> +		dev_err(dev, "Hwmon core init failed\n");
> +		return -EIO;

The returned error is ENODEV. Please don't hide it (static analysis checkers
will complain). Also, the error suggests that there was no hwmon device,
so ENODEV (as returned) is more appropriate, and it should not need
an extra error message - either it is on purpose or an implementation
bug, and both should be caught in development.

> +	}
> +
> +	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]);

Why ignore errors from those functions ?

> +	}
> +
> +	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 = {
> +		.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
>


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 6/6] Add Advantech iManager Watchdog driver
  2016-01-10 23:31 ` [PATCH v3 6/6] Add Advantech iManager Watchdog driver richard.dorsch
@ 2016-01-17 19:15   ` Guenter Roeck
       [not found]   ` <1452468675-5827-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  1 sibling, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2016-01-17 19:15 UTC (permalink / raw)
  To: richard.dorsch, linux-kernel
  Cc: lm-sensors, linux-i2c, linux-watchdog, linux-gpio, lee.jones,
	jdelvare, wim, jo.sunga

On 01/10/2016 03:31 PM, richard.dorsch@gmail.com wrote:
> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
>
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>   drivers/watchdog/Kconfig           |  12 ++
>   drivers/watchdog/Makefile          |   2 +
>   drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
>   drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
>   include/linux/mfd/imanager/wdt.h   |  37 +++++
>   5 files changed, 554 insertions(+)
>   create mode 100644 drivers/watchdog/imanager-ec-wdt.c
>   create mode 100644 drivers/watchdog/imanager-wdt.c
>   create mode 100644 include/linux/mfd/imanager/wdt.h
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 1c427be..e555127 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -846,6 +846,18 @@ config ITCO_VENDOR_SUPPORT
>   	  devices. At this moment we only have additional support for some
>   	  SuperMicro Inc. motherboards.
>
> +config IMANAGER_WDT
> +	tristate "Advantech iManager Watchdog"
> +	depends on MFD_IMANAGER
> +	select WATCHDOG_CORE
> +	help
> +	  This driver provides support for Advantech iManager watchdog
> +	  of 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_wdt.
> +
>   config IT8712F_WDT
>   	tristate "IT8712F (Smart Guardian) Watchdog Timer"
>   	depends on X86
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 53d4827..647eca87 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -100,6 +100,8 @@ obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
>   ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
>   obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
>   endif
> +imanager_wdt-objs := imanager-wdt.o imanager-ec-wdt.o
> +obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
>   obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
>   obj-$(CONFIG_IT87_WDT) += it87_wdt.o
>   obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> diff --git a/drivers/watchdog/imanager-ec-wdt.c b/drivers/watchdog/imanager-ec-wdt.c
> new file mode 100644
> index 0000000..9c94b21
> --- /dev/null
> +++ b/drivers/watchdog/imanager-ec-wdt.c
> @@ -0,0 +1,170 @@
> +/*
> + * Advantech iManager Watchdog 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.
> + *
> + * Note: Current release only implements RESET of the EC WDT.
> + *       In other words, no PWR button, NMI, SCI, IRQ, or WDPin support yet!
> + */
> +
> +#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/wdt.h>
> +
> +/* Timer resolution */
> +#define WDT_FREQ	10 /* Hz */
> +
> +enum wdt_ctrl {
> +	START = 1,
> +	STOP,
> +	RST,
> +	GET_TIMEOUT,
> +	SET_TIMEOUT,
> +	STOPBOOT = 8,
> +};
> +
> +struct wdt_event_delay {
> +	u16	delay,
> +		pwrbtn,
> +		nmi,
> +		reset,
> +		wdpin,
> +		sci,
> +		dummy;
> +};
> +
> +static const struct imanager_watchdog_device *wd;
> +

I am not entirely happy with those static variables. Any chance
to just read it in the wdt file and pass it around ?

> +static inline int set_timer(enum wdt_ctrl ctrl)
> +{
> +	if (WARN_ON(ctrl == SET_TIMEOUT))
> +		return -EINVAL;
> +

Unnecessary error message and warning.

> +	return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);
> +}
> +
> +static int
> +wdt_ctrl(enum wdt_ctrl ctrl, enum wdt_event type, unsigned int timeout)
> +{
> +	u16 val;
> +	int ret;
> +	struct ec_message msg = {
> +		.rlen = 0,
> +		.wlen = 0,
> +	};
> +	u8 *fevent = &msg.u.data[0];
> +	struct wdt_event_delay *event =
> +				(struct wdt_event_delay *)&msg.u.data[1];
> +
> +	switch (ctrl) {
> +	case SET_TIMEOUT:
> +		memset(event, 0xff, sizeof(*event));
> +		msg.wlen = sizeof(*event);
> +		*fevent = 0;
> +		val = (!timeout) ? 0xffff : swab16(timeout * WDT_FREQ);

cpu_to_be16()

> +
> +		switch (type) {
> +		case DELAY:
> +			event->delay = val;
> +			break;
> +		case PWRBTN:
> +			event->pwrbtn = val;
> +			break;
> +		case NMI:
> +			event->nmi = val;
> +			break;
> +		case RESET:
> +			event->reset = val;
> +			break;
> +		case WDPIN:
> +			event->wdpin = val;
> +			break;
> +		case SCI:
> +			event->sci = val;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		ret = imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg);
> +		if (ret < 0) {
> +			pr_err("Failed to set timeout\n");

Please consider dropping the error messages.

> +			return ret;
> +		}
> +		break;
> +	case START:
> +	case STOP:
> +	case RST:
> +	case STOPBOOT:
> +		/* simple command, no data */
> +		return imanager_msg_write(EC_CMD_WDT_CTRL, ctrl, NULL);

Why not use simple(r) wrappers for those or just call imanager_msg_write()
directly, as you do it for set_timer() ?

Actually, you never call wdt_ctrl() with anything but SET_TIMEOUT as parameter,
so everything else in this function seems to be unused.

> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return timeout;
> +}
> +
> +int wdt_core_start_timer(void)
> +{
> +	return set_timer(START);
> +}
> +
> +int wdt_core_stop_timer(void)
> +{
> +	return set_timer(STOP);
> +}
> +
> +int wdt_core_reset_timer(void)
> +{
> +	return set_timer(RST);
> +}
> +
> +int wdt_core_stop_boot_timer(void)
> +{
> +	return set_timer(STOPBOOT);
> +}
> +
> +inline int wdt_core_set_timeout(enum wdt_event type, u32 timeout)
> +{
> +	return wdt_ctrl(SET_TIMEOUT, type, timeout);
> +}
> +
> +int wdt_core_disable_all(void)

What is the point of a return value which is never checked ?

> +{
> +	struct ec_message msg = {
> +		.rlen = 0,
> +		.wlen = sizeof(struct wdt_event_delay),
> +	};
> +	struct wdt_event_delay *event =
> +				(struct wdt_event_delay *)&msg.u.data[1];
> +
> +	memset(event, 0xff, sizeof(*event));
> +
> +	return  (wdt_core_stop_timer() ||
> +		 wdt_core_stop_boot_timer() ||
> +		 imanager_msg_write(EC_CMD_WDT_CTRL, SET_TIMEOUT, &msg));

Please use individual error checks here.

> +}
> +
> +int wdt_core_init(void)
> +{
> +	wd = imanager_get_watchdog_device();
> +	if (!wd)
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
> diff --git a/drivers/watchdog/imanager-wdt.c b/drivers/watchdog/imanager-wdt.c
> new file mode 100644
> index 0000000..10f8e22
> --- /dev/null
> +++ b/drivers/watchdog/imanager-wdt.c
> @@ -0,0 +1,333 @@
> +/*
> + * Advantech iManager Watchdog 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/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/watchdog.h>
> +#include <linux/notifier.h>
> +#include <linux/reboot.h>
> +#include <linux/uaccess.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/imanager/core.h>
> +#include <linux/mfd/imanager/wdt.h>
> +
> +#define WATCHDOG_TIMEOUT 30 /* in seconds */
> +
> +static unsigned long last_updated = -1;
> +
> +static uint timeout = WATCHDOG_TIMEOUT;
> +module_param(timeout, uint, 0);
> +MODULE_PARM_DESC(timeout,
> +	"Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
> +	__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> +	"Watchdog cannot be stopped once started (default="
> +	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static int wdt_start_timer(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_start_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int wdt_stop_timer(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_stop_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = 0;
> +
> +	return 0;
> +}
> +
> +static int wdt_ping(void)
> +{
> +	int ret;
> +
> +	ret = wdt_core_reset_timer();
> +	if (ret < 0)
> +		return ret;
> +
> +	last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int imanager_wdt_set(unsigned int _timeout)
> +{
> +	int ret;
> +
> +	if (timeout != _timeout)
> +		timeout = _timeout;
> +
> +	ret = wdt_core_set_timeout(PWRBTN, timeout);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (last_updated != 0)
> +		last_updated = jiffies;
> +
> +	return 0;
> +}
> +
> +static int imanager_wdt_set_timeout(struct watchdog_device *wdt_dev,
> +				    unsigned int timeout)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;
> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = imanager_wdt_set(timeout);
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to set timeout\n");
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static unsigned int imanager_wdt_get_timeleft(struct watchdog_device *wdt_dev)
> +{
> +	unsigned int timeleft = 0;
> +	unsigned long time_diff = ((jiffies - last_updated) / HZ);
> +
> +	if (last_updated && (timeout > time_diff))
> +		timeleft = timeout - time_diff;
> +

This is supposed to be provided by the hardware. Please do not implement
get_timeleft in software (if we wanted to do that we could do it in the
watchdog core).

> +	return timeleft;
> +}
> +
> +static int imanager_wdt_start(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;

Unnecessary variable initialization.

> +
> +	mutex_lock(&idev->lock);
> +

The watchdog core handles locking for watchdog devices.

> +	ret = wdt_start_timer();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to start timer\n");
> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_stop(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;

Unnecessary variable initialization.

> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = wdt_stop_timer();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to stop timer\n");
> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_ping(struct watchdog_device *wdt_dev)
> +{
> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	int ret = 0;
> +
> +	mutex_lock(&idev->lock);
> +
> +	ret = wdt_ping();
> +	if (ret < 0)
> +		dev_err(wdt_dev->dev, "Failed to reset timer\n");

Please consider dropping the error messages.

> +
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static long imanager_wdt_ioctl(struct watchdog_device *wdt_dev,
> +			       unsigned int cmd, unsigned long arg)
> +{

Why do you re-implement this function ?

> +	struct imanager_device_data *idev = watchdog_get_drvdata(wdt_dev);
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	int ret = 0;
> +	int timeval, options;
> +	static const struct watchdog_info ident = {
> +		.options = WDIOF_KEEPALIVEPING |
> +			   WDIOF_SETTIMEOUT |
> +			   WDIOF_MAGICCLOSE,
> +		.firmware_version = 0,
> +		.identity = "imanager_wdt",
> +	};
> +
> +	mutex_lock(&idev->lock);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &ident, sizeof(ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			wdt_stop_timer();
> +		if (options & WDIOS_ENABLECARD) {
> +			wdt_ping();
> +			wdt_start_timer();
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		wdt_ping();
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(timeval, p)) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		if (imanager_wdt_set(timeval)) {
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +		wdt_ping();
> +		/* Fall through */
> +	case WDIOC_GETTIMEOUT:
> +		ret = put_user(timeout, p);
> +		break;
> +	case WDIOC_GETTIMELEFT:
> +		timeval = imanager_wdt_get_timeleft(wdt_dev);
> +		ret = put_user(timeval, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +out:
> +	mutex_unlock(&idev->lock);
> +
> +	return ret;
> +}
> +
> +static const struct watchdog_info imanager_wdt_info = {
> +	.options		= WDIOF_SETTIMEOUT |
> +				  WDIOF_KEEPALIVEPING |
> +				  WDIOF_MAGICCLOSE,
> +	.identity		= "imanager_wdt",
> +	.firmware_version	= 0,
> +};
> +
> +static const struct watchdog_ops imanager_wdt_ops = {
> +	.owner		= THIS_MODULE,
> +	.start		= imanager_wdt_start,
> +	.stop		= imanager_wdt_stop,
> +	.ping		= imanager_wdt_ping,
> +	.set_timeout	= imanager_wdt_set_timeout,
> +	.get_timeleft	= imanager_wdt_get_timeleft,
> +	.ioctl		= imanager_wdt_ioctl,
> +};
> +
> +static struct watchdog_device imanager_wdt_dev = {
> +	.info		= &imanager_wdt_info,
> +	.ops		= &imanager_wdt_ops,
> +	.timeout	= WATCHDOG_TIMEOUT,
> +	.min_timeout	= 1,
> +	.max_timeout	= 0xfffe,
> +};
> +
> +static int imanager_wdt_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
> +	int ret;
> +
> +	if (!idev) {
> +		dev_err(dev, "Invalid platform data\n");

-ENODEV, and please no error message here.

> +		return -EINVAL;
> +	}
> +
> +	watchdog_set_nowayout(&imanager_wdt_dev, nowayout);
> +	watchdog_set_drvdata(&imanager_wdt_dev, idev);
> +
> +	ret = watchdog_register_device(&imanager_wdt_dev);
> +	if (ret) {
> +		dev_err(dev, "Failed to register watchdog device\n");
> +		return ret;
> +	}
> +
> +	ret = wdt_core_init();

Shouldn't that come first, before you register the watchdog device ?

> +	if (ret) {
> +		dev_err(dev, "Failed to initialize watchdog core\n");

Please no error message here (the error indicates that there is no watchdog device,
which is controlled by the mfd driver and would be on purpose if it happens).

> +		goto unregister_driver;
> +	}
> +
> +	wdt_core_disable_all();

After registration there is a distinct possibility that the watchdog has already
been enabled by user space by the time this code executes. Disabling it here
might be racy.

> +
> +	imanager_wdt_set_timeout(&imanager_wdt_dev, timeout);
> +
Same here - in theory, user space could already have opened the watchdog device
and changed the timeout. I would suggest to use watchdog_init_timeout() prior
to registering the watchdog.

> +	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
> +
> +	return 0;
> +
> +unregister_driver:
> +	watchdog_unregister_device(&imanager_wdt_dev);
> +	platform_set_drvdata(pdev, NULL);

Unnecessary.

> +
> +	return ret;
> +}
> +
> +static int imanager_wdt_remove(struct platform_device *pdev)
> +{
> +	wdt_core_disable_all();
> +
> +	watchdog_unregister_device(&imanager_wdt_dev);
> +	platform_set_drvdata(pdev, NULL);

Unnecessary.

> +
> +	return 0;
> +}
> +
> +static struct platform_driver imanager_wdt_driver = {
> +	.driver = {
> +		.name	= "imanager_wdt",
> +	},
> +	.probe	= imanager_wdt_probe,
> +	.remove	= imanager_wdt_remove,
> +};
> +
> +module_platform_driver(imanager_wdt_driver);
> +
> +MODULE_DESCRIPTION("Advantech iManager Watchdog Driver");
> +MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imanager_wdt");
> diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
> new file mode 100644
> index 0000000..eb709b7
> --- /dev/null
> +++ b/include/linux/mfd/imanager/wdt.h
> @@ -0,0 +1,37 @@
> +/*
> + * Advantech iManager Watchdog 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 __WDT_H__
> +#define __WDT_H__
> +
> +#include <linux/types.h>
> +
> +enum wdt_event {
> +	DELAY,
> +	PWRBTN,
> +	NMI,
> +	RESET,
> +	WDPIN,
> +	SCI,
> +};
> +
> +int wdt_core_init(void);
> +
> +int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
> +
> +int wdt_core_disable_all(void);
> +int wdt_core_start_timer(void);
> +int wdt_core_stop_timer(void);
> +int wdt_core_reset_timer(void);
> +int wdt_core_stop_boot_timer(void);
> +
> +#endif
>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 6/6] Add Advantech iManager Watchdog driver
  2016-01-10 23:31 ` [PATCH v3 6/6] Add Advantech iManager Watchdog driver richard.dorsch
@ 2016-01-18  7:38       ` Lee Jones
       [not found]   ` <1452468675-5827-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  1 sibling, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:38 UTC (permalink / raw)
  To: richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	lm-sensors-GZX6beZjE8VD60Wz+7aTrA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, jdelvare-IBi9RG/b67k,
	linux-0h96xk9xTtrk1uMJSBkQmQ, wim-IQzOog9fTRqzQB+pC5nmwQ,
	jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg

On Sun, 10 Jan 2016, richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

> From: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

Some more information required about the device here.

> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
>  drivers/watchdog/Kconfig           |  12 ++
>  drivers/watchdog/Makefile          |   2 +
>  drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
>  drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager/wdt.h   |  37 +++++

Please relocate this to include/linux/watchdog.

>  5 files changed, 554 insertions(+)
>  create mode 100644 drivers/watchdog/imanager-ec-wdt.c
>  create mode 100644 drivers/watchdog/imanager-wdt.c
>  create mode 100644 include/linux/mfd/imanager/wdt.h

[...]

> diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
> new file mode 100644
> index 0000000..eb709b7
> --- /dev/null
> +++ b/include/linux/mfd/imanager/wdt.h
> @@ -0,0 +1,37 @@
> +/*
> + * Advantech iManager Watchdog 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 __WDT_H__
> +#define __WDT_H__

These need to be less generic.

> +#include <linux/types.h>
> +
> +enum wdt_event {
> +	DELAY,
> +	PWRBTN,
> +	NMI,
> +	RESET,
> +	WDPIN,
> +	SCI,
> +};

Unless shared between other source files, this should probably move
into the driver.

> +int wdt_core_init(void);
> +
> +int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
> +
> +int wdt_core_disable_all(void);
> +int wdt_core_start_timer(void);
> +int wdt_core_stop_timer(void);
> +int wdt_core_reset_timer(void);
> +int wdt_core_stop_boot_timer(void);

What's the point of these?

> +#endif
> -- 
> 2.7.0
> 

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
--
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	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 6/6] Add Advantech iManager Watchdog driver
@ 2016-01-18  7:38       ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:38 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	jdelvare, linux, wim, jo.sunga

On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

Some more information required about the device here.

> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>  drivers/watchdog/Kconfig           |  12 ++
>  drivers/watchdog/Makefile          |   2 +
>  drivers/watchdog/imanager-ec-wdt.c | 170 +++++++++++++++++++
>  drivers/watchdog/imanager-wdt.c    | 333 +++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager/wdt.h   |  37 +++++

Please relocate this to include/linux/watchdog.

>  5 files changed, 554 insertions(+)
>  create mode 100644 drivers/watchdog/imanager-ec-wdt.c
>  create mode 100644 drivers/watchdog/imanager-wdt.c
>  create mode 100644 include/linux/mfd/imanager/wdt.h

[...]

> diff --git a/include/linux/mfd/imanager/wdt.h b/include/linux/mfd/imanager/wdt.h
> new file mode 100644
> index 0000000..eb709b7
> --- /dev/null
> +++ b/include/linux/mfd/imanager/wdt.h
> @@ -0,0 +1,37 @@
> +/*
> + * Advantech iManager Watchdog 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 __WDT_H__
> +#define __WDT_H__

These need to be less generic.

> +#include <linux/types.h>
> +
> +enum wdt_event {
> +	DELAY,
> +	PWRBTN,
> +	NMI,
> +	RESET,
> +	WDPIN,
> +	SCI,
> +};

Unless shared between other source files, this should probably move
into the driver.

> +int wdt_core_init(void);
> +
> +int wdt_core_set_timeout(enum wdt_event type, u32 timeout);
> +
> +int wdt_core_disable_all(void);
> +int wdt_core_start_timer(void);
> +int wdt_core_stop_timer(void);
> +int wdt_core_reset_timer(void);
> +int wdt_core_stop_boot_timer(void);

What's the point of these?

> +#endif
> -- 
> 2.7.0
> 

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 3/6] Add Advantech iManager HWmon driver
  2016-01-17 18:53   ` Guenter Roeck
@ 2016-01-18  7:40     ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:40 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: richard.dorsch, linux-kernel, lm-sensors, linux-i2c,
	linux-watchdog, linux-gpio, jdelvare, wim, jo.sunga

On Sun, 17 Jan 2016, Guenter Roeck wrote:

> On 01/10/2016 03:31 PM, richard.dorsch@gmail.com wrote:
> >From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

Tell us about the device.

> >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     | 1057 ++++++++++++++++++++++++++++++++++++
> >  include/linux/mfd/imanager/hwmon.h |  120 ++++

I'm not keen on stuffing header files in /include/linux/mfd/.  Please
re-locate them to somewhere specific to 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__

Less generic.

> >+#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,
> >+};

Are these used outside of the driver?

If not, consider moving them into the *.c file.

> >+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);

Are all of these exported somewhere?

> >+#endif
> >
> 

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 4/6] Add Advantech iManager I2C driver
  2016-01-10 23:31 ` [PATCH v3 4/6] Add Advantech iManager I2C driver richard.dorsch
@ 2016-01-18  7:41       ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:41 UTC (permalink / raw)
  To: richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	lm-sensors-GZX6beZjE8VD60Wz+7aTrA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, jdelvare-IBi9RG/b67k,
	linux-0h96xk9xTtrk1uMJSBkQmQ, wim-IQzOog9fTRqzQB+pC5nmwQ,
	jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg

On Sun, 10 Jan 2016, richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

> From: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> 
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
>  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     | 239 +++++++++++++++++
>  include/linux/mfd/imanager/i2c.h      |  55 ++++

Same comments as Watchdog and HWMON.

>  6 files changed, 821 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

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
--
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	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 4/6] Add Advantech iManager I2C driver
@ 2016-01-18  7:41       ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:41 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	jdelvare, linux, wim, jo.sunga

On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> 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     | 239 +++++++++++++++++
>  include/linux/mfd/imanager/i2c.h      |  55 ++++

Same comments as Watchdog and HWMON.

>  6 files changed, 821 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

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 2/6] Add Advantech iManager GPIO driver
  2016-01-10 23:31 ` [PATCH v3 2/6] Add Advantech iManager GPIO driver richard.dorsch
@ 2016-01-18  7:42     ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:42 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	jdelvare, linux, wim, jo.sunga

On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> 
> Signed-off-by: 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      | 181 ++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager/gpio.h |  27 ++++++

Same comments as Watchdog and HWMON.

>  5 files changed, 316 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

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 2/6] Add Advantech iManager GPIO driver
@ 2016-01-18  7:42     ` Lee Jones
  0 siblings, 0 replies; 22+ messages in thread
From: Lee Jones @ 2016-01-18  7:42 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	jdelvare, linux, wim, jo.sunga

On Sun, 10 Jan 2016, richard.dorsch@gmail.com wrote:

> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> 
> Signed-off-by: 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      | 181 ++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager/gpio.h |  27 ++++++

Same comments as Watchdog and HWMON.

>  5 files changed, 316 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

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 4/6] Add Advantech iManager I2C driver
  2016-01-10 23:31 ` [PATCH v3 4/6] Add Advantech iManager I2C driver richard.dorsch
@ 2016-03-03 20:48       ` Wolfram Sang
  0 siblings, 0 replies; 22+ messages in thread
From: Wolfram Sang @ 2016-03-03 20:48 UTC (permalink / raw)
  To: richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	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

[-- Attachment #1: Type: text/plain, Size: 488 bytes --]

On Sun, Jan 10, 2016 at 03:31:13PM -0800, richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:
> From: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> 
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

Well, most of the comments for the other drivers regarding the style
apply to this driver as well. I'll wait and see if the MFD core parts
get improved before throughly reviewing this one.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 4/6] Add Advantech iManager I2C driver
@ 2016-03-03 20:48       ` Wolfram Sang
  0 siblings, 0 replies; 22+ messages in thread
From: Wolfram Sang @ 2016-03-03 20:48 UTC (permalink / raw)
  To: richard.dorsch
  Cc: linux-kernel, lm-sensors, linux-i2c, linux-watchdog, linux-gpio,
	lee.jones, jdelvare, linux, wim, jo.sunga

[-- Attachment #1: Type: text/plain, Size: 398 bytes --]

On Sun, Jan 10, 2016 at 03:31:13PM -0800, richard.dorsch@gmail.com wrote:
> From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> 
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

Well, most of the comments for the other drivers regarding the style
apply to this driver as well. I'll wait and see if the MFD core parts
get improved before throughly reviewing this one.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2016-03-03 20:49 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-10 23:31 [PATCH v3 0/6] Add Advantech iManager EC driver set richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w
2016-01-10 23:31 ` richard.dorsch
2016-01-10 23:31 ` [PATCH v3 1/6] Add Advantech iManager MFD core driver richard.dorsch
2016-01-11  0:54   ` Krzysztof Kozlowski
2016-01-10 23:31 ` [PATCH v3 2/6] Add Advantech iManager GPIO driver richard.dorsch
2016-01-18  7:42   ` Lee Jones
2016-01-18  7:42     ` Lee Jones
2016-01-10 23:31 ` [PATCH v3 3/6] Add Advantech iManager HWmon driver richard.dorsch
2016-01-17 18:53   ` Guenter Roeck
2016-01-18  7:40     ` Lee Jones
2016-01-10 23:31 ` [PATCH v3 4/6] Add Advantech iManager I2C driver richard.dorsch
     [not found]   ` <1452468675-5827-5-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2016-01-18  7:41     ` Lee Jones
2016-01-18  7:41       ` Lee Jones
2016-03-03 20:48     ` Wolfram Sang
2016-03-03 20:48       ` Wolfram Sang
2016-01-10 23:31 ` [PATCH v3 5/6] Add Advantech iManager Backlight driver richard.dorsch
2016-01-11 10:23   ` Lee Jones
2016-01-10 23:31 ` [PATCH v3 6/6] Add Advantech iManager Watchdog driver richard.dorsch
2016-01-17 19:15   ` Guenter Roeck
     [not found]   ` <1452468675-5827-7-git-send-email-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2016-01-18  7:38     ` Lee Jones
2016-01-18  7:38       ` Lee Jones
2016-01-11  0:54 ` [PATCH v3 0/6] Add Advantech iManager EC driver set Guenter Roeck

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.