162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2022 Linaro Ltd. 462306a36Sopenharmony_ci * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/mhi_ep.h> 862306a36Sopenharmony_ci#include "internal.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_cisize_t mhi_ep_ring_addr2offset(struct mhi_ep_ring *ring, u64 ptr) 1162306a36Sopenharmony_ci{ 1262306a36Sopenharmony_ci return (ptr - ring->rbase) / sizeof(struct mhi_ring_element); 1362306a36Sopenharmony_ci} 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic u32 mhi_ep_ring_num_elems(struct mhi_ep_ring *ring) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci __le64 rlen; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci memcpy_fromio(&rlen, (void __iomem *) &ring->ring_ctx->generic.rlen, sizeof(u64)); 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci return le64_to_cpu(rlen) / sizeof(struct mhi_ring_element); 2262306a36Sopenharmony_ci} 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_civoid mhi_ep_ring_inc_index(struct mhi_ep_ring *ring) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci ring->rd_offset = (ring->rd_offset + 1) % ring->ring_size; 2762306a36Sopenharmony_ci} 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic int __mhi_ep_cache_ring(struct mhi_ep_ring *ring, size_t end) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl; 3262306a36Sopenharmony_ci struct device *dev = &mhi_cntrl->mhi_dev->dev; 3362306a36Sopenharmony_ci struct mhi_ep_buf_info buf_info = {}; 3462306a36Sopenharmony_ci size_t start; 3562306a36Sopenharmony_ci int ret; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* Don't proceed in the case of event ring. This happens during mhi_ep_ring_start(). */ 3862306a36Sopenharmony_ci if (ring->type == RING_TYPE_ER) 3962306a36Sopenharmony_ci return 0; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci /* No need to cache the ring if write pointer is unmodified */ 4262306a36Sopenharmony_ci if (ring->wr_offset == end) 4362306a36Sopenharmony_ci return 0; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci start = ring->wr_offset; 4662306a36Sopenharmony_ci if (start < end) { 4762306a36Sopenharmony_ci buf_info.size = (end - start) * sizeof(struct mhi_ring_element); 4862306a36Sopenharmony_ci buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element)); 4962306a36Sopenharmony_ci buf_info.dev_addr = &ring->ring_cache[start]; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci ret = mhi_cntrl->read_from_host(mhi_cntrl, &buf_info); 5262306a36Sopenharmony_ci if (ret < 0) 5362306a36Sopenharmony_ci return ret; 5462306a36Sopenharmony_ci } else { 5562306a36Sopenharmony_ci buf_info.size = (ring->ring_size - start) * sizeof(struct mhi_ring_element); 5662306a36Sopenharmony_ci buf_info.host_addr = ring->rbase + (start * sizeof(struct mhi_ring_element)); 5762306a36Sopenharmony_ci buf_info.dev_addr = &ring->ring_cache[start]; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci ret = mhi_cntrl->read_from_host(mhi_cntrl, &buf_info); 6062306a36Sopenharmony_ci if (ret < 0) 6162306a36Sopenharmony_ci return ret; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (end) { 6462306a36Sopenharmony_ci buf_info.host_addr = ring->rbase; 6562306a36Sopenharmony_ci buf_info.dev_addr = &ring->ring_cache[0]; 6662306a36Sopenharmony_ci buf_info.size = end * sizeof(struct mhi_ring_element); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci ret = mhi_cntrl->read_from_host(mhi_cntrl, &buf_info); 6962306a36Sopenharmony_ci if (ret < 0) 7062306a36Sopenharmony_ci return ret; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci dev_dbg(dev, "Cached ring: start %zu end %zu size %zu\n", start, end, buf_info.size); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int mhi_ep_cache_ring(struct mhi_ep_ring *ring, u64 wr_ptr) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci size_t wr_offset; 8262306a36Sopenharmony_ci int ret; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci wr_offset = mhi_ep_ring_addr2offset(ring, wr_ptr); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci /* Cache the host ring till write offset */ 8762306a36Sopenharmony_ci ret = __mhi_ep_cache_ring(ring, wr_offset); 8862306a36Sopenharmony_ci if (ret) 8962306a36Sopenharmony_ci return ret; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci ring->wr_offset = wr_offset; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ciint mhi_ep_update_wr_offset(struct mhi_ep_ring *ring) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci u64 wr_ptr; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci wr_ptr = mhi_ep_mmio_get_db(ring); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci return mhi_ep_cache_ring(ring, wr_ptr); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/* TODO: Support for adding multiple ring elements to the ring */ 10662306a36Sopenharmony_ciint mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ring_element *el) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl; 10962306a36Sopenharmony_ci struct device *dev = &mhi_cntrl->mhi_dev->dev; 11062306a36Sopenharmony_ci struct mhi_ep_buf_info buf_info = {}; 11162306a36Sopenharmony_ci size_t old_offset = 0; 11262306a36Sopenharmony_ci u32 num_free_elem; 11362306a36Sopenharmony_ci __le64 rp; 11462306a36Sopenharmony_ci int ret; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci ret = mhi_ep_update_wr_offset(ring); 11762306a36Sopenharmony_ci if (ret) { 11862306a36Sopenharmony_ci dev_err(dev, "Error updating write pointer\n"); 11962306a36Sopenharmony_ci return ret; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (ring->rd_offset < ring->wr_offset) 12362306a36Sopenharmony_ci num_free_elem = (ring->wr_offset - ring->rd_offset) - 1; 12462306a36Sopenharmony_ci else 12562306a36Sopenharmony_ci num_free_elem = ((ring->ring_size - ring->rd_offset) + ring->wr_offset) - 1; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Check if there is space in ring for adding at least an element */ 12862306a36Sopenharmony_ci if (!num_free_elem) { 12962306a36Sopenharmony_ci dev_err(dev, "No space left in the ring\n"); 13062306a36Sopenharmony_ci return -ENOSPC; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci old_offset = ring->rd_offset; 13462306a36Sopenharmony_ci mhi_ep_ring_inc_index(ring); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci dev_dbg(dev, "Adding an element to ring at offset (%zu)\n", ring->rd_offset); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* Update rp in ring context */ 13962306a36Sopenharmony_ci rp = cpu_to_le64(ring->rd_offset * sizeof(*el) + ring->rbase); 14062306a36Sopenharmony_ci memcpy_toio((void __iomem *) &ring->ring_ctx->generic.rp, &rp, sizeof(u64)); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci buf_info.host_addr = ring->rbase + (old_offset * sizeof(*el)); 14362306a36Sopenharmony_ci buf_info.dev_addr = el; 14462306a36Sopenharmony_ci buf_info.size = sizeof(*el); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return mhi_cntrl->write_to_host(mhi_cntrl, &buf_info); 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_civoid mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci ring->type = type; 15262306a36Sopenharmony_ci if (ring->type == RING_TYPE_CMD) { 15362306a36Sopenharmony_ci ring->db_offset_h = EP_CRDB_HIGHER; 15462306a36Sopenharmony_ci ring->db_offset_l = EP_CRDB_LOWER; 15562306a36Sopenharmony_ci } else if (ring->type == RING_TYPE_CH) { 15662306a36Sopenharmony_ci ring->db_offset_h = CHDB_HIGHER_n(id); 15762306a36Sopenharmony_ci ring->db_offset_l = CHDB_LOWER_n(id); 15862306a36Sopenharmony_ci ring->ch_id = id; 15962306a36Sopenharmony_ci } else { 16062306a36Sopenharmony_ci ring->db_offset_h = ERDB_HIGHER_n(id); 16162306a36Sopenharmony_ci ring->db_offset_l = ERDB_LOWER_n(id); 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ciint mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring, 16662306a36Sopenharmony_ci union mhi_ep_ring_ctx *ctx) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct device *dev = &mhi_cntrl->mhi_dev->dev; 16962306a36Sopenharmony_ci __le64 val; 17062306a36Sopenharmony_ci int ret; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci ring->mhi_cntrl = mhi_cntrl; 17362306a36Sopenharmony_ci ring->ring_ctx = ctx; 17462306a36Sopenharmony_ci ring->ring_size = mhi_ep_ring_num_elems(ring); 17562306a36Sopenharmony_ci memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rbase, sizeof(u64)); 17662306a36Sopenharmony_ci ring->rbase = le64_to_cpu(val); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (ring->type == RING_TYPE_CH) 17962306a36Sopenharmony_ci ring->er_index = le32_to_cpu(ring->ring_ctx->ch.erindex); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (ring->type == RING_TYPE_ER) 18262306a36Sopenharmony_ci ring->irq_vector = le32_to_cpu(ring->ring_ctx->ev.msivec); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* During ring init, both rp and wp are equal */ 18562306a36Sopenharmony_ci memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.rp, sizeof(u64)); 18662306a36Sopenharmony_ci ring->rd_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val)); 18762306a36Sopenharmony_ci ring->wr_offset = mhi_ep_ring_addr2offset(ring, le64_to_cpu(val)); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci /* Allocate ring cache memory for holding the copy of host ring */ 19062306a36Sopenharmony_ci ring->ring_cache = kcalloc(ring->ring_size, sizeof(struct mhi_ring_element), GFP_KERNEL); 19162306a36Sopenharmony_ci if (!ring->ring_cache) 19262306a36Sopenharmony_ci return -ENOMEM; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci memcpy_fromio(&val, (void __iomem *) &ring->ring_ctx->generic.wp, sizeof(u64)); 19562306a36Sopenharmony_ci ret = mhi_ep_cache_ring(ring, le64_to_cpu(val)); 19662306a36Sopenharmony_ci if (ret) { 19762306a36Sopenharmony_ci dev_err(dev, "Failed to cache ring\n"); 19862306a36Sopenharmony_ci kfree(ring->ring_cache); 19962306a36Sopenharmony_ci return ret; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci ring->started = true; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return 0; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_civoid mhi_ep_ring_reset(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci ring->started = false; 21062306a36Sopenharmony_ci kfree(ring->ring_cache); 21162306a36Sopenharmony_ci ring->ring_cache = NULL; 21262306a36Sopenharmony_ci} 213