18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications AB.
48c2ecf20Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
88c2ecf20Sopenharmony_ci#include <linux/io.h>
98c2ecf20Sopenharmony_ci#include <linux/mailbox_client.h>
108c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
138c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/regmap.h>
168c2ecf20Sopenharmony_ci#include <linux/sched.h>
178c2ecf20Sopenharmony_ci#include <linux/sizes.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem.h>
208c2ecf20Sopenharmony_ci#include <linux/wait.h>
218c2ecf20Sopenharmony_ci#include <linux/rpmsg.h>
228c2ecf20Sopenharmony_ci#include <linux/rpmsg/qcom_smd.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include "rpmsg_internal.h"
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/*
278c2ecf20Sopenharmony_ci * The Qualcomm Shared Memory communication solution provides point-to-point
288c2ecf20Sopenharmony_ci * channels for clients to send and receive streaming or packet based data.
298c2ecf20Sopenharmony_ci *
308c2ecf20Sopenharmony_ci * Each channel consists of a control item (channel info) and a ring buffer
318c2ecf20Sopenharmony_ci * pair. The channel info carry information related to channel state, flow
328c2ecf20Sopenharmony_ci * control and the offsets within the ring buffer.
338c2ecf20Sopenharmony_ci *
348c2ecf20Sopenharmony_ci * All allocated channels are listed in an allocation table, identifying the
358c2ecf20Sopenharmony_ci * pair of items by name, type and remote processor.
368c2ecf20Sopenharmony_ci *
378c2ecf20Sopenharmony_ci * Upon creating a new channel the remote processor allocates channel info and
388c2ecf20Sopenharmony_ci * ring buffer items from the smem heap and populate the allocation table. An
398c2ecf20Sopenharmony_ci * interrupt is sent to the other end of the channel and a scan for new
408c2ecf20Sopenharmony_ci * channels should be done. A channel never goes away, it will only change
418c2ecf20Sopenharmony_ci * state.
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * The remote processor signals it intent for bring up the communication
448c2ecf20Sopenharmony_ci * channel by setting the state of its end of the channel to "opening" and
458c2ecf20Sopenharmony_ci * sends out an interrupt. We detect this change and register a smd device to
468c2ecf20Sopenharmony_ci * consume the channel. Upon finding a consumer we finish the handshake and the
478c2ecf20Sopenharmony_ci * channel is up.
488c2ecf20Sopenharmony_ci *
498c2ecf20Sopenharmony_ci * Upon closing a channel, the remote processor will update the state of its
508c2ecf20Sopenharmony_ci * end of the channel and signal us, we will then unregister any attached
518c2ecf20Sopenharmony_ci * device and close our end of the channel.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * Devices attached to a channel can use the qcom_smd_send function to push
548c2ecf20Sopenharmony_ci * data to the channel, this is done by copying the data into the tx ring
558c2ecf20Sopenharmony_ci * buffer, updating the pointers in the channel info and signaling the remote
568c2ecf20Sopenharmony_ci * processor.
578c2ecf20Sopenharmony_ci *
588c2ecf20Sopenharmony_ci * The remote processor does the equivalent when it transfer data and upon
598c2ecf20Sopenharmony_ci * receiving the interrupt we check the channel info for new data and delivers
608c2ecf20Sopenharmony_ci * this to the attached device. If the device is not ready to receive the data
618c2ecf20Sopenharmony_ci * we leave it in the ring buffer for now.
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistruct smd_channel_info;
658c2ecf20Sopenharmony_cistruct smd_channel_info_pair;
668c2ecf20Sopenharmony_cistruct smd_channel_info_word;
678c2ecf20Sopenharmony_cistruct smd_channel_info_word_pair;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci#define SMD_ALLOC_TBL_COUNT	2
728c2ecf20Sopenharmony_ci#define SMD_ALLOC_TBL_SIZE	64
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci/*
758c2ecf20Sopenharmony_ci * This lists the various smem heap items relevant for the allocation table and
768c2ecf20Sopenharmony_ci * smd channel entries.
778c2ecf20Sopenharmony_ci */
788c2ecf20Sopenharmony_cistatic const struct {
798c2ecf20Sopenharmony_ci	unsigned alloc_tbl_id;
808c2ecf20Sopenharmony_ci	unsigned info_base_id;
818c2ecf20Sopenharmony_ci	unsigned fifo_base_id;
828c2ecf20Sopenharmony_ci} smem_items[SMD_ALLOC_TBL_COUNT] = {
838c2ecf20Sopenharmony_ci	{
848c2ecf20Sopenharmony_ci		.alloc_tbl_id = 13,
858c2ecf20Sopenharmony_ci		.info_base_id = 14,
868c2ecf20Sopenharmony_ci		.fifo_base_id = 338
878c2ecf20Sopenharmony_ci	},
888c2ecf20Sopenharmony_ci	{
898c2ecf20Sopenharmony_ci		.alloc_tbl_id = 266,
908c2ecf20Sopenharmony_ci		.info_base_id = 138,
918c2ecf20Sopenharmony_ci		.fifo_base_id = 202,
928c2ecf20Sopenharmony_ci	},
938c2ecf20Sopenharmony_ci};
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci/**
968c2ecf20Sopenharmony_ci * struct qcom_smd_edge - representing a remote processor
978c2ecf20Sopenharmony_ci * @dev:		device associated with this edge
988c2ecf20Sopenharmony_ci * @name:		name of this edge
998c2ecf20Sopenharmony_ci * @of_node:		of_node handle for information related to this edge
1008c2ecf20Sopenharmony_ci * @edge_id:		identifier of this edge
1018c2ecf20Sopenharmony_ci * @remote_pid:		identifier of remote processor
1028c2ecf20Sopenharmony_ci * @irq:		interrupt for signals on this edge
1038c2ecf20Sopenharmony_ci * @ipc_regmap:		regmap handle holding the outgoing ipc register
1048c2ecf20Sopenharmony_ci * @ipc_offset:		offset within @ipc_regmap of the register for ipc
1058c2ecf20Sopenharmony_ci * @ipc_bit:		bit in the register at @ipc_offset of @ipc_regmap
1068c2ecf20Sopenharmony_ci * @mbox_client:	mailbox client handle
1078c2ecf20Sopenharmony_ci * @mbox_chan:		apcs ipc mailbox channel handle
1088c2ecf20Sopenharmony_ci * @channels:		list of all channels detected on this edge
1098c2ecf20Sopenharmony_ci * @channels_lock:	guard for modifications of @channels
1108c2ecf20Sopenharmony_ci * @allocated:		array of bitmaps representing already allocated channels
1118c2ecf20Sopenharmony_ci * @smem_available:	last available amount of smem triggering a channel scan
1128c2ecf20Sopenharmony_ci * @new_channel_event:	wait queue for new channel events
1138c2ecf20Sopenharmony_ci * @scan_work:		work item for discovering new channels
1148c2ecf20Sopenharmony_ci * @state_work:		work item for edge state changes
1158c2ecf20Sopenharmony_ci */
1168c2ecf20Sopenharmony_cistruct qcom_smd_edge {
1178c2ecf20Sopenharmony_ci	struct device dev;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	const char *name;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	struct device_node *of_node;
1228c2ecf20Sopenharmony_ci	unsigned edge_id;
1238c2ecf20Sopenharmony_ci	unsigned remote_pid;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	int irq;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	struct regmap *ipc_regmap;
1288c2ecf20Sopenharmony_ci	int ipc_offset;
1298c2ecf20Sopenharmony_ci	int ipc_bit;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	struct mbox_client mbox_client;
1328c2ecf20Sopenharmony_ci	struct mbox_chan *mbox_chan;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	struct list_head channels;
1358c2ecf20Sopenharmony_ci	spinlock_t channels_lock;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	unsigned smem_available;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	wait_queue_head_t new_channel_event;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	struct work_struct scan_work;
1448c2ecf20Sopenharmony_ci	struct work_struct state_work;
1458c2ecf20Sopenharmony_ci};
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci/*
1488c2ecf20Sopenharmony_ci * SMD channel states.
1498c2ecf20Sopenharmony_ci */
1508c2ecf20Sopenharmony_cienum smd_channel_state {
1518c2ecf20Sopenharmony_ci	SMD_CHANNEL_CLOSED,
1528c2ecf20Sopenharmony_ci	SMD_CHANNEL_OPENING,
1538c2ecf20Sopenharmony_ci	SMD_CHANNEL_OPENED,
1548c2ecf20Sopenharmony_ci	SMD_CHANNEL_FLUSHING,
1558c2ecf20Sopenharmony_ci	SMD_CHANNEL_CLOSING,
1568c2ecf20Sopenharmony_ci	SMD_CHANNEL_RESET,
1578c2ecf20Sopenharmony_ci	SMD_CHANNEL_RESET_OPENING
1588c2ecf20Sopenharmony_ci};
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistruct qcom_smd_device {
1618c2ecf20Sopenharmony_ci	struct rpmsg_device rpdev;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge;
1648c2ecf20Sopenharmony_ci};
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistruct qcom_smd_endpoint {
1678c2ecf20Sopenharmony_ci	struct rpmsg_endpoint ept;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	struct qcom_smd_channel *qsch;
1708c2ecf20Sopenharmony_ci};
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci#define to_smd_device(r)	container_of(r, struct qcom_smd_device, rpdev)
1738c2ecf20Sopenharmony_ci#define to_smd_edge(d)		container_of(d, struct qcom_smd_edge, dev)
1748c2ecf20Sopenharmony_ci#define to_smd_endpoint(e)	container_of(e, struct qcom_smd_endpoint, ept)
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci/**
1778c2ecf20Sopenharmony_ci * struct qcom_smd_channel - smd channel struct
1788c2ecf20Sopenharmony_ci * @edge:		qcom_smd_edge this channel is living on
1798c2ecf20Sopenharmony_ci * @qsept:		reference to a associated smd endpoint
1808c2ecf20Sopenharmony_ci * @registered:		flag to indicate if the channel is registered
1818c2ecf20Sopenharmony_ci * @name:		name of the channel
1828c2ecf20Sopenharmony_ci * @state:		local state of the channel
1838c2ecf20Sopenharmony_ci * @remote_state:	remote state of the channel
1848c2ecf20Sopenharmony_ci * @state_change_event:	state change event
1858c2ecf20Sopenharmony_ci * @info:		byte aligned outgoing/incoming channel info
1868c2ecf20Sopenharmony_ci * @info_word:		word aligned outgoing/incoming channel info
1878c2ecf20Sopenharmony_ci * @tx_lock:		lock to make writes to the channel mutually exclusive
1888c2ecf20Sopenharmony_ci * @fblockread_event:	wakeup event tied to tx fBLOCKREADINTR
1898c2ecf20Sopenharmony_ci * @tx_fifo:		pointer to the outgoing ring buffer
1908c2ecf20Sopenharmony_ci * @rx_fifo:		pointer to the incoming ring buffer
1918c2ecf20Sopenharmony_ci * @fifo_size:		size of each ring buffer
1928c2ecf20Sopenharmony_ci * @bounce_buffer:	bounce buffer for reading wrapped packets
1938c2ecf20Sopenharmony_ci * @cb:			callback function registered for this channel
1948c2ecf20Sopenharmony_ci * @recv_lock:		guard for rx info modifications and cb pointer
1958c2ecf20Sopenharmony_ci * @pkt_size:		size of the currently handled packet
1968c2ecf20Sopenharmony_ci * @drvdata:		driver private data
1978c2ecf20Sopenharmony_ci * @list:		lite entry for @channels in qcom_smd_edge
1988c2ecf20Sopenharmony_ci */
1998c2ecf20Sopenharmony_cistruct qcom_smd_channel {
2008c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept;
2038c2ecf20Sopenharmony_ci	bool registered;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	char *name;
2068c2ecf20Sopenharmony_ci	enum smd_channel_state state;
2078c2ecf20Sopenharmony_ci	enum smd_channel_state remote_state;
2088c2ecf20Sopenharmony_ci	wait_queue_head_t state_change_event;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	struct smd_channel_info_pair *info;
2118c2ecf20Sopenharmony_ci	struct smd_channel_info_word_pair *info_word;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	spinlock_t tx_lock;
2148c2ecf20Sopenharmony_ci	wait_queue_head_t fblockread_event;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	void *tx_fifo;
2178c2ecf20Sopenharmony_ci	void *rx_fifo;
2188c2ecf20Sopenharmony_ci	int fifo_size;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	void *bounce_buffer;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	spinlock_t recv_lock;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	int pkt_size;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	void *drvdata;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	struct list_head list;
2298c2ecf20Sopenharmony_ci};
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci/*
2328c2ecf20Sopenharmony_ci * Format of the smd_info smem items, for byte aligned channels.
2338c2ecf20Sopenharmony_ci */
2348c2ecf20Sopenharmony_cistruct smd_channel_info {
2358c2ecf20Sopenharmony_ci	__le32 state;
2368c2ecf20Sopenharmony_ci	u8  fDSR;
2378c2ecf20Sopenharmony_ci	u8  fCTS;
2388c2ecf20Sopenharmony_ci	u8  fCD;
2398c2ecf20Sopenharmony_ci	u8  fRI;
2408c2ecf20Sopenharmony_ci	u8  fHEAD;
2418c2ecf20Sopenharmony_ci	u8  fTAIL;
2428c2ecf20Sopenharmony_ci	u8  fSTATE;
2438c2ecf20Sopenharmony_ci	u8  fBLOCKREADINTR;
2448c2ecf20Sopenharmony_ci	__le32 tail;
2458c2ecf20Sopenharmony_ci	__le32 head;
2468c2ecf20Sopenharmony_ci};
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_cistruct smd_channel_info_pair {
2498c2ecf20Sopenharmony_ci	struct smd_channel_info tx;
2508c2ecf20Sopenharmony_ci	struct smd_channel_info rx;
2518c2ecf20Sopenharmony_ci};
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci/*
2548c2ecf20Sopenharmony_ci * Format of the smd_info smem items, for word aligned channels.
2558c2ecf20Sopenharmony_ci */
2568c2ecf20Sopenharmony_cistruct smd_channel_info_word {
2578c2ecf20Sopenharmony_ci	__le32 state;
2588c2ecf20Sopenharmony_ci	__le32 fDSR;
2598c2ecf20Sopenharmony_ci	__le32 fCTS;
2608c2ecf20Sopenharmony_ci	__le32 fCD;
2618c2ecf20Sopenharmony_ci	__le32 fRI;
2628c2ecf20Sopenharmony_ci	__le32 fHEAD;
2638c2ecf20Sopenharmony_ci	__le32 fTAIL;
2648c2ecf20Sopenharmony_ci	__le32 fSTATE;
2658c2ecf20Sopenharmony_ci	__le32 fBLOCKREADINTR;
2668c2ecf20Sopenharmony_ci	__le32 tail;
2678c2ecf20Sopenharmony_ci	__le32 head;
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistruct smd_channel_info_word_pair {
2718c2ecf20Sopenharmony_ci	struct smd_channel_info_word tx;
2728c2ecf20Sopenharmony_ci	struct smd_channel_info_word rx;
2738c2ecf20Sopenharmony_ci};
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci#define GET_RX_CHANNEL_FLAG(channel, param)				     \
2768c2ecf20Sopenharmony_ci	({								     \
2778c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
2788c2ecf20Sopenharmony_ci		channel->info_word ?					     \
2798c2ecf20Sopenharmony_ci			le32_to_cpu(channel->info_word->rx.param) :	     \
2808c2ecf20Sopenharmony_ci			channel->info->rx.param;			     \
2818c2ecf20Sopenharmony_ci	})
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci#define GET_RX_CHANNEL_INFO(channel, param)				      \
2848c2ecf20Sopenharmony_ci	({								      \
2858c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
2868c2ecf20Sopenharmony_ci		le32_to_cpu(channel->info_word ?			      \
2878c2ecf20Sopenharmony_ci			channel->info_word->rx.param :			      \
2888c2ecf20Sopenharmony_ci			channel->info->rx.param);			      \
2898c2ecf20Sopenharmony_ci	})
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci#define SET_RX_CHANNEL_FLAG(channel, param, value)			     \
2928c2ecf20Sopenharmony_ci	({								     \
2938c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
2948c2ecf20Sopenharmony_ci		if (channel->info_word)					     \
2958c2ecf20Sopenharmony_ci			channel->info_word->rx.param = cpu_to_le32(value);   \
2968c2ecf20Sopenharmony_ci		else							     \
2978c2ecf20Sopenharmony_ci			channel->info->rx.param = value;		     \
2988c2ecf20Sopenharmony_ci	})
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci#define SET_RX_CHANNEL_INFO(channel, param, value)			      \
3018c2ecf20Sopenharmony_ci	({								      \
3028c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
3038c2ecf20Sopenharmony_ci		if (channel->info_word)					      \
3048c2ecf20Sopenharmony_ci			channel->info_word->rx.param = cpu_to_le32(value);    \
3058c2ecf20Sopenharmony_ci		else							      \
3068c2ecf20Sopenharmony_ci			channel->info->rx.param = cpu_to_le32(value);	      \
3078c2ecf20Sopenharmony_ci	})
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci#define GET_TX_CHANNEL_FLAG(channel, param)				     \
3108c2ecf20Sopenharmony_ci	({								     \
3118c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
3128c2ecf20Sopenharmony_ci		channel->info_word ?					     \
3138c2ecf20Sopenharmony_ci			le32_to_cpu(channel->info_word->tx.param) :          \
3148c2ecf20Sopenharmony_ci			channel->info->tx.param;			     \
3158c2ecf20Sopenharmony_ci	})
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci#define GET_TX_CHANNEL_INFO(channel, param)				      \
3188c2ecf20Sopenharmony_ci	({								      \
3198c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
3208c2ecf20Sopenharmony_ci		le32_to_cpu(channel->info_word ?			      \
3218c2ecf20Sopenharmony_ci			channel->info_word->tx.param :			      \
3228c2ecf20Sopenharmony_ci			channel->info->tx.param);			      \
3238c2ecf20Sopenharmony_ci	})
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci#define SET_TX_CHANNEL_FLAG(channel, param, value)			     \
3268c2ecf20Sopenharmony_ci	({								     \
3278c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
3288c2ecf20Sopenharmony_ci		if (channel->info_word)					     \
3298c2ecf20Sopenharmony_ci			channel->info_word->tx.param = cpu_to_le32(value);   \
3308c2ecf20Sopenharmony_ci		else							     \
3318c2ecf20Sopenharmony_ci			channel->info->tx.param = value;		     \
3328c2ecf20Sopenharmony_ci	})
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci#define SET_TX_CHANNEL_INFO(channel, param, value)			      \
3358c2ecf20Sopenharmony_ci	({								      \
3368c2ecf20Sopenharmony_ci		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
3378c2ecf20Sopenharmony_ci		if (channel->info_word)					      \
3388c2ecf20Sopenharmony_ci			channel->info_word->tx.param = cpu_to_le32(value);   \
3398c2ecf20Sopenharmony_ci		else							      \
3408c2ecf20Sopenharmony_ci			channel->info->tx.param = cpu_to_le32(value);	      \
3418c2ecf20Sopenharmony_ci	})
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci/**
3448c2ecf20Sopenharmony_ci * struct qcom_smd_alloc_entry - channel allocation entry
3458c2ecf20Sopenharmony_ci * @name:	channel name
3468c2ecf20Sopenharmony_ci * @cid:	channel index
3478c2ecf20Sopenharmony_ci * @flags:	channel flags and edge id
3488c2ecf20Sopenharmony_ci * @ref_count:	reference count of the channel
3498c2ecf20Sopenharmony_ci */
3508c2ecf20Sopenharmony_cistruct qcom_smd_alloc_entry {
3518c2ecf20Sopenharmony_ci	u8 name[20];
3528c2ecf20Sopenharmony_ci	__le32 cid;
3538c2ecf20Sopenharmony_ci	__le32 flags;
3548c2ecf20Sopenharmony_ci	__le32 ref_count;
3558c2ecf20Sopenharmony_ci} __packed;
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci#define SMD_CHANNEL_FLAGS_EDGE_MASK	0xff
3588c2ecf20Sopenharmony_ci#define SMD_CHANNEL_FLAGS_STREAM	BIT(8)
3598c2ecf20Sopenharmony_ci#define SMD_CHANNEL_FLAGS_PACKET	BIT(9)
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci/*
3628c2ecf20Sopenharmony_ci * Each smd packet contains a 20 byte header, with the first 4 being the length
3638c2ecf20Sopenharmony_ci * of the packet.
3648c2ecf20Sopenharmony_ci */
3658c2ecf20Sopenharmony_ci#define SMD_PACKET_HEADER_LEN	20
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci/*
3688c2ecf20Sopenharmony_ci * Signal the remote processor associated with 'channel'.
3698c2ecf20Sopenharmony_ci */
3708c2ecf20Sopenharmony_cistatic void qcom_smd_signal_channel(struct qcom_smd_channel *channel)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = channel->edge;
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	if (edge->mbox_chan) {
3758c2ecf20Sopenharmony_ci		/*
3768c2ecf20Sopenharmony_ci		 * We can ignore a failing mbox_send_message() as the only
3778c2ecf20Sopenharmony_ci		 * possible cause is that the FIFO in the framework is full of
3788c2ecf20Sopenharmony_ci		 * other writes to the same bit.
3798c2ecf20Sopenharmony_ci		 */
3808c2ecf20Sopenharmony_ci		mbox_send_message(edge->mbox_chan, NULL);
3818c2ecf20Sopenharmony_ci		mbox_client_txdone(edge->mbox_chan, 0);
3828c2ecf20Sopenharmony_ci	} else {
3838c2ecf20Sopenharmony_ci		regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit));
3848c2ecf20Sopenharmony_ci	}
3858c2ecf20Sopenharmony_ci}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci/*
3888c2ecf20Sopenharmony_ci * Initialize the tx channel info
3898c2ecf20Sopenharmony_ci */
3908c2ecf20Sopenharmony_cistatic void qcom_smd_channel_reset(struct qcom_smd_channel *channel)
3918c2ecf20Sopenharmony_ci{
3928c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED);
3938c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fDSR, 0);
3948c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fCTS, 0);
3958c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fCD, 0);
3968c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fRI, 0);
3978c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fHEAD, 0);
3988c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
3998c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
4008c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
4018c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_INFO(channel, head, 0);
4028c2ecf20Sopenharmony_ci	SET_RX_CHANNEL_INFO(channel, tail, 0);
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	qcom_smd_signal_channel(channel);
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	channel->state = SMD_CHANNEL_CLOSED;
4078c2ecf20Sopenharmony_ci	channel->pkt_size = 0;
4088c2ecf20Sopenharmony_ci}
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci/*
4118c2ecf20Sopenharmony_ci * Set the callback for a channel, with appropriate locking
4128c2ecf20Sopenharmony_ci */
4138c2ecf20Sopenharmony_cistatic void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,
4148c2ecf20Sopenharmony_ci					  rpmsg_rx_cb_t cb)
4158c2ecf20Sopenharmony_ci{
4168c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept = &channel->qsept->ept;
4178c2ecf20Sopenharmony_ci	unsigned long flags;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->recv_lock, flags);
4208c2ecf20Sopenharmony_ci	ept->cb = cb;
4218c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->recv_lock, flags);
4228c2ecf20Sopenharmony_ci};
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci/*
4258c2ecf20Sopenharmony_ci * Calculate the amount of data available in the rx fifo
4268c2ecf20Sopenharmony_ci */
4278c2ecf20Sopenharmony_cistatic size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel)
4288c2ecf20Sopenharmony_ci{
4298c2ecf20Sopenharmony_ci	unsigned head;
4308c2ecf20Sopenharmony_ci	unsigned tail;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	head = GET_RX_CHANNEL_INFO(channel, head);
4338c2ecf20Sopenharmony_ci	tail = GET_RX_CHANNEL_INFO(channel, tail);
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	return (head - tail) & (channel->fifo_size - 1);
4368c2ecf20Sopenharmony_ci}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci/*
4398c2ecf20Sopenharmony_ci * Set tx channel state and inform the remote processor
4408c2ecf20Sopenharmony_ci */
4418c2ecf20Sopenharmony_cistatic void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
4428c2ecf20Sopenharmony_ci				       int state)
4438c2ecf20Sopenharmony_ci{
4448c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = channel->edge;
4458c2ecf20Sopenharmony_ci	bool is_open = state == SMD_CHANNEL_OPENED;
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	if (channel->state == state)
4488c2ecf20Sopenharmony_ci		return;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state);
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fDSR, is_open);
4538c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fCTS, is_open);
4548c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fCD, is_open);
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_INFO(channel, state, state);
4578c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	channel->state = state;
4608c2ecf20Sopenharmony_ci	qcom_smd_signal_channel(channel);
4618c2ecf20Sopenharmony_ci}
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci/*
4648c2ecf20Sopenharmony_ci * Copy count bytes of data using 32bit accesses, if that's required.
4658c2ecf20Sopenharmony_ci */
4668c2ecf20Sopenharmony_cistatic void smd_copy_to_fifo(void __iomem *dst,
4678c2ecf20Sopenharmony_ci			     const void *src,
4688c2ecf20Sopenharmony_ci			     size_t count,
4698c2ecf20Sopenharmony_ci			     bool word_aligned)
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci	if (word_aligned) {
4728c2ecf20Sopenharmony_ci		__iowrite32_copy(dst, src, count / sizeof(u32));
4738c2ecf20Sopenharmony_ci	} else {
4748c2ecf20Sopenharmony_ci		memcpy_toio(dst, src, count);
4758c2ecf20Sopenharmony_ci	}
4768c2ecf20Sopenharmony_ci}
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci/*
4798c2ecf20Sopenharmony_ci * Copy count bytes of data using 32bit accesses, if that is required.
4808c2ecf20Sopenharmony_ci */
4818c2ecf20Sopenharmony_cistatic void smd_copy_from_fifo(void *dst,
4828c2ecf20Sopenharmony_ci			       const void __iomem *src,
4838c2ecf20Sopenharmony_ci			       size_t count,
4848c2ecf20Sopenharmony_ci			       bool word_aligned)
4858c2ecf20Sopenharmony_ci{
4868c2ecf20Sopenharmony_ci	if (word_aligned) {
4878c2ecf20Sopenharmony_ci		__ioread32_copy(dst, src, count / sizeof(u32));
4888c2ecf20Sopenharmony_ci	} else {
4898c2ecf20Sopenharmony_ci		memcpy_fromio(dst, src, count);
4908c2ecf20Sopenharmony_ci	}
4918c2ecf20Sopenharmony_ci}
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci/*
4948c2ecf20Sopenharmony_ci * Read count bytes of data from the rx fifo into buf, but don't advance the
4958c2ecf20Sopenharmony_ci * tail.
4968c2ecf20Sopenharmony_ci */
4978c2ecf20Sopenharmony_cistatic size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
4988c2ecf20Sopenharmony_ci				    void *buf, size_t count)
4998c2ecf20Sopenharmony_ci{
5008c2ecf20Sopenharmony_ci	bool word_aligned;
5018c2ecf20Sopenharmony_ci	unsigned tail;
5028c2ecf20Sopenharmony_ci	size_t len;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci	word_aligned = channel->info_word;
5058c2ecf20Sopenharmony_ci	tail = GET_RX_CHANNEL_INFO(channel, tail);
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	len = min_t(size_t, count, channel->fifo_size - tail);
5088c2ecf20Sopenharmony_ci	if (len) {
5098c2ecf20Sopenharmony_ci		smd_copy_from_fifo(buf,
5108c2ecf20Sopenharmony_ci				   channel->rx_fifo + tail,
5118c2ecf20Sopenharmony_ci				   len,
5128c2ecf20Sopenharmony_ci				   word_aligned);
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	if (len != count) {
5168c2ecf20Sopenharmony_ci		smd_copy_from_fifo(buf + len,
5178c2ecf20Sopenharmony_ci				   channel->rx_fifo,
5188c2ecf20Sopenharmony_ci				   count - len,
5198c2ecf20Sopenharmony_ci				   word_aligned);
5208c2ecf20Sopenharmony_ci	}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	return count;
5238c2ecf20Sopenharmony_ci}
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_ci/*
5268c2ecf20Sopenharmony_ci * Advance the rx tail by count bytes.
5278c2ecf20Sopenharmony_ci */
5288c2ecf20Sopenharmony_cistatic void qcom_smd_channel_advance(struct qcom_smd_channel *channel,
5298c2ecf20Sopenharmony_ci				     size_t count)
5308c2ecf20Sopenharmony_ci{
5318c2ecf20Sopenharmony_ci	unsigned tail;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	tail = GET_RX_CHANNEL_INFO(channel, tail);
5348c2ecf20Sopenharmony_ci	tail += count;
5358c2ecf20Sopenharmony_ci	tail &= (channel->fifo_size - 1);
5368c2ecf20Sopenharmony_ci	SET_RX_CHANNEL_INFO(channel, tail, tail);
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci/*
5408c2ecf20Sopenharmony_ci * Read out a single packet from the rx fifo and deliver it to the device
5418c2ecf20Sopenharmony_ci */
5428c2ecf20Sopenharmony_cistatic int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel)
5438c2ecf20Sopenharmony_ci{
5448c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept = &channel->qsept->ept;
5458c2ecf20Sopenharmony_ci	unsigned tail;
5468c2ecf20Sopenharmony_ci	size_t len;
5478c2ecf20Sopenharmony_ci	void *ptr;
5488c2ecf20Sopenharmony_ci	int ret;
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	tail = GET_RX_CHANNEL_INFO(channel, tail);
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	/* Use bounce buffer if the data wraps */
5538c2ecf20Sopenharmony_ci	if (tail + channel->pkt_size >= channel->fifo_size) {
5548c2ecf20Sopenharmony_ci		ptr = channel->bounce_buffer;
5558c2ecf20Sopenharmony_ci		len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size);
5568c2ecf20Sopenharmony_ci	} else {
5578c2ecf20Sopenharmony_ci		ptr = channel->rx_fifo + tail;
5588c2ecf20Sopenharmony_ci		len = channel->pkt_size;
5598c2ecf20Sopenharmony_ci	}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY);
5628c2ecf20Sopenharmony_ci	if (ret < 0)
5638c2ecf20Sopenharmony_ci		return ret;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	/* Only forward the tail if the client consumed the data */
5668c2ecf20Sopenharmony_ci	qcom_smd_channel_advance(channel, len);
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci	channel->pkt_size = 0;
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci	return 0;
5718c2ecf20Sopenharmony_ci}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci/*
5748c2ecf20Sopenharmony_ci * Per channel interrupt handling
5758c2ecf20Sopenharmony_ci */
5768c2ecf20Sopenharmony_cistatic bool qcom_smd_channel_intr(struct qcom_smd_channel *channel)
5778c2ecf20Sopenharmony_ci{
5788c2ecf20Sopenharmony_ci	bool need_state_scan = false;
5798c2ecf20Sopenharmony_ci	int remote_state;
5808c2ecf20Sopenharmony_ci	__le32 pktlen;
5818c2ecf20Sopenharmony_ci	int avail;
5828c2ecf20Sopenharmony_ci	int ret;
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ci	/* Handle state changes */
5858c2ecf20Sopenharmony_ci	remote_state = GET_RX_CHANNEL_INFO(channel, state);
5868c2ecf20Sopenharmony_ci	if (remote_state != channel->remote_state) {
5878c2ecf20Sopenharmony_ci		channel->remote_state = remote_state;
5888c2ecf20Sopenharmony_ci		need_state_scan = true;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci		wake_up_interruptible_all(&channel->state_change_event);
5918c2ecf20Sopenharmony_ci	}
5928c2ecf20Sopenharmony_ci	/* Indicate that we have seen any state change */
5938c2ecf20Sopenharmony_ci	SET_RX_CHANNEL_FLAG(channel, fSTATE, 0);
5948c2ecf20Sopenharmony_ci
5958c2ecf20Sopenharmony_ci	/* Signal waiting qcom_smd_send() about the interrupt */
5968c2ecf20Sopenharmony_ci	if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR))
5978c2ecf20Sopenharmony_ci		wake_up_interruptible_all(&channel->fblockread_event);
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_ci	/* Don't consume any data until we've opened the channel */
6008c2ecf20Sopenharmony_ci	if (channel->state != SMD_CHANNEL_OPENED)
6018c2ecf20Sopenharmony_ci		goto out;
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci	/* Indicate that we've seen the new data */
6048c2ecf20Sopenharmony_ci	SET_RX_CHANNEL_FLAG(channel, fHEAD, 0);
6058c2ecf20Sopenharmony_ci
6068c2ecf20Sopenharmony_ci	/* Consume data */
6078c2ecf20Sopenharmony_ci	for (;;) {
6088c2ecf20Sopenharmony_ci		avail = qcom_smd_channel_get_rx_avail(channel);
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci		if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) {
6118c2ecf20Sopenharmony_ci			qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen));
6128c2ecf20Sopenharmony_ci			qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN);
6138c2ecf20Sopenharmony_ci			channel->pkt_size = le32_to_cpu(pktlen);
6148c2ecf20Sopenharmony_ci		} else if (channel->pkt_size && avail >= channel->pkt_size) {
6158c2ecf20Sopenharmony_ci			ret = qcom_smd_channel_recv_single(channel);
6168c2ecf20Sopenharmony_ci			if (ret)
6178c2ecf20Sopenharmony_ci				break;
6188c2ecf20Sopenharmony_ci		} else {
6198c2ecf20Sopenharmony_ci			break;
6208c2ecf20Sopenharmony_ci		}
6218c2ecf20Sopenharmony_ci	}
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci	/* Indicate that we have seen and updated tail */
6248c2ecf20Sopenharmony_ci	SET_RX_CHANNEL_FLAG(channel, fTAIL, 1);
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	/* Signal the remote that we've consumed the data (if requested) */
6278c2ecf20Sopenharmony_ci	if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) {
6288c2ecf20Sopenharmony_ci		/* Ensure ordering of channel info updates */
6298c2ecf20Sopenharmony_ci		wmb();
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci		qcom_smd_signal_channel(channel);
6328c2ecf20Sopenharmony_ci	}
6338c2ecf20Sopenharmony_ci
6348c2ecf20Sopenharmony_ciout:
6358c2ecf20Sopenharmony_ci	return need_state_scan;
6368c2ecf20Sopenharmony_ci}
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci/*
6398c2ecf20Sopenharmony_ci * The edge interrupts are triggered by the remote processor on state changes,
6408c2ecf20Sopenharmony_ci * channel info updates or when new channels are created.
6418c2ecf20Sopenharmony_ci */
6428c2ecf20Sopenharmony_cistatic irqreturn_t qcom_smd_edge_intr(int irq, void *data)
6438c2ecf20Sopenharmony_ci{
6448c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = data;
6458c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
6468c2ecf20Sopenharmony_ci	unsigned available;
6478c2ecf20Sopenharmony_ci	bool kick_scanner = false;
6488c2ecf20Sopenharmony_ci	bool kick_state = false;
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	/*
6518c2ecf20Sopenharmony_ci	 * Handle state changes or data on each of the channels on this edge
6528c2ecf20Sopenharmony_ci	 */
6538c2ecf20Sopenharmony_ci	spin_lock(&edge->channels_lock);
6548c2ecf20Sopenharmony_ci	list_for_each_entry(channel, &edge->channels, list) {
6558c2ecf20Sopenharmony_ci		spin_lock(&channel->recv_lock);
6568c2ecf20Sopenharmony_ci		kick_state |= qcom_smd_channel_intr(channel);
6578c2ecf20Sopenharmony_ci		spin_unlock(&channel->recv_lock);
6588c2ecf20Sopenharmony_ci	}
6598c2ecf20Sopenharmony_ci	spin_unlock(&edge->channels_lock);
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci	/*
6628c2ecf20Sopenharmony_ci	 * Creating a new channel requires allocating an smem entry, so we only
6638c2ecf20Sopenharmony_ci	 * have to scan if the amount of available space in smem have changed
6648c2ecf20Sopenharmony_ci	 * since last scan.
6658c2ecf20Sopenharmony_ci	 */
6668c2ecf20Sopenharmony_ci	available = qcom_smem_get_free_space(edge->remote_pid);
6678c2ecf20Sopenharmony_ci	if (available != edge->smem_available) {
6688c2ecf20Sopenharmony_ci		edge->smem_available = available;
6698c2ecf20Sopenharmony_ci		kick_scanner = true;
6708c2ecf20Sopenharmony_ci	}
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_ci	if (kick_scanner)
6738c2ecf20Sopenharmony_ci		schedule_work(&edge->scan_work);
6748c2ecf20Sopenharmony_ci	if (kick_state)
6758c2ecf20Sopenharmony_ci		schedule_work(&edge->state_work);
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
6788c2ecf20Sopenharmony_ci}
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci/*
6818c2ecf20Sopenharmony_ci * Calculate how much space is available in the tx fifo.
6828c2ecf20Sopenharmony_ci */
6838c2ecf20Sopenharmony_cistatic size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel)
6848c2ecf20Sopenharmony_ci{
6858c2ecf20Sopenharmony_ci	unsigned head;
6868c2ecf20Sopenharmony_ci	unsigned tail;
6878c2ecf20Sopenharmony_ci	unsigned mask = channel->fifo_size - 1;
6888c2ecf20Sopenharmony_ci
6898c2ecf20Sopenharmony_ci	head = GET_TX_CHANNEL_INFO(channel, head);
6908c2ecf20Sopenharmony_ci	tail = GET_TX_CHANNEL_INFO(channel, tail);
6918c2ecf20Sopenharmony_ci
6928c2ecf20Sopenharmony_ci	return mask - ((head - tail) & mask);
6938c2ecf20Sopenharmony_ci}
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci/*
6968c2ecf20Sopenharmony_ci * Write count bytes of data into channel, possibly wrapping in the ring buffer
6978c2ecf20Sopenharmony_ci */
6988c2ecf20Sopenharmony_cistatic int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
6998c2ecf20Sopenharmony_ci			       const void *data,
7008c2ecf20Sopenharmony_ci			       size_t count)
7018c2ecf20Sopenharmony_ci{
7028c2ecf20Sopenharmony_ci	bool word_aligned;
7038c2ecf20Sopenharmony_ci	unsigned head;
7048c2ecf20Sopenharmony_ci	size_t len;
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	word_aligned = channel->info_word;
7078c2ecf20Sopenharmony_ci	head = GET_TX_CHANNEL_INFO(channel, head);
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	len = min_t(size_t, count, channel->fifo_size - head);
7108c2ecf20Sopenharmony_ci	if (len) {
7118c2ecf20Sopenharmony_ci		smd_copy_to_fifo(channel->tx_fifo + head,
7128c2ecf20Sopenharmony_ci				 data,
7138c2ecf20Sopenharmony_ci				 len,
7148c2ecf20Sopenharmony_ci				 word_aligned);
7158c2ecf20Sopenharmony_ci	}
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci	if (len != count) {
7188c2ecf20Sopenharmony_ci		smd_copy_to_fifo(channel->tx_fifo,
7198c2ecf20Sopenharmony_ci				 data + len,
7208c2ecf20Sopenharmony_ci				 count - len,
7218c2ecf20Sopenharmony_ci				 word_aligned);
7228c2ecf20Sopenharmony_ci	}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	head += count;
7258c2ecf20Sopenharmony_ci	head &= (channel->fifo_size - 1);
7268c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_INFO(channel, head, head);
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci	return count;
7298c2ecf20Sopenharmony_ci}
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_ci/**
7328c2ecf20Sopenharmony_ci * qcom_smd_send - write data to smd channel
7338c2ecf20Sopenharmony_ci * @channel:	channel handle
7348c2ecf20Sopenharmony_ci * @data:	buffer of data to write
7358c2ecf20Sopenharmony_ci * @len:	number of bytes to write
7368c2ecf20Sopenharmony_ci * @wait:	flag to indicate if write has ca wait
7378c2ecf20Sopenharmony_ci *
7388c2ecf20Sopenharmony_ci * This is a blocking write of len bytes into the channel's tx ring buffer and
7398c2ecf20Sopenharmony_ci * signal the remote end. It will sleep until there is enough space available
7408c2ecf20Sopenharmony_ci * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid
7418c2ecf20Sopenharmony_ci * polling.
7428c2ecf20Sopenharmony_ci */
7438c2ecf20Sopenharmony_cistatic int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data,
7448c2ecf20Sopenharmony_ci			   int len, bool wait)
7458c2ecf20Sopenharmony_ci{
7468c2ecf20Sopenharmony_ci	__le32 hdr[5] = { cpu_to_le32(len), };
7478c2ecf20Sopenharmony_ci	int tlen = sizeof(hdr) + len;
7488c2ecf20Sopenharmony_ci	unsigned long flags;
7498c2ecf20Sopenharmony_ci	int ret;
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_ci	/* Word aligned channels only accept word size aligned data */
7528c2ecf20Sopenharmony_ci	if (channel->info_word && len % 4)
7538c2ecf20Sopenharmony_ci		return -EINVAL;
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	/* Reject packets that are too big */
7568c2ecf20Sopenharmony_ci	if (tlen >= channel->fifo_size)
7578c2ecf20Sopenharmony_ci		return -EINVAL;
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_ci	/* Highlight the fact that if we enter the loop below we might sleep */
7608c2ecf20Sopenharmony_ci	if (wait)
7618c2ecf20Sopenharmony_ci		might_sleep();
7628c2ecf20Sopenharmony_ci
7638c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->tx_lock, flags);
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	while (qcom_smd_get_tx_avail(channel) < tlen &&
7668c2ecf20Sopenharmony_ci	       channel->state == SMD_CHANNEL_OPENED) {
7678c2ecf20Sopenharmony_ci		if (!wait) {
7688c2ecf20Sopenharmony_ci			ret = -EAGAIN;
7698c2ecf20Sopenharmony_ci			goto out_unlock;
7708c2ecf20Sopenharmony_ci		}
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_ci		SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0);
7738c2ecf20Sopenharmony_ci
7748c2ecf20Sopenharmony_ci		/* Wait without holding the tx_lock */
7758c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&channel->tx_lock, flags);
7768c2ecf20Sopenharmony_ci
7778c2ecf20Sopenharmony_ci		ret = wait_event_interruptible(channel->fblockread_event,
7788c2ecf20Sopenharmony_ci				       qcom_smd_get_tx_avail(channel) >= tlen ||
7798c2ecf20Sopenharmony_ci				       channel->state != SMD_CHANNEL_OPENED);
7808c2ecf20Sopenharmony_ci		if (ret)
7818c2ecf20Sopenharmony_ci			return ret;
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci		spin_lock_irqsave(&channel->tx_lock, flags);
7848c2ecf20Sopenharmony_ci
7858c2ecf20Sopenharmony_ci		SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
7868c2ecf20Sopenharmony_ci	}
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	/* Fail if the channel was closed */
7898c2ecf20Sopenharmony_ci	if (channel->state != SMD_CHANNEL_OPENED) {
7908c2ecf20Sopenharmony_ci		ret = -EPIPE;
7918c2ecf20Sopenharmony_ci		goto out_unlock;
7928c2ecf20Sopenharmony_ci	}
7938c2ecf20Sopenharmony_ci
7948c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
7958c2ecf20Sopenharmony_ci
7968c2ecf20Sopenharmony_ci	qcom_smd_write_fifo(channel, hdr, sizeof(hdr));
7978c2ecf20Sopenharmony_ci	qcom_smd_write_fifo(channel, data, len);
7988c2ecf20Sopenharmony_ci
7998c2ecf20Sopenharmony_ci	SET_TX_CHANNEL_FLAG(channel, fHEAD, 1);
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci	/* Ensure ordering of channel info updates */
8028c2ecf20Sopenharmony_ci	wmb();
8038c2ecf20Sopenharmony_ci
8048c2ecf20Sopenharmony_ci	qcom_smd_signal_channel(channel);
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ciout_unlock:
8078c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->tx_lock, flags);
8088c2ecf20Sopenharmony_ci
8098c2ecf20Sopenharmony_ci	return ret;
8108c2ecf20Sopenharmony_ci}
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci/*
8138c2ecf20Sopenharmony_ci * Helper for opening a channel
8148c2ecf20Sopenharmony_ci */
8158c2ecf20Sopenharmony_cistatic int qcom_smd_channel_open(struct qcom_smd_channel *channel,
8168c2ecf20Sopenharmony_ci				 rpmsg_rx_cb_t cb)
8178c2ecf20Sopenharmony_ci{
8188c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = channel->edge;
8198c2ecf20Sopenharmony_ci	size_t bb_size;
8208c2ecf20Sopenharmony_ci	int ret;
8218c2ecf20Sopenharmony_ci
8228c2ecf20Sopenharmony_ci	/*
8238c2ecf20Sopenharmony_ci	 * Packets are maximum 4k, but reduce if the fifo is smaller
8248c2ecf20Sopenharmony_ci	 */
8258c2ecf20Sopenharmony_ci	bb_size = min(channel->fifo_size, SZ_4K);
8268c2ecf20Sopenharmony_ci	channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL);
8278c2ecf20Sopenharmony_ci	if (!channel->bounce_buffer)
8288c2ecf20Sopenharmony_ci		return -ENOMEM;
8298c2ecf20Sopenharmony_ci
8308c2ecf20Sopenharmony_ci	qcom_smd_channel_set_callback(channel, cb);
8318c2ecf20Sopenharmony_ci	qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING);
8328c2ecf20Sopenharmony_ci
8338c2ecf20Sopenharmony_ci	/* Wait for remote to enter opening or opened */
8348c2ecf20Sopenharmony_ci	ret = wait_event_interruptible_timeout(channel->state_change_event,
8358c2ecf20Sopenharmony_ci			channel->remote_state == SMD_CHANNEL_OPENING ||
8368c2ecf20Sopenharmony_ci			channel->remote_state == SMD_CHANNEL_OPENED,
8378c2ecf20Sopenharmony_ci			HZ);
8388c2ecf20Sopenharmony_ci	if (!ret) {
8398c2ecf20Sopenharmony_ci		dev_err(&edge->dev, "remote side did not enter opening state\n");
8408c2ecf20Sopenharmony_ci		goto out_close_timeout;
8418c2ecf20Sopenharmony_ci	}
8428c2ecf20Sopenharmony_ci
8438c2ecf20Sopenharmony_ci	qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED);
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci	/* Wait for remote to enter opened */
8468c2ecf20Sopenharmony_ci	ret = wait_event_interruptible_timeout(channel->state_change_event,
8478c2ecf20Sopenharmony_ci			channel->remote_state == SMD_CHANNEL_OPENED,
8488c2ecf20Sopenharmony_ci			HZ);
8498c2ecf20Sopenharmony_ci	if (!ret) {
8508c2ecf20Sopenharmony_ci		dev_err(&edge->dev, "remote side did not enter open state\n");
8518c2ecf20Sopenharmony_ci		goto out_close_timeout;
8528c2ecf20Sopenharmony_ci	}
8538c2ecf20Sopenharmony_ci
8548c2ecf20Sopenharmony_ci	return 0;
8558c2ecf20Sopenharmony_ci
8568c2ecf20Sopenharmony_ciout_close_timeout:
8578c2ecf20Sopenharmony_ci	qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
8588c2ecf20Sopenharmony_ci	return -ETIMEDOUT;
8598c2ecf20Sopenharmony_ci}
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci/*
8628c2ecf20Sopenharmony_ci * Helper for closing and resetting a channel
8638c2ecf20Sopenharmony_ci */
8648c2ecf20Sopenharmony_cistatic void qcom_smd_channel_close(struct qcom_smd_channel *channel)
8658c2ecf20Sopenharmony_ci{
8668c2ecf20Sopenharmony_ci	qcom_smd_channel_set_callback(channel, NULL);
8678c2ecf20Sopenharmony_ci
8688c2ecf20Sopenharmony_ci	kfree(channel->bounce_buffer);
8698c2ecf20Sopenharmony_ci	channel->bounce_buffer = NULL;
8708c2ecf20Sopenharmony_ci
8718c2ecf20Sopenharmony_ci	qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
8728c2ecf20Sopenharmony_ci	qcom_smd_channel_reset(channel);
8738c2ecf20Sopenharmony_ci}
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_cistatic struct qcom_smd_channel *
8768c2ecf20Sopenharmony_ciqcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
8778c2ecf20Sopenharmony_ci{
8788c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
8798c2ecf20Sopenharmony_ci	struct qcom_smd_channel *ret = NULL;
8808c2ecf20Sopenharmony_ci	unsigned long flags;
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	spin_lock_irqsave(&edge->channels_lock, flags);
8838c2ecf20Sopenharmony_ci	list_for_each_entry(channel, &edge->channels, list) {
8848c2ecf20Sopenharmony_ci		if (!strcmp(channel->name, name)) {
8858c2ecf20Sopenharmony_ci			ret = channel;
8868c2ecf20Sopenharmony_ci			break;
8878c2ecf20Sopenharmony_ci		}
8888c2ecf20Sopenharmony_ci	}
8898c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&edge->channels_lock, flags);
8908c2ecf20Sopenharmony_ci
8918c2ecf20Sopenharmony_ci	return ret;
8928c2ecf20Sopenharmony_ci}
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_cistatic void __ept_release(struct kref *kref)
8958c2ecf20Sopenharmony_ci{
8968c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint,
8978c2ecf20Sopenharmony_ci						  refcount);
8988c2ecf20Sopenharmony_ci	kfree(to_smd_endpoint(ept));
8998c2ecf20Sopenharmony_ci}
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_cistatic struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev,
9028c2ecf20Sopenharmony_ci						  rpmsg_rx_cb_t cb, void *priv,
9038c2ecf20Sopenharmony_ci						  struct rpmsg_channel_info chinfo)
9048c2ecf20Sopenharmony_ci{
9058c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept;
9068c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
9078c2ecf20Sopenharmony_ci	struct qcom_smd_device *qsdev = to_smd_device(rpdev);
9088c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = qsdev->edge;
9098c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept;
9108c2ecf20Sopenharmony_ci	const char *name = chinfo.name;
9118c2ecf20Sopenharmony_ci	int ret;
9128c2ecf20Sopenharmony_ci
9138c2ecf20Sopenharmony_ci	/* Wait up to HZ for the channel to appear */
9148c2ecf20Sopenharmony_ci	ret = wait_event_interruptible_timeout(edge->new_channel_event,
9158c2ecf20Sopenharmony_ci			(channel = qcom_smd_find_channel(edge, name)) != NULL,
9168c2ecf20Sopenharmony_ci			HZ);
9178c2ecf20Sopenharmony_ci	if (!ret)
9188c2ecf20Sopenharmony_ci		return NULL;
9198c2ecf20Sopenharmony_ci
9208c2ecf20Sopenharmony_ci	if (channel->state != SMD_CHANNEL_CLOSED) {
9218c2ecf20Sopenharmony_ci		dev_err(&rpdev->dev, "channel %s is busy\n", channel->name);
9228c2ecf20Sopenharmony_ci		return NULL;
9238c2ecf20Sopenharmony_ci	}
9248c2ecf20Sopenharmony_ci
9258c2ecf20Sopenharmony_ci	qsept = kzalloc(sizeof(*qsept), GFP_KERNEL);
9268c2ecf20Sopenharmony_ci	if (!qsept)
9278c2ecf20Sopenharmony_ci		return NULL;
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_ci	ept = &qsept->ept;
9308c2ecf20Sopenharmony_ci
9318c2ecf20Sopenharmony_ci	kref_init(&ept->refcount);
9328c2ecf20Sopenharmony_ci
9338c2ecf20Sopenharmony_ci	ept->rpdev = rpdev;
9348c2ecf20Sopenharmony_ci	ept->cb = cb;
9358c2ecf20Sopenharmony_ci	ept->priv = priv;
9368c2ecf20Sopenharmony_ci	ept->ops = &qcom_smd_endpoint_ops;
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci	channel->qsept = qsept;
9398c2ecf20Sopenharmony_ci	qsept->qsch = channel;
9408c2ecf20Sopenharmony_ci
9418c2ecf20Sopenharmony_ci	ret = qcom_smd_channel_open(channel, cb);
9428c2ecf20Sopenharmony_ci	if (ret)
9438c2ecf20Sopenharmony_ci		goto free_ept;
9448c2ecf20Sopenharmony_ci
9458c2ecf20Sopenharmony_ci	return ept;
9468c2ecf20Sopenharmony_ci
9478c2ecf20Sopenharmony_cifree_ept:
9488c2ecf20Sopenharmony_ci	channel->qsept = NULL;
9498c2ecf20Sopenharmony_ci	kref_put(&ept->refcount, __ept_release);
9508c2ecf20Sopenharmony_ci	return NULL;
9518c2ecf20Sopenharmony_ci}
9528c2ecf20Sopenharmony_ci
9538c2ecf20Sopenharmony_cistatic void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept)
9548c2ecf20Sopenharmony_ci{
9558c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
9568c2ecf20Sopenharmony_ci	struct qcom_smd_channel *ch = qsept->qsch;
9578c2ecf20Sopenharmony_ci
9588c2ecf20Sopenharmony_ci	qcom_smd_channel_close(ch);
9598c2ecf20Sopenharmony_ci	ch->qsept = NULL;
9608c2ecf20Sopenharmony_ci	kref_put(&ept->refcount, __ept_release);
9618c2ecf20Sopenharmony_ci}
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_cistatic int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len)
9648c2ecf20Sopenharmony_ci{
9658c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_ci	return __qcom_smd_send(qsept->qsch, data, len, true);
9688c2ecf20Sopenharmony_ci}
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_cistatic int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len)
9718c2ecf20Sopenharmony_ci{
9728c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
9738c2ecf20Sopenharmony_ci
9748c2ecf20Sopenharmony_ci	return __qcom_smd_send(qsept->qsch, data, len, false);
9758c2ecf20Sopenharmony_ci}
9768c2ecf20Sopenharmony_ci
9778c2ecf20Sopenharmony_cistatic __poll_t qcom_smd_poll(struct rpmsg_endpoint *ept,
9788c2ecf20Sopenharmony_ci				  struct file *filp, poll_table *wait)
9798c2ecf20Sopenharmony_ci{
9808c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
9818c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel = qsept->qsch;
9828c2ecf20Sopenharmony_ci	__poll_t mask = 0;
9838c2ecf20Sopenharmony_ci
9848c2ecf20Sopenharmony_ci	poll_wait(filp, &channel->fblockread_event, wait);
9858c2ecf20Sopenharmony_ci
9868c2ecf20Sopenharmony_ci	if (qcom_smd_get_tx_avail(channel) > 20)
9878c2ecf20Sopenharmony_ci		mask |= EPOLLOUT | EPOLLWRNORM;
9888c2ecf20Sopenharmony_ci
9898c2ecf20Sopenharmony_ci	return mask;
9908c2ecf20Sopenharmony_ci}
9918c2ecf20Sopenharmony_ci
9928c2ecf20Sopenharmony_ci/*
9938c2ecf20Sopenharmony_ci * Finds the device_node for the smd child interested in this channel.
9948c2ecf20Sopenharmony_ci */
9958c2ecf20Sopenharmony_cistatic struct device_node *qcom_smd_match_channel(struct device_node *edge_node,
9968c2ecf20Sopenharmony_ci						  const char *channel)
9978c2ecf20Sopenharmony_ci{
9988c2ecf20Sopenharmony_ci	struct device_node *child;
9998c2ecf20Sopenharmony_ci	const char *name;
10008c2ecf20Sopenharmony_ci	const char *key;
10018c2ecf20Sopenharmony_ci	int ret;
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	for_each_available_child_of_node(edge_node, child) {
10048c2ecf20Sopenharmony_ci		key = "qcom,smd-channels";
10058c2ecf20Sopenharmony_ci		ret = of_property_read_string(child, key, &name);
10068c2ecf20Sopenharmony_ci		if (ret)
10078c2ecf20Sopenharmony_ci			continue;
10088c2ecf20Sopenharmony_ci
10098c2ecf20Sopenharmony_ci		if (strcmp(name, channel) == 0)
10108c2ecf20Sopenharmony_ci			return child;
10118c2ecf20Sopenharmony_ci	}
10128c2ecf20Sopenharmony_ci
10138c2ecf20Sopenharmony_ci	return NULL;
10148c2ecf20Sopenharmony_ci}
10158c2ecf20Sopenharmony_ci
10168c2ecf20Sopenharmony_cistatic int qcom_smd_announce_create(struct rpmsg_device *rpdev)
10178c2ecf20Sopenharmony_ci{
10188c2ecf20Sopenharmony_ci	struct qcom_smd_endpoint *qept = to_smd_endpoint(rpdev->ept);
10198c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel = qept->qsch;
10208c2ecf20Sopenharmony_ci	unsigned long flags;
10218c2ecf20Sopenharmony_ci	bool kick_state;
10228c2ecf20Sopenharmony_ci
10238c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->recv_lock, flags);
10248c2ecf20Sopenharmony_ci	kick_state = qcom_smd_channel_intr(channel);
10258c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->recv_lock, flags);
10268c2ecf20Sopenharmony_ci
10278c2ecf20Sopenharmony_ci	if (kick_state)
10288c2ecf20Sopenharmony_ci		schedule_work(&channel->edge->state_work);
10298c2ecf20Sopenharmony_ci
10308c2ecf20Sopenharmony_ci	return 0;
10318c2ecf20Sopenharmony_ci}
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_cistatic const struct rpmsg_device_ops qcom_smd_device_ops = {
10348c2ecf20Sopenharmony_ci	.create_ept = qcom_smd_create_ept,
10358c2ecf20Sopenharmony_ci	.announce_create = qcom_smd_announce_create,
10368c2ecf20Sopenharmony_ci};
10378c2ecf20Sopenharmony_ci
10388c2ecf20Sopenharmony_cistatic const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = {
10398c2ecf20Sopenharmony_ci	.destroy_ept = qcom_smd_destroy_ept,
10408c2ecf20Sopenharmony_ci	.send = qcom_smd_send,
10418c2ecf20Sopenharmony_ci	.trysend = qcom_smd_trysend,
10428c2ecf20Sopenharmony_ci	.poll = qcom_smd_poll,
10438c2ecf20Sopenharmony_ci};
10448c2ecf20Sopenharmony_ci
10458c2ecf20Sopenharmony_cistatic void qcom_smd_release_device(struct device *dev)
10468c2ecf20Sopenharmony_ci{
10478c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev = to_rpmsg_device(dev);
10488c2ecf20Sopenharmony_ci	struct qcom_smd_device *qsdev = to_smd_device(rpdev);
10498c2ecf20Sopenharmony_ci
10508c2ecf20Sopenharmony_ci	kfree(qsdev);
10518c2ecf20Sopenharmony_ci}
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_ci/*
10548c2ecf20Sopenharmony_ci * Create a smd client device for channel that is being opened.
10558c2ecf20Sopenharmony_ci */
10568c2ecf20Sopenharmony_cistatic int qcom_smd_create_device(struct qcom_smd_channel *channel)
10578c2ecf20Sopenharmony_ci{
10588c2ecf20Sopenharmony_ci	struct qcom_smd_device *qsdev;
10598c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev;
10608c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = channel->edge;
10618c2ecf20Sopenharmony_ci
10628c2ecf20Sopenharmony_ci	dev_dbg(&edge->dev, "registering '%s'\n", channel->name);
10638c2ecf20Sopenharmony_ci
10648c2ecf20Sopenharmony_ci	qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
10658c2ecf20Sopenharmony_ci	if (!qsdev)
10668c2ecf20Sopenharmony_ci		return -ENOMEM;
10678c2ecf20Sopenharmony_ci
10688c2ecf20Sopenharmony_ci	/* Link qsdev to our SMD edge */
10698c2ecf20Sopenharmony_ci	qsdev->edge = edge;
10708c2ecf20Sopenharmony_ci
10718c2ecf20Sopenharmony_ci	/* Assign callbacks for rpmsg_device */
10728c2ecf20Sopenharmony_ci	qsdev->rpdev.ops = &qcom_smd_device_ops;
10738c2ecf20Sopenharmony_ci
10748c2ecf20Sopenharmony_ci	/* Assign public information to the rpmsg_device */
10758c2ecf20Sopenharmony_ci	rpdev = &qsdev->rpdev;
10768c2ecf20Sopenharmony_ci	strscpy_pad(rpdev->id.name, channel->name, RPMSG_NAME_SIZE);
10778c2ecf20Sopenharmony_ci	rpdev->src = RPMSG_ADDR_ANY;
10788c2ecf20Sopenharmony_ci	rpdev->dst = RPMSG_ADDR_ANY;
10798c2ecf20Sopenharmony_ci
10808c2ecf20Sopenharmony_ci	rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name);
10818c2ecf20Sopenharmony_ci	rpdev->dev.parent = &edge->dev;
10828c2ecf20Sopenharmony_ci	rpdev->dev.release = qcom_smd_release_device;
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_ci	return rpmsg_register_device(rpdev);
10858c2ecf20Sopenharmony_ci}
10868c2ecf20Sopenharmony_ci
10878c2ecf20Sopenharmony_cistatic int qcom_smd_create_chrdev(struct qcom_smd_edge *edge)
10888c2ecf20Sopenharmony_ci{
10898c2ecf20Sopenharmony_ci	struct qcom_smd_device *qsdev;
10908c2ecf20Sopenharmony_ci
10918c2ecf20Sopenharmony_ci	qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
10928c2ecf20Sopenharmony_ci	if (!qsdev)
10938c2ecf20Sopenharmony_ci		return -ENOMEM;
10948c2ecf20Sopenharmony_ci
10958c2ecf20Sopenharmony_ci	qsdev->edge = edge;
10968c2ecf20Sopenharmony_ci	qsdev->rpdev.ops = &qcom_smd_device_ops;
10978c2ecf20Sopenharmony_ci	qsdev->rpdev.dev.parent = &edge->dev;
10988c2ecf20Sopenharmony_ci	qsdev->rpdev.dev.release = qcom_smd_release_device;
10998c2ecf20Sopenharmony_ci
11008c2ecf20Sopenharmony_ci	return rpmsg_chrdev_register_device(&qsdev->rpdev);
11018c2ecf20Sopenharmony_ci}
11028c2ecf20Sopenharmony_ci
11038c2ecf20Sopenharmony_ci/*
11048c2ecf20Sopenharmony_ci * Allocate the qcom_smd_channel object for a newly found smd channel,
11058c2ecf20Sopenharmony_ci * retrieving and validating the smem items involved.
11068c2ecf20Sopenharmony_ci */
11078c2ecf20Sopenharmony_cistatic struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge,
11088c2ecf20Sopenharmony_ci							unsigned smem_info_item,
11098c2ecf20Sopenharmony_ci							unsigned smem_fifo_item,
11108c2ecf20Sopenharmony_ci							char *name)
11118c2ecf20Sopenharmony_ci{
11128c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
11138c2ecf20Sopenharmony_ci	size_t fifo_size;
11148c2ecf20Sopenharmony_ci	size_t info_size;
11158c2ecf20Sopenharmony_ci	void *fifo_base;
11168c2ecf20Sopenharmony_ci	void *info;
11178c2ecf20Sopenharmony_ci	int ret;
11188c2ecf20Sopenharmony_ci
11198c2ecf20Sopenharmony_ci	channel = kzalloc(sizeof(*channel), GFP_KERNEL);
11208c2ecf20Sopenharmony_ci	if (!channel)
11218c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
11228c2ecf20Sopenharmony_ci
11238c2ecf20Sopenharmony_ci	channel->edge = edge;
11248c2ecf20Sopenharmony_ci	channel->name = kstrdup(name, GFP_KERNEL);
11258c2ecf20Sopenharmony_ci	if (!channel->name) {
11268c2ecf20Sopenharmony_ci		ret = -ENOMEM;
11278c2ecf20Sopenharmony_ci		goto free_channel;
11288c2ecf20Sopenharmony_ci	}
11298c2ecf20Sopenharmony_ci
11308c2ecf20Sopenharmony_ci	spin_lock_init(&channel->tx_lock);
11318c2ecf20Sopenharmony_ci	spin_lock_init(&channel->recv_lock);
11328c2ecf20Sopenharmony_ci	init_waitqueue_head(&channel->fblockread_event);
11338c2ecf20Sopenharmony_ci	init_waitqueue_head(&channel->state_change_event);
11348c2ecf20Sopenharmony_ci
11358c2ecf20Sopenharmony_ci	info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size);
11368c2ecf20Sopenharmony_ci	if (IS_ERR(info)) {
11378c2ecf20Sopenharmony_ci		ret = PTR_ERR(info);
11388c2ecf20Sopenharmony_ci		goto free_name_and_channel;
11398c2ecf20Sopenharmony_ci	}
11408c2ecf20Sopenharmony_ci
11418c2ecf20Sopenharmony_ci	/*
11428c2ecf20Sopenharmony_ci	 * Use the size of the item to figure out which channel info struct to
11438c2ecf20Sopenharmony_ci	 * use.
11448c2ecf20Sopenharmony_ci	 */
11458c2ecf20Sopenharmony_ci	if (info_size == 2 * sizeof(struct smd_channel_info_word)) {
11468c2ecf20Sopenharmony_ci		channel->info_word = info;
11478c2ecf20Sopenharmony_ci	} else if (info_size == 2 * sizeof(struct smd_channel_info)) {
11488c2ecf20Sopenharmony_ci		channel->info = info;
11498c2ecf20Sopenharmony_ci	} else {
11508c2ecf20Sopenharmony_ci		dev_err(&edge->dev,
11518c2ecf20Sopenharmony_ci			"channel info of size %zu not supported\n", info_size);
11528c2ecf20Sopenharmony_ci		ret = -EINVAL;
11538c2ecf20Sopenharmony_ci		goto free_name_and_channel;
11548c2ecf20Sopenharmony_ci	}
11558c2ecf20Sopenharmony_ci
11568c2ecf20Sopenharmony_ci	fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size);
11578c2ecf20Sopenharmony_ci	if (IS_ERR(fifo_base)) {
11588c2ecf20Sopenharmony_ci		ret =  PTR_ERR(fifo_base);
11598c2ecf20Sopenharmony_ci		goto free_name_and_channel;
11608c2ecf20Sopenharmony_ci	}
11618c2ecf20Sopenharmony_ci
11628c2ecf20Sopenharmony_ci	/* The channel consist of a rx and tx fifo of equal size */
11638c2ecf20Sopenharmony_ci	fifo_size /= 2;
11648c2ecf20Sopenharmony_ci
11658c2ecf20Sopenharmony_ci	dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
11668c2ecf20Sopenharmony_ci			  name, info_size, fifo_size);
11678c2ecf20Sopenharmony_ci
11688c2ecf20Sopenharmony_ci	channel->tx_fifo = fifo_base;
11698c2ecf20Sopenharmony_ci	channel->rx_fifo = fifo_base + fifo_size;
11708c2ecf20Sopenharmony_ci	channel->fifo_size = fifo_size;
11718c2ecf20Sopenharmony_ci
11728c2ecf20Sopenharmony_ci	qcom_smd_channel_reset(channel);
11738c2ecf20Sopenharmony_ci
11748c2ecf20Sopenharmony_ci	return channel;
11758c2ecf20Sopenharmony_ci
11768c2ecf20Sopenharmony_cifree_name_and_channel:
11778c2ecf20Sopenharmony_ci	kfree(channel->name);
11788c2ecf20Sopenharmony_cifree_channel:
11798c2ecf20Sopenharmony_ci	kfree(channel);
11808c2ecf20Sopenharmony_ci
11818c2ecf20Sopenharmony_ci	return ERR_PTR(ret);
11828c2ecf20Sopenharmony_ci}
11838c2ecf20Sopenharmony_ci
11848c2ecf20Sopenharmony_ci/*
11858c2ecf20Sopenharmony_ci * Scans the allocation table for any newly allocated channels, calls
11868c2ecf20Sopenharmony_ci * qcom_smd_create_channel() to create representations of these and add
11878c2ecf20Sopenharmony_ci * them to the edge's list of channels.
11888c2ecf20Sopenharmony_ci */
11898c2ecf20Sopenharmony_cistatic void qcom_channel_scan_worker(struct work_struct *work)
11908c2ecf20Sopenharmony_ci{
11918c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work);
11928c2ecf20Sopenharmony_ci	struct qcom_smd_alloc_entry *alloc_tbl;
11938c2ecf20Sopenharmony_ci	struct qcom_smd_alloc_entry *entry;
11948c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
11958c2ecf20Sopenharmony_ci	unsigned long flags;
11968c2ecf20Sopenharmony_ci	unsigned fifo_id;
11978c2ecf20Sopenharmony_ci	unsigned info_id;
11988c2ecf20Sopenharmony_ci	int tbl;
11998c2ecf20Sopenharmony_ci	int i;
12008c2ecf20Sopenharmony_ci	u32 eflags, cid;
12018c2ecf20Sopenharmony_ci
12028c2ecf20Sopenharmony_ci	for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) {
12038c2ecf20Sopenharmony_ci		alloc_tbl = qcom_smem_get(edge->remote_pid,
12048c2ecf20Sopenharmony_ci				    smem_items[tbl].alloc_tbl_id, NULL);
12058c2ecf20Sopenharmony_ci		if (IS_ERR(alloc_tbl))
12068c2ecf20Sopenharmony_ci			continue;
12078c2ecf20Sopenharmony_ci
12088c2ecf20Sopenharmony_ci		for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) {
12098c2ecf20Sopenharmony_ci			entry = &alloc_tbl[i];
12108c2ecf20Sopenharmony_ci			eflags = le32_to_cpu(entry->flags);
12118c2ecf20Sopenharmony_ci			if (test_bit(i, edge->allocated[tbl]))
12128c2ecf20Sopenharmony_ci				continue;
12138c2ecf20Sopenharmony_ci
12148c2ecf20Sopenharmony_ci			if (entry->ref_count == 0)
12158c2ecf20Sopenharmony_ci				continue;
12168c2ecf20Sopenharmony_ci
12178c2ecf20Sopenharmony_ci			if (!entry->name[0])
12188c2ecf20Sopenharmony_ci				continue;
12198c2ecf20Sopenharmony_ci
12208c2ecf20Sopenharmony_ci			if (!(eflags & SMD_CHANNEL_FLAGS_PACKET))
12218c2ecf20Sopenharmony_ci				continue;
12228c2ecf20Sopenharmony_ci
12238c2ecf20Sopenharmony_ci			if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id)
12248c2ecf20Sopenharmony_ci				continue;
12258c2ecf20Sopenharmony_ci
12268c2ecf20Sopenharmony_ci			cid = le32_to_cpu(entry->cid);
12278c2ecf20Sopenharmony_ci			info_id = smem_items[tbl].info_base_id + cid;
12288c2ecf20Sopenharmony_ci			fifo_id = smem_items[tbl].fifo_base_id + cid;
12298c2ecf20Sopenharmony_ci
12308c2ecf20Sopenharmony_ci			channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name);
12318c2ecf20Sopenharmony_ci			if (IS_ERR(channel))
12328c2ecf20Sopenharmony_ci				continue;
12338c2ecf20Sopenharmony_ci
12348c2ecf20Sopenharmony_ci			spin_lock_irqsave(&edge->channels_lock, flags);
12358c2ecf20Sopenharmony_ci			list_add(&channel->list, &edge->channels);
12368c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&edge->channels_lock, flags);
12378c2ecf20Sopenharmony_ci
12388c2ecf20Sopenharmony_ci			dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name);
12398c2ecf20Sopenharmony_ci			set_bit(i, edge->allocated[tbl]);
12408c2ecf20Sopenharmony_ci
12418c2ecf20Sopenharmony_ci			wake_up_interruptible_all(&edge->new_channel_event);
12428c2ecf20Sopenharmony_ci		}
12438c2ecf20Sopenharmony_ci	}
12448c2ecf20Sopenharmony_ci
12458c2ecf20Sopenharmony_ci	schedule_work(&edge->state_work);
12468c2ecf20Sopenharmony_ci}
12478c2ecf20Sopenharmony_ci
12488c2ecf20Sopenharmony_ci/*
12498c2ecf20Sopenharmony_ci * This per edge worker scans smem for any new channels and register these. It
12508c2ecf20Sopenharmony_ci * then scans all registered channels for state changes that should be handled
12518c2ecf20Sopenharmony_ci * by creating or destroying smd client devices for the registered channels.
12528c2ecf20Sopenharmony_ci *
12538c2ecf20Sopenharmony_ci * LOCKING: edge->channels_lock only needs to cover the list operations, as the
12548c2ecf20Sopenharmony_ci * worker is killed before any channels are deallocated
12558c2ecf20Sopenharmony_ci */
12568c2ecf20Sopenharmony_cistatic void qcom_channel_state_worker(struct work_struct *work)
12578c2ecf20Sopenharmony_ci{
12588c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel;
12598c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = container_of(work,
12608c2ecf20Sopenharmony_ci						  struct qcom_smd_edge,
12618c2ecf20Sopenharmony_ci						  state_work);
12628c2ecf20Sopenharmony_ci	struct rpmsg_channel_info chinfo;
12638c2ecf20Sopenharmony_ci	unsigned remote_state;
12648c2ecf20Sopenharmony_ci	unsigned long flags;
12658c2ecf20Sopenharmony_ci
12668c2ecf20Sopenharmony_ci	/*
12678c2ecf20Sopenharmony_ci	 * Register a device for any closed channel where the remote processor
12688c2ecf20Sopenharmony_ci	 * is showing interest in opening the channel.
12698c2ecf20Sopenharmony_ci	 */
12708c2ecf20Sopenharmony_ci	spin_lock_irqsave(&edge->channels_lock, flags);
12718c2ecf20Sopenharmony_ci	list_for_each_entry(channel, &edge->channels, list) {
12728c2ecf20Sopenharmony_ci		if (channel->state != SMD_CHANNEL_CLOSED)
12738c2ecf20Sopenharmony_ci			continue;
12748c2ecf20Sopenharmony_ci
12758c2ecf20Sopenharmony_ci		remote_state = GET_RX_CHANNEL_INFO(channel, state);
12768c2ecf20Sopenharmony_ci		if (remote_state != SMD_CHANNEL_OPENING &&
12778c2ecf20Sopenharmony_ci		    remote_state != SMD_CHANNEL_OPENED)
12788c2ecf20Sopenharmony_ci			continue;
12798c2ecf20Sopenharmony_ci
12808c2ecf20Sopenharmony_ci		if (channel->registered)
12818c2ecf20Sopenharmony_ci			continue;
12828c2ecf20Sopenharmony_ci
12838c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&edge->channels_lock, flags);
12848c2ecf20Sopenharmony_ci		qcom_smd_create_device(channel);
12858c2ecf20Sopenharmony_ci		channel->registered = true;
12868c2ecf20Sopenharmony_ci		spin_lock_irqsave(&edge->channels_lock, flags);
12878c2ecf20Sopenharmony_ci
12888c2ecf20Sopenharmony_ci		channel->registered = true;
12898c2ecf20Sopenharmony_ci	}
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci	/*
12928c2ecf20Sopenharmony_ci	 * Unregister the device for any channel that is opened where the
12938c2ecf20Sopenharmony_ci	 * remote processor is closing the channel.
12948c2ecf20Sopenharmony_ci	 */
12958c2ecf20Sopenharmony_ci	list_for_each_entry(channel, &edge->channels, list) {
12968c2ecf20Sopenharmony_ci		if (channel->state != SMD_CHANNEL_OPENING &&
12978c2ecf20Sopenharmony_ci		    channel->state != SMD_CHANNEL_OPENED)
12988c2ecf20Sopenharmony_ci			continue;
12998c2ecf20Sopenharmony_ci
13008c2ecf20Sopenharmony_ci		remote_state = GET_RX_CHANNEL_INFO(channel, state);
13018c2ecf20Sopenharmony_ci		if (remote_state == SMD_CHANNEL_OPENING ||
13028c2ecf20Sopenharmony_ci		    remote_state == SMD_CHANNEL_OPENED)
13038c2ecf20Sopenharmony_ci			continue;
13048c2ecf20Sopenharmony_ci
13058c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&edge->channels_lock, flags);
13068c2ecf20Sopenharmony_ci
13078c2ecf20Sopenharmony_ci		strscpy_pad(chinfo.name, channel->name, sizeof(chinfo.name));
13088c2ecf20Sopenharmony_ci		chinfo.src = RPMSG_ADDR_ANY;
13098c2ecf20Sopenharmony_ci		chinfo.dst = RPMSG_ADDR_ANY;
13108c2ecf20Sopenharmony_ci		rpmsg_unregister_device(&edge->dev, &chinfo);
13118c2ecf20Sopenharmony_ci		channel->registered = false;
13128c2ecf20Sopenharmony_ci		spin_lock_irqsave(&edge->channels_lock, flags);
13138c2ecf20Sopenharmony_ci	}
13148c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&edge->channels_lock, flags);
13158c2ecf20Sopenharmony_ci}
13168c2ecf20Sopenharmony_ci
13178c2ecf20Sopenharmony_ci/*
13188c2ecf20Sopenharmony_ci * Parses an of_node describing an edge.
13198c2ecf20Sopenharmony_ci */
13208c2ecf20Sopenharmony_cistatic int qcom_smd_parse_edge(struct device *dev,
13218c2ecf20Sopenharmony_ci			       struct device_node *node,
13228c2ecf20Sopenharmony_ci			       struct qcom_smd_edge *edge)
13238c2ecf20Sopenharmony_ci{
13248c2ecf20Sopenharmony_ci	struct device_node *syscon_np;
13258c2ecf20Sopenharmony_ci	const char *key;
13268c2ecf20Sopenharmony_ci	int irq;
13278c2ecf20Sopenharmony_ci	int ret;
13288c2ecf20Sopenharmony_ci
13298c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&edge->channels);
13308c2ecf20Sopenharmony_ci	spin_lock_init(&edge->channels_lock);
13318c2ecf20Sopenharmony_ci
13328c2ecf20Sopenharmony_ci	INIT_WORK(&edge->scan_work, qcom_channel_scan_worker);
13338c2ecf20Sopenharmony_ci	INIT_WORK(&edge->state_work, qcom_channel_state_worker);
13348c2ecf20Sopenharmony_ci
13358c2ecf20Sopenharmony_ci	edge->of_node = of_node_get(node);
13368c2ecf20Sopenharmony_ci
13378c2ecf20Sopenharmony_ci	key = "qcom,smd-edge";
13388c2ecf20Sopenharmony_ci	ret = of_property_read_u32(node, key, &edge->edge_id);
13398c2ecf20Sopenharmony_ci	if (ret) {
13408c2ecf20Sopenharmony_ci		dev_err(dev, "edge missing %s property\n", key);
13418c2ecf20Sopenharmony_ci		goto put_node;
13428c2ecf20Sopenharmony_ci	}
13438c2ecf20Sopenharmony_ci
13448c2ecf20Sopenharmony_ci	edge->remote_pid = QCOM_SMEM_HOST_ANY;
13458c2ecf20Sopenharmony_ci	key = "qcom,remote-pid";
13468c2ecf20Sopenharmony_ci	of_property_read_u32(node, key, &edge->remote_pid);
13478c2ecf20Sopenharmony_ci
13488c2ecf20Sopenharmony_ci	edge->mbox_client.dev = dev;
13498c2ecf20Sopenharmony_ci	edge->mbox_client.knows_txdone = true;
13508c2ecf20Sopenharmony_ci	edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0);
13518c2ecf20Sopenharmony_ci	if (IS_ERR(edge->mbox_chan)) {
13528c2ecf20Sopenharmony_ci		if (PTR_ERR(edge->mbox_chan) != -ENODEV) {
13538c2ecf20Sopenharmony_ci			ret = PTR_ERR(edge->mbox_chan);
13548c2ecf20Sopenharmony_ci			goto put_node;
13558c2ecf20Sopenharmony_ci		}
13568c2ecf20Sopenharmony_ci
13578c2ecf20Sopenharmony_ci		edge->mbox_chan = NULL;
13588c2ecf20Sopenharmony_ci
13598c2ecf20Sopenharmony_ci		syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
13608c2ecf20Sopenharmony_ci		if (!syscon_np) {
13618c2ecf20Sopenharmony_ci			dev_err(dev, "no qcom,ipc node\n");
13628c2ecf20Sopenharmony_ci			ret = -ENODEV;
13638c2ecf20Sopenharmony_ci			goto put_node;
13648c2ecf20Sopenharmony_ci		}
13658c2ecf20Sopenharmony_ci
13668c2ecf20Sopenharmony_ci		edge->ipc_regmap = syscon_node_to_regmap(syscon_np);
13678c2ecf20Sopenharmony_ci		of_node_put(syscon_np);
13688c2ecf20Sopenharmony_ci		if (IS_ERR(edge->ipc_regmap)) {
13698c2ecf20Sopenharmony_ci			ret = PTR_ERR(edge->ipc_regmap);
13708c2ecf20Sopenharmony_ci			goto put_node;
13718c2ecf20Sopenharmony_ci		}
13728c2ecf20Sopenharmony_ci
13738c2ecf20Sopenharmony_ci		key = "qcom,ipc";
13748c2ecf20Sopenharmony_ci		ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset);
13758c2ecf20Sopenharmony_ci		if (ret < 0) {
13768c2ecf20Sopenharmony_ci			dev_err(dev, "no offset in %s\n", key);
13778c2ecf20Sopenharmony_ci			goto put_node;
13788c2ecf20Sopenharmony_ci		}
13798c2ecf20Sopenharmony_ci
13808c2ecf20Sopenharmony_ci		ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit);
13818c2ecf20Sopenharmony_ci		if (ret < 0) {
13828c2ecf20Sopenharmony_ci			dev_err(dev, "no bit in %s\n", key);
13838c2ecf20Sopenharmony_ci			goto put_node;
13848c2ecf20Sopenharmony_ci		}
13858c2ecf20Sopenharmony_ci	}
13868c2ecf20Sopenharmony_ci
13878c2ecf20Sopenharmony_ci	ret = of_property_read_string(node, "label", &edge->name);
13888c2ecf20Sopenharmony_ci	if (ret < 0)
13898c2ecf20Sopenharmony_ci		edge->name = node->name;
13908c2ecf20Sopenharmony_ci
13918c2ecf20Sopenharmony_ci	irq = irq_of_parse_and_map(node, 0);
13928c2ecf20Sopenharmony_ci	if (!irq) {
13938c2ecf20Sopenharmony_ci		dev_err(dev, "required smd interrupt missing\n");
13948c2ecf20Sopenharmony_ci		ret = -EINVAL;
13958c2ecf20Sopenharmony_ci		goto put_node;
13968c2ecf20Sopenharmony_ci	}
13978c2ecf20Sopenharmony_ci
13988c2ecf20Sopenharmony_ci	ret = devm_request_irq(dev, irq,
13998c2ecf20Sopenharmony_ci			       qcom_smd_edge_intr, IRQF_TRIGGER_RISING,
14008c2ecf20Sopenharmony_ci			       node->name, edge);
14018c2ecf20Sopenharmony_ci	if (ret) {
14028c2ecf20Sopenharmony_ci		dev_err(dev, "failed to request smd irq\n");
14038c2ecf20Sopenharmony_ci		goto put_node;
14048c2ecf20Sopenharmony_ci	}
14058c2ecf20Sopenharmony_ci
14068c2ecf20Sopenharmony_ci	edge->irq = irq;
14078c2ecf20Sopenharmony_ci
14088c2ecf20Sopenharmony_ci	return 0;
14098c2ecf20Sopenharmony_ci
14108c2ecf20Sopenharmony_ciput_node:
14118c2ecf20Sopenharmony_ci	of_node_put(node);
14128c2ecf20Sopenharmony_ci	edge->of_node = NULL;
14138c2ecf20Sopenharmony_ci
14148c2ecf20Sopenharmony_ci	return ret;
14158c2ecf20Sopenharmony_ci}
14168c2ecf20Sopenharmony_ci
14178c2ecf20Sopenharmony_ci/*
14188c2ecf20Sopenharmony_ci * Release function for an edge.
14198c2ecf20Sopenharmony_ci  * Reset the state of each associated channel and free the edge context.
14208c2ecf20Sopenharmony_ci */
14218c2ecf20Sopenharmony_cistatic void qcom_smd_edge_release(struct device *dev)
14228c2ecf20Sopenharmony_ci{
14238c2ecf20Sopenharmony_ci	struct qcom_smd_channel *channel, *tmp;
14248c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = to_smd_edge(dev);
14258c2ecf20Sopenharmony_ci
14268c2ecf20Sopenharmony_ci	list_for_each_entry_safe(channel, tmp, &edge->channels, list) {
14278c2ecf20Sopenharmony_ci		list_del(&channel->list);
14288c2ecf20Sopenharmony_ci		kfree(channel->name);
14298c2ecf20Sopenharmony_ci		kfree(channel);
14308c2ecf20Sopenharmony_ci	}
14318c2ecf20Sopenharmony_ci
14328c2ecf20Sopenharmony_ci	kfree(edge);
14338c2ecf20Sopenharmony_ci}
14348c2ecf20Sopenharmony_ci
14358c2ecf20Sopenharmony_cistatic ssize_t rpmsg_name_show(struct device *dev,
14368c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *buf)
14378c2ecf20Sopenharmony_ci{
14388c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = to_smd_edge(dev);
14398c2ecf20Sopenharmony_ci
14408c2ecf20Sopenharmony_ci	return sprintf(buf, "%s\n", edge->name);
14418c2ecf20Sopenharmony_ci}
14428c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(rpmsg_name);
14438c2ecf20Sopenharmony_ci
14448c2ecf20Sopenharmony_cistatic struct attribute *qcom_smd_edge_attrs[] = {
14458c2ecf20Sopenharmony_ci	&dev_attr_rpmsg_name.attr,
14468c2ecf20Sopenharmony_ci	NULL
14478c2ecf20Sopenharmony_ci};
14488c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(qcom_smd_edge);
14498c2ecf20Sopenharmony_ci
14508c2ecf20Sopenharmony_ci/**
14518c2ecf20Sopenharmony_ci * qcom_smd_register_edge() - register an edge based on an device_node
14528c2ecf20Sopenharmony_ci * @parent:    parent device for the edge
14538c2ecf20Sopenharmony_ci * @node:      device_node describing the edge
14548c2ecf20Sopenharmony_ci *
14558c2ecf20Sopenharmony_ci * Returns an edge reference, or negative ERR_PTR() on failure.
14568c2ecf20Sopenharmony_ci */
14578c2ecf20Sopenharmony_cistruct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
14588c2ecf20Sopenharmony_ci					     struct device_node *node)
14598c2ecf20Sopenharmony_ci{
14608c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge;
14618c2ecf20Sopenharmony_ci	int ret;
14628c2ecf20Sopenharmony_ci
14638c2ecf20Sopenharmony_ci	edge = kzalloc(sizeof(*edge), GFP_KERNEL);
14648c2ecf20Sopenharmony_ci	if (!edge)
14658c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
14668c2ecf20Sopenharmony_ci
14678c2ecf20Sopenharmony_ci	init_waitqueue_head(&edge->new_channel_event);
14688c2ecf20Sopenharmony_ci
14698c2ecf20Sopenharmony_ci	edge->dev.parent = parent;
14708c2ecf20Sopenharmony_ci	edge->dev.release = qcom_smd_edge_release;
14718c2ecf20Sopenharmony_ci	edge->dev.of_node = node;
14728c2ecf20Sopenharmony_ci	edge->dev.groups = qcom_smd_edge_groups;
14738c2ecf20Sopenharmony_ci	dev_set_name(&edge->dev, "%s:%pOFn", dev_name(parent), node);
14748c2ecf20Sopenharmony_ci	ret = device_register(&edge->dev);
14758c2ecf20Sopenharmony_ci	if (ret) {
14768c2ecf20Sopenharmony_ci		pr_err("failed to register smd edge\n");
14778c2ecf20Sopenharmony_ci		put_device(&edge->dev);
14788c2ecf20Sopenharmony_ci		return ERR_PTR(ret);
14798c2ecf20Sopenharmony_ci	}
14808c2ecf20Sopenharmony_ci
14818c2ecf20Sopenharmony_ci	ret = qcom_smd_parse_edge(&edge->dev, node, edge);
14828c2ecf20Sopenharmony_ci	if (ret) {
14838c2ecf20Sopenharmony_ci		dev_err(&edge->dev, "failed to parse smd edge\n");
14848c2ecf20Sopenharmony_ci		goto unregister_dev;
14858c2ecf20Sopenharmony_ci	}
14868c2ecf20Sopenharmony_ci
14878c2ecf20Sopenharmony_ci	ret = qcom_smd_create_chrdev(edge);
14888c2ecf20Sopenharmony_ci	if (ret) {
14898c2ecf20Sopenharmony_ci		dev_err(&edge->dev, "failed to register chrdev for edge\n");
14908c2ecf20Sopenharmony_ci		goto unregister_dev;
14918c2ecf20Sopenharmony_ci	}
14928c2ecf20Sopenharmony_ci
14938c2ecf20Sopenharmony_ci	schedule_work(&edge->scan_work);
14948c2ecf20Sopenharmony_ci
14958c2ecf20Sopenharmony_ci	return edge;
14968c2ecf20Sopenharmony_ci
14978c2ecf20Sopenharmony_ciunregister_dev:
14988c2ecf20Sopenharmony_ci	if (!IS_ERR_OR_NULL(edge->mbox_chan))
14998c2ecf20Sopenharmony_ci		mbox_free_channel(edge->mbox_chan);
15008c2ecf20Sopenharmony_ci
15018c2ecf20Sopenharmony_ci	device_unregister(&edge->dev);
15028c2ecf20Sopenharmony_ci	return ERR_PTR(ret);
15038c2ecf20Sopenharmony_ci}
15048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qcom_smd_register_edge);
15058c2ecf20Sopenharmony_ci
15068c2ecf20Sopenharmony_cistatic int qcom_smd_remove_device(struct device *dev, void *data)
15078c2ecf20Sopenharmony_ci{
15088c2ecf20Sopenharmony_ci	device_unregister(dev);
15098c2ecf20Sopenharmony_ci
15108c2ecf20Sopenharmony_ci	return 0;
15118c2ecf20Sopenharmony_ci}
15128c2ecf20Sopenharmony_ci
15138c2ecf20Sopenharmony_ci/**
15148c2ecf20Sopenharmony_ci * qcom_smd_unregister_edge() - release an edge and its children
15158c2ecf20Sopenharmony_ci * @edge:      edge reference acquired from qcom_smd_register_edge
15168c2ecf20Sopenharmony_ci */
15178c2ecf20Sopenharmony_ciint qcom_smd_unregister_edge(struct qcom_smd_edge *edge)
15188c2ecf20Sopenharmony_ci{
15198c2ecf20Sopenharmony_ci	int ret;
15208c2ecf20Sopenharmony_ci
15218c2ecf20Sopenharmony_ci	disable_irq(edge->irq);
15228c2ecf20Sopenharmony_ci	cancel_work_sync(&edge->scan_work);
15238c2ecf20Sopenharmony_ci	cancel_work_sync(&edge->state_work);
15248c2ecf20Sopenharmony_ci
15258c2ecf20Sopenharmony_ci	ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device);
15268c2ecf20Sopenharmony_ci	if (ret)
15278c2ecf20Sopenharmony_ci		dev_warn(&edge->dev, "can't remove smd device: %d\n", ret);
15288c2ecf20Sopenharmony_ci
15298c2ecf20Sopenharmony_ci	mbox_free_channel(edge->mbox_chan);
15308c2ecf20Sopenharmony_ci	device_unregister(&edge->dev);
15318c2ecf20Sopenharmony_ci
15328c2ecf20Sopenharmony_ci	return 0;
15338c2ecf20Sopenharmony_ci}
15348c2ecf20Sopenharmony_ciEXPORT_SYMBOL(qcom_smd_unregister_edge);
15358c2ecf20Sopenharmony_ci
15368c2ecf20Sopenharmony_cistatic int qcom_smd_probe(struct platform_device *pdev)
15378c2ecf20Sopenharmony_ci{
15388c2ecf20Sopenharmony_ci	struct device_node *node;
15398c2ecf20Sopenharmony_ci	void *p;
15408c2ecf20Sopenharmony_ci
15418c2ecf20Sopenharmony_ci	/* Wait for smem */
15428c2ecf20Sopenharmony_ci	p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL);
15438c2ecf20Sopenharmony_ci	if (PTR_ERR(p) == -EPROBE_DEFER)
15448c2ecf20Sopenharmony_ci		return PTR_ERR(p);
15458c2ecf20Sopenharmony_ci
15468c2ecf20Sopenharmony_ci	for_each_available_child_of_node(pdev->dev.of_node, node)
15478c2ecf20Sopenharmony_ci		qcom_smd_register_edge(&pdev->dev, node);
15488c2ecf20Sopenharmony_ci
15498c2ecf20Sopenharmony_ci	return 0;
15508c2ecf20Sopenharmony_ci}
15518c2ecf20Sopenharmony_ci
15528c2ecf20Sopenharmony_cistatic int qcom_smd_remove_edge(struct device *dev, void *data)
15538c2ecf20Sopenharmony_ci{
15548c2ecf20Sopenharmony_ci	struct qcom_smd_edge *edge = to_smd_edge(dev);
15558c2ecf20Sopenharmony_ci
15568c2ecf20Sopenharmony_ci	return qcom_smd_unregister_edge(edge);
15578c2ecf20Sopenharmony_ci}
15588c2ecf20Sopenharmony_ci
15598c2ecf20Sopenharmony_ci/*
15608c2ecf20Sopenharmony_ci * Shut down all smd clients by making sure that each edge stops processing
15618c2ecf20Sopenharmony_ci * events and scanning for new channels, then call destroy on the devices.
15628c2ecf20Sopenharmony_ci */
15638c2ecf20Sopenharmony_cistatic int qcom_smd_remove(struct platform_device *pdev)
15648c2ecf20Sopenharmony_ci{
15658c2ecf20Sopenharmony_ci	int ret;
15668c2ecf20Sopenharmony_ci
15678c2ecf20Sopenharmony_ci	ret = device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge);
15688c2ecf20Sopenharmony_ci	if (ret)
15698c2ecf20Sopenharmony_ci		dev_warn(&pdev->dev, "can't remove smd device: %d\n", ret);
15708c2ecf20Sopenharmony_ci
15718c2ecf20Sopenharmony_ci	return ret;
15728c2ecf20Sopenharmony_ci}
15738c2ecf20Sopenharmony_ci
15748c2ecf20Sopenharmony_cistatic const struct of_device_id qcom_smd_of_match[] = {
15758c2ecf20Sopenharmony_ci	{ .compatible = "qcom,smd" },
15768c2ecf20Sopenharmony_ci	{}
15778c2ecf20Sopenharmony_ci};
15788c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_smd_of_match);
15798c2ecf20Sopenharmony_ci
15808c2ecf20Sopenharmony_cistatic struct platform_driver qcom_smd_driver = {
15818c2ecf20Sopenharmony_ci	.probe = qcom_smd_probe,
15828c2ecf20Sopenharmony_ci	.remove = qcom_smd_remove,
15838c2ecf20Sopenharmony_ci	.driver = {
15848c2ecf20Sopenharmony_ci		.name = "qcom-smd",
15858c2ecf20Sopenharmony_ci		.of_match_table = qcom_smd_of_match,
15868c2ecf20Sopenharmony_ci	},
15878c2ecf20Sopenharmony_ci};
15888c2ecf20Sopenharmony_ci
15898c2ecf20Sopenharmony_cistatic int __init qcom_smd_init(void)
15908c2ecf20Sopenharmony_ci{
15918c2ecf20Sopenharmony_ci	return platform_driver_register(&qcom_smd_driver);
15928c2ecf20Sopenharmony_ci}
15938c2ecf20Sopenharmony_cisubsys_initcall(qcom_smd_init);
15948c2ecf20Sopenharmony_ci
15958c2ecf20Sopenharmony_cistatic void __exit qcom_smd_exit(void)
15968c2ecf20Sopenharmony_ci{
15978c2ecf20Sopenharmony_ci	platform_driver_unregister(&qcom_smd_driver);
15988c2ecf20Sopenharmony_ci}
15998c2ecf20Sopenharmony_cimodule_exit(qcom_smd_exit);
16008c2ecf20Sopenharmony_ci
16018c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
16028c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Shared Memory Driver");
16038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1604