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