blob: 3007b5ae8a34387def663e179986d8bc6c422159 [file] [log] [blame]
/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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/kernel.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/platform_device.h>
#include "mhi.h"
static uint32_t mhi_dev_ring_addr2ofst(struct mhi_dev_ring *ring, uint64_t p)
{
uint64_t rbase;
rbase = ring->ring_ctx->generic.rbase;
return (p - rbase)/sizeof(union mhi_dev_ring_element_type);
}
static uint32_t mhi_dev_ring_num_elems(struct mhi_dev_ring *ring)
{
return ring->ring_ctx->generic.rlen/
sizeof(union mhi_dev_ring_element_type);
}
/* fetch ring elements from stat->end, take care of wrap-around case */
int mhi_dev_fetch_ring_elements(struct mhi_dev_ring *ring,
uint32_t start, uint32_t end)
{
struct mhi_addr host_addr;
host_addr.device_pa = ring->ring_shadow.device_pa
+ sizeof(union mhi_dev_ring_element_type) * start;
host_addr.device_va = ring->ring_shadow.device_va
+ sizeof(union mhi_dev_ring_element_type) * start;
host_addr.host_pa = ring->ring_shadow.host_pa
+ sizeof(union mhi_dev_ring_element_type) * start;
if (start < end) {
mhi_dev_read_from_host(&host_addr,
(ring->ring_cache_dma_handle +
sizeof(union mhi_dev_ring_element_type) * start),
(end-start) *
sizeof(union mhi_dev_ring_element_type));
} else if (start > end) {
/* copy from 'start' to ring end, then ring start to 'end'*/
mhi_dev_read_from_host(&host_addr,
(ring->ring_cache_dma_handle +
sizeof(union mhi_dev_ring_element_type) * start),
(ring->ring_size-start) *
sizeof(union mhi_dev_ring_element_type));
if (end) {
/* wrapped around */
host_addr.device_pa = ring->ring_shadow.device_pa;
host_addr.device_va = ring->ring_shadow.device_va;
host_addr.host_pa = ring->ring_shadow.host_pa;
mhi_dev_read_from_host(&host_addr,
(ring->ring_cache_dma_handle +
sizeof(union mhi_dev_ring_element_type) *
start),
end * sizeof(union mhi_dev_ring_element_type));
}
}
return 0;
}
int mhi_dev_cache_ring(struct mhi_dev_ring *ring, uint32_t wr_offset)
{
uint32_t old_offset = 0;
struct mhi_dev *mhi_ctx;
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return -EINVAL;
}
mhi_ctx = ring->mhi_dev;
if (ring->wr_offset == wr_offset) {
mhi_log(MHI_MSG_INFO,
"nothing to cache for ring %d, local wr_ofst %d\n",
ring->id, ring->wr_offset);
mhi_log(MHI_MSG_INFO,
"new wr_offset %d\n", wr_offset);
return 0;
}
old_offset = ring->wr_offset;
mhi_log(MHI_MSG_ERROR,
"caching - rng size :%d local ofst:%d new ofst: %d\n",
(uint32_t) ring->ring_size, old_offset,
ring->wr_offset);
/*
* copy the elements starting from old_offset to wr_offset
* take in to account wrap around case event rings are not
* cached, not required
*/
if (ring->id >= mhi_ctx->ev_ring_start &&
ring->id < (mhi_ctx->ev_ring_start +
mhi_ctx->cfg.event_rings)) {
mhi_log(MHI_MSG_ERROR,
"not caching event ring %d\n", ring->id);
return 0;
}
mhi_log(MHI_MSG_ERROR, "caching ring %d, start %d, end %d\n",
ring->id, old_offset, wr_offset);
if (mhi_dev_fetch_ring_elements(ring, old_offset, wr_offset)) {
mhi_log(MHI_MSG_ERROR,
"failed to fetch elements for ring %d, start %d, end %d\n",
ring->id, old_offset, wr_offset);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(mhi_dev_cache_ring);
int mhi_dev_update_wr_offset(struct mhi_dev_ring *ring)
{
uint64_t wr_offset = 0;
uint32_t new_wr_offset = 0;
int32_t rc = 0;
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return -EINVAL;
}
switch (ring->type) {
case RING_TYPE_CMD:
rc = mhi_dev_mmio_get_cmd_db(ring, &wr_offset);
if (rc) {
pr_err("%s: CMD DB read failed\n", __func__);
return rc;
}
mhi_log(MHI_MSG_ERROR,
"ring %d wr_offset from db 0x%x\n",
ring->id, (uint32_t) wr_offset);
break;
case RING_TYPE_ER:
rc = mhi_dev_mmio_get_erc_db(ring, &wr_offset);
if (rc) {
pr_err("%s: EVT DB read failed\n", __func__);
return rc;
}
break;
case RING_TYPE_CH:
rc = mhi_dev_mmio_get_ch_db(ring, &wr_offset);
if (rc) {
pr_err("%s: CH DB read failed\n", __func__);
return rc;
}
mhi_log(MHI_MSG_ERROR,
"ring %d wr_offset from db 0x%x\n",
ring->id, (uint32_t) wr_offset);
break;
default:
mhi_log(MHI_MSG_ERROR, "invalid ring type\n");
return -EINVAL;
}
new_wr_offset = mhi_dev_ring_addr2ofst(ring, wr_offset);
mhi_dev_cache_ring(ring, new_wr_offset);
ring->wr_offset = new_wr_offset;
return 0;
}
EXPORT_SYMBOL(mhi_dev_update_wr_offset);
int mhi_dev_process_ring_element(struct mhi_dev_ring *ring, uint32_t offset)
{
union mhi_dev_ring_element_type *el;
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return -EINVAL;
}
/* get the element and invoke the respective callback */
el = &ring->ring_cache[offset];
if (ring->ring_cb)
ring->ring_cb(ring->mhi_dev, el, (void *)ring);
else
mhi_log(MHI_MSG_INFO, "No callback registered for ring %d\n",
ring->id);
return 0;
}
EXPORT_SYMBOL(mhi_dev_process_ring_element);
int mhi_dev_process_ring(struct mhi_dev_ring *ring)
{
int rc = 0;
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return -EINVAL;
}
rc = mhi_dev_update_wr_offset(ring);
if (rc) {
mhi_log(MHI_MSG_ERROR,
"Error updating write-offset for ring %d\n",
ring->id);
return rc;
}
if (ring->type == RING_TYPE_CH) {
/* notify the clients that there are elements in the ring */
rc = mhi_dev_process_ring_element(ring, ring->rd_offset);
if (rc)
pr_err("Error fetching elements\n");
return rc;
}
while (ring->rd_offset != ring->wr_offset) {
rc = mhi_dev_process_ring_element(ring, ring->rd_offset);
if (rc) {
mhi_log(MHI_MSG_ERROR,
"Error processing ring (%d) element (%d)\n",
ring->id, ring->rd_offset);
return rc;
}
mhi_log(MHI_MSG_ERROR,
"Processing ring (%d) rd_offset:%d, wr_offset:%d\n",
ring->id, ring->rd_offset, ring->wr_offset);
mhi_dev_ring_inc_index(ring, ring->rd_offset);
}
if (!(ring->rd_offset == ring->wr_offset)) {
mhi_log(MHI_MSG_ERROR,
"Error with the rd offset/wr offset\n");
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(mhi_dev_process_ring);
int mhi_dev_add_element(struct mhi_dev_ring *ring,
union mhi_dev_ring_element_type *element)
{
uint32_t old_offset = 0;
struct mhi_addr host_addr;
if (!ring || !element) {
pr_err("%s: Invalid context\n", __func__);
return -EINVAL;
}
mhi_dev_update_wr_offset(ring);
if ((ring->rd_offset + 1) % ring->ring_size == ring->wr_offset) {
mhi_log(MHI_MSG_INFO, "ring full to insert element\n");
return -EINVAL;
}
old_offset = ring->rd_offset;
mhi_dev_ring_inc_index(ring, ring->rd_offset);
ring->ring_ctx->generic.rp = (ring->rd_offset *
sizeof(union mhi_dev_ring_element_type)) +
ring->ring_ctx->generic.rbase;
/*
* Write the element, ring_base has to be the
* iomap of the ring_base for memcpy
*/
host_addr.host_pa = ring->ring_shadow.host_pa +
sizeof(union mhi_dev_ring_element_type) * old_offset;
host_addr.device_va = ring->ring_shadow.device_va +
sizeof(union mhi_dev_ring_element_type) * old_offset;
mhi_log(MHI_MSG_ERROR, "adding element to ring (%d)\n", ring->id);
mhi_log(MHI_MSG_ERROR, "rd_ofset %d\n", ring->rd_offset);
mhi_log(MHI_MSG_ERROR, "type %d\n", element->generic.type);
mhi_dev_write_to_host(&host_addr, element,
sizeof(union mhi_dev_ring_element_type), ring->mhi_dev);
return 0;
}
EXPORT_SYMBOL(mhi_dev_add_element);
int mhi_ring_start(struct mhi_dev_ring *ring, union mhi_dev_ring_ctx *ctx,
struct mhi_dev *mhi)
{
int rc = 0;
uint32_t wr_offset = 0;
uint32_t offset = 0;
if (!ring || !ctx || !mhi) {
pr_err("%s: Invalid context\n", __func__);
return -EINVAL;
}
ring->ring_ctx = ctx;
ring->ring_size = mhi_dev_ring_num_elems(ring);
ring->rd_offset = mhi_dev_ring_addr2ofst(ring,
ring->ring_ctx->generic.rp);
ring->wr_offset = mhi_dev_ring_addr2ofst(ring,
ring->ring_ctx->generic.rp);
ring->mhi_dev = mhi;
mhi_ring_set_state(ring, RING_STATE_IDLE);
wr_offset = mhi_dev_ring_addr2ofst(ring,
ring->ring_ctx->generic.wp);
ring->ring_cache = dma_alloc_coherent(mhi->dev,
ring->ring_size *
sizeof(union mhi_dev_ring_element_type),
&ring->ring_cache_dma_handle,
GFP_KERNEL);
if (!ring->ring_cache)
return -ENOMEM;
offset = (uint32_t)(ring->ring_ctx->generic.rbase -
mhi->ctrl_base.host_pa);
ring->ring_shadow.device_pa = mhi->ctrl_base.device_pa + offset;
ring->ring_shadow.device_va = mhi->ctrl_base.device_va + offset;
ring->ring_shadow.host_pa = mhi->ctrl_base.host_pa + offset;
if (ring->type == RING_TYPE_ER)
ring->ring_ctx_shadow =
(union mhi_dev_ring_ctx *) (mhi->ev_ctx_shadow.device_va +
(ring->id - mhi->ev_ring_start) *
sizeof(union mhi_dev_ring_ctx));
else if (ring->type == RING_TYPE_CMD)
ring->ring_ctx_shadow =
(union mhi_dev_ring_ctx *) mhi->cmd_ctx_shadow.device_va;
else if (ring->type == RING_TYPE_CH)
ring->ring_ctx_shadow =
(union mhi_dev_ring_ctx *) (mhi->ch_ctx_shadow.device_va +
(ring->id - mhi->ch_ring_start)*sizeof(union mhi_dev_ring_ctx));
ring->ring_ctx_shadow = ring->ring_ctx;
if (ring->type != RING_TYPE_ER) {
rc = mhi_dev_cache_ring(ring, wr_offset);
if (rc)
return rc;
}
mhi_log(MHI_MSG_ERROR, "ctx ring_base:0x%x, rp:0x%x, wp:0x%x\n",
(uint32_t)ring->ring_ctx->generic.rbase,
(uint32_t)ring->ring_ctx->generic.rp,
(uint32_t)ring->ring_ctx->generic.wp);
ring->wr_offset = wr_offset;
return rc;
}
EXPORT_SYMBOL(mhi_ring_start);
void mhi_ring_init(struct mhi_dev_ring *ring, enum mhi_dev_ring_type type,
int id)
{
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return;
}
ring->id = id;
ring->state = RING_STATE_UINT;
ring->ring_cb = NULL;
ring->type = type;
}
EXPORT_SYMBOL(mhi_ring_init);
void mhi_ring_set_cb(struct mhi_dev_ring *ring,
void (*ring_cb)(struct mhi_dev *dev,
union mhi_dev_ring_element_type *el, void *ctx))
{
if (!ring || !ring_cb) {
pr_err("%s: Invalid context\n", __func__);
return;
}
ring->ring_cb = ring_cb;
}
EXPORT_SYMBOL(mhi_ring_set_cb);
void mhi_ring_set_state(struct mhi_dev_ring *ring,
enum mhi_dev_ring_state state)
{
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return;
}
if (state > RING_STATE_PENDING) {
pr_err("%s: Invalid ring state\n", __func__);
return;
}
ring->state = state;
}
EXPORT_SYMBOL(mhi_ring_set_state);
enum mhi_dev_ring_state mhi_ring_get_state(struct mhi_dev_ring *ring)
{
if (!ring) {
pr_err("%s: Invalid ring context\n", __func__);
return -EINVAL;
}
return ring->state;
}
EXPORT_SYMBOL(mhi_ring_get_state);