xref: /linux/drivers/rpmsg/qcom_smd.c (revision 8fc947230fbc5da1d6d198c758b894f847bf2a28)
153e2822eSBjorn Andersson /*
253e2822eSBjorn Andersson  * Copyright (c) 2015, Sony Mobile Communications AB.
353e2822eSBjorn Andersson  * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
453e2822eSBjorn Andersson  *
553e2822eSBjorn Andersson  * This program is free software; you can redistribute it and/or modify
653e2822eSBjorn Andersson  * it under the terms of the GNU General Public License version 2 and
753e2822eSBjorn Andersson  * only version 2 as published by the Free Software Foundation.
853e2822eSBjorn Andersson  *
953e2822eSBjorn Andersson  * This program is distributed in the hope that it will be useful,
1053e2822eSBjorn Andersson  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1153e2822eSBjorn Andersson  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1253e2822eSBjorn Andersson  * GNU General Public License for more details.
1353e2822eSBjorn Andersson  */
1453e2822eSBjorn Andersson 
1553e2822eSBjorn Andersson #include <linux/interrupt.h>
1653e2822eSBjorn Andersson #include <linux/io.h>
1753e2822eSBjorn Andersson #include <linux/mfd/syscon.h>
1853e2822eSBjorn Andersson #include <linux/module.h>
1953e2822eSBjorn Andersson #include <linux/of_irq.h>
2053e2822eSBjorn Andersson #include <linux/of_platform.h>
2153e2822eSBjorn Andersson #include <linux/platform_device.h>
2253e2822eSBjorn Andersson #include <linux/regmap.h>
2353e2822eSBjorn Andersson #include <linux/sched.h>
2453e2822eSBjorn Andersson #include <linux/slab.h>
2553e2822eSBjorn Andersson #include <linux/soc/qcom/smem.h>
2653e2822eSBjorn Andersson #include <linux/wait.h>
2753e2822eSBjorn Andersson #include <linux/rpmsg.h>
28*8fc94723SBjorn Andersson #include <linux/rpmsg/qcom_smd.h>
2953e2822eSBjorn Andersson 
3053e2822eSBjorn Andersson #include "rpmsg_internal.h"
3153e2822eSBjorn Andersson 
3253e2822eSBjorn Andersson /*
3353e2822eSBjorn Andersson  * The Qualcomm Shared Memory communication solution provides point-to-point
3453e2822eSBjorn Andersson  * channels for clients to send and receive streaming or packet based data.
3553e2822eSBjorn Andersson  *
3653e2822eSBjorn Andersson  * Each channel consists of a control item (channel info) and a ring buffer
3753e2822eSBjorn Andersson  * pair. The channel info carry information related to channel state, flow
3853e2822eSBjorn Andersson  * control and the offsets within the ring buffer.
3953e2822eSBjorn Andersson  *
4053e2822eSBjorn Andersson  * All allocated channels are listed in an allocation table, identifying the
4153e2822eSBjorn Andersson  * pair of items by name, type and remote processor.
4253e2822eSBjorn Andersson  *
4353e2822eSBjorn Andersson  * Upon creating a new channel the remote processor allocates channel info and
4453e2822eSBjorn Andersson  * ring buffer items from the smem heap and populate the allocation table. An
4553e2822eSBjorn Andersson  * interrupt is sent to the other end of the channel and a scan for new
4653e2822eSBjorn Andersson  * channels should be done. A channel never goes away, it will only change
4753e2822eSBjorn Andersson  * state.
4853e2822eSBjorn Andersson  *
4953e2822eSBjorn Andersson  * The remote processor signals it intent for bring up the communication
5053e2822eSBjorn Andersson  * channel by setting the state of its end of the channel to "opening" and
5153e2822eSBjorn Andersson  * sends out an interrupt. We detect this change and register a smd device to
5253e2822eSBjorn Andersson  * consume the channel. Upon finding a consumer we finish the handshake and the
5353e2822eSBjorn Andersson  * channel is up.
5453e2822eSBjorn Andersson  *
5553e2822eSBjorn Andersson  * Upon closing a channel, the remote processor will update the state of its
5653e2822eSBjorn Andersson  * end of the channel and signal us, we will then unregister any attached
5753e2822eSBjorn Andersson  * device and close our end of the channel.
5853e2822eSBjorn Andersson  *
5953e2822eSBjorn Andersson  * Devices attached to a channel can use the qcom_smd_send function to push
6053e2822eSBjorn Andersson  * data to the channel, this is done by copying the data into the tx ring
6153e2822eSBjorn Andersson  * buffer, updating the pointers in the channel info and signaling the remote
6253e2822eSBjorn Andersson  * processor.
6353e2822eSBjorn Andersson  *
6453e2822eSBjorn Andersson  * The remote processor does the equivalent when it transfer data and upon
6553e2822eSBjorn Andersson  * receiving the interrupt we check the channel info for new data and delivers
6653e2822eSBjorn Andersson  * this to the attached device. If the device is not ready to receive the data
6753e2822eSBjorn Andersson  * we leave it in the ring buffer for now.
6853e2822eSBjorn Andersson  */
6953e2822eSBjorn Andersson 
7053e2822eSBjorn Andersson struct smd_channel_info;
7153e2822eSBjorn Andersson struct smd_channel_info_pair;
7253e2822eSBjorn Andersson struct smd_channel_info_word;
7353e2822eSBjorn Andersson struct smd_channel_info_word_pair;
7453e2822eSBjorn Andersson 
7553e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops;
7653e2822eSBjorn Andersson 
7753e2822eSBjorn Andersson #define SMD_ALLOC_TBL_COUNT	2
7853e2822eSBjorn Andersson #define SMD_ALLOC_TBL_SIZE	64
7953e2822eSBjorn Andersson 
8053e2822eSBjorn Andersson /*
8153e2822eSBjorn Andersson  * This lists the various smem heap items relevant for the allocation table and
8253e2822eSBjorn Andersson  * smd channel entries.
8353e2822eSBjorn Andersson  */
8453e2822eSBjorn Andersson static const struct {
8553e2822eSBjorn Andersson 	unsigned alloc_tbl_id;
8653e2822eSBjorn Andersson 	unsigned info_base_id;
8753e2822eSBjorn Andersson 	unsigned fifo_base_id;
8853e2822eSBjorn Andersson } smem_items[SMD_ALLOC_TBL_COUNT] = {
8953e2822eSBjorn Andersson 	{
9053e2822eSBjorn Andersson 		.alloc_tbl_id = 13,
9153e2822eSBjorn Andersson 		.info_base_id = 14,
9253e2822eSBjorn Andersson 		.fifo_base_id = 338
9353e2822eSBjorn Andersson 	},
9453e2822eSBjorn Andersson 	{
9553e2822eSBjorn Andersson 		.alloc_tbl_id = 266,
9653e2822eSBjorn Andersson 		.info_base_id = 138,
9753e2822eSBjorn Andersson 		.fifo_base_id = 202,
9853e2822eSBjorn Andersson 	},
9953e2822eSBjorn Andersson };
10053e2822eSBjorn Andersson 
10153e2822eSBjorn Andersson /**
10253e2822eSBjorn Andersson  * struct qcom_smd_edge - representing a remote processor
10353e2822eSBjorn Andersson  * @of_node:		of_node handle for information related to this edge
10453e2822eSBjorn Andersson  * @edge_id:		identifier of this edge
10553e2822eSBjorn Andersson  * @remote_pid:		identifier of remote processor
10653e2822eSBjorn Andersson  * @irq:		interrupt for signals on this edge
10753e2822eSBjorn Andersson  * @ipc_regmap:		regmap handle holding the outgoing ipc register
10853e2822eSBjorn Andersson  * @ipc_offset:		offset within @ipc_regmap of the register for ipc
10953e2822eSBjorn Andersson  * @ipc_bit:		bit in the register at @ipc_offset of @ipc_regmap
11053e2822eSBjorn Andersson  * @channels:		list of all channels detected on this edge
11153e2822eSBjorn Andersson  * @channels_lock:	guard for modifications of @channels
11253e2822eSBjorn Andersson  * @allocated:		array of bitmaps representing already allocated channels
11353e2822eSBjorn Andersson  * @smem_available:	last available amount of smem triggering a channel scan
11453e2822eSBjorn Andersson  * @scan_work:		work item for discovering new channels
11553e2822eSBjorn Andersson  * @state_work:		work item for edge state changes
11653e2822eSBjorn Andersson  */
11753e2822eSBjorn Andersson struct qcom_smd_edge {
11853e2822eSBjorn Andersson 	struct device dev;
11953e2822eSBjorn Andersson 
12053e2822eSBjorn Andersson 	struct device_node *of_node;
12153e2822eSBjorn Andersson 	unsigned edge_id;
12253e2822eSBjorn Andersson 	unsigned remote_pid;
12353e2822eSBjorn Andersson 
12453e2822eSBjorn Andersson 	int irq;
12553e2822eSBjorn Andersson 
12653e2822eSBjorn Andersson 	struct regmap *ipc_regmap;
12753e2822eSBjorn Andersson 	int ipc_offset;
12853e2822eSBjorn Andersson 	int ipc_bit;
12953e2822eSBjorn Andersson 
13053e2822eSBjorn Andersson 	struct list_head channels;
13153e2822eSBjorn Andersson 	spinlock_t channels_lock;
13253e2822eSBjorn Andersson 
13353e2822eSBjorn Andersson 	DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE);
13453e2822eSBjorn Andersson 
13553e2822eSBjorn Andersson 	unsigned smem_available;
13653e2822eSBjorn Andersson 
13753e2822eSBjorn Andersson 	wait_queue_head_t new_channel_event;
13853e2822eSBjorn Andersson 
13953e2822eSBjorn Andersson 	struct work_struct scan_work;
14053e2822eSBjorn Andersson 	struct work_struct state_work;
14153e2822eSBjorn Andersson };
14253e2822eSBjorn Andersson 
14353e2822eSBjorn Andersson /*
14453e2822eSBjorn Andersson  * SMD channel states.
14553e2822eSBjorn Andersson  */
14653e2822eSBjorn Andersson enum smd_channel_state {
14753e2822eSBjorn Andersson 	SMD_CHANNEL_CLOSED,
14853e2822eSBjorn Andersson 	SMD_CHANNEL_OPENING,
14953e2822eSBjorn Andersson 	SMD_CHANNEL_OPENED,
15053e2822eSBjorn Andersson 	SMD_CHANNEL_FLUSHING,
15153e2822eSBjorn Andersson 	SMD_CHANNEL_CLOSING,
15253e2822eSBjorn Andersson 	SMD_CHANNEL_RESET,
15353e2822eSBjorn Andersson 	SMD_CHANNEL_RESET_OPENING
15453e2822eSBjorn Andersson };
15553e2822eSBjorn Andersson 
15653e2822eSBjorn Andersson struct qcom_smd_device {
15753e2822eSBjorn Andersson 	struct rpmsg_device rpdev;
15853e2822eSBjorn Andersson 
15953e2822eSBjorn Andersson 	struct qcom_smd_edge *edge;
16053e2822eSBjorn Andersson };
16153e2822eSBjorn Andersson 
16253e2822eSBjorn Andersson struct qcom_smd_endpoint {
16353e2822eSBjorn Andersson 	struct rpmsg_endpoint ept;
16453e2822eSBjorn Andersson 
16553e2822eSBjorn Andersson 	struct qcom_smd_channel *qsch;
16653e2822eSBjorn Andersson };
16753e2822eSBjorn Andersson 
16853e2822eSBjorn Andersson #define to_smd_device(_rpdev)	container_of(_rpdev, struct qcom_smd_device, rpdev)
16953e2822eSBjorn Andersson #define to_smd_edge(d)		container_of(d, struct qcom_smd_edge, dev)
17053e2822eSBjorn Andersson #define to_smd_endpoint(ept)	container_of(ept, struct qcom_smd_endpoint, ept)
17153e2822eSBjorn Andersson 
17253e2822eSBjorn Andersson /**
17353e2822eSBjorn Andersson  * struct qcom_smd_channel - smd channel struct
17453e2822eSBjorn Andersson  * @edge:		qcom_smd_edge this channel is living on
17553e2822eSBjorn Andersson  * @qsdev:		reference to a associated smd client device
17653e2822eSBjorn Andersson  * @name:		name of the channel
17753e2822eSBjorn Andersson  * @state:		local state of the channel
17853e2822eSBjorn Andersson  * @remote_state:	remote state of the channel
17953e2822eSBjorn Andersson  * @info:		byte aligned outgoing/incoming channel info
18053e2822eSBjorn Andersson  * @info_word:		word aligned outgoing/incoming channel info
18153e2822eSBjorn Andersson  * @tx_lock:		lock to make writes to the channel mutually exclusive
18253e2822eSBjorn Andersson  * @fblockread_event:	wakeup event tied to tx fBLOCKREADINTR
18353e2822eSBjorn Andersson  * @tx_fifo:		pointer to the outgoing ring buffer
18453e2822eSBjorn Andersson  * @rx_fifo:		pointer to the incoming ring buffer
18553e2822eSBjorn Andersson  * @fifo_size:		size of each ring buffer
18653e2822eSBjorn Andersson  * @bounce_buffer:	bounce buffer for reading wrapped packets
18753e2822eSBjorn Andersson  * @cb:			callback function registered for this channel
18853e2822eSBjorn Andersson  * @recv_lock:		guard for rx info modifications and cb pointer
18953e2822eSBjorn Andersson  * @pkt_size:		size of the currently handled packet
19053e2822eSBjorn Andersson  * @list:		lite entry for @channels in qcom_smd_edge
19153e2822eSBjorn Andersson  */
19253e2822eSBjorn Andersson struct qcom_smd_channel {
19353e2822eSBjorn Andersson 	struct qcom_smd_edge *edge;
19453e2822eSBjorn Andersson 
19553e2822eSBjorn Andersson 	struct qcom_smd_endpoint *qsept;
19653e2822eSBjorn Andersson 	bool registered;
19753e2822eSBjorn Andersson 
19853e2822eSBjorn Andersson 	char *name;
19953e2822eSBjorn Andersson 	enum smd_channel_state state;
20053e2822eSBjorn Andersson 	enum smd_channel_state remote_state;
20153e2822eSBjorn Andersson 
20253e2822eSBjorn Andersson 	struct smd_channel_info_pair *info;
20353e2822eSBjorn Andersson 	struct smd_channel_info_word_pair *info_word;
20453e2822eSBjorn Andersson 
20553e2822eSBjorn Andersson 	struct mutex tx_lock;
20653e2822eSBjorn Andersson 	wait_queue_head_t fblockread_event;
20753e2822eSBjorn Andersson 
20853e2822eSBjorn Andersson 	void *tx_fifo;
20953e2822eSBjorn Andersson 	void *rx_fifo;
21053e2822eSBjorn Andersson 	int fifo_size;
21153e2822eSBjorn Andersson 
21253e2822eSBjorn Andersson 	void *bounce_buffer;
21353e2822eSBjorn Andersson 
21453e2822eSBjorn Andersson 	spinlock_t recv_lock;
21553e2822eSBjorn Andersson 
21653e2822eSBjorn Andersson 	int pkt_size;
21753e2822eSBjorn Andersson 
21853e2822eSBjorn Andersson 	void *drvdata;
21953e2822eSBjorn Andersson 
22053e2822eSBjorn Andersson 	struct list_head list;
22153e2822eSBjorn Andersson };
22253e2822eSBjorn Andersson 
22353e2822eSBjorn Andersson /*
22453e2822eSBjorn Andersson  * Format of the smd_info smem items, for byte aligned channels.
22553e2822eSBjorn Andersson  */
22653e2822eSBjorn Andersson struct smd_channel_info {
22753e2822eSBjorn Andersson 	__le32 state;
22853e2822eSBjorn Andersson 	u8  fDSR;
22953e2822eSBjorn Andersson 	u8  fCTS;
23053e2822eSBjorn Andersson 	u8  fCD;
23153e2822eSBjorn Andersson 	u8  fRI;
23253e2822eSBjorn Andersson 	u8  fHEAD;
23353e2822eSBjorn Andersson 	u8  fTAIL;
23453e2822eSBjorn Andersson 	u8  fSTATE;
23553e2822eSBjorn Andersson 	u8  fBLOCKREADINTR;
23653e2822eSBjorn Andersson 	__le32 tail;
23753e2822eSBjorn Andersson 	__le32 head;
23853e2822eSBjorn Andersson };
23953e2822eSBjorn Andersson 
24053e2822eSBjorn Andersson struct smd_channel_info_pair {
24153e2822eSBjorn Andersson 	struct smd_channel_info tx;
24253e2822eSBjorn Andersson 	struct smd_channel_info rx;
24353e2822eSBjorn Andersson };
24453e2822eSBjorn Andersson 
24553e2822eSBjorn Andersson /*
24653e2822eSBjorn Andersson  * Format of the smd_info smem items, for word aligned channels.
24753e2822eSBjorn Andersson  */
24853e2822eSBjorn Andersson struct smd_channel_info_word {
24953e2822eSBjorn Andersson 	__le32 state;
25053e2822eSBjorn Andersson 	__le32 fDSR;
25153e2822eSBjorn Andersson 	__le32 fCTS;
25253e2822eSBjorn Andersson 	__le32 fCD;
25353e2822eSBjorn Andersson 	__le32 fRI;
25453e2822eSBjorn Andersson 	__le32 fHEAD;
25553e2822eSBjorn Andersson 	__le32 fTAIL;
25653e2822eSBjorn Andersson 	__le32 fSTATE;
25753e2822eSBjorn Andersson 	__le32 fBLOCKREADINTR;
25853e2822eSBjorn Andersson 	__le32 tail;
25953e2822eSBjorn Andersson 	__le32 head;
26053e2822eSBjorn Andersson };
26153e2822eSBjorn Andersson 
26253e2822eSBjorn Andersson struct smd_channel_info_word_pair {
26353e2822eSBjorn Andersson 	struct smd_channel_info_word tx;
26453e2822eSBjorn Andersson 	struct smd_channel_info_word rx;
26553e2822eSBjorn Andersson };
26653e2822eSBjorn Andersson 
26753e2822eSBjorn Andersson #define GET_RX_CHANNEL_FLAG(channel, param)				     \
26853e2822eSBjorn Andersson 	({								     \
26953e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
27053e2822eSBjorn Andersson 		channel->info_word ?					     \
27153e2822eSBjorn Andersson 			le32_to_cpu(channel->info_word->rx.param) :	     \
27253e2822eSBjorn Andersson 			channel->info->rx.param;			     \
27353e2822eSBjorn Andersson 	})
27453e2822eSBjorn Andersson 
27553e2822eSBjorn Andersson #define GET_RX_CHANNEL_INFO(channel, param)				      \
27653e2822eSBjorn Andersson 	({								      \
27753e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
27853e2822eSBjorn Andersson 		le32_to_cpu(channel->info_word ?			      \
27953e2822eSBjorn Andersson 			channel->info_word->rx.param :			      \
28053e2822eSBjorn Andersson 			channel->info->rx.param);			      \
28153e2822eSBjorn Andersson 	})
28253e2822eSBjorn Andersson 
28353e2822eSBjorn Andersson #define SET_RX_CHANNEL_FLAG(channel, param, value)			     \
28453e2822eSBjorn Andersson 	({								     \
28553e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
28653e2822eSBjorn Andersson 		if (channel->info_word)					     \
28753e2822eSBjorn Andersson 			channel->info_word->rx.param = cpu_to_le32(value);   \
28853e2822eSBjorn Andersson 		else							     \
28953e2822eSBjorn Andersson 			channel->info->rx.param = value;		     \
29053e2822eSBjorn Andersson 	})
29153e2822eSBjorn Andersson 
29253e2822eSBjorn Andersson #define SET_RX_CHANNEL_INFO(channel, param, value)			      \
29353e2822eSBjorn Andersson 	({								      \
29453e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
29553e2822eSBjorn Andersson 		if (channel->info_word)					      \
29653e2822eSBjorn Andersson 			channel->info_word->rx.param = cpu_to_le32(value);    \
29753e2822eSBjorn Andersson 		else							      \
29853e2822eSBjorn Andersson 			channel->info->rx.param = cpu_to_le32(value);	      \
29953e2822eSBjorn Andersson 	})
30053e2822eSBjorn Andersson 
30153e2822eSBjorn Andersson #define GET_TX_CHANNEL_FLAG(channel, param)				     \
30253e2822eSBjorn Andersson 	({								     \
30353e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
30453e2822eSBjorn Andersson 		channel->info_word ?					     \
30553e2822eSBjorn Andersson 			le32_to_cpu(channel->info_word->tx.param) :          \
30653e2822eSBjorn Andersson 			channel->info->tx.param;			     \
30753e2822eSBjorn Andersson 	})
30853e2822eSBjorn Andersson 
30953e2822eSBjorn Andersson #define GET_TX_CHANNEL_INFO(channel, param)				      \
31053e2822eSBjorn Andersson 	({								      \
31153e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
31253e2822eSBjorn Andersson 		le32_to_cpu(channel->info_word ?			      \
31353e2822eSBjorn Andersson 			channel->info_word->tx.param :			      \
31453e2822eSBjorn Andersson 			channel->info->tx.param);			      \
31553e2822eSBjorn Andersson 	})
31653e2822eSBjorn Andersson 
31753e2822eSBjorn Andersson #define SET_TX_CHANNEL_FLAG(channel, param, value)			     \
31853e2822eSBjorn Andersson 	({								     \
31953e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
32053e2822eSBjorn Andersson 		if (channel->info_word)					     \
32153e2822eSBjorn Andersson 			channel->info_word->tx.param = cpu_to_le32(value);   \
32253e2822eSBjorn Andersson 		else							     \
32353e2822eSBjorn Andersson 			channel->info->tx.param = value;		     \
32453e2822eSBjorn Andersson 	})
32553e2822eSBjorn Andersson 
32653e2822eSBjorn Andersson #define SET_TX_CHANNEL_INFO(channel, param, value)			      \
32753e2822eSBjorn Andersson 	({								      \
32853e2822eSBjorn Andersson 		BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
32953e2822eSBjorn Andersson 		if (channel->info_word)					      \
33053e2822eSBjorn Andersson 			channel->info_word->tx.param = cpu_to_le32(value);   \
33153e2822eSBjorn Andersson 		else							      \
33253e2822eSBjorn Andersson 			channel->info->tx.param = cpu_to_le32(value);	      \
33353e2822eSBjorn Andersson 	})
33453e2822eSBjorn Andersson 
33553e2822eSBjorn Andersson /**
33653e2822eSBjorn Andersson  * struct qcom_smd_alloc_entry - channel allocation entry
33753e2822eSBjorn Andersson  * @name:	channel name
33853e2822eSBjorn Andersson  * @cid:	channel index
33953e2822eSBjorn Andersson  * @flags:	channel flags and edge id
34053e2822eSBjorn Andersson  * @ref_count:	reference count of the channel
34153e2822eSBjorn Andersson  */
34253e2822eSBjorn Andersson struct qcom_smd_alloc_entry {
34353e2822eSBjorn Andersson 	u8 name[20];
34453e2822eSBjorn Andersson 	__le32 cid;
34553e2822eSBjorn Andersson 	__le32 flags;
34653e2822eSBjorn Andersson 	__le32 ref_count;
34753e2822eSBjorn Andersson } __packed;
34853e2822eSBjorn Andersson 
34953e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_EDGE_MASK	0xff
35053e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_STREAM	BIT(8)
35153e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_PACKET	BIT(9)
35253e2822eSBjorn Andersson 
35353e2822eSBjorn Andersson /*
35453e2822eSBjorn Andersson  * Each smd packet contains a 20 byte header, with the first 4 being the length
35553e2822eSBjorn Andersson  * of the packet.
35653e2822eSBjorn Andersson  */
35753e2822eSBjorn Andersson #define SMD_PACKET_HEADER_LEN	20
35853e2822eSBjorn Andersson 
35953e2822eSBjorn Andersson /*
36053e2822eSBjorn Andersson  * Signal the remote processor associated with 'channel'.
36153e2822eSBjorn Andersson  */
36253e2822eSBjorn Andersson static void qcom_smd_signal_channel(struct qcom_smd_channel *channel)
36353e2822eSBjorn Andersson {
36453e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = channel->edge;
36553e2822eSBjorn Andersson 
36653e2822eSBjorn Andersson 	regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit));
36753e2822eSBjorn Andersson }
36853e2822eSBjorn Andersson 
36953e2822eSBjorn Andersson /*
37053e2822eSBjorn Andersson  * Initialize the tx channel info
37153e2822eSBjorn Andersson  */
37253e2822eSBjorn Andersson static void qcom_smd_channel_reset(struct qcom_smd_channel *channel)
37353e2822eSBjorn Andersson {
37453e2822eSBjorn Andersson 	SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED);
37553e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fDSR, 0);
37653e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fCTS, 0);
37753e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fCD, 0);
37853e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fRI, 0);
37953e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fHEAD, 0);
38053e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
38153e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
38253e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
38353e2822eSBjorn Andersson 	SET_TX_CHANNEL_INFO(channel, head, 0);
38453e2822eSBjorn Andersson 	SET_RX_CHANNEL_INFO(channel, tail, 0);
38553e2822eSBjorn Andersson 
38653e2822eSBjorn Andersson 	qcom_smd_signal_channel(channel);
38753e2822eSBjorn Andersson 
38853e2822eSBjorn Andersson 	channel->state = SMD_CHANNEL_CLOSED;
38953e2822eSBjorn Andersson 	channel->pkt_size = 0;
39053e2822eSBjorn Andersson }
39153e2822eSBjorn Andersson 
39253e2822eSBjorn Andersson /*
39353e2822eSBjorn Andersson  * Set the callback for a channel, with appropriate locking
39453e2822eSBjorn Andersson  */
39553e2822eSBjorn Andersson static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,
39653e2822eSBjorn Andersson 					  rpmsg_rx_cb_t cb)
39753e2822eSBjorn Andersson {
39853e2822eSBjorn Andersson 	struct rpmsg_endpoint *ept = &channel->qsept->ept;
39953e2822eSBjorn Andersson 	unsigned long flags;
40053e2822eSBjorn Andersson 
40153e2822eSBjorn Andersson 	spin_lock_irqsave(&channel->recv_lock, flags);
40253e2822eSBjorn Andersson 	ept->cb = cb;
40353e2822eSBjorn Andersson 	spin_unlock_irqrestore(&channel->recv_lock, flags);
40453e2822eSBjorn Andersson };
40553e2822eSBjorn Andersson 
40653e2822eSBjorn Andersson /*
40753e2822eSBjorn Andersson  * Calculate the amount of data available in the rx fifo
40853e2822eSBjorn Andersson  */
40953e2822eSBjorn Andersson static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel)
41053e2822eSBjorn Andersson {
41153e2822eSBjorn Andersson 	unsigned head;
41253e2822eSBjorn Andersson 	unsigned tail;
41353e2822eSBjorn Andersson 
41453e2822eSBjorn Andersson 	head = GET_RX_CHANNEL_INFO(channel, head);
41553e2822eSBjorn Andersson 	tail = GET_RX_CHANNEL_INFO(channel, tail);
41653e2822eSBjorn Andersson 
41753e2822eSBjorn Andersson 	return (head - tail) & (channel->fifo_size - 1);
41853e2822eSBjorn Andersson }
41953e2822eSBjorn Andersson 
42053e2822eSBjorn Andersson /*
42153e2822eSBjorn Andersson  * Set tx channel state and inform the remote processor
42253e2822eSBjorn Andersson  */
42353e2822eSBjorn Andersson static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
42453e2822eSBjorn Andersson 				       int state)
42553e2822eSBjorn Andersson {
42653e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = channel->edge;
42753e2822eSBjorn Andersson 	bool is_open = state == SMD_CHANNEL_OPENED;
42853e2822eSBjorn Andersson 
42953e2822eSBjorn Andersson 	if (channel->state == state)
43053e2822eSBjorn Andersson 		return;
43153e2822eSBjorn Andersson 
43253e2822eSBjorn Andersson 	dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state);
43353e2822eSBjorn Andersson 
43453e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fDSR, is_open);
43553e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fCTS, is_open);
43653e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fCD, is_open);
43753e2822eSBjorn Andersson 
43853e2822eSBjorn Andersson 	SET_TX_CHANNEL_INFO(channel, state, state);
43953e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
44053e2822eSBjorn Andersson 
44153e2822eSBjorn Andersson 	channel->state = state;
44253e2822eSBjorn Andersson 	qcom_smd_signal_channel(channel);
44353e2822eSBjorn Andersson }
44453e2822eSBjorn Andersson 
44553e2822eSBjorn Andersson /*
44653e2822eSBjorn Andersson  * Copy count bytes of data using 32bit accesses, if that's required.
44753e2822eSBjorn Andersson  */
44853e2822eSBjorn Andersson static void smd_copy_to_fifo(void __iomem *dst,
44953e2822eSBjorn Andersson 			     const void *src,
45053e2822eSBjorn Andersson 			     size_t count,
45153e2822eSBjorn Andersson 			     bool word_aligned)
45253e2822eSBjorn Andersson {
45353e2822eSBjorn Andersson 	if (word_aligned) {
45453e2822eSBjorn Andersson 		__iowrite32_copy(dst, src, count / sizeof(u32));
45553e2822eSBjorn Andersson 	} else {
45653e2822eSBjorn Andersson 		memcpy_toio(dst, src, count);
45753e2822eSBjorn Andersson 	}
45853e2822eSBjorn Andersson }
45953e2822eSBjorn Andersson 
46053e2822eSBjorn Andersson /*
46153e2822eSBjorn Andersson  * Copy count bytes of data using 32bit accesses, if that is required.
46253e2822eSBjorn Andersson  */
46353e2822eSBjorn Andersson static void smd_copy_from_fifo(void *dst,
46453e2822eSBjorn Andersson 			       const void __iomem *src,
46553e2822eSBjorn Andersson 			       size_t count,
46653e2822eSBjorn Andersson 			       bool word_aligned)
46753e2822eSBjorn Andersson {
46853e2822eSBjorn Andersson 	if (word_aligned) {
46953e2822eSBjorn Andersson 		__ioread32_copy(dst, src, count / sizeof(u32));
47053e2822eSBjorn Andersson 	} else {
47153e2822eSBjorn Andersson 		memcpy_fromio(dst, src, count);
47253e2822eSBjorn Andersson 	}
47353e2822eSBjorn Andersson }
47453e2822eSBjorn Andersson 
47553e2822eSBjorn Andersson /*
47653e2822eSBjorn Andersson  * Read count bytes of data from the rx fifo into buf, but don't advance the
47753e2822eSBjorn Andersson  * tail.
47853e2822eSBjorn Andersson  */
47953e2822eSBjorn Andersson static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
48053e2822eSBjorn Andersson 				    void *buf, size_t count)
48153e2822eSBjorn Andersson {
48253e2822eSBjorn Andersson 	bool word_aligned;
48353e2822eSBjorn Andersson 	unsigned tail;
48453e2822eSBjorn Andersson 	size_t len;
48553e2822eSBjorn Andersson 
48653e2822eSBjorn Andersson 	word_aligned = channel->info_word;
48753e2822eSBjorn Andersson 	tail = GET_RX_CHANNEL_INFO(channel, tail);
48853e2822eSBjorn Andersson 
48953e2822eSBjorn Andersson 	len = min_t(size_t, count, channel->fifo_size - tail);
49053e2822eSBjorn Andersson 	if (len) {
49153e2822eSBjorn Andersson 		smd_copy_from_fifo(buf,
49253e2822eSBjorn Andersson 				   channel->rx_fifo + tail,
49353e2822eSBjorn Andersson 				   len,
49453e2822eSBjorn Andersson 				   word_aligned);
49553e2822eSBjorn Andersson 	}
49653e2822eSBjorn Andersson 
49753e2822eSBjorn Andersson 	if (len != count) {
49853e2822eSBjorn Andersson 		smd_copy_from_fifo(buf + len,
49953e2822eSBjorn Andersson 				   channel->rx_fifo,
50053e2822eSBjorn Andersson 				   count - len,
50153e2822eSBjorn Andersson 				   word_aligned);
50253e2822eSBjorn Andersson 	}
50353e2822eSBjorn Andersson 
50453e2822eSBjorn Andersson 	return count;
50553e2822eSBjorn Andersson }
50653e2822eSBjorn Andersson 
50753e2822eSBjorn Andersson /*
50853e2822eSBjorn Andersson  * Advance the rx tail by count bytes.
50953e2822eSBjorn Andersson  */
51053e2822eSBjorn Andersson static void qcom_smd_channel_advance(struct qcom_smd_channel *channel,
51153e2822eSBjorn Andersson 				     size_t count)
51253e2822eSBjorn Andersson {
51353e2822eSBjorn Andersson 	unsigned tail;
51453e2822eSBjorn Andersson 
51553e2822eSBjorn Andersson 	tail = GET_RX_CHANNEL_INFO(channel, tail);
51653e2822eSBjorn Andersson 	tail += count;
51753e2822eSBjorn Andersson 	tail &= (channel->fifo_size - 1);
51853e2822eSBjorn Andersson 	SET_RX_CHANNEL_INFO(channel, tail, tail);
51953e2822eSBjorn Andersson }
52053e2822eSBjorn Andersson 
52153e2822eSBjorn Andersson /*
52253e2822eSBjorn Andersson  * Read out a single packet from the rx fifo and deliver it to the device
52353e2822eSBjorn Andersson  */
52453e2822eSBjorn Andersson static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel)
52553e2822eSBjorn Andersson {
52653e2822eSBjorn Andersson 	struct rpmsg_endpoint *ept = &channel->qsept->ept;
52753e2822eSBjorn Andersson 	unsigned tail;
52853e2822eSBjorn Andersson 	size_t len;
52953e2822eSBjorn Andersson 	void *ptr;
53053e2822eSBjorn Andersson 	int ret;
53153e2822eSBjorn Andersson 
53253e2822eSBjorn Andersson 	tail = GET_RX_CHANNEL_INFO(channel, tail);
53353e2822eSBjorn Andersson 
53453e2822eSBjorn Andersson 	/* Use bounce buffer if the data wraps */
53553e2822eSBjorn Andersson 	if (tail + channel->pkt_size >= channel->fifo_size) {
53653e2822eSBjorn Andersson 		ptr = channel->bounce_buffer;
53753e2822eSBjorn Andersson 		len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size);
53853e2822eSBjorn Andersson 	} else {
53953e2822eSBjorn Andersson 		ptr = channel->rx_fifo + tail;
54053e2822eSBjorn Andersson 		len = channel->pkt_size;
54153e2822eSBjorn Andersson 	}
54253e2822eSBjorn Andersson 
54353e2822eSBjorn Andersson 	ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY);
54453e2822eSBjorn Andersson 	if (ret < 0)
54553e2822eSBjorn Andersson 		return ret;
54653e2822eSBjorn Andersson 
54753e2822eSBjorn Andersson 	/* Only forward the tail if the client consumed the data */
54853e2822eSBjorn Andersson 	qcom_smd_channel_advance(channel, len);
54953e2822eSBjorn Andersson 
55053e2822eSBjorn Andersson 	channel->pkt_size = 0;
55153e2822eSBjorn Andersson 
55253e2822eSBjorn Andersson 	return 0;
55353e2822eSBjorn Andersson }
55453e2822eSBjorn Andersson 
55553e2822eSBjorn Andersson /*
55653e2822eSBjorn Andersson  * Per channel interrupt handling
55753e2822eSBjorn Andersson  */
55853e2822eSBjorn Andersson static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel)
55953e2822eSBjorn Andersson {
56053e2822eSBjorn Andersson 	bool need_state_scan = false;
56153e2822eSBjorn Andersson 	int remote_state;
56253e2822eSBjorn Andersson 	__le32 pktlen;
56353e2822eSBjorn Andersson 	int avail;
56453e2822eSBjorn Andersson 	int ret;
56553e2822eSBjorn Andersson 
56653e2822eSBjorn Andersson 	/* Handle state changes */
56753e2822eSBjorn Andersson 	remote_state = GET_RX_CHANNEL_INFO(channel, state);
56853e2822eSBjorn Andersson 	if (remote_state != channel->remote_state) {
56953e2822eSBjorn Andersson 		channel->remote_state = remote_state;
57053e2822eSBjorn Andersson 		need_state_scan = true;
57153e2822eSBjorn Andersson 	}
57253e2822eSBjorn Andersson 	/* Indicate that we have seen any state change */
57353e2822eSBjorn Andersson 	SET_RX_CHANNEL_FLAG(channel, fSTATE, 0);
57453e2822eSBjorn Andersson 
57553e2822eSBjorn Andersson 	/* Signal waiting qcom_smd_send() about the interrupt */
57653e2822eSBjorn Andersson 	if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR))
57753e2822eSBjorn Andersson 		wake_up_interruptible(&channel->fblockread_event);
57853e2822eSBjorn Andersson 
57953e2822eSBjorn Andersson 	/* Don't consume any data until we've opened the channel */
58053e2822eSBjorn Andersson 	if (channel->state != SMD_CHANNEL_OPENED)
58153e2822eSBjorn Andersson 		goto out;
58253e2822eSBjorn Andersson 
58353e2822eSBjorn Andersson 	/* Indicate that we've seen the new data */
58453e2822eSBjorn Andersson 	SET_RX_CHANNEL_FLAG(channel, fHEAD, 0);
58553e2822eSBjorn Andersson 
58653e2822eSBjorn Andersson 	/* Consume data */
58753e2822eSBjorn Andersson 	for (;;) {
58853e2822eSBjorn Andersson 		avail = qcom_smd_channel_get_rx_avail(channel);
58953e2822eSBjorn Andersson 
59053e2822eSBjorn Andersson 		if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) {
59153e2822eSBjorn Andersson 			qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen));
59253e2822eSBjorn Andersson 			qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN);
59353e2822eSBjorn Andersson 			channel->pkt_size = le32_to_cpu(pktlen);
59453e2822eSBjorn Andersson 		} else if (channel->pkt_size && avail >= channel->pkt_size) {
59553e2822eSBjorn Andersson 			ret = qcom_smd_channel_recv_single(channel);
59653e2822eSBjorn Andersson 			if (ret)
59753e2822eSBjorn Andersson 				break;
59853e2822eSBjorn Andersson 		} else {
59953e2822eSBjorn Andersson 			break;
60053e2822eSBjorn Andersson 		}
60153e2822eSBjorn Andersson 	}
60253e2822eSBjorn Andersson 
60353e2822eSBjorn Andersson 	/* Indicate that we have seen and updated tail */
60453e2822eSBjorn Andersson 	SET_RX_CHANNEL_FLAG(channel, fTAIL, 1);
60553e2822eSBjorn Andersson 
60653e2822eSBjorn Andersson 	/* Signal the remote that we've consumed the data (if requested) */
60753e2822eSBjorn Andersson 	if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) {
60853e2822eSBjorn Andersson 		/* Ensure ordering of channel info updates */
60953e2822eSBjorn Andersson 		wmb();
61053e2822eSBjorn Andersson 
61153e2822eSBjorn Andersson 		qcom_smd_signal_channel(channel);
61253e2822eSBjorn Andersson 	}
61353e2822eSBjorn Andersson 
61453e2822eSBjorn Andersson out:
61553e2822eSBjorn Andersson 	return need_state_scan;
61653e2822eSBjorn Andersson }
61753e2822eSBjorn Andersson 
61853e2822eSBjorn Andersson /*
61953e2822eSBjorn Andersson  * The edge interrupts are triggered by the remote processor on state changes,
62053e2822eSBjorn Andersson  * channel info updates or when new channels are created.
62153e2822eSBjorn Andersson  */
62253e2822eSBjorn Andersson static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
62353e2822eSBjorn Andersson {
62453e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = data;
62553e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
62653e2822eSBjorn Andersson 	unsigned available;
62753e2822eSBjorn Andersson 	bool kick_scanner = false;
62853e2822eSBjorn Andersson 	bool kick_state = false;
62953e2822eSBjorn Andersson 
63053e2822eSBjorn Andersson 	/*
63153e2822eSBjorn Andersson 	 * Handle state changes or data on each of the channels on this edge
63253e2822eSBjorn Andersson 	 */
63353e2822eSBjorn Andersson 	spin_lock(&edge->channels_lock);
63453e2822eSBjorn Andersson 	list_for_each_entry(channel, &edge->channels, list) {
63553e2822eSBjorn Andersson 		spin_lock(&channel->recv_lock);
63653e2822eSBjorn Andersson 		kick_state |= qcom_smd_channel_intr(channel);
63753e2822eSBjorn Andersson 		spin_unlock(&channel->recv_lock);
63853e2822eSBjorn Andersson 	}
63953e2822eSBjorn Andersson 	spin_unlock(&edge->channels_lock);
64053e2822eSBjorn Andersson 
64153e2822eSBjorn Andersson 	/*
64253e2822eSBjorn Andersson 	 * Creating a new channel requires allocating an smem entry, so we only
64353e2822eSBjorn Andersson 	 * have to scan if the amount of available space in smem have changed
64453e2822eSBjorn Andersson 	 * since last scan.
64553e2822eSBjorn Andersson 	 */
64653e2822eSBjorn Andersson 	available = qcom_smem_get_free_space(edge->remote_pid);
64753e2822eSBjorn Andersson 	if (available != edge->smem_available) {
64853e2822eSBjorn Andersson 		edge->smem_available = available;
64953e2822eSBjorn Andersson 		kick_scanner = true;
65053e2822eSBjorn Andersson 	}
65153e2822eSBjorn Andersson 
65253e2822eSBjorn Andersson 	if (kick_scanner)
65353e2822eSBjorn Andersson 		schedule_work(&edge->scan_work);
65453e2822eSBjorn Andersson 	if (kick_state)
65553e2822eSBjorn Andersson 		schedule_work(&edge->state_work);
65653e2822eSBjorn Andersson 
65753e2822eSBjorn Andersson 	return IRQ_HANDLED;
65853e2822eSBjorn Andersson }
65953e2822eSBjorn Andersson 
66053e2822eSBjorn Andersson /*
66153e2822eSBjorn Andersson  * Calculate how much space is available in the tx fifo.
66253e2822eSBjorn Andersson  */
66353e2822eSBjorn Andersson static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel)
66453e2822eSBjorn Andersson {
66553e2822eSBjorn Andersson 	unsigned head;
66653e2822eSBjorn Andersson 	unsigned tail;
66753e2822eSBjorn Andersson 	unsigned mask = channel->fifo_size - 1;
66853e2822eSBjorn Andersson 
66953e2822eSBjorn Andersson 	head = GET_TX_CHANNEL_INFO(channel, head);
67053e2822eSBjorn Andersson 	tail = GET_TX_CHANNEL_INFO(channel, tail);
67153e2822eSBjorn Andersson 
67253e2822eSBjorn Andersson 	return mask - ((head - tail) & mask);
67353e2822eSBjorn Andersson }
67453e2822eSBjorn Andersson 
67553e2822eSBjorn Andersson /*
67653e2822eSBjorn Andersson  * Write count bytes of data into channel, possibly wrapping in the ring buffer
67753e2822eSBjorn Andersson  */
67853e2822eSBjorn Andersson static int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
67953e2822eSBjorn Andersson 			       const void *data,
68053e2822eSBjorn Andersson 			       size_t count)
68153e2822eSBjorn Andersson {
68253e2822eSBjorn Andersson 	bool word_aligned;
68353e2822eSBjorn Andersson 	unsigned head;
68453e2822eSBjorn Andersson 	size_t len;
68553e2822eSBjorn Andersson 
68653e2822eSBjorn Andersson 	word_aligned = channel->info_word;
68753e2822eSBjorn Andersson 	head = GET_TX_CHANNEL_INFO(channel, head);
68853e2822eSBjorn Andersson 
68953e2822eSBjorn Andersson 	len = min_t(size_t, count, channel->fifo_size - head);
69053e2822eSBjorn Andersson 	if (len) {
69153e2822eSBjorn Andersson 		smd_copy_to_fifo(channel->tx_fifo + head,
69253e2822eSBjorn Andersson 				 data,
69353e2822eSBjorn Andersson 				 len,
69453e2822eSBjorn Andersson 				 word_aligned);
69553e2822eSBjorn Andersson 	}
69653e2822eSBjorn Andersson 
69753e2822eSBjorn Andersson 	if (len != count) {
69853e2822eSBjorn Andersson 		smd_copy_to_fifo(channel->tx_fifo,
69953e2822eSBjorn Andersson 				 data + len,
70053e2822eSBjorn Andersson 				 count - len,
70153e2822eSBjorn Andersson 				 word_aligned);
70253e2822eSBjorn Andersson 	}
70353e2822eSBjorn Andersson 
70453e2822eSBjorn Andersson 	head += count;
70553e2822eSBjorn Andersson 	head &= (channel->fifo_size - 1);
70653e2822eSBjorn Andersson 	SET_TX_CHANNEL_INFO(channel, head, head);
70753e2822eSBjorn Andersson 
70853e2822eSBjorn Andersson 	return count;
70953e2822eSBjorn Andersson }
71053e2822eSBjorn Andersson 
71153e2822eSBjorn Andersson /**
71253e2822eSBjorn Andersson  * qcom_smd_send - write data to smd channel
71353e2822eSBjorn Andersson  * @channel:	channel handle
71453e2822eSBjorn Andersson  * @data:	buffer of data to write
71553e2822eSBjorn Andersson  * @len:	number of bytes to write
71653e2822eSBjorn Andersson  *
71753e2822eSBjorn Andersson  * This is a blocking write of len bytes into the channel's tx ring buffer and
71853e2822eSBjorn Andersson  * signal the remote end. It will sleep until there is enough space available
71953e2822eSBjorn Andersson  * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid
72053e2822eSBjorn Andersson  * polling.
72153e2822eSBjorn Andersson  */
72253e2822eSBjorn Andersson static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data,
72353e2822eSBjorn Andersson 			   int len, bool wait)
72453e2822eSBjorn Andersson {
72553e2822eSBjorn Andersson 	__le32 hdr[5] = { cpu_to_le32(len), };
72653e2822eSBjorn Andersson 	int tlen = sizeof(hdr) + len;
72753e2822eSBjorn Andersson 	int ret;
72853e2822eSBjorn Andersson 
72953e2822eSBjorn Andersson 	/* Word aligned channels only accept word size aligned data */
73053e2822eSBjorn Andersson 	if (channel->info_word && len % 4)
73153e2822eSBjorn Andersson 		return -EINVAL;
73253e2822eSBjorn Andersson 
73353e2822eSBjorn Andersson 	/* Reject packets that are too big */
73453e2822eSBjorn Andersson 	if (tlen >= channel->fifo_size)
73553e2822eSBjorn Andersson 		return -EINVAL;
73653e2822eSBjorn Andersson 
73753e2822eSBjorn Andersson 	ret = mutex_lock_interruptible(&channel->tx_lock);
73853e2822eSBjorn Andersson 	if (ret)
73953e2822eSBjorn Andersson 		return ret;
74053e2822eSBjorn Andersson 
74153e2822eSBjorn Andersson 	while (qcom_smd_get_tx_avail(channel) < tlen) {
74253e2822eSBjorn Andersson 		if (!wait) {
74353e2822eSBjorn Andersson 			ret = -ENOMEM;
74453e2822eSBjorn Andersson 			goto out;
74553e2822eSBjorn Andersson 		}
74653e2822eSBjorn Andersson 
74753e2822eSBjorn Andersson 		if (channel->state != SMD_CHANNEL_OPENED) {
74853e2822eSBjorn Andersson 			ret = -EPIPE;
74953e2822eSBjorn Andersson 			goto out;
75053e2822eSBjorn Andersson 		}
75153e2822eSBjorn Andersson 
75253e2822eSBjorn Andersson 		SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0);
75353e2822eSBjorn Andersson 
75453e2822eSBjorn Andersson 		ret = wait_event_interruptible(channel->fblockread_event,
75553e2822eSBjorn Andersson 				       qcom_smd_get_tx_avail(channel) >= tlen ||
75653e2822eSBjorn Andersson 				       channel->state != SMD_CHANNEL_OPENED);
75753e2822eSBjorn Andersson 		if (ret)
75853e2822eSBjorn Andersson 			goto out;
75953e2822eSBjorn Andersson 
76053e2822eSBjorn Andersson 		SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
76153e2822eSBjorn Andersson 	}
76253e2822eSBjorn Andersson 
76353e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
76453e2822eSBjorn Andersson 
76553e2822eSBjorn Andersson 	qcom_smd_write_fifo(channel, hdr, sizeof(hdr));
76653e2822eSBjorn Andersson 	qcom_smd_write_fifo(channel, data, len);
76753e2822eSBjorn Andersson 
76853e2822eSBjorn Andersson 	SET_TX_CHANNEL_FLAG(channel, fHEAD, 1);
76953e2822eSBjorn Andersson 
77053e2822eSBjorn Andersson 	/* Ensure ordering of channel info updates */
77153e2822eSBjorn Andersson 	wmb();
77253e2822eSBjorn Andersson 
77353e2822eSBjorn Andersson 	qcom_smd_signal_channel(channel);
77453e2822eSBjorn Andersson 
77553e2822eSBjorn Andersson out:
77653e2822eSBjorn Andersson 	mutex_unlock(&channel->tx_lock);
77753e2822eSBjorn Andersson 
77853e2822eSBjorn Andersson 	return ret;
77953e2822eSBjorn Andersson }
78053e2822eSBjorn Andersson 
78153e2822eSBjorn Andersson /*
78253e2822eSBjorn Andersson  * Helper for opening a channel
78353e2822eSBjorn Andersson  */
78453e2822eSBjorn Andersson static int qcom_smd_channel_open(struct qcom_smd_channel *channel,
78553e2822eSBjorn Andersson 				 rpmsg_rx_cb_t cb)
78653e2822eSBjorn Andersson {
78753e2822eSBjorn Andersson 	size_t bb_size;
78853e2822eSBjorn Andersson 
78953e2822eSBjorn Andersson 	/*
79053e2822eSBjorn Andersson 	 * Packets are maximum 4k, but reduce if the fifo is smaller
79153e2822eSBjorn Andersson 	 */
79253e2822eSBjorn Andersson 	bb_size = min(channel->fifo_size, SZ_4K);
79353e2822eSBjorn Andersson 	channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL);
79453e2822eSBjorn Andersson 	if (!channel->bounce_buffer)
79553e2822eSBjorn Andersson 		return -ENOMEM;
79653e2822eSBjorn Andersson 
79753e2822eSBjorn Andersson 	qcom_smd_channel_set_callback(channel, cb);
79853e2822eSBjorn Andersson 	qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING);
79953e2822eSBjorn Andersson 	qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED);
80053e2822eSBjorn Andersson 
80153e2822eSBjorn Andersson 	return 0;
80253e2822eSBjorn Andersson }
80353e2822eSBjorn Andersson 
80453e2822eSBjorn Andersson /*
80553e2822eSBjorn Andersson  * Helper for closing and resetting a channel
80653e2822eSBjorn Andersson  */
80753e2822eSBjorn Andersson static void qcom_smd_channel_close(struct qcom_smd_channel *channel)
80853e2822eSBjorn Andersson {
80953e2822eSBjorn Andersson 	qcom_smd_channel_set_callback(channel, NULL);
81053e2822eSBjorn Andersson 
81153e2822eSBjorn Andersson 	kfree(channel->bounce_buffer);
81253e2822eSBjorn Andersson 	channel->bounce_buffer = NULL;
81353e2822eSBjorn Andersson 
81453e2822eSBjorn Andersson 	qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
81553e2822eSBjorn Andersson 	qcom_smd_channel_reset(channel);
81653e2822eSBjorn Andersson }
81753e2822eSBjorn Andersson 
81853e2822eSBjorn Andersson static struct qcom_smd_channel *
81953e2822eSBjorn Andersson qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
82053e2822eSBjorn Andersson {
82153e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
82253e2822eSBjorn Andersson 	struct qcom_smd_channel *ret = NULL;
82353e2822eSBjorn Andersson 	unsigned long flags;
82453e2822eSBjorn Andersson 	unsigned state;
82553e2822eSBjorn Andersson 
82653e2822eSBjorn Andersson 	spin_lock_irqsave(&edge->channels_lock, flags);
82753e2822eSBjorn Andersson 	list_for_each_entry(channel, &edge->channels, list) {
82853e2822eSBjorn Andersson 		if (strcmp(channel->name, name))
82953e2822eSBjorn Andersson 			continue;
83053e2822eSBjorn Andersson 
83153e2822eSBjorn Andersson 		state = GET_RX_CHANNEL_INFO(channel, state);
83253e2822eSBjorn Andersson 		if (state != SMD_CHANNEL_OPENING &&
83353e2822eSBjorn Andersson 		    state != SMD_CHANNEL_OPENED)
83453e2822eSBjorn Andersson 			continue;
83553e2822eSBjorn Andersson 
83653e2822eSBjorn Andersson 		ret = channel;
83753e2822eSBjorn Andersson 		break;
83853e2822eSBjorn Andersson 	}
83953e2822eSBjorn Andersson 	spin_unlock_irqrestore(&edge->channels_lock, flags);
84053e2822eSBjorn Andersson 
84153e2822eSBjorn Andersson 	return ret;
84253e2822eSBjorn Andersson }
84353e2822eSBjorn Andersson 
84453e2822eSBjorn Andersson static void __ept_release(struct kref *kref)
84553e2822eSBjorn Andersson {
84653e2822eSBjorn Andersson 	struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint,
84753e2822eSBjorn Andersson 						  refcount);
84853e2822eSBjorn Andersson 	kfree(to_smd_endpoint(ept));
84953e2822eSBjorn Andersson }
85053e2822eSBjorn Andersson 
85153e2822eSBjorn Andersson static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev,
85253e2822eSBjorn Andersson 						  rpmsg_rx_cb_t cb, void *priv,
85353e2822eSBjorn Andersson 						  struct rpmsg_channel_info chinfo)
85453e2822eSBjorn Andersson {
85553e2822eSBjorn Andersson 	struct qcom_smd_endpoint *qsept;
85653e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
85753e2822eSBjorn Andersson 	struct qcom_smd_device *qsdev = to_smd_device(rpdev);
85853e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = qsdev->edge;
85953e2822eSBjorn Andersson 	struct rpmsg_endpoint *ept;
86053e2822eSBjorn Andersson 	const char *name = chinfo.name;
86153e2822eSBjorn Andersson 	int ret;
86253e2822eSBjorn Andersson 
86353e2822eSBjorn Andersson 	/* Wait up to HZ for the channel to appear */
86453e2822eSBjorn Andersson 	ret = wait_event_interruptible_timeout(edge->new_channel_event,
86553e2822eSBjorn Andersson 			(channel = qcom_smd_find_channel(edge, name)) != NULL,
86653e2822eSBjorn Andersson 			HZ);
86753e2822eSBjorn Andersson 	if (!ret)
86853e2822eSBjorn Andersson 		return NULL;
86953e2822eSBjorn Andersson 
87053e2822eSBjorn Andersson 	if (channel->state != SMD_CHANNEL_CLOSED) {
87153e2822eSBjorn Andersson 		dev_err(&rpdev->dev, "channel %s is busy\n", channel->name);
87253e2822eSBjorn Andersson 		return NULL;
87353e2822eSBjorn Andersson 	}
87453e2822eSBjorn Andersson 
87553e2822eSBjorn Andersson 	qsept = kzalloc(sizeof(*qsept), GFP_KERNEL);
87653e2822eSBjorn Andersson 	if (!qsept)
87753e2822eSBjorn Andersson 		return NULL;
87853e2822eSBjorn Andersson 
87953e2822eSBjorn Andersson 	ept = &qsept->ept;
88053e2822eSBjorn Andersson 
88153e2822eSBjorn Andersson 	kref_init(&ept->refcount);
88253e2822eSBjorn Andersson 
88353e2822eSBjorn Andersson 	ept->rpdev = rpdev;
88453e2822eSBjorn Andersson 	ept->cb = cb;
88553e2822eSBjorn Andersson 	ept->priv = priv;
88653e2822eSBjorn Andersson 	ept->ops = &qcom_smd_endpoint_ops;
88753e2822eSBjorn Andersson 
88853e2822eSBjorn Andersson 	channel->qsept = qsept;
88953e2822eSBjorn Andersson 	qsept->qsch = channel;
89053e2822eSBjorn Andersson 
89153e2822eSBjorn Andersson 	ret = qcom_smd_channel_open(channel, cb);
89253e2822eSBjorn Andersson 	if (ret)
89353e2822eSBjorn Andersson 		goto free_ept;
89453e2822eSBjorn Andersson 
89553e2822eSBjorn Andersson 	return ept;
89653e2822eSBjorn Andersson 
89753e2822eSBjorn Andersson free_ept:
89853e2822eSBjorn Andersson 	channel->qsept = NULL;
89953e2822eSBjorn Andersson 	kref_put(&ept->refcount, __ept_release);
90053e2822eSBjorn Andersson 	return NULL;
90153e2822eSBjorn Andersson }
90253e2822eSBjorn Andersson 
90353e2822eSBjorn Andersson static void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept)
90453e2822eSBjorn Andersson {
90553e2822eSBjorn Andersson 	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
90653e2822eSBjorn Andersson 	struct qcom_smd_channel *ch = qsept->qsch;
90753e2822eSBjorn Andersson 
90853e2822eSBjorn Andersson 	qcom_smd_channel_close(ch);
90953e2822eSBjorn Andersson 	ch->qsept = NULL;
91053e2822eSBjorn Andersson 	kref_put(&ept->refcount, __ept_release);
91153e2822eSBjorn Andersson }
91253e2822eSBjorn Andersson 
91353e2822eSBjorn Andersson static int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len)
91453e2822eSBjorn Andersson {
91553e2822eSBjorn Andersson 	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
91653e2822eSBjorn Andersson 
91753e2822eSBjorn Andersson 	return __qcom_smd_send(qsept->qsch, data, len, true);
91853e2822eSBjorn Andersson }
91953e2822eSBjorn Andersson 
92053e2822eSBjorn Andersson static int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len)
92153e2822eSBjorn Andersson {
92253e2822eSBjorn Andersson 	struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
92353e2822eSBjorn Andersson 
92453e2822eSBjorn Andersson 	return __qcom_smd_send(qsept->qsch, data, len, false);
92553e2822eSBjorn Andersson }
92653e2822eSBjorn Andersson 
92753e2822eSBjorn Andersson /*
92853e2822eSBjorn Andersson  * Finds the device_node for the smd child interested in this channel.
92953e2822eSBjorn Andersson  */
93053e2822eSBjorn Andersson static struct device_node *qcom_smd_match_channel(struct device_node *edge_node,
93153e2822eSBjorn Andersson 						  const char *channel)
93253e2822eSBjorn Andersson {
93353e2822eSBjorn Andersson 	struct device_node *child;
93453e2822eSBjorn Andersson 	const char *name;
93553e2822eSBjorn Andersson 	const char *key;
93653e2822eSBjorn Andersson 	int ret;
93753e2822eSBjorn Andersson 
93853e2822eSBjorn Andersson 	for_each_available_child_of_node(edge_node, child) {
93953e2822eSBjorn Andersson 		key = "qcom,smd-channels";
94053e2822eSBjorn Andersson 		ret = of_property_read_string(child, key, &name);
94153e2822eSBjorn Andersson 		if (ret)
94253e2822eSBjorn Andersson 			continue;
94353e2822eSBjorn Andersson 
94453e2822eSBjorn Andersson 		if (strcmp(name, channel) == 0)
94553e2822eSBjorn Andersson 			return child;
94653e2822eSBjorn Andersson 	}
94753e2822eSBjorn Andersson 
94853e2822eSBjorn Andersson 	return NULL;
94953e2822eSBjorn Andersson }
95053e2822eSBjorn Andersson 
95153e2822eSBjorn Andersson static const struct rpmsg_device_ops qcom_smd_device_ops = {
95253e2822eSBjorn Andersson 	.create_ept = qcom_smd_create_ept,
95353e2822eSBjorn Andersson };
95453e2822eSBjorn Andersson 
95553e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = {
95653e2822eSBjorn Andersson 	.destroy_ept = qcom_smd_destroy_ept,
95753e2822eSBjorn Andersson 	.send = qcom_smd_send,
95853e2822eSBjorn Andersson 	.trysend = qcom_smd_trysend,
95953e2822eSBjorn Andersson };
96053e2822eSBjorn Andersson 
96153e2822eSBjorn Andersson /*
96253e2822eSBjorn Andersson  * Create a smd client device for channel that is being opened.
96353e2822eSBjorn Andersson  */
96453e2822eSBjorn Andersson static int qcom_smd_create_device(struct qcom_smd_channel *channel)
96553e2822eSBjorn Andersson {
96653e2822eSBjorn Andersson 	struct qcom_smd_device *qsdev;
96753e2822eSBjorn Andersson 	struct rpmsg_device *rpdev;
96853e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = channel->edge;
96953e2822eSBjorn Andersson 
97053e2822eSBjorn Andersson 	dev_dbg(&edge->dev, "registering '%s'\n", channel->name);
97153e2822eSBjorn Andersson 
97253e2822eSBjorn Andersson 	qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
97353e2822eSBjorn Andersson 	if (!qsdev)
97453e2822eSBjorn Andersson 		return -ENOMEM;
97553e2822eSBjorn Andersson 
97653e2822eSBjorn Andersson 	/* Link qsdev to our SMD edge */
97753e2822eSBjorn Andersson 	qsdev->edge = edge;
97853e2822eSBjorn Andersson 
97953e2822eSBjorn Andersson 	/* Assign callbacks for rpmsg_device */
98053e2822eSBjorn Andersson 	qsdev->rpdev.ops = &qcom_smd_device_ops;
98153e2822eSBjorn Andersson 
98253e2822eSBjorn Andersson 	/* Assign public information to the rpmsg_device */
98353e2822eSBjorn Andersson 	rpdev = &qsdev->rpdev;
98453e2822eSBjorn Andersson 	strncpy(rpdev->id.name, channel->name, RPMSG_NAME_SIZE);
98553e2822eSBjorn Andersson 	rpdev->src = RPMSG_ADDR_ANY;
98653e2822eSBjorn Andersson 	rpdev->dst = RPMSG_ADDR_ANY;
98753e2822eSBjorn Andersson 
98853e2822eSBjorn Andersson 	rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name);
98953e2822eSBjorn Andersson 	rpdev->dev.parent = &edge->dev;
99053e2822eSBjorn Andersson 
99153e2822eSBjorn Andersson 	return rpmsg_register_device(rpdev);
99253e2822eSBjorn Andersson }
99353e2822eSBjorn Andersson 
99453e2822eSBjorn Andersson /*
99553e2822eSBjorn Andersson  * Allocate the qcom_smd_channel object for a newly found smd channel,
99653e2822eSBjorn Andersson  * retrieving and validating the smem items involved.
99753e2822eSBjorn Andersson  */
99853e2822eSBjorn Andersson static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge,
99953e2822eSBjorn Andersson 							unsigned smem_info_item,
100053e2822eSBjorn Andersson 							unsigned smem_fifo_item,
100153e2822eSBjorn Andersson 							char *name)
100253e2822eSBjorn Andersson {
100353e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
100453e2822eSBjorn Andersson 	size_t fifo_size;
100553e2822eSBjorn Andersson 	size_t info_size;
100653e2822eSBjorn Andersson 	void *fifo_base;
100753e2822eSBjorn Andersson 	void *info;
100853e2822eSBjorn Andersson 	int ret;
100953e2822eSBjorn Andersson 
101053e2822eSBjorn Andersson 	channel = devm_kzalloc(&edge->dev, sizeof(*channel), GFP_KERNEL);
101153e2822eSBjorn Andersson 	if (!channel)
101253e2822eSBjorn Andersson 		return ERR_PTR(-ENOMEM);
101353e2822eSBjorn Andersson 
101453e2822eSBjorn Andersson 	channel->edge = edge;
101553e2822eSBjorn Andersson 	channel->name = devm_kstrdup(&edge->dev, name, GFP_KERNEL);
101653e2822eSBjorn Andersson 	if (!channel->name)
101753e2822eSBjorn Andersson 		return ERR_PTR(-ENOMEM);
101853e2822eSBjorn Andersson 
101953e2822eSBjorn Andersson 	mutex_init(&channel->tx_lock);
102053e2822eSBjorn Andersson 	spin_lock_init(&channel->recv_lock);
102153e2822eSBjorn Andersson 	init_waitqueue_head(&channel->fblockread_event);
102253e2822eSBjorn Andersson 
102353e2822eSBjorn Andersson 	info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size);
102453e2822eSBjorn Andersson 	if (IS_ERR(info)) {
102553e2822eSBjorn Andersson 		ret = PTR_ERR(info);
102653e2822eSBjorn Andersson 		goto free_name_and_channel;
102753e2822eSBjorn Andersson 	}
102853e2822eSBjorn Andersson 
102953e2822eSBjorn Andersson 	/*
103053e2822eSBjorn Andersson 	 * Use the size of the item to figure out which channel info struct to
103153e2822eSBjorn Andersson 	 * use.
103253e2822eSBjorn Andersson 	 */
103353e2822eSBjorn Andersson 	if (info_size == 2 * sizeof(struct smd_channel_info_word)) {
103453e2822eSBjorn Andersson 		channel->info_word = info;
103553e2822eSBjorn Andersson 	} else if (info_size == 2 * sizeof(struct smd_channel_info)) {
103653e2822eSBjorn Andersson 		channel->info = info;
103753e2822eSBjorn Andersson 	} else {
103853e2822eSBjorn Andersson 		dev_err(&edge->dev,
103953e2822eSBjorn Andersson 			"channel info of size %zu not supported\n", info_size);
104053e2822eSBjorn Andersson 		ret = -EINVAL;
104153e2822eSBjorn Andersson 		goto free_name_and_channel;
104253e2822eSBjorn Andersson 	}
104353e2822eSBjorn Andersson 
104453e2822eSBjorn Andersson 	fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size);
104553e2822eSBjorn Andersson 	if (IS_ERR(fifo_base)) {
104653e2822eSBjorn Andersson 		ret =  PTR_ERR(fifo_base);
104753e2822eSBjorn Andersson 		goto free_name_and_channel;
104853e2822eSBjorn Andersson 	}
104953e2822eSBjorn Andersson 
105053e2822eSBjorn Andersson 	/* The channel consist of a rx and tx fifo of equal size */
105153e2822eSBjorn Andersson 	fifo_size /= 2;
105253e2822eSBjorn Andersson 
105353e2822eSBjorn Andersson 	dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
105453e2822eSBjorn Andersson 			  name, info_size, fifo_size);
105553e2822eSBjorn Andersson 
105653e2822eSBjorn Andersson 	channel->tx_fifo = fifo_base;
105753e2822eSBjorn Andersson 	channel->rx_fifo = fifo_base + fifo_size;
105853e2822eSBjorn Andersson 	channel->fifo_size = fifo_size;
105953e2822eSBjorn Andersson 
106053e2822eSBjorn Andersson 	qcom_smd_channel_reset(channel);
106153e2822eSBjorn Andersson 
106253e2822eSBjorn Andersson 	return channel;
106353e2822eSBjorn Andersson 
106453e2822eSBjorn Andersson free_name_and_channel:
106553e2822eSBjorn Andersson 	devm_kfree(&edge->dev, channel->name);
106653e2822eSBjorn Andersson 	devm_kfree(&edge->dev, channel);
106753e2822eSBjorn Andersson 
106853e2822eSBjorn Andersson 	return ERR_PTR(ret);
106953e2822eSBjorn Andersson }
107053e2822eSBjorn Andersson 
107153e2822eSBjorn Andersson /*
107253e2822eSBjorn Andersson  * Scans the allocation table for any newly allocated channels, calls
107353e2822eSBjorn Andersson  * qcom_smd_create_channel() to create representations of these and add
107453e2822eSBjorn Andersson  * them to the edge's list of channels.
107553e2822eSBjorn Andersson  */
107653e2822eSBjorn Andersson static void qcom_channel_scan_worker(struct work_struct *work)
107753e2822eSBjorn Andersson {
107853e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work);
107953e2822eSBjorn Andersson 	struct qcom_smd_alloc_entry *alloc_tbl;
108053e2822eSBjorn Andersson 	struct qcom_smd_alloc_entry *entry;
108153e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
108253e2822eSBjorn Andersson 	unsigned long flags;
108353e2822eSBjorn Andersson 	unsigned fifo_id;
108453e2822eSBjorn Andersson 	unsigned info_id;
108553e2822eSBjorn Andersson 	int tbl;
108653e2822eSBjorn Andersson 	int i;
108753e2822eSBjorn Andersson 	u32 eflags, cid;
108853e2822eSBjorn Andersson 
108953e2822eSBjorn Andersson 	for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) {
109053e2822eSBjorn Andersson 		alloc_tbl = qcom_smem_get(edge->remote_pid,
109153e2822eSBjorn Andersson 				    smem_items[tbl].alloc_tbl_id, NULL);
109253e2822eSBjorn Andersson 		if (IS_ERR(alloc_tbl))
109353e2822eSBjorn Andersson 			continue;
109453e2822eSBjorn Andersson 
109553e2822eSBjorn Andersson 		for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) {
109653e2822eSBjorn Andersson 			entry = &alloc_tbl[i];
109753e2822eSBjorn Andersson 			eflags = le32_to_cpu(entry->flags);
109853e2822eSBjorn Andersson 			if (test_bit(i, edge->allocated[tbl]))
109953e2822eSBjorn Andersson 				continue;
110053e2822eSBjorn Andersson 
110153e2822eSBjorn Andersson 			if (entry->ref_count == 0)
110253e2822eSBjorn Andersson 				continue;
110353e2822eSBjorn Andersson 
110453e2822eSBjorn Andersson 			if (!entry->name[0])
110553e2822eSBjorn Andersson 				continue;
110653e2822eSBjorn Andersson 
110753e2822eSBjorn Andersson 			if (!(eflags & SMD_CHANNEL_FLAGS_PACKET))
110853e2822eSBjorn Andersson 				continue;
110953e2822eSBjorn Andersson 
111053e2822eSBjorn Andersson 			if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id)
111153e2822eSBjorn Andersson 				continue;
111253e2822eSBjorn Andersson 
111353e2822eSBjorn Andersson 			cid = le32_to_cpu(entry->cid);
111453e2822eSBjorn Andersson 			info_id = smem_items[tbl].info_base_id + cid;
111553e2822eSBjorn Andersson 			fifo_id = smem_items[tbl].fifo_base_id + cid;
111653e2822eSBjorn Andersson 
111753e2822eSBjorn Andersson 			channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name);
111853e2822eSBjorn Andersson 			if (IS_ERR(channel))
111953e2822eSBjorn Andersson 				continue;
112053e2822eSBjorn Andersson 
112153e2822eSBjorn Andersson 			spin_lock_irqsave(&edge->channels_lock, flags);
112253e2822eSBjorn Andersson 			list_add(&channel->list, &edge->channels);
112353e2822eSBjorn Andersson 			spin_unlock_irqrestore(&edge->channels_lock, flags);
112453e2822eSBjorn Andersson 
112553e2822eSBjorn Andersson 			dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name);
112653e2822eSBjorn Andersson 			set_bit(i, edge->allocated[tbl]);
112753e2822eSBjorn Andersson 
112853e2822eSBjorn Andersson 			wake_up_interruptible(&edge->new_channel_event);
112953e2822eSBjorn Andersson 		}
113053e2822eSBjorn Andersson 	}
113153e2822eSBjorn Andersson 
113253e2822eSBjorn Andersson 	schedule_work(&edge->state_work);
113353e2822eSBjorn Andersson }
113453e2822eSBjorn Andersson 
113553e2822eSBjorn Andersson /*
113653e2822eSBjorn Andersson  * This per edge worker scans smem for any new channels and register these. It
113753e2822eSBjorn Andersson  * then scans all registered channels for state changes that should be handled
113853e2822eSBjorn Andersson  * by creating or destroying smd client devices for the registered channels.
113953e2822eSBjorn Andersson  *
114053e2822eSBjorn Andersson  * LOCKING: edge->channels_lock only needs to cover the list operations, as the
114153e2822eSBjorn Andersson  * worker is killed before any channels are deallocated
114253e2822eSBjorn Andersson  */
114353e2822eSBjorn Andersson static void qcom_channel_state_worker(struct work_struct *work)
114453e2822eSBjorn Andersson {
114553e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
114653e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = container_of(work,
114753e2822eSBjorn Andersson 						  struct qcom_smd_edge,
114853e2822eSBjorn Andersson 						  state_work);
114953e2822eSBjorn Andersson 	struct rpmsg_channel_info chinfo;
115053e2822eSBjorn Andersson 	unsigned remote_state;
115153e2822eSBjorn Andersson 	unsigned long flags;
115253e2822eSBjorn Andersson 
115353e2822eSBjorn Andersson 	/*
115453e2822eSBjorn Andersson 	 * Register a device for any closed channel where the remote processor
115553e2822eSBjorn Andersson 	 * is showing interest in opening the channel.
115653e2822eSBjorn Andersson 	 */
115753e2822eSBjorn Andersson 	spin_lock_irqsave(&edge->channels_lock, flags);
115853e2822eSBjorn Andersson 	list_for_each_entry(channel, &edge->channels, list) {
115953e2822eSBjorn Andersson 		if (channel->state != SMD_CHANNEL_CLOSED)
116053e2822eSBjorn Andersson 			continue;
116153e2822eSBjorn Andersson 
116253e2822eSBjorn Andersson 		remote_state = GET_RX_CHANNEL_INFO(channel, state);
116353e2822eSBjorn Andersson 		if (remote_state != SMD_CHANNEL_OPENING &&
116453e2822eSBjorn Andersson 		    remote_state != SMD_CHANNEL_OPENED)
116553e2822eSBjorn Andersson 			continue;
116653e2822eSBjorn Andersson 
116753e2822eSBjorn Andersson 		if (channel->registered)
116853e2822eSBjorn Andersson 			continue;
116953e2822eSBjorn Andersson 
117053e2822eSBjorn Andersson 		spin_unlock_irqrestore(&edge->channels_lock, flags);
117153e2822eSBjorn Andersson 		qcom_smd_create_device(channel);
117253e2822eSBjorn Andersson 		channel->registered = true;
117353e2822eSBjorn Andersson 		spin_lock_irqsave(&edge->channels_lock, flags);
117453e2822eSBjorn Andersson 
117553e2822eSBjorn Andersson 		channel->registered = true;
117653e2822eSBjorn Andersson 	}
117753e2822eSBjorn Andersson 
117853e2822eSBjorn Andersson 	/*
117953e2822eSBjorn Andersson 	 * Unregister the device for any channel that is opened where the
118053e2822eSBjorn Andersson 	 * remote processor is closing the channel.
118153e2822eSBjorn Andersson 	 */
118253e2822eSBjorn Andersson 	list_for_each_entry(channel, &edge->channels, list) {
118353e2822eSBjorn Andersson 		if (channel->state != SMD_CHANNEL_OPENING &&
118453e2822eSBjorn Andersson 		    channel->state != SMD_CHANNEL_OPENED)
118553e2822eSBjorn Andersson 			continue;
118653e2822eSBjorn Andersson 
118753e2822eSBjorn Andersson 		remote_state = GET_RX_CHANNEL_INFO(channel, state);
118853e2822eSBjorn Andersson 		if (remote_state == SMD_CHANNEL_OPENING ||
118953e2822eSBjorn Andersson 		    remote_state == SMD_CHANNEL_OPENED)
119053e2822eSBjorn Andersson 			continue;
119153e2822eSBjorn Andersson 
119253e2822eSBjorn Andersson 		spin_unlock_irqrestore(&edge->channels_lock, flags);
119353e2822eSBjorn Andersson 
119453e2822eSBjorn Andersson 		strncpy(chinfo.name, channel->name, sizeof(chinfo.name));
119553e2822eSBjorn Andersson 		chinfo.src = RPMSG_ADDR_ANY;
119653e2822eSBjorn Andersson 		chinfo.dst = RPMSG_ADDR_ANY;
119753e2822eSBjorn Andersson 		rpmsg_unregister_device(&edge->dev, &chinfo);
119853e2822eSBjorn Andersson 		channel->registered = false;
119953e2822eSBjorn Andersson 		spin_lock_irqsave(&edge->channels_lock, flags);
120053e2822eSBjorn Andersson 	}
120153e2822eSBjorn Andersson 	spin_unlock_irqrestore(&edge->channels_lock, flags);
120253e2822eSBjorn Andersson }
120353e2822eSBjorn Andersson 
120453e2822eSBjorn Andersson /*
120553e2822eSBjorn Andersson  * Parses an of_node describing an edge.
120653e2822eSBjorn Andersson  */
120753e2822eSBjorn Andersson static int qcom_smd_parse_edge(struct device *dev,
120853e2822eSBjorn Andersson 			       struct device_node *node,
120953e2822eSBjorn Andersson 			       struct qcom_smd_edge *edge)
121053e2822eSBjorn Andersson {
121153e2822eSBjorn Andersson 	struct device_node *syscon_np;
121253e2822eSBjorn Andersson 	const char *key;
121353e2822eSBjorn Andersson 	int irq;
121453e2822eSBjorn Andersson 	int ret;
121553e2822eSBjorn Andersson 
121653e2822eSBjorn Andersson 	INIT_LIST_HEAD(&edge->channels);
121753e2822eSBjorn Andersson 	spin_lock_init(&edge->channels_lock);
121853e2822eSBjorn Andersson 
121953e2822eSBjorn Andersson 	INIT_WORK(&edge->scan_work, qcom_channel_scan_worker);
122053e2822eSBjorn Andersson 	INIT_WORK(&edge->state_work, qcom_channel_state_worker);
122153e2822eSBjorn Andersson 
122253e2822eSBjorn Andersson 	edge->of_node = of_node_get(node);
122353e2822eSBjorn Andersson 
122453e2822eSBjorn Andersson 	key = "qcom,smd-edge";
122553e2822eSBjorn Andersson 	ret = of_property_read_u32(node, key, &edge->edge_id);
122653e2822eSBjorn Andersson 	if (ret) {
122753e2822eSBjorn Andersson 		dev_err(dev, "edge missing %s property\n", key);
122853e2822eSBjorn Andersson 		return -EINVAL;
122953e2822eSBjorn Andersson 	}
123053e2822eSBjorn Andersson 
123153e2822eSBjorn Andersson 	edge->remote_pid = QCOM_SMEM_HOST_ANY;
123253e2822eSBjorn Andersson 	key = "qcom,remote-pid";
123353e2822eSBjorn Andersson 	of_property_read_u32(node, key, &edge->remote_pid);
123453e2822eSBjorn Andersson 
123553e2822eSBjorn Andersson 	syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
123653e2822eSBjorn Andersson 	if (!syscon_np) {
123753e2822eSBjorn Andersson 		dev_err(dev, "no qcom,ipc node\n");
123853e2822eSBjorn Andersson 		return -ENODEV;
123953e2822eSBjorn Andersson 	}
124053e2822eSBjorn Andersson 
124153e2822eSBjorn Andersson 	edge->ipc_regmap = syscon_node_to_regmap(syscon_np);
124253e2822eSBjorn Andersson 	if (IS_ERR(edge->ipc_regmap))
124353e2822eSBjorn Andersson 		return PTR_ERR(edge->ipc_regmap);
124453e2822eSBjorn Andersson 
124553e2822eSBjorn Andersson 	key = "qcom,ipc";
124653e2822eSBjorn Andersson 	ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset);
124753e2822eSBjorn Andersson 	if (ret < 0) {
124853e2822eSBjorn Andersson 		dev_err(dev, "no offset in %s\n", key);
124953e2822eSBjorn Andersson 		return -EINVAL;
125053e2822eSBjorn Andersson 	}
125153e2822eSBjorn Andersson 
125253e2822eSBjorn Andersson 	ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit);
125353e2822eSBjorn Andersson 	if (ret < 0) {
125453e2822eSBjorn Andersson 		dev_err(dev, "no bit in %s\n", key);
125553e2822eSBjorn Andersson 		return -EINVAL;
125653e2822eSBjorn Andersson 	}
125753e2822eSBjorn Andersson 
125853e2822eSBjorn Andersson 	irq = irq_of_parse_and_map(node, 0);
125953e2822eSBjorn Andersson 	if (irq < 0) {
126053e2822eSBjorn Andersson 		dev_err(dev, "required smd interrupt missing\n");
126153e2822eSBjorn Andersson 		return -EINVAL;
126253e2822eSBjorn Andersson 	}
126353e2822eSBjorn Andersson 
126453e2822eSBjorn Andersson 	ret = devm_request_irq(dev, irq,
126553e2822eSBjorn Andersson 			       qcom_smd_edge_intr, IRQF_TRIGGER_RISING,
126653e2822eSBjorn Andersson 			       node->name, edge);
126753e2822eSBjorn Andersson 	if (ret) {
126853e2822eSBjorn Andersson 		dev_err(dev, "failed to request smd irq\n");
126953e2822eSBjorn Andersson 		return ret;
127053e2822eSBjorn Andersson 	}
127153e2822eSBjorn Andersson 
127253e2822eSBjorn Andersson 	edge->irq = irq;
127353e2822eSBjorn Andersson 
127453e2822eSBjorn Andersson 	return 0;
127553e2822eSBjorn Andersson }
127653e2822eSBjorn Andersson 
127753e2822eSBjorn Andersson /*
127853e2822eSBjorn Andersson  * Release function for an edge.
127953e2822eSBjorn Andersson   * Reset the state of each associated channel and free the edge context.
128053e2822eSBjorn Andersson  */
128153e2822eSBjorn Andersson static void qcom_smd_edge_release(struct device *dev)
128253e2822eSBjorn Andersson {
128353e2822eSBjorn Andersson 	struct qcom_smd_channel *channel;
128453e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = to_smd_edge(dev);
128553e2822eSBjorn Andersson 
128653e2822eSBjorn Andersson 	list_for_each_entry(channel, &edge->channels, list) {
128753e2822eSBjorn Andersson 		SET_RX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED);
128853e2822eSBjorn Andersson 		SET_RX_CHANNEL_INFO(channel, head, 0);
128953e2822eSBjorn Andersson 		SET_RX_CHANNEL_INFO(channel, tail, 0);
129053e2822eSBjorn Andersson 	}
129153e2822eSBjorn Andersson 
129253e2822eSBjorn Andersson 	kfree(edge);
129353e2822eSBjorn Andersson }
129453e2822eSBjorn Andersson 
129553e2822eSBjorn Andersson /**
129653e2822eSBjorn Andersson  * qcom_smd_register_edge() - register an edge based on an device_node
129753e2822eSBjorn Andersson  * @parent:    parent device for the edge
129853e2822eSBjorn Andersson  * @node:      device_node describing the edge
129953e2822eSBjorn Andersson  *
130053e2822eSBjorn Andersson  * Returns an edge reference, or negative ERR_PTR() on failure.
130153e2822eSBjorn Andersson  */
130253e2822eSBjorn Andersson struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
130353e2822eSBjorn Andersson 					     struct device_node *node)
130453e2822eSBjorn Andersson {
130553e2822eSBjorn Andersson 	struct qcom_smd_edge *edge;
130653e2822eSBjorn Andersson 	int ret;
130753e2822eSBjorn Andersson 
130853e2822eSBjorn Andersson 	edge = kzalloc(sizeof(*edge), GFP_KERNEL);
130953e2822eSBjorn Andersson 	if (!edge)
131053e2822eSBjorn Andersson 		return ERR_PTR(-ENOMEM);
131153e2822eSBjorn Andersson 
131253e2822eSBjorn Andersson 	init_waitqueue_head(&edge->new_channel_event);
131353e2822eSBjorn Andersson 
131453e2822eSBjorn Andersson 	edge->dev.parent = parent;
131553e2822eSBjorn Andersson 	edge->dev.release = qcom_smd_edge_release;
131653e2822eSBjorn Andersson 	dev_set_name(&edge->dev, "%s:%s", dev_name(parent), node->name);
131753e2822eSBjorn Andersson 	ret = device_register(&edge->dev);
131853e2822eSBjorn Andersson 	if (ret) {
131953e2822eSBjorn Andersson 		pr_err("failed to register smd edge\n");
132053e2822eSBjorn Andersson 		return ERR_PTR(ret);
132153e2822eSBjorn Andersson 	}
132253e2822eSBjorn Andersson 
132353e2822eSBjorn Andersson 	ret = qcom_smd_parse_edge(&edge->dev, node, edge);
132453e2822eSBjorn Andersson 	if (ret) {
132553e2822eSBjorn Andersson 		dev_err(&edge->dev, "failed to parse smd edge\n");
132653e2822eSBjorn Andersson 		goto unregister_dev;
132753e2822eSBjorn Andersson 	}
132853e2822eSBjorn Andersson 
132953e2822eSBjorn Andersson 	schedule_work(&edge->scan_work);
133053e2822eSBjorn Andersson 
133153e2822eSBjorn Andersson 	return edge;
133253e2822eSBjorn Andersson 
133353e2822eSBjorn Andersson unregister_dev:
133453e2822eSBjorn Andersson 	put_device(&edge->dev);
133553e2822eSBjorn Andersson 	return ERR_PTR(ret);
133653e2822eSBjorn Andersson }
133753e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_register_edge);
133853e2822eSBjorn Andersson 
133953e2822eSBjorn Andersson static int qcom_smd_remove_device(struct device *dev, void *data)
134053e2822eSBjorn Andersson {
134153e2822eSBjorn Andersson 	device_unregister(dev);
134253e2822eSBjorn Andersson 
134353e2822eSBjorn Andersson 	return 0;
134453e2822eSBjorn Andersson }
134553e2822eSBjorn Andersson 
134653e2822eSBjorn Andersson /**
134753e2822eSBjorn Andersson  * qcom_smd_unregister_edge() - release an edge and its children
134853e2822eSBjorn Andersson  * @edge:      edge reference acquired from qcom_smd_register_edge
134953e2822eSBjorn Andersson  */
135053e2822eSBjorn Andersson int qcom_smd_unregister_edge(struct qcom_smd_edge *edge)
135153e2822eSBjorn Andersson {
135253e2822eSBjorn Andersson 	int ret;
135353e2822eSBjorn Andersson 
135453e2822eSBjorn Andersson 	disable_irq(edge->irq);
135553e2822eSBjorn Andersson 	cancel_work_sync(&edge->scan_work);
135653e2822eSBjorn Andersson 	cancel_work_sync(&edge->state_work);
135753e2822eSBjorn Andersson 
135853e2822eSBjorn Andersson 	ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device);
135953e2822eSBjorn Andersson 	if (ret)
136053e2822eSBjorn Andersson 		dev_warn(&edge->dev, "can't remove smd device: %d\n", ret);
136153e2822eSBjorn Andersson 
136253e2822eSBjorn Andersson 	device_unregister(&edge->dev);
136353e2822eSBjorn Andersson 
136453e2822eSBjorn Andersson 	return 0;
136553e2822eSBjorn Andersson }
136653e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_unregister_edge);
136753e2822eSBjorn Andersson 
136853e2822eSBjorn Andersson static int qcom_smd_probe(struct platform_device *pdev)
136953e2822eSBjorn Andersson {
137053e2822eSBjorn Andersson 	struct device_node *node;
137153e2822eSBjorn Andersson 	void *p;
137253e2822eSBjorn Andersson 
137353e2822eSBjorn Andersson 	/* Wait for smem */
137453e2822eSBjorn Andersson 	p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL);
137553e2822eSBjorn Andersson 	if (PTR_ERR(p) == -EPROBE_DEFER)
137653e2822eSBjorn Andersson 		return PTR_ERR(p);
137753e2822eSBjorn Andersson 
137853e2822eSBjorn Andersson 	for_each_available_child_of_node(pdev->dev.of_node, node)
137953e2822eSBjorn Andersson 		qcom_smd_register_edge(&pdev->dev, node);
138053e2822eSBjorn Andersson 
138153e2822eSBjorn Andersson 	return 0;
138253e2822eSBjorn Andersson }
138353e2822eSBjorn Andersson 
138453e2822eSBjorn Andersson static int qcom_smd_remove_edge(struct device *dev, void *data)
138553e2822eSBjorn Andersson {
138653e2822eSBjorn Andersson 	struct qcom_smd_edge *edge = to_smd_edge(dev);
138753e2822eSBjorn Andersson 
138853e2822eSBjorn Andersson 	return qcom_smd_unregister_edge(edge);
138953e2822eSBjorn Andersson }
139053e2822eSBjorn Andersson 
139153e2822eSBjorn Andersson /*
139253e2822eSBjorn Andersson  * Shut down all smd clients by making sure that each edge stops processing
139353e2822eSBjorn Andersson  * events and scanning for new channels, then call destroy on the devices.
139453e2822eSBjorn Andersson  */
139553e2822eSBjorn Andersson static int qcom_smd_remove(struct platform_device *pdev)
139653e2822eSBjorn Andersson {
139753e2822eSBjorn Andersson 	int ret;
139853e2822eSBjorn Andersson 
139953e2822eSBjorn Andersson 	ret = device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge);
140053e2822eSBjorn Andersson 	if (ret)
140153e2822eSBjorn Andersson 		dev_warn(&pdev->dev, "can't remove smd device: %d\n", ret);
140253e2822eSBjorn Andersson 
140353e2822eSBjorn Andersson 	return ret;
140453e2822eSBjorn Andersson }
140553e2822eSBjorn Andersson 
140653e2822eSBjorn Andersson static const struct of_device_id qcom_smd_of_match[] = {
140753e2822eSBjorn Andersson 	{ .compatible = "qcom,smd" },
140853e2822eSBjorn Andersson 	{}
140953e2822eSBjorn Andersson };
141053e2822eSBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smd_of_match);
141153e2822eSBjorn Andersson 
141253e2822eSBjorn Andersson static struct platform_driver qcom_smd_driver = {
141353e2822eSBjorn Andersson 	.probe = qcom_smd_probe,
141453e2822eSBjorn Andersson 	.remove = qcom_smd_remove,
141553e2822eSBjorn Andersson 	.driver = {
141653e2822eSBjorn Andersson 		.name = "qcom-smd",
141753e2822eSBjorn Andersson 		.of_match_table = qcom_smd_of_match,
141853e2822eSBjorn Andersson 	},
141953e2822eSBjorn Andersson };
142053e2822eSBjorn Andersson 
142153e2822eSBjorn Andersson static int __init qcom_smd_init(void)
142253e2822eSBjorn Andersson {
142353e2822eSBjorn Andersson 	return platform_driver_register(&qcom_smd_driver);
142453e2822eSBjorn Andersson }
142553e2822eSBjorn Andersson subsys_initcall(qcom_smd_init);
142653e2822eSBjorn Andersson 
142753e2822eSBjorn Andersson static void __exit qcom_smd_exit(void)
142853e2822eSBjorn Andersson {
142953e2822eSBjorn Andersson 	platform_driver_unregister(&qcom_smd_driver);
143053e2822eSBjorn Andersson }
143153e2822eSBjorn Andersson module_exit(qcom_smd_exit);
143253e2822eSBjorn Andersson 
143353e2822eSBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
143453e2822eSBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Driver");
143553e2822eSBjorn Andersson MODULE_LICENSE("GPL v2");
1436