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