18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2016, Linaro Ltd 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/io.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/of.h> 98c2ecf20Sopenharmony_ci#include <linux/of_address.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/rpmsg.h> 158c2ecf20Sopenharmony_ci#include <linux/idr.h> 168c2ecf20Sopenharmony_ci#include <linux/circ_buf.h> 178c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem.h> 188c2ecf20Sopenharmony_ci#include <linux/sizes.h> 198c2ecf20Sopenharmony_ci#include <linux/delay.h> 208c2ecf20Sopenharmony_ci#include <linux/regmap.h> 218c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 228c2ecf20Sopenharmony_ci#include <linux/list.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <linux/rpmsg/qcom_glink.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include "qcom_glink_native.h" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define FIFO_FULL_RESERVE 8 298c2ecf20Sopenharmony_ci#define FIFO_ALIGNMENT 8 308c2ecf20Sopenharmony_ci#define TX_BLOCKED_CMD_RESERVE 8 /* size of struct read_notif_request */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR 478 338c2ecf20Sopenharmony_ci#define SMEM_GLINK_NATIVE_XPRT_FIFO_0 479 348c2ecf20Sopenharmony_ci#define SMEM_GLINK_NATIVE_XPRT_FIFO_1 480 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct glink_smem_pipe { 378c2ecf20Sopenharmony_ci struct qcom_glink_pipe native; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci __le32 *tail; 408c2ecf20Sopenharmony_ci __le32 *head; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci void *fifo; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci int remote_pid; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#define to_smem_pipe(p) container_of(p, struct glink_smem_pipe, native) 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic size_t glink_smem_rx_avail(struct qcom_glink_pipe *np) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci struct glink_smem_pipe *pipe = to_smem_pipe(np); 528c2ecf20Sopenharmony_ci size_t len; 538c2ecf20Sopenharmony_ci void *fifo; 548c2ecf20Sopenharmony_ci u32 head; 558c2ecf20Sopenharmony_ci u32 tail; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci if (!pipe->fifo) { 588c2ecf20Sopenharmony_ci fifo = qcom_smem_get(pipe->remote_pid, 598c2ecf20Sopenharmony_ci SMEM_GLINK_NATIVE_XPRT_FIFO_1, &len); 608c2ecf20Sopenharmony_ci if (IS_ERR(fifo)) { 618c2ecf20Sopenharmony_ci pr_err("failed to acquire RX fifo handle: %ld\n", 628c2ecf20Sopenharmony_ci PTR_ERR(fifo)); 638c2ecf20Sopenharmony_ci return 0; 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci pipe->fifo = fifo; 678c2ecf20Sopenharmony_ci pipe->native.length = len; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci head = le32_to_cpu(*pipe->head); 718c2ecf20Sopenharmony_ci tail = le32_to_cpu(*pipe->tail); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (head < tail) 748c2ecf20Sopenharmony_ci return pipe->native.length - tail + head; 758c2ecf20Sopenharmony_ci else 768c2ecf20Sopenharmony_ci return head - tail; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic void glink_smem_rx_peak(struct qcom_glink_pipe *np, 808c2ecf20Sopenharmony_ci void *data, unsigned int offset, size_t count) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct glink_smem_pipe *pipe = to_smem_pipe(np); 838c2ecf20Sopenharmony_ci size_t len; 848c2ecf20Sopenharmony_ci u32 tail; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci tail = le32_to_cpu(*pipe->tail); 878c2ecf20Sopenharmony_ci tail += offset; 888c2ecf20Sopenharmony_ci if (tail >= pipe->native.length) 898c2ecf20Sopenharmony_ci tail -= pipe->native.length; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci len = min_t(size_t, count, pipe->native.length - tail); 928c2ecf20Sopenharmony_ci if (len) 938c2ecf20Sopenharmony_ci memcpy_fromio(data, pipe->fifo + tail, len); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (len != count) 968c2ecf20Sopenharmony_ci memcpy_fromio(data + len, pipe->fifo, (count - len)); 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic void glink_smem_rx_advance(struct qcom_glink_pipe *np, 1008c2ecf20Sopenharmony_ci size_t count) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct glink_smem_pipe *pipe = to_smem_pipe(np); 1038c2ecf20Sopenharmony_ci u32 tail; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci tail = le32_to_cpu(*pipe->tail); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci tail += count; 1088c2ecf20Sopenharmony_ci if (tail >= pipe->native.length) 1098c2ecf20Sopenharmony_ci tail -= pipe->native.length; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci *pipe->tail = cpu_to_le32(tail); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic size_t glink_smem_tx_avail(struct qcom_glink_pipe *np) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct glink_smem_pipe *pipe = to_smem_pipe(np); 1178c2ecf20Sopenharmony_ci u32 head; 1188c2ecf20Sopenharmony_ci u32 tail; 1198c2ecf20Sopenharmony_ci u32 avail; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci head = le32_to_cpu(*pipe->head); 1228c2ecf20Sopenharmony_ci tail = le32_to_cpu(*pipe->tail); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (tail <= head) 1258c2ecf20Sopenharmony_ci avail = pipe->native.length - head + tail; 1268c2ecf20Sopenharmony_ci else 1278c2ecf20Sopenharmony_ci avail = tail - head; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (avail < (FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE)) 1308c2ecf20Sopenharmony_ci avail = 0; 1318c2ecf20Sopenharmony_ci else 1328c2ecf20Sopenharmony_ci avail -= FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return avail; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic unsigned int glink_smem_tx_write_one(struct glink_smem_pipe *pipe, 1388c2ecf20Sopenharmony_ci unsigned int head, 1398c2ecf20Sopenharmony_ci const void *data, size_t count) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci size_t len; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci len = min_t(size_t, count, pipe->native.length - head); 1448c2ecf20Sopenharmony_ci if (len) 1458c2ecf20Sopenharmony_ci memcpy(pipe->fifo + head, data, len); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (len != count) 1488c2ecf20Sopenharmony_ci memcpy(pipe->fifo, data + len, count - len); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci head += count; 1518c2ecf20Sopenharmony_ci if (head >= pipe->native.length) 1528c2ecf20Sopenharmony_ci head -= pipe->native.length; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci return head; 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic void glink_smem_tx_write(struct qcom_glink_pipe *glink_pipe, 1588c2ecf20Sopenharmony_ci const void *hdr, size_t hlen, 1598c2ecf20Sopenharmony_ci const void *data, size_t dlen) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct glink_smem_pipe *pipe = to_smem_pipe(glink_pipe); 1628c2ecf20Sopenharmony_ci unsigned int head; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci head = le32_to_cpu(*pipe->head); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci head = glink_smem_tx_write_one(pipe, head, hdr, hlen); 1678c2ecf20Sopenharmony_ci head = glink_smem_tx_write_one(pipe, head, data, dlen); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci /* Ensure head is always aligned to 8 bytes */ 1708c2ecf20Sopenharmony_ci head = ALIGN(head, 8); 1718c2ecf20Sopenharmony_ci if (head >= pipe->native.length) 1728c2ecf20Sopenharmony_ci head -= pipe->native.length; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* Ensure ordering of fifo and head update */ 1758c2ecf20Sopenharmony_ci wmb(); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci *pipe->head = cpu_to_le32(head); 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic void qcom_glink_smem_release(struct device *dev) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci kfree(dev); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistruct qcom_glink *qcom_glink_smem_register(struct device *parent, 1868c2ecf20Sopenharmony_ci struct device_node *node) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci struct glink_smem_pipe *rx_pipe; 1898c2ecf20Sopenharmony_ci struct glink_smem_pipe *tx_pipe; 1908c2ecf20Sopenharmony_ci struct qcom_glink *glink; 1918c2ecf20Sopenharmony_ci struct device *dev; 1928c2ecf20Sopenharmony_ci u32 remote_pid; 1938c2ecf20Sopenharmony_ci __le32 *descs; 1948c2ecf20Sopenharmony_ci size_t size; 1958c2ecf20Sopenharmony_ci int ret; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 1988c2ecf20Sopenharmony_ci if (!dev) 1998c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci dev->parent = parent; 2028c2ecf20Sopenharmony_ci dev->of_node = node; 2038c2ecf20Sopenharmony_ci dev->release = qcom_glink_smem_release; 2048c2ecf20Sopenharmony_ci dev_set_name(dev, "%s:%pOFn", dev_name(parent->parent), node); 2058c2ecf20Sopenharmony_ci ret = device_register(dev); 2068c2ecf20Sopenharmony_ci if (ret) { 2078c2ecf20Sopenharmony_ci pr_err("failed to register glink edge\n"); 2088c2ecf20Sopenharmony_ci put_device(dev); 2098c2ecf20Sopenharmony_ci return ERR_PTR(ret); 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci ret = of_property_read_u32(dev->of_node, "qcom,remote-pid", 2138c2ecf20Sopenharmony_ci &remote_pid); 2148c2ecf20Sopenharmony_ci if (ret) { 2158c2ecf20Sopenharmony_ci dev_err(dev, "failed to parse qcom,remote-pid\n"); 2168c2ecf20Sopenharmony_ci goto err_put_dev; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci rx_pipe = devm_kzalloc(dev, sizeof(*rx_pipe), GFP_KERNEL); 2208c2ecf20Sopenharmony_ci tx_pipe = devm_kzalloc(dev, sizeof(*tx_pipe), GFP_KERNEL); 2218c2ecf20Sopenharmony_ci if (!rx_pipe || !tx_pipe) { 2228c2ecf20Sopenharmony_ci ret = -ENOMEM; 2238c2ecf20Sopenharmony_ci goto err_put_dev; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci ret = qcom_smem_alloc(remote_pid, 2278c2ecf20Sopenharmony_ci SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, 32); 2288c2ecf20Sopenharmony_ci if (ret && ret != -EEXIST) { 2298c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate glink descriptors\n"); 2308c2ecf20Sopenharmony_ci goto err_put_dev; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci descs = qcom_smem_get(remote_pid, 2348c2ecf20Sopenharmony_ci SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, &size); 2358c2ecf20Sopenharmony_ci if (IS_ERR(descs)) { 2368c2ecf20Sopenharmony_ci dev_err(dev, "failed to acquire xprt descriptor\n"); 2378c2ecf20Sopenharmony_ci ret = PTR_ERR(descs); 2388c2ecf20Sopenharmony_ci goto err_put_dev; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (size != 32) { 2428c2ecf20Sopenharmony_ci dev_err(dev, "glink descriptor of invalid size\n"); 2438c2ecf20Sopenharmony_ci ret = -EINVAL; 2448c2ecf20Sopenharmony_ci goto err_put_dev; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci tx_pipe->tail = &descs[0]; 2488c2ecf20Sopenharmony_ci tx_pipe->head = &descs[1]; 2498c2ecf20Sopenharmony_ci rx_pipe->tail = &descs[2]; 2508c2ecf20Sopenharmony_ci rx_pipe->head = &descs[3]; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci ret = qcom_smem_alloc(remote_pid, SMEM_GLINK_NATIVE_XPRT_FIFO_0, 2538c2ecf20Sopenharmony_ci SZ_16K); 2548c2ecf20Sopenharmony_ci if (ret && ret != -EEXIST) { 2558c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate TX fifo\n"); 2568c2ecf20Sopenharmony_ci goto err_put_dev; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci tx_pipe->fifo = qcom_smem_get(remote_pid, SMEM_GLINK_NATIVE_XPRT_FIFO_0, 2608c2ecf20Sopenharmony_ci &tx_pipe->native.length); 2618c2ecf20Sopenharmony_ci if (IS_ERR(tx_pipe->fifo)) { 2628c2ecf20Sopenharmony_ci dev_err(dev, "failed to acquire TX fifo\n"); 2638c2ecf20Sopenharmony_ci ret = PTR_ERR(tx_pipe->fifo); 2648c2ecf20Sopenharmony_ci goto err_put_dev; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci rx_pipe->native.avail = glink_smem_rx_avail; 2688c2ecf20Sopenharmony_ci rx_pipe->native.peak = glink_smem_rx_peak; 2698c2ecf20Sopenharmony_ci rx_pipe->native.advance = glink_smem_rx_advance; 2708c2ecf20Sopenharmony_ci rx_pipe->remote_pid = remote_pid; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci tx_pipe->native.avail = glink_smem_tx_avail; 2738c2ecf20Sopenharmony_ci tx_pipe->native.write = glink_smem_tx_write; 2748c2ecf20Sopenharmony_ci tx_pipe->remote_pid = remote_pid; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci *rx_pipe->tail = 0; 2778c2ecf20Sopenharmony_ci *tx_pipe->head = 0; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci glink = qcom_glink_native_probe(dev, 2808c2ecf20Sopenharmony_ci GLINK_FEATURE_INTENT_REUSE, 2818c2ecf20Sopenharmony_ci &rx_pipe->native, &tx_pipe->native, 2828c2ecf20Sopenharmony_ci false); 2838c2ecf20Sopenharmony_ci if (IS_ERR(glink)) { 2848c2ecf20Sopenharmony_ci ret = PTR_ERR(glink); 2858c2ecf20Sopenharmony_ci goto err_put_dev; 2868c2ecf20Sopenharmony_ci } 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci return glink; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cierr_put_dev: 2918c2ecf20Sopenharmony_ci device_unregister(dev); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci return ERR_PTR(ret); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_glink_smem_register); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_civoid qcom_glink_smem_unregister(struct qcom_glink *glink) 2988c2ecf20Sopenharmony_ci{ 2998c2ecf20Sopenharmony_ci qcom_glink_native_remove(glink); 3008c2ecf20Sopenharmony_ci qcom_glink_native_unregister(glink); 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_glink_smem_unregister); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>"); 3058c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm GLINK SMEM driver"); 3068c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 307