linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/6] Advantech iManager EC driver set
@ 2016-11-02  8:37 Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang

The Advantech iManager is a custom embedded controller based on ITE IT8518
or IT8528 EC (depending on PCB). It runs a custom firmware that provides
access to features such as GPIO, I2C/SMbus, hwmon, watchdog, and
backlight/brightness control.  All drivers are being managed by the
iManager (mfd) core driver.  It acts as a 'gateway' and handles
communications between EC and sub-drivers.  The imanager-core follows a
similar concept as Kontron's kempld-core driver.
During core init, the device id list is retrieved from the firmware and an
internal device list is being created. This list is then passed down to
managed drivers.

The out-of-tree iManager driver set is maintained at
https://www.github.com/rvido/iManager

Notebook manufactures such as Dell (XPS series) or Razerzone (Razer Blade)
are using similar ITE ECs or even the same ones to implement their own
embedded functions.  Due to the level of firmware customization which those
ITE ECs provide, the iManager EC driver set is incompatible with those
vendors solutions (and probably others too).

Note that v1..v3 were submitted in Jan. 2016.  It took some time to apply
suggested changes.  However, I kept the original versioning to avoid conflict
with previous patch submissions.

Thanks to Krzysztof Kozłowski, Guenter Roeck, and Lee Jones for their
invaluable feedback.  Those drivers clearly needed to be further improved
and cleaned.

Changes from v3:
- Merge imanager-ec-* with corresponding drivers
  This eliminates static init vars and reduces code size
- Remove Documentation/devicetree/bindings/mfd/imanager.txt
  No support for DT
- Add API comments
- List include files in alphabetic order
- Run patches through ./scripts/checkpatch.pl --strict
- Remove headers except imanger.h/imanager-ec.h
  Store them in include/linux/mfd/
imanager-core:
  - Reduce amount of exported functions in core
  - Use usleep_range() instead of udelay()
  - Use dev_info() or dev_warn() where necessary
  - Fix register/probing and __init/__exit
  - Optimize imanager_read_device_config()
  - Shrink down EC device table to known and supported devices
  - Define flags with BIT()
  - Use new imanager device struct
i2c-imanager:
  - Add support for multiple I2C/SMbus adapters
  - Use new imanager device struct
imanager-hwmon-*
gpio-imanager:
imanager_bl:
imanager_wdt:
  - Define flags with BIT()
  - Use new imanager device struct

Changes from v2:
- 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

 drivers/gpio/Kconfig                  |   10 +
 drivers/gpio/Makefile                 |    1 +
 drivers/gpio/gpio-imanager.c          |  155 +++++
 drivers/hwmon/Kconfig                 |   11 +
 drivers/hwmon/Makefile                |    1 +
 drivers/hwmon/imanager-hwmon.c        | 1226 +++++++++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig            |   10 +
 drivers/i2c/busses/Makefile           |    1 +
 drivers/i2c/busses/i2c-imanager.c     |  461 +++++++++++++
 drivers/mfd/Kconfig                   |   18 +
 drivers/mfd/Makefile                  |    1 +
 drivers/mfd/imanager-core.c           |  941 +++++++++++++++++++++++++
 drivers/video/backlight/Kconfig       |    8 +
 drivers/video/backlight/Makefile      |    1 +
 drivers/video/backlight/imanager_bl.c |  210 ++++++
 drivers/watchdog/Kconfig              |   11 +
 drivers/watchdog/Makefile             |    1 +
 drivers/watchdog/imanager_wdt.c       |  303 ++++++++
 include/linux/mfd/imanager-ec.h       |  228 ++++++
 include/linux/mfd/imanager.h          |  221 ++++++
 20 files changed, 3819 insertions(+)
 create mode 100644 drivers/gpio/gpio-imanager.c
 create mode 100644 drivers/hwmon/imanager-hwmon.c
 create mode 100644 drivers/i2c/busses/i2c-imanager.c
 create mode 100644 drivers/mfd/imanager-core.c
 create mode 100644 drivers/video/backlight/imanager_bl.c
 create mode 100644 drivers/watchdog/imanager_wdt.c
 create mode 100644 include/linux/mfd/imanager-ec.h
 create mode 100644 include/linux/mfd/imanager.h

-- 
2.10.1

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

