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