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