149b05978SSuman Anna // SPDX-License-Identifier: GPL-2.0 253e2822eSBjorn Andersson /* 353e2822eSBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications AB. 453e2822eSBjorn Andersson * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 553e2822eSBjorn Andersson */ 653e2822eSBjorn Andersson 753e2822eSBjorn Andersson #include <linux/interrupt.h> 853e2822eSBjorn Andersson #include <linux/io.h> 9ab460a2eSBjorn Andersson #include <linux/mailbox_client.h> 1053e2822eSBjorn Andersson #include <linux/mfd/syscon.h> 1153e2822eSBjorn Andersson #include <linux/module.h> 1253e2822eSBjorn Andersson #include <linux/of_irq.h> 1353e2822eSBjorn Andersson #include <linux/of_platform.h> 1453e2822eSBjorn Andersson #include <linux/platform_device.h> 1553e2822eSBjorn Andersson #include <linux/regmap.h> 1653e2822eSBjorn Andersson #include <linux/sched.h> 1767cd0eecSNiklas Cassel #include <linux/sizes.h> 1853e2822eSBjorn Andersson #include <linux/slab.h> 1953e2822eSBjorn Andersson #include <linux/soc/qcom/smem.h> 2053e2822eSBjorn Andersson #include <linux/wait.h> 2153e2822eSBjorn Andersson #include <linux/rpmsg.h> 228fc94723SBjorn Andersson #include <linux/rpmsg/qcom_smd.h> 2353e2822eSBjorn Andersson 2453e2822eSBjorn Andersson #include "rpmsg_internal.h" 2553e2822eSBjorn Andersson 2653e2822eSBjorn Andersson /* 2753e2822eSBjorn Andersson * The Qualcomm Shared Memory communication solution provides point-to-point 2853e2822eSBjorn Andersson * channels for clients to send and receive streaming or packet based data. 2953e2822eSBjorn Andersson * 3053e2822eSBjorn Andersson * Each channel consists of a control item (channel info) and a ring buffer 3153e2822eSBjorn Andersson * pair. The channel info carry information related to channel state, flow 3253e2822eSBjorn Andersson * control and the offsets within the ring buffer. 3353e2822eSBjorn Andersson * 3453e2822eSBjorn Andersson * All allocated channels are listed in an allocation table, identifying the 3553e2822eSBjorn Andersson * pair of items by name, type and remote processor. 3653e2822eSBjorn Andersson * 3753e2822eSBjorn Andersson * Upon creating a new channel the remote processor allocates channel info and 3853e2822eSBjorn Andersson * ring buffer items from the smem heap and populate the allocation table. An 3953e2822eSBjorn Andersson * interrupt is sent to the other end of the channel and a scan for new 4053e2822eSBjorn Andersson * channels should be done. A channel never goes away, it will only change 4153e2822eSBjorn Andersson * state. 4253e2822eSBjorn Andersson * 4353e2822eSBjorn Andersson * The remote processor signals it intent for bring up the communication 4453e2822eSBjorn Andersson * channel by setting the state of its end of the channel to "opening" and 4553e2822eSBjorn Andersson * sends out an interrupt. We detect this change and register a smd device to 4653e2822eSBjorn Andersson * consume the channel. Upon finding a consumer we finish the handshake and the 4753e2822eSBjorn Andersson * channel is up. 4853e2822eSBjorn Andersson * 4953e2822eSBjorn Andersson * Upon closing a channel, the remote processor will update the state of its 5053e2822eSBjorn Andersson * end of the channel and signal us, we will then unregister any attached 5153e2822eSBjorn Andersson * device and close our end of the channel. 5253e2822eSBjorn Andersson * 5353e2822eSBjorn Andersson * Devices attached to a channel can use the qcom_smd_send function to push 5453e2822eSBjorn Andersson * data to the channel, this is done by copying the data into the tx ring 5553e2822eSBjorn Andersson * buffer, updating the pointers in the channel info and signaling the remote 5653e2822eSBjorn Andersson * processor. 5753e2822eSBjorn Andersson * 5853e2822eSBjorn Andersson * The remote processor does the equivalent when it transfer data and upon 5953e2822eSBjorn Andersson * receiving the interrupt we check the channel info for new data and delivers 6053e2822eSBjorn Andersson * this to the attached device. If the device is not ready to receive the data 6153e2822eSBjorn Andersson * we leave it in the ring buffer for now. 6253e2822eSBjorn Andersson */ 6353e2822eSBjorn Andersson 6453e2822eSBjorn Andersson struct smd_channel_info; 6553e2822eSBjorn Andersson struct smd_channel_info_pair; 6653e2822eSBjorn Andersson struct smd_channel_info_word; 6753e2822eSBjorn Andersson struct smd_channel_info_word_pair; 6853e2822eSBjorn Andersson 6953e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops; 7053e2822eSBjorn Andersson 7153e2822eSBjorn Andersson #define SMD_ALLOC_TBL_COUNT 2 7253e2822eSBjorn Andersson #define SMD_ALLOC_TBL_SIZE 64 7353e2822eSBjorn Andersson 7453e2822eSBjorn Andersson /* 7553e2822eSBjorn Andersson * This lists the various smem heap items relevant for the allocation table and 7653e2822eSBjorn Andersson * smd channel entries. 7753e2822eSBjorn Andersson */ 7853e2822eSBjorn Andersson static const struct { 7953e2822eSBjorn Andersson unsigned alloc_tbl_id; 8053e2822eSBjorn Andersson unsigned info_base_id; 8153e2822eSBjorn Andersson unsigned fifo_base_id; 8253e2822eSBjorn Andersson } smem_items[SMD_ALLOC_TBL_COUNT] = { 8353e2822eSBjorn Andersson { 8453e2822eSBjorn Andersson .alloc_tbl_id = 13, 8553e2822eSBjorn Andersson .info_base_id = 14, 8653e2822eSBjorn Andersson .fifo_base_id = 338 8753e2822eSBjorn Andersson }, 8853e2822eSBjorn Andersson { 8953e2822eSBjorn Andersson .alloc_tbl_id = 266, 9053e2822eSBjorn Andersson .info_base_id = 138, 9153e2822eSBjorn Andersson .fifo_base_id = 202, 9253e2822eSBjorn Andersson }, 9353e2822eSBjorn Andersson }; 9453e2822eSBjorn Andersson 9553e2822eSBjorn Andersson /** 9653e2822eSBjorn Andersson * struct qcom_smd_edge - representing a remote processor 9782eca590SSrinivas Kandagatla * @dev: device associated with this edge 9882eca590SSrinivas Kandagatla * @name: name of this edge 9953e2822eSBjorn Andersson * @of_node: of_node handle for information related to this edge 10053e2822eSBjorn Andersson * @edge_id: identifier of this edge 10153e2822eSBjorn Andersson * @remote_pid: identifier of remote processor 10253e2822eSBjorn Andersson * @irq: interrupt for signals on this edge 10353e2822eSBjorn Andersson * @ipc_regmap: regmap handle holding the outgoing ipc register 10453e2822eSBjorn Andersson * @ipc_offset: offset within @ipc_regmap of the register for ipc 10553e2822eSBjorn Andersson * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap 106ab460a2eSBjorn Andersson * @mbox_client: mailbox client handle 107ab460a2eSBjorn Andersson * @mbox_chan: apcs ipc mailbox channel handle 10853e2822eSBjorn Andersson * @channels: list of all channels detected on this edge 10953e2822eSBjorn Andersson * @channels_lock: guard for modifications of @channels 11053e2822eSBjorn Andersson * @allocated: array of bitmaps representing already allocated channels 11153e2822eSBjorn Andersson * @smem_available: last available amount of smem triggering a channel scan 11282eca590SSrinivas Kandagatla * @new_channel_event: wait queue for new channel events 11353e2822eSBjorn Andersson * @scan_work: work item for discovering new channels 11453e2822eSBjorn Andersson * @state_work: work item for edge state changes 11553e2822eSBjorn Andersson */ 11653e2822eSBjorn Andersson struct qcom_smd_edge { 11753e2822eSBjorn Andersson struct device dev; 11853e2822eSBjorn Andersson 1195e53c42cSBjorn Andersson const char *name; 1205e53c42cSBjorn Andersson 12153e2822eSBjorn Andersson struct device_node *of_node; 12253e2822eSBjorn Andersson unsigned edge_id; 12353e2822eSBjorn Andersson unsigned remote_pid; 12453e2822eSBjorn Andersson 12553e2822eSBjorn Andersson int irq; 12653e2822eSBjorn Andersson 12753e2822eSBjorn Andersson struct regmap *ipc_regmap; 12853e2822eSBjorn Andersson int ipc_offset; 12953e2822eSBjorn Andersson int ipc_bit; 13053e2822eSBjorn Andersson 131ab460a2eSBjorn Andersson struct mbox_client mbox_client; 132ab460a2eSBjorn Andersson struct mbox_chan *mbox_chan; 133ab460a2eSBjorn Andersson 13453e2822eSBjorn Andersson struct list_head channels; 13553e2822eSBjorn Andersson spinlock_t channels_lock; 13653e2822eSBjorn Andersson 13753e2822eSBjorn Andersson DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE); 13853e2822eSBjorn Andersson 13953e2822eSBjorn Andersson unsigned smem_available; 14053e2822eSBjorn Andersson 14153e2822eSBjorn Andersson wait_queue_head_t new_channel_event; 14253e2822eSBjorn Andersson 14353e2822eSBjorn Andersson struct work_struct scan_work; 14453e2822eSBjorn Andersson struct work_struct state_work; 14553e2822eSBjorn Andersson }; 14653e2822eSBjorn Andersson 14753e2822eSBjorn Andersson /* 14853e2822eSBjorn Andersson * SMD channel states. 14953e2822eSBjorn Andersson */ 15053e2822eSBjorn Andersson enum smd_channel_state { 15153e2822eSBjorn Andersson SMD_CHANNEL_CLOSED, 15253e2822eSBjorn Andersson SMD_CHANNEL_OPENING, 15353e2822eSBjorn Andersson SMD_CHANNEL_OPENED, 15453e2822eSBjorn Andersson SMD_CHANNEL_FLUSHING, 15553e2822eSBjorn Andersson SMD_CHANNEL_CLOSING, 15653e2822eSBjorn Andersson SMD_CHANNEL_RESET, 15753e2822eSBjorn Andersson SMD_CHANNEL_RESET_OPENING 15853e2822eSBjorn Andersson }; 15953e2822eSBjorn Andersson 16053e2822eSBjorn Andersson struct qcom_smd_device { 16153e2822eSBjorn Andersson struct rpmsg_device rpdev; 16253e2822eSBjorn Andersson 16353e2822eSBjorn Andersson struct qcom_smd_edge *edge; 16453e2822eSBjorn Andersson }; 16553e2822eSBjorn Andersson 16653e2822eSBjorn Andersson struct qcom_smd_endpoint { 16753e2822eSBjorn Andersson struct rpmsg_endpoint ept; 16853e2822eSBjorn Andersson 16953e2822eSBjorn Andersson struct qcom_smd_channel *qsch; 17053e2822eSBjorn Andersson }; 17153e2822eSBjorn Andersson 1726ddf12d3SBjorn Andersson #define to_smd_device(r) container_of(r, struct qcom_smd_device, rpdev) 17353e2822eSBjorn Andersson #define to_smd_edge(d) container_of(d, struct qcom_smd_edge, dev) 1746ddf12d3SBjorn Andersson #define to_smd_endpoint(e) container_of(e, struct qcom_smd_endpoint, ept) 17553e2822eSBjorn Andersson 17653e2822eSBjorn Andersson /** 17753e2822eSBjorn Andersson * struct qcom_smd_channel - smd channel struct 17853e2822eSBjorn Andersson * @edge: qcom_smd_edge this channel is living on 17982eca590SSrinivas Kandagatla * @qsept: reference to a associated smd endpoint 18082eca590SSrinivas Kandagatla * @registered: flag to indicate if the channel is registered 18153e2822eSBjorn Andersson * @name: name of the channel 18253e2822eSBjorn Andersson * @state: local state of the channel 18353e2822eSBjorn Andersson * @remote_state: remote state of the channel 18482eca590SSrinivas Kandagatla * @state_change_event: state change event 18553e2822eSBjorn Andersson * @info: byte aligned outgoing/incoming channel info 18653e2822eSBjorn Andersson * @info_word: word aligned outgoing/incoming channel info 18753e2822eSBjorn Andersson * @tx_lock: lock to make writes to the channel mutually exclusive 18853e2822eSBjorn Andersson * @fblockread_event: wakeup event tied to tx fBLOCKREADINTR 18953e2822eSBjorn Andersson * @tx_fifo: pointer to the outgoing ring buffer 19053e2822eSBjorn Andersson * @rx_fifo: pointer to the incoming ring buffer 19153e2822eSBjorn Andersson * @fifo_size: size of each ring buffer 19253e2822eSBjorn Andersson * @bounce_buffer: bounce buffer for reading wrapped packets 19353e2822eSBjorn Andersson * @cb: callback function registered for this channel 19453e2822eSBjorn Andersson * @recv_lock: guard for rx info modifications and cb pointer 19553e2822eSBjorn Andersson * @pkt_size: size of the currently handled packet 19682eca590SSrinivas Kandagatla * @drvdata: driver private data 19753e2822eSBjorn Andersson * @list: lite entry for @channels in qcom_smd_edge 19853e2822eSBjorn Andersson */ 19953e2822eSBjorn Andersson struct qcom_smd_channel { 20053e2822eSBjorn Andersson struct qcom_smd_edge *edge; 20153e2822eSBjorn Andersson 20253e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept; 20353e2822eSBjorn Andersson bool registered; 20453e2822eSBjorn Andersson 20553e2822eSBjorn Andersson char *name; 20653e2822eSBjorn Andersson enum smd_channel_state state; 20753e2822eSBjorn Andersson enum smd_channel_state remote_state; 208268105fbSBjorn Andersson wait_queue_head_t state_change_event; 20953e2822eSBjorn Andersson 21053e2822eSBjorn Andersson struct smd_channel_info_pair *info; 21153e2822eSBjorn Andersson struct smd_channel_info_word_pair *info_word; 21253e2822eSBjorn Andersson 21333e3820dSBjorn Andersson spinlock_t tx_lock; 21453e2822eSBjorn Andersson wait_queue_head_t fblockread_event; 21553e2822eSBjorn Andersson 21653e2822eSBjorn Andersson void *tx_fifo; 21753e2822eSBjorn Andersson void *rx_fifo; 21853e2822eSBjorn Andersson int fifo_size; 21953e2822eSBjorn Andersson 22053e2822eSBjorn Andersson void *bounce_buffer; 22153e2822eSBjorn Andersson 22253e2822eSBjorn Andersson spinlock_t recv_lock; 22353e2822eSBjorn Andersson 22453e2822eSBjorn Andersson int pkt_size; 22553e2822eSBjorn Andersson 22653e2822eSBjorn Andersson void *drvdata; 22753e2822eSBjorn Andersson 22853e2822eSBjorn Andersson struct list_head list; 22953e2822eSBjorn Andersson }; 23053e2822eSBjorn Andersson 23153e2822eSBjorn Andersson /* 23253e2822eSBjorn Andersson * Format of the smd_info smem items, for byte aligned channels. 23353e2822eSBjorn Andersson */ 23453e2822eSBjorn Andersson struct smd_channel_info { 23553e2822eSBjorn Andersson __le32 state; 23653e2822eSBjorn Andersson u8 fDSR; 23753e2822eSBjorn Andersson u8 fCTS; 23853e2822eSBjorn Andersson u8 fCD; 23953e2822eSBjorn Andersson u8 fRI; 24053e2822eSBjorn Andersson u8 fHEAD; 24153e2822eSBjorn Andersson u8 fTAIL; 24253e2822eSBjorn Andersson u8 fSTATE; 24353e2822eSBjorn Andersson u8 fBLOCKREADINTR; 24453e2822eSBjorn Andersson __le32 tail; 24553e2822eSBjorn Andersson __le32 head; 24653e2822eSBjorn Andersson }; 24753e2822eSBjorn Andersson 24853e2822eSBjorn Andersson struct smd_channel_info_pair { 24953e2822eSBjorn Andersson struct smd_channel_info tx; 25053e2822eSBjorn Andersson struct smd_channel_info rx; 25153e2822eSBjorn Andersson }; 25253e2822eSBjorn Andersson 25353e2822eSBjorn Andersson /* 25453e2822eSBjorn Andersson * Format of the smd_info smem items, for word aligned channels. 25553e2822eSBjorn Andersson */ 25653e2822eSBjorn Andersson struct smd_channel_info_word { 25753e2822eSBjorn Andersson __le32 state; 25853e2822eSBjorn Andersson __le32 fDSR; 25953e2822eSBjorn Andersson __le32 fCTS; 26053e2822eSBjorn Andersson __le32 fCD; 26153e2822eSBjorn Andersson __le32 fRI; 26253e2822eSBjorn Andersson __le32 fHEAD; 26353e2822eSBjorn Andersson __le32 fTAIL; 26453e2822eSBjorn Andersson __le32 fSTATE; 26553e2822eSBjorn Andersson __le32 fBLOCKREADINTR; 26653e2822eSBjorn Andersson __le32 tail; 26753e2822eSBjorn Andersson __le32 head; 26853e2822eSBjorn Andersson }; 26953e2822eSBjorn Andersson 27053e2822eSBjorn Andersson struct smd_channel_info_word_pair { 27153e2822eSBjorn Andersson struct smd_channel_info_word tx; 27253e2822eSBjorn Andersson struct smd_channel_info_word rx; 27353e2822eSBjorn Andersson }; 27453e2822eSBjorn Andersson 27553e2822eSBjorn Andersson #define GET_RX_CHANNEL_FLAG(channel, param) \ 27653e2822eSBjorn Andersson ({ \ 27753e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 27853e2822eSBjorn Andersson channel->info_word ? \ 27953e2822eSBjorn Andersson le32_to_cpu(channel->info_word->rx.param) : \ 28053e2822eSBjorn Andersson channel->info->rx.param; \ 28153e2822eSBjorn Andersson }) 28253e2822eSBjorn Andersson 28353e2822eSBjorn Andersson #define GET_RX_CHANNEL_INFO(channel, param) \ 28453e2822eSBjorn Andersson ({ \ 28553e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 28653e2822eSBjorn Andersson le32_to_cpu(channel->info_word ? \ 28753e2822eSBjorn Andersson channel->info_word->rx.param : \ 28853e2822eSBjorn Andersson channel->info->rx.param); \ 28953e2822eSBjorn Andersson }) 29053e2822eSBjorn Andersson 29153e2822eSBjorn Andersson #define SET_RX_CHANNEL_FLAG(channel, param, value) \ 29253e2822eSBjorn Andersson ({ \ 29353e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 29453e2822eSBjorn Andersson if (channel->info_word) \ 29553e2822eSBjorn Andersson channel->info_word->rx.param = cpu_to_le32(value); \ 29653e2822eSBjorn Andersson else \ 29753e2822eSBjorn Andersson channel->info->rx.param = value; \ 29853e2822eSBjorn Andersson }) 29953e2822eSBjorn Andersson 30053e2822eSBjorn Andersson #define SET_RX_CHANNEL_INFO(channel, param, value) \ 30153e2822eSBjorn Andersson ({ \ 30253e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 30353e2822eSBjorn Andersson if (channel->info_word) \ 30453e2822eSBjorn Andersson channel->info_word->rx.param = cpu_to_le32(value); \ 30553e2822eSBjorn Andersson else \ 30653e2822eSBjorn Andersson channel->info->rx.param = cpu_to_le32(value); \ 30753e2822eSBjorn Andersson }) 30853e2822eSBjorn Andersson 30953e2822eSBjorn Andersson #define GET_TX_CHANNEL_FLAG(channel, param) \ 31053e2822eSBjorn Andersson ({ \ 31153e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 31253e2822eSBjorn Andersson channel->info_word ? \ 31353e2822eSBjorn Andersson le32_to_cpu(channel->info_word->tx.param) : \ 31453e2822eSBjorn Andersson channel->info->tx.param; \ 31553e2822eSBjorn Andersson }) 31653e2822eSBjorn Andersson 31753e2822eSBjorn Andersson #define GET_TX_CHANNEL_INFO(channel, param) \ 31853e2822eSBjorn Andersson ({ \ 31953e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 32053e2822eSBjorn Andersson le32_to_cpu(channel->info_word ? \ 32153e2822eSBjorn Andersson channel->info_word->tx.param : \ 32253e2822eSBjorn Andersson channel->info->tx.param); \ 32353e2822eSBjorn Andersson }) 32453e2822eSBjorn Andersson 32553e2822eSBjorn Andersson #define SET_TX_CHANNEL_FLAG(channel, param, value) \ 32653e2822eSBjorn Andersson ({ \ 32753e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 32853e2822eSBjorn Andersson if (channel->info_word) \ 32953e2822eSBjorn Andersson channel->info_word->tx.param = cpu_to_le32(value); \ 33053e2822eSBjorn Andersson else \ 33153e2822eSBjorn Andersson channel->info->tx.param = value; \ 33253e2822eSBjorn Andersson }) 33353e2822eSBjorn Andersson 33453e2822eSBjorn Andersson #define SET_TX_CHANNEL_INFO(channel, param, value) \ 33553e2822eSBjorn Andersson ({ \ 33653e2822eSBjorn Andersson BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 33753e2822eSBjorn Andersson if (channel->info_word) \ 33853e2822eSBjorn Andersson channel->info_word->tx.param = cpu_to_le32(value); \ 33953e2822eSBjorn Andersson else \ 34053e2822eSBjorn Andersson channel->info->tx.param = cpu_to_le32(value); \ 34153e2822eSBjorn Andersson }) 34253e2822eSBjorn Andersson 34353e2822eSBjorn Andersson /** 34453e2822eSBjorn Andersson * struct qcom_smd_alloc_entry - channel allocation entry 34553e2822eSBjorn Andersson * @name: channel name 34653e2822eSBjorn Andersson * @cid: channel index 34753e2822eSBjorn Andersson * @flags: channel flags and edge id 34853e2822eSBjorn Andersson * @ref_count: reference count of the channel 34953e2822eSBjorn Andersson */ 35053e2822eSBjorn Andersson struct qcom_smd_alloc_entry { 35153e2822eSBjorn Andersson u8 name[20]; 35253e2822eSBjorn Andersson __le32 cid; 35353e2822eSBjorn Andersson __le32 flags; 35453e2822eSBjorn Andersson __le32 ref_count; 35553e2822eSBjorn Andersson } __packed; 35653e2822eSBjorn Andersson 35753e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_EDGE_MASK 0xff 35853e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_STREAM BIT(8) 35953e2822eSBjorn Andersson #define SMD_CHANNEL_FLAGS_PACKET BIT(9) 36053e2822eSBjorn Andersson 36153e2822eSBjorn Andersson /* 36253e2822eSBjorn Andersson * Each smd packet contains a 20 byte header, with the first 4 being the length 36353e2822eSBjorn Andersson * of the packet. 36453e2822eSBjorn Andersson */ 36553e2822eSBjorn Andersson #define SMD_PACKET_HEADER_LEN 20 36653e2822eSBjorn Andersson 36753e2822eSBjorn Andersson /* 36853e2822eSBjorn Andersson * Signal the remote processor associated with 'channel'. 36953e2822eSBjorn Andersson */ 37053e2822eSBjorn Andersson static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) 37153e2822eSBjorn Andersson { 37253e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 37353e2822eSBjorn Andersson 374ab460a2eSBjorn Andersson if (edge->mbox_chan) { 375ab460a2eSBjorn Andersson /* 376ab460a2eSBjorn Andersson * We can ignore a failing mbox_send_message() as the only 377ab460a2eSBjorn Andersson * possible cause is that the FIFO in the framework is full of 378ab460a2eSBjorn Andersson * other writes to the same bit. 379ab460a2eSBjorn Andersson */ 380ab460a2eSBjorn Andersson mbox_send_message(edge->mbox_chan, NULL); 381ab460a2eSBjorn Andersson mbox_client_txdone(edge->mbox_chan, 0); 382ab460a2eSBjorn Andersson } else { 38353e2822eSBjorn Andersson regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); 38453e2822eSBjorn Andersson } 385ab460a2eSBjorn Andersson } 38653e2822eSBjorn Andersson 38753e2822eSBjorn Andersson /* 38853e2822eSBjorn Andersson * Initialize the tx channel info 38953e2822eSBjorn Andersson */ 39053e2822eSBjorn Andersson static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) 39153e2822eSBjorn Andersson { 39253e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); 39353e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fDSR, 0); 39453e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCTS, 0); 39553e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCD, 0); 39653e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fRI, 0); 39753e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fHEAD, 0); 39853e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 39953e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 40053e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 40153e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, head, 0); 40253e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, tail, 0); 40353e2822eSBjorn Andersson 40453e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 40553e2822eSBjorn Andersson 40653e2822eSBjorn Andersson channel->state = SMD_CHANNEL_CLOSED; 40753e2822eSBjorn Andersson channel->pkt_size = 0; 40853e2822eSBjorn Andersson } 40953e2822eSBjorn Andersson 41053e2822eSBjorn Andersson /* 41153e2822eSBjorn Andersson * Set the callback for a channel, with appropriate locking 41253e2822eSBjorn Andersson */ 41353e2822eSBjorn Andersson static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel, 41453e2822eSBjorn Andersson rpmsg_rx_cb_t cb) 41553e2822eSBjorn Andersson { 41653e2822eSBjorn Andersson struct rpmsg_endpoint *ept = &channel->qsept->ept; 41753e2822eSBjorn Andersson unsigned long flags; 41853e2822eSBjorn Andersson 41953e2822eSBjorn Andersson spin_lock_irqsave(&channel->recv_lock, flags); 42053e2822eSBjorn Andersson ept->cb = cb; 42153e2822eSBjorn Andersson spin_unlock_irqrestore(&channel->recv_lock, flags); 42253e2822eSBjorn Andersson }; 42353e2822eSBjorn Andersson 42453e2822eSBjorn Andersson /* 42553e2822eSBjorn Andersson * Calculate the amount of data available in the rx fifo 42653e2822eSBjorn Andersson */ 42753e2822eSBjorn Andersson static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) 42853e2822eSBjorn Andersson { 42953e2822eSBjorn Andersson unsigned head; 43053e2822eSBjorn Andersson unsigned tail; 43153e2822eSBjorn Andersson 43253e2822eSBjorn Andersson head = GET_RX_CHANNEL_INFO(channel, head); 43353e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 43453e2822eSBjorn Andersson 43553e2822eSBjorn Andersson return (head - tail) & (channel->fifo_size - 1); 43653e2822eSBjorn Andersson } 43753e2822eSBjorn Andersson 43853e2822eSBjorn Andersson /* 43953e2822eSBjorn Andersson * Set tx channel state and inform the remote processor 44053e2822eSBjorn Andersson */ 44153e2822eSBjorn Andersson static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, 44253e2822eSBjorn Andersson int state) 44353e2822eSBjorn Andersson { 44453e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 44553e2822eSBjorn Andersson bool is_open = state == SMD_CHANNEL_OPENED; 44653e2822eSBjorn Andersson 44753e2822eSBjorn Andersson if (channel->state == state) 44853e2822eSBjorn Andersson return; 44953e2822eSBjorn Andersson 45053e2822eSBjorn Andersson dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state); 45153e2822eSBjorn Andersson 45253e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fDSR, is_open); 45353e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCTS, is_open); 45453e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fCD, is_open); 45553e2822eSBjorn Andersson 45653e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, state, state); 45753e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 45853e2822eSBjorn Andersson 45953e2822eSBjorn Andersson channel->state = state; 46053e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 46153e2822eSBjorn Andersson } 46253e2822eSBjorn Andersson 46353e2822eSBjorn Andersson /* 46453e2822eSBjorn Andersson * Copy count bytes of data using 32bit accesses, if that's required. 46553e2822eSBjorn Andersson */ 46653e2822eSBjorn Andersson static void smd_copy_to_fifo(void __iomem *dst, 46753e2822eSBjorn Andersson const void *src, 46853e2822eSBjorn Andersson size_t count, 46953e2822eSBjorn Andersson bool word_aligned) 47053e2822eSBjorn Andersson { 47153e2822eSBjorn Andersson if (word_aligned) { 47253e2822eSBjorn Andersson __iowrite32_copy(dst, src, count / sizeof(u32)); 47353e2822eSBjorn Andersson } else { 47453e2822eSBjorn Andersson memcpy_toio(dst, src, count); 47553e2822eSBjorn Andersson } 47653e2822eSBjorn Andersson } 47753e2822eSBjorn Andersson 47853e2822eSBjorn Andersson /* 47953e2822eSBjorn Andersson * Copy count bytes of data using 32bit accesses, if that is required. 48053e2822eSBjorn Andersson */ 48153e2822eSBjorn Andersson static void smd_copy_from_fifo(void *dst, 48253e2822eSBjorn Andersson const void __iomem *src, 48353e2822eSBjorn Andersson size_t count, 48453e2822eSBjorn Andersson bool word_aligned) 48553e2822eSBjorn Andersson { 48653e2822eSBjorn Andersson if (word_aligned) { 48753e2822eSBjorn Andersson __ioread32_copy(dst, src, count / sizeof(u32)); 48853e2822eSBjorn Andersson } else { 48953e2822eSBjorn Andersson memcpy_fromio(dst, src, count); 49053e2822eSBjorn Andersson } 49153e2822eSBjorn Andersson } 49253e2822eSBjorn Andersson 49353e2822eSBjorn Andersson /* 49453e2822eSBjorn Andersson * Read count bytes of data from the rx fifo into buf, but don't advance the 49553e2822eSBjorn Andersson * tail. 49653e2822eSBjorn Andersson */ 49753e2822eSBjorn Andersson static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel, 49853e2822eSBjorn Andersson void *buf, size_t count) 49953e2822eSBjorn Andersson { 50053e2822eSBjorn Andersson bool word_aligned; 50153e2822eSBjorn Andersson unsigned tail; 50253e2822eSBjorn Andersson size_t len; 50353e2822eSBjorn Andersson 50453e2822eSBjorn Andersson word_aligned = channel->info_word; 50553e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 50653e2822eSBjorn Andersson 50753e2822eSBjorn Andersson len = min_t(size_t, count, channel->fifo_size - tail); 50853e2822eSBjorn Andersson if (len) { 50953e2822eSBjorn Andersson smd_copy_from_fifo(buf, 51053e2822eSBjorn Andersson channel->rx_fifo + tail, 51153e2822eSBjorn Andersson len, 51253e2822eSBjorn Andersson word_aligned); 51353e2822eSBjorn Andersson } 51453e2822eSBjorn Andersson 51553e2822eSBjorn Andersson if (len != count) { 51653e2822eSBjorn Andersson smd_copy_from_fifo(buf + len, 51753e2822eSBjorn Andersson channel->rx_fifo, 51853e2822eSBjorn Andersson count - len, 51953e2822eSBjorn Andersson word_aligned); 52053e2822eSBjorn Andersson } 52153e2822eSBjorn Andersson 52253e2822eSBjorn Andersson return count; 52353e2822eSBjorn Andersson } 52453e2822eSBjorn Andersson 52553e2822eSBjorn Andersson /* 52653e2822eSBjorn Andersson * Advance the rx tail by count bytes. 52753e2822eSBjorn Andersson */ 52853e2822eSBjorn Andersson static void qcom_smd_channel_advance(struct qcom_smd_channel *channel, 52953e2822eSBjorn Andersson size_t count) 53053e2822eSBjorn Andersson { 53153e2822eSBjorn Andersson unsigned tail; 53253e2822eSBjorn Andersson 53353e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 53453e2822eSBjorn Andersson tail += count; 53553e2822eSBjorn Andersson tail &= (channel->fifo_size - 1); 53653e2822eSBjorn Andersson SET_RX_CHANNEL_INFO(channel, tail, tail); 53753e2822eSBjorn Andersson } 53853e2822eSBjorn Andersson 53953e2822eSBjorn Andersson /* 54053e2822eSBjorn Andersson * Read out a single packet from the rx fifo and deliver it to the device 54153e2822eSBjorn Andersson */ 54253e2822eSBjorn Andersson static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) 54353e2822eSBjorn Andersson { 54453e2822eSBjorn Andersson struct rpmsg_endpoint *ept = &channel->qsept->ept; 54553e2822eSBjorn Andersson unsigned tail; 54653e2822eSBjorn Andersson size_t len; 54753e2822eSBjorn Andersson void *ptr; 54853e2822eSBjorn Andersson int ret; 54953e2822eSBjorn Andersson 55053e2822eSBjorn Andersson tail = GET_RX_CHANNEL_INFO(channel, tail); 55153e2822eSBjorn Andersson 55253e2822eSBjorn Andersson /* Use bounce buffer if the data wraps */ 55353e2822eSBjorn Andersson if (tail + channel->pkt_size >= channel->fifo_size) { 55453e2822eSBjorn Andersson ptr = channel->bounce_buffer; 55553e2822eSBjorn Andersson len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size); 55653e2822eSBjorn Andersson } else { 55753e2822eSBjorn Andersson ptr = channel->rx_fifo + tail; 55853e2822eSBjorn Andersson len = channel->pkt_size; 55953e2822eSBjorn Andersson } 56053e2822eSBjorn Andersson 56153e2822eSBjorn Andersson ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY); 56253e2822eSBjorn Andersson if (ret < 0) 56353e2822eSBjorn Andersson return ret; 56453e2822eSBjorn Andersson 56553e2822eSBjorn Andersson /* Only forward the tail if the client consumed the data */ 56653e2822eSBjorn Andersson qcom_smd_channel_advance(channel, len); 56753e2822eSBjorn Andersson 56853e2822eSBjorn Andersson channel->pkt_size = 0; 56953e2822eSBjorn Andersson 57053e2822eSBjorn Andersson return 0; 57153e2822eSBjorn Andersson } 57253e2822eSBjorn Andersson 57353e2822eSBjorn Andersson /* 57453e2822eSBjorn Andersson * Per channel interrupt handling 57553e2822eSBjorn Andersson */ 57653e2822eSBjorn Andersson static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) 57753e2822eSBjorn Andersson { 57853e2822eSBjorn Andersson bool need_state_scan = false; 57953e2822eSBjorn Andersson int remote_state; 58053e2822eSBjorn Andersson __le32 pktlen; 58153e2822eSBjorn Andersson int avail; 58253e2822eSBjorn Andersson int ret; 58353e2822eSBjorn Andersson 58453e2822eSBjorn Andersson /* Handle state changes */ 58553e2822eSBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 58653e2822eSBjorn Andersson if (remote_state != channel->remote_state) { 58753e2822eSBjorn Andersson channel->remote_state = remote_state; 58853e2822eSBjorn Andersson need_state_scan = true; 589268105fbSBjorn Andersson 590268105fbSBjorn Andersson wake_up_interruptible_all(&channel->state_change_event); 59153e2822eSBjorn Andersson } 59253e2822eSBjorn Andersson /* Indicate that we have seen any state change */ 59353e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fSTATE, 0); 59453e2822eSBjorn Andersson 59553e2822eSBjorn Andersson /* Signal waiting qcom_smd_send() about the interrupt */ 59653e2822eSBjorn Andersson if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) 597eb114f27SBjorn Andersson wake_up_interruptible_all(&channel->fblockread_event); 59853e2822eSBjorn Andersson 59953e2822eSBjorn Andersson /* Don't consume any data until we've opened the channel */ 60053e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_OPENED) 60153e2822eSBjorn Andersson goto out; 60253e2822eSBjorn Andersson 60353e2822eSBjorn Andersson /* Indicate that we've seen the new data */ 60453e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fHEAD, 0); 60553e2822eSBjorn Andersson 60653e2822eSBjorn Andersson /* Consume data */ 60753e2822eSBjorn Andersson for (;;) { 60853e2822eSBjorn Andersson avail = qcom_smd_channel_get_rx_avail(channel); 60953e2822eSBjorn Andersson 61053e2822eSBjorn Andersson if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) { 61153e2822eSBjorn Andersson qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); 61253e2822eSBjorn Andersson qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); 61353e2822eSBjorn Andersson channel->pkt_size = le32_to_cpu(pktlen); 61453e2822eSBjorn Andersson } else if (channel->pkt_size && avail >= channel->pkt_size) { 61553e2822eSBjorn Andersson ret = qcom_smd_channel_recv_single(channel); 61653e2822eSBjorn Andersson if (ret) 61753e2822eSBjorn Andersson break; 61853e2822eSBjorn Andersson } else { 61953e2822eSBjorn Andersson break; 62053e2822eSBjorn Andersson } 62153e2822eSBjorn Andersson } 62253e2822eSBjorn Andersson 62353e2822eSBjorn Andersson /* Indicate that we have seen and updated tail */ 62453e2822eSBjorn Andersson SET_RX_CHANNEL_FLAG(channel, fTAIL, 1); 62553e2822eSBjorn Andersson 62653e2822eSBjorn Andersson /* Signal the remote that we've consumed the data (if requested) */ 62753e2822eSBjorn Andersson if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) { 62853e2822eSBjorn Andersson /* Ensure ordering of channel info updates */ 62953e2822eSBjorn Andersson wmb(); 63053e2822eSBjorn Andersson 63153e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 63253e2822eSBjorn Andersson } 63353e2822eSBjorn Andersson 63453e2822eSBjorn Andersson out: 63553e2822eSBjorn Andersson return need_state_scan; 63653e2822eSBjorn Andersson } 63753e2822eSBjorn Andersson 63853e2822eSBjorn Andersson /* 63953e2822eSBjorn Andersson * The edge interrupts are triggered by the remote processor on state changes, 64053e2822eSBjorn Andersson * channel info updates or when new channels are created. 64153e2822eSBjorn Andersson */ 64253e2822eSBjorn Andersson static irqreturn_t qcom_smd_edge_intr(int irq, void *data) 64353e2822eSBjorn Andersson { 64453e2822eSBjorn Andersson struct qcom_smd_edge *edge = data; 64553e2822eSBjorn Andersson struct qcom_smd_channel *channel; 64653e2822eSBjorn Andersson unsigned available; 64753e2822eSBjorn Andersson bool kick_scanner = false; 64853e2822eSBjorn Andersson bool kick_state = false; 64953e2822eSBjorn Andersson 65053e2822eSBjorn Andersson /* 65153e2822eSBjorn Andersson * Handle state changes or data on each of the channels on this edge 65253e2822eSBjorn Andersson */ 65353e2822eSBjorn Andersson spin_lock(&edge->channels_lock); 65453e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 65553e2822eSBjorn Andersson spin_lock(&channel->recv_lock); 65653e2822eSBjorn Andersson kick_state |= qcom_smd_channel_intr(channel); 65753e2822eSBjorn Andersson spin_unlock(&channel->recv_lock); 65853e2822eSBjorn Andersson } 65953e2822eSBjorn Andersson spin_unlock(&edge->channels_lock); 66053e2822eSBjorn Andersson 66153e2822eSBjorn Andersson /* 66253e2822eSBjorn Andersson * Creating a new channel requires allocating an smem entry, so we only 66353e2822eSBjorn Andersson * have to scan if the amount of available space in smem have changed 66453e2822eSBjorn Andersson * since last scan. 66553e2822eSBjorn Andersson */ 66653e2822eSBjorn Andersson available = qcom_smem_get_free_space(edge->remote_pid); 66753e2822eSBjorn Andersson if (available != edge->smem_available) { 66853e2822eSBjorn Andersson edge->smem_available = available; 66953e2822eSBjorn Andersson kick_scanner = true; 67053e2822eSBjorn Andersson } 67153e2822eSBjorn Andersson 67253e2822eSBjorn Andersson if (kick_scanner) 67353e2822eSBjorn Andersson schedule_work(&edge->scan_work); 67453e2822eSBjorn Andersson if (kick_state) 67553e2822eSBjorn Andersson schedule_work(&edge->state_work); 67653e2822eSBjorn Andersson 67753e2822eSBjorn Andersson return IRQ_HANDLED; 67853e2822eSBjorn Andersson } 67953e2822eSBjorn Andersson 68053e2822eSBjorn Andersson /* 68153e2822eSBjorn Andersson * Calculate how much space is available in the tx fifo. 68253e2822eSBjorn Andersson */ 68353e2822eSBjorn Andersson static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel) 68453e2822eSBjorn Andersson { 68553e2822eSBjorn Andersson unsigned head; 68653e2822eSBjorn Andersson unsigned tail; 68753e2822eSBjorn Andersson unsigned mask = channel->fifo_size - 1; 68853e2822eSBjorn Andersson 68953e2822eSBjorn Andersson head = GET_TX_CHANNEL_INFO(channel, head); 69053e2822eSBjorn Andersson tail = GET_TX_CHANNEL_INFO(channel, tail); 69153e2822eSBjorn Andersson 69253e2822eSBjorn Andersson return mask - ((head - tail) & mask); 69353e2822eSBjorn Andersson } 69453e2822eSBjorn Andersson 69553e2822eSBjorn Andersson /* 69653e2822eSBjorn Andersson * Write count bytes of data into channel, possibly wrapping in the ring buffer 69753e2822eSBjorn Andersson */ 69853e2822eSBjorn Andersson static int qcom_smd_write_fifo(struct qcom_smd_channel *channel, 69953e2822eSBjorn Andersson const void *data, 70053e2822eSBjorn Andersson size_t count) 70153e2822eSBjorn Andersson { 70253e2822eSBjorn Andersson bool word_aligned; 70353e2822eSBjorn Andersson unsigned head; 70453e2822eSBjorn Andersson size_t len; 70553e2822eSBjorn Andersson 70653e2822eSBjorn Andersson word_aligned = channel->info_word; 70753e2822eSBjorn Andersson head = GET_TX_CHANNEL_INFO(channel, head); 70853e2822eSBjorn Andersson 70953e2822eSBjorn Andersson len = min_t(size_t, count, channel->fifo_size - head); 71053e2822eSBjorn Andersson if (len) { 71153e2822eSBjorn Andersson smd_copy_to_fifo(channel->tx_fifo + head, 71253e2822eSBjorn Andersson data, 71353e2822eSBjorn Andersson len, 71453e2822eSBjorn Andersson word_aligned); 71553e2822eSBjorn Andersson } 71653e2822eSBjorn Andersson 71753e2822eSBjorn Andersson if (len != count) { 71853e2822eSBjorn Andersson smd_copy_to_fifo(channel->tx_fifo, 71953e2822eSBjorn Andersson data + len, 72053e2822eSBjorn Andersson count - len, 72153e2822eSBjorn Andersson word_aligned); 72253e2822eSBjorn Andersson } 72353e2822eSBjorn Andersson 72453e2822eSBjorn Andersson head += count; 72553e2822eSBjorn Andersson head &= (channel->fifo_size - 1); 72653e2822eSBjorn Andersson SET_TX_CHANNEL_INFO(channel, head, head); 72753e2822eSBjorn Andersson 72853e2822eSBjorn Andersson return count; 72953e2822eSBjorn Andersson } 73053e2822eSBjorn Andersson 73153e2822eSBjorn Andersson /** 73253e2822eSBjorn Andersson * qcom_smd_send - write data to smd channel 73353e2822eSBjorn Andersson * @channel: channel handle 73453e2822eSBjorn Andersson * @data: buffer of data to write 73553e2822eSBjorn Andersson * @len: number of bytes to write 73682eca590SSrinivas Kandagatla * @wait: flag to indicate if write has ca wait 73753e2822eSBjorn Andersson * 73853e2822eSBjorn Andersson * This is a blocking write of len bytes into the channel's tx ring buffer and 73953e2822eSBjorn Andersson * signal the remote end. It will sleep until there is enough space available 74053e2822eSBjorn Andersson * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid 74153e2822eSBjorn Andersson * polling. 74253e2822eSBjorn Andersson */ 74353e2822eSBjorn Andersson static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data, 74453e2822eSBjorn Andersson int len, bool wait) 74553e2822eSBjorn Andersson { 74653e2822eSBjorn Andersson __le32 hdr[5] = { cpu_to_le32(len), }; 74753e2822eSBjorn Andersson int tlen = sizeof(hdr) + len; 74833e3820dSBjorn Andersson unsigned long flags; 74953e2822eSBjorn Andersson int ret; 75053e2822eSBjorn Andersson 75153e2822eSBjorn Andersson /* Word aligned channels only accept word size aligned data */ 75253e2822eSBjorn Andersson if (channel->info_word && len % 4) 75353e2822eSBjorn Andersson return -EINVAL; 75453e2822eSBjorn Andersson 75553e2822eSBjorn Andersson /* Reject packets that are too big */ 75653e2822eSBjorn Andersson if (tlen >= channel->fifo_size) 75753e2822eSBjorn Andersson return -EINVAL; 75853e2822eSBjorn Andersson 75933e3820dSBjorn Andersson /* Highlight the fact that if we enter the loop below we might sleep */ 76033e3820dSBjorn Andersson if (wait) 76133e3820dSBjorn Andersson might_sleep(); 76233e3820dSBjorn Andersson 76333e3820dSBjorn Andersson spin_lock_irqsave(&channel->tx_lock, flags); 76453e2822eSBjorn Andersson 765b2c932e7SBjorn Andersson while (qcom_smd_get_tx_avail(channel) < tlen && 766b2c932e7SBjorn Andersson channel->state == SMD_CHANNEL_OPENED) { 76753e2822eSBjorn Andersson if (!wait) { 7681d74e7edSBjorn Andersson ret = -EAGAIN; 769c3388a07SDan Carpenter goto out_unlock; 77053e2822eSBjorn Andersson } 77153e2822eSBjorn Andersson 77253e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0); 77353e2822eSBjorn Andersson 774178f3f75SBjorn Andersson /* Wait without holding the tx_lock */ 77533e3820dSBjorn Andersson spin_unlock_irqrestore(&channel->tx_lock, flags); 776178f3f75SBjorn Andersson 77753e2822eSBjorn Andersson ret = wait_event_interruptible(channel->fblockread_event, 77853e2822eSBjorn Andersson qcom_smd_get_tx_avail(channel) >= tlen || 77953e2822eSBjorn Andersson channel->state != SMD_CHANNEL_OPENED); 78053e2822eSBjorn Andersson if (ret) 781c3388a07SDan Carpenter return ret; 78253e2822eSBjorn Andersson 78333e3820dSBjorn Andersson spin_lock_irqsave(&channel->tx_lock, flags); 78453e2822eSBjorn Andersson 78553e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 78653e2822eSBjorn Andersson } 78753e2822eSBjorn Andersson 788b2c932e7SBjorn Andersson /* Fail if the channel was closed */ 789b2c932e7SBjorn Andersson if (channel->state != SMD_CHANNEL_OPENED) { 790b2c932e7SBjorn Andersson ret = -EPIPE; 791c3388a07SDan Carpenter goto out_unlock; 792b2c932e7SBjorn Andersson } 793b2c932e7SBjorn Andersson 79453e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 79553e2822eSBjorn Andersson 79653e2822eSBjorn Andersson qcom_smd_write_fifo(channel, hdr, sizeof(hdr)); 79753e2822eSBjorn Andersson qcom_smd_write_fifo(channel, data, len); 79853e2822eSBjorn Andersson 79953e2822eSBjorn Andersson SET_TX_CHANNEL_FLAG(channel, fHEAD, 1); 80053e2822eSBjorn Andersson 80153e2822eSBjorn Andersson /* Ensure ordering of channel info updates */ 80253e2822eSBjorn Andersson wmb(); 80353e2822eSBjorn Andersson 80453e2822eSBjorn Andersson qcom_smd_signal_channel(channel); 80553e2822eSBjorn Andersson 806c3388a07SDan Carpenter out_unlock: 80733e3820dSBjorn Andersson spin_unlock_irqrestore(&channel->tx_lock, flags); 80853e2822eSBjorn Andersson 80953e2822eSBjorn Andersson return ret; 81053e2822eSBjorn Andersson } 81153e2822eSBjorn Andersson 81253e2822eSBjorn Andersson /* 81353e2822eSBjorn Andersson * Helper for opening a channel 81453e2822eSBjorn Andersson */ 81553e2822eSBjorn Andersson static int qcom_smd_channel_open(struct qcom_smd_channel *channel, 81653e2822eSBjorn Andersson rpmsg_rx_cb_t cb) 81753e2822eSBjorn Andersson { 818268105fbSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 81953e2822eSBjorn Andersson size_t bb_size; 820268105fbSBjorn Andersson int ret; 82153e2822eSBjorn Andersson 82253e2822eSBjorn Andersson /* 82353e2822eSBjorn Andersson * Packets are maximum 4k, but reduce if the fifo is smaller 82453e2822eSBjorn Andersson */ 82553e2822eSBjorn Andersson bb_size = min(channel->fifo_size, SZ_4K); 82653e2822eSBjorn Andersson channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL); 82753e2822eSBjorn Andersson if (!channel->bounce_buffer) 82853e2822eSBjorn Andersson return -ENOMEM; 82953e2822eSBjorn Andersson 83053e2822eSBjorn Andersson qcom_smd_channel_set_callback(channel, cb); 83153e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING); 832268105fbSBjorn Andersson 833268105fbSBjorn Andersson /* Wait for remote to enter opening or opened */ 834268105fbSBjorn Andersson ret = wait_event_interruptible_timeout(channel->state_change_event, 835268105fbSBjorn Andersson channel->remote_state == SMD_CHANNEL_OPENING || 836268105fbSBjorn Andersson channel->remote_state == SMD_CHANNEL_OPENED, 837268105fbSBjorn Andersson HZ); 838268105fbSBjorn Andersson if (!ret) { 839268105fbSBjorn Andersson dev_err(&edge->dev, "remote side did not enter opening state\n"); 840268105fbSBjorn Andersson goto out_close_timeout; 841268105fbSBjorn Andersson } 842268105fbSBjorn Andersson 84353e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED); 84453e2822eSBjorn Andersson 845268105fbSBjorn Andersson /* Wait for remote to enter opened */ 846268105fbSBjorn Andersson ret = wait_event_interruptible_timeout(channel->state_change_event, 847268105fbSBjorn Andersson channel->remote_state == SMD_CHANNEL_OPENED, 848268105fbSBjorn Andersson HZ); 849268105fbSBjorn Andersson if (!ret) { 850268105fbSBjorn Andersson dev_err(&edge->dev, "remote side did not enter open state\n"); 851268105fbSBjorn Andersson goto out_close_timeout; 852268105fbSBjorn Andersson } 853268105fbSBjorn Andersson 85453e2822eSBjorn Andersson return 0; 855268105fbSBjorn Andersson 856268105fbSBjorn Andersson out_close_timeout: 857268105fbSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); 858268105fbSBjorn Andersson return -ETIMEDOUT; 85953e2822eSBjorn Andersson } 86053e2822eSBjorn Andersson 86153e2822eSBjorn Andersson /* 86253e2822eSBjorn Andersson * Helper for closing and resetting a channel 86353e2822eSBjorn Andersson */ 86453e2822eSBjorn Andersson static void qcom_smd_channel_close(struct qcom_smd_channel *channel) 86553e2822eSBjorn Andersson { 86653e2822eSBjorn Andersson qcom_smd_channel_set_callback(channel, NULL); 86753e2822eSBjorn Andersson 86853e2822eSBjorn Andersson kfree(channel->bounce_buffer); 86953e2822eSBjorn Andersson channel->bounce_buffer = NULL; 87053e2822eSBjorn Andersson 87153e2822eSBjorn Andersson qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); 87253e2822eSBjorn Andersson qcom_smd_channel_reset(channel); 87353e2822eSBjorn Andersson } 87453e2822eSBjorn Andersson 87553e2822eSBjorn Andersson static struct qcom_smd_channel * 87653e2822eSBjorn Andersson qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name) 87753e2822eSBjorn Andersson { 87853e2822eSBjorn Andersson struct qcom_smd_channel *channel; 87953e2822eSBjorn Andersson struct qcom_smd_channel *ret = NULL; 88053e2822eSBjorn Andersson unsigned long flags; 88153e2822eSBjorn Andersson 88253e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 88353e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 88466dca399SBjorn Andersson if (!strcmp(channel->name, name)) { 88553e2822eSBjorn Andersson ret = channel; 88653e2822eSBjorn Andersson break; 88753e2822eSBjorn Andersson } 88866dca399SBjorn Andersson } 88953e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 89053e2822eSBjorn Andersson 89153e2822eSBjorn Andersson return ret; 89253e2822eSBjorn Andersson } 89353e2822eSBjorn Andersson 89453e2822eSBjorn Andersson static void __ept_release(struct kref *kref) 89553e2822eSBjorn Andersson { 89653e2822eSBjorn Andersson struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint, 89753e2822eSBjorn Andersson refcount); 89853e2822eSBjorn Andersson kfree(to_smd_endpoint(ept)); 89953e2822eSBjorn Andersson } 90053e2822eSBjorn Andersson 90153e2822eSBjorn Andersson static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev, 90253e2822eSBjorn Andersson rpmsg_rx_cb_t cb, void *priv, 90353e2822eSBjorn Andersson struct rpmsg_channel_info chinfo) 90453e2822eSBjorn Andersson { 90553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept; 90653e2822eSBjorn Andersson struct qcom_smd_channel *channel; 90753e2822eSBjorn Andersson struct qcom_smd_device *qsdev = to_smd_device(rpdev); 90853e2822eSBjorn Andersson struct qcom_smd_edge *edge = qsdev->edge; 90953e2822eSBjorn Andersson struct rpmsg_endpoint *ept; 91053e2822eSBjorn Andersson const char *name = chinfo.name; 91153e2822eSBjorn Andersson int ret; 91253e2822eSBjorn Andersson 91353e2822eSBjorn Andersson /* Wait up to HZ for the channel to appear */ 91453e2822eSBjorn Andersson ret = wait_event_interruptible_timeout(edge->new_channel_event, 91553e2822eSBjorn Andersson (channel = qcom_smd_find_channel(edge, name)) != NULL, 91653e2822eSBjorn Andersson HZ); 91753e2822eSBjorn Andersson if (!ret) 91853e2822eSBjorn Andersson return NULL; 91953e2822eSBjorn Andersson 92053e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_CLOSED) { 92153e2822eSBjorn Andersson dev_err(&rpdev->dev, "channel %s is busy\n", channel->name); 92253e2822eSBjorn Andersson return NULL; 92353e2822eSBjorn Andersson } 92453e2822eSBjorn Andersson 92553e2822eSBjorn Andersson qsept = kzalloc(sizeof(*qsept), GFP_KERNEL); 92653e2822eSBjorn Andersson if (!qsept) 92753e2822eSBjorn Andersson return NULL; 92853e2822eSBjorn Andersson 92953e2822eSBjorn Andersson ept = &qsept->ept; 93053e2822eSBjorn Andersson 93153e2822eSBjorn Andersson kref_init(&ept->refcount); 93253e2822eSBjorn Andersson 93353e2822eSBjorn Andersson ept->rpdev = rpdev; 93453e2822eSBjorn Andersson ept->cb = cb; 93553e2822eSBjorn Andersson ept->priv = priv; 93653e2822eSBjorn Andersson ept->ops = &qcom_smd_endpoint_ops; 93753e2822eSBjorn Andersson 93853e2822eSBjorn Andersson channel->qsept = qsept; 93953e2822eSBjorn Andersson qsept->qsch = channel; 94053e2822eSBjorn Andersson 94153e2822eSBjorn Andersson ret = qcom_smd_channel_open(channel, cb); 94253e2822eSBjorn Andersson if (ret) 94353e2822eSBjorn Andersson goto free_ept; 94453e2822eSBjorn Andersson 94553e2822eSBjorn Andersson return ept; 94653e2822eSBjorn Andersson 94753e2822eSBjorn Andersson free_ept: 94853e2822eSBjorn Andersson channel->qsept = NULL; 94953e2822eSBjorn Andersson kref_put(&ept->refcount, __ept_release); 95053e2822eSBjorn Andersson return NULL; 95153e2822eSBjorn Andersson } 95253e2822eSBjorn Andersson 95353e2822eSBjorn Andersson static void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept) 95453e2822eSBjorn Andersson { 95553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 95653e2822eSBjorn Andersson struct qcom_smd_channel *ch = qsept->qsch; 95753e2822eSBjorn Andersson 95853e2822eSBjorn Andersson qcom_smd_channel_close(ch); 95953e2822eSBjorn Andersson ch->qsept = NULL; 96053e2822eSBjorn Andersson kref_put(&ept->refcount, __ept_release); 96153e2822eSBjorn Andersson } 96253e2822eSBjorn Andersson 96353e2822eSBjorn Andersson static int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len) 96453e2822eSBjorn Andersson { 96553e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 96653e2822eSBjorn Andersson 96753e2822eSBjorn Andersson return __qcom_smd_send(qsept->qsch, data, len, true); 96853e2822eSBjorn Andersson } 96953e2822eSBjorn Andersson 97053e2822eSBjorn Andersson static int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len) 97153e2822eSBjorn Andersson { 97253e2822eSBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 97353e2822eSBjorn Andersson 97453e2822eSBjorn Andersson return __qcom_smd_send(qsept->qsch, data, len, false); 97553e2822eSBjorn Andersson } 97653e2822eSBjorn Andersson 977*b4ce7e2eSArnaud Pouliquen static int qcom_smd_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 978*b4ce7e2eSArnaud Pouliquen { 979*b4ce7e2eSArnaud Pouliquen struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 980*b4ce7e2eSArnaud Pouliquen 981*b4ce7e2eSArnaud Pouliquen return __qcom_smd_send(qsept->qsch, data, len, true); 982*b4ce7e2eSArnaud Pouliquen } 983*b4ce7e2eSArnaud Pouliquen 984*b4ce7e2eSArnaud Pouliquen static int qcom_smd_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 985*b4ce7e2eSArnaud Pouliquen { 986*b4ce7e2eSArnaud Pouliquen struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 987*b4ce7e2eSArnaud Pouliquen 988*b4ce7e2eSArnaud Pouliquen return __qcom_smd_send(qsept->qsch, data, len, false); 989*b4ce7e2eSArnaud Pouliquen } 990*b4ce7e2eSArnaud Pouliquen 991afc9a42bSAl Viro static __poll_t qcom_smd_poll(struct rpmsg_endpoint *ept, 992adaa11b0SBjorn Andersson struct file *filp, poll_table *wait) 993adaa11b0SBjorn Andersson { 994adaa11b0SBjorn Andersson struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 995adaa11b0SBjorn Andersson struct qcom_smd_channel *channel = qsept->qsch; 996afc9a42bSAl Viro __poll_t mask = 0; 997adaa11b0SBjorn Andersson 998adaa11b0SBjorn Andersson poll_wait(filp, &channel->fblockread_event, wait); 999adaa11b0SBjorn Andersson 1000adaa11b0SBjorn Andersson if (qcom_smd_get_tx_avail(channel) > 20) 1001a9a08845SLinus Torvalds mask |= EPOLLOUT | EPOLLWRNORM; 1002adaa11b0SBjorn Andersson 1003adaa11b0SBjorn Andersson return mask; 1004adaa11b0SBjorn Andersson } 1005adaa11b0SBjorn Andersson 100653e2822eSBjorn Andersson /* 100753e2822eSBjorn Andersson * Finds the device_node for the smd child interested in this channel. 100853e2822eSBjorn Andersson */ 100953e2822eSBjorn Andersson static struct device_node *qcom_smd_match_channel(struct device_node *edge_node, 101053e2822eSBjorn Andersson const char *channel) 101153e2822eSBjorn Andersson { 101253e2822eSBjorn Andersson struct device_node *child; 101353e2822eSBjorn Andersson const char *name; 101453e2822eSBjorn Andersson const char *key; 101553e2822eSBjorn Andersson int ret; 101653e2822eSBjorn Andersson 101753e2822eSBjorn Andersson for_each_available_child_of_node(edge_node, child) { 101853e2822eSBjorn Andersson key = "qcom,smd-channels"; 101953e2822eSBjorn Andersson ret = of_property_read_string(child, key, &name); 102053e2822eSBjorn Andersson if (ret) 102153e2822eSBjorn Andersson continue; 102253e2822eSBjorn Andersson 102353e2822eSBjorn Andersson if (strcmp(name, channel) == 0) 102453e2822eSBjorn Andersson return child; 102553e2822eSBjorn Andersson } 102653e2822eSBjorn Andersson 102753e2822eSBjorn Andersson return NULL; 102853e2822eSBjorn Andersson } 102953e2822eSBjorn Andersson 10300d72038cSBjorn Andersson static int qcom_smd_announce_create(struct rpmsg_device *rpdev) 10310d72038cSBjorn Andersson { 10320d72038cSBjorn Andersson struct qcom_smd_endpoint *qept = to_smd_endpoint(rpdev->ept); 10330d72038cSBjorn Andersson struct qcom_smd_channel *channel = qept->qsch; 10340d72038cSBjorn Andersson unsigned long flags; 10350d72038cSBjorn Andersson bool kick_state; 10360d72038cSBjorn Andersson 10370d72038cSBjorn Andersson spin_lock_irqsave(&channel->recv_lock, flags); 10380d72038cSBjorn Andersson kick_state = qcom_smd_channel_intr(channel); 10390d72038cSBjorn Andersson spin_unlock_irqrestore(&channel->recv_lock, flags); 10400d72038cSBjorn Andersson 10410d72038cSBjorn Andersson if (kick_state) 10420d72038cSBjorn Andersson schedule_work(&channel->edge->state_work); 10430d72038cSBjorn Andersson 10440d72038cSBjorn Andersson return 0; 10450d72038cSBjorn Andersson } 10460d72038cSBjorn Andersson 104753e2822eSBjorn Andersson static const struct rpmsg_device_ops qcom_smd_device_ops = { 104853e2822eSBjorn Andersson .create_ept = qcom_smd_create_ept, 10490d72038cSBjorn Andersson .announce_create = qcom_smd_announce_create, 105053e2822eSBjorn Andersson }; 105153e2822eSBjorn Andersson 105253e2822eSBjorn Andersson static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = { 105353e2822eSBjorn Andersson .destroy_ept = qcom_smd_destroy_ept, 105453e2822eSBjorn Andersson .send = qcom_smd_send, 1055*b4ce7e2eSArnaud Pouliquen .sendto = qcom_smd_sendto, 105653e2822eSBjorn Andersson .trysend = qcom_smd_trysend, 1057*b4ce7e2eSArnaud Pouliquen .trysendto = qcom_smd_trysendto, 1058adaa11b0SBjorn Andersson .poll = qcom_smd_poll, 105953e2822eSBjorn Andersson }; 106053e2822eSBjorn Andersson 1061b0b03b81SBjorn Andersson static void qcom_smd_release_device(struct device *dev) 1062b0b03b81SBjorn Andersson { 1063b0b03b81SBjorn Andersson struct rpmsg_device *rpdev = to_rpmsg_device(dev); 1064b0b03b81SBjorn Andersson struct qcom_smd_device *qsdev = to_smd_device(rpdev); 1065b0b03b81SBjorn Andersson 1066b0b03b81SBjorn Andersson kfree(qsdev); 1067b0b03b81SBjorn Andersson } 1068b0b03b81SBjorn Andersson 106953e2822eSBjorn Andersson /* 107053e2822eSBjorn Andersson * Create a smd client device for channel that is being opened. 107153e2822eSBjorn Andersson */ 107253e2822eSBjorn Andersson static int qcom_smd_create_device(struct qcom_smd_channel *channel) 107353e2822eSBjorn Andersson { 107453e2822eSBjorn Andersson struct qcom_smd_device *qsdev; 107553e2822eSBjorn Andersson struct rpmsg_device *rpdev; 107653e2822eSBjorn Andersson struct qcom_smd_edge *edge = channel->edge; 107753e2822eSBjorn Andersson 107853e2822eSBjorn Andersson dev_dbg(&edge->dev, "registering '%s'\n", channel->name); 107953e2822eSBjorn Andersson 108053e2822eSBjorn Andersson qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); 108153e2822eSBjorn Andersson if (!qsdev) 108253e2822eSBjorn Andersson return -ENOMEM; 108353e2822eSBjorn Andersson 108453e2822eSBjorn Andersson /* Link qsdev to our SMD edge */ 108553e2822eSBjorn Andersson qsdev->edge = edge; 108653e2822eSBjorn Andersson 108753e2822eSBjorn Andersson /* Assign callbacks for rpmsg_device */ 108853e2822eSBjorn Andersson qsdev->rpdev.ops = &qcom_smd_device_ops; 108953e2822eSBjorn Andersson 109053e2822eSBjorn Andersson /* Assign public information to the rpmsg_device */ 109153e2822eSBjorn Andersson rpdev = &qsdev->rpdev; 109253e2822eSBjorn Andersson strncpy(rpdev->id.name, channel->name, RPMSG_NAME_SIZE); 109353e2822eSBjorn Andersson rpdev->src = RPMSG_ADDR_ANY; 109453e2822eSBjorn Andersson rpdev->dst = RPMSG_ADDR_ANY; 109553e2822eSBjorn Andersson 109653e2822eSBjorn Andersson rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name); 109753e2822eSBjorn Andersson rpdev->dev.parent = &edge->dev; 1098b0b03b81SBjorn Andersson rpdev->dev.release = qcom_smd_release_device; 109953e2822eSBjorn Andersson 110053e2822eSBjorn Andersson return rpmsg_register_device(rpdev); 110153e2822eSBjorn Andersson } 110253e2822eSBjorn Andersson 11030be363bfSBjorn Andersson static int qcom_smd_create_chrdev(struct qcom_smd_edge *edge) 11040be363bfSBjorn Andersson { 11050be363bfSBjorn Andersson struct qcom_smd_device *qsdev; 11060be363bfSBjorn Andersson 11070be363bfSBjorn Andersson qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); 11080be363bfSBjorn Andersson if (!qsdev) 11090be363bfSBjorn Andersson return -ENOMEM; 11100be363bfSBjorn Andersson 11110be363bfSBjorn Andersson qsdev->edge = edge; 11120be363bfSBjorn Andersson qsdev->rpdev.ops = &qcom_smd_device_ops; 11130be363bfSBjorn Andersson qsdev->rpdev.dev.parent = &edge->dev; 1114b0b03b81SBjorn Andersson qsdev->rpdev.dev.release = qcom_smd_release_device; 1115b0b03b81SBjorn Andersson 11160be363bfSBjorn Andersson return rpmsg_chrdev_register_device(&qsdev->rpdev); 11170be363bfSBjorn Andersson } 11180be363bfSBjorn Andersson 111953e2822eSBjorn Andersson /* 112053e2822eSBjorn Andersson * Allocate the qcom_smd_channel object for a newly found smd channel, 112153e2822eSBjorn Andersson * retrieving and validating the smem items involved. 112253e2822eSBjorn Andersson */ 112353e2822eSBjorn Andersson static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge, 112453e2822eSBjorn Andersson unsigned smem_info_item, 112553e2822eSBjorn Andersson unsigned smem_fifo_item, 112653e2822eSBjorn Andersson char *name) 112753e2822eSBjorn Andersson { 112853e2822eSBjorn Andersson struct qcom_smd_channel *channel; 112953e2822eSBjorn Andersson size_t fifo_size; 113053e2822eSBjorn Andersson size_t info_size; 113153e2822eSBjorn Andersson void *fifo_base; 113253e2822eSBjorn Andersson void *info; 113353e2822eSBjorn Andersson int ret; 113453e2822eSBjorn Andersson 11354a2e84c6SSrinivas Kandagatla channel = kzalloc(sizeof(*channel), GFP_KERNEL); 113653e2822eSBjorn Andersson if (!channel) 113753e2822eSBjorn Andersson return ERR_PTR(-ENOMEM); 113853e2822eSBjorn Andersson 113953e2822eSBjorn Andersson channel->edge = edge; 11404a2e84c6SSrinivas Kandagatla channel->name = kstrdup(name, GFP_KERNEL); 1141940c620dSColin Ian King if (!channel->name) { 1142940c620dSColin Ian King ret = -ENOMEM; 1143940c620dSColin Ian King goto free_channel; 1144940c620dSColin Ian King } 114553e2822eSBjorn Andersson 114633e3820dSBjorn Andersson spin_lock_init(&channel->tx_lock); 114753e2822eSBjorn Andersson spin_lock_init(&channel->recv_lock); 114853e2822eSBjorn Andersson init_waitqueue_head(&channel->fblockread_event); 1149268105fbSBjorn Andersson init_waitqueue_head(&channel->state_change_event); 115053e2822eSBjorn Andersson 115153e2822eSBjorn Andersson info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size); 115253e2822eSBjorn Andersson if (IS_ERR(info)) { 115353e2822eSBjorn Andersson ret = PTR_ERR(info); 115453e2822eSBjorn Andersson goto free_name_and_channel; 115553e2822eSBjorn Andersson } 115653e2822eSBjorn Andersson 115753e2822eSBjorn Andersson /* 115853e2822eSBjorn Andersson * Use the size of the item to figure out which channel info struct to 115953e2822eSBjorn Andersson * use. 116053e2822eSBjorn Andersson */ 116153e2822eSBjorn Andersson if (info_size == 2 * sizeof(struct smd_channel_info_word)) { 116253e2822eSBjorn Andersson channel->info_word = info; 116353e2822eSBjorn Andersson } else if (info_size == 2 * sizeof(struct smd_channel_info)) { 116453e2822eSBjorn Andersson channel->info = info; 116553e2822eSBjorn Andersson } else { 116653e2822eSBjorn Andersson dev_err(&edge->dev, 116753e2822eSBjorn Andersson "channel info of size %zu not supported\n", info_size); 116853e2822eSBjorn Andersson ret = -EINVAL; 116953e2822eSBjorn Andersson goto free_name_and_channel; 117053e2822eSBjorn Andersson } 117153e2822eSBjorn Andersson 117253e2822eSBjorn Andersson fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size); 117353e2822eSBjorn Andersson if (IS_ERR(fifo_base)) { 117453e2822eSBjorn Andersson ret = PTR_ERR(fifo_base); 117553e2822eSBjorn Andersson goto free_name_and_channel; 117653e2822eSBjorn Andersson } 117753e2822eSBjorn Andersson 117853e2822eSBjorn Andersson /* The channel consist of a rx and tx fifo of equal size */ 117953e2822eSBjorn Andersson fifo_size /= 2; 118053e2822eSBjorn Andersson 118153e2822eSBjorn Andersson dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n", 118253e2822eSBjorn Andersson name, info_size, fifo_size); 118353e2822eSBjorn Andersson 118453e2822eSBjorn Andersson channel->tx_fifo = fifo_base; 118553e2822eSBjorn Andersson channel->rx_fifo = fifo_base + fifo_size; 118653e2822eSBjorn Andersson channel->fifo_size = fifo_size; 118753e2822eSBjorn Andersson 118853e2822eSBjorn Andersson qcom_smd_channel_reset(channel); 118953e2822eSBjorn Andersson 119053e2822eSBjorn Andersson return channel; 119153e2822eSBjorn Andersson 119253e2822eSBjorn Andersson free_name_and_channel: 11934a2e84c6SSrinivas Kandagatla kfree(channel->name); 1194940c620dSColin Ian King free_channel: 11954a2e84c6SSrinivas Kandagatla kfree(channel); 119653e2822eSBjorn Andersson 119753e2822eSBjorn Andersson return ERR_PTR(ret); 119853e2822eSBjorn Andersson } 119953e2822eSBjorn Andersson 120053e2822eSBjorn Andersson /* 120153e2822eSBjorn Andersson * Scans the allocation table for any newly allocated channels, calls 120253e2822eSBjorn Andersson * qcom_smd_create_channel() to create representations of these and add 120353e2822eSBjorn Andersson * them to the edge's list of channels. 120453e2822eSBjorn Andersson */ 120553e2822eSBjorn Andersson static void qcom_channel_scan_worker(struct work_struct *work) 120653e2822eSBjorn Andersson { 120753e2822eSBjorn Andersson struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work); 120853e2822eSBjorn Andersson struct qcom_smd_alloc_entry *alloc_tbl; 120953e2822eSBjorn Andersson struct qcom_smd_alloc_entry *entry; 121053e2822eSBjorn Andersson struct qcom_smd_channel *channel; 121153e2822eSBjorn Andersson unsigned long flags; 121253e2822eSBjorn Andersson unsigned fifo_id; 121353e2822eSBjorn Andersson unsigned info_id; 121453e2822eSBjorn Andersson int tbl; 121553e2822eSBjorn Andersson int i; 121653e2822eSBjorn Andersson u32 eflags, cid; 121753e2822eSBjorn Andersson 121853e2822eSBjorn Andersson for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) { 121953e2822eSBjorn Andersson alloc_tbl = qcom_smem_get(edge->remote_pid, 122053e2822eSBjorn Andersson smem_items[tbl].alloc_tbl_id, NULL); 122153e2822eSBjorn Andersson if (IS_ERR(alloc_tbl)) 122253e2822eSBjorn Andersson continue; 122353e2822eSBjorn Andersson 122453e2822eSBjorn Andersson for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) { 122553e2822eSBjorn Andersson entry = &alloc_tbl[i]; 122653e2822eSBjorn Andersson eflags = le32_to_cpu(entry->flags); 122753e2822eSBjorn Andersson if (test_bit(i, edge->allocated[tbl])) 122853e2822eSBjorn Andersson continue; 122953e2822eSBjorn Andersson 123053e2822eSBjorn Andersson if (entry->ref_count == 0) 123153e2822eSBjorn Andersson continue; 123253e2822eSBjorn Andersson 123353e2822eSBjorn Andersson if (!entry->name[0]) 123453e2822eSBjorn Andersson continue; 123553e2822eSBjorn Andersson 123653e2822eSBjorn Andersson if (!(eflags & SMD_CHANNEL_FLAGS_PACKET)) 123753e2822eSBjorn Andersson continue; 123853e2822eSBjorn Andersson 123953e2822eSBjorn Andersson if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) 124053e2822eSBjorn Andersson continue; 124153e2822eSBjorn Andersson 124253e2822eSBjorn Andersson cid = le32_to_cpu(entry->cid); 124353e2822eSBjorn Andersson info_id = smem_items[tbl].info_base_id + cid; 124453e2822eSBjorn Andersson fifo_id = smem_items[tbl].fifo_base_id + cid; 124553e2822eSBjorn Andersson 124653e2822eSBjorn Andersson channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name); 124753e2822eSBjorn Andersson if (IS_ERR(channel)) 124853e2822eSBjorn Andersson continue; 124953e2822eSBjorn Andersson 125053e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 125153e2822eSBjorn Andersson list_add(&channel->list, &edge->channels); 125253e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 125353e2822eSBjorn Andersson 125453e2822eSBjorn Andersson dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name); 125553e2822eSBjorn Andersson set_bit(i, edge->allocated[tbl]); 125653e2822eSBjorn Andersson 1257eb114f27SBjorn Andersson wake_up_interruptible_all(&edge->new_channel_event); 125853e2822eSBjorn Andersson } 125953e2822eSBjorn Andersson } 126053e2822eSBjorn Andersson 126153e2822eSBjorn Andersson schedule_work(&edge->state_work); 126253e2822eSBjorn Andersson } 126353e2822eSBjorn Andersson 126453e2822eSBjorn Andersson /* 126553e2822eSBjorn Andersson * This per edge worker scans smem for any new channels and register these. It 126653e2822eSBjorn Andersson * then scans all registered channels for state changes that should be handled 126753e2822eSBjorn Andersson * by creating or destroying smd client devices for the registered channels. 126853e2822eSBjorn Andersson * 126953e2822eSBjorn Andersson * LOCKING: edge->channels_lock only needs to cover the list operations, as the 127053e2822eSBjorn Andersson * worker is killed before any channels are deallocated 127153e2822eSBjorn Andersson */ 127253e2822eSBjorn Andersson static void qcom_channel_state_worker(struct work_struct *work) 127353e2822eSBjorn Andersson { 127453e2822eSBjorn Andersson struct qcom_smd_channel *channel; 127553e2822eSBjorn Andersson struct qcom_smd_edge *edge = container_of(work, 127653e2822eSBjorn Andersson struct qcom_smd_edge, 127753e2822eSBjorn Andersson state_work); 127853e2822eSBjorn Andersson struct rpmsg_channel_info chinfo; 127953e2822eSBjorn Andersson unsigned remote_state; 128053e2822eSBjorn Andersson unsigned long flags; 128153e2822eSBjorn Andersson 128253e2822eSBjorn Andersson /* 128353e2822eSBjorn Andersson * Register a device for any closed channel where the remote processor 128453e2822eSBjorn Andersson * is showing interest in opening the channel. 128553e2822eSBjorn Andersson */ 128653e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 128753e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 128853e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_CLOSED) 128953e2822eSBjorn Andersson continue; 129053e2822eSBjorn Andersson 12912bd9b438SBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 12922bd9b438SBjorn Andersson if (remote_state != SMD_CHANNEL_OPENING && 12932bd9b438SBjorn Andersson remote_state != SMD_CHANNEL_OPENED) 12942bd9b438SBjorn Andersson continue; 12952bd9b438SBjorn Andersson 129653e2822eSBjorn Andersson if (channel->registered) 129753e2822eSBjorn Andersson continue; 129853e2822eSBjorn Andersson 129953e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 130053e2822eSBjorn Andersson qcom_smd_create_device(channel); 130153e2822eSBjorn Andersson channel->registered = true; 130253e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 130353e2822eSBjorn Andersson 130453e2822eSBjorn Andersson channel->registered = true; 130553e2822eSBjorn Andersson } 130653e2822eSBjorn Andersson 130753e2822eSBjorn Andersson /* 130853e2822eSBjorn Andersson * Unregister the device for any channel that is opened where the 130953e2822eSBjorn Andersson * remote processor is closing the channel. 131053e2822eSBjorn Andersson */ 131153e2822eSBjorn Andersson list_for_each_entry(channel, &edge->channels, list) { 131253e2822eSBjorn Andersson if (channel->state != SMD_CHANNEL_OPENING && 131353e2822eSBjorn Andersson channel->state != SMD_CHANNEL_OPENED) 131453e2822eSBjorn Andersson continue; 131553e2822eSBjorn Andersson 131653e2822eSBjorn Andersson remote_state = GET_RX_CHANNEL_INFO(channel, state); 131753e2822eSBjorn Andersson if (remote_state == SMD_CHANNEL_OPENING || 131853e2822eSBjorn Andersson remote_state == SMD_CHANNEL_OPENED) 131953e2822eSBjorn Andersson continue; 132053e2822eSBjorn Andersson 132153e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 132253e2822eSBjorn Andersson 132353e2822eSBjorn Andersson strncpy(chinfo.name, channel->name, sizeof(chinfo.name)); 132453e2822eSBjorn Andersson chinfo.src = RPMSG_ADDR_ANY; 132553e2822eSBjorn Andersson chinfo.dst = RPMSG_ADDR_ANY; 132653e2822eSBjorn Andersson rpmsg_unregister_device(&edge->dev, &chinfo); 132753e2822eSBjorn Andersson channel->registered = false; 132853e2822eSBjorn Andersson spin_lock_irqsave(&edge->channels_lock, flags); 132953e2822eSBjorn Andersson } 133053e2822eSBjorn Andersson spin_unlock_irqrestore(&edge->channels_lock, flags); 133153e2822eSBjorn Andersson } 133253e2822eSBjorn Andersson 133353e2822eSBjorn Andersson /* 133453e2822eSBjorn Andersson * Parses an of_node describing an edge. 133553e2822eSBjorn Andersson */ 133653e2822eSBjorn Andersson static int qcom_smd_parse_edge(struct device *dev, 133753e2822eSBjorn Andersson struct device_node *node, 133853e2822eSBjorn Andersson struct qcom_smd_edge *edge) 133953e2822eSBjorn Andersson { 134053e2822eSBjorn Andersson struct device_node *syscon_np; 134153e2822eSBjorn Andersson const char *key; 134253e2822eSBjorn Andersson int irq; 134353e2822eSBjorn Andersson int ret; 134453e2822eSBjorn Andersson 134553e2822eSBjorn Andersson INIT_LIST_HEAD(&edge->channels); 134653e2822eSBjorn Andersson spin_lock_init(&edge->channels_lock); 134753e2822eSBjorn Andersson 134853e2822eSBjorn Andersson INIT_WORK(&edge->scan_work, qcom_channel_scan_worker); 134953e2822eSBjorn Andersson INIT_WORK(&edge->state_work, qcom_channel_state_worker); 135053e2822eSBjorn Andersson 135153e2822eSBjorn Andersson edge->of_node = of_node_get(node); 135253e2822eSBjorn Andersson 135353e2822eSBjorn Andersson key = "qcom,smd-edge"; 135453e2822eSBjorn Andersson ret = of_property_read_u32(node, key, &edge->edge_id); 135553e2822eSBjorn Andersson if (ret) { 135653e2822eSBjorn Andersson dev_err(dev, "edge missing %s property\n", key); 1357e69ee0cfSDan Carpenter goto put_node; 135853e2822eSBjorn Andersson } 135953e2822eSBjorn Andersson 136053e2822eSBjorn Andersson edge->remote_pid = QCOM_SMEM_HOST_ANY; 136153e2822eSBjorn Andersson key = "qcom,remote-pid"; 136253e2822eSBjorn Andersson of_property_read_u32(node, key, &edge->remote_pid); 136353e2822eSBjorn Andersson 1364ab460a2eSBjorn Andersson edge->mbox_client.dev = dev; 1365ab460a2eSBjorn Andersson edge->mbox_client.knows_txdone = true; 1366ab460a2eSBjorn Andersson edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0); 1367ab460a2eSBjorn Andersson if (IS_ERR(edge->mbox_chan)) { 1368e69ee0cfSDan Carpenter if (PTR_ERR(edge->mbox_chan) != -ENODEV) { 1369e69ee0cfSDan Carpenter ret = PTR_ERR(edge->mbox_chan); 1370e69ee0cfSDan Carpenter goto put_node; 1371e69ee0cfSDan Carpenter } 1372ab460a2eSBjorn Andersson 1373ab460a2eSBjorn Andersson edge->mbox_chan = NULL; 1374ab460a2eSBjorn Andersson 137553e2822eSBjorn Andersson syscon_np = of_parse_phandle(node, "qcom,ipc", 0); 137653e2822eSBjorn Andersson if (!syscon_np) { 137753e2822eSBjorn Andersson dev_err(dev, "no qcom,ipc node\n"); 1378e69ee0cfSDan Carpenter ret = -ENODEV; 1379e69ee0cfSDan Carpenter goto put_node; 138053e2822eSBjorn Andersson } 138153e2822eSBjorn Andersson 138253e2822eSBjorn Andersson edge->ipc_regmap = syscon_node_to_regmap(syscon_np); 1383e69ee0cfSDan Carpenter if (IS_ERR(edge->ipc_regmap)) { 1384e69ee0cfSDan Carpenter ret = PTR_ERR(edge->ipc_regmap); 1385e69ee0cfSDan Carpenter goto put_node; 1386e69ee0cfSDan Carpenter } 138753e2822eSBjorn Andersson 138853e2822eSBjorn Andersson key = "qcom,ipc"; 138953e2822eSBjorn Andersson ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); 139053e2822eSBjorn Andersson if (ret < 0) { 139153e2822eSBjorn Andersson dev_err(dev, "no offset in %s\n", key); 1392e69ee0cfSDan Carpenter goto put_node; 139353e2822eSBjorn Andersson } 139453e2822eSBjorn Andersson 139553e2822eSBjorn Andersson ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); 139653e2822eSBjorn Andersson if (ret < 0) { 139753e2822eSBjorn Andersson dev_err(dev, "no bit in %s\n", key); 1398e69ee0cfSDan Carpenter goto put_node; 139953e2822eSBjorn Andersson } 1400ab460a2eSBjorn Andersson } 140153e2822eSBjorn Andersson 14025e53c42cSBjorn Andersson ret = of_property_read_string(node, "label", &edge->name); 14035e53c42cSBjorn Andersson if (ret < 0) 14045e53c42cSBjorn Andersson edge->name = node->name; 14055e53c42cSBjorn Andersson 140653e2822eSBjorn Andersson irq = irq_of_parse_and_map(node, 0); 140753e2822eSBjorn Andersson if (irq < 0) { 140853e2822eSBjorn Andersson dev_err(dev, "required smd interrupt missing\n"); 1409e69ee0cfSDan Carpenter ret = irq; 1410e69ee0cfSDan Carpenter goto put_node; 141153e2822eSBjorn Andersson } 141253e2822eSBjorn Andersson 141353e2822eSBjorn Andersson ret = devm_request_irq(dev, irq, 141453e2822eSBjorn Andersson qcom_smd_edge_intr, IRQF_TRIGGER_RISING, 141553e2822eSBjorn Andersson node->name, edge); 141653e2822eSBjorn Andersson if (ret) { 141753e2822eSBjorn Andersson dev_err(dev, "failed to request smd irq\n"); 1418e69ee0cfSDan Carpenter goto put_node; 141953e2822eSBjorn Andersson } 142053e2822eSBjorn Andersson 142153e2822eSBjorn Andersson edge->irq = irq; 142253e2822eSBjorn Andersson 142353e2822eSBjorn Andersson return 0; 1424e69ee0cfSDan Carpenter 1425e69ee0cfSDan Carpenter put_node: 1426e69ee0cfSDan Carpenter of_node_put(node); 1427e69ee0cfSDan Carpenter edge->of_node = NULL; 1428e69ee0cfSDan Carpenter 1429e69ee0cfSDan Carpenter return ret; 143053e2822eSBjorn Andersson } 143153e2822eSBjorn Andersson 143253e2822eSBjorn Andersson /* 143353e2822eSBjorn Andersson * Release function for an edge. 143453e2822eSBjorn Andersson * Reset the state of each associated channel and free the edge context. 143553e2822eSBjorn Andersson */ 143653e2822eSBjorn Andersson static void qcom_smd_edge_release(struct device *dev) 143753e2822eSBjorn Andersson { 14384a2e84c6SSrinivas Kandagatla struct qcom_smd_channel *channel, *tmp; 143953e2822eSBjorn Andersson struct qcom_smd_edge *edge = to_smd_edge(dev); 144053e2822eSBjorn Andersson 14414a2e84c6SSrinivas Kandagatla list_for_each_entry_safe(channel, tmp, &edge->channels, list) { 14424a2e84c6SSrinivas Kandagatla list_del(&channel->list); 14434a2e84c6SSrinivas Kandagatla kfree(channel->name); 14444a2e84c6SSrinivas Kandagatla kfree(channel); 144553e2822eSBjorn Andersson } 144653e2822eSBjorn Andersson 144753e2822eSBjorn Andersson kfree(edge); 144853e2822eSBjorn Andersson } 144953e2822eSBjorn Andersson 14505e53c42cSBjorn Andersson static ssize_t rpmsg_name_show(struct device *dev, 14515e53c42cSBjorn Andersson struct device_attribute *attr, char *buf) 14525e53c42cSBjorn Andersson { 14535e53c42cSBjorn Andersson struct qcom_smd_edge *edge = to_smd_edge(dev); 14545e53c42cSBjorn Andersson 14555e53c42cSBjorn Andersson return sprintf(buf, "%s\n", edge->name); 14565e53c42cSBjorn Andersson } 14575e53c42cSBjorn Andersson static DEVICE_ATTR_RO(rpmsg_name); 14585e53c42cSBjorn Andersson 14595e53c42cSBjorn Andersson static struct attribute *qcom_smd_edge_attrs[] = { 14605e53c42cSBjorn Andersson &dev_attr_rpmsg_name.attr, 14615e53c42cSBjorn Andersson NULL 14625e53c42cSBjorn Andersson }; 14635e53c42cSBjorn Andersson ATTRIBUTE_GROUPS(qcom_smd_edge); 14645e53c42cSBjorn Andersson 146553e2822eSBjorn Andersson /** 146653e2822eSBjorn Andersson * qcom_smd_register_edge() - register an edge based on an device_node 146753e2822eSBjorn Andersson * @parent: parent device for the edge 146853e2822eSBjorn Andersson * @node: device_node describing the edge 146953e2822eSBjorn Andersson * 147053e2822eSBjorn Andersson * Returns an edge reference, or negative ERR_PTR() on failure. 147153e2822eSBjorn Andersson */ 147253e2822eSBjorn Andersson struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, 147353e2822eSBjorn Andersson struct device_node *node) 147453e2822eSBjorn Andersson { 147553e2822eSBjorn Andersson struct qcom_smd_edge *edge; 147653e2822eSBjorn Andersson int ret; 147753e2822eSBjorn Andersson 147853e2822eSBjorn Andersson edge = kzalloc(sizeof(*edge), GFP_KERNEL); 147953e2822eSBjorn Andersson if (!edge) 148053e2822eSBjorn Andersson return ERR_PTR(-ENOMEM); 148153e2822eSBjorn Andersson 148253e2822eSBjorn Andersson init_waitqueue_head(&edge->new_channel_event); 148353e2822eSBjorn Andersson 148453e2822eSBjorn Andersson edge->dev.parent = parent; 148553e2822eSBjorn Andersson edge->dev.release = qcom_smd_edge_release; 1486aaafb24eSSrinivas Kandagatla edge->dev.of_node = node; 14875e53c42cSBjorn Andersson edge->dev.groups = qcom_smd_edge_groups; 1488c8a54c0cSRob Herring dev_set_name(&edge->dev, "%s:%pOFn", dev_name(parent), node); 148953e2822eSBjorn Andersson ret = device_register(&edge->dev); 149053e2822eSBjorn Andersson if (ret) { 149153e2822eSBjorn Andersson pr_err("failed to register smd edge\n"); 1492be5acd24SArvind Yadav put_device(&edge->dev); 149353e2822eSBjorn Andersson return ERR_PTR(ret); 149453e2822eSBjorn Andersson } 149553e2822eSBjorn Andersson 149653e2822eSBjorn Andersson ret = qcom_smd_parse_edge(&edge->dev, node, edge); 149753e2822eSBjorn Andersson if (ret) { 149853e2822eSBjorn Andersson dev_err(&edge->dev, "failed to parse smd edge\n"); 149953e2822eSBjorn Andersson goto unregister_dev; 150053e2822eSBjorn Andersson } 150153e2822eSBjorn Andersson 15020be363bfSBjorn Andersson ret = qcom_smd_create_chrdev(edge); 15030be363bfSBjorn Andersson if (ret) { 15040be363bfSBjorn Andersson dev_err(&edge->dev, "failed to register chrdev for edge\n"); 15050be363bfSBjorn Andersson goto unregister_dev; 15060be363bfSBjorn Andersson } 15070be363bfSBjorn Andersson 150853e2822eSBjorn Andersson schedule_work(&edge->scan_work); 150953e2822eSBjorn Andersson 151053e2822eSBjorn Andersson return edge; 151153e2822eSBjorn Andersson 151253e2822eSBjorn Andersson unregister_dev: 1513ab460a2eSBjorn Andersson if (!IS_ERR_OR_NULL(edge->mbox_chan)) 1514ab460a2eSBjorn Andersson mbox_free_channel(edge->mbox_chan); 1515ab460a2eSBjorn Andersson 1516be5acd24SArvind Yadav device_unregister(&edge->dev); 151753e2822eSBjorn Andersson return ERR_PTR(ret); 151853e2822eSBjorn Andersson } 151953e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_register_edge); 152053e2822eSBjorn Andersson 152153e2822eSBjorn Andersson static int qcom_smd_remove_device(struct device *dev, void *data) 152253e2822eSBjorn Andersson { 152353e2822eSBjorn Andersson device_unregister(dev); 152453e2822eSBjorn Andersson 152553e2822eSBjorn Andersson return 0; 152653e2822eSBjorn Andersson } 152753e2822eSBjorn Andersson 152853e2822eSBjorn Andersson /** 152953e2822eSBjorn Andersson * qcom_smd_unregister_edge() - release an edge and its children 153053e2822eSBjorn Andersson * @edge: edge reference acquired from qcom_smd_register_edge 153153e2822eSBjorn Andersson */ 153253e2822eSBjorn Andersson int qcom_smd_unregister_edge(struct qcom_smd_edge *edge) 153353e2822eSBjorn Andersson { 153453e2822eSBjorn Andersson int ret; 153553e2822eSBjorn Andersson 153653e2822eSBjorn Andersson disable_irq(edge->irq); 153753e2822eSBjorn Andersson cancel_work_sync(&edge->scan_work); 153853e2822eSBjorn Andersson cancel_work_sync(&edge->state_work); 153953e2822eSBjorn Andersson 154053e2822eSBjorn Andersson ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device); 154153e2822eSBjorn Andersson if (ret) 154253e2822eSBjorn Andersson dev_warn(&edge->dev, "can't remove smd device: %d\n", ret); 154353e2822eSBjorn Andersson 1544ab460a2eSBjorn Andersson mbox_free_channel(edge->mbox_chan); 154553e2822eSBjorn Andersson device_unregister(&edge->dev); 154653e2822eSBjorn Andersson 154753e2822eSBjorn Andersson return 0; 154853e2822eSBjorn Andersson } 154953e2822eSBjorn Andersson EXPORT_SYMBOL(qcom_smd_unregister_edge); 155053e2822eSBjorn Andersson 155153e2822eSBjorn Andersson static int qcom_smd_probe(struct platform_device *pdev) 155253e2822eSBjorn Andersson { 155353e2822eSBjorn Andersson struct device_node *node; 155453e2822eSBjorn Andersson void *p; 155553e2822eSBjorn Andersson 155653e2822eSBjorn Andersson /* Wait for smem */ 155753e2822eSBjorn Andersson p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL); 155853e2822eSBjorn Andersson if (PTR_ERR(p) == -EPROBE_DEFER) 155953e2822eSBjorn Andersson return PTR_ERR(p); 156053e2822eSBjorn Andersson 156153e2822eSBjorn Andersson for_each_available_child_of_node(pdev->dev.of_node, node) 156253e2822eSBjorn Andersson qcom_smd_register_edge(&pdev->dev, node); 156353e2822eSBjorn Andersson 156453e2822eSBjorn Andersson return 0; 156553e2822eSBjorn Andersson } 156653e2822eSBjorn Andersson 156753e2822eSBjorn Andersson static int qcom_smd_remove_edge(struct device *dev, void *data) 156853e2822eSBjorn Andersson { 156953e2822eSBjorn Andersson struct qcom_smd_edge *edge = to_smd_edge(dev); 157053e2822eSBjorn Andersson 157153e2822eSBjorn Andersson return qcom_smd_unregister_edge(edge); 157253e2822eSBjorn Andersson } 157353e2822eSBjorn Andersson 157453e2822eSBjorn Andersson /* 157553e2822eSBjorn Andersson * Shut down all smd clients by making sure that each edge stops processing 157653e2822eSBjorn Andersson * events and scanning for new channels, then call destroy on the devices. 157753e2822eSBjorn Andersson */ 157853e2822eSBjorn Andersson static int qcom_smd_remove(struct platform_device *pdev) 157953e2822eSBjorn Andersson { 158053e2822eSBjorn Andersson int ret; 158153e2822eSBjorn Andersson 158253e2822eSBjorn Andersson ret = device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge); 158353e2822eSBjorn Andersson if (ret) 158453e2822eSBjorn Andersson dev_warn(&pdev->dev, "can't remove smd device: %d\n", ret); 158553e2822eSBjorn Andersson 158653e2822eSBjorn Andersson return ret; 158753e2822eSBjorn Andersson } 158853e2822eSBjorn Andersson 158953e2822eSBjorn Andersson static const struct of_device_id qcom_smd_of_match[] = { 159053e2822eSBjorn Andersson { .compatible = "qcom,smd" }, 159153e2822eSBjorn Andersson {} 159253e2822eSBjorn Andersson }; 159353e2822eSBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smd_of_match); 159453e2822eSBjorn Andersson 159553e2822eSBjorn Andersson static struct platform_driver qcom_smd_driver = { 159653e2822eSBjorn Andersson .probe = qcom_smd_probe, 159753e2822eSBjorn Andersson .remove = qcom_smd_remove, 159853e2822eSBjorn Andersson .driver = { 159953e2822eSBjorn Andersson .name = "qcom-smd", 160053e2822eSBjorn Andersson .of_match_table = qcom_smd_of_match, 160153e2822eSBjorn Andersson }, 160253e2822eSBjorn Andersson }; 160353e2822eSBjorn Andersson 160453e2822eSBjorn Andersson static int __init qcom_smd_init(void) 160553e2822eSBjorn Andersson { 160653e2822eSBjorn Andersson return platform_driver_register(&qcom_smd_driver); 160753e2822eSBjorn Andersson } 160853e2822eSBjorn Andersson subsys_initcall(qcom_smd_init); 160953e2822eSBjorn Andersson 161053e2822eSBjorn Andersson static void __exit qcom_smd_exit(void) 161153e2822eSBjorn Andersson { 161253e2822eSBjorn Andersson platform_driver_unregister(&qcom_smd_driver); 161353e2822eSBjorn Andersson } 161453e2822eSBjorn Andersson module_exit(qcom_smd_exit); 161553e2822eSBjorn Andersson 161653e2822eSBjorn Andersson MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 161753e2822eSBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory Driver"); 161853e2822eSBjorn Andersson MODULE_LICENSE("GPL v2"); 1619