[PATCH 1/6] Add Advantech iManager MFD core driver

From: richard . dorsch
Date: Fri Jan 08 2016 - 17:31:45 EST


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

This patch adds Advantech iManager Embedded Controller MFD core driver.
This mfd core dirver provides an interface to GPIO, I2C, HWmon,
Watchdog, and Backlight/Brightness control.
---
Documentation/devicetree/bindings/mfd/imanager.txt | 33 +
MAINTAINERS | 13 +
drivers/mfd/Kconfig | 20 +
drivers/mfd/Makefile | 2 +
drivers/mfd/imanager-core.c | 288 +++++
drivers/mfd/imanager-ec.c | 1345 ++++++++++++++++++++
include/linux/mfd/imanager/core.h | 31 +
include/linux/mfd/imanager/ec.h | 210 +++
8 files changed, 1942 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/imanager.txt
create mode 100644 drivers/mfd/imanager-core.c
create mode 100644 drivers/mfd/imanager-ec.c
create mode 100644 include/linux/mfd/imanager/core.h
create mode 100644 include/linux/mfd/imanager/ec.h

diff --git a/Documentation/devicetree/bindings/mfd/imanager.txt b/Documentation/devicetree/bindings/mfd/imanager.txt
new file mode 100644
index 0000000..bf58a96
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/imanager.txt
@@ -0,0 +1,33 @@
+Note
+====
+
+This is a set of platform drivers which provide support for multiple
+embedded features such as GPIO, I2C/SMBus, Hardware Monitoring, Watchdog,
+and Backlight/Brightness control. Those features are available on Advantech
+Embedded boards such as SOM, MIO, AIMB, and PCM.
+Datasheets of each product line can be downloaded from www.advantech.com
+
+Author:
+ Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+
+
+Kernel driver imanager
+======================
+
+This driver provides a communication layer to the Advantech iManager EC
+firmware which is factory loaded onto ITE IT8518/28 chips. The type of
+communication is message based. Clients (gpio, i2c, hwmon etc. drivers)
+request data from Advantech iManager (polling, not interrupt driven). If
+a response is been received within a time frame, the data is been extracted
+from the message and then passed to the caller (clients).
+
+ Supported chips:
+ * Advantech EC based on ITE IT8518
+ Prefix: imanager
+ Addresses: 0x029e/0x029f
+ Datasheet: Available from ITE upon request
+ * Advantech EC based on ITE IT8528
+ Prefix: imanager
+ Addresses: 0x0299/0x29a
+ Datasheet: Available from ITE upon request
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 233f834..8d25fdc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5391,6 +5391,19 @@ M: Stanislaw Gruszka <stf_xl@xxxxx>
S: Maintained
F: drivers/usb/atm/ueagle-atm.c

+IMANAGER ADVANTECH EC DRIVER
+M: Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+S: Maintained
+F: Documentation/devicetree/bindings/mfd/imanager.txt
+F: Documentation/hwmon/imanager
+F: Documentation/i2c/busses/i2c-imanager
+F: drivers/mfd/imanager-core.c
+F: drivers/gpio/imanager-gpio.c
+F: drivers/hwmon/imanager-hwmon.c
+F: drivers/i2c/busses/imanager-i2c.c
+F: drivers/video/backlight/imanager-bl.c
+F: drivers/watchdog/imanager-wdt.c
+
INA209 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@xxxxxxxxxxxx>
L: lm-sensors@xxxxxxxxxxxxxx
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4d92df6..4dc7c13 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -322,6 +322,26 @@ config MFD_INTEL_QUARK_I2C_GPIO
their respective IO driver.
The GPIO exports a total amount of 8 interrupt-capable GPIOs.

