From 7bab97a6a300777ed812811df354f8419d9e396a Mon Sep 17 00:00:00 2001 From: Arnab Basu Date: Tue, 20 Jan 2015 11:57:57 +0530 Subject: i2c/busses: Add tegra HV I2C driver This is the driver to be used when Linux runs as a guest in the virtualization environment. It expects an i2c server to be present and will communucate with the server over IVC. Bug 200069021 Change-Id: I37e945e52b5470f8fb1478093e41e6ff2118e7b0 Signed-off-by: Arnab Basu Reviewed-on: http://git-master/r/709378 Reviewed-on: http://git-master/r/1198220 Reviewed-on: http://git-master/r/1314789 Reviewed-by: Aniruddha Banerjee Tested-by: Aniruddha Banerjee Reviewed-by: Bahadir Balban GVS: Gerrit_Virtual_Submit Reviewed-by: Timo Alho --- drivers/i2c/busses/i2c-tegra-hv-common.c | 508 +++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-tegra-hv-common.h | 152 +++++++++ drivers/i2c/busses/i2c-tegra-hv.c | 324 ++++++++++++++++++++ 3 files changed, 984 insertions(+) create mode 100644 drivers/i2c/busses/i2c-tegra-hv-common.c create mode 100644 drivers/i2c/busses/i2c-tegra-hv-common.h create mode 100644 drivers/i2c/busses/i2c-tegra-hv.c (limited to 'drivers/i2c') diff --git a/drivers/i2c/busses/i2c-tegra-hv-common.c b/drivers/i2c/busses/i2c-tegra-hv-common.c new file mode 100644 index 000000000..db3d2e65e --- /dev/null +++ b/drivers/i2c/busses/i2c-tegra-hv-common.c @@ -0,0 +1,508 @@ +/* + * IVC based Library for I2C services. + * + * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-tegra-hv-common.h" + +static int _hv_i2c_ivc_send(struct tegra_hv_i2c_comm_chan *comm_chan, + struct i2c_ivc_msg *msg, int size) +{ + struct tegra_hv_i2c_comm_dev *comm_dev = comm_chan->hv_comm_dev; + unsigned long flags = 0; + int ret = 0; + + if (!comm_chan->ivck || !msg || !size) + return -EINVAL; + + spin_lock_irqsave(&comm_dev->ivck_tx_lock, flags); + + if (!tegra_hv_ivc_can_write(comm_chan->ivck)) { + ret = -EBUSY; + goto fail; + } + + ret = tegra_hv_ivc_write(comm_chan->ivck, msg, size); + if (ret != size) { + ret = -EIO; + goto fail; + } + +fail: + spin_unlock_irqrestore(&comm_dev->ivck_tx_lock, flags); + return ret; +} + +static inline int _hv_i2c_get_data_ptr_offset(enum i2c_ivc_msg_t type) +{ + switch (type) { + case I2C_READ_RESPONSE: + case I2C_WRITE_RESPONSE: + return I2C_IVC_COMMON_HEADER_LEN + + sizeof(struct i2c_ivc_msg_tx_rx_hdr); + case I2C_GET_MAX_PAYLOAD_RESPONSE: + return I2C_IVC_COMMON_HEADER_LEN; + default: + WARN(1, "Unsupported message type\n"); + } + return -1; +} + +static void _hv_i2c_comm_chan_cleanup(struct tegra_hv_i2c_comm_chan *comm_chan) +{ + unsigned long flags; + + spin_lock_irqsave(&comm_chan->lock, flags); + /* Free this channel up for further use */ + comm_chan->rcvd_data = NULL; + comm_chan->data_len = 0; + comm_chan->rcvd_err = NULL; + comm_chan->rx_state = I2C_RX_INIT; + spin_unlock_irqrestore(&comm_chan->lock, flags); +} + +static void _hv_i2c_prep_msg_generic(int comm_chan_id, int cont_id, + struct i2c_ivc_msg *msg) +{ + i2c_ivc_start_marker(msg) = 0xf005ba11; + i2c_ivc_chan_id(msg) = comm_chan_id; + i2c_ivc_controller_instance(msg) = cont_id + 1; + i2c_ivc_msg_type(msg) = 0; + i2c_ivc_error_field(msg) = 0; + i2c_ivc_end_marker(msg) = 0x11ab500f; +} + +static int _hv_i2c_send_msg(struct device *dev, + struct tegra_hv_i2c_comm_chan *comm_chan, + struct i2c_ivc_msg *msg, uint8_t *buf, int *err, + size_t data_len, size_t total_len) +{ + unsigned long flags; + int rv; + + spin_lock_irqsave(&comm_chan->lock, flags); + + if (comm_chan->rx_state != I2C_RX_INIT) { + dev_err(dev, "can only handle 1 frame per adapter at a time\n"); + rv = -EBUSY; + goto fail; + } + + if (i2c_ivc_msg_type(msg) == I2C_CLEANUP) + comm_chan->rx_state = I2C_RX_PENDING_CLEANUP; + else + comm_chan->rx_state = I2C_RX_PENDING; + + comm_chan->rcvd_data = buf; + comm_chan->data_len = data_len; + comm_chan->rcvd_err = err; + rv = _hv_i2c_ivc_send(comm_chan, msg, total_len); + if (rv < 0) { + dev_err(dev, "ivc_send failed err %d\n", rv); + goto fail; /* Redundant but safe */ + } +fail: + spin_unlock_irqrestore(&comm_chan->lock, flags); + return rv; +} + +int hv_i2c_comm_chan_cleanup(struct tegra_hv_i2c_comm_chan *comm_chan, + int cont_id) +{ + /* Using skbs for fast allocation and deallocation */ + struct sk_buff *tx_msg_skb = NULL; + struct i2c_ivc_msg *tx_msg = NULL; + int rv; + struct device *dev = comm_chan->dev; + + _hv_i2c_comm_chan_cleanup(comm_chan); + + tx_msg_skb = alloc_skb(I2C_IVC_COMMON_HEADER_LEN, GFP_KERNEL); + if (!tx_msg_skb) { + dev_err(dev, "could not allocate memory\n"); + return -ENOMEM; + } + + tx_msg = (struct i2c_ivc_msg *)skb_put(tx_msg_skb, + I2C_IVC_COMMON_HEADER_LEN); + _hv_i2c_prep_msg_generic(comm_chan->id, cont_id, tx_msg); + + i2c_ivc_msg_type(tx_msg) = I2C_CLEANUP; + rv = _hv_i2c_send_msg(dev, comm_chan, tx_msg, NULL, NULL, 0, + I2C_IVC_COMMON_HEADER_LEN); + kfree_skb(tx_msg_skb); + return rv; +} + +/* + * hv_i2c_transfer + * Send a message to the i2c server, caller is expected to wait for response + * and handle possible timeout + */ +int hv_i2c_transfer(struct tegra_hv_i2c_comm_chan *comm_chan, int cont_id, + int addr, int read, uint8_t *buf, size_t len, int *err, + int seq_no, bool more_msgs) +{ + /* Using skbs for fast allocation and deallocation */ + struct sk_buff *tx_msg_skb = NULL; + struct i2c_ivc_msg *tx_msg = NULL; + int rv; + int msg_len = I2C_IVC_COMMON_HEADER_LEN + + sizeof(struct i2c_ivc_msg_tx_rx_hdr) + len - 1; + struct device *dev = comm_chan->dev; + + tx_msg_skb = alloc_skb(msg_len, GFP_KERNEL); + if (!tx_msg_skb) { + dev_err(dev, "could not allocate memory\n"); + return -ENOMEM; + } + + tx_msg = (struct i2c_ivc_msg *)skb_put(tx_msg_skb, msg_len); + _hv_i2c_prep_msg_generic(comm_chan->id, cont_id, tx_msg); + + if (read) + i2c_ivc_msg_type(tx_msg) = I2C_READ; + else { + i2c_ivc_msg_type(tx_msg) = I2C_WRITE; + memcpy(&i2c_ivc_message_buffer(tx_msg), &(buf[1]), len-1); + len = len - 1; + } + + i2c_ivc_message_seq_nr(tx_msg) = seq_no; + i2c_ivc_message_slave_addr(tx_msg) = addr; + i2c_ivc_message_slave_reg(tx_msg) = buf[0]; + i2c_ivc_message_buf_len(tx_msg) = len; + if (more_msgs) + i2c_ivc_message_flags(tx_msg) = HV_I2C_FLAGS_REPEAT_START; + else + i2c_ivc_message_flags(tx_msg) = 0; + + rv = _hv_i2c_send_msg(dev, comm_chan, tx_msg, buf, err, len, msg_len); + kfree_skb(tx_msg_skb); + return rv; +} + +int hv_i2c_get_max_payload(struct tegra_hv_i2c_comm_chan *comm_chan, + int cont_id, uint32_t *max_payload, int *err) +{ + /* Using skbs for fast allocation and deallocation */ + struct sk_buff *tx_msg_skb = NULL; + struct i2c_ivc_msg *tx_msg = NULL; + int rv; + int msg_len = I2C_IVC_COMMON_HEADER_LEN + sizeof(*max_payload); + struct device *dev = comm_chan->dev; + + tx_msg_skb = alloc_skb(msg_len, GFP_KERNEL); + if (!tx_msg_skb) { + dev_err(dev, "could not allocate memory\n"); + return -ENOMEM; + } + + tx_msg = (struct i2c_ivc_msg *)skb_put(tx_msg_skb, msg_len); + _hv_i2c_prep_msg_generic(comm_chan->id, cont_id, tx_msg); + + i2c_ivc_msg_type(tx_msg) = I2C_GET_MAX_PAYLOAD; + i2c_ivc_max_payload_field(tx_msg) = 0; + rv = _hv_i2c_send_msg(dev, comm_chan, tx_msg, (uint8_t *)max_payload, + err, sizeof(*max_payload), msg_len); + kfree_skb(tx_msg_skb); + return rv; +} + +static void *_hv_i2c_comm_chan_alloc(i2c_isr_handler handler, void *data, + struct device *dev, struct tegra_hv_i2c_comm_dev *comm_dev) +{ + struct tegra_hv_i2c_comm_chan *comm_chan = NULL; + unsigned long flags; + void *err = NULL; + int chan_id; + + comm_chan = devm_kzalloc(dev, sizeof(*comm_chan), + GFP_KERNEL); + if (!comm_chan) { + err = ERR_PTR(-ENOMEM); + goto out; + } + + comm_chan->dev = dev; + comm_chan->rx_state = I2C_RX_INIT; + comm_chan->hv_comm_dev = comm_dev; + spin_lock_init(&comm_chan->lock); + comm_chan->handler = handler; + comm_chan->data = data; + comm_chan->ivck = comm_dev->ivck; + + spin_lock_irqsave(&comm_dev->lock, flags); + /* Find a free channel number */ + for (chan_id = 0; chan_id < MAX_COMM_CHANS; chan_id++) { + if (comm_dev->hv_comm_chan[chan_id] == NULL) + break; + } + + if (chan_id >= MAX_COMM_CHANS) { + /* No free channel available */ + err = ERR_PTR(-ENOMEM); + goto fail_cleanup; + } + comm_chan->id = chan_id; + + comm_dev->hv_comm_chan[comm_chan->id] = comm_chan; + spin_unlock_irqrestore(&comm_dev->lock, flags); + return comm_chan; +fail_cleanup: + spin_unlock_irqrestore(&comm_dev->lock, flags); + devm_kfree(dev, comm_chan); +out: + return err; +} + +void hv_i2c_comm_chan_free(struct tegra_hv_i2c_comm_chan *comm_chan) +{ + unsigned long flags; + struct tegra_hv_i2c_comm_dev *comm_dev = comm_chan->hv_comm_dev; + struct device *dev = comm_chan->dev; + + spin_lock_irqsave(&comm_dev->lock, flags); + comm_dev->hv_comm_chan[comm_chan->id] = NULL; + spin_unlock_irqrestore(&comm_dev->lock, flags); + + devm_kfree(dev, comm_chan); + +} + +static irqreturn_t hv_i2c_isr(int irq, void *dev_id) +{ + struct tegra_hv_i2c_comm_dev *comm_dev = + (struct tegra_hv_i2c_comm_dev *)dev_id; + + queue_work(system_wq, &comm_dev->work); + + return IRQ_HANDLED; +} + +static void hv_i2c_work(struct work_struct *work) +{ + /* In theory it is possible that the comm_chan referred to in the + * received message might not have been allocated yet on this side + * (although that is unlikely given that the server responds to + * messages from the client only) + */ + struct tegra_hv_i2c_comm_dev *comm_dev = + container_of(work, struct tegra_hv_i2c_comm_dev, work); + struct tegra_hv_ivc_cookie *ivck = comm_dev->ivck; + struct i2c_ivc_msg_common rx_msg_hdr; + struct i2c_ivc_msg *fake_rx_msg = (struct i2c_ivc_msg *)&rx_msg_hdr; + /* fake in the sense that this ptr does not represent the whole message, + * DO NOT use it to access anything except common header fields + */ + struct tegra_hv_i2c_comm_chan *comm_chan = NULL; + int comm_chan_id; + u32 len = 0; + + for (; tegra_hv_ivc_can_read(ivck); tegra_hv_ivc_read_advance(ivck)) { + /* Message available + * Initialize local variables to safe values + */ + comm_chan = NULL; + comm_chan_id = -1; + len = 0; + memset(&rx_msg_hdr, 0, I2C_IVC_COMMON_HEADER_LEN); + + len = tegra_hv_ivc_read_peek(ivck, &rx_msg_hdr, 0, + I2C_IVC_COMMON_HEADER_LEN); + if (len != I2C_IVC_COMMON_HEADER_LEN) { + pr_err("%s: IVC read failure (msg size error)\n", + __func__); + continue; + } + + if ((i2c_ivc_start_marker(fake_rx_msg) != 0xf005ba11) || + (i2c_ivc_end_marker(fake_rx_msg) != 0x11ab500f)) { + pr_err("%s: IVC read failure (invalid markers)\n", + __func__); + continue; + } + + comm_chan_id = i2c_ivc_chan_id(fake_rx_msg); + + if (!((comm_chan_id >= 0) && (comm_chan_id < MAX_COMM_CHANS))) { + pr_err("%s: IVC read failure (invalid comm chan id)\n", + __func__); + continue; + } + + comm_chan = comm_dev->hv_comm_chan[comm_chan_id]; + if (!comm_chan || comm_chan->id != comm_chan_id) { + pr_err("%s: Invalid channel from server %d\n", __func__, + comm_chan_id); + continue; + } + + if (comm_chan->rx_state == I2C_RX_INIT) { + dev_err(comm_chan->dev, + "Spurious message from server (channel %d)\n", + comm_chan_id); + WARN_ON(comm_chan->rcvd_data != NULL); + WARN_ON(comm_chan->data_len != 0); + WARN_ON(comm_chan->rcvd_err != NULL); + continue; + } + + if (comm_chan->rx_state == I2C_RX_PENDING) { + /* Copy the message to consumer*/ + tegra_hv_ivc_read_peek(ivck, comm_chan->rcvd_data, + _hv_i2c_get_data_ptr_offset( + i2c_ivc_msg_type(fake_rx_msg)), + comm_chan->data_len); + *comm_chan->rcvd_err = i2c_ivc_error_field(fake_rx_msg); + _hv_i2c_comm_chan_cleanup(comm_chan); + comm_chan->handler(comm_chan->data); + } else if (comm_chan->rx_state == I2C_RX_PENDING_CLEANUP) { + /* We might get some stale responses in this scenario, + * ignore those + */ + if (i2c_ivc_msg_type(fake_rx_msg) + == I2C_CLEANUP_RESPONSE) { + _hv_i2c_comm_chan_cleanup(comm_chan); + comm_chan->handler(comm_chan->data); + } else { + dev_err(comm_chan->dev, + "Stale response from server (channel %d)\n", + comm_chan_id); + } + } else { + WARN(1, "Bad channel state\n"); + } + } + + return; +} + +struct tegra_hv_i2c_comm_dev *_hv_i2c_get_comm_dev(struct device *dev, + struct device_node *hv_dn, uint32_t ivc_queue) +{ + static HLIST_HEAD(ivc_comm_devs); + static DEFINE_SPINLOCK(ivc_comm_devs_lock); + unsigned long flags = 0; + struct tegra_hv_i2c_comm_dev *comm_dev = NULL; + struct tegra_hv_ivc_cookie *ivck = NULL; + int err; + + spin_lock_irqsave(&ivc_comm_devs_lock, flags); + + hlist_for_each_entry(comm_dev, &ivc_comm_devs, list) { + if (comm_dev->queue_id == ivc_queue) + goto end; + } + + /* could not find a previously created comm_dev for this ivc + * queue, create one. + */ + + ivck = tegra_hv_ivc_reserve(hv_dn, ivc_queue, NULL); + if (IS_ERR_OR_NULL(ivck)) { + dev_err(dev, "Failed to reserve ivc queue %d\n", + ivc_queue); + comm_dev = ERR_PTR(-EINVAL); + goto end; + } + + comm_dev = devm_kzalloc(dev, sizeof(*comm_dev), + GFP_KERNEL); + if (!comm_dev) { + /* Unreserve the queue here because other controllers + * will probably try to reserve it again until one + * succeeds or all of them fail + */ + tegra_hv_ivc_unreserve(ivck); + comm_dev = ERR_PTR(-ENOMEM); + goto end; + } + + comm_dev->ivck = ivck; + comm_dev->queue_id = ivc_queue; + + spin_lock_init(&comm_dev->ivck_tx_lock); + spin_lock_init(&comm_dev->lock); + + INIT_HLIST_NODE(&comm_dev->list); + hlist_add_head(&comm_dev->list, &ivc_comm_devs); + + /* Our comm_dev is ready, so enable irq here. But channels are + * not yet allocated, we need to take care of that in the + * handler + */ + err = request_threaded_irq(ivck->irq, hv_i2c_isr, NULL, 0, + dev_name(dev), comm_dev); + if (err) { + hlist_del(&comm_dev->list); + devm_kfree(dev, comm_dev); + tegra_hv_ivc_unreserve(ivck); + comm_dev = ERR_PTR(-ENOMEM); + goto end; + } + + INIT_WORK(&comm_dev->work, hv_i2c_work); + +end: + spin_unlock_irqrestore(&ivc_comm_devs_lock, flags); + return comm_dev; +} + +void *hv_i2c_comm_init(struct device *dev, i2c_isr_handler handler, + void *data) +{ + int err; + uint32_t ivc_queue; + struct device_node *dn, *hv_dn; + struct tegra_hv_i2c_comm_dev *comm_dev = NULL; + + dn = dev->of_node; + if (dn == NULL) { + dev_err(dev, "No OF data\n"); + return ERR_PTR(-EINVAL); + } + + hv_dn = of_parse_phandle(dn, "ivc_queue", 0); + if (hv_dn == NULL) { + dev_err(dev, "Failed to parse phandle of ivc prop\n"); + return ERR_PTR(-EINVAL); + } + + err = of_property_read_u32_index(dn, "ivc_queue", 1, + &ivc_queue); + if (err != 0) { + dev_err(dev, "Failed to read IVC property ID\n"); + of_node_put(hv_dn); + return ERR_PTR(-EINVAL); + } + + comm_dev = _hv_i2c_get_comm_dev(dev, hv_dn, ivc_queue); + + if (IS_ERR_OR_NULL(comm_dev)) + return comm_dev; + + return _hv_i2c_comm_chan_alloc(handler, data, dev, comm_dev); +} diff --git a/drivers/i2c/busses/i2c-tegra-hv-common.h b/drivers/i2c/busses/i2c-tegra-hv-common.h new file mode 100644 index 000000000..b41f822c1 --- /dev/null +++ b/drivers/i2c/busses/i2c-tegra-hv-common.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#ifndef __I2C_LIB_HV_H__ +#define __I2C_LIB_HV_H__ + +#ifdef CONFIG_TEGRA_HV_MANAGER +#include +#include +#include +#include +#include + +typedef void (*i2c_isr_handler)(void *context); + +struct tegra_hv_i2c_comm_chan; + +int hv_i2c_transfer(struct tegra_hv_i2c_comm_chan *comm_chan, int cont_id, + int addr, int read, uint8_t *buf, size_t len, int *err, + int seq_no, bool more_msgs); +int hv_i2c_get_max_payload(struct tegra_hv_i2c_comm_chan *comm_chan, + int cont_id, uint32_t *max_payload, int *err); +int hv_i2c_comm_chan_cleanup(struct tegra_hv_i2c_comm_chan *comm_chan, + int cont_id); +void hv_i2c_comm_chan_free(struct tegra_hv_i2c_comm_chan *comm_chan); +void *hv_i2c_comm_init(struct device *dev, i2c_isr_handler handler, + void *data); + +#define MAX_COMM_CHANS 7 +#define COMM_CHAN_UNASSIGNED MAX_COMM_CHANS + +enum i2c_ivc_msg_t { + I2C_READ, + I2C_READ_RESPONSE, + I2C_WRITE, + I2C_WRITE_RESPONSE, + I2C_GET_MAX_PAYLOAD, + I2C_GET_MAX_PAYLOAD_RESPONSE, + I2C_CLEANUP, + I2C_CLEANUP_RESPONSE, + I2C_INVALID, +}; + +enum i2c_rx_state_t { + I2C_RX_INIT, + I2C_RX_PENDING, + I2C_RX_PENDING_CLEANUP, +}; + +#define HV_I2C_FLAGS_REPEAT_START (1<<0) + +struct i2c_ivc_msg_common { + uint32_t s_marker; + uint32_t msg_type; + int32_t comm_chan_id; + uint32_t controller_instance; + uint32_t err; + uint32_t e_marker; +}; + +struct i2c_ivc_msg_tx_rx_hdr { + int32_t seq_no; + uint32_t slave_address; + uint32_t slave_reg; + uint32_t buf_len; + uint32_t flags; +}; + +struct i2c_ivc_msg_tx_rx { + struct i2c_ivc_msg_tx_rx_hdr fixed; + uint8_t buffer[4096]; +}; + +struct i2c_ivc_msg_max_payload { + uint32_t max_payload; +}; + +struct i2c_ivc_msg { + struct i2c_ivc_msg_common hdr; + union { + struct i2c_ivc_msg_tx_rx m; + struct i2c_ivc_msg_max_payload p; + } body; +}; + +#define I2C_IVC_COMMON_HEADER_LEN sizeof(struct i2c_ivc_msg_common) + +#define i2c_ivc_start_marker(_msg_ptr) (_msg_ptr->hdr.s_marker) +#define i2c_ivc_end_marker(_msg_ptr) (_msg_ptr->hdr.e_marker) +#define i2c_ivc_chan_id(_msg_ptr) (_msg_ptr->hdr.comm_chan_id) +#define i2c_ivc_controller_instance(_msg_ptr) \ + (_msg_ptr->hdr.controller_instance) +#define i2c_ivc_msg_type(_msg_ptr) (_msg_ptr->hdr.msg_type) +#define i2c_ivc_error_field(_msg_ptr) (_msg_ptr->hdr.err) + +#define i2c_ivc_message_seq_nr(_msg_ptr) \ + (_msg_ptr->body.m.fixed.seq_no) +#define i2c_ivc_message_slave_addr(_msg_ptr) \ + (_msg_ptr->body.m.fixed.slave_address) +#define i2c_ivc_message_slave_reg(_msg_ptr) \ + (_msg_ptr->body.m.fixed.slave_reg) +#define i2c_ivc_message_buf_len(_msg_ptr) \ + (_msg_ptr->body.m.fixed.buf_len) +#define i2c_ivc_message_flags(_msg_ptr) (_msg_ptr->body.m.fixed.flags) +#define i2c_ivc_message_buffer(_msg_ptr) \ + (_msg_ptr->body.m.buffer[0]) + +#define i2c_ivc_max_payload_field(_msg_ptr) \ + (_msg_ptr->body.p.max_payload) + +struct tegra_hv_i2c_comm_dev; + +/* channel is virtual abstraction over a single ivc queue + * each channel holds messages that are independent of other channels + * we allocate one channel per i2c adapter as these can operate in parallel + */ +struct tegra_hv_i2c_comm_chan { + struct tegra_hv_ivc_cookie *ivck; + struct device *dev; + int id; + i2c_isr_handler handler; + void *data; + void *rcvd_data; + size_t data_len; + int *rcvd_err; + enum i2c_rx_state_t rx_state; + struct tegra_hv_i2c_comm_dev *hv_comm_dev; + spinlock_t lock; +}; + + +struct tegra_hv_i2c_comm_dev { + uint32_t queue_id; + struct tegra_hv_ivc_cookie *ivck; + spinlock_t ivck_tx_lock; + spinlock_t lock; + struct hlist_node list; + struct work_struct work; + struct tegra_hv_i2c_comm_chan *hv_comm_chan[MAX_COMM_CHANS]; +}; +#endif +#endif diff --git a/drivers/i2c/busses/i2c-tegra-hv.c b/drivers/i2c/busses/i2c-tegra-hv.c new file mode 100644 index 000000000..c4de07c42 --- /dev/null +++ b/drivers/i2c/busses/i2c-tegra-hv.c @@ -0,0 +1,324 @@ +/* + * drivers/i2c/busses/i2c-tegra-hv.c + * + * Copyright (C) 2015 NVIDIA Corporation. All rights reserved. + * Author: Arnab Basu + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-tegra-hv-common.h" + +#include + +#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000)) +#define TEGRA_I2C_RETRIES 3 + +#define I2C_NO_ERROR 0 + +/** + * struct tegra_hv_i2c_dev - per device i2c context + * @dev: device reference + * @adapter: core i2c layer adapter information + * @cont_id: i2c controller id + * @comm_chan: context for the logical channel over which this + * device communicates with the i2c server + * @msg_complete: transfer completion notifier + * @max_payload_size: maximum packet size supported + * by this i2c device + * @completion_timeout: time to wait for reply from server + */ +struct tegra_hv_i2c_dev { + struct device *dev; + struct i2c_adapter adapter; + int cont_id; + void *comm_chan; + struct completion msg_complete; + u32 max_payload_size; + u32 completion_timeout; +}; + +static void tegra_hv_i2c_isr(void *dev_id) +{ + struct tegra_hv_i2c_dev *i2c_dev = dev_id; + complete(&i2c_dev->msg_complete); +} + +static int tegra_hv_i2c_xfer_msg(struct tegra_hv_i2c_dev *i2c_dev, + struct i2c_msg *msg, int sno, bool more_msgs) +{ + int ret; + int msg_err; + int msg_read; + int rv; + + if (msg->len == 0) + return -EINVAL; + + if (msg->len > i2c_dev->max_payload_size) + return -E2BIG; + + msg_err = I2C_NO_ERROR; + msg_read = (msg->flags & I2C_M_RD); + INIT_COMPLETION(i2c_dev->msg_complete); + + ret = hv_i2c_transfer(i2c_dev->comm_chan, i2c_dev->cont_id, msg->addr, + msg_read, msg->buf, msg->len, &msg_err, sno, more_msgs); + if (ret < 0) { + dev_err(i2c_dev->dev, "unable to send message (%d)\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&i2c_dev->msg_complete, + i2c_dev->completion_timeout); + + if (ret == 0) { + dev_err(i2c_dev->dev, + "i2c transfer timed out, addr 0x%04x, data 0x%02x\n", + msg->addr, msg->buf[0]); + rv = -EBUSY; + goto error; + } + + dev_dbg(i2c_dev->dev, "transfer complete: %d %d %d\n", + ret, completion_done(&i2c_dev->msg_complete), msg_err); + + if (likely(msg_err == I2C_NO_ERROR)) + return 0; + + dev_dbg(i2c_dev->dev, "received error code %d\n", msg_err); + rv = -EIO; +error: + INIT_COMPLETION(i2c_dev->msg_complete); + ret = hv_i2c_comm_chan_cleanup(i2c_dev->comm_chan, i2c_dev->cont_id); + + if (ret < 0) { + dev_err(i2c_dev->dev, "Failed to send cleanup message\n"); + BUG(); + } + + ret = wait_for_completion_timeout(&i2c_dev->msg_complete, + i2c_dev->completion_timeout * 2); + if (ret == 0) { + dev_err(i2c_dev->dev, "Cleanup failed after timeout\n"); + BUG(); + } + return rv; +} + +static int tegra_hv_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + struct tegra_hv_i2c_dev *i2c_dev = i2c_get_adapdata(adap); + int i; + int ret = 0; + + for (i = 0; i < num; i++) { + ret = tegra_hv_i2c_xfer_msg(i2c_dev, &msgs[i], i, + (i < (num - 1))); + if (ret) + break; + } + + return ret ? ret : i; +} + +static u32 tegra_hv_i2c_func(struct i2c_adapter *adap) +{ + u32 ret = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + + return ret; +} + +static const struct i2c_algorithm tegra_hv_i2c_algo = { + .master_xfer = tegra_hv_i2c_xfer, + .functionality = tegra_hv_i2c_func, +}; + +static struct tegra_hv_i2c_platform_data *parse_i2c_tegra_dt( + struct platform_device *pdev) +{ + struct tegra_hv_i2c_platform_data *pdata; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + return pdata; +} + +/* Match table for of_platform binding */ +static const struct of_device_id tegra_hv_i2c_of_match[] = { + { .compatible = "nvidia,tegra124-i2c-hv", .data = NULL}, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_hv_i2c_of_match); + +static int tegra_hv_i2c_probe(struct platform_device *pdev) +{ + struct tegra_hv_i2c_dev *i2c_dev; + struct tegra_hv_i2c_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + const struct of_device_id *match; + int bus_num = -1; + void *chan; + int err; + + if (pdev->dev.of_node) { + match = of_match_device(of_match_ptr(tegra_hv_i2c_of_match), + &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Device Not matching\n"); + return -ENODEV; + } + if (!pdata) + pdata = parse_i2c_tegra_dt(pdev); + } else { + WARN(1, "Only device tree based init is supported\n"); + return -EINVAL; + } + + i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL); + if (!i2c_dev) { + dev_err(&pdev->dev, "Could not allocate struct tegra_hv_i2c_dev"); + return -ENOMEM; + } + + chan = hv_i2c_comm_init(&pdev->dev, tegra_hv_i2c_isr, i2c_dev); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + i2c_dev->dev = &pdev->dev; + i2c_dev->comm_chan = chan; + + platform_set_drvdata(pdev, i2c_dev); + + i2c_set_adapdata(&i2c_dev->adapter, i2c_dev); + i2c_dev->adapter.owner = THIS_MODULE; + i2c_dev->adapter.class = I2C_CLASS_HWMON; + strlcpy(i2c_dev->adapter.name, "Tegra I2C HV adapter", + sizeof(i2c_dev->adapter.name)); + i2c_dev->adapter.algo = &tegra_hv_i2c_algo; + i2c_dev->adapter.dev.parent = &pdev->dev; + i2c_dev->adapter.nr = bus_num; + i2c_dev->adapter.dev.of_node = pdev->dev.of_node; + + if (pdata->retries) + i2c_dev->adapter.retries = pdata->retries; + else + i2c_dev->adapter.retries = TEGRA_I2C_RETRIES; + + if (pdata->timeout) + i2c_dev->adapter.timeout = pdata->timeout; + + if (i2c_dev->adapter.timeout) + i2c_dev->completion_timeout = i2c_dev->adapter.timeout; + else + i2c_dev->completion_timeout = TEGRA_I2C_TIMEOUT; + + ret = i2c_add_numbered_adapter(&i2c_dev->adapter); + if (ret) { + dev_err(&pdev->dev, "Failed to add I2C adapter\n"); + return ret; + } + i2c_dev->cont_id = i2c_dev->adapter.nr; + init_completion(&i2c_dev->msg_complete); + INIT_COMPLETION(i2c_dev->msg_complete); + + ret = hv_i2c_get_max_payload(i2c_dev->comm_chan, i2c_dev->cont_id, + &(i2c_dev->max_payload_size), &err); + if (ret < 0) { + dev_warn(&pdev->dev, "Could not get max payload, defaulting to 4096\n"); + i2c_dev->max_payload_size = 4096; + } else { + ret = wait_for_completion_timeout(&i2c_dev->msg_complete, + i2c_dev->completion_timeout); + if (ret == 0) { + dev_warn(&pdev->dev, "Timed out getting max payload, defaulting to 4096\n"); + i2c_dev->max_payload_size = 4096; + } else if (err != I2C_NO_ERROR) { + dev_warn(&pdev->dev, "Error getting max payload, defaulting to 4096\n"); + i2c_dev->max_payload_size = 4096; + } + } + + of_i2c_register_devices(&i2c_dev->adapter); + + return 0; +} + +static int tegra_hv_i2c_remove(struct platform_device *pdev) +{ + struct tegra_hv_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + + hv_i2c_comm_chan_free(i2c_dev->comm_chan); + i2c_dev->comm_chan = NULL; + i2c_del_adapter(&i2c_dev->adapter); + return 0; +} + +static void tegra_hv_i2c_shutdown(struct platform_device *pdev) +{ + struct tegra_hv_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + + dev_info(i2c_dev->dev, "Bus is shutdown down..\n"); + i2c_shutdown_adapter(&i2c_dev->adapter); +} + +static struct platform_device_id tegra_hv_i2c_devtype[] = { + { + .name = "tegra12-hv-i2c", + .driver_data = 0, + }, + +}; + +static struct platform_driver tegra_hv_i2c_driver = { + .probe = tegra_hv_i2c_probe, + .remove = tegra_hv_i2c_remove, + .late_shutdown = tegra_hv_i2c_shutdown, + .id_table = tegra_hv_i2c_devtype, + .driver = { + .name = "tegra-hv-i2c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_hv_i2c_of_match), + .pm = NULL, + }, +}; + +static int __init tegra_hv_i2c_init_driver(void) +{ + return platform_driver_register(&tegra_hv_i2c_driver); +} + +static void __exit tegra_hv_i2c_exit_driver(void) +{ + platform_driver_unregister(&tegra_hv_i2c_driver); +} + +subsys_initcall(tegra_hv_i2c_init_driver); +module_exit(tegra_hv_i2c_exit_driver); + +MODULE_DESCRIPTION("nVidia Tegra Hypervisor I2C Bus Controller driver"); +MODULE_AUTHOR("Arnab Basu"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.2