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