LF-191-2 i2c: introduce xen i2c paravirtualization driver
Add i2c backend frontend support. The transaction only support one msg each time. The frontend sends a request to backend, backend use i2c_transfer to do real transaction to hardware and return the results to frontend. Now i2cdump/get/set works. In domu cfg file, use "vi2c = ['backend=0,be-adapter=5a800000.i2c,addr=0x51;0x44']" to create a dummy controller in frontend and allowed slaves in backend. Currently the slave address check not added, it will be supported in furture patch. Signed-off-by: Peng Fan <peng.fan@nxp.com> Acked-by: Leonard Crestez <leonard.crestez@nxp.com>
This commit is contained in:
parent
1990cee78d
commit
dda4dde724
|
@ -1419,4 +1419,24 @@ config I2C_FSI
|
|||
This driver can also be built as a module. If so, the module will be
|
||||
called as i2c-fsi.
|
||||
|
||||
config XEN_I2C_FRONTEND
|
||||
tristate "Xen virtual i2c device support"
|
||||
depends on XEN
|
||||
default y
|
||||
select XEN_XENBUS_FRONTEND
|
||||
help
|
||||
This driver implements the front-end of the Xen virtual
|
||||
i2c device driver. It communicates with a back-end driver
|
||||
in another domain which drives the actual i2c device.
|
||||
|
||||
config XEN_I2C_BACKEND
|
||||
tristate "Xen i2c-device backend driver"
|
||||
depends on XEN_BACKEND
|
||||
help
|
||||
The i2c-device backend driver allows the kernel to export its
|
||||
block devices to other guests.
|
||||
|
||||
The corresponding Linux frontend driver is enabled by the
|
||||
CONFIG_XEN_I2C_FRONTEND configuration option.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -149,4 +149,7 @@ obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o
|
|||
obj-$(CONFIG_SCx200_ACB) += scx200_acb.o
|
||||
obj-$(CONFIG_I2C_FSI) += i2c-fsi.o
|
||||
|
||||
obj-$(CONFIG_XEN_I2C_FRONTEND) += xen-i2cfront.o
|
||||
obj-$(CONFIG_XEN_I2C_BACKEND) += xen-i2cback.o
|
||||
|
||||
ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
|
||||
|
|
|
@ -0,0 +1,485 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright 2018-2019 NXP
|
||||
*
|
||||
* Peng Fan <peng.fan@nxp.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <xen/xen.h>
|
||||
#include <xen/events.h>
|
||||
#include <xen/xenbus.h>
|
||||
#include <xen/grant_table.h>
|
||||
#include <xen/page.h>
|
||||
|
||||
#include <xen/interface/grant_table.h>
|
||||
#include <xen/interface/io/i2cif.h>
|
||||
|
||||
struct i2cback_info {
|
||||
domid_t domid;
|
||||
u32 irq;
|
||||
u64 handle;
|
||||
struct xenbus_device *i2cdev;
|
||||
spinlock_t i2c_ring_lock;
|
||||
struct i2cif_back_ring i2c_ring;
|
||||
int is_connected;
|
||||
int ring_error;
|
||||
struct i2c_adapter *adapter;
|
||||
u32 num_slaves;
|
||||
u32 *allowed_slaves;
|
||||
};
|
||||
|
||||
static bool i2cback_access_allowed(struct i2cback_info *info,
|
||||
struct i2cif_request *req)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (req->is_smbus) {/*check for smbus access permission*/
|
||||
for (i = 0; i < info->num_slaves; i++)
|
||||
if (req->addr == info->allowed_slaves[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*check for master_xfer access permission*/
|
||||
if (req->num_msg == I2CIF_MAX_MSG) {
|
||||
if (req->msg[0].addr != req->msg[1].addr)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < info->num_slaves; i++) {
|
||||
if (req->msg[0].addr == info->allowed_slaves[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool i2cback_handle_int(struct i2cback_info *info)
|
||||
{
|
||||
struct i2cif_back_ring *i2c_ring = &info->i2c_ring;
|
||||
struct i2cif_request req;
|
||||
struct i2cif_response *res;
|
||||
RING_IDX rc, rp;
|
||||
int more_to_do, notify, num_msg = 0, ret;
|
||||
struct i2c_msg msg[I2CIF_MAX_MSG];
|
||||
union i2c_smbus_data smbus_data;
|
||||
char tmp_buf[I2CIF_BUF_LEN];
|
||||
unsigned long flags;
|
||||
bool allow_access;
|
||||
int i;
|
||||
|
||||
rc = i2c_ring->req_cons;
|
||||
rp = i2c_ring->sring->req_prod;
|
||||
rmb(); /* req_cons is written by frontend. */
|
||||
|
||||
if (RING_REQUEST_PROD_OVERFLOW(i2c_ring, rp)) {
|
||||
rc = i2c_ring->rsp_prod_pvt;
|
||||
dev_err(&info->i2cdev->dev, "ring overflow\n");
|
||||
info->ring_error = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (rc != rp) {
|
||||
if (RING_REQUEST_CONS_OVERFLOW(i2c_ring, rc)) {
|
||||
dev_err(&info->i2cdev->dev, "%s overflow\n", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
req = *RING_GET_REQUEST(i2c_ring, rc);
|
||||
allow_access = i2cback_access_allowed(info, &req);
|
||||
if (allow_access && !req.is_smbus) {
|
||||
/* Write/Read sequence */
|
||||
num_msg = req.num_msg;
|
||||
if (num_msg > I2CIF_MAX_MSG)
|
||||
num_msg = I2CIF_MAX_MSG;
|
||||
|
||||
for (i = 0; i < num_msg; i++) {
|
||||
msg[i].addr = req.msg[i].addr;
|
||||
msg[i].len = req.msg[i].len;
|
||||
msg[i].flags = 0;
|
||||
if (req.msg[i].flags & I2CIF_M_RD)
|
||||
msg[i].flags |= I2C_M_RD;
|
||||
if (req.msg[i].flags & I2CIF_M_TEN)
|
||||
msg[i].flags |= I2C_M_TEN;
|
||||
if (req.msg[i].flags & I2CIF_M_RECV_LEN)
|
||||
msg[i].flags |= I2C_M_RECV_LEN;
|
||||
if (req.msg[i].flags & I2CIF_M_NO_RD_ACK)
|
||||
msg[i].flags |= I2C_M_NO_RD_ACK;
|
||||
if (req.msg[i].flags & I2CIF_M_IGNORE_NAK)
|
||||
msg[i].flags |= I2C_M_IGNORE_NAK;
|
||||
if (req.msg[i].flags & I2CIF_M_REV_DIR_ADDR)
|
||||
msg[i].flags |= I2C_M_REV_DIR_ADDR;
|
||||
if (req.msg[i].flags & I2CIF_M_NOSTART)
|
||||
msg[i].flags |= I2C_M_NOSTART;
|
||||
if (req.msg[i].flags & I2CIF_M_STOP)
|
||||
msg[i].flags |= I2C_M_STOP;
|
||||
}
|
||||
|
||||
if ((num_msg == 2) &&
|
||||
(!(msg[0].flags & I2C_M_RD)) &&
|
||||
(msg[1].flags & I2C_M_RD)) {
|
||||
|
||||
/* overwrite the remote buf with local buf */
|
||||
msg[0].buf = tmp_buf;
|
||||
msg[1].buf = tmp_buf;
|
||||
|
||||
/* msg[0] write buf */
|
||||
memcpy(tmp_buf, req.write_buf, I2CIF_BUF_LEN);
|
||||
ret = i2c_transfer(info->adapter, msg,
|
||||
num_msg);
|
||||
} else if (num_msg == 1) {
|
||||
msg[0].buf = tmp_buf;
|
||||
if (!(msg[0].flags & I2C_M_RD))
|
||||
memcpy(tmp_buf, req.write_buf,
|
||||
I2CIF_BUF_LEN);
|
||||
ret = i2c_transfer(info->adapter, msg,
|
||||
req.num_msg);
|
||||
} else {
|
||||
dev_dbg(&info->i2cdev->dev, "too many msgs\n");
|
||||
|
||||
ret = -EIO;
|
||||
}
|
||||
} else if (allow_access && req.is_smbus) {
|
||||
memcpy(&smbus_data, &req.write_buf, sizeof(smbus_data));
|
||||
|
||||
ret = i2c_smbus_xfer(info->adapter,
|
||||
req.addr,
|
||||
req.flags,
|
||||
req.read_write,
|
||||
req.command,
|
||||
req.protocol,
|
||||
&smbus_data);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&info->i2c_ring_lock, flags);
|
||||
res = RING_GET_RESPONSE(&info->i2c_ring,
|
||||
info->i2c_ring.rsp_prod_pvt);
|
||||
|
||||
if (allow_access && !req.is_smbus) {
|
||||
res->result = ret;
|
||||
|
||||
if ((req.num_msg == 2) &&
|
||||
(!(msg[0].flags & I2C_M_RD)) &&
|
||||
(msg[1].flags & I2C_M_RD) && (ret >= 0)) {
|
||||
memcpy(res->read_buf, tmp_buf, I2CIF_BUF_LEN);
|
||||
} else if (req.num_msg == 1) {
|
||||
if ((msg[0].flags & I2C_M_RD) && (ret >= 0))
|
||||
memcpy(res->read_buf, tmp_buf,
|
||||
I2CIF_BUF_LEN);
|
||||
}
|
||||
} else if (allow_access && req.is_smbus) {
|
||||
if (req.read_write == I2C_SMBUS_READ)
|
||||
memcpy(&res->read_buf, &smbus_data, sizeof(smbus_data));
|
||||
res->result = ret;
|
||||
} else
|
||||
res->result = -EPERM;
|
||||
|
||||
info->i2c_ring.rsp_prod_pvt++;
|
||||
|
||||
barrier();
|
||||
RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&info->i2c_ring,
|
||||
notify);
|
||||
spin_unlock_irqrestore(&info->i2c_ring_lock, flags);
|
||||
|
||||
if (notify)
|
||||
notify_remote_via_irq(info->irq);
|
||||
|
||||
i2c_ring->req_cons = ++rc;
|
||||
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
RING_FINAL_CHECK_FOR_REQUESTS(i2c_ring, more_to_do);
|
||||
|
||||
return !!more_to_do;
|
||||
}
|
||||
|
||||
static irqreturn_t i2cback_be_int(int irq, void *dev_id)
|
||||
{
|
||||
struct i2cback_info *info = dev_id;
|
||||
|
||||
if (info->ring_error)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
while (i2cback_handle_int(info))
|
||||
cond_resched();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int i2cback_map(struct i2cback_info *info, grant_ref_t *i2c_ring_ref,
|
||||
evtchn_port_t evtchn)
|
||||
{
|
||||
int err;
|
||||
void *addr;
|
||||
struct i2cif_sring *i2c_sring;
|
||||
|
||||
if (info->irq)
|
||||
return 0;
|
||||
|
||||
err = xenbus_map_ring_valloc(info->i2cdev, i2c_ring_ref, 1, &addr);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
i2c_sring = addr;
|
||||
|
||||
BACK_RING_INIT(&info->i2c_ring, i2c_sring, PAGE_SIZE);
|
||||
|
||||
err = bind_interdomain_evtchn_to_irq(info->domid, evtchn);
|
||||
if (err < 0)
|
||||
goto fail_evtchn;
|
||||
info->irq = err;
|
||||
|
||||
err = request_threaded_irq(info->irq, NULL, i2cback_be_int,
|
||||
IRQF_ONESHOT, "xen-i2cback", info);
|
||||
if (err) {
|
||||
dev_err(&info->i2cdev->dev, "bind evtchn to irq failure!\n");
|
||||
goto free_irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
free_irq:
|
||||
unbind_from_irqhandler(info->irq, info);
|
||||
info->irq = 0;
|
||||
info->i2c_ring.sring = NULL;
|
||||
fail_evtchn:
|
||||
xenbus_unmap_ring_vfree(info->i2cdev, i2c_sring);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int i2cback_connect_rings(struct i2cback_info *info)
|
||||
{
|
||||
struct xenbus_device *dev = info->i2cdev;
|
||||
unsigned int i2c_ring_ref, evtchn;
|
||||
int i, err;
|
||||
char *buf;
|
||||
u32 adapter_id;
|
||||
|
||||
err = xenbus_scanf(XBT_NIL, dev->nodename,
|
||||
"adapter", "%u", &adapter_id);
|
||||
if (err != 1) {
|
||||
xenbus_dev_fatal(dev, err, "%s reading adapter", dev->nodename);
|
||||
return err;
|
||||
}
|
||||
|
||||
info->adapter = i2c_get_adapter(adapter_id);
|
||||
if (!info->adapter)
|
||||
return -ENODEV;
|
||||
|
||||
err = xenbus_scanf(XBT_NIL, dev->nodename,
|
||||
"num-slaves", "%u", &info->num_slaves);
|
||||
if (err != 1) {
|
||||
xenbus_dev_fatal(dev, err, "%s reading num-slaves",
|
||||
dev->nodename);
|
||||
return err;
|
||||
}
|
||||
|
||||
info->allowed_slaves = devm_kmalloc(&dev->dev,
|
||||
info->num_slaves * sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!info->allowed_slaves)
|
||||
return -ENOMEM;
|
||||
|
||||
/* 128 bytes is enough */
|
||||
buf = kmalloc(128, GFP_KERNEL);
|
||||
|
||||
for (i = 0; i < info->num_slaves; i++) {
|
||||
snprintf(buf, 128, "%s/%d", dev->nodename, i);
|
||||
err = xenbus_scanf(XBT_NIL, buf, "addr", "%x",
|
||||
&info->allowed_slaves[i]);
|
||||
if (err != 1) {
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
|
||||
err = xenbus_gather(XBT_NIL, dev->otherend,
|
||||
"ring-ref", "%u", &i2c_ring_ref,
|
||||
"event-channel", "%u", &evtchn, NULL);
|
||||
if (err) {
|
||||
xenbus_dev_fatal(dev, err,
|
||||
"reading %s/ring-ref and event-channel",
|
||||
dev->otherend);
|
||||
return err;
|
||||
}
|
||||
|
||||
dev_info(&info->i2cdev->dev,
|
||||
"xen-pvi2c: ring-ref %u, event-channel %u\n",
|
||||
i2c_ring_ref, evtchn);
|
||||
|
||||
err = i2cback_map(info, &i2c_ring_ref, evtchn);
|
||||
if (err)
|
||||
xenbus_dev_fatal(dev, err, "mapping ring-ref %u evtchn %u",
|
||||
i2c_ring_ref, evtchn);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void i2cback_disconnect(struct i2cback_info *info)
|
||||
{
|
||||
if (info->irq) {
|
||||
unbind_from_irqhandler(info->irq, info);
|
||||
info->irq = 0;
|
||||
}
|
||||
|
||||
if (info->i2c_ring.sring) {
|
||||
xenbus_unmap_ring_vfree(info->i2cdev, info->i2c_ring.sring);
|
||||
info->i2c_ring.sring = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void i2cback_frontend_changed(struct xenbus_device *dev,
|
||||
enum xenbus_state frontend_state)
|
||||
{
|
||||
struct i2cback_info *info = dev_get_drvdata(&dev->dev);
|
||||
int ret;
|
||||
|
||||
switch (frontend_state) {
|
||||
case XenbusStateInitialised:
|
||||
case XenbusStateReconfiguring:
|
||||
case XenbusStateReconfigured:
|
||||
break;
|
||||
|
||||
case XenbusStateInitialising:
|
||||
if (dev->state == XenbusStateClosed) {
|
||||
dev_info(&dev->dev,
|
||||
"xen-pvi2c: %s: prepare for reconnect\n",
|
||||
dev->nodename);
|
||||
xenbus_switch_state(dev, XenbusStateInitWait);
|
||||
}
|
||||
break;
|
||||
case XenbusStateConnected:
|
||||
if (dev->state == XenbusStateConnected)
|
||||
break;
|
||||
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
|
||||
ret = i2cback_connect_rings(info);
|
||||
if (ret)
|
||||
xenbus_dev_fatal(dev, ret, "connect ring fail");
|
||||
break;
|
||||
case XenbusStateClosing:
|
||||
i2cback_disconnect(info);
|
||||
xenbus_switch_state(dev, XenbusStateClosing);
|
||||
break;
|
||||
case XenbusStateClosed:
|
||||
xenbus_switch_state(dev, XenbusStateClosed);
|
||||
if (xenbus_dev_is_online(dev))
|
||||
break;
|
||||
device_unregister(&dev->dev);
|
||||
break;
|
||||
case XenbusStateUnknown:
|
||||
device_unregister(&dev->dev);
|
||||
break;
|
||||
|
||||
default:
|
||||
xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend",
|
||||
frontend_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct i2cback_info *i2cback_alloc(domid_t domid, u64 handle)
|
||||
{
|
||||
struct i2cback_info *info;
|
||||
|
||||
info = kzalloc(sizeof(struct i2cback_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return NULL;
|
||||
|
||||
info->domid = domid;
|
||||
info->handle = handle;
|
||||
spin_lock_init(&info->i2c_ring_lock);
|
||||
info->ring_error = 0;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static int i2cback_probe(struct xenbus_device *dev,
|
||||
const struct xenbus_device_id *id)
|
||||
{
|
||||
struct i2cback_info *info;
|
||||
unsigned long handle;
|
||||
int err;
|
||||
|
||||
if (kstrtoul(strrchr(dev->otherend, '/') + 1, 0, &handle))
|
||||
return -EINVAL;
|
||||
|
||||
info = i2cback_alloc(dev->otherend_id, handle);
|
||||
if (!info) {
|
||||
xenbus_dev_fatal(dev, -ENOMEM, "Allocating backend interface");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
info->i2cdev = dev;
|
||||
dev_set_drvdata(&dev->dev, info);
|
||||
|
||||
err = xenbus_switch_state(dev, XenbusStateInitWait);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2cback_remove(struct xenbus_device *dev)
|
||||
{
|
||||
struct i2cback_info *info = dev_get_drvdata(&dev->dev);
|
||||
|
||||
if (!info)
|
||||
return 0;
|
||||
|
||||
i2cback_disconnect(info);
|
||||
|
||||
kfree(info);
|
||||
dev_set_drvdata(&dev->dev, NULL);
|
||||
|
||||
dev_info(&dev->dev, "%s\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct xenbus_device_id i2cback_ids[] = {
|
||||
{ "vi2c" },
|
||||
{ "" },
|
||||
};
|
||||
|
||||
static struct xenbus_driver i2cback_driver = {
|
||||
.ids = i2cback_ids,
|
||||
.probe = i2cback_probe,
|
||||
.otherend_changed = i2cback_frontend_changed,
|
||||
.remove = i2cback_remove,
|
||||
};
|
||||
|
||||
static int __init i2cback_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!xen_domain())
|
||||
return -ENODEV;
|
||||
|
||||
err = xenbus_register_backend(&i2cback_driver);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(i2cback_init);
|
||||
|
||||
static void __exit i2cback_exit(void)
|
||||
{
|
||||
xenbus_unregister_driver(&i2cback_driver);
|
||||
}
|
||||
module_exit(i2cback_exit);
|
||||
|
||||
MODULE_ALIAS("xen-i2cback:vi2c");
|
||||
MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
|
||||
MODULE_DESCRIPTION("Xen I2C backend driver (i2cback)");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -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);
|
|
@ -32,7 +32,7 @@
|
|||
#include <xen/interface/io/ring.h>
|
||||
#include <xen/interface/grant_table.h>
|
||||
|
||||
#define I2CIF_BUF_LEN 32
|
||||
#define I2CIF_BUF_LEN I2C_SMBUS_BLOCK_MAX + 2
|
||||
#define I2CIF_MAX_MSG 2
|
||||
|
||||
#define I2CIF_M_RD 0x0001 /* read data, from slave to master */
|
||||
|
@ -76,6 +76,13 @@ struct i2cif_request {
|
|||
} msg[I2CIF_MAX_MSG];
|
||||
int num_msg;
|
||||
__u8 write_buf[I2CIF_BUF_LEN];
|
||||
|
||||
bool is_smbus;
|
||||
__u16 addr;
|
||||
__u16 flags;
|
||||
__u8 read_write;
|
||||
__u8 command;
|
||||
int protocol;
|
||||
};
|
||||
|
||||
struct i2cif_response {
|
||||
|
|
Loading…
Reference in New Issue