+config MFD_IMANAGER
+ tristate "Advantech iManager Embedded Controller"
+ depends on PCI
+ depends on X86
+ select MFD_CORE
+ help
+ This is the core driver for Advantech iManager EC as found on some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards. The EC may provide
+ functions like GPIO, I2C interface, HW monitoring, Watchdog, and
+ backlight/brightness control.
+
+ The following Advantech boards are supported:
+ * All SOM modules newer than SOM-5788
+ * MIO-5250/5251/5270/5271 and newer
+ * PCM-9388
+ * AIMB-274/231
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager.
+
config LPC_ICH
tristate "Intel ICH LPC"
depends on PCI
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a8b76b8..c9c63d9 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -142,6 +142,8 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
+imanager-objs := imanager-core.o imanager-ec.o
+obj-$(CONFIG_MFD_IMANAGER) += imanager.o
obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o
obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
diff --git a/drivers/mfd/imanager-core.c b/drivers/mfd/imanager-core.c
new file mode 100644
index 0000000..f161b62
--- /dev/null
+++ b/drivers/mfd/imanager-core.c
@@ -0,0 +1,288 @@
+/*
+ * Advantech iManager MFD core driver
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * Author: Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/ec.h>
+
+static struct platform_device *pdev;
+
+enum imanager_cells {
+ IMANAGER_BL,
+ IMANAGER_GPIO,
+ IMANAGER_HWMON,
+ IMANAGER_I2C,
+ IMANAGER_WDT,
+};
+
+static const char * const chip_names[] = {
+ "it8516",
+ "it8518",
+ "it8528",
+ NULL
+};
+
+static struct resource imanager_ioresource = {
+ .start = IT8516_DAT_PORT,
+ .end = IT8518_DAT_PORT,
+ .flags = IORESOURCE_IO,
+};
+
+/*
+ * Devices which are part of the iManager and are available via firmware.
+ */
+static struct mfd_cell imanager_devs[] = {
+ [IMANAGER_BL] = {
+ .name = "imanager_backlight",
+ },
+ [IMANAGER_GPIO] = {
+ .name = "imanager_gpio",
+ },
+ [IMANAGER_HWMON] = {
+ .name = "imanager_hwmon",
+ },
+ [IMANAGER_I2C] = {
+ .name = "imanager_i2c",
+ },
+ [IMANAGER_WDT] = {
+ .name = "imanager_wdt",
+ },
+};
+
+const char *project_type_to_str(int type)
+{
+ const char *version_type;
+
+ switch (type) {
+ case 'V':
+ version_type = "Release";
+ break;
+ case 'X':
+ version_type = "Engineering Sample";
+ break;
+ case 'A' ... 'U':
+ version_type = "Custom";
+ break;
+ default:
+ version_type = "Unknown";
+ break;
+ }
+
+ return version_type;
+}
+
+static ssize_t imanager_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", info->pcb_name);
+}
+
+static ssize_t imanager_kversion_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n",
+ info->version.kernel_major,
+ info->version.kernel_minor);
+}
+
+static ssize_t imanager_fwversion_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n",
+ info->version.firmware_major,
+ info->version.firmware_minor);
+}
+
+static ssize_t imanager_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ const struct ec_info *info = &pdata->dev->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ project_type_to_str(info->version.type));
+}
+
+static ssize_t imanager_chip_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", pdata->chip_name);
+}
+
+static DEVICE_ATTR(imanager_name, S_IRUGO, imanager_name_show, NULL);
+static DEVICE_ATTR(imanager_kversion, S_IRUGO, imanager_kversion_show, NULL);
+static DEVICE_ATTR(imanager_fwversion, S_IRUGO, imanager_fwversion_show, NULL);
+static DEVICE_ATTR(imanager_type, S_IRUGO, imanager_type_show, NULL);
+static DEVICE_ATTR(imanager_chip_name, S_IRUGO, imanager_chip_name_show, NULL);
+
+static struct attribute *imanager_core_attributes[] = {
+ &dev_attr_imanager_name.attr,
+ &dev_attr_imanager_kversion.attr,
+ &dev_attr_imanager_fwversion.attr,
+ &dev_attr_imanager_type.attr,
+ &dev_attr_imanager_chip_name.attr,
+ NULL
+};
+
+static const struct attribute_group imanager_core_attr_group = {
+ .attrs = imanager_core_attributes,
+};
+
+static int imanager_platform_create(void)
+{
+ struct device *dev;
+ struct imanager_platform_data platdata;
+ int err;
+
+ pdev = platform_device_alloc("imanager-core", -1);
+ if (!pdev)
+ return -ENOMEM;
+
+ dev = &pdev->dev;
+
+ err = platform_device_add_data(pdev, &platdata, sizeof(platdata));
+ if (err)
+ goto exit_device_put;
+
+ err = platform_device_add_resources(pdev, &imanager_ioresource, 1);
+ if (err)
+ goto exit_device_put;
+
+ err = platform_device_add(pdev);
+ if (err)
+ goto exit_device_put;
+
+ err = mfd_add_devices(dev, pdev->id, imanager_devs,
+ ARRAY_SIZE(imanager_devs), NULL, -1, NULL);
+ if (err)
+ goto exit_device_unregister;
+
+ return 0;
+
+exit_device_unregister:
+ platform_device_unregister(pdev);
+exit_device_put:
+ platform_device_put(pdev);
+
+ return err;
+}
+
+static int imanager_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_platform_data *pdata = dev_get_platdata(dev);
+ struct imanager_device_data *imanager;
+ int ret;
+
+ if (!pdev)
+ return -EINVAL;
+
+ imanager = devm_kzalloc(dev, sizeof(*imanager), GFP_KERNEL);
+ if (!imanager)
+ return -ENOMEM;
+
+ imanager->dev = dev;
+ mutex_init(&imanager->lock);
+
+ platform_set_drvdata(pdev, imanager);
+
+ pdata->dev = imanager_get_ec_device();
+ pdata->chip_name = chip_names[pdata->dev->id];
+
+ dev_info(dev, "Found Advantech iManager %s - %s %d.%d/%d.%d (%s)\n",
+ pdata->chip_name,
+ pdata->dev->info.pcb_name,
+ pdata->dev->info.version.kernel_major,
+ pdata->dev->info.version.kernel_minor,
+ pdata->dev->info.version.firmware_major,
+ pdata->dev->info.version.firmware_minor,
+ project_type_to_str(pdata->dev->info.version.type));
+
+ ret = sysfs_create_group(&dev->kobj, &imanager_core_attr_group);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imanager_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ sysfs_remove_group(&dev->kobj, &imanager_core_attr_group);
+
+ mfd_remove_devices(dev);
+
+ return 0;
+}
+
+static struct platform_driver imanager_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imanager-core",
+ },
+ .probe = imanager_probe,
+ .remove = imanager_remove,
+};
+
+static int __init imanager_init(void)
+{
+ int ret;
+
+ ret = imanager_ec_probe();
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_platform_create();
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&imanager_driver);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void __exit imanager_exit(void)
+{
+ if (pdev)
+ platform_device_unregister(pdev);
+
+ platform_driver_unregister(&imanager_driver);
+}
+
+module_init(imanager_init);
+module_exit(imanager_exit);
+
+MODULE_DESCRIPTION("Advantech iManager Core Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-core");
diff --git a/drivers/mfd/imanager-ec.c b/drivers/mfd/imanager-ec.c
new file mode 100644
index 0000000..a885cdf
--- /dev/null
+++ b/drivers/mfd/imanager-ec.c
@@ -0,0 +1,1345 @@
+/*
+ * Advantech iManager Core - Firmware Interface
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * Author: Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/module.h>
+#include <linux/swab.h>
+#include <linux/mfd/imanager/ec.h>
+
+/**
+ * This is the delay time between two EC transactions.
+ * Values lower than 200us are not encouraged and may
+ * cause I/O errors
+ */
+#define EC_MICRO_DELAY 200
+#define EC_MAX_RETRY 1000
+
+#define EC_MSG_OFFSET_CMD 0UL
+#define EC_MSG_OFFSET_STATUS 1UL
+#define EC_MSG_OFFSET_PARAM 2UL
+#define EC_MSG_OFFSET_DATA(N) (3UL + N)
+#define EC_MSG_OFFSET_PAYLOAD(N) (7UL + N)
+
+/* The Device ID registers - 16 bit */
+#define DEVID_REG_MSB 0x20
+#define DEVID_REG_LSB 0x21
+
+/*
+ * IT8528 based firmware require a read/write command offset.
+ */
+#define EC_CMD_OFFSET_READ 0xA0UL
+#define EC_CMD_OFFSET_WRITE 0x50UL
+
+#define EC_STATUS_SUCCESS BIT(0)
+#define EC_STATUS_CMD_COMPLETE BIT(7)
+
+#define PCB_NAME_MAX_SIZE 8UL
+#define EC_I2C_BLOCK_SIZE 32
+#define EC_MAX_DID 32UL
+
+#define DID_LABEL_SIZE 24UL
+#define DID_DESC_SIZE 32UL
+
+#define EC_KERNEL_MINOR(x) (LOBYTE16(x))
+#define EC_KERNEL_MAJOR(x) ({ \
+ typeof(x) __x = (HIBYTE16(x)); \
+ ((__x >> 4) * 10 + (__x & 0x000f)); })
+#define EC_FIRMWARE_MINOR(x) (LOBYTE16(x))
+#define EC_FIRMWARE_MAJOR(x) EC_KERNEL_MAJOR(x)
+#define EC_PROJECT_CODE(x) ((char)(LOBYTE16(x)))
+
+enum ec_device_type {
+ ADC = 1,
+ DAC,
+ GPIO,
+ IRQ,
+ PWM,
+ SMB,
+ TACH
+};
+
+enum ec_device_id {
+ /* GPIO */
+ ALTGPIO0 = 0x10,
+ ALTGPIO1,
+ ALTGPIO2,
+ ALTGPIO3,
+ ALTGPIO4,
+ ALTGPIO5,
+ ALTGPIO6,
+ ALTGPIO7,
+ /* Button (GPIO) */
+ BUTTON0,
+ BUTTON1,
+ BUTTON2,
+ BUTTON3,
+ BUTTON4,
+ BUTTON5,
+ BUTTON6,
+ BUTTON7,
+ /* FAN */
+ CPUFAN_2P,
+ CPUFAN_4P,
+ SYSFAN1_2P,
+ SYSFAN1_4P,
+ SYSFAN2_2P,
+ SYSFAN2_4P,
+ /* Brightness Control */
+ BRIGHTNESS,
+ /* System Speaker */
+ PCBEEP,
+ /* SMBus */
+ SMBOEM0,
+ SMBOEM1,
+ SMBOEM2,
+ SMBEEPROM,
+ SMBTHERMAL0,
+ SMBTHERMAL1,
+ SMBSECEEP,
+ I2COEM,
+ /* Speaker */
+ SPEAKER = 0x30,
+ /* SMBus */
+ SMBEEP2K = 0x38,
+ OEMEEP,
+ OEMEEP2K,
+ PECI,
+ SMBOEM3,
+ SMLINK,
+ SMBSLV,
+ /* LED */
+ POWERLED = 0x40,
+ BATLEDG,
+ OEMLED0,
+ OEMLED1,
+ OEMLED2,
+ BATLEDR,
+ /* Smart Battery */
+ SMARTBAT1 = 0x48,
+ SMARTBAT2,
+ /* ADC */
+ CMOSBAT = 0x50,
+ CMOSBATx2,
+ CMOSBATx10,
+ LIBAT,
+ LIBATx2,
+ LIBATx10,
+ ADC5VS0,
+ ADC5VS0x2,
+ ADC5VS0x10,
+ ADC5VS5,
+ ADC5VS5x2,
+ ADC5VS5x10,
+ ADC33VS0,
+ ADC33VS0x2,
+ ADC33VS0x10,
+ ADC33VS5,
+ ADC33VS5x2, /* 0x60 */
+ ADC33VS5x10,
+ ADC12VS0,
+ ADC12VS0x2,
+ ADC12VS0x10,
+ VCOREA,
+ VCOREAx2,
+ VCOREAx10,
+ VCOREB,
+ VCOREBx2,
+ VCOREBx10,
+ ADCDC,
+ ADCDCx2,
+ ADCDCx10,
+ VSTBY,
+ VSTBYx2,
+ VSTBYx10, /* 0x70 */
+ VAUX,
+ VAUXx2,
+ VAUXx10,
+ CURRENT,
+ /* Watchdog */
+ WDIRQ = 0x78,
+ WDNMI,
+ /* FAN Tacho */
+ TACHO0 = 0x80,
+ TACHO1,
+ TACHO2,
+ /* Brightness/Backlight Control */
+ BRIGHTNESS2 = 0x88,
+ BACKLIGHT1,
+ BACKLIGHT2
+};
+
+enum ec_dynamic_table_type {
+ EC_DYN_DID,
+ EC_DYN_HWP,
+ EC_DYN_POL
+};
+
+struct ec_devtbl {
+ int id;
+ int type;
+ int scale;
+ const char label[DID_LABEL_SIZE];
+ const char description[DID_DESC_SIZE];
+};
+
+struct ec_dyn_devtbl {
+ int did; /* Device ID */
+ int hwp; /* Hardware Pin */
+ int pol; /* Polarity */
+ const struct ec_devtbl *devtbl; /* Device table Entry */
+};
+
+struct imanager_data {
+ int (*read)(int cmd);
+ int (*write)(int cmd, int value);
+
+ struct ec_dyn_devtbl dyn[EC_MAX_DID];
+
+ struct imanager_ec_device dev;
+ struct imanager_hwmon_device sensors;
+ struct imanager_gpio_device gpio;
+ struct imanager_i2c_device i2c;
+ struct imanager_watchdog_device wdt;
+ struct imanager_backlight_device blc;
+};
+
+struct ec_version_raw {
+ u16 kernel,
+ chipid,
+ project_code,
+ firmware;
+};
+
+enum ec_ram_type {
+ EC_RAM_ACPI = 1,
+ EC_RAM_HW,
+ EC_RAM_EXT
+};
+
+static struct imanager_data ec;
+
+static const struct ec_devtbl devtbl[] = {
+ { ALTGPIO0, GPIO, -1, "gpio0" },
+ { ALTGPIO1, GPIO, -1, "gpio1" },
+ { ALTGPIO2, GPIO, -1, "gpio2" },
+ { ALTGPIO3, GPIO, -1, "gpio3" },
+ { ALTGPIO4, GPIO, -1, "gpio4" },
+ { ALTGPIO5, GPIO, -1, "gpio5" },
+ { ALTGPIO6, GPIO, -1, "gpio6" },
+ { ALTGPIO7, GPIO, -1, "gpio7" },
+ { BUTTON0, GPIO, -1, "button0" },
+ { BUTTON1, GPIO, -1, "button1" },
+ { BUTTON2, GPIO, -1, "button2" },
+ { BUTTON3, GPIO, -1, "button3" },
+ { BUTTON4, GPIO, -1, "button4" },
+ { BUTTON5, GPIO, -1, "button4" },
+ { BUTTON6, GPIO, -1, "button4" },
+ { BUTTON7, GPIO, -1, "button4" },
+ { CPUFAN_2P, PWM, 2, "FAN CPU" },
+ { CPUFAN_4P, PWM, 4, "FAN CPU" },
+ { SYSFAN1_2P, PWM, 2, "FAN SYS1" },
+ { SYSFAN1_4P, PWM, 4, "FAN SYS1" },
+ { SYSFAN2_2P, PWM, 2, "FAN SYS2" },
+ { SYSFAN2_4P, PWM, 4, "FAN SYS2" },
+ { BRIGHTNESS, PWM, -1, "Brightness1" },
+ { PCBEEP, PWM, -1, "Beep" },
+ { SMBOEM0, SMB, -1, "SMB1" },
+ { SMBOEM1, SMB, -1, "SMB2" },
+ { SMBOEM2, SMB, -1, "SMB3" },
+ { SMBEEPROM, SMB, -1, "SMBEEP" },
+ { SMBTHERMAL0, SMB, -1, "SMBTHERM0" },
+ { SMBTHERMAL1, SMB, -1, "SMBTHERM1" },
+ { SMBSECEEP, SMB, -1, "SMBSECEEP" },
+ { I2COEM, SMB, -1, "I2COEM" },
+ { SPEAKER, DAC, -1, "Speaker" },
+ { SMBEEP2K, SMB, -1, "SMBEEP2K" },
+ { OEMEEP, SMB, -1, "OEMEEP" },
+ { OEMEEP2K, SMB, -1, "OEMEEP2K" },
+ { PECI, SMB, -1, "SMB_PECI" },
+ { SMBOEM3, SMB, -1, "SMBOEM3" },
+ { SMLINK, SMB, -1, "SMLINK" },
+ { SMBSLV, SMB, -1, "SMBSLV" },
+ { POWERLED, GPIO, -1, "Power LED" },
+ { BATLEDG, GPIO, -1, "BATLEDG" },
+ { OEMLED0, GPIO, -1, "OEMLED0" },
+ { OEMLED1, GPIO, -1, "OEMLED1" },
+ { OEMLED2, GPIO, -1, "OEMLED2" },
+ { BATLEDR, GPIO, -1, "OEMLEDR" },
+ { SMARTBAT1, SMB, -1, "SmartBat1" },
+ { SMARTBAT2, SMB, -1, "SmartBat2" },
+ { CMOSBAT, ADC, 1, "VBat" },
+ { CMOSBATx2, ADC, 2, "VBat" },
+ { CMOSBATx10, ADC, 10, "VBat" },
+ { LIBAT, ADC, 1, "VBat2" },
+ { LIBATx2, ADC, 2, "VBat2" },
+ { LIBATx10, ADC, 10, "VBat2" },
+ { ADC5VS0, ADC, 1, "+5V" },
+ { ADC5VS0x2, ADC, 2, "+5V" },
+ { ADC5VS0x10, ADC, 10, "+5V" },
+ { ADC5VS5, ADC, 1, "+5V" },
+ { ADC5VS5x2, ADC, 2, "+5V" },
+ { ADC5VS5x10, ADC, 10, "+5V" },
+ { ADC33VS0, ADC, 1, "+3.3V" },
+ { ADC33VS0x2, ADC, 2, "+3.3V" },
+ { ADC33VS0x10, ADC, 10, "+3.3V" },
+ { ADC33VS5, ADC, 1, "+3.3V" },
+ { ADC33VS5x2, ADC, 2, "+3.3V" },
+ { ADC33VS5x10, ADC, 10, "+3.3V" },
+ { ADC12VS0, ADC, 1, "+12V" },
+ { ADC12VS0x2, ADC, 2, "+12V" },
+ { ADC12VS0x10, ADC, 10, "+12V" },
+ { VCOREA, ADC, 1, "VCore" },
+ { VCOREAx2, ADC, 2, "VCore" },
+ { VCOREAx10, ADC, 10, "VCore" },
+ { VCOREB, ADC, 1, "VCore2" },
+ { VCOREBx2, ADC, 2, "VCore2" },
+ { VCOREBx10, ADC, 10, "VCore2" },
+ { ADCDC, ADC, 1, "ADCDC" },
+ { ADCDCx2, ADC, 2, "ADCDCx2" },
+ { ADCDCx10, ADC, 10, "ADCDCx10" },
+ { VSTBY, ADC, 1, "Vsb" },
+ { VSTBYx2, ADC, 2, "Vsb" },
+ { VSTBYx10, ADC, 10, "Vsb" },
+ { VAUX, ADC, 1, "VAUX" },
+ { VAUXx2, ADC, 2, "VAUX" },
+ { VAUXx10, ADC, 10, "VAUX" },
+ { CURRENT, ADC, 1, "Imon" },
+ { WDIRQ, IRQ, -1, "WDIRQ" },
+ { WDNMI, GPIO, -1, "WDNMI" },
+ { TACHO0, TACH, -1, "Tacho1" },
+ { TACHO1, TACH, -1, "Tacho2" },
+ { TACHO2, TACH, -1, "Tacho3" },
+ { BRIGHTNESS2, PWM, -1, "Brightness2" },
+ { BACKLIGHT1, GPIO, -1, "Backlight1" },
+ { BACKLIGHT2, GPIO, -1, "Backlight2" },
+ { 0, 0, 0, "" }
+};
+
+/**
+ * EC I/O
+ */
+
+static inline void imanager_delay(void)
+{
+ udelay(EC_MICRO_DELAY);
+}
+
+static int wait_ibf_cleared(void)
+{
+ int i = 0;
+
+ do {
+ if (!(inb(IT8516_CMD_PORT) & BIT(1)))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+static int wait_obf_set(void)
+{
+ int i = 0;
+
+ do {
+ if (inb(IT8516_CMD_PORT) & BIT(0))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+static inline int ec_inb(int addr, int reg)
+{
+ outb(reg, addr);
+ return inb(addr + 1);
+}
+
+static inline void ec_outb(int addr, int reg, int val)
+{
+ outb(reg, addr);
+ outb(val, addr + 1);
+}
+
+static inline int ec_io28_inb(int addr, int reg)
+{
+ int ret;
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ /* clear data to prevent lock */
+ inb(addr - 1);
+
+ outb(reg, addr);
+
+ ret = wait_obf_set();
+ if (ret)
+ return ret;
+
+ return inb(addr - 1);
+}
+
+static inline int ec_io28_outb(int addr, int reg, int val)
+{
+ int ret;
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ outb(reg, addr);
+
+ ret = wait_ibf_cleared();
+ if (ret)
+ return ret;
+
+ outb(val, addr - 1);
+
+ return 0;
+}
+
+static int ec_io18_read(int cmd)
+{
+ return ec_inb(IT8518_CMD_PORT, cmd);
+}
+
+static int ec_io18_write(int cmd, int value)
+{
+ ec_outb(IT8518_CMD_PORT, cmd, value);
+
+ return 0;
+}
+
+static int ec_io28_read(int cmd)
+{
+ return ec_io28_inb(IT8516_CMD_PORT, cmd + EC_CMD_OFFSET_READ);
+}
+
+static int ec_io28_write(int cmd, int value)
+{
+ return ec_io28_outb(IT8516_CMD_PORT, cmd + EC_CMD_OFFSET_WRITE, value);
+}
+
+/* Prevent FW lock */
+static void ec_clear_ports(void)
+{
+ inb(IT8516_DAT_PORT);
+ inb(IT8518_DAT_PORT);
+}
+
+static inline u16 ec_read_chipid(u16 addr)
+{
+ return (ec_inb(addr, DEVID_REG_MSB) << 8 |
+ ec_inb(addr, DEVID_REG_LSB));
+}
+
+static int ec_wait_cmd_clear(void)
+{
+ int i = 0;
+
+ do {
+ if (!ec.read(0))
+ return 0;
+ imanager_delay();
+ } while (i++ < EC_MAX_RETRY);
+
+ pr_err("No respons from EC (timeout)\n");
+
+ return -ETIME;
+}
+
+static int ec_read_ram(u8 bank, u8 offset, u8 len, u8 *buf, u8 bufsz)
+{
+ int i;
+ int ret;
+
+ if (WARN_ON(!buf))
+ return -EINVAL;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, bank);
+ ec.write(EC_MSG_OFFSET_DATA(0), offset);
+ ec.write(EC_MSG_OFFSET_DATA(0x2C), len);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (i = 0; (i < len) && (len < EC_MSG_SIZE) && (len <= bufsz); i++)
+ buf[i] = ec.read(EC_MSG_OFFSET_DATA(i + 1));
+
+ return 0;
+}
+
+static int ec_write_ram(u8 bank, u8 offset, u8 len, u8 *buf)
+{
+ int i;
+ int ret;
+
+ if (WARN_ON(!buf))
+ return -EINVAL;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, bank);
+ ec.write(EC_MSG_OFFSET_DATA(0), offset);
+ ec.write(EC_MSG_OFFSET_DATA(0x2C), len);
+
+ for (i = 0; (i < len) && (len < EC_MSG_SIZE); i++)
+ ec.write(EC_MSG_OFFSET_DATA(i + 1), buf[i]);
+
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_WR);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ return 0;
+}
+
+static int ec_read_dynamic_devtbl(struct imanager_data *ec)
+{
+ u32 i, j;
+ int ret;
+ struct ec_message did = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_message hwp = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_message pol = {
+ .rlen = EC_MAX_DID,
+ .wlen = 0,
+ };
+ struct ec_dyn_devtbl *dyn;
+
+ memset(ec->dyn, 0, sizeof(ec->dyn));
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_DID, &did);
+ if (ret)
+ return -EIO;
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_HWP, &hwp);
+ if (ret)
+ return -EIO;
+
+ ret = imanager_msg_read(EC_CMD_DYN_TBL_RD, EC_DYN_POL, &pol);
+ if (ret)
+ return -EIO;
+
+ for (i = 0; (i < EC_MAX_DID) && did.u.data[i]; i++) {
+ dyn = &ec->dyn[i];
+ for (j = 0; j < ARRAY_SIZE(devtbl); j++) {
+ if (devtbl[j].id == did.u.data[i]) {
+ dyn->did = did.u.data[i];
+ dyn->hwp = hwp.u.data[i];
+ dyn->pol = pol.u.data[i];
+ dyn->devtbl = &devtbl[j];
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_read_buffer(u8 *data, int rlen)
+{
+ int ret, i, j;
+ int pages = rlen % EC_I2C_BLOCK_SIZE;
+ int rem = rlen / EC_I2C_BLOCK_SIZE;
+
+ /* pre-condition: rlen <= 256 */
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ for (i = 0; i < pages; i++) {
+ ec.write(EC_MSG_OFFSET_PARAM, i);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_BUF_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (j = 0; j < EC_I2C_BLOCK_SIZE; j++)
+ data[i * EC_I2C_BLOCK_SIZE + j] =
+ ec.read(EC_MSG_OFFSET_DATA(j));
+ }
+
+ if (rem) {
+ ec.write(EC_MSG_OFFSET_PARAM, pages);
+ ec.write(EC_MSG_OFFSET_CMD, EC_CMD_BUF_RD);
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_STATUS_SUCCESS)
+ return -EIO;
+
+ for (j = 0; j < rem; j++)
+ data[pages * EC_I2C_BLOCK_SIZE + j] =
+ ec.read(EC_MSG_OFFSET_DATA(j));
+ }
+
+ return 0;
+}
+
+static int
+imanager_msg_trans(u8 cmd, u8 param, struct ec_message *msg, bool payload)
+{
+ int ret, i, len;
+ u32 offset;
+
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ ec.write(EC_MSG_OFFSET_PARAM, param);
+
+ if (msg && msg->wlen) {
+ if (!msg->data) {
+ for (i = 0; i < msg->wlen; i++)
+ ec.write(EC_MSG_OFFSET_DATA(i),
+ msg->u.data[i]);
+ } else {
+ for (i = 0; i < msg->wlen; i++)
+ ec.write(EC_MSG_OFFSET_DATA(i), msg->data[i]);
+ ec.write(EC_MSG_OFFSET_DATA(0x2c), msg->wlen);
+ }
+ }
+
+ ec.write(EC_MSG_OFFSET_CMD, cmd);
+ ret = ec_wait_cmd_clear();
+ if (ret)
+ return ret;
+
+ /* GPIO and I2C have different success return values */
+ ret = ec.read(EC_MSG_OFFSET_STATUS);
+ if ((ret != EC_STATUS_SUCCESS) && !(ret & EC_STATUS_CMD_COMPLETE))
+ return -EIO;
+ /*
+ * EC I2C may return an error code which we need to hand-off
+ * to the caller
+ */
+ else if (ret & 0x07e)
+ return ret;
+
+ if (msg && msg->data) {
+ ret = ec_read_buffer(msg->data, msg->rlen);
+ if (ret < 0)
+ return ret;
+ } else if (msg && msg->rlen) {
+ if (msg->rlen == 0xff)
+ /* Use alternate message body for hwmon */
+ len = ec.read(EC_MSG_OFFSET_DATA(0x2C));
+ else
+ len = (msg->rlen > EC_MSG_SIZE ? EC_MSG_SIZE :
+ msg->rlen);
+ offset = payload ? EC_MSG_OFFSET_PAYLOAD(0) :
+ EC_MSG_OFFSET_DATA(0);
+ for (i = 0; i < len; i++)
+ msg->u.data[i] = ec.read(offset + i);
+ }
+
+ return 0;
+}
+
+int imanager_msg_read(u8 cmd, u8 param, struct ec_message *msg)
+{
+ return imanager_msg_trans(cmd, param, msg, false);
+}
+EXPORT_SYMBOL_GPL(imanager_msg_read);
+
+int imanager_msg_write(u8 cmd, u8 param, struct ec_message *msg)
+{
+ return imanager_msg_trans(cmd, param, msg, true);
+}
+EXPORT_SYMBOL_GPL(imanager_msg_write);
+
+int imanager_read_byte(u8 cmd, u8 param)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 1,
+ .wlen = 0,
+ };
+
+ ret = imanager_msg_read(cmd, param, &msg);
+ if (ret)
+ return ret;
+
+ return msg.u.data[0];
+}
+EXPORT_SYMBOL_GPL(imanager_read_byte);
+
+int imanager_read_word(u8 cmd, u8 param)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = 2,
+ .wlen = 0,
+ };
+
+ ret = imanager_msg_read(cmd, param, &msg);
+ if (ret)
+ return ret;
+
+ return (msg.u.data[0] << 8 | msg.u.data[1]);
+}
+EXPORT_SYMBOL_GPL(imanager_read_word);
+
+int imanager_write_byte(u8 cmd, u8 param, u8 byte)
+{
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = 1,
+ .u = {
+ .data = { byte, 0 },
+ },
+ };
+
+ return imanager_msg_write(cmd, param, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write_byte);
+
+int imanager_write_word(u8 cmd, u8 param, u16 word)
+{
+ struct ec_message msg = {
+ .rlen = 0,
+ .wlen = 2,
+ .u = {
+ .data = { HIBYTE16(word), LOBYTE16(word), 0 },
+ },
+ };
+
+ return imanager_msg_write(cmd, param, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write_word);
+
+static int ec_hwram_read_byte(u8 offset)
+{
+ int ret;
+ u8 val;
+
+ ret = ec_read_ram(EC_RAM_HW, offset, sizeof(val), &val, sizeof(val));
+ if (ret < 0) {
+ pr_err("Failed to read from HWRAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return val;
+}
+
+int imanager_acpiram_read_byte(u8 offset)
+{
+ int ret;
+ u8 value;
+
+ ret = ec_read_ram(EC_RAM_ACPI, offset, sizeof(value), (u8 *)&value,
+ sizeof(value));
+ if (ret < 0) {
+ pr_err("Failed to read from ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return value;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_read_byte);
+
+int imanager_acpiram_write_byte(u8 offset, u8 value)
+{
+ int ret;
+
+ ret = ec_write_ram(EC_RAM_ACPI, offset, sizeof(value), &value);
+ if (ret) {
+ pr_err("Failed to write to ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_write_byte);
+
+int imanager_acpiram_read_block(u8 offset, u8 *buf, u8 len)
+{
+ int ret;
+
+ ret = ec_read_ram(EC_RAM_ACPI, offset, len, buf, len);
+ if (ret < 0) {
+ pr_err("Failed to read from ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_read_block);
+
+int imanager_acpiram_write_block(u8 offset, u8 *buf, u8 len)
+{
+ int ret;
+
+ ret = ec_write_ram(EC_RAM_ACPI, offset, len, buf);
+ if (ret) {
+ pr_err("Failed to write to ACPI RAM @ 0x%02X\n", offset);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_acpiram_write_block);
+
+int imanager_wait_proc_complete(u8 offset, int cond)
+{
+ int ret, i;
+
+ for (i = 0; i < EC_MAX_RETRY; i++) {
+ ret = ec_hwram_read_byte(offset);
+ if (ret < 0)
+ return ret;
+
+ if (ret == cond)
+ return 0;
+
+ imanager_delay();
+ }
+
+ return -EIO;
+}
+EXPORT_SYMBOL_GPL(imanager_wait_proc_complete);
+
+static inline void ec_get_dev_attr(struct ec_dev_attr *attr,
+ const struct ec_dyn_devtbl *tbl)
+{
+ attr->did = tbl->did;
+ attr->hwp = tbl->hwp;
+ attr->pol = tbl->pol;
+ attr->scale = tbl->devtbl->scale;
+ attr->label = tbl->devtbl->label;
+}
+
+static int ec_get_dev_gpio(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_gpio_device *gpio = &ec->gpio;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == GPIO)) {
+ switch (dyn->did) {
+ case ALTGPIO0:
+ ec_get_dev_attr(&gpio->attr[0], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO1:
+ ec_get_dev_attr(&gpio->attr[1], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO2:
+ ec_get_dev_attr(&gpio->attr[2], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO3:
+ ec_get_dev_attr(&gpio->attr[3], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO4:
+ ec_get_dev_attr(&gpio->attr[4], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO5:
+ ec_get_dev_attr(&gpio->attr[5], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO6:
+ ec_get_dev_attr(&gpio->attr[6], dyn);
+ gpio->num++;
+ break;
+ case ALTGPIO7:
+ ec_get_dev_attr(&gpio->attr[7], dyn);
+ gpio->num++;
+ break;
+ case BUTTON0:
+ case BUTTON1:
+ case BUTTON2:
+ case BUTTON3:
+ case BUTTON4:
+ case BUTTON5:
+ case BUTTON6:
+ case BUTTON7:
+ case POWERLED:
+ case BATLEDG:
+ case OEMLED0:
+ case OEMLED1:
+ case OEMLED2:
+ case BATLEDR:
+ case WDNMI:
+ case BACKLIGHT1:
+ case BACKLIGHT2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ gpio->info = &ec->dev.info;
+
+ return 0;
+}
+
+static int ec_get_dev_adc(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct dev_adc *adc = &ec->sensors.adc;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == ADC)) {
+ switch (dyn->did) {
+ case ADC12VS0:
+ case ADC12VS0x2:
+ case ADC12VS0x10:
+ ec_get_dev_attr(&adc->attr[0], dyn);
+ adc->num++;
+ break;
+ case ADC5VS5:
+ case ADC5VS5x2:
+ case ADC5VS5x10:
+ ec_get_dev_attr(&adc->attr[1], dyn);
+ adc->num++;
+ break;
+ case CMOSBAT:
+ case CMOSBATx2:
+ case CMOSBATx10:
+ ec_get_dev_attr(&adc->attr[2], dyn);
+ adc->num++;
+ break;
+ case VCOREA:
+ case ADC5VS0:
+ case ADC5VS0x2:
+ case ADC5VS0x10:
+ ec_get_dev_attr(&adc->attr[3], dyn);
+ adc->num++;
+ break;
+ case CURRENT:
+ case ADC33VS0:
+ case ADC33VS0x2:
+ case ADC33VS0x10:
+ ec_get_dev_attr(&adc->attr[4], dyn);
+ adc->num++;
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_get_dev_fan(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct dev_fan *fan = &ec->sensors.fan;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && ((dyn->devtbl->type == TACH) ||
+ (dyn->devtbl->type == PWM))) {
+ switch (dyn->did) {
+ case CPUFAN_2P:
+ case CPUFAN_4P:
+ ec_get_dev_attr(&fan->attr[0], dyn);
+ fan->num++;
+ fan->active |= 1 << 0;
+ break;
+ case SYSFAN1_2P:
+ case SYSFAN1_4P:
+ ec_get_dev_attr(&fan->attr[1], dyn);
+ fan->num++;
+ fan->active |= 1 << 1;
+ break;
+ case SYSFAN2_2P:
+ case SYSFAN2_4P:
+ ec_get_dev_attr(&fan->attr[2], dyn);
+ fan->num++;
+ fan->active |= 1 << 2;
+ break;
+ case TACHO0:
+ case TACHO1:
+ case TACHO2:
+ case BRIGHTNESS:
+ case BRIGHTNESS2:
+ case PCBEEP:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int ec_get_dev_hwmon(struct imanager_data *ec)
+{
+ int ret;
+
+ ret = ec_get_dev_adc(ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_fan(ec);
+ if (ret < 0)
+ return ret;
+
+ ec->sensors.ecdev = &ec->dev;
+
+ return 0;
+}
+
+static int ec_get_dev_i2c(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_i2c_device *i2c = &ec->i2c;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == SMB)) {
+ switch (dyn->did) {
+ case SMBEEPROM:
+ ec_get_dev_attr(&i2c->attr[0], dyn);
+ i2c->eeprom = &i2c->attr[0];
+ i2c->num++;
+ break;
+ case I2COEM:
+ ec_get_dev_attr(&i2c->attr[1], dyn);
+ i2c->i2coem = &i2c->attr[1];
+ i2c->num++;
+ break;
+ case SMBOEM0:
+ case SMBOEM1:
+ case SMBOEM2:
+ case SMBTHERMAL0:
+ case SMBTHERMAL1:
+ case SMBSECEEP:
+ case SMBEEP2K:
+ case OEMEEP:
+ case OEMEEP2K:
+ case PECI:
+ case SMBOEM3:
+ case SMLINK:
+ case SMBSLV:
+ case SMARTBAT1:
+ case SMARTBAT2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ i2c->ecdev = &ec->dev;
+
+ return 0;
+}
+
+static int ec_get_dev_blc(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_backlight_device *blc = &ec->blc;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == PWM)) {
+ switch (dyn->did) {
+ case BRIGHTNESS:
+ ec_get_dev_attr(&blc->attr[0], dyn);
+ blc->brightness[0] = EC_ACPIRAM_BRIGHTNESS1;
+ blc->num++;
+ break;
+ case BRIGHTNESS2:
+ ec_get_dev_attr(&blc->attr[1], dyn);
+ blc->brightness[1] = EC_ACPIRAM_BRIGHTNESS2;
+ blc->num++;
+ break;
+ case CPUFAN_2P:
+ case CPUFAN_4P:
+ case SYSFAN1_2P:
+ case SYSFAN1_4P:
+ case SYSFAN2_2P:
+ case SYSFAN2_4P:
+ case PCBEEP:
+ case TACHO0:
+ case TACHO1:
+ case TACHO2:
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ blc->info = &ec->dev.info;
+
+ return 0;
+}
+
+static int ec_get_dev_wdt(struct imanager_data *ec)
+{
+ size_t i;
+ struct ec_dyn_devtbl *dyn;
+ struct imanager_watchdog_device *wdt = &ec->wdt;
+
+ for (i = 0; i < ARRAY_SIZE(ec->dyn); i++) {
+ dyn = &ec->dyn[i];
+ if (dyn->did && (dyn->devtbl->type == IRQ)) {
+ switch (dyn->did) {
+ case WDIRQ:
+ ec_get_dev_attr(&wdt->attr[0], dyn);
+ wdt->irq = &wdt->attr[0];
+ wdt->num++;
+ break;
+ case WDNMI:
+ ec_get_dev_attr(&wdt->attr[1], dyn);
+ wdt->nmi = &wdt->attr[1];
+ wdt->num++;
+ break;
+ default:
+ pr_err("DID 0x%02X not handled\n", dyn->did);
+ return -EINVAL;
+ }
+ }
+ }
+
+ wdt->info = &ec->dev.info;
+
+ return 0;
+}
+
+const struct imanager_ec_device *imanager_get_ec_device(void)
+{
+ return &ec.dev;
+};
+EXPORT_SYMBOL_GPL(imanager_get_ec_device);
+
+const struct imanager_hwmon_device *imanager_get_hwmon_device(void)
+{
+ return &ec.sensors;
+}
+EXPORT_SYMBOL_GPL(imanager_get_hwmon_device);
+
+const struct imanager_gpio_device *imanager_get_gpio_device(void)
+{
+ return &ec.gpio;
+}
+EXPORT_SYMBOL_GPL(imanager_get_gpio_device);
+
+const struct imanager_i2c_device *imanager_get_i2c_device(void)
+{
+ return &ec.i2c;
+}
+EXPORT_SYMBOL_GPL(imanager_get_i2c_device);
+
+const struct imanager_backlight_device *imanager_get_backlight_device(void)
+{
+ return &ec.blc;
+}
+EXPORT_SYMBOL_GPL(imanager_get_backlight_device);
+
+const struct imanager_watchdog_device *imanager_get_watchdog_device(void)
+{
+ return &ec.wdt;
+}
+EXPORT_SYMBOL_GPL(imanager_get_watchdog_device);
+
+static int ec_get_version(struct ec_version *version)
+{
+ int ret;
+ u16 raw;
+ struct ec_version_raw ver;
+
+ if (WARN_ON(!version))
+ return -EINVAL;
+
+ ret = ec_read_ram(EC_RAM_ACPI, EC_ACPIRAM_FW_RELEASE_RD, sizeof(ver),
+ (u8 *)&ver, sizeof(ver));
+ if (ret < 0)
+ return ret;
+
+ raw = swab16(ver.kernel);
+ version->kernel_major = EC_KERNEL_MAJOR(raw);
+ version->kernel_minor = EC_KERNEL_MINOR(raw);
+
+ raw = swab16(ver.firmware);
+ version->firmware_major = EC_FIRMWARE_MAJOR(raw);
+ version->firmware_minor = EC_FIRMWARE_MINOR(raw);
+
+ raw = swab16(ver.project_code);
+ version->type = EC_PROJECT_CODE(raw);
+
+ return 0;
+}
+
+static int ec_get_pcb_name(struct ec_info *info)
+{
+ int ret;
+ struct ec_message msg = {
+ .rlen = ARRAY_SIZE(info->pcb_name),
+ .wlen = 0,
+ };
+
+ if (WARN_ON(!info))
+ return -EINVAL;
+
+ ret = imanager_msg_read(EC_CMD_FW_INFO_RD, 0, &msg);
+ if (ret)
+ return ret;
+
+ /*
+ * Sadly, the string is not Null-terminated so we will need to read a
+ * fixed amount of chars. There is, apparently, no exact definition
+ * of board name (SOM6867 vs. MIO-5271).
+ */
+ memset(info->pcb_name, 0, ARRAY_SIZE(info->pcb_name));
+ strncpy(info->pcb_name, (const char *)msg.u.data, PCB_NAME_MAX_SIZE);
+
+ if (strchr(info->pcb_name, '-') == NULL)
+ info->pcb_name[PCB_NAME_MAX_SIZE - 1] = '\0';
+
+ return 0;
+}
+
+static int ec_get_fw_info(struct ec_info *info)
+{
+ int ret;
+
+ if (WARN_ON(!info))
+ return -EINVAL;
+
+ ret = ec_get_version(&info->version);
+ if (ret)
+ return ret;
+
+ return ec_get_pcb_name(info);
+}
+
+static int ec_init(void)
+{
+ int ret;
+
+ ec_clear_ports();
+
+ ret = ec_read_dynamic_devtbl(&ec);
+ if (ret)
+ return ret;
+
+ ret = ec_get_fw_info(&ec.dev.info);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_gpio(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_hwmon(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_i2c(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_blc(&ec);
+ if (ret < 0)
+ return ret;
+
+ ret = ec_get_dev_wdt(&ec);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int imanager_ec_probe(void)
+{
+ int chipid = ec_read_chipid(EC_BASE_ADDR);
+
+ memset((void *)&ec, 0, sizeof(ec));
+
+ switch (chipid) {
+ case EC_DEVID_IT8516:
+ pr_err("EC IT8516 not supported\n");
+ ec.dev.id = IT8516;
+ return -ENODEV;
+ case EC_DEVID_IT8518:
+ ec.read = ec_io18_read;
+ ec.write = ec_io18_write;
+ ec.dev.id = IT8518;
+ break;
+ case EC_DEVID_IT8528:
+ ec.read = ec_io28_read;
+ ec.write = ec_io28_write;
+ ec.dev.id = IT8528;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ec.dev.addr = EC_BASE_ADDR;
+
+ return ec_init();
+}
+
diff --git a/include/linux/mfd/imanager/core.h b/include/linux/mfd/imanager/core.h
new file mode 100644
index 0000000..7aad4b7
--- /dev/null
+++ b/include/linux/mfd/imanager/core.h
@@ -0,0 +1,31 @@
+/*
+ * Advantech iManager MFD core
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * Author: Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __CORE_H__
+#define __CORE_H__
+
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/mfd/imanager/ec.h>
+
+struct imanager_platform_data {
+ const struct imanager_ec_device *dev;
+ const char *chip_name;
+};
+
+struct imanager_device_data {
+ struct device *dev;
+ struct mutex lock;
+};
+
+#endif
diff --git a/include/linux/mfd/imanager/ec.h b/include/linux/mfd/imanager/ec.h
new file mode 100644
index 0000000..943dad7
--- /dev/null
+++ b/include/linux/mfd/imanager/ec.h
@@ -0,0 +1,210 @@
+/*
+ * Advantech iManager core - firmware interface
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * Author: Richard Vidal-Dorsch <richard.dorsch@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __EC_H__
+#define __EC_H__
+
+#include <linux/types.h>
+
+#define EC_DEVID_IT8516 0x8516
+#define EC_DEVID_IT8518 0x8518
+#define EC_DEVID_IT8528 0x8528
+
+#define EC_BASE_ADDR 0x029C
+
+#define IT8516_CMD_PORT 0x029A /* it8528 */
+#define IT8516_DAT_PORT 0x0299
+
+#define IT8518_CMD_PORT 0x029E /* it8518 */
+#define IT8518_DAT_PORT 0x029F
+
+#define EC_GPIO_MAX_NUM 8
+#define EC_HWM_MAX_ADC 5
+#define EC_HWM_MAX_FAN 3
+#define EC_BLC_MAX_NUM 2
+#define EC_SMB_MAX_NUM 4
+#define EC_WDT_MAX_NUM 2
+
+#define PCB_NAME_SIZE 32
+#define EC_PAYLOAD_SIZE 40
+#define EC_MSG_SIZE sizeof(struct ec_smb_message)
+
+#define LOBYTE16(x) (x & 0x00FF)
+#define HIBYTE16(x) (LOBYTE16(x >> 8))
+#define LOADDR16(x) LOBYTE16(x)
+#define HIADDR16(x) (x >= 0xF000 ? LOBYTE16(x >> 8) : 0)
+
+/*
+ * iManager commands
+ */
+#define EC_CMD_HWP_RD 0x11UL
+#define EC_CMD_HWP_WR 0x12UL
+#define EC_CMD_GPIO_DIR_RD 0x30UL
+#define EC_CMD_GPIO_DIR_WR 0x31UL
+#define EC_CMD_PWM_FREQ_RD 0x36UL
+#define EC_CMD_PWM_FREQ_WR 0x32UL
+#define EC_CMD_PWM_POL_RD 0x37UL
+#define EC_CMD_PWM_POL_WR 0x33UL
+#define EC_CMD_SMB_FREQ_RD 0x34UL
+#define EC_CMD_SMB_FREQ_WR 0x35UL
+#define EC_CMD_FAN_CTL_RD 0x40UL
+#define EC_CMD_FAN_CTL_WR 0x41UL
+#define EC_CMD_THZ_RD 0x42UL
+#define EC_CMD_DYN_TBL_RD 0x20UL
+#define EC_CMD_FW_INFO_RD 0xF0UL
+#define EC_CMD_BUF_CLR 0xC0UL
+#define EC_CMD_BUF_RD 0xC1UL
+#define EC_CMD_BUF_WR 0xC2UL
+#define EC_CMD_RAM_RD 0x1EUL
+#define EC_CMD_RAM_WR 0x1FUL
+#define EC_CMD_I2C_RW 0x0EUL
+#define EC_CMD_I2C_WR 0x0FUL
+#define EC_CMD_WDT_CTRL 0x28UL
+
+/*
+ * ACPI and HW RAM offsets
+ */
+#define EC_ACPIRAM_FAN_ALERT 0x6FUL
+#define EC_ACPIRAM_FAN_SPEED_LIMIT 0x76UL
+#define EC_ACPIRAM_BRIGHTNESS1 0x50UL
+#define EC_ACPIRAM_BRIGHTNESS2 0x52UL
+#define EC_ACPIRAM_BLC_CTRL 0x99UL
+#define EC_ACPIRAM_FW_RELEASE_RD 0xF8UL
+
+enum chips { IT8516, IT8518, IT8528 };
+
+struct ec_message_header {
+ u8 addr_low; /* SMB low-byte address or data low-byte */
+ /* of byte-/word-transaction */
+ u8 addr_high; /* SMB high-byte address or data high-byte */
+ /* of word-transaction */
+ u8 rlen; /* SMB read length */
+ u8 wlen; /* SMB write length */
+ u8 cmd; /* SMB command */
+};
+
+struct ec_smb_message {
+ struct ec_message_header hdr;
+ u8 data[EC_PAYLOAD_SIZE];
+};
+
+struct ec_message {
+ u8 rlen; /* EC message read length */
+ u8 wlen; /* EC message write length */
+ union {
+ struct ec_smb_message smb;
+ u8 data[EC_MSG_SIZE];
+ } u;
+
+ u8 *data;
+};
+
+struct ec_version {
+ u32 kernel_major;
+ u32 kernel_minor;
+ u32 firmware_major;
+ u32 firmware_minor;
+ u32 type;
+};
+
+struct ec_info {
+ struct ec_version version;
+ char pcb_name[PCB_NAME_SIZE];
+};
+
+struct imanager_ec_device {
+ int id; /* enum chip */
+ u16 addr;
+ struct ec_info info;
+};
+
+struct ec_dev_attr {
+ int did; /* Device ID */
+ int hwp; /* Hardware Pin number */
+ int pol; /* Polarity */
+ int scale; /* Scaling factor */
+ const char *label;
+};
+
+struct imanager_gpio_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_GPIO_MAX_NUM];
+ struct ec_info *info;
+};
+
+struct dev_adc {
+ u32 num;
+ struct ec_dev_attr attr[EC_HWM_MAX_ADC];
+};
+
+struct dev_fan {
+ u32 num;
+ u32 active;
+ struct ec_dev_attr attr[EC_HWM_MAX_FAN];
+};
+
+struct imanager_hwmon_device {
+ struct dev_adc adc;
+ struct dev_fan fan;
+ struct imanager_ec_device *ecdev;
+};
+
+struct imanager_i2c_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_SMB_MAX_NUM];
+ struct ec_dev_attr *eeprom;
+ struct ec_dev_attr *i2coem;
+ struct imanager_ec_device *ecdev;
+};
+
+struct imanager_backlight_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_BLC_MAX_NUM];
+ u8 brightness[EC_BLC_MAX_NUM];
+ struct ec_info *info;
+};
+
+struct imanager_watchdog_device {
+ u32 num;
+ struct ec_dev_attr attr[EC_WDT_MAX_NUM];
+ struct ec_dev_attr *irq;
+ struct ec_dev_attr *nmi;
+ struct ec_info *info;
+};
+
+/* Must be called first, obviously */
+int imanager_ec_probe(void);
+
+int imanager_msg_write(u8 cmd, u8 param, struct ec_message *msg);
+int imanager_msg_read(u8 cmd, u8 param, struct ec_message *msg);
+
+int imanager_read_byte(u8 cmd, u8 param);
+int imanager_read_word(u8 cmd, u8 param);
+
+int imanager_write_byte(u8 cmd, u8 param, u8 byte);
+int imanager_write_word(u8 cmd, u8 param, u16 word);
+
+int imanager_acpiram_read_byte(u8 offset);
+int imanager_acpiram_write_byte(u8 offset, u8 value);
+int imanager_acpiram_read_block(u8 offset, u8 *buf, u8 len);
+int imanager_acpiram_write_block(u8 offset, u8 *buf, u8 len);
+
+int imanager_wait_proc_complete(u8 offset, int cond);
+
+const struct imanager_ec_device *imanager_get_ec_device(void);
+const struct imanager_hwmon_device *imanager_get_hwmon_device(void);
+const struct imanager_gpio_device *imanager_get_gpio_device(void);
+const struct imanager_i2c_device *imanager_get_i2c_device(void);
+const struct imanager_watchdog_device *imanager_get_watchdog_device(void);
+const struct imanager_backlight_device *imanager_get_backlight_device(void);
+
+#endif
--
2.6.4