blob: 8a18a7982aafa4b6d3e8591099d5f93a9b2201d8 [file] [log] [blame]
/*
* 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);