* [PATCH v4 1/6] Add Advantech iManager MFD core driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  2016-11-03  8:41   ` Lee Jones
  2016-11-02  8:37 ` [PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

This patch adds Advantech iManager Embedded Controller MFD core driver.
This mfd core dirver provides an interface for GPIO, I2C, HWmon,
Watchdog, and Backlight/Brightness control.

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/mfd/Kconfig             |  18 +
 drivers/mfd/Makefile            |   1 +
 drivers/mfd/imanager-core.c     | 941 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/imanager-ec.h | 228 ++++++++++
 include/linux/mfd/imanager.h    | 221 ++++++++++
 5 files changed, 1409 insertions(+)
 create mode 100644 drivers/mfd/imanager-core.c
 create mode 100644 include/linux/mfd/imanager-ec.h
 create mode 100644 include/linux/mfd/imanager.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c6df644..294c19d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -388,6 +388,24 @@ 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 EC device"
+	select MFD_CORE
+	help
+	  This is the core driver for Advantech iManager Embedded Controller
+	  found on some Advantech SOM, MIO, AIMB, and PCM modules/boards. The
+	  EC may provide functions like GPIO, I2C bus, 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/5272/5290 and newer
+		* PCM-9389/9365/9376 and newer
+		* AIMB-273/274/230/231 and newer
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called imanager-core.
+
 config LPC_ICH
 	tristate "Intel ICH LPC"
 	depends on PCI
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9834e66..e4b0a4d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -156,6 +156,7 @@ 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
+obj-$(CONFIG_MFD_IMANAGER)	+= imanager-core.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..2ec1b79
--- /dev/null
+++ b/drivers/mfd/imanager-core.c
@@ -0,0 +1,941 @@
+/*
+ * Advantech iManager MFD driver
+ * Partially derived from kempld-core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/imanager.h>
+#include <linux/mfd/imanager-ec.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+
+enum kinds { IT8518, IT8528 };
+
+static struct platform_device *imanager_pdev;
+
+static const char * const chip_names[] = {
+	"it8518",
+	"it8528",
+	NULL
+};
+
+static const struct imanager_ec_device ecdev_table[] = {
+	/* GPIO */
+	{ IMANAGER_EC_DEVICE(GPIO0, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO1, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO2, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO3, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO4, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO5, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO6, GPIO, -1) },
+	{ IMANAGER_EC_DEVICE(GPIO7, GPIO, -1) },
+	/* FAN */
+	{ IMANAGER_EC_DEVICE(CPUFAN_2P,  PWM, 2) },
+	{ IMANAGER_EC_DEVICE(CPUFAN_4P,  PWM, 4) },
+	{ IMANAGER_EC_DEVICE(SYSFAN1_2P, PWM, 2) },
+	{ IMANAGER_EC_DEVICE(SYSFAN1_4P, PWM, 4) },
+	{ IMANAGER_EC_DEVICE(SYSFAN2_2P, PWM, 2) },
+	{ IMANAGER_EC_DEVICE(SYSFAN2_4P, PWM, 4) },
+	/* ADC */
+	{ IMANAGER_EC_DEVICE(ADC12VS0,    ADC, 1) },
+	{ IMANAGER_EC_DEVICE(ADC12VS0_2,  ADC, 2) },
+	{ IMANAGER_EC_DEVICE(ADC12VS0_10, ADC, 10) },
+	{ IMANAGER_EC_DEVICE(ADC5VS0,     ADC, 1) },
+	{ IMANAGER_EC_DEVICE(ADC5VS0_2,   ADC, 2) },
+	{ IMANAGER_EC_DEVICE(ADC5VS0_10,  ADC, 10) },
+	{ IMANAGER_EC_DEVICE(ADC5VS5,     ADC, 1) },
+	{ IMANAGER_EC_DEVICE(ADC5VS5_2,   ADC, 2) },
+	{ IMANAGER_EC_DEVICE(ADC5VS5_10,  ADC, 10) },
+	{ IMANAGER_EC_DEVICE(ADC33VS0,    ADC, 1) },
+	{ IMANAGER_EC_DEVICE(ADC33VS0_2,  ADC, 2) },
+	{ IMANAGER_EC_DEVICE(ADC33VS0_10, ADC, 10) },
+	{ IMANAGER_EC_DEVICE(CMOSBAT,     ADC, 1) },
+	{ IMANAGER_EC_DEVICE(CMOSBAT_2,   ADC, 2) },
+	{ IMANAGER_EC_DEVICE(CMOSBAT_10,  ADC, 10) },
+	{ IMANAGER_EC_DEVICE(VCOREA,      ADC, 1) },
+	{ IMANAGER_EC_DEVICE(CURRENT,     ADC, 1) },
+	/* I2C/SMBus */
+	{ IMANAGER_EC_DEVICE(SMBEEPROM,   SMB, -1) },
+	{ IMANAGER_EC_DEVICE(I2COEM,      SMB, -1) },
+	{ IMANAGER_EC_DEVICE(SMBOEM0,     SMB, -1) },
+	{ IMANAGER_EC_DEVICE(SMBPECI,     SMB, -1) },
+	/* Backlight/Brightness */
+	{ IMANAGER_EC_DEVICE(BRIGHTNESS,  PWM, -1) },
+	{ IMANAGER_EC_DEVICE(BRIGHTNESS2, PWM, -1) },
+	/* Watchdog */
+	{ IMANAGER_EC_DEVICE(WDIRQ, IRQ,  -1) },
+	{ IMANAGER_EC_DEVICE(WDNMI, GPIO, -1) },
+	{ 0 }
+};
+
+/**
+ * iManager I/O
+ */
+
+enum imanager_io_buffer_status { IS_CLEARED = 0, IS_SET };
+
+#define CHECK_BIT(reg, bit) ((reg) & (bit))
+
+static inline int check_io28_ready(uint bit, uint state)
+{
+	int ret, i = 0;
+
+	do {
+		ret = inb(IT8528_CMD_PORT);
+		if (CHECK_BIT(ret, bit) == state)
+			return 0;
+		usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+	} 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 = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+	if (ret)
+		return ret;
+
+	/* prevent firmware lock */
+	inb(addr - 1);
+
+	outb(reg, addr);
+
+	ret = check_io28_ready(EC_IO28_OUTBUF, IS_SET);
+	if (ret)
+		return ret;
+
+	return inb(addr - 1);
+}
+
+static inline int ec_io28_outb(int addr, int reg, int val)
+{
+	int ret;
+
+	ret = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+	if (ret)
+		return ret;
+
+	outb(reg, addr);
+
+	ret = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+	if (ret)
+		return ret;
+
+	outb(val, addr - 1);
+
+	return 0;
+}
+
+static inline int ec_io18_read(int cmd)
+{
+	return ec_inb(IT8518_CMD_PORT, cmd);
+}
+
+static inline int ec_io18_write(int cmd, int value)
+{
+	ec_outb(IT8518_CMD_PORT, cmd, value);
+
+	return 0;
+}
+
+static inline int ec_io28_read(int cmd)
+{
+	return ec_io28_inb(IT8528_CMD_PORT, cmd + EC_CMD_OFFSET_READ);
+}
+
+static inline int ec_io28_write(int cmd, int value)
+{
+	return ec_io28_outb(IT8528_CMD_PORT, cmd + EC_CMD_OFFSET_WRITE, value);
+}
+
+static int imanager_check_ec_ready(struct imanager_io_ops *io)
+{
+	int i = 0;
+
+	do {
+		if (!io->read(EC_CMD_CHK_RDY))
+			return 0;
+		usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+	} while (i++ < EC_MAX_RETRY);
+
+	return -ETIME;
+}
+
+/**
+ * iManager Device Configuration
+ */
+
+static void imanager_add_attribute(struct imanager_ec_data *ec,
+				   struct imanager_device_attribute *attr)
+{
+	struct imanager_gpio_device *gpio = &ec->gpio;
+	struct imanager_adc_device *adc = &ec->hwmon.adc;
+	struct imanager_fan_device *fan = &ec->hwmon.fan;
+	struct imanager_i2c_device *i2c = &ec->i2c;
+	struct imanager_backlight_device *bl = &ec->bl;
+	struct imanager_watchdog_device *wdt = &ec->wdt;
+
+	switch (attr->ecdev->type) {
+	case GPIO:
+		switch (attr->did) {
+		case GPIO0:
+		case GPIO1:
+		case GPIO2:
+		case GPIO3:
+		case GPIO4:
+		case GPIO5:
+		case GPIO6:
+		case GPIO7:
+			gpio->attr[gpio->num++] = attr;
+			break;
+		case WDNMI:
+			wdt->attr[1] = attr;
+			wdt->num++;
+			break;
+		}
+	case ADC:
+		switch (attr->did) {
+		case ADC12VS0:
+		case ADC12VS0_2:
+		case ADC12VS0_10:
+			adc->attr[0] = attr;
+			adc->label[0] = "+12VS0";
+			adc->num++;
+			break;
+		case ADC5VS5:
+		case ADC5VS5_2:
+		case ADC5VS5_10:
+			adc->attr[1] = attr;
+			adc->label[1] = "+5VS0";
+			adc->num++;
+			break;
+		case CMOSBAT:
+		case CMOSBAT_2:
+		case CMOSBAT_10:
+			adc->attr[2] = attr;
+			adc->label[2] = "+3.3VS0";
+			adc->num++;
+			break;
+		case VCOREA:
+		case ADC5VS0:
+		case ADC5VS0_2:
+		case ADC5VS0_10:
+			adc->attr[3] = attr;
+			adc->num++;
+			break;
+		case CURRENT:
+		case ADC33VS0:
+		case ADC33VS0_2:
+		case ADC33VS0_10:
+			adc->attr[4] = attr;
+			adc->num++;
+			break;
+		}
+	case PWM:
+		switch (attr->did) {
+		case CPUFAN_2P:
+		case CPUFAN_4P:
+			fan->attr[0] = attr;
+			fan->label[0] = "FAN CPU";
+			fan->temp_label[0] = "Temp CPU";
+			fan->num++;
+			break;
+		case SYSFAN1_2P:
+		case SYSFAN1_4P:
+			fan->attr[1] = attr;
+			fan->label[1] = "FAN SYS1";
+			fan->temp_label[1] = "Temp SYS1";
+			fan->num++;
+			break;
+		case SYSFAN2_2P:
+		case SYSFAN2_4P:
+			fan->attr[2] = attr;
+			fan->label[2] = "FAN SYS2";
+			fan->temp_label[2] = "Temp SYS2";
+			fan->num++;
+			break;
+		case BRIGHTNESS:
+			bl->attr[0] = attr;
+			bl->brightness[0] = EC_OFFSET_BRIGHTNESS1;
+			bl->num++;
+			break;
+		case BRIGHTNESS2:
+			bl->attr[1] = attr;
+			bl->brightness[1] = EC_OFFSET_BRIGHTNESS2;
+			bl->num++;
+			break;
+		}
+	case SMB:
+		switch (attr->did) {
+		case SMBEEPROM:
+			i2c->attr[SMB_EEP] = attr;
+			i2c->num++;
+			break;
+		case I2COEM:
+			i2c->attr[I2C_OEM] = attr;
+			i2c->num++;
+			break;
+		case SMBOEM0:
+			i2c->attr[SMB_1] = attr;
+			i2c->num++;
+			break;
+		case SMBPECI:
+			i2c->attr[SMB_PECI] = attr;
+			i2c->num++;
+			break;
+		}
+	case IRQ:
+		if (attr->did == WDIRQ) {
+			wdt->attr[0] = attr;
+			wdt->num++;
+			break;
+		}
+	}
+}
+
+enum imanager_device_table_type { DEVID = 0, HWPIN, POLARITY };
+
+static int imanager_read_device_config(struct imanager_ec_data *ec)
+{
+	struct imanager_ec_message msgs[] = {
+		{ IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, DEVID, NULL) },
+		{ IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, HWPIN, NULL) },
+		{ IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, POLARITY, NULL) },
+	};
+	struct imanager_device_attribute *attr;
+	int i, j, ret;
+
+	/* Read iManager device configurations */
+	for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+		ret = imanager_read(ec, EC_CMD_DEV_TBL_RD, &msgs[i]);
+		if (ret)
+			return ret;
+	}
+
+	/* Generate iManager device atributes */
+	for (i = 0; i < EC_MAX_DID && msgs[DEVID].u.data[i]; i++) {
+		attr = &ec->attr[i];
+		for (j = 0; j < ARRAY_SIZE(ecdev_table); j++) {
+			if (ecdev_table[j].did == msgs[DEVID].u.data[i]) {
+				attr->did = msgs[DEVID].u.data[i];
+				attr->hwp = msgs[HWPIN].u.data[i];
+				attr->pol = msgs[POLARITY].u.data[i];
+				attr->ecdev = &ecdev_table[j];
+				imanager_add_attribute(ec, attr);
+				break;
+			}
+		}
+	}
+
+	if (ec->gpio.num)
+		ec->features |= IMANAGER_FEATURE_GPIO;
+	if (ec->hwmon.adc.num)
+		ec->features |= IMANAGER_FEATURE_HWMON_ADC;
+	if (ec->hwmon.fan.num)
+		ec->features |= IMANAGER_FEATURE_HWMON_FAN;
+	if (ec->i2c.num)
+		ec->features |= IMANAGER_FEATURE_SMBUS;
+	if (ec->bl.num)
+		ec->features |= IMANAGER_FEATURE_BACKLIGHT;
+	if (ec->wdt.num)
+		ec->features |= IMANAGER_FEATURE_WDT;
+
+	return 0;
+}
+
+static const char *project_code_to_str(unsigned int code)
+{
+	switch ((char)code) {
+	case 'V':
+		return "release";
+	case 'X':
+		return "debug";
+	case 'A' ... 'U':
+	case 'Y':
+	case 'Z':
+		return "custom";
+	}
+
+	return "unspecified";
+}
+
+static int imanager_read_firmware_version(struct imanager_ec_data *ec)
+{
+	char pcb_name[IMANAGER_PCB_NAME_LEN] = { 0 };
+	struct imanager_info *info = &ec->info;
+	struct imanager_ec_message msg = {
+		.rlen = ARRAY_SIZE(pcb_name) - 1,
+		.wlen = 0,
+		.param = 0,
+		.data = pcb_name,
+	};
+	struct imanager_ec_version ver;
+	unsigned int val;
+	int ret;
+
+	ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FW_RELEASE,
+				(u8 *)&ver, sizeof(ver));
+	if (ret < 0)
+		return ret;
+
+	val = cpu_to_be16(ver.kernel);
+	info->kernel_major = EC_KERNEL_MAJOR(val);
+	info->kernel_minor = EC_KERNEL_MINOR(val);
+
+	val = cpu_to_be16(ver.firmware);
+	info->firmware_major = EC_FIRMWARE_MAJOR(val);
+	info->firmware_minor = EC_FIRMWARE_MINOR(val);
+
+	val = cpu_to_be16(ver.project_code);
+	info->type = project_code_to_str(EC_PROJECT_CODE(val));
+
+	/*
+	 * The PCB name string, in some FW releases, is not Null-terminated,
+	 * so we need to read a fixed amount of chars. Also, the name length
+	 * may vary by one char (SOM6867 vs. SOM-6867).
+	 */
+	ret = imanager_read(ec, EC_CMD_FW_INFO_RD, &msg);
+	if (ret)
+		return ret;
+
+	if (!strchr(pcb_name, '-'))
+		pcb_name[IMANAGER_PCB_NAME_LEN - 2] = '\0';
+
+	return scnprintf(info->version, sizeof(info->version),
+			 "%s_k%d.%d_f%d.%d_%s", pcb_name, info->kernel_major,
+			 info->kernel_minor, info->firmware_major,
+			 info->firmware_minor, info->type);
+}
+
+static int imanager_ec_init(struct imanager_ec_data *ec)
+{
+	int ret;
+
+	/* Prevent firmware lock */
+	inb(IT8528_DAT_PORT);
+	inb(IT8518_DAT_PORT);
+
+	ret = imanager_read_firmware_version(ec);
+	if (ret < 0)
+		return ret;
+
+	return imanager_read_device_config(ec);
+}
+
+static inline void data_to_ec(struct imanager_io_ops *io, u8 *data, u8 len,
+			      int offset)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+		io->write(offset++, data[i]);
+}
+
+static inline void data_from_ec(struct imanager_io_ops *io, u8 *data, u8 len,
+				int offset)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+		data[i] = io->read(offset++);
+}
+
+static int imanager_msg_xfer(struct imanager_ec_data *ec, u8 cmd,
+			     struct imanager_ec_message *msg, bool payload)
+{
+	int ret;
+	int offset = EC_MSG_OFFSET_DATA;
+
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	ec->io.write(EC_MSG_OFFSET_PARAM, msg->param);
+
+	if (msg->wlen) {
+		if (msg->data) {
+			data_to_ec(&ec->io, msg->data, msg->wlen, offset);
+			ec->io.write(EC_MSG_OFFSET_LEN, msg->wlen);
+		} else {
+			data_to_ec(&ec->io, msg->u.data, msg->wlen, offset);
+		}
+	}
+
+	/* Execute command */
+	ec->io.write(EC_MSG_OFFSET_CMD, cmd);
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	/* GPIO and I2C have different success return values */
+	ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+	if ((ret != EC_F_SUCCESS) && !(ret & EC_F_CMD_COMPLETE))
+		return -EFAULT;
+	/*
+	 * EC I2C may return an error code which we need to handoff
+	 * to the caller
+	 */
+	else if (ret & 0x007e)
+		return ret;
+
+	if (msg->rlen) {
+		if (msg->rlen == EC_F_HWMON_MSG)
+			msg->rlen = ec->io.read(EC_MSG_OFFSET_LEN);
+		if (payload) /* i2c, hwmon, wdt */
+			offset = EC_MSG_OFFSET_PAYLOAD;
+		if (msg->data)
+			data_from_ec(&ec->io, msg->data, msg->rlen, offset);
+		else
+			data_from_ec(&ec->io, msg->u.data, msg->rlen, offset);
+	}
+
+	return 0;
+}
+
+/**
+ * imanager_read_ram - read 'size' amount of data @ 'offset' of 'ram_type'
+ * @ec:		imanager_ec_data structure describing the EC
+ * @ram_type:	RAM type such as ACPI, HW, or EXternal
+ * @offset:	offset within the RAM segment
+ * @data:	data pointer
+ * @len:	data length
+ */
+int imanager_read_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+		      u8 *data, u8 len)
+{
+	int ret;
+
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	ec->io.write(EC_MSG_OFFSET_PARAM, ram_type);
+	ec->io.write(EC_MSG_OFFSET_DATA, offset);
+	ec->io.write(EC_MSG_OFFSET_LEN, len);
+	ec->io.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_RD);
+
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+	if (ret != EC_F_SUCCESS)
+		return -EIO;
+
+	data_from_ec(&ec->io, data, len, EC_MSG_OFFSET_RAM_DATA);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_read_ram);
+
+/**
+ * imanager_write_ram - write 'len' amount of data @ 'offset' of 'ram_type'
+ * @ec:		imanager_ec_data structure describing the EC
+ * @ram_type:	RAM type such as ACPI, HW, or EXternal
+ * @offset:	offset within the RAM segment
+ * @data:	data pointer
+ * @len:	data length
+ */
+int imanager_write_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+		       u8 *data, u8 len)
+{
+	int ret;
+
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	ec->io.write(EC_MSG_OFFSET_PARAM, ram_type);
+	ec->io.write(EC_MSG_OFFSET_DATA, offset);
+	ec->io.write(EC_MSG_OFFSET_LEN, len);
+
+	data_to_ec(&ec->io, data, len, EC_MSG_OFFSET_RAM_DATA);
+
+	ec->io.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_WR);
+
+	ret = imanager_check_ec_ready(&ec->io);
+	if (ret)
+		return ret;
+
+	ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+	if (ret != EC_F_SUCCESS)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_write_ram);
+
+/**
+ * imanager_read - read data through request/response messaging
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @msg:	imanager_ec_message structure holding the message
+ */
+int imanager_read(struct imanager_ec_data *ec, u8 cmd,
+		  struct imanager_ec_message *msg)
+{
+	return imanager_msg_xfer(ec, cmd, msg, false);
+}
+EXPORT_SYMBOL_GPL(imanager_read);
+
+/**
+ * imanager_write - write data through request/response messaging
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @msg:	imanager_ec_message structure holding the message
+ */
+int imanager_write(struct imanager_ec_data *ec, u8 cmd,
+		   struct imanager_ec_message *msg)
+{
+	return imanager_msg_xfer(ec, cmd, msg, true);
+}
+EXPORT_SYMBOL_GPL(imanager_write);
+
+/**
+ * imanager_read8 - read 8-bit data
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @param:	parameter depening on cmd - device ID, offset or unit number
+ */
+int imanager_read8(struct imanager_ec_data *ec, u8 cmd, u8 param)
+{
+	int ret;
+	struct imanager_ec_message msg = {
+		.rlen = 1,
+		.wlen = 0,
+		.param = param,
+		.data = NULL,
+	};
+
+	ret = imanager_read(ec, cmd, &msg);
+	if (ret)
+		return ret;
+
+	return msg.u.data[0];
+}
+EXPORT_SYMBOL_GPL(imanager_read8);
+
+/**
+ * imanager_read16 - read 16-bit data
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @param:	parameter depening on cmd - device ID, offset or unit number
+ */
+int imanager_read16(struct imanager_ec_data *ec, u8 cmd, u8 param)
+{
+	int ret;
+	struct imanager_ec_message msg = {
+		.rlen = 2,
+		.wlen = 0,
+		.param = param,
+		.data = NULL,
+	};
+
+	ret = imanager_read(ec, cmd, &msg);
+	if (ret)
+		return ret;
+
+	return (msg.u.data[0] << 8 | msg.u.data[1]);
+}
+EXPORT_SYMBOL_GPL(imanager_read16);
+
+/**
+ * imanager_write8 - write 8-bit data
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @param:	parameter depening on cmd - device ID, offset or unit number
+ * @byte:	8-bit data
+ */
+int imanager_write8(struct imanager_ec_data *ec, u8 cmd, u8 param, u8 byte)
+{
+	struct imanager_ec_message msg = {
+		.rlen = 0,
+		.wlen = 1,
+		.param = param,
+		.u = {
+			.data = { byte, 0 },
+		},
+	};
+
+	return imanager_write(ec, cmd, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write8);
+
+/**
+ * imanager_write16 - write 16-bit data
+ * @ec:		imanager_ec_data structure describing the EC
+ * @cmd:	imanager EC firmware command
+ * @param:	parameter depening on cmd - device ID, offset or unit number
+ * @word:	16-bit data
+ */
+int imanager_write16(struct imanager_ec_data *ec, u8 cmd, u8 param, u16 word)
+{
+	struct imanager_ec_message msg = {
+		.rlen = 0,
+		.wlen = 2,
+		.param = param,
+		.u = {
+			.data = { (word >> 8), (word & 0xff), 0 },
+		},
+	};
+
+	return imanager_write(ec, cmd, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write16);
+
+enum imanager_cells {
+	IMANAGER_BACKLIGHT = 0,
+	IMANAGER_GPIO,
+	IMANAGER_HWMON,
+	IMANAGER_SMB,
+	IMANAGER_WDT,
+};
+
+/**
+ * iManager devices which are available via firmware.
+ */
+
+static const struct mfd_cell imanager_devs[] = {
+	[IMANAGER_BACKLIGHT] = {
+		.name = "imanager-backlight",
+	},
+	[IMANAGER_GPIO] = {
+		.name = "imanager-gpio",
+	},
+	[IMANAGER_HWMON] = {
+		.name = "imanager-hwmon",
+	},
+	[IMANAGER_SMB] = {
+		.name = "imanager-smbus",
+	},
+	[IMANAGER_WDT] = {
+		.name = "imanager-wdt",
+	},
+};
+
+static int imanager_register_cells(struct imanager_device_data *imgr)
+{
+	struct imanager_ec_data *ec = &imgr->ec;
+	struct mfd_cell devs[ARRAY_SIZE(imanager_devs)];
+	int i = 0;
+
+	if (ec->features & IMANAGER_FEATURE_BACKLIGHT)
+		devs[i++] = imanager_devs[IMANAGER_BACKLIGHT];
+
+	if (ec->features & IMANAGER_FEATURE_GPIO)
+		devs[i++] = imanager_devs[IMANAGER_GPIO];
+
+	if (ec->features & IMANAGER_FEATURE_HWMON_ADC)
+		devs[i++] = imanager_devs[IMANAGER_HWMON];
+
+	if (ec->features & IMANAGER_FEATURE_SMBUS)
+		devs[i++] = imanager_devs[IMANAGER_SMB];
+
+	if (ec->features & IMANAGER_FEATURE_WDT)
+		devs[i++] = imanager_devs[IMANAGER_WDT];
+
+	return mfd_add_devices(imgr->dev, -1, devs, i, NULL, 0, NULL);
+}
+
+static struct resource imanager_ioresource = {
+	.start  = IT8528_DAT_PORT,
+	.end    = IT8518_DAT_PORT,
+	.flags  = IORESOURCE_IO,
+};
+
+static ssize_t imanager_version_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct imanager_device_data *data = dev_get_drvdata(dev);
+	struct imanager_info *info = &data->ec.info;
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", info->version);
+}
+
+static ssize_t imanager_chip_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct imanager_device_data *data = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", data->ec.chip_name);
+}
+
+static DEVICE_ATTR(imanager_version, 0444, imanager_version_show, NULL);
+static DEVICE_ATTR(imanager_chip, 0444, imanager_chip_show, NULL);
+
+static struct attribute *imanager_attributes[] = {
+	&dev_attr_imanager_version.attr,
+	&dev_attr_imanager_chip.attr,
+	NULL
+};
+
+static const struct attribute_group imanager_attr_group = {
+	.attrs = imanager_attributes,
+};
+
+static int imanager_platform_create(void)
+{
+	int ret;
+
+	imanager_pdev = platform_device_alloc("imanager", -1);
+	if (!imanager_pdev)
+		return -ENOMEM;
+
+	/* No platform device data required */
+
+	ret = platform_device_add_resources(imanager_pdev,
+					    &imanager_ioresource, 1);
+	if (ret)
+		goto err;
+
+	ret = platform_device_add(imanager_pdev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	platform_device_put(imanager_pdev);
+	return ret;
+}
+
+static inline int ec_read_chipid(u16 addr)
+{
+	return (ec_inb(addr, CHIP_DEVID_MSB) << 8 |
+		ec_inb(addr, CHIP_DEVID_LSB));
+}
+
+static int imanager_detect_device(struct imanager_device_data *imgr)
+{
+	struct imanager_ec_data *ec = &imgr->ec;
+	struct device *dev = imgr->dev;
+	struct imanager_info *info = &imgr->ec.info;
+	int chipid = ec_read_chipid(EC_BASE_ADDR);
+	int ret;
+
+	if (chipid == CHIP_ID_IT8518) {
+		ec->io.read	= ec_io18_read;
+		ec->io.write	= ec_io18_write;
+		ec->chip_name	= chip_names[IT8518];
+	} else if (chipid == CHIP_ID_IT8528) {
+		ec->io.read	= ec_io28_read;
+		ec->io.write	= ec_io28_write;
+		ec->chip_name	= chip_names[IT8528];
+	}
+
+	ret = imanager_ec_init(ec);
+	if (ret) {
+		dev_err(dev, "iManager firmware communication error\n");
+		return ret;
+	}
+
+	dev_info(dev, "Found Advantech iManager %s: %s (%s)\n",
+		 ec->chip_name, info->version, info->type);
+
+	ret = sysfs_create_group(&dev->kobj, &imanager_attr_group);
+	if (ret)
+		return ret;
+
+	ret = imanager_register_cells(imgr);
+	if (ret)
+		sysfs_remove_group(&dev->kobj, &imanager_attr_group);
+
+	return ret;
+}
+
+static int imanager_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr;
+
+	imgr = devm_kzalloc(dev, sizeof(*imgr), GFP_KERNEL);
+	if (!imgr)
+		return -ENOMEM;
+
+	imgr->dev = dev;
+	mutex_init(&imgr->lock);
+
+	platform_set_drvdata(pdev, imgr);
+
+	return imanager_detect_device(imgr);
+}
+
+static int imanager_remove(struct platform_device *pdev)
+{
+	sysfs_remove_group(&pdev->dev.kobj, &imanager_attr_group);
+	mfd_remove_devices(&pdev->dev);
+
+	return 0;
+}
+
+static struct platform_driver imanager_driver = {
+	.driver = {
+		.name  = "imanager",
+	},
+	.probe	= imanager_probe,
+	.remove	= imanager_remove,
+};
+
+static int __init imanager_init(void)
+{
+	int chipid = ec_read_chipid(EC_BASE_ADDR);
+	int ret;
+
+	/* Check for the presence of the EC chip */
+	if ((chipid != CHIP_ID_IT8518) && (chipid != CHIP_ID_IT8528))
+		return -ENODEV;
+
+	ret = imanager_platform_create();
+	if (ret)
+		return ret;
+
+	return platform_driver_register(&imanager_driver);
+}
+
+static void __exit imanager_exit(void)
+{
+	if (imanager_pdev)
+		platform_device_unregister(imanager_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/include/linux/mfd/imanager-ec.h b/include/linux/mfd/imanager-ec.h
new file mode 100644
index 0000000..6319b7a
--- /dev/null
+++ b/include/linux/mfd/imanager-ec.h
@@ -0,0 +1,228 @@
+/*
+ * Advantech iManager - firmware interface
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _LINUX_MFD_IMANAGER_EC_H_
+#define _LINUX_MFD_IMANAGER_EC_H_
+
+#include <linux/types.h>
+
+/* Delay time for port polling in micro seconds */
+#define EC_DELAY_MIN			200UL
+#define EC_DELAY_MAX			250UL
+
+#define EC_MAX_RETRY			400UL
+
+#define CHIP_ID_IT8518			0x8518
+#define CHIP_ID_IT8528			0x8528
+
+#define EC_BASE_ADDR			0x029C
+
+#define IT8528_CMD_PORT			0x029A
+#define IT8528_DAT_PORT			0x0299
+#define IT8518_CMD_PORT			0x029E
+#define IT8518_DAT_PORT			0x029F
+
+/* 16-bit device ID registers */
+#define CHIP_DEVID_MSB			0x20
+#define CHIP_DEVID_LSB			0x21
+
+#define EC_MAX_GPIO_NUM			8UL
+#define EC_MAX_ADC_NUM			5UL
+#define EC_MAX_FAN_NUM			3UL
+#define EC_MAX_BLC_NUM			2UL
+#define EC_MAX_SMB_NUM			4UL
+#define EC_MAX_WDT_NUM			2UL
+
+#define EC_PAYLOAD_SIZE			40UL
+#define EC_MSG_SIZE			sizeof(struct imanager_ec_smb_message)
+#define EC_MSG_HDR_SIZE			sizeof(struct imanager_ec_smb_msg_hdr)
+
+#define EC_MAX_DID			32UL
+
+/*
+ * iManager commands
+ */
+#define EC_CMD_CHK_RDY			0UL
+#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_DEV_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 RAM offsets
+ */
+#define EC_OFFSET_FAN_ALERT		0x6FUL
+#define EC_OFFSET_FAN_ALERT_LIMIT	0x76UL
+#define EC_OFFSET_BRIGHTNESS1		0x50UL
+#define EC_OFFSET_BRIGHTNESS2		0x52UL
+#define EC_OFFSET_BACKLIGHT_CTRL	0x99UL
+#define EC_OFFSET_FW_RELEASE		0xF8UL
+
+/* iManager flags */
+#define IMANAGER_FEATURE_BACKLIGHT	BIT(0)
+#define IMANAGER_FEATURE_GPIO		BIT(1)
+#define IMANAGER_FEATURE_HWMON_ADC	BIT(2)
+#define IMANAGER_FEATURE_HWMON_FAN	BIT(3)
+#define IMANAGER_FEATURE_SMBUS		BIT(4)
+#define IMANAGER_FEATURE_WDT		BIT(5)
+
+#define EC_IO28_OUTBUF			BIT(0)
+#define EC_IO28_INBUF			BIT(1)
+
+#define EC_F_SUCCESS			BIT(0)
+#define EC_F_CMD_COMPLETE		BIT(7)
+#define EC_F_HWMON_MSG			BIT(9)
+
+/* iManager offsets */
+#define EC_MSG_OFFSET(N)		(0UL + (N))
+#define EC_MSG_OFFSET_CMD		EC_MSG_OFFSET(0)
+#define EC_MSG_OFFSET_STATUS		EC_MSG_OFFSET(1)
+#define EC_MSG_OFFSET_PARAM		EC_MSG_OFFSET(2)
+#define EC_MSG_OFFSET_DATA		EC_MSG_OFFSET(3)
+#define EC_MSG_OFFSET_RAM_DATA		EC_MSG_OFFSET(4)
+#define EC_MSG_OFFSET_PAYLOAD		EC_MSG_OFFSET(7)
+#define EC_MSG_OFFSET_LEN		EC_MSG_OFFSET(0x2F)
+
+/* IT8528 based firmware require a read/write command offset. */
+#define EC_CMD_OFFSET_READ		0xA0UL
+#define EC_CMD_OFFSET_WRITE		0x50UL
+
+#define EC_KERNEL_MINOR(x)		((x) & 0xff)
+#define EC_KERNEL_MAJOR(x)		({ typeof(x) __x = (x >> 8); \
+					((__x >> 4) * 10 + (__x & 0x0f)); })
+#define EC_FIRMWARE_MINOR(x)		EC_KERNEL_MINOR(x)
+#define EC_FIRMWARE_MAJOR(x)		EC_KERNEL_MAJOR(x)
+#define EC_PROJECT_CODE(x)		EC_KERNEL_MINOR(x)
+
+enum imanager_smb_cells { SMB_EEP = 0, I2C_OEM, SMB_1, SMB_PECI };
+
+enum imanager_device_type { ADC = 1, DAC, GPIO, IRQ, PWM, SMB };
+
+enum imanager_device_id {
+	/* GPIO */
+	GPIO0 = 0x10, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7,
+	/* FAN */
+	CPUFAN_2P = 0x20, CPUFAN_4P, SYSFAN1_2P, SYSFAN1_4P, SYSFAN2_2P,
+	SYSFAN2_4P,
+	/* Brightness Control */
+	BRIGHTNESS = 0x26, BRIGHTNESS2 = 0x88,
+	/* SMBus */
+	SMBOEM0	 = 0x28, SMBOEM1, SMBOEM2, SMBEEPROM,
+	SMBTHM0  = 0x2C, SMBTHM1, SMBSECEEP, I2COEM,
+	SMBEEP2K = 0x38, OEMEEP, OEMEEP2K, SMBPECI,
+	/* ADC */
+	CMOSBAT  = 0x50, CMOSBAT_2, CMOSBAT_10,
+	ADC5VS0  = 0x56, ADC5VS0_2, ADC5VS0_10,
+	ADC5VS5  = 0x59, ADC5VS5_2, ADC5VS5_10,
+	ADC33VS0 = 0x5C, ADC33VS0_2, ADC33VS0_10,
+	ADC33VS5 = 0x5F, ADC33VS5_2, ADC33VS5_10,
+	ADC12VS0 = 0x62, ADC12VS0_2, ADC12VS0_10,
+	VCOREA   = 0x65, VCOREA_2, VCOREA_10,
+	CURRENT  = 0x74,
+	/* Watchdog */
+	WDIRQ    = 0x78, WDNMI,
+};
+
+/**
+ * struct imanager_ec_device - Describes iManager EC Device
+ * @did:	iManager Device ID
+ * @type:	iManager Device Type
+ * @scale:	Scaling factor
+ */
+struct imanager_ec_device {
+	unsigned int did;
+	unsigned int type;
+	unsigned int scale;
+};
+
+/**
+ * IMANAGER_EC_DEVICE - macro used to describe a specific iManager device
+ * @device_id:		the 8 bit iManager device ID
+ * @device_type:	the iManager device type
+ * @scaling_factor:	the iManager sensor device scaling factor
+ *
+ * This macro is used to create a struct imanager_ec_device that matches a
+ * specific iManager device
+ */
+#define IMANAGER_EC_DEVICE(device_id, device_type, scaling_factor) \
+	.did = (device_id), .type = (device_type), .scale = (scaling_factor)
+
+/**
+ * struct imanager_io_ops - iManager I/O operation structure
+ * @read:	iManager read call-back
+ * @write:	iManager write call-back
+ */
+struct imanager_io_ops {
+	int (*read)(int cmd);
+	int (*write)(int cmd, int value);
+};
+
+/**
+ * struct imanager_ec_smb_msg_hdr - Defines iManager EC SMBus message header
+ * @addr_low:	low-byte of word address (or data)
+ * @addr_high:	high-byte of word address (or data)
+ * @rlen:	SMB read length
+ * @wlen:	SMB write length
+ * @cmd:	SMB command
+ */
+struct imanager_ec_smb_msg_hdr {
+	unsigned char addr_low;
+	unsigned char addr_high;
+	unsigned char rlen;
+	unsigned char wlen;
+	unsigned char cmd;
+} __attribute__((__packed__));
+
+/**
+ * struct imanager_ec_smb_message - Defines iManager SMBus message
+ * @hdr:	iManager SMBus message header
+ * @data:	iManager SMBus message data field (payload)
+ */
+struct imanager_ec_smb_message {
+	struct imanager_ec_smb_msg_hdr hdr;
+	unsigned char data[EC_PAYLOAD_SIZE];
+} __attribute__((__packed__));
+
+/**
+ * struct imanager_ec_version - Defines iManager EC firmware version structure
+ * @kernel:		iManager EC FW kernel release
+ * @chipid:		iManager EC chip ID
+ * @project_code:	iManager EC FW status
+ * @firmware:		iManager EC FW release
+ */
+struct imanager_ec_version {
+	unsigned short kernel;
+	unsigned short chipid;
+	unsigned short project_code;
+	unsigned short firmware;
+} __attribute__((__packed__));
+
+#endif
diff --git a/include/linux/mfd/imanager.h b/include/linux/mfd/imanager.h
new file mode 100644
index 0000000..32d7af6
--- /dev/null
+++ b/include/linux/mfd/imanager.h
@@ -0,0 +1,221 @@
+/*
+ * Advantech iManager MFD
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _LINUX_MFD_IMANAGER_H_
+#define _LINUX_MFD_IMANAGER_H_
+
+#include <linux/mutex.h>
+#include <linux/mfd/imanager-ec.h>
+
+#define IMANAGER_PCB_NAME_LEN	9
+#define IMANAGER_VERSION_LEN	40
+
+/**
+ * IMANAGER_MSG_SIMPLE - macro used to describe a simple iManager message
+ * @read_len:	the message read length
+ * @write_len:	the message write length
+ * @parameter:	the message parameter
+ * @_data:	pointer to data field
+ *
+ * This macro is used to create a struct imanager_ec_message used for basic
+ * EC communication
+ */
+#define IMANAGER_MSG_SIMPLE(read_len, write_len, parameter, _data) \
+	.rlen = (read_len), .wlen = (write_len), \
+	.param = (parameter), .data = (_data)
+
+/**
+ * struct imanager_ec_message - Describes iManager EC message
+ * @rlen:	iManager message read length
+ * @wlen:	iManager message write length
+ * @param:	iManager message parameter (offset, id, or unit number)
+ * @u:		union holding struct imanager_ec_smb_message and data field
+ * @data:	pointer to data field
+ */
+struct imanager_ec_message {
+	unsigned int rlen;
+	unsigned int wlen;
+	unsigned int param;
+	union {
+		struct imanager_ec_smb_message smb;
+		unsigned char data[EC_MSG_SIZE];
+	} u;
+
+	unsigned char *data;
+};
+
+/**
+ * struct imanager_device_attribute - Describes iManager Device attribute
+ * @did:	iManager Device ID
+ * @hwp:	iManager Hardware Pin number
+ * @pol:	iManager Device Polarity
+ * @ecdev:	pointer to iManager device table entry
+ */
+struct imanager_device_attribute {
+	unsigned int did;
+	unsigned int hwp;
+	unsigned int pol;
+	const struct imanager_ec_device *ecdev;
+};
+
+/**
+ * struct imanager_gpio_device - Describes iManager GPIO device
+ * @num:	available GPIO pins
+ * @attr:	pointer to array of iManager GPIO device attribute
+ */
+struct imanager_gpio_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_GPIO_NUM];
+};
+
+/**
+ * struct imanager_adc_device - Describes iManager ADC device
+ * @num:	available ADC devices
+ * @attr:	pointer to array of iManager ADC device attribute
+ * @label	pointer to ADC label
+ */
+struct imanager_adc_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_ADC_NUM];
+	const char *label[EC_MAX_ADC_NUM];
+};
+
+/**
+ * struct imanager_fan_device - Describes iManager FAN device
+ * @num:	available FAN devices
+ * @attr:	pointer to array of iManager FAN device attribute
+ * @label	pointer to FAN label
+ * @temp_label	pointer to FAN temperature label
+ */
+struct imanager_fan_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_FAN_NUM];
+	const char *label[EC_MAX_FAN_NUM];
+	const char *temp_label[EC_MAX_FAN_NUM];
+};
+
+/**
+ * struct imanager_hwmon_device - Describes iManager hwmon device
+ * @adc:	iManager ADC device
+ * @fan:	iManager FAN device
+ */
+struct imanager_hwmon_device {
+	struct imanager_adc_device adc;
+	struct imanager_fan_device fan;
+};
+
+/**
+ * struct imanager_i2c_device - Describes iManager I2C device
+ * @num:	available I2C devices
+ * @attr:	pointer to array of iManager GPIO device attribute
+ */
+struct imanager_i2c_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_SMB_NUM];
+};
+
+/**
+ * struct imanager_backlight_device - Describes iManager backlight device
+ * @num:	available backlight devices
+ * @attr:	pointer to array of iManager backlight device attribute
+ * @brightnes:	array of brightness devices
+ */
+struct imanager_backlight_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_BLC_NUM];
+	unsigned char brightness[EC_MAX_BLC_NUM];
+};
+
+/**
+ * struct imanager_watchdog_device - Describes iManager watchdog device
+ * @num:	available WD devices
+ * @attr:	pointer to array of iManager watchdog device attribute
+ */
+struct imanager_watchdog_device {
+	unsigned int num;
+	struct imanager_device_attribute *attr[EC_MAX_BLC_NUM];
+};
+
+/**
+ * struct imanager_info - iManager device information structure
+ * @kernel_major:	iManager EC kernel major revision
+ * @kernel_minor:	iManager EC kernel minor revision
+ * @firmware_major:	iManager EC firmware major revision
+ * @firmware_minor:	iManager EC firmware minor revision
+ * @type:		iManager type - release/debug/custom
+ * @pcb_name:		PC board name
+ * @version:		iManager version string
+ */
+struct imanager_info {
+	unsigned int kernel_major;
+	unsigned int kernel_minor;
+	unsigned int firmware_major;
+	unsigned int firmware_minor;
+	const char *type;
+	char version[IMANAGER_VERSION_LEN];
+};
+
+/**
+ * struct imanager_ec_data - iManager EC data structure
+ * @features:	iManager feature mask
+ * @attr:	array of iManager device attribute structure
+ * @io:		imanager_io_ops structure providing I/O operations
+ * @gpio:	iManager GPIO device structure
+ * @hwmon:	iManager Hardware monitor device structure
+ * @i2c:	iManager I2C/SMBus device structure
+ * @bl:		iManager Backlight/Brightness device structure
+ * @wdt:	iManager Watchdog device structure
+ */
+struct imanager_ec_data {
+	unsigned int features;
+	const char *chip_name;
+	struct imanager_device_attribute	attr[EC_MAX_DID];
+	struct imanager_io_ops			io;
+	struct imanager_gpio_device		gpio;
+	struct imanager_hwmon_device		hwmon;
+	struct imanager_i2c_device		i2c;
+	struct imanager_backlight_device	bl;
+	struct imanager_watchdog_device		wdt;
+	struct imanager_info			info;
+};
+
+/**
+ * struct imanager_device_data - Internal representation of the iManager device
+ * @ec:		iManager data structure describing the EC
+ * @dev:	Pointer to kernel device structure
+ * @lock:	iManager mutex
+ */
+struct imanager_device_data {
+	struct imanager_ec_data	ec;
+	struct device	*dev;
+	struct mutex	lock; /* generic mutex for imanager core */
+};
+
+enum ec_ram_type { EC_RAM_ACPI = 1, EC_RAM_HW, EC_RAM_EXT };
+
+int imanager_read(struct imanager_ec_data *ec, u8 cmd,
+		  struct imanager_ec_message *msg);
+int imanager_write(struct imanager_ec_data *ec, u8 cmd,
+		   struct imanager_ec_message *msg);
+
+int imanager_read8(struct imanager_ec_data *ec, u8 cmd, u8 param);
+int imanager_write8(struct imanager_ec_data *ec, u8 cmd, u8 param, u8 byte);
+
+int imanager_read16(struct imanager_ec_data *ec, u8 cmd, u8 param);
+int imanager_write16(struct imanager_ec_data *ec, u8 cmd, u8 param, u16 word);
+
+int imanager_read_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+		      u8 *buf, u8 len);
+int imanager_write_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+		       u8 *data, u8 size);
+
+#endif
-- 
2.10.1

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

