diff options
author | Clark Wang <xiaoning.wang@nxp.com> | 2019-02-18 16:55:09 +0800 |
---|---|---|
committer | Clark Wang <xiaoning.wang@nxp.com> | 2019-02-21 18:38:12 +0800 |
commit | 9feeac93a7d91ce67537a8a6c67e624eb7986a01 (patch) | |
tree | 56b978a81ca44195d47a22e8c69edd0c0338e524 /drivers | |
parent | 2f84f4701ced8fad33a0a9947ec5a7bb5a9c2c1f (diff) |
MLK-20940-1 i2c: busses: Add virtual i2c driver based on rpmsg
Add virtual i2c driver to send SRTM i2c messages to M4.
Each virtual I2C bus has a specal bus id, which is abstracted by M4.
Each SRTM message include a bus id for the bus which the device is on.
Virtual i2c rpmsg bus will bind rpbus nodes with compatible string
"fsl,i2c-rpbus". And "rpmsg-i2c-channel" will probe only one rpmsg
channel for all rpbuses.
This virtual i2c driver depends on CONFIG_I2C and CONFIG_RPMSG.
Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/Kconfig | 6 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-rpmsg-imx.c | 427 |
3 files changed, 434 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d87754cbb7a8..ed0cd58a34c9 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -863,6 +863,12 @@ config I2C_RK3X This driver can also be built as a module. If so, the module will be called i2c-rk3x. +config I2C_RPBUS + tristate "I2C proxy bus over RPMSG" + depends on I2C && RPMSG + help + This driver can support virtual i2c-rpmsg function. + config HAVE_S3C2410_I2C bool help diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 8221b50eb272..85c2bf496ecf 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -120,6 +120,7 @@ obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o +obj-$(CONFIG_I2C_RPBUS) += i2c-rpmsg-imx.o obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o diff --git a/drivers/i2c/busses/i2c-rpmsg-imx.c b/drivers/i2c/busses/i2c-rpmsg-imx.c new file mode 100644 index 000000000000..2c13056167f1 --- /dev/null +++ b/drivers/i2c/busses/i2c-rpmsg-imx.c @@ -0,0 +1,427 @@ +/* + * Copyright 2019 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* The i2c-rpmsg transfer protocol: + * + * +---------------+-------------------------------+ + * | Byte Offset | Content | + * +---------------+---+---+---+---+---+---+---+---+ + * | 0 | Category | + * +---------------+---+---+---+---+---+---+---+---+ + * | 1 ~ 2 | Version | + * +---------------+---+---+---+---+---+---+---+---+ + * | 3 | Type | + * +---------------+---+---+---+---+---+---+---+---+ + * | 4 | Command | + * +---------------+---+---+---+---+---+---+---+---+ + * | 5 | Priority | + * +---------------+---+---+---+---+---+---+---+---+ + * | 6 | Reserved1 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 7 | Reserved2 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 8 | Reserved3 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 9 | Reserved4 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 10 | BUS ID | + * +---------------+---+---+---+---+---+---+---+---+ + * | 11 | Return Value | + * +---------------+---+---+---+---+---+---+---+---+ + * | 12 ~ 13 | BUS ID | + * +---------------+---+---+---+---+---+---+---+---+ + * | 14 ~ 15 | Address | + * +---------------+---+---+---+---+---+---+---+---+ + * | 16 ~ 17 | Data Len | + * +---------------+---+---+---+---+---+---+---+---+ + * | 18 ~ 33 | 16 Bytes Data | + * +---------------+---+---+---+---+---+---+---+---+ + * + * The definition of Return Value: + * 0x00 = Success + * 0x01 = Failed + * 0x02 = Invalid parameter + * 0x03 = Invalid message + * 0x04 = Operate in invalid state + * 0x05 = Memory allocation failed + * 0x06 = Timeout when waiting for an event + * 0x07 = Cannot add to list as node already in another list + * 0x08 = Cannot remove from list as node not in list + * 0x09 = Transfer timeout + * 0x0A = Transfer failed due to peer core not ready + * 0x0B = Transfer failed due to communication failure + * 0x0C = Cannot find service for a request/notification + * 0x0D = Service version cannot support the request/notification + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/imx_rpmsg.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/rpmsg.h> + +#define I2C_RPMSG_MAX_BUF_SIZE 16 +#define I2C_RPMSG_TIMEOUT 100 /* unit: ms */ + +#define I2C_RPMSG_CATEGORY 0x09 +#define I2C_RPMSG_VERSION 0x0001 +#define I2C_RPMSG_TYPE_REQUEST 0x00 +#define I2C_RPMSG_TYPE_RESPONSE 0x01 +#define I2C_RPMSG_COMMAND_READ 0x00 +#define I2C_RPMSG_COMMAND_WRITE 0x01 +#define I2C_RPMSG_PRIORITY 0x01 + +#define I2C_RPMSG_M_STOP 0x0200 + +struct i2c_rpmsg_msg { + struct imx_rpmsg_head header; + + /* Payload Start*/ + u8 bus_id; + u8 ret_val; + u16 addr; + u16 flags; + u16 len; + u8 buf[I2C_RPMSG_MAX_BUF_SIZE]; +} __packed __aligned(1); + +struct i2c_rpmsg_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct i2c_rpmsg_msg *msg; + struct completion cmd_complete; +}; + +static struct i2c_rpmsg_info i2c_rpmsg; + +struct imx_rpmsg_i2c_data { + struct i2c_adapter adapter; +}; + +static int i2c_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct i2c_rpmsg_msg *msg = (struct i2c_rpmsg_msg *)data; + + if (msg->header.type != I2C_RPMSG_TYPE_RESPONSE) + return -EINVAL; + + if (msg->len > I2C_RPMSG_MAX_BUF_SIZE) { + dev_err(&rpdev->dev, + "%s failed: data length greater than %d, len=%d\n", + __func__, I2C_RPMSG_MAX_BUF_SIZE, msg->len); + return -EINVAL; + } + + /* Receive Success */ + i2c_rpmsg.msg = msg; + + complete(&i2c_rpmsg.cmd_complete); + + return 0; +} + +static int rpmsg_xfer(struct i2c_rpmsg_msg *rmsg, struct i2c_rpmsg_info *info) +{ + int ret = 0; + + ret = rpmsg_send(info->rpdev->ept, (void *)rmsg, + sizeof(struct i2c_rpmsg_msg)); + if (ret < 0) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(I2C_RPMSG_TIMEOUT)); + if (!ret) { + dev_err(&info->rpdev->dev, "%s failed: timeout\n", __func__); + return -ETIMEDOUT; + } + + if (info->msg->ret_val) { + dev_dbg(&info->rpdev->dev, + "%s failed: %d\n", __func__, info->msg->ret_val); + return -(info->msg->ret_val); + } + + return 0; +} + +static int i2c_rpmsg_read(struct i2c_msg *msg, struct i2c_rpmsg_info *info, + int bus_id, bool is_last) +{ + int ret; + struct i2c_rpmsg_msg rmsg; + + if (!info->rpdev) + return -EINVAL; + + if (msg->len > I2C_RPMSG_MAX_BUF_SIZE) { + dev_err(&info->rpdev->dev, + "%s failed: data length greater than %d, len=%d\n", + __func__, I2C_RPMSG_MAX_BUF_SIZE, msg->len); + return -EINVAL; + } + + memset(&rmsg, 0, sizeof(struct i2c_rpmsg_msg)); + rmsg.header.cate = I2C_RPMSG_CATEGORY; + rmsg.header.major = I2C_RPMSG_VERSION; + rmsg.header.minor = I2C_RPMSG_VERSION >> 8; + rmsg.header.type = I2C_RPMSG_TYPE_REQUEST; + rmsg.header.cmd = I2C_RPMSG_COMMAND_READ; + rmsg.header.reserved[0] = I2C_RPMSG_PRIORITY; + rmsg.bus_id = bus_id; + rmsg.ret_val = 0; + rmsg.addr = msg->addr; + if (is_last) + rmsg.flags = msg->flags | I2C_RPMSG_M_STOP; + else + rmsg.flags = msg->flags; + rmsg.len = (msg->len); + + reinit_completion(&info->cmd_complete); + + ret = rpmsg_xfer(&rmsg, info); + if (ret) + return ret; + + if (!info->msg || + (info->msg->len != msg->len)) { + dev_err(&info->rpdev->dev, + "%s failed: %d\n", __func__, -EPROTO); + return -EPROTO; + } + + memcpy(msg->buf, info->msg->buf, info->msg->len); + + return msg->len; +} + +int i2c_rpmsg_write(struct i2c_msg *msg, struct i2c_rpmsg_info *info, + int bus_id, bool is_last) +{ + int i, ret; + struct i2c_rpmsg_msg rmsg; + + if (!info || !info->rpdev) + return -EINVAL; + + if (msg->len > I2C_RPMSG_MAX_BUF_SIZE) { + dev_err(&info->rpdev->dev, + "%s failed: data length greater than %d, len=%d\n", + __func__, I2C_RPMSG_MAX_BUF_SIZE, msg->len); + return -EINVAL; + } + + memset(&rmsg, 0, sizeof(struct i2c_rpmsg_msg)); + rmsg.header.cate = I2C_RPMSG_CATEGORY; + rmsg.header.major = I2C_RPMSG_VERSION; + rmsg.header.minor = I2C_RPMSG_VERSION >> 8; + rmsg.header.type = I2C_RPMSG_TYPE_REQUEST; + rmsg.header.cmd = I2C_RPMSG_COMMAND_WRITE; + rmsg.header.reserved[0] = I2C_RPMSG_PRIORITY; + rmsg.bus_id = bus_id; + rmsg.ret_val = 0; + rmsg.addr = msg->addr; + if (is_last) + rmsg.flags = msg->flags | I2C_RPMSG_M_STOP; + else + rmsg.flags = msg->flags; + rmsg.len = msg->len; + + for (i = 0; i < rmsg.len; i++) + rmsg.buf[i] = msg->buf[i]; + + reinit_completion(&info->cmd_complete); + + ret = rpmsg_xfer(&rmsg, info); + if (ret) + return ret; + + return ret; +} + +static int i2c_rpmsg_probe(struct rpmsg_device *rpdev) +{ + int ret = 0; + + if (!rpdev) { + dev_info(&rpdev->dev, "%s failed, rpdev=NULL\n", __func__); + return -EINVAL; + } + + i2c_rpmsg.rpdev = rpdev; + + init_completion(&i2c_rpmsg.cmd_complete); + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + return ret; +} + +static void i2c_rpmsg_remove(struct rpmsg_device *rpdev) +{ + i2c_rpmsg.rpdev = NULL; + dev_info(&rpdev->dev, "i2c rpmsg driver is removed\n"); +} + +static struct rpmsg_device_id i2c_rpmsg_id_table[] = { + { .name = "rpmsg-i2c-channel" }, + { }, +}; + +static struct rpmsg_driver i2c_rpmsg_driver = { + .drv.name = "i2c-rpmsg", + .drv.owner = THIS_MODULE, + .id_table = i2c_rpmsg_id_table, + .probe = i2c_rpmsg_probe, + .remove = i2c_rpmsg_remove, + .callback = i2c_rpmsg_cb, +}; + + +static int i2c_rpbus_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct imx_rpmsg_i2c_data *rdata = + container_of(adapter, struct imx_rpmsg_i2c_data, adapter); + struct i2c_msg *pmsg; + int i, ret; + bool is_last = false; + + for (i = 0; i < num; i++) { + if (i == num - 1) + is_last = true; + + pmsg = &msgs[i]; + + if (pmsg->flags & I2C_M_RD) { + ret = i2c_rpmsg_read(pmsg, &i2c_rpmsg, + rdata->adapter.nr, is_last); + if (ret < 0) + return ret; + + pmsg->len = ret; + } else { + ret = i2c_rpmsg_write(pmsg, &i2c_rpmsg, + rdata->adapter.nr, is_last); + if (ret < 0) + return ret; + } + } + + return num; +} + +static u32 i2c_rpbus_func(struct i2c_adapter *a) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL + | I2C_FUNC_SMBUS_READ_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_rpbus_algorithm = { + .master_xfer = i2c_rpbus_xfer, + .functionality = i2c_rpbus_func, +}; + +static const struct i2c_adapter_quirks i2c_rpbus_quirks = { + .max_write_len = I2C_RPMSG_MAX_BUF_SIZE, + .max_read_len = I2C_RPMSG_MAX_BUF_SIZE, +}; + +static int i2c_rpbus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct imx_rpmsg_i2c_data *rdata; + struct i2c_adapter *adapter; + int ret; + + rdata = devm_kzalloc(&pdev->dev, sizeof(*rdata), GFP_KERNEL); + if (!rdata) + return -ENOMEM; + + adapter = &rdata->adapter; + /* setup i2c adapter description */ + adapter->owner = THIS_MODULE; + adapter->class = I2C_CLASS_HWMON; + adapter->algo = &i2c_rpbus_algorithm; + adapter->dev.parent = dev; + adapter->dev.of_node = np; + adapter->nr = of_alias_get_id(np, "i2c"); + adapter->quirks = &i2c_rpbus_quirks; + snprintf(rdata->adapter.name, sizeof(rdata->adapter.name), "%s", + "i2c-rpmsg-adapter"); + platform_set_drvdata(pdev, rdata); + + ret = i2c_add_adapter(&rdata->adapter); + if (ret < 0) { + dev_err(dev, "failed to add I2C adapter: %d\n", ret); + return ret; + } + + dev_info(dev, "add I2C adapter %s successfully\n", rdata->adapter.name); + + return 0; +} + +static int i2c_rpbus_remove(struct platform_device *pdev) +{ + struct imx_rpmsg_i2c_data *rdata = platform_get_drvdata(pdev); + + i2c_del_adapter(&rdata->adapter); + + return 0; +} + +static const struct of_device_id imx_rpmsg_i2c_dt_ids[] = { + { .compatible = "fsl,i2c-rpbus", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_rpmsg_i2c_dt_ids); + +static struct platform_driver imx_rpmsg_i2c_driver = { + .driver = { + .name = "imx_rpmsg_i2c", + .of_match_table = imx_rpmsg_i2c_dt_ids, + }, + .probe = i2c_rpbus_probe, + .remove = i2c_rpbus_remove +}; + +static int __init imx_rpmsg_i2c_driver_init(void) +{ + int ret = 0; + + ret = register_rpmsg_driver(&i2c_rpmsg_driver); + if (ret < 0) + return ret; + + return platform_driver_register(&(imx_rpmsg_i2c_driver)); +} +subsys_initcall(imx_rpmsg_i2c_driver_init); + +MODULE_AUTHOR("Clark Wang<xiaoning.wang@nxp.com>"); +MODULE_DESCRIPTION("Driver for i2c over rpmsg"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-rpbus"); |