// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "qcom_glink_native.h" #define FIFO_FULL_RESERVE 8 #define FIFO_ALIGNMENT 8 #define TX_BLOCKED_CMD_RESERVE 8 /* size of struct read_notif_request */ #define SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR 478 #define SPSS_TX_FIFO_SIZE SZ_2K #define SPSS_RX_FIFO_SIZE SZ_2K struct glink_spss_cfg { __le32 tx_tail; __le32 tx_head; __le32 tx_fifo_size; __le32 rx_tail; __le32 rx_head; __le32 rx_fifo_size; }; struct glink_spss_pipe { struct qcom_glink_pipe native; __le32 *tail; __le32 *head; void *fifo; int remote_pid; }; #define to_spss_pipe(p) container_of(p, struct glink_spss_pipe, native) static void glink_spss_reset(struct qcom_glink_pipe *np) { struct glink_spss_pipe *pipe = to_spss_pipe(np); *pipe->head = cpu_to_le32(0); *pipe->tail = cpu_to_le32(0); } static size_t glink_spss_rx_avail(struct qcom_glink_pipe *np) { struct glink_spss_pipe *pipe = to_spss_pipe(np); u32 head; u32 tail; head = le32_to_cpu(*pipe->head); tail = le32_to_cpu(*pipe->tail); if (head < tail) return pipe->native.length - tail + head; else return head - tail; } static void glink_spss_rx_peak(struct qcom_glink_pipe *np, void *data, unsigned int offset, size_t count) { struct glink_spss_pipe *pipe = to_spss_pipe(np); size_t len; u32 tail; tail = le32_to_cpu(*pipe->tail); tail += offset; if (tail >= pipe->native.length) tail -= pipe->native.length; len = min_t(size_t, count, pipe->native.length - tail); if (len) memcpy_fromio(data, pipe->fifo + tail, len); if (len != count) memcpy_fromio(data + len, pipe->fifo, count - len); } static void glink_spss_rx_advance(struct qcom_glink_pipe *np, size_t count) { struct glink_spss_pipe *pipe = to_spss_pipe(np); u32 tail; tail = le32_to_cpu(*pipe->tail); tail += count; if (tail >= pipe->native.length) tail -= pipe->native.length; *pipe->tail = cpu_to_le32(tail); } static size_t glink_spss_tx_avail(struct qcom_glink_pipe *np) { struct glink_spss_pipe *pipe = to_spss_pipe(np); u32 head; u32 tail; u32 avail; head = le32_to_cpu(*pipe->head); tail = le32_to_cpu(*pipe->tail); if (tail <= head) avail = pipe->native.length - head + tail; else avail = tail - head; if (avail < (FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE)) avail = 0; else avail -= FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE; return avail; } static unsigned int glink_spss_tx_write_one(struct glink_spss_pipe *pipe, unsigned int head, const void *data, size_t count) { size_t len; len = min_t(size_t, count, pipe->native.length - head); if (len) memcpy(pipe->fifo + head, data, len); if (len != count) memcpy(pipe->fifo, data + len, count - len); head += count; if (head >= pipe->native.length) head -= pipe->native.length; return head; } static void glink_spss_tx_write(struct qcom_glink_pipe *glink_pipe, const void *hdr, size_t hlen, const void *data, size_t dlen) { struct glink_spss_pipe *pipe = to_spss_pipe(glink_pipe); unsigned int head; head = le32_to_cpu(*pipe->head); head = glink_spss_tx_write_one(pipe, head, hdr, hlen); head = glink_spss_tx_write_one(pipe, head, data, dlen); /* Ensure head is always aligned to 8 bytes */ head = ALIGN(head, 8); if (head >= pipe->native.length) head -= pipe->native.length; /* Ensure ordering of fifo and head update */ wmb(); *pipe->head = cpu_to_le32(head); } static void qcom_glink_spss_release(struct device *dev) { kfree(dev); } static int glink_spss_advertise_cfg(struct device *dev, u32 size, phys_addr_t addr) { struct device_node *np = dev->of_node; __le64 __iomem *spss_addr; __le32 __iomem *spss_size; struct resource addr_r; struct resource size_r; int addr_idx; int size_idx; addr_idx = of_property_match_string(np, "reg-names", "qcom,spss-addr"); size_idx = of_property_match_string(np, "reg-names", "qcom,spss-size"); if (addr_idx < 0 || size_idx < 0) { dev_err(dev, "failed to find location registers\n"); return -EINVAL; } if (of_address_to_resource(np, addr_idx, &addr_r)) return -ENOMEM; spss_addr = devm_ioremap(dev, addr_r.start, resource_size(&addr_r)); if (IS_ERR_OR_NULL(spss_addr)) { dev_err(dev, "failed to map spss addr resource\n"); return -ENOMEM; } if (of_address_to_resource(np, size_idx, &size_r)) return -ENOMEM; spss_size = devm_ioremap(dev, size_r.start, resource_size(&size_r)); if (IS_ERR_OR_NULL(spss_size)) { dev_err(dev, "failed to map spss size resource\n"); return -ENOMEM; } *spss_addr = cpu_to_le64(addr); *spss_size = cpu_to_le32(size); devm_iounmap(dev, spss_addr); devm_iounmap(dev, spss_size); return 0; } struct qcom_glink *qcom_glink_spss_register(struct device *parent, struct device_node *node) { struct glink_spss_pipe *rx_pipe; struct glink_spss_pipe *tx_pipe; struct glink_spss_cfg *cfg; struct qcom_glink *glink; struct device *dev; u32 remote_pid; size_t tx_size; size_t rx_size; size_t size; int ret; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return ERR_PTR(-ENOMEM); dev->parent = parent; dev->of_node = node; dev->release = qcom_glink_spss_release; dev_set_name(dev, "%s:%s", node->parent->name, node->name); ret = device_register(dev); if (ret) { pr_err("failed to register glink edge %s\n", node->name); return ERR_PTR(ret); } ret = of_property_read_u32(dev->of_node, "qcom,remote-pid", &remote_pid); if (ret) { dev_err(dev, "failed to parse qcom,remote-pid\n"); goto err_put_dev; } rx_pipe = devm_kzalloc(dev, sizeof(*rx_pipe), GFP_KERNEL); tx_pipe = devm_kzalloc(dev, sizeof(*tx_pipe), GFP_KERNEL); if (!rx_pipe || !tx_pipe) { ret = -ENOMEM; goto err_put_dev; } tx_size = SPSS_TX_FIFO_SIZE; rx_size = SPSS_RX_FIFO_SIZE; size = tx_size + rx_size + sizeof(*cfg); ret = qcom_smem_alloc(remote_pid, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, size); if (ret && ret != -EEXIST) { dev_err(dev, "failed to allocate glink descriptors\n"); goto err_put_dev; } cfg = qcom_smem_get(remote_pid, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, &size); if (IS_ERR(cfg)) { dev_err(dev, "failed to acquire xprt descriptor\n"); ret = PTR_ERR(cfg); goto err_put_dev; } if (size != tx_size + rx_size + sizeof(*cfg)) { dev_err(dev, "glink descriptor of invalid size\n"); ret = -EINVAL; goto err_put_dev; } cfg->tx_fifo_size = cpu_to_le32(tx_size); cfg->rx_fifo_size = cpu_to_le32(rx_size); tx_pipe->tail = &cfg->tx_tail; tx_pipe->head = &cfg->tx_head; tx_pipe->native.length = tx_size; tx_pipe->fifo = (u8 *)cfg + sizeof(*cfg); rx_pipe->tail = &cfg->rx_tail; rx_pipe->head = &cfg->rx_head; rx_pipe->native.length = rx_size; rx_pipe->fifo = (u8 *)cfg + sizeof(*cfg) + tx_size; rx_pipe->native.avail = glink_spss_rx_avail; rx_pipe->native.peak = glink_spss_rx_peak; rx_pipe->native.advance = glink_spss_rx_advance; rx_pipe->native.reset = glink_spss_reset; rx_pipe->remote_pid = remote_pid; tx_pipe->native.avail = glink_spss_tx_avail; tx_pipe->native.write = glink_spss_tx_write; tx_pipe->native.reset = glink_spss_reset; tx_pipe->remote_pid = remote_pid; *rx_pipe->tail = 0; *tx_pipe->head = 0; ret = glink_spss_advertise_cfg(dev, size, qcom_smem_virt_to_phys(cfg)); if (ret) goto err_put_dev; glink = qcom_glink_native_probe(dev, GLINK_FEATURE_INTENT_REUSE, &rx_pipe->native, &tx_pipe->native, false); if (IS_ERR(glink)) { ret = PTR_ERR(glink); goto err_put_dev; } return glink; err_put_dev: put_device(dev); return ERR_PTR(ret); } EXPORT_SYMBOL(qcom_glink_spss_register); void qcom_glink_spss_unregister(struct qcom_glink *glink) { qcom_glink_native_remove(glink); qcom_glink_native_unregister(glink); } EXPORT_SYMBOL(qcom_glink_spss_unregister); MODULE_DESCRIPTION("QTI GLINK SPSS driver"); MODULE_LICENSE("GPL v2");