* [PATCH v4 2/6] Add Advantech iManager GPIO driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  2016-11-05  8:29   ` Linus Walleij
  2016-11-02  8:37 ` [PATCH v4 3/6] Add Advantech iManager HWmon driver Richard Vidal-Dorsch
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/gpio/Kconfig         |  10 +++
 drivers/gpio/Makefile        |   1 +
 drivers/gpio/gpio-imanager.c | 155 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+)
 create mode 100644 drivers/gpio/gpio-imanager.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d011cb8..52e371f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -884,6 +884,16 @@ config HTC_EGPIO
 	    several HTC phones.  It provides basic support for input
 	    pins, output pins, and irqs.
 
+config GPIO_IMANAGER
+	tristate "Advantech iManager GPIO"
+	depends on MFD_IMANAGER
+	help
+	  This enables support for the iManager GPIO interface on some
+	  Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-imanager.
+
 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 ab28a2d..e1250eb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_GPIO_GPIO_MM)	+= gpio-gpio-mm.o
 obj-$(CONFIG_GPIO_GRGPIO)	+= gpio-grgpio.o
 obj-$(CONFIG_HTC_EGPIO)		+= gpio-htc-egpio.o
 obj-$(CONFIG_GPIO_ICH)		+= gpio-ich.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/gpio-imanager.c b/drivers/gpio/gpio-imanager.c
