diff options
Diffstat (limited to 'drivers/i2c/busses/xen-i2cfront.c')
-rw-r--r-- | drivers/i2c/busses/xen-i2cfront.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/drivers/i2c/busses/xen-i2cfront.c b/drivers/i2c/busses/xen-i2cfront.c new file mode 100644 index 000000000000..6b8702074fb5 --- /dev/null +++ b/drivers/i2c/busses/xen-i2cfront.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/list.h> +#include <linux/io.h> + +#include <xen/xen.h> +#include <xen/xenbus.h> +#include <xen/grant_table.h> +#include <xen/events.h> +#include <xen/page.h> + +#include <xen/interface/io/i2cif.h> + +#define GRANT_INVALID_REF 0 + +struct i2cfront_info { + spinlock_t lock; + struct mutex xferlock; + struct i2c_adapter adapter; + struct xenbus_device *i2cdev; + int i2c_ring_ref; + struct i2cif_front_ring i2c_ring; + unsigned int evtchn; + unsigned int irq; + struct completion completion; + struct i2cif_request *req; + struct i2cif_response *res; +}; + +static void i2cfront_destroy_rings(struct i2cfront_info *info) +{ + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->irq = 0; + + if (info->i2c_ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->i2c_ring_ref, 0, + (unsigned long)info->i2c_ring.sring); + info->i2c_ring_ref = GRANT_INVALID_REF; + } + info->i2c_ring.sring = NULL; +} + +static int i2cfront_do_req(struct i2c_adapter *adapter, struct i2c_msg *msg, + int num) +{ + struct i2cfront_info *info = i2c_get_adapdata(adapter); + struct i2cif_request *req; + struct i2cif_response *res; + int notify; + int ret; + RING_IDX i, rp; + int more_to_do = 0; + unsigned long flags; + int index; + + mutex_lock(&info->xferlock); + req = RING_GET_REQUEST(&info->i2c_ring, info->i2c_ring.req_prod_pvt); + + for (index = 0; index < num; index++) { + req->msg[index].addr = msg[index].addr; + req->msg[index].len = msg[index].len; + req->msg[index].flags = 0; + if (msg[index].flags & I2C_M_RD) + req->msg[index].flags |= I2CIF_M_RD; + if (msg[index].flags & I2C_M_TEN) + req->msg[index].flags |= I2CIF_M_TEN; + if (msg[index].flags & I2C_M_RECV_LEN) + req->msg[index].flags |= I2CIF_M_RECV_LEN; + if (msg[index].flags & I2C_M_NO_RD_ACK) + req->msg[index].flags |= I2CIF_M_NO_RD_ACK; + if (msg[index].flags & I2C_M_IGNORE_NAK) + req->msg[index].flags |= I2CIF_M_IGNORE_NAK; + if (msg[index].flags & I2C_M_REV_DIR_ADDR) + req->msg[index].flags |= I2CIF_M_REV_DIR_ADDR; + if (msg[index].flags & I2C_M_NOSTART) + req->msg[index].flags |= I2CIF_M_NOSTART; + if (msg[index].flags & I2C_M_STOP) + req->msg[index].flags |= I2CIF_M_STOP; + } + + req->num_msg = num; + req->is_smbus = false; + + if ((num == 2) && !(msg[0].flags & I2C_M_RD) && + (msg[1].flags & I2C_M_RD)) { + memcpy(req->write_buf, msg[0].buf, + min_t(int, msg[0].len, I2CIF_BUF_LEN)); + } else if (num == 1) { + if (!(msg->flags & I2C_M_RD)) + memcpy(req->write_buf, msg->buf, + min_t(int, msg->len, I2CIF_BUF_LEN)); + } else { + dev_err(&adapter->dev, "%s not supported\n", __func__); + return -EIO; + } + + spin_lock(&info->lock); + info->i2c_ring.req_prod_pvt++; + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->i2c_ring, notify); + spin_unlock(&info->lock); + if (notify) + notify_remote_via_irq(info->irq); + + wait_for_completion(&info->completion); + + spin_lock_irqsave(&info->lock, flags); + rp = info->i2c_ring.sring->rsp_prod; + rmb(); /* ensure we see queued responses up to "rp" */ + + ret = -EIO; + for (i = info->i2c_ring.rsp_cons; i != rp; i++) { + res = RING_GET_RESPONSE(&info->i2c_ring, i); + if ((num == 2) && !(msg[0].flags & I2C_M_RD) && + (msg[1].flags & I2C_M_RD)) { + memcpy(msg[1].buf, res->read_buf, + min_t(int, msg[1].len, I2CIF_BUF_LEN)); + } else if (num == 1) { + if (!(msg->flags & I2C_M_RD)) + memcpy(msg->buf, res->read_buf, + min_t(int, msg->len, I2CIF_BUF_LEN)); + } + + ret = res->result; + } + + info->i2c_ring.rsp_cons = i; + + if (i != info->i2c_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->i2c_ring, more_to_do); + else + info->i2c_ring.sring->rsp_event = i + 1; + + spin_unlock_irqrestore(&info->lock, flags); + + mutex_unlock(&info->xferlock); + + return ret; +} + +int i2cfront_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) +{ + struct i2cfront_info *info = i2c_get_adapdata(adapter); + int ret, i; + + if (!info || !info->i2cdev) { + dev_err(&adapter->dev, "Not initialized\n"); + return -EIO; + } + + if (info->i2cdev->state != XenbusStateConnected) { + dev_err(&adapter->dev, "Not connected\n"); + return -EIO; + } + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + ret = i2cfront_do_req(adapter, &msgs[i], 1); + } else if ((i + 1 < num) && (msgs[i + 1].flags & I2C_M_RD) && + (msgs[i].addr == msgs[i + 1].addr)) { + ret = i2cfront_do_req(adapter, &msgs[i], 2); + i++; + } else { + ret = i2cfront_do_req(adapter, &msgs[i], 1); + } + + if (ret < 0) + goto err; + } +err: + return (ret < 0) ? ret : num; +} + +static int i2cfront_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct i2cfront_info *info = i2c_get_adapdata(adapter); + struct i2cif_response *res; + struct i2cif_request *req; + unsigned long lock_flags; + int more_to_do = 0; + RING_IDX i, rp; + int notify; + int ret; + + if (!info || !info->i2cdev) { + dev_err(&adapter->dev, "Not initialized\n"); + return -EIO; + } + + if (info->i2cdev->state != XenbusStateConnected) { + dev_err(&adapter->dev, "Not connected\n"); + return -EIO; + } + + mutex_lock(&info->xferlock); + req = RING_GET_REQUEST(&info->i2c_ring, info->i2c_ring.req_prod_pvt); + + req->is_smbus = true; + req->addr = addr; + req->flags = flags; + req->read_write = read_write; + req->command = command; + req->protocol = size; + if (data != NULL) + memcpy(&req->write_buf, data, sizeof(union i2c_smbus_data)); + + spin_lock(&info->lock); + info->i2c_ring.req_prod_pvt++; + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->i2c_ring, notify); + spin_unlock(&info->lock); + if (notify) + notify_remote_via_irq(info->irq); + + wait_for_completion(&info->completion); + + spin_lock_irqsave(&info->lock, lock_flags); + rp = info->i2c_ring.sring->rsp_prod; + rmb(); /* ensure we see queued responses up to "rp" */ + + ret = -EIO; + for (i = info->i2c_ring.rsp_cons; i != rp; i++) { + res = RING_GET_RESPONSE(&info->i2c_ring, i); + + if (data != NULL && read_write == I2C_SMBUS_READ) + memcpy(data, &res->read_buf, sizeof(union i2c_smbus_data)); + + ret = res->result; + } + + info->i2c_ring.rsp_cons = i; + + if (i != info->i2c_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->i2c_ring, more_to_do); + else + info->i2c_ring.sring->rsp_event = i + 1; + + spin_unlock_irqrestore(&info->lock, lock_flags); + + mutex_unlock(&info->xferlock); + + return ret; +} + +static u32 i2cfront_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_QUICK | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; +} + +static const struct i2c_algorithm i2cfront_algo = { + .master_xfer = i2cfront_xfer, + .smbus_xfer = i2cfront_smbus_xfer, + .functionality = i2cfront_func, +}; + +static int i2cfront_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct i2cfront_info *info; + + info = kzalloc(sizeof(struct i2cfront_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->i2cdev = dev; + dev_set_drvdata(&dev->dev, info); + info->adapter.owner = THIS_MODULE; + info->adapter.algo = &i2cfront_algo; + info->adapter.dev.parent = &dev->dev; + strlcpy(info->adapter.name, dev->nodename, sizeof(info->adapter.name)); + i2c_set_adapdata(&info->adapter, info); + spin_lock_init(&info->lock); + mutex_init(&info->xferlock); + init_completion(&info->completion); + + return 0; +} + +static int i2cfront_handle_int(struct i2cfront_info *info) +{ + complete(&info->completion); + + return 0; +} + +static irqreturn_t i2cfront_int(int irq, void *dev_id) +{ + struct i2cfront_info *info = dev_id; + + while (i2cfront_handle_int(info)) + cond_resched(); + + return IRQ_HANDLED; +} + +static int i2cfront_setup_rings(struct xenbus_device *dev, + struct i2cfront_info *info) +{ + struct i2cif_sring *i2c_sring; + grant_ref_t gref; + int err; + + info->i2c_ring_ref = GRANT_INVALID_REF; + i2c_sring = (struct i2cif_sring *)get_zeroed_page(GFP_NOIO | + __GFP_HIGH); + if (!i2c_sring) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating i2c sring"); + return -ENOMEM; + } + + SHARED_RING_INIT(i2c_sring); + FRONT_RING_INIT(&info->i2c_ring, i2c_sring, PAGE_SIZE); + + err = xenbus_grant_ring(dev, i2c_sring, 1, &gref); + if (err < 0) { + free_page((unsigned long)i2c_sring); + info->i2c_ring.sring = NULL; + goto fail; + } + info->i2c_ring_ref = gref; + + err = xenbus_alloc_evtchn(dev, &info->evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn"); + goto fail; + } + + err = bind_evtchn_to_irqhandler(info->evtchn, i2cfront_int, 0, + "xen_i2cif", info); + if (err <= 0) { + xenbus_dev_fatal(dev, err, "bind_evtchn_to_irqhandler failed"); + goto fail; + } + + info->irq = err; + + return 0; + +fail: + i2cfront_destroy_rings(info); + return err; +} + +static int i2cfront_connect(struct xenbus_device *dev) +{ + struct i2cfront_info *info = dev_get_drvdata(&dev->dev); + struct xenbus_transaction xbt; + struct device_node *np; + const char *be_adapter; + char xenstore_adapter[I2CIF_ADAPTER_NAME_LEN]; + char *message; + int err; + + err = i2cfront_setup_rings(dev, info); + if (err) { + dev_err(&dev->dev, "%s:failure....", __func__); + return err; + } +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + goto destroy_ring; + } + + err = xenbus_printf(xbt, dev->nodename, "ring-ref", "%u", + info->i2c_ring_ref); + if (err) { + message = "writing i2c ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + info->evtchn); + if (err) { + message = "writing event-channel"; + goto abort_transaction; + } + + err = xenbus_scanf(xbt, dev->nodename, + "be-adapter", "%32s", xenstore_adapter); + if (err != 1) { + message = "getting be-adapter"; + goto abort_transaction; + } + + err = xenbus_transaction_end(xbt, 0); + if (err) { + if (err == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, err, "completing transaction"); + goto destroy_ring; + } + + for_each_compatible_node(np, NULL, "xen,i2c") { + err = of_property_read_string(np, "be-adapter", &be_adapter); + if (err) + continue; + if (!strncmp(xenstore_adapter, be_adapter, + I2CIF_ADAPTER_NAME_LEN)) { + info->adapter.dev.of_node = np; + break; + } + } + + err = i2c_add_adapter(&info->adapter); + if (err) + return err; + + dev_info(&info->adapter.dev, "XEN I2C adapter registered\n"); + + return 0; + +abort_transaction: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, err, "%s", message); + +destroy_ring: + i2cfront_destroy_rings(info); + + return err; +} + +static void i2cfront_disconnect(struct xenbus_device *dev) +{ + pr_info("%s\n", __func__); + xenbus_frontend_closed(dev); +} + +static void i2cfront_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + break; + + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateConnected: + if (dev->state != XenbusStateInitialising) + break; + if (!i2cfront_connect(dev)) + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosed: + if (dev->state == XenbusStateClosed) + break; + i2cfront_disconnect(dev); + break; + case XenbusStateClosing: + i2cfront_disconnect(dev); + break; + + default: + xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", + backend_state); + break; + } +} + +static int i2cfront_remove(struct xenbus_device *dev) +{ + struct i2cfront_info *info = dev_get_drvdata(&dev->dev); + + i2c_del_adapter(&info->adapter); + i2cfront_destroy_rings(info); + + kfree(info); + + dev_info(&dev->dev, "Remove"); + return 0; +} + +static const struct xenbus_device_id i2cfront_ids[] = { + { "vi2c" }, + { "" }, +}; + +static struct xenbus_driver i2cfront_driver = { + .ids = i2cfront_ids, + .probe = i2cfront_probe, + .otherend_changed = i2cfront_backend_changed, + .remove = i2cfront_remove, +}; + +static int __init i2cfront_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + return xenbus_register_frontend(&i2cfront_driver); +} +subsys_initcall(i2cfront_init); |