| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 Linaro Ltd. |
| * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> |
| */ |
| |
| #include <linux/mhi_ep.h> |
| #include "internal.h" |
| |
| size_t mhi_ep_ring_addr2offset(struct mhi_ep_ring *ring, u64 ptr) |
| { |
| return (ptr - ring->rbase) / sizeof(struct mhi_ring_element); |
| } |
| |
| static u32 mhi_ep_ring_num_elems(struct mhi_ep_ring *ring) |
| { |
| __le64 rlen; |
| |
| memcpy_fromio(&rlen, (void __iomem *) &ring->ring_ctx->generic.rlen, sizeof(u64)); |
| |
| return le64_to_cpu(rlen) / sizeof(struct mhi_ring_element); |
| } |
| |
| void mhi_ep_ring_inc_index(struct mhi_ep_ring *ring) |
| { |
| ring->rd_offset = (ring->rd_offset + 1) % ring->ring_size; |
| } |
| |
| static int __mhi_ep_cache_ring(struct mhi_ep_ring *ring, size_t end) |
| { |
| struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl; |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| struct mhi_ep_buf_info buf_info = {}; |
| size_t start; |
| int ret; |
| |
| /* Don't proceed in the case of event ring. This happens during mhi_ep_ring_start(). */ |
| if (ring->type == RING_TYPE_ER) |
| return 0; |
| |
| /* No need to cache the ring if write pointer is unmodified */ |
| if (ring->wr_offset == end) |
| return 0; |
| |
| start = ring->wr_offset; |
| if (start < end) { |
| buf_info.size = (end - start) * sizeof(struct mhi_ring_element); |
| buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element)); |
| buf_info.dev_addr = &ring->ring_cache[start]; |
| |
| ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info); |
| if (ret < 0) |
| return ret; |
| } else { |
| buf_info.size = (ring->ring_size - start) * sizeof(struct mhi_ring_element); |
| buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element)); |
| buf_info.dev_addr = &ring->ring_cache[start]; |
| |
| ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info); |
| if (ret < 0) |
| return ret; |
| |
| if (end) { |
| buf_info.host_addr = ring->rbase; |
| buf_info.dev_addr = &ring->ring_cache[0]; |
| buf_info.size = end * sizeof(struct mhi_ring_element); |
| |
| ret = mhi_cntrl->read_sync(mhi_cntrl, &buf_info); |
| if (ret < 0) |
| return ret; |
| } |
| } |
| |
| dev_dbg(dev, "Cached ring: start %zu end %zu size %zu\n", start, end, buf_info.size); |
| |
| return 0; |
| } |
| |
| static int mhi_ep_cache_ring(struct mhi_ep_ring *ring, u64 wr_ptr) |
| { |
| size_t wr_offset; |
| int ret; |
| |
| wr_offset = mhi_ep_ring_addr2offset(ring, wr_ptr); |
| |
| /* Cache the host ring till write offset */ |
| ret = __mhi_ep_cache_ring(ring, wr_offset); |
| if (ret) |
| return ret; |
| |
| ring->wr_offset = wr_offset; |
| |
| return 0; |
| } |
| |
| int mhi_ep_update_wr_offset(struct mhi_ep_ring *ring) |
| { |
| u64 wr_ptr; |
| |
| wr_ptr = mhi_ep_mmio_get_db(ring); |
| |
| return mhi_ep_cache_ring(ring, wr_ptr); |
| } |
| |
| /* TODO: Support for adding multiple ring elements to the ring */ |
| int mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ring_element *el) |
| { |
| struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl; |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| struct mhi_ep_buf_info buf_info = {}; |
| size_t old_offset = 0; |
| u32 num_free_elem; |
| __le64 rp; |
| int ret; |
| |
| ret = mhi_ep_update_wr_offset(ring); |
| if (ret) { |
| dev_err(dev, "Error updating write pointer\n"); |
| return ret; |
| } |
| |
| if (ring->rd_offset < ring->wr_offset) |
| num_free_elem = (ring->wr_offset - ring->rd_offset) - 1; |
| else |
| num_free_elem = ((ring->ring_size - ring->rd_offset) + ring->wr_offset) - 1; |
| |
| /* Check if there is space in ring for adding at least an element */ |
| if (!num_free_elem) { |
| dev_err(dev, "No space left in the ring\n"); |
| return -ENOSPC; |
| } |
| |
| old_offset = ring->rd_offset; |
| mhi_ep_ring_inc_index(ring); |
| |
| dev_dbg(dev, "Adding an element to ring at offset (%zu)\n", ring->rd_offset); |
| |
| /* Update rp in ring context */ |
| rp = cpu_to_le64(ring->rd_offset * sizeof(*el) + ring->rbase); |
| memcpy_toio((void __iomem *) &ring->ring_ctx->generic.rp, &rp, sizeof(u64)); |
| |
| buf_info.host_addr = ring->rbase + (old_offset * sizeof(*el)); |
| buf_info.dev_addr = el; |
| buf_info.size = sizeof(*el); |
| |
| return mhi_cntrl->write_sync(mhi_cntrl, &buf_info); |
| } |
| |
| void mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id) |
| { |
| ring->type = type; |
| if (ring->type == RING_TYPE_CMD) { |
| ring->db_offset_h = EP_CRDB_HIGHER; |
| ring->db_offset_l = EP_CRDB_LOWER; |
| } else if (ring->type == RING_TYPE_CH) { |
| ring->db_offset_h = CHDB_HIGHER_n(id); |
| ring->db_offset_l = CHDB_LOWER_n(id); |
| ring->ch_id = id; |
| } else { |
| ring->db_offset_h = ERDB_HIGHER_n(id); |
| ring->db_offset_l = ERDB_LOWER_n(id); |
| } |
| } |
| |
| static void mhi_ep_raise_irq(struct work_struct *work) |
| { |
| struct mhi_ep_ring *ring = container_of(work, struct mhi_ep_ring, intmodt_work.work); |
| struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl; |
| |
| mhi_cntrl->raise_irq(mhi_cntrl, ring->irq_vector); |
| WRITE_ONCE(ring->irq_pending, false); |
| } |
| |
| int mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring, |
| union mhi_ep_ring_ctx *ctx) |
| { |
| struct device *dev = &mhi_cntrl->mhi_dev->dev; |
| __le64 val; |
| int ret; |
| |
| ring->mhi_cntrl = mhi_cntrl; |
| ring->ring_ctx = ctx; |
| ring->ring_size = mhi_ep_ring_num_elems(ring); |
| memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rbase, sizeof(u64)); |
| ring->rbase = le64_to_cpu(val); |
| |
| if (ring->type == RING_TYPE_CH) |
| ring->er_index = le32_to_cpu(ring->ring_ctx->ch.erindex); |
| |
| if (ring->type == RING_TYPE_ER) { |
| ring->irq_vector = le32_to_cpu(ring->ring_ctx->ev.msivec); |
| ring->intmodt = FIELD_GET(EV_CTX_INTMODT_MASK, |
| le32_to_cpu(ring->ring_ctx->ev.intmod)); |
| |
| INIT_DELAYED_WORK(&ring->intmodt_work, mhi_ep_raise_irq); |
| } |
| |
| /* During ring init, both rp and wp are equal */ |
| memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rp, sizeof(u64)); |
| ring->rd_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val)); |
| ring->wr_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val)); |
| |
| /* Allocate ring cache memory for holding the copy of host ring */ |
| ring->ring_cache = kcalloc(ring->ring_size, sizeof(struct mhi_ring_element), GFP_KERNEL); |
| if (!ring->ring_cache) |
| return -ENOMEM; |
| |
| memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.wp, sizeof(u64)); |
| ret = mhi_ep_cache_ring(ring, le64_to_cpu(val)); |
| if (ret) { |
| dev_err(dev, "Failed to cache ring\n"); |
| kfree(ring->ring_cache); |
| return ret; |
| } |
| |
| ring->started = true; |
| |
| return 0; |
| } |
| |
| void mhi_ep_ring_reset(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring) |
| { |
| if (ring->type == RING_TYPE_ER) |
| cancel_delayed_work_sync(&ring->intmodt_work); |
| |
| ring->started = false; |
| kfree(ring->ring_cache); |
| ring->ring_cache = NULL; |
| } |