new file mode 100644
index 0000000..f60592b
--- /dev/null
+++ b/drivers/gpio/gpio-imanager.c
@@ -0,0 +1,155 @@
+/*
+ * Advantech iManager GPIO driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define EC_GPIOF_DIR_OUT	BIT(6)
+#define EC_GPIOF_DIR_IN		BIT(7)
+
+struct imanager_gpio_data {
+	struct imanager_device_data *imgr;
+	struct gpio_chip chip;
+};
+
+static int imanager_gpio_direction_in(struct gpio_chip *chip, uint offset)
+{
+	struct imanager_gpio_data *data = gpiochip_get_data(chip);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+	mutex_lock(&imgr->lock);
+	imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+			EC_GPIOF_DIR_IN);
+	mutex_unlock(&imgr->lock);
+
+	return 0;
+}
+
+static int
+imanager_gpio_direction_out(struct gpio_chip *chip, uint offset, int val)
+{
+	struct imanager_gpio_data *data = gpiochip_get_data(chip);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+	mutex_lock(&imgr->lock);
+	imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+			EC_GPIOF_DIR_OUT);
+	mutex_unlock(&imgr->lock);
+
+	return 0;
+}
+
+static int imanager_gpio_get_direction(struct gpio_chip *chip, uint offset)
+{
+	struct imanager_gpio_data *data = gpiochip_get_data(chip);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_read8(&imgr->ec, EC_CMD_GPIO_DIR_RD, attr->did);
+	mutex_unlock(&imgr->lock);
+
+	return ret & EC_GPIOF_DIR_IN ? GPIOF_DIR_IN : GPIOF_DIR_OUT;
+}
+
+static int imanager_gpio_get(struct gpio_chip *chip, uint offset)
+{
+	struct imanager_gpio_data *data = gpiochip_get_data(chip);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, attr->did);
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static void imanager_gpio_set(struct gpio_chip *chip, uint offset, int val)
+{
+	struct imanager_gpio_data *data = gpiochip_get_data(chip);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+	mutex_lock(&imgr->lock);
+	imanager_write8(&imgr->ec, EC_CMD_HWP_WR, attr->did, val);
+	mutex_unlock(&imgr->lock);
+}
+
+static int imanager_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_gpio_data *gpio;
+	struct gpio_chip *chip;
+	int ret;
+
+	gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+	if (!gpio)
+		return -ENOMEM;
+
+	gpio->imgr = imgr;
+
+	platform_set_drvdata(pdev, gpio);
+
+	chip = &gpio->chip;
+
+	chip->owner = THIS_MODULE;
+	chip->parent = dev;
+	chip->label = "gpio-imanager";
+	chip->base = -1;
+	chip->ngpio = imgr->ec.gpio.num;
+	chip->get = imanager_gpio_get;
+	chip->set = imanager_gpio_set;
+	chip->direction_input = imanager_gpio_direction_in;
+	chip->direction_output = imanager_gpio_direction_out;
+	chip->get_direction = imanager_gpio_get_direction;
+	if (!chip->ngpio) {
+		dev_err(dev, "No GPIO pins detected\n");
+		return -ENODEV;
+	}
+
+	ret = devm_gpiochip_add_data(dev, chip, gpio);
+	if (ret < 0) {
+		dev_err(dev, "Could not register GPIO chip\n");
+		return ret;
+	}
+
+	dev_info(dev, "GPIO initialized with %d pins\n", chip->ngpio);
+
+	return 0;
+}
+
+static struct platform_driver imanager_gpio_driver = {
+	.driver = {
+		.name	= "imanager-gpio",
+	},
+	.probe	= imanager_gpio_probe,
+};
+
+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");
-- 
2.10.1

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

* [PATCH v4 3/6] Add Advantech iManager HWmon driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 4/6] Add Advantech iManager I2C driver Richard Vidal-Dorsch
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/hwmon/Kconfig          |   11 +
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/imanager-hwmon.c | 1226 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1238 insertions(+)
 create mode 100644 drivers/hwmon/imanager-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..7a9db12 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -625,6 +625,17 @@ config SENSORS_CORETEMP
 	  sensor inside your CPU. Most of the family 6 CPUs
 	  are supported. Check Documentation/hwmon/coretemp for details.
 
+config SENSORS_IMANAGER
+	tristate "Advantech iManager Hardware Monitoring"
+	depends on MFD_IMANAGER
+	select HWMON_VID
+	help
+	  This enables support for Advantech iManager hardware monitoring
+	  of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called imanager-hwmon.
+
 config SENSORS_IT87
 	tristate "ITE IT87xx and compatibles"
 	depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..3f489ae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
 obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
 obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER)	+= imanager-hwmon.o
 obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
 obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
 obj-$(CONFIG_SENSORS_INA3221)	+= ina3221.o
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..af72c35
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1226 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Partially derived from nct6775
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN			2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+#define HWM_STATUS_UNDEFINED_ITEM	2UL
+#define HWM_STATUS_UNDEFINED_DID	3UL
+#define HWM_STATUS_UNDEFINED_HWPIN	4UL
+
+/* iManager EC FW pwm[1-*]_mode values are switched */
+enum imanager_pwm_mode { CTRL_PWM, CTRL_RPM };
+/* EC FW defines pwm_enable mode 'full speed' besides mode 'off' */
+enum imanager_pwm_enable { MODE_OFF, MODE_FULL, MODE_MANUAL, MODE_AUTO };
+
+/*
+ * iManager FAN device defs
+ */
+struct fan_dev_config {
+	u8	did,
+		hwpin,
+		tachoid,
+		status,
+		control,
+		temp_max,
+		temp_min,
+		temp_stop,
+		pwm_max,
+		pwm_min;
+	u16	rpm_max;
+	u16	rpm_min;
+	u8	debounce;	/* debounce time, not used */
+	u8	temp;		/* Current Thermal Zone Temperature */
+	u16	rpm_target;	/* RPM Target Speed, not used */
+} __attribute__((__packed__));
+
+struct fan_alert_limit {
+	u16	min,
+		max;
+} __attribute__((__packed__));
+
+struct fan_status {
+	u32	sysctl	: 1,	/* System Control flag */
+		tacho	: 1,	/* FAN tacho source defined */
+		pulse	: 1,	/* FAN pulse type defined */
+		thermal	: 1,	/* Thermal zone init */
+		i2clink	: 1,	/* I2C protocol fail flag (thermal sensor) */
+		dnc	: 1,	/* don't care */
+		mode	: 2;	/* FAN Control mode */
+};
+
+/**
+ * FAN Control bit field
+ * enable:   0:Disabled, 1:Enabled
+ * type:     0:PWM,      1:RPM
+ * pulse:    0:Undefined 1:2 Pulses  2:4 Pulses
+ * tacho:    1:CPU FAN,  2:SYS FAN1, 3:SYS FAN2
+ * mode:     0:Off,      1:Full,     2:Manual, 3:Auto
+ */
+struct fan_ctrl {
+	u32	enable	: 1,	/* SmartFAN control on/off */
+		type	: 1,	/* FAN control type [0, 1] PWM/RPM */
+		pulse	: 2,	/* FAN pulse [0..2] */
+		tacho	: 2,	/* FAN Tacho Input [1..3] */
+		mode	: 2;	/* off/full/manual/auto */
+};
+
+/* Default Voltage Sensors */
+struct imanager_hwmon_adc {
+	bool valid; /* if set, below values are valid */
+	unsigned int value;
+	unsigned int min;
+	unsigned int max;
+	unsigned int average;
+	unsigned int lowest;
+	unsigned int highest;
+};
+
+struct imanager_hwmon_smartfan {
+	bool valid; /* if set, below values are valid */
+	unsigned int pwm;
+	unsigned int speed;
+	bool speed_min_alarm;
+	bool speed_max_alarm;
+	struct fan_dev_config cfg;
+};
+
+struct imanager_hwmon_data {
+	struct imanager_device_data	*imgr;
+	const struct attribute_group	*groups[3];
+	unsigned long			samples;
+	unsigned long			last_updated;
+	struct imanager_hwmon_adc	adc[EC_MAX_ADC_NUM];
+	struct imanager_hwmon_smartfan	fan[EC_MAX_FAN_NUM];
+};
+
+static int imanager_hwmon_read_fan_config(struct imanager_ec_data *ec, int num,
+					  struct imanager_hwmon_smartfan *fan)
+{
+	struct imanager_ec_message msg = {
+		IMANAGER_MSG_SIMPLE(EC_F_HWMON_MSG, 0, num, (u8 *)&fan->cfg)
+	};
+	int ret;
+
+	ret = imanager_read(ec, EC_CMD_FAN_CTL_RD, &msg);
+	if (ret)
+		return ret;
+
+	return fan->cfg.did ? 0 : -ENODEV;
+}
+
+static int
+imanager_hwmon_write_fan_config(struct imanager_ec_data *ec, int num,
+				struct imanager_hwmon_smartfan *fan)
+{
+	struct imanager_ec_message msg = {
+		IMANAGER_MSG_SIMPLE(0, sizeof(fan->cfg), num, (u8 *)&fan->cfg)
+	};
+	int err;
+
+	err = imanager_write(ec, EC_CMD_FAN_CTL_WR, &msg);
+	if (err < 0) {
+		return err;
+	} else if (err) {
+		switch (err) {
+		case HWM_STATUS_UNDEFINED_ITEM:
+		case HWM_STATUS_UNDEFINED_DID:
+		case HWM_STATUS_UNDEFINED_HWPIN:
+			return -ENODEV;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * FAN max/min alert bits are stored as bit pairs in a 8-bit register.
+ * FAN0~2: 2:{max2, min2}, 1:{max1, min1}, 0:{max0, min0}
+ */
+#define IS_FAN_ALERT_MIN(fan_num, var) test_bit(BIT((fan_num) << 1), var)
+#define IS_FAN_ALERT_MAX(fan_num, var) test_bit(BIT(((fan_num) << 1) + 1), var)
+
+static int imanager_hwmon_read_fan_alert(struct imanager_ec_data *ec, int num,
+					 struct imanager_hwmon_smartfan *fan)
+{
+	const ulong alert_flags = 0;
+	int ret;
+
+	ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT,
+				(u8 *)&alert_flags, sizeof(u8));
+	if (ret < 0)
+		return ret;
+
+	fan->speed_min_alarm = IS_FAN_ALERT_MIN(num, &alert_flags);
+	fan->speed_max_alarm = IS_FAN_ALERT_MAX(num, &alert_flags);
+
+	return 0;
+}
+
+static int imanager_hwmon_write_fan_alert(struct imanager_ec_data *ec, int fnum,
+					  struct imanager_hwmon_smartfan *fan)
+{
+	struct fan_alert_limit limits[EC_MAX_FAN_NUM];
+	struct fan_alert_limit *limit = &limits[fnum];
+	int ret;
+
+	ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+				(u8 *)limits, sizeof(limits));
+	if (ret < 0)
+		return ret;
+
+	limit->min = fan->cfg.rpm_min;
+	limit->max = fan->cfg.rpm_max;
+
+	return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+				  (u8 *)limits, sizeof(limits));
+}
+
+static int imanager_hwmon_read_adc(struct imanager_ec_data *ec, int num,
+				   struct imanager_hwmon_adc *adc)
+{
+	struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+	int ret;
+
+	adc->valid = false;
+
+	ret = imanager_read16(ec, EC_CMD_HWP_RD, attr->did);
+	if (ret < 0)
+		return ret;
+
+	adc->value = ret * attr->ecdev->scale;
+	adc->valid = true;
+
+	return 0;
+}
+
+static int imanager_hwmon_read_fan_ctrl(struct imanager_ec_data *ec, int num,
+					struct imanager_hwmon_smartfan *fan)
+{
+	struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+
+	int ret;
+
+	fan->valid = false;
+
+	ret = imanager_hwmon_read_fan_config(ec, num, fan);
+	if (ret < 0)
+		return ret;
+
+	ret = imanager_read16(ec, EC_CMD_HWP_RD, fan->cfg.tachoid);
+	if (ret < 0)
+		return ret;
+
+	fan->speed = ret;
+
+	ret = imanager_read8(ec, EC_CMD_HWP_RD, attr->did);
+	if (ret < 0)
+		return ret;
+
+	fan->pwm = ret;
+
+	ret = imanager_hwmon_read_fan_alert(ec, num, fan);
+	if (ret)
+		return ret;
+
+	fan->valid = true;
+
+	return 0;
+}
+
+static inline uint in_from_reg(u16 val)
+{
+	return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(uint val)
+{
+	return clamp_val(DIV_ROUND_CLOSEST(val * 1000, SCALE_IN), 0, 65535);
+}
+
+static struct imanager_hwmon_data *
+imanager_hwmon_update_device(struct device *dev)
+{
+	struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_ec_data *ec = &imgr->ec;
+	struct imanager_hwmon_device *hwmon = &ec->hwmon;
+	int i;
+
+	mutex_lock(&imgr->lock);
+
+	if (time_after(jiffies, data->last_updated + HZ + HZ / 2)) {
+		/* Measured voltages */
+		for (i = 0; i < hwmon->adc.num; i++)
+			imanager_hwmon_read_adc(ec, i, &data->adc[i]);
+
+		/* Measured fan speeds */
+		for (i = 0; i < hwmon->fan.num; i++)
+			imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+		data->last_updated = jiffies;
+	}
+
+	mutex_unlock(&imgr->lock);
+
+	return data;
+}
+
+static ssize_t
+show_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+
+	return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].value));
+}
+
+static ssize_t
+show_in_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+
+	return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].min));
+}
+
+static ssize_t
+show_in_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+
+	return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].max));
+}
+
+static ssize_t
+store_in_min(struct device *dev, struct device_attribute *attr,
+	     const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	data->adc[nr].min = in_to_reg(val);
+
+	return count;
+}
+
+static ssize_t
+store_in_max(struct device *dev, struct device_attribute *attr,
+	     const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	data->adc[nr].max = in_to_reg(val);
+
+	return count;
+}
+
+static ssize_t
+show_in_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	struct imanager_hwmon_adc *adc = &data->adc[nr];
+	int val = 0;
+
+	if (adc->valid)
+		val = (adc->value < adc->min) || (adc->value > adc->max);
+
+	return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+show_in_average(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+	if (adc->average) {
+		adc->average = DIV_ROUND_CLOSEST(adc->average * data->samples +
+						 adc->value, ++data->samples);
+	} else {
+		adc->average = adc->value;
+		data->samples = 1;
+	}
+
+	return sprintf(buf, "%u\n", in_from_reg(adc->average));
+}
+
+static ssize_t
+show_in_lowest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+	if (!adc->lowest || (adc->value < adc->lowest))
+		adc->lowest = adc->value;
+
+	return sprintf(buf, "%u\n", in_from_reg(adc->lowest));
+}
+
+static ssize_t
+show_in_highest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+	if (!adc->highest || (adc->value > adc->highest))
+		adc->highest = adc->value;
+
+	return sprintf(buf, "%u\n", in_from_reg(adc->highest));
+}
+
+static ssize_t
+store_in_reset_history(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index;
+	unsigned long reset;
+	int err;
+
+	err = kstrtoul(buf, 10, &reset);
+	if (err < 0)
+		return err;
+
+	if (reset) {
+		data->adc[nr].lowest = 0;
+		data->adc[nr].highest = 0;
+		data->adc[nr].average = 0;
+		data->samples = 0;
+	}
+
+	return count;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%u\n", data->fan[nr].cfg.temp * 1000);
+}
+
+static ssize_t
+show_fan_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+
+	return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int check_fan_alarm(const struct imanager_hwmon_smartfan *fan)
+{
+	int rpm_min = cpu_to_be16(fan->cfg.rpm_min);
+	int rpm_max = cpu_to_be16(fan->cfg.rpm_max);
+
+	return !fan->valid || fan->speed_min_alarm || fan->speed_max_alarm ||
+	       (!fan->speed && fan->pwm) || (fan->speed < rpm_min) ||
+	       (fan->speed > rpm_max);
+}
+
+static ssize_t
+show_fan_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 : check_fan_alarm(fan);
+
+	return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	int rpm_min = cpu_to_be16(data->fan[nr].cfg.rpm_min);
+
+	return sprintf(buf, "%u\n", rpm_min);
+}
+
+static ssize_t
+show_fan_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	int rpm_max = cpu_to_be16(data->fan[nr].cfg.rpm_max);
+
+	return sprintf(buf, "%u\n", rpm_max);
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+	      const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	unsigned long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	/* do not apply value if not in cruise mode */
+	if (ctrl->mode != MODE_AUTO)
+		return count;
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.rpm_min = cpu_to_be16(val);
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+store_fan_max(struct device *dev, struct device_attribute *attr,
+	      const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	unsigned long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	/* do not apply value if not in cruise mode */
+	if (ctrl->mode != MODE_AUTO)
+		return count;
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.rpm_max = cpu_to_be16(val);
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	u32 val = DIV_ROUND_CLOSEST(data->fan[nr].pwm * 255, 100);
+
+	return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+store_pwm(struct device *dev, struct device_attribute *attr,
+	  const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	int did = imgr->ec.hwmon.fan.attr[nr]->did;
+	unsigned long pwm = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &pwm);
+	if (err < 0)
+		return err;
+
+	pwm = DIV_ROUND_CLOSEST(pwm * 100, 255);
+
+	mutex_lock(&imgr->lock);
+
+	if (ctrl->mode == MODE_MANUAL) {
+		ctrl->type = CTRL_PWM;
+		ctrl->pulse = 0;
+		ctrl->enable = 1;
+		imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+		imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+	} else if ((ctrl->mode == MODE_AUTO) && (ctrl->type == CTRL_PWM)) {
+		ctrl->pulse = 0;
+		ctrl->enable = 1;
+		fan->cfg.rpm_min = 0;
+		fan->cfg.rpm_max = 0;
+		imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+		imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+		imanager_hwmon_write_fan_alert(&imgr->ec, nr, fan);
+	}
+
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+show_pwm_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_min);
+}
+
+static ssize_t
+show_pwm_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_max);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	uint mode = ctrl->mode - 1;
+
+	if (ctrl->mode == MODE_OFF)
+		mode = 0;
+
+	return sprintf(buf, "%u\n", mode);
+}
+
+enum pwm_enable { OFF, MANUAL, THERMAL_CRUISE };
+
+static ssize_t
+store_pwm_enable(struct device *dev, struct device_attribute *attr,
+		 const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	int did = imgr->ec.hwmon.fan.attr[nr]->did;
+	unsigned long mode = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &mode);
+	if (err < 0)
+		return err;
+
+	mutex_lock(&imgr->lock);
+
+	switch (mode) {
+	case OFF:
+		ctrl->mode = MODE_FULL;
+		ctrl->type = CTRL_PWM;
+		ctrl->enable = 1;
+		imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+		imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 100);
+		break;
+	case MANUAL:
+		ctrl->mode = MODE_MANUAL;
+		ctrl->type = CTRL_PWM;
+		ctrl->enable = 1;
+		imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+		imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+		break;
+	case THERMAL_CRUISE:
+		ctrl->mode = MODE_AUTO;
+		ctrl->enable = 1;
+		imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+		imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+		break;
+	}
+
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	uint mode = (ctrl->type == CTRL_PWM) ? 1 : 0;
+
+	if (ctrl->mode == MODE_OFF)
+		mode = 0;
+
+	return sprintf(buf, "%u\n", mode);
+}
+
+static ssize_t
+store_pwm_mode(struct device *dev, struct device_attribute *attr,
+	       const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	unsigned long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	if (ctrl->mode != MODE_AUTO)
+		return count;
+
+	mutex_lock(&imgr->lock);
+	ctrl->type = val ? CTRL_RPM : CTRL_PWM;
+	ctrl->enable = 1;
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%d\n", data->fan[nr].cfg.temp_min * 1000);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%u\n", data->fan[nr].cfg.temp_max * 1000);
+}
+
+static inline bool is_outside(int min, int max, int value)
+{
+	return (value < min) || (value > max);
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct fan_dev_config *cfg = &data->fan[nr].cfg;
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg->control;
+	bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 :
+			is_outside(cfg->temp_min, cfg->temp_max, cfg->temp);
+
+	return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	/* temperature in 1/10 degC */
+	val = DIV_ROUND_CLOSEST(val, 1000);
+	val = val > 100 ? 100 : val;
+
+	/* do not apply value if not in cruise mode */
+	if (ctrl->mode != MODE_AUTO)
+		return count;
+
+	/*
+	 * The iManager provides three different temperature limit values
+	 * (stop, min, and max) where stop indicates a minimum temp value
+	 * (threshold) from which the FAN will turn off.  We are setting
+	 * temp_stop to the same value as temp_min since it cannot be mapped
+	 * to anything else.
+	 */
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.temp_stop = val;
+	fan->cfg.temp_min = val;
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+store_temp_max(struct device *dev, struct device_attribute *attr,
+	       const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+	long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	val = DIV_ROUND_CLOSEST(val, 1000);
+	val = val > 100 ? 100 : val;
+
+	/* do not apply value if not in cruise mode */
+	if (ctrl->mode != MODE_AUTO)
+		return count;
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.temp_max = val;
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+store_pwm_min(struct device *dev, struct device_attribute *attr,
+	      const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.pwm_min = val;
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+store_pwm_max(struct device *dev, struct device_attribute *attr,
+	      const char *buf, size_t count)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_device_data *imgr = data->imgr;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+	struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+	long val = 0;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err < 0)
+		return err;
+
+	val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+	mutex_lock(&imgr->lock);
+	fan->cfg.pwm_max = val;
+	imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+	mutex_unlock(&imgr->lock);
+
+	return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+	int nr = to_sensor_dev_attr(attr)->index;
+
+	return sprintf(buf, "%s\n", hwmon->adc.label[nr]);
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%s\n", hwmon->fan.temp_label[nr]);
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+	struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+	int nr = to_sensor_dev_attr(attr)->index - 1;
+
+	return sprintf(buf, "%s\n", hwmon->fan.label[nr]);
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, 0444, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, 0644, show_in_min, store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, 0644, show_in_max, store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, 0444, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, 0644, show_in_min, store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, 0644, show_in_max, store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, 0444, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, 0644, show_in_min, store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, 0644, show_in_max, store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, 0444, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_temp_min, store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, 0644, show_temp_min, store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, 0644, show_temp_min, store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, 0444, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, 0644, show_fan_min, store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, 0644, show_fan_max, store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, 0444, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, 0644, show_fan_min, store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, 0644, show_fan_max, store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, 0444, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, 0644, show_fan_min, store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, 0644, show_fan_max, store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644, show_pwm_min, store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644, show_pwm_max, store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, show_pwm_enable,
+			  store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, 0644, show_pwm_mode, store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, 0644, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, 0644, show_pwm_min, store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, 0644, show_pwm_max, store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, show_pwm_enable,
+			  store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, 0644, show_pwm_mode, store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, 0644, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, 0644, show_pwm_min, store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, 0644, show_pwm_max, store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644, show_pwm_enable,
+			  store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, 0644, show_pwm_mode, store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, 0444, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, 0644, show_in_min, store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, 0644, show_in_max, store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, 0444, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, 0444, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, 0444, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, 0444, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, 0200, NULL,
+			  store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, 0444, show_in, NULL, 3);
+
+static struct attribute *imanager_in_attributes[] = {
+	&sensor_dev_attr_in0_label.dev_attr.attr,
+	&sensor_dev_attr_in0_input.dev_attr.attr,
+	&sensor_dev_attr_in0_min.dev_attr.attr,
+	&sensor_dev_attr_in0_max.dev_attr.attr,
+	&sensor_dev_attr_in0_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_in1_label.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_in2_label.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_min.dev_attr.attr,
+	&sensor_dev_attr_in2_max.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+
+	NULL
+};
+
+#define to_dev(obj) container_of(obj, struct device, kobj)
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	struct device *dev = to_dev(kobj);
+	struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+	struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+	if (hwmon->adc.num <= EC_MAX_ADC_NUM)
+		return attr->mode;
+
+	return 0;
+}
+
+static const struct attribute_group imanager_group_in = {
+	.attrs = imanager_in_attributes,
+	.is_visible = imanager_in_is_visible,
+};
+
+static struct attribute *imanager_other_attributes[] = {
+	&sensor_dev_attr_curr1_input.dev_attr.attr,
+	&sensor_dev_attr_curr1_min.dev_attr.attr,
+	&sensor_dev_attr_curr1_max.dev_attr.attr,
+	&sensor_dev_attr_curr1_alarm.dev_attr.attr,
+	&sensor_dev_attr_curr1_average.dev_attr.attr,
+	&sensor_dev_attr_curr1_lowest.dev_attr.attr,
+	&sensor_dev_attr_curr1_highest.dev_attr.attr,
+	&sensor_dev_attr_curr1_reset_history.dev_attr.attr,
+
+	&sensor_dev_attr_cpu0_vid.dev_attr.attr,
+
+	NULL
+};
+
+static umode_t imanager_other_is_visible(struct kobject *kobj,
+					 struct attribute *attr, int index)
+{
+	struct device *dev = to_dev(kobj);
+	struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+	struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+	/*
+	 * There are either 3 or 5 VINs available
+	 * vin3 is current monitoring
+	 * vin4 is CPU VID
+	 */
+	if (hwmon->adc.num == EC_MAX_ADC_NUM)
+		return attr->mode;
+
+	return 0;
+}
+
+static const struct attribute_group imanager_group_other = {
+	.attrs = imanager_other_attributes,
+	.is_visible = imanager_other_is_visible,
+};
+
+static struct attribute *imanager_fan_attributes[] = {
+	&sensor_dev_attr_fan1_label.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_fan2_label.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_max.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_fan3_label.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_max.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_temp1_label.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_temp2_label.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_temp3_label.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_min.dev_attr.attr,
+	&sensor_dev_attr_pwm1_max.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_mode.dev_attr.attr,
+
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm2_min.dev_attr.attr,
+	&sensor_dev_attr_pwm2_max.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_mode.dev_attr.attr,
+
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm3_min.dev_attr.attr,
+	&sensor_dev_attr_pwm3_max.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_mode.dev_attr.attr,
+
+	NULL
+};
+
+static umode_t
+imanager_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	struct device *dev = to_dev(kobj);
+	struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+	struct imanager_fan_device *fan = &data->imgr->ec.hwmon.fan;
+
+	if ((index >= 0) && (index <= 14)) { /* fan */
+		if (!fan->attr[index / 5])
+			return 0;
+	} else if ((index >= 15) && (index <= 29)) { /* temp */
+		if (!fan->attr[(index - 15) / 5])
+			return 0;
+	} else if ((index >= 30) && (index <= 34)) { /* pwm */
+		if (!fan->attr[(index - 30) / 5])
+			return 0;
+	}
+
+	return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+	.attrs = imanager_fan_attributes,
+	.is_visible = imanager_fan_is_visible,
+};
+
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_ec_data *ec = &imgr->ec;
+	struct imanager_hwmon_device *hwmon = &ec->hwmon;
+	struct imanager_hwmon_data *data;
+	struct device *hwmon_dev;
+	int i, num_attr_groups = 0;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->imgr = imgr;
+	platform_set_drvdata(pdev, data);
+
+	/* read fan control settings */
+	for (i = 0; i < hwmon->fan.num; i++)
+		imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+	data->groups[num_attr_groups++] = &imanager_group_in;
+
+	if (hwmon->adc.num > 3)
+		data->groups[num_attr_groups++] = &imanager_group_other;
+
+	if (hwmon->fan.num)
+		data->groups[num_attr_groups++] = &imanager_group_fan;
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+							   "imanager_hwmon",
+							   data, data->groups);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver imanager_hwmon_driver = {
+	.driver = {
+		.name  = "imanager-hwmon",
+	},
+	.probe	= imanager_hwmon_probe,
+};
+
+module_platform_driver(imanager_hwmon_driver);
+
+MODULE_DESCRIPTION("Advantech iManager HWmon Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-hwmon");
-- 
2.10.1

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

