/* Copyright (c) 2015, 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 #include #include #include #include #include #include #include #include #include #include #include #include #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);