| /* 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); |