* [PATCH v4 4/6] Add Advantech iManager I2C driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
                   ` (2 preceding siblings ...)
  2016-11-02  8:37 ` [PATCH v4 3/6] Add Advantech iManager HWmon driver Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 5/6] Add Advantech iManager Backlight driver Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 6/6] Add Advantech iManager Watchdog driver Richard Vidal-Dorsch
  5 siblings, 0 replies; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/i2c/busses/Kconfig        |  10 +
 drivers/i2c/busses/Makefile       |   1 +
 drivers/i2c/busses/i2c-imanager.c | 461 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 472 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-imanager.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d252276..7d1ecb4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -42,6 +42,16 @@ 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.
+
+	  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 29764cc..d9ff210 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_GPIO)		+= i2c-gpio.o
 obj-$(CONFIG_I2C_HIGHLANDER)	+= i2c-highlander.o
 obj-$(CONFIG_I2C_HIX5HD2)	+= i2c-hix5hd2.o
 obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
+obj-$(CONFIG_I2C_IMANAGER)	+= i2c-imanager.o
 obj-$(CONFIG_I2C_IMG)		+= i2c-img-scb.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
 obj-$(CONFIG_I2C_IOP3XX)	+= i2c-iop3xx.o
diff --git a/drivers/i2c/busses/i2c-imanager.c b/drivers/i2c/busses/i2c-imanager.c
new file mode 100644
index 0000000..15c496d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-imanager.c
@@ -0,0 +1,461 @@
+/*
+ * Advantech iManager SMBus bus driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define I2C_SMBUS_BLOCK_SIZE	32UL
+#define I2C_MAX_READ_SIZE	I2C_SMBUS_BLOCK_SIZE
+#define I2C_MAX_WRITE_SIZE	(I2C_SMBUS_BLOCK_SIZE - 1)
+
+#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 SMBUS_FREQ_50KHZ	0x0100
+#define SMBUS_FREQ_100KHZ	0x0200
+#define SMBUS_FREQ_400KHZ	0x0300
+
+#define imanager_i2c_wr_combined(ec, message) \
+	imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_WR)
+
+#define imanager_i2c_rw_combined(ec, message) \
+	imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_RW)
+
+struct ec_i2c_status {
+	u32 error	: 7;
+	u32 complete	: 1;
+};
+
+struct adapter_info {
+	struct i2c_adapter adapter;
+	int smb_devid;
+};
+
+struct imanager_i2c_data {
+	struct device *dev;
+	struct imanager_device_data *imgr;
+	struct adapter_info adap_info[EC_MAX_SMB_NUM];
+	int nadap;
+};
+
+static int imanager_i2c_eval_status(u8 status)
+{
+	struct ec_i2c_status *_status = (struct ec_i2c_status *)&status;
+
+	switch (_status->error) {
+	case 0:
+		return 0;
+	case I2C_ERR_ADDR_NACK:
+		return -ENXIO;
+	case I2C_ERR_ACCESS:
+		return -EACCES;
+	case I2C_ERR_UNKNOWN:
+		return -EAGAIN;
+	case I2C_ERR_TIMEOUT:
+		return -ETIME;
+	case I2C_ERR_PROTO:
+		return -EPROTO;
+	}
+
+	return -EIO;
+}
+
+static int imanager_i2c_wait_proc_complete(struct imanager_ec_data *ec)
+{
+	int ret, i;
+	u8 val;
+
+	for (i = 0; i < EC_MAX_RETRY; i++) {
+		ret = imanager_read_ram(ec, EC_RAM_HW, EC_HWRAM_OFFSET_STATUS,
+					&val, sizeof(val));
+		if (ret < 0)
+			return ret;
+
+		if (!val)
+			return 0;
+
+		usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+	}
+
+	return -ETIME;
+}
+
+static int imanager_i2c_block_wr_rw_combined(struct imanager_ec_data *ec,
+					     struct imanager_ec_message *msg,
+					     unsigned int protocol)
+{
+	int ret;
+
+	ret = imanager_i2c_wait_proc_complete(ec);
+	if (ret)
+		return ret;
+
+	ret = imanager_write(ec, protocol, msg);
+	if (ret)
+		return imanager_i2c_eval_status(ret);
+
+	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;
+}
+
+static inline int
+imanager_i2c_write_freq(struct imanager_ec_data *ec, int did, int freq)
+{
+	return imanager_write16(ec, EC_CMD_SMB_FREQ_WR, did, freq);
+}
+
+static inline int eval_read_len(int len)
+{
+	return len && len <= I2C_MAX_READ_SIZE ? len : I2C_MAX_READ_SIZE;
+}
+
+static inline int
+imanager_i2c_read_block(struct imanager_ec_data *ec,
+			struct imanager_ec_message *msg, u8 *buf)
+{
+	int ret;
+
+	ret = imanager_i2c_wr_combined(ec, msg);
+	if (ret < 0)
+		return ret;
+
+	buf[0] = ret;
+	memcpy(&buf[1], msg->u.data, ret);
+
+	return 0;
+}
+
+static inline int
+imanager_i2c_write_block(struct imanager_ec_data *ec,
+			 struct imanager_ec_message *msg, u8 *buf)
+{
+	if (!buf[0] || (buf[0] > I2C_MAX_WRITE_SIZE))
+		return -EINVAL;
+
+	memcpy(&msg->u.data[EC_MSG_HDR_SIZE], &buf[1], buf[0]);
+
+	return imanager_i2c_wr_combined(ec, msg);
+}
+
+static s32 imanager_i2c_xfer(struct i2c_adapter *adap, u16 addr, ushort flags,
+			     char read_write, u8 command, int size,
+			     union i2c_smbus_data *smb_data)
+{
+	struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+	struct imanager_device_data *imgr = data->imgr;
+	struct imanager_ec_data *ec = &imgr->ec;
+	struct device *dev = data->dev;
+	int smb_devid = *(int *)adap->algo_data;
+	int val, ret = 0;
+	u16 addr16 = addr << 1; /* convert to 8-bit i2c slave address */
+	u8 *buf = smb_data->block;
+	struct imanager_ec_message msg = {
+		.rlen = 0,
+		.wlen = EC_MSG_HDR_SIZE,
+		.param = smb_devid,
+		.u = {
+			.smb.hdr = {
+				.addr_low  = addr16 & 0x00ff,
+				.addr_high = addr16 >> 8,
+				.rlen = 0,
+				.wlen = 0,
+			},
+		},
+	};
+	struct imanager_ec_smb_message *smb = &msg.u.smb;
+
+	mutex_lock(&imgr->lock);
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		msg.rlen = 0;
+		smb->hdr.rlen = 0;
+		smb->hdr.wlen = 1;
+		ret = imanager_i2c_wr_combined(ec, &msg);
+		break;
+	case I2C_SMBUS_BYTE:
+		if (read_write == I2C_SMBUS_WRITE) {
+			msg.rlen = 1;
+			smb->hdr.rlen = 1;
+			smb->hdr.wlen = 1;
+			smb->hdr.cmd = command;
+			val = imanager_i2c_wr_combined(ec, &msg);
+			if (val < 0)
+				ret = val;
+		} else {
+			if (!smb_data) {
+				ret = -EINVAL;
+				break;
+			}
+			msg.rlen = 1;
+			smb->hdr.rlen = 1;
+			smb->hdr.wlen = 0;
+			val = imanager_i2c_rw_combined(ec, &msg);
+			if (val < 0)
+				ret = val;
+			else
+				smb_data->byte = val;
+			break;
+		}
+	case I2C_SMBUS_BYTE_DATA:
+		if (!smb_data) {
+			ret = -EINVAL;
+			break;
+		}
+		if (read_write == I2C_SMBUS_WRITE) {
+			msg.rlen = 1;
+			msg.wlen += 1;
+			smb->hdr.rlen = 0;
+			smb->hdr.wlen = 2;
+			smb->hdr.cmd = command;
+			smb->data[0] = smb_data->byte;
+			val = imanager_i2c_wr_combined(ec, &msg);
+		} else {
+			msg.rlen = 1;
+			smb->hdr.rlen = 1;
+			smb->hdr.wlen = 1;
+			smb->hdr.cmd = command;
+			val = imanager_i2c_wr_combined(ec, &msg);
+		}
+		if (val < 0)
+			ret = val;
+		else
+			smb_data->byte = val;
+		break;
+	case I2C_SMBUS_WORD_DATA:
+		if (!smb_data) {
+			ret = -EINVAL;
+			break;
+		}
+		if (read_write == I2C_SMBUS_WRITE) {
+			msg.rlen = 1;
+			msg.wlen += 2;
+			smb->hdr.rlen = 0;
+			smb->hdr.wlen = 3;
+			smb->hdr.cmd = command;
+			smb->data[0] = smb_data->word & 0x00ff;
+			smb->data[1] = smb_data->word >> 8;
+			val = imanager_i2c_wr_combined(ec, &msg);
+		} else {
+			msg.rlen = 2;
+			smb->hdr.rlen = 2;
+			smb->hdr.wlen = 1;
+			smb->hdr.cmd = command;
+			val = imanager_i2c_wr_combined(ec, &msg);
+		}
+		if (val < 0)
+			ret = val;
+		else
+			smb_data->word = val;
+		break;
+	case I2C_SMBUS_BLOCK_DATA:
+		if (!smb_data) {
+			ret = -EINVAL;
+			break;
+		}
+		if (read_write == I2C_SMBUS_WRITE) {
+			msg.rlen = 1;
+			msg.wlen += buf[0];
+			smb->hdr.rlen = 0;
+			smb->hdr.wlen = 1 + buf[0];
+			smb->hdr.cmd = command;
+			ret = imanager_i2c_write_block(ec, &msg, buf);
+		} else {
+			msg.rlen = eval_read_len(buf[0]);
+			smb->hdr.rlen = msg.rlen;
+			smb->hdr.wlen = 1;
+			smb->hdr.cmd = command;
+			ret = imanager_i2c_read_block(ec, &msg, buf);
+		}
+		break;
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (!smb_data) {
+			ret = -EINVAL;
+			break;
+		}
+		if (read_write == I2C_SMBUS_WRITE) {
+			msg.rlen = 1;
+			msg.wlen += buf[0];
+			smb->hdr.rlen = 0;
+			smb->hdr.wlen = 1 + buf[0];
+			smb->hdr.cmd = command;
+			ret = imanager_i2c_write_block(ec, &msg, buf);
+		} else {
+			msg.rlen = eval_read_len(buf[0]);
+			smb->hdr.rlen = msg.rlen;
+			smb->hdr.wlen = 1;
+			smb->hdr.cmd = command;
+			ret = imanager_i2c_read_block(ec, &msg, buf);
+		}
+		break;
+	default:
+		dev_err(dev, "Unsupported transaction %d\n", size);
+		ret = -EOPNOTSUPP;
+	}
+
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static u32 imanager_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;
+}
+
+static const struct i2c_algorithm imanager_i2c_algorithm = {
+	.smbus_xfer     = imanager_i2c_xfer,
+	.functionality  = imanager_i2c_func,
+};
+
+static const struct i2c_adapter imanager_i2c_adapters[] = {
+	[SMB_EEP] = {
+		.owner	= THIS_MODULE,
+		.name	= "iManager SMB EEP adapter",
+		.class	= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+		.algo	= &imanager_i2c_algorithm,
+	},
+	[I2C_OEM] = {
+		.owner	= THIS_MODULE,
+		.name	= "iManager I2C OEM adapter",
+		.class	= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+		.algo	= &imanager_i2c_algorithm,
+	},
+	[SMB_1] = {
+		.owner	= THIS_MODULE,
+		.name	= "iManager SMB 1 adapter",
+		.class	= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+		.algo	= &imanager_i2c_algorithm,
+	},
+	[SMB_PECI] = {
+		.owner	= THIS_MODULE,
+		.name	= "iManager SMB PECI adapter",
+		.class	= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+		.algo	= &imanager_i2c_algorithm,
+	},
+};
+
+static int
+imanager_i2c_add_bus(struct imanager_i2c_data *i2c, struct adapter_info *info,
+		     const struct i2c_adapter *adap, int did, int freq)
+{
+	int ret;
+
+	info->adapter = *adap;
+	info->adapter.dev.parent = i2c->dev;
+	info->smb_devid = did;
+	info->adapter.algo_data = &info->smb_devid;
+	i2c_set_adapdata(&info->adapter, i2c);
+
+	ret = i2c_add_adapter(&info->adapter);
+	if (ret) {
+		dev_warn(i2c->dev, "Failed to add %s\n", info->adapter.name);
+		return ret;
+	}
+
+	ret = imanager_i2c_write_freq(&i2c->imgr->ec, did, freq);
+	if (ret < 0)
+		dev_warn(i2c->dev, "Failed to set bus frequency of %s\n",
+			 info->adapter.name);
+
+	return 0;
+}
+
+static int imanager_i2c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_device_attribute **attr = imgr->ec.i2c.attr;
+	struct imanager_i2c_data *data;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->imgr = imgr;
+	data->dev = dev;
+
+	if (attr[SMB_EEP])
+		imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+				     &imanager_i2c_adapters[SMB_EEP],
+				     attr[SMB_EEP]->did, SMBUS_FREQ_100KHZ);
+
+	if (attr[I2C_OEM])
+		imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+				     &imanager_i2c_adapters[I2C_OEM],
+				     attr[I2C_OEM]->did, SMBUS_FREQ_400KHZ);
+
+	if (attr[SMB_1])
+		imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+				     &imanager_i2c_adapters[SMB_1],
+				     attr[SMB_1]->did, SMBUS_FREQ_100KHZ);
+
+	if (attr[SMB_PECI])
+		imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+				     &imanager_i2c_adapters[SMB_PECI],
+				     attr[SMB_PECI]->did, SMBUS_FREQ_100KHZ);
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int imanager_i2c_remove(struct platform_device *pdev)
+{
+	struct imanager_i2c_data *i2c = dev_get_drvdata(&pdev->dev);
+	int i;
+
+	for (i = 0; i < i2c->nadap; i++) {
+		i2c_del_adapter(&i2c->adap_info[i].adapter);
+		i2c_set_adapdata(&i2c->adap_info[i].adapter, NULL);
+	}
+
+	return 0;
+}
+
+static struct platform_driver imanager_i2c_driver = {
+	.driver = {
+		.name  = "imanager-smbus",
+	},
+	.probe	= imanager_i2c_probe,
+	.remove	= imanager_i2c_remove,
+};
+
+module_platform_driver(imanager_i2c_driver);
+
+MODULE_DESCRIPTION("Advantech iManager SMBus Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-smbus");
-- 
2.10.1

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

