| /* |
| * Copyright 2018 NXP |
| * |
| * Peng Fan <peng.fan@nxp.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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #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; |
| /* Missed the backend's Closing state -- fallthrough */ |
| 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); |