* [PATCH v4 5/6] Add Advantech iManager Backlight driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
                   ` (3 preceding siblings ...)
  2016-11-02  8:37 ` [PATCH v4 4/6] Add Advantech iManager I2C driver Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  2016-11-02  8:37 ` [PATCH v4 6/6] Add Advantech iManager Watchdog driver Richard Vidal-Dorsch
  5 siblings, 0 replies; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/video/backlight/Kconfig       |   8 ++
 drivers/video/backlight/Makefile      |   1 +
 drivers/video/backlight/imanager_bl.c | 210 ++++++++++++++++++++++++++++++++++
 3 files changed, 219 insertions(+)
 create mode 100644 drivers/video/backlight/imanager_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5ffa4b4..2dac696 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -251,6 +251,14 @@ 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.
+
 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..713b406 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_BACKLIGHT_GENERIC)		+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_GPIO)		+= gpio_backlight.o
 obj-$(CONFIG_BACKLIGHT_HP680)		+= hp680_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)		+= jornada720_bl.o
+obj-$(CONFIG_BACKLIGHT_IMANAGER)	+= imanager_bl.o
 obj-$(CONFIG_BACKLIGHT_IPAQ_MICRO)	+= ipaq_micro_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3533)		+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3630A)		+= lm3630a_bl.o
diff --git a/drivers/video/backlight/imanager_bl.c b/drivers/video/backlight/imanager_bl.c
new file mode 100644
index 0000000..d8b4e3d
--- /dev/null
+++ b/drivers/video/backlight/imanager_bl.c
@@ -0,0 +1,210 @@
+/*
+ * Advantech iManager Backlight driver
+ * Partially derived from wm831x_bl
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/backlight.h>
+#include <linux/device.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/imanager.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define BL_MAX_PWM	100
+
+enum backlight_units { BL_UNIT_1 = 0, BL_UNIT_2 };
+
+static bool polarity = PWM_POLARITY_NORMAL;
+module_param(polarity, bool, 0444);
+MODULE_PARM_DESC(polarity, "Select backlight polarity (inverted := 1)");
+
+static ushort unit = BL_UNIT_1;
+module_param(unit, ushort, 0444);
+MODULE_PARM_DESC(unit, "Select backlight control unit [0, 1] (defaults to 0)");
+
+struct imanager_backlight_data {
+	struct imanager_device_data *imgr;
+};
+
+struct brightness_level {
+	uint value	: 7,	/* Brightness Value  - LSB [6..0] */
+	     enable	: 1;	/* Brightness Enable - MSB [7] */
+};
+
+struct backlight_ctrl {
+	uint 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 int imanager_bl_enable(struct imanager_ec_data *ec, int unit)
+{
+	u8 val8;
+	struct brightness_level *ctrl = (struct brightness_level *)&val8;
+	u8 devid = ec->bl.attr[unit]->did;
+	u8 bl_unit = ec->bl.brightness[unit];
+	int ret;
+
+	ret = imanager_read_ram(ec, EC_RAM_ACPI, bl_unit, &val8, sizeof(val8));
+	if (ret < 0)
+		return ret;
+
+	ctrl->enable = 1;
+
+	return imanager_write_ram(ec, EC_RAM_ACPI, devid, &val8, sizeof(val8));
+}
+
+static int imanager_bl_set_polarity(struct imanager_ec_data *ec, uint polarity)
+{
+	u8 val8;
+	struct backlight_ctrl *ctrl = (struct backlight_ctrl *)&val8;
+	int ret;
+
+	ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+				&val8, sizeof(val8));
+	if (ret < 0)
+		return ret;
+
+	ctrl->blpol = polarity ? 1 : 0;
+
+	return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+				  &val8, sizeof(val8));
+}
+
+static int imanager_bl_get_brightness(struct backlight_device *bd)
+{
+	struct imanager_backlight_data *data = bl_get_data(bd);
+	struct imanager_device_data *imgr = data->imgr;
+	u8 devid = imgr->ec.bl.attr[unit]->did;
+	int pwm;
+
+	mutex_lock(&imgr->lock);
+
+	pwm = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, devid);
+	if (pwm < 0) {
+		dev_warn(&bd->dev, "Failed while reading PWM\n");
+		pwm = 0;
+	}
+
+	mutex_unlock(&imgr->lock);
+
+	return polarity ? BL_MAX_PWM - pwm : pwm;
+}
+
+static int imanager_bl_set_brightness(struct backlight_device *bd)
+{
+	struct imanager_backlight_data *data = bl_get_data(bd);
+	struct imanager_device_data *imgr = data->imgr;
+	u8 devid = imgr->ec.bl.attr[unit]->did;
+	u8 brightness = bd->props.brightness;
+	int ret;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (bd->props.state & BL_CORE_SUSPENDED)
+		brightness = 0;
+
+	/* invert brightness if polarity is set */
+	brightness = polarity ? BL_MAX_PWM - brightness : brightness;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_write8(&imgr->ec, EC_CMD_HWP_WR, devid, brightness);
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static const struct backlight_ops imanager_bl_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.get_brightness = imanager_bl_get_brightness,
+	.update_status  = imanager_bl_set_brightness,
+};
+
+static int imanager_bl_init(struct device *dev,
+			    struct imanager_backlight_data *data)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = BL_MAX_PWM;
+	bd = devm_backlight_device_register(dev, "imanager-backlight", dev,
+					    data, &imanager_bl_ops, &props);
+
+	if (IS_ERR(bd)) {
+		dev_err(dev, "Unable to register backlight device\n");
+		return PTR_ERR(bd);
+	}
+
+	bd->props.brightness = imanager_bl_get_brightness(bd);
+	bd->props.max_brightness = BL_MAX_PWM;
+	bd->props.power = FB_BLANK_UNBLANK;
+
+	backlight_update_status(bd);
+
+	ret = imanager_bl_enable(&data->imgr->ec, unit);
+	if (ret < 0)
+		dev_warn(dev, "Could not enable backlight control\n");
+
+	ret = imanager_bl_set_polarity(&data->imgr->ec, polarity);
+	if (ret < 0)
+		dev_warn(dev, "Could not set backlight polarity\n");
+
+	return 0;
+}
+
+static int imanager_bl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_backlight_data *data;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->imgr = imgr;
+
+	ret = imanager_bl_init(dev, data);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static struct platform_driver imanager_backlight_driver = {
+	.driver = {
+		.name	= "imanager-backlight",
+	},
+	.probe	= imanager_bl_probe,
+};
+
+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");
-- 
2.10.1

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

* [PATCH v4 6/6] Add Advantech iManager Watchdog driver
  2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
                   ` (4 preceding siblings ...)
  2016-11-02  8:37 ` [PATCH v4 5/6] Add Advantech iManager Backlight driver Richard Vidal-Dorsch
@ 2016-11-02  8:37 ` Richard Vidal-Dorsch
  5 siblings, 0 replies; 9+ messages in thread
From: Richard Vidal-Dorsch @ 2016-11-02  8:37 UTC (permalink / raw)
  To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
	jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
	linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
  Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou

Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
 drivers/watchdog/Kconfig        |  11 ++
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/imanager_wdt.c | 303 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 drivers/watchdog/imanager_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index fdd3228..d6859da 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -912,6 +912,17 @@ config WAFER_WDT
 	  To compile this driver as a module, choose M here: the
 	  module will be called wafer5823wdt.
 
+config IMANAGER_WDT
+	tristate "Advantech iManager Watchdog"
+	depends on MFD_IMANAGER
+	select WATCHDOG_CORE
+	help
+	  Support for Advantech iManager watchdog on some Advantech
+	  SOM, MIO, AIMB, and PCM modules/boards.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called imanager_wdt.
+
 config I6300ESB_WDT
 	tristate "Intel 6300ESB Timer/Watchdog"
 	depends on PCI
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index caa9f4a..eb7fccf 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -117,6 +117,7 @@ endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
 obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
diff --git a/drivers/watchdog/imanager_wdt.c b/drivers/watchdog/imanager_wdt.c
new file mode 100644
index 0000000..53b409e
--- /dev/null
+++ b/drivers/watchdog/imanager_wdt.c
@@ -0,0 +1,303 @@
+/*
+ * Advantech iManager Watchdog driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT	30 /* seconds */
+#define WDT_FREQ		10 /* Hz */
+
+struct imanager_wdt_data {
+	struct imanager_device_data *imgr;
+	struct watchdog_device wdt;
+	ulong last_updated;
+	uint timeout;
+};
+
+static uint timeout = WDT_DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
+		 __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum wdt_ctrl {
+	START = 1, STOP, RESET, GET_TIMEOUT, SET_TIMEOUT, STOPBOOT = 8
+};
+
+enum imanager_wdt_event {
+	WDT_EVT_NONE,
+	WDT_EVT_DELAY,
+	WDT_EVT_PWRBTN,
+	WDT_EVT_NMI,
+	WDT_EVT_RESET,
+	WDT_EVT_WDPIN,
+	WDT_EVT_SCI,
+};
+
+struct event_delay {
+	u16	delay,
+		pwrbtn,
+		nmi,
+		reset,
+		wdpin,
+		sci,
+		dummy;
+} __attribute__((__packed__));
+
+static int imanager_wdt_ctrl(struct imanager_ec_data *ec, int ctrl,
+			     int event_type, uint timeout)
+{
+	struct imanager_ec_message msg = {
+		IMANAGER_MSG_SIMPLE(0, 0, ctrl, NULL)
+	};
+	u8 *fevent = &msg.u.data[0];
+	struct event_delay *event = (struct event_delay *)&msg.u.data[1];
+	int val;
+
+	if (ctrl == SET_TIMEOUT) {
+		memset(event, 0xff, sizeof(*event));
+		msg.wlen = sizeof(*event);
+		*fevent = 0;
+		val = (!timeout) ? 0xffff : cpu_to_be16(timeout * WDT_FREQ);
+
+		switch (event_type) {
+		case WDT_EVT_DELAY:
+			event->delay = val;
+			break;
+		case WDT_EVT_PWRBTN:
+			event->pwrbtn = val;
+			break;
+		case WDT_EVT_NMI:
+			event->nmi = val;
+			break;
+		case WDT_EVT_RESET:
+			event->reset = val;
+			break;
+		case WDT_EVT_WDPIN:
+			event->wdpin = val;
+			break;
+		case WDT_EVT_SCI:
+			event->sci = val;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return imanager_write(ec, EC_CMD_WDT_CTRL, &msg);
+}
+
+static inline int imanager_wdt_disable_all(struct imanager_wdt_data *data)
+{
+	struct imanager_ec_data *ec = &data->imgr->ec;
+
+	return (imanager_wdt_ctrl(ec, STOP, WDT_EVT_NONE, 0) ||
+		imanager_wdt_ctrl(ec, STOPBOOT, WDT_EVT_NONE, 0));
+}
+
+static int imanager_wdt_set(struct imanager_wdt_data *data, uint timeout)
+{
+	struct imanager_ec_data *ec = &data->imgr->ec;
+	int ret;
+
+	if (time_before(jiffies, data->last_updated + HZ + HZ / 2))
+		return 0;
+
+	if (data->timeout == timeout)
+		return 0;
+
+	ret = imanager_wdt_ctrl(ec, SET_TIMEOUT, WDT_EVT_PWRBTN, timeout);
+	if (ret < 0)
+		return ret;
+
+	data->timeout = timeout;
+	data->last_updated = jiffies;
+
+	return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt, uint timeout)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_set(data, timeout);
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static uint imanager_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	uint timeleft = 0;
+	ulong time_diff = ((jiffies - data->last_updated) / HZ);
+
+	if (data->last_updated && (data->timeout > time_diff))
+		timeleft = data->timeout - time_diff;
+
+	return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, START, WDT_EVT_NONE, 0);
+	data->last_updated = jiffies;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+	data->last_updated = 0;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt)
+{
+	struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+	struct imanager_device_data *imgr = data->imgr;
+	int ret;
+
+	mutex_lock(&imgr->lock);
+	ret = imanager_wdt_ctrl(&imgr->ec, RESET, WDT_EVT_NONE, 0);
+	data->last_updated = jiffies;
+	mutex_unlock(&imgr->lock);
+
+	return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+	.options		= WDIOF_SETTIMEOUT |
+				  WDIOF_KEEPALIVEPING |
+				  WDIOF_MAGICCLOSE,
+	.firmware_version	= 0,
+	.identity		= "imanager-wdt",
+};
+
+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,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+	struct imanager_wdt_data *data;
+	struct watchdog_device *wdt_dev;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->imgr = imgr;
+
+	wdt_dev = &data->wdt;
+	wdt_dev->info		= &imanager_wdt_info;
+	wdt_dev->ops		= &imanager_wdt_ops;
+	wdt_dev->timeout	= WDT_DEFAULT_TIMEOUT;
+	wdt_dev->min_timeout	= 1;
+	wdt_dev->max_timeout	= 0xfffe;
+
+	watchdog_set_nowayout(wdt_dev, nowayout);
+	watchdog_set_drvdata(wdt_dev, data);
+
+	ret = watchdog_register_device(wdt_dev);
+	if (ret) {
+		dev_err(dev, "Could not register watchdog device\n");
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, data);
+
+	imanager_wdt_disable_all(data);
+	imanager_wdt_set_timeout(wdt_dev, timeout);
+
+	dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+	return 0;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+	struct imanager_wdt_data *data = platform_get_drvdata(pdev);
+
+	if (!nowayout)
+		imanager_wdt_disable_all(data);
+
+	watchdog_unregister_device(&data->wdt);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static void imanager_wdt_shutdown(struct platform_device *pdev)
+{
+	struct imanager_device_data *imgr = dev_get_drvdata(pdev->dev.parent);
+
+	mutex_lock(&imgr->lock);
+	imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+	mutex_unlock(&imgr->lock);
+}
+
+static struct platform_driver imanager_wdt_driver = {
+	.driver = {
+		.name	= "imanager-wdt",
+	},
+	.probe		= imanager_wdt_probe,
+	.remove		= imanager_wdt_remove,
+	.shutdown	= imanager_wdt_shutdown,
+};
+
+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");
-- 
2.10.1

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

* Re: [PATCH v4 1/6] Add Advantech iManager MFD core driver
  2016-11-02  8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
@ 2016-11-03  8:41   ` Lee Jones
  0 siblings, 0 replies; 9+ messages in thread
From: Lee Jones @ 2016-11-03  8:41 UTC (permalink / raw)
  To: Richard Vidal-Dorsch
  Cc: linus.walleij, gnurou, jdelvare, linux, wsa, jingoohan1,
	tomi.valkeinen, wim, linux-kernel, linux-gpio, linux-hwmon,
	linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski, jo.sunga,
	weilun.huang, andrew.chou

On Wed, 02 Nov 2016, Richard Vidal-Dorsch wrote:

> This patch adds Advantech iManager Embedded Controller MFD core driver.
> This mfd core dirver provides an interface for GPIO, I2C, HWmon,
> Watchdog, and Backlight/Brightness control.
> 
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
>  drivers/mfd/Kconfig             |  18 +
>  drivers/mfd/Makefile            |   1 +
>  drivers/mfd/imanager-core.c     | 941 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/imanager-ec.h | 228 ++++++++++
>  include/linux/mfd/imanager.h    | 221 ++++++++++
>  5 files changed, 1409 insertions(+)
>  create mode 100644 drivers/mfd/imanager-core.c
>  create mode 100644 include/linux/mfd/imanager-ec.h
>  create mode 100644 include/linux/mfd/imanager.h

After a brief review, it is my belief that this looks like a platform
driver, which should live in drivers/platform/.

-- 
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] 9+ messages in thread

* Re: [PATCH v4 2/6] Add Advantech iManager GPIO driver
  2016-11-02  8:37 ` [PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch
@ 2016-11-05  8:29   ` Linus Walleij
  0 siblings, 0 replies; 9+ messages in thread
From: Linus Walleij @ 2016-11-05  8:29 UTC (permalink / raw)
  To: Richard Vidal-Dorsch
  Cc: Alexandre Courbot, Jean Delvare, Guenter Roeck, Wolfram Sang,
	Lee Jones, Jingoo Han, Tomi Valkeinen, Wim Van Sebroeck,
	linux-kernel, linux-gpio, linux-hwmon, linux-i2c, linux-fbdev,
	linux-watchdog, Krzysztof Kozlowski, jo.sunga, weilun.huang,
	andrew.chou

On Wed, Nov 2, 2016 at 9:37 AM, Richard Vidal-Dorsch
<richard.dorsch@gmail.com> wrote:

> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

> +#include <linux/device.h>
> +#include <linux/gpio.h>

#include <linux/gpio/driver.h>
should be enough.

> +#include <linux/init.h>
> +#include <linux/mfd/imanager.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +
> +#define EC_GPIOF_DIR_OUT       BIT(6)
> +#define EC_GPIOF_DIR_IN                BIT(7)
> +
> +struct imanager_gpio_data {
> +       struct imanager_device_data *imgr;
> +       struct gpio_chip chip;
> +};

Maybe some kerneldoc for this. Not necessary though since its sort of
self-explanatory.

> +static int imanager_gpio_direction_in(struct gpio_chip *chip, uint offset)
> +{
> +       struct imanager_gpio_data *data = gpiochip_get_data(chip);
> +       struct imanager_device_data *imgr = data->imgr;
> +       struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> +
> +       mutex_lock(&imgr->lock);
> +       imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
> +                       EC_GPIOF_DIR_IN);
> +       mutex_unlock(&imgr->lock);

It kind of looks like it would be smarter if the imanager_write8() was
taking and releasing the lock so you don't have to do it everywhere.

> +static int imanager_gpio_get_direction(struct gpio_chip *chip, uint offset)
> +{
> +       struct imanager_gpio_data *data = gpiochip_get_data(chip);
> +       struct imanager_device_data *imgr = data->imgr;
> +       struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> +       int ret;
> +
> +       mutex_lock(&imgr->lock);
> +       ret = imanager_read8(&imgr->ec, EC_CMD_GPIO_DIR_RD, attr->did);
> +       mutex_unlock(&imgr->lock);
> +
> +       return ret & EC_GPIOF_DIR_IN ? GPIOF_DIR_IN : GPIOF_DIR_OUT;

Don't use GPIOF* flags, those are for consumers. Just return 0/1.

> +static int imanager_gpio_get(struct gpio_chip *chip, uint offset)
> +{
> +       struct imanager_gpio_data *data = gpiochip_get_data(chip);
> +       struct imanager_device_data *imgr = data->imgr;
> +       struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> +       int ret;
> +
> +       mutex_lock(&imgr->lock);
> +       ret = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, attr->did);
> +       mutex_unlock(&imgr->lock);
> +
> +       return ret;
> +}

Can the read function return an error code? In that case it should be checked
everywhere.

Also be sure to clamp ret like this:

return !!ret;

Apart from this it looks good.

Yours,
Linus Walleij

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

end of thread, other threads:[~2016-11-05  8:29 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-11-02  8:37 [PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch
2016-11-02  8:37 ` [PATCH v4 1/6] Add Advantech iManager MFD core driver Richard Vidal-Dorsch
2016-11-03  8:41   ` Lee Jones
2016-11-02  8:37 ` [PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch
2016-11-05  8:29   ` Linus Walleij
2016-11-02  8:37 ` [PATCH v4 3/6] Add Advantech iManager HWmon driver Richard Vidal-Dorsch
2016-11-02  8:37 ` [PATCH v4 4/6] Add Advantech iManager I2C driver Richard Vidal-Dorsch
2016-11-02  8:37 ` [PATCH v4 5/6] Add Advantech iManager Backlight driver Richard Vidal-Dorsch
2016-11-02  8:37 ` [PATCH v4 6/6] Add Advantech iManager Watchdog driver Richard Vidal-Dorsch

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).