162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications AB. 462306a36Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/interrupt.h> 862306a36Sopenharmony_ci#include <linux/io.h> 962306a36Sopenharmony_ci#include <linux/mailbox_client.h> 1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/of_irq.h> 1362306a36Sopenharmony_ci#include <linux/of_platform.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci#include <linux/sched.h> 1762306a36Sopenharmony_ci#include <linux/sizes.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/soc/qcom/smem.h> 2062306a36Sopenharmony_ci#include <linux/wait.h> 2162306a36Sopenharmony_ci#include <linux/rpmsg.h> 2262306a36Sopenharmony_ci#include <linux/rpmsg/qcom_smd.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "rpmsg_internal.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* 2762306a36Sopenharmony_ci * The Qualcomm Shared Memory communication solution provides point-to-point 2862306a36Sopenharmony_ci * channels for clients to send and receive streaming or packet based data. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * Each channel consists of a control item (channel info) and a ring buffer 3162306a36Sopenharmony_ci * pair. The channel info carry information related to channel state, flow 3262306a36Sopenharmony_ci * control and the offsets within the ring buffer. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * All allocated channels are listed in an allocation table, identifying the 3562306a36Sopenharmony_ci * pair of items by name, type and remote processor. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * Upon creating a new channel the remote processor allocates channel info and 3862306a36Sopenharmony_ci * ring buffer items from the smem heap and populate the allocation table. An 3962306a36Sopenharmony_ci * interrupt is sent to the other end of the channel and a scan for new 4062306a36Sopenharmony_ci * channels should be done. A channel never goes away, it will only change 4162306a36Sopenharmony_ci * state. 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * The remote processor signals it intent for bring up the communication 4462306a36Sopenharmony_ci * channel by setting the state of its end of the channel to "opening" and 4562306a36Sopenharmony_ci * sends out an interrupt. We detect this change and register a smd device to 4662306a36Sopenharmony_ci * consume the channel. Upon finding a consumer we finish the handshake and the 4762306a36Sopenharmony_ci * channel is up. 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * Upon closing a channel, the remote processor will update the state of its 5062306a36Sopenharmony_ci * end of the channel and signal us, we will then unregister any attached 5162306a36Sopenharmony_ci * device and close our end of the channel. 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci * Devices attached to a channel can use the qcom_smd_send function to push 5462306a36Sopenharmony_ci * data to the channel, this is done by copying the data into the tx ring 5562306a36Sopenharmony_ci * buffer, updating the pointers in the channel info and signaling the remote 5662306a36Sopenharmony_ci * processor. 5762306a36Sopenharmony_ci * 5862306a36Sopenharmony_ci * The remote processor does the equivalent when it transfer data and upon 5962306a36Sopenharmony_ci * receiving the interrupt we check the channel info for new data and delivers 6062306a36Sopenharmony_ci * this to the attached device. If the device is not ready to receive the data 6162306a36Sopenharmony_ci * we leave it in the ring buffer for now. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistruct smd_channel_info; 6562306a36Sopenharmony_cistruct smd_channel_info_pair; 6662306a36Sopenharmony_cistruct smd_channel_info_word; 6762306a36Sopenharmony_cistruct smd_channel_info_word_pair; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci#define SMD_ALLOC_TBL_COUNT 2 7262306a36Sopenharmony_ci#define SMD_ALLOC_TBL_SIZE 64 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci/* 7562306a36Sopenharmony_ci * This lists the various smem heap items relevant for the allocation table and 7662306a36Sopenharmony_ci * smd channel entries. 7762306a36Sopenharmony_ci */ 7862306a36Sopenharmony_cistatic const struct { 7962306a36Sopenharmony_ci unsigned alloc_tbl_id; 8062306a36Sopenharmony_ci unsigned info_base_id; 8162306a36Sopenharmony_ci unsigned fifo_base_id; 8262306a36Sopenharmony_ci} smem_items[SMD_ALLOC_TBL_COUNT] = { 8362306a36Sopenharmony_ci { 8462306a36Sopenharmony_ci .alloc_tbl_id = 13, 8562306a36Sopenharmony_ci .info_base_id = 14, 8662306a36Sopenharmony_ci .fifo_base_id = 338 8762306a36Sopenharmony_ci }, 8862306a36Sopenharmony_ci { 8962306a36Sopenharmony_ci .alloc_tbl_id = 266, 9062306a36Sopenharmony_ci .info_base_id = 138, 9162306a36Sopenharmony_ci .fifo_base_id = 202, 9262306a36Sopenharmony_ci }, 9362306a36Sopenharmony_ci}; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci/** 9662306a36Sopenharmony_ci * struct qcom_smd_edge - representing a remote processor 9762306a36Sopenharmony_ci * @dev: device associated with this edge 9862306a36Sopenharmony_ci * @name: name of this edge 9962306a36Sopenharmony_ci * @of_node: of_node handle for information related to this edge 10062306a36Sopenharmony_ci * @edge_id: identifier of this edge 10162306a36Sopenharmony_ci * @remote_pid: identifier of remote processor 10262306a36Sopenharmony_ci * @irq: interrupt for signals on this edge 10362306a36Sopenharmony_ci * @ipc_regmap: regmap handle holding the outgoing ipc register 10462306a36Sopenharmony_ci * @ipc_offset: offset within @ipc_regmap of the register for ipc 10562306a36Sopenharmony_ci * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap 10662306a36Sopenharmony_ci * @mbox_client: mailbox client handle 10762306a36Sopenharmony_ci * @mbox_chan: apcs ipc mailbox channel handle 10862306a36Sopenharmony_ci * @channels: list of all channels detected on this edge 10962306a36Sopenharmony_ci * @channels_lock: guard for modifications of @channels 11062306a36Sopenharmony_ci * @allocated: array of bitmaps representing already allocated channels 11162306a36Sopenharmony_ci * @smem_available: last available amount of smem triggering a channel scan 11262306a36Sopenharmony_ci * @new_channel_event: wait queue for new channel events 11362306a36Sopenharmony_ci * @scan_work: work item for discovering new channels 11462306a36Sopenharmony_ci * @state_work: work item for edge state changes 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistruct qcom_smd_edge { 11762306a36Sopenharmony_ci struct device dev; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci const char *name; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci struct device_node *of_node; 12262306a36Sopenharmony_ci unsigned edge_id; 12362306a36Sopenharmony_ci unsigned remote_pid; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci int irq; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci struct regmap *ipc_regmap; 12862306a36Sopenharmony_ci int ipc_offset; 12962306a36Sopenharmony_ci int ipc_bit; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci struct mbox_client mbox_client; 13262306a36Sopenharmony_ci struct mbox_chan *mbox_chan; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci struct list_head channels; 13562306a36Sopenharmony_ci spinlock_t channels_lock; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci unsigned smem_available; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci wait_queue_head_t new_channel_event; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci struct work_struct scan_work; 14462306a36Sopenharmony_ci struct work_struct state_work; 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/* 14862306a36Sopenharmony_ci * SMD channel states. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_cienum smd_channel_state { 15162306a36Sopenharmony_ci SMD_CHANNEL_CLOSED, 15262306a36Sopenharmony_ci SMD_CHANNEL_OPENING, 15362306a36Sopenharmony_ci SMD_CHANNEL_OPENED, 15462306a36Sopenharmony_ci SMD_CHANNEL_FLUSHING, 15562306a36Sopenharmony_ci SMD_CHANNEL_CLOSING, 15662306a36Sopenharmony_ci SMD_CHANNEL_RESET, 15762306a36Sopenharmony_ci SMD_CHANNEL_RESET_OPENING 15862306a36Sopenharmony_ci}; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistruct qcom_smd_device { 16162306a36Sopenharmony_ci struct rpmsg_device rpdev; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci struct qcom_smd_edge *edge; 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistruct qcom_smd_endpoint { 16762306a36Sopenharmony_ci struct rpmsg_endpoint ept; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci struct qcom_smd_channel *qsch; 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci#define to_smd_device(r) container_of(r, struct qcom_smd_device, rpdev) 17362306a36Sopenharmony_ci#define to_smd_edge(d) container_of(d, struct qcom_smd_edge, dev) 17462306a36Sopenharmony_ci#define to_smd_endpoint(e) container_of(e, struct qcom_smd_endpoint, ept) 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci/** 17762306a36Sopenharmony_ci * struct qcom_smd_channel - smd channel struct 17862306a36Sopenharmony_ci * @edge: qcom_smd_edge this channel is living on 17962306a36Sopenharmony_ci * @qsept: reference to a associated smd endpoint 18062306a36Sopenharmony_ci * @registered: flag to indicate if the channel is registered 18162306a36Sopenharmony_ci * @name: name of the channel 18262306a36Sopenharmony_ci * @state: local state of the channel 18362306a36Sopenharmony_ci * @remote_state: remote state of the channel 18462306a36Sopenharmony_ci * @state_change_event: state change event 18562306a36Sopenharmony_ci * @info: byte aligned outgoing/incoming channel info 18662306a36Sopenharmony_ci * @info_word: word aligned outgoing/incoming channel info 18762306a36Sopenharmony_ci * @tx_lock: lock to make writes to the channel mutually exclusive 18862306a36Sopenharmony_ci * @fblockread_event: wakeup event tied to tx fBLOCKREADINTR 18962306a36Sopenharmony_ci * @tx_fifo: pointer to the outgoing ring buffer 19062306a36Sopenharmony_ci * @rx_fifo: pointer to the incoming ring buffer 19162306a36Sopenharmony_ci * @fifo_size: size of each ring buffer 19262306a36Sopenharmony_ci * @bounce_buffer: bounce buffer for reading wrapped packets 19362306a36Sopenharmony_ci * @cb: callback function registered for this channel 19462306a36Sopenharmony_ci * @recv_lock: guard for rx info modifications and cb pointer 19562306a36Sopenharmony_ci * @pkt_size: size of the currently handled packet 19662306a36Sopenharmony_ci * @drvdata: driver private data 19762306a36Sopenharmony_ci * @list: lite entry for @channels in qcom_smd_edge 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_cistruct qcom_smd_channel { 20062306a36Sopenharmony_ci struct qcom_smd_edge *edge; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept; 20362306a36Sopenharmony_ci bool registered; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci char *name; 20662306a36Sopenharmony_ci enum smd_channel_state state; 20762306a36Sopenharmony_ci enum smd_channel_state remote_state; 20862306a36Sopenharmony_ci wait_queue_head_t state_change_event; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci struct smd_channel_info_pair *info; 21162306a36Sopenharmony_ci struct smd_channel_info_word_pair *info_word; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci spinlock_t tx_lock; 21462306a36Sopenharmony_ci wait_queue_head_t fblockread_event; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci void *tx_fifo; 21762306a36Sopenharmony_ci void *rx_fifo; 21862306a36Sopenharmony_ci int fifo_size; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci void *bounce_buffer; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci spinlock_t recv_lock; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci int pkt_size; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci void *drvdata; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci struct list_head list; 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci/* 23262306a36Sopenharmony_ci * Format of the smd_info smem items, for byte aligned channels. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_cistruct smd_channel_info { 23562306a36Sopenharmony_ci __le32 state; 23662306a36Sopenharmony_ci u8 fDSR; 23762306a36Sopenharmony_ci u8 fCTS; 23862306a36Sopenharmony_ci u8 fCD; 23962306a36Sopenharmony_ci u8 fRI; 24062306a36Sopenharmony_ci u8 fHEAD; 24162306a36Sopenharmony_ci u8 fTAIL; 24262306a36Sopenharmony_ci u8 fSTATE; 24362306a36Sopenharmony_ci u8 fBLOCKREADINTR; 24462306a36Sopenharmony_ci __le32 tail; 24562306a36Sopenharmony_ci __le32 head; 24662306a36Sopenharmony_ci}; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistruct smd_channel_info_pair { 24962306a36Sopenharmony_ci struct smd_channel_info tx; 25062306a36Sopenharmony_ci struct smd_channel_info rx; 25162306a36Sopenharmony_ci}; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci/* 25462306a36Sopenharmony_ci * Format of the smd_info smem items, for word aligned channels. 25562306a36Sopenharmony_ci */ 25662306a36Sopenharmony_cistruct smd_channel_info_word { 25762306a36Sopenharmony_ci __le32 state; 25862306a36Sopenharmony_ci __le32 fDSR; 25962306a36Sopenharmony_ci __le32 fCTS; 26062306a36Sopenharmony_ci __le32 fCD; 26162306a36Sopenharmony_ci __le32 fRI; 26262306a36Sopenharmony_ci __le32 fHEAD; 26362306a36Sopenharmony_ci __le32 fTAIL; 26462306a36Sopenharmony_ci __le32 fSTATE; 26562306a36Sopenharmony_ci __le32 fBLOCKREADINTR; 26662306a36Sopenharmony_ci __le32 tail; 26762306a36Sopenharmony_ci __le32 head; 26862306a36Sopenharmony_ci}; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistruct smd_channel_info_word_pair { 27162306a36Sopenharmony_ci struct smd_channel_info_word tx; 27262306a36Sopenharmony_ci struct smd_channel_info_word rx; 27362306a36Sopenharmony_ci}; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci#define GET_RX_CHANNEL_FLAG(channel, param) \ 27662306a36Sopenharmony_ci ({ \ 27762306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 27862306a36Sopenharmony_ci channel->info_word ? \ 27962306a36Sopenharmony_ci le32_to_cpu(channel->info_word->rx.param) : \ 28062306a36Sopenharmony_ci channel->info->rx.param; \ 28162306a36Sopenharmony_ci }) 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci#define GET_RX_CHANNEL_INFO(channel, param) \ 28462306a36Sopenharmony_ci ({ \ 28562306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 28662306a36Sopenharmony_ci le32_to_cpu(channel->info_word ? \ 28762306a36Sopenharmony_ci channel->info_word->rx.param : \ 28862306a36Sopenharmony_ci channel->info->rx.param); \ 28962306a36Sopenharmony_ci }) 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci#define SET_RX_CHANNEL_FLAG(channel, param, value) \ 29262306a36Sopenharmony_ci ({ \ 29362306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ 29462306a36Sopenharmony_ci if (channel->info_word) \ 29562306a36Sopenharmony_ci channel->info_word->rx.param = cpu_to_le32(value); \ 29662306a36Sopenharmony_ci else \ 29762306a36Sopenharmony_ci channel->info->rx.param = value; \ 29862306a36Sopenharmony_ci }) 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci#define SET_RX_CHANNEL_INFO(channel, param, value) \ 30162306a36Sopenharmony_ci ({ \ 30262306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ 30362306a36Sopenharmony_ci if (channel->info_word) \ 30462306a36Sopenharmony_ci channel->info_word->rx.param = cpu_to_le32(value); \ 30562306a36Sopenharmony_ci else \ 30662306a36Sopenharmony_ci channel->info->rx.param = cpu_to_le32(value); \ 30762306a36Sopenharmony_ci }) 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci#define GET_TX_CHANNEL_FLAG(channel, param) \ 31062306a36Sopenharmony_ci ({ \ 31162306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 31262306a36Sopenharmony_ci channel->info_word ? \ 31362306a36Sopenharmony_ci le32_to_cpu(channel->info_word->tx.param) : \ 31462306a36Sopenharmony_ci channel->info->tx.param; \ 31562306a36Sopenharmony_ci }) 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci#define GET_TX_CHANNEL_INFO(channel, param) \ 31862306a36Sopenharmony_ci ({ \ 31962306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 32062306a36Sopenharmony_ci le32_to_cpu(channel->info_word ? \ 32162306a36Sopenharmony_ci channel->info_word->tx.param : \ 32262306a36Sopenharmony_ci channel->info->tx.param); \ 32362306a36Sopenharmony_ci }) 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci#define SET_TX_CHANNEL_FLAG(channel, param, value) \ 32662306a36Sopenharmony_ci ({ \ 32762306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ 32862306a36Sopenharmony_ci if (channel->info_word) \ 32962306a36Sopenharmony_ci channel->info_word->tx.param = cpu_to_le32(value); \ 33062306a36Sopenharmony_ci else \ 33162306a36Sopenharmony_ci channel->info->tx.param = value; \ 33262306a36Sopenharmony_ci }) 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci#define SET_TX_CHANNEL_INFO(channel, param, value) \ 33562306a36Sopenharmony_ci ({ \ 33662306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ 33762306a36Sopenharmony_ci if (channel->info_word) \ 33862306a36Sopenharmony_ci channel->info_word->tx.param = cpu_to_le32(value); \ 33962306a36Sopenharmony_ci else \ 34062306a36Sopenharmony_ci channel->info->tx.param = cpu_to_le32(value); \ 34162306a36Sopenharmony_ci }) 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci/** 34462306a36Sopenharmony_ci * struct qcom_smd_alloc_entry - channel allocation entry 34562306a36Sopenharmony_ci * @name: channel name 34662306a36Sopenharmony_ci * @cid: channel index 34762306a36Sopenharmony_ci * @flags: channel flags and edge id 34862306a36Sopenharmony_ci * @ref_count: reference count of the channel 34962306a36Sopenharmony_ci */ 35062306a36Sopenharmony_cistruct qcom_smd_alloc_entry { 35162306a36Sopenharmony_ci u8 name[20]; 35262306a36Sopenharmony_ci __le32 cid; 35362306a36Sopenharmony_ci __le32 flags; 35462306a36Sopenharmony_ci __le32 ref_count; 35562306a36Sopenharmony_ci} __packed; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci#define SMD_CHANNEL_FLAGS_EDGE_MASK 0xff 35862306a36Sopenharmony_ci#define SMD_CHANNEL_FLAGS_STREAM BIT(8) 35962306a36Sopenharmony_ci#define SMD_CHANNEL_FLAGS_PACKET BIT(9) 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci/* 36262306a36Sopenharmony_ci * Each smd packet contains a 20 byte header, with the first 4 being the length 36362306a36Sopenharmony_ci * of the packet. 36462306a36Sopenharmony_ci */ 36562306a36Sopenharmony_ci#define SMD_PACKET_HEADER_LEN 20 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci/* 36862306a36Sopenharmony_ci * Signal the remote processor associated with 'channel'. 36962306a36Sopenharmony_ci */ 37062306a36Sopenharmony_cistatic void qcom_smd_signal_channel(struct qcom_smd_channel *channel) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct qcom_smd_edge *edge = channel->edge; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (edge->mbox_chan) { 37562306a36Sopenharmony_ci /* 37662306a36Sopenharmony_ci * We can ignore a failing mbox_send_message() as the only 37762306a36Sopenharmony_ci * possible cause is that the FIFO in the framework is full of 37862306a36Sopenharmony_ci * other writes to the same bit. 37962306a36Sopenharmony_ci */ 38062306a36Sopenharmony_ci mbox_send_message(edge->mbox_chan, NULL); 38162306a36Sopenharmony_ci mbox_client_txdone(edge->mbox_chan, 0); 38262306a36Sopenharmony_ci } else { 38362306a36Sopenharmony_ci regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci} 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci/* 38862306a36Sopenharmony_ci * Initialize the tx channel info 38962306a36Sopenharmony_ci */ 39062306a36Sopenharmony_cistatic void qcom_smd_channel_reset(struct qcom_smd_channel *channel) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); 39362306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fDSR, 0); 39462306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fCTS, 0); 39562306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fCD, 0); 39662306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fRI, 0); 39762306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fHEAD, 0); 39862306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 39962306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 40062306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 40162306a36Sopenharmony_ci SET_TX_CHANNEL_INFO(channel, head, 0); 40262306a36Sopenharmony_ci SET_RX_CHANNEL_INFO(channel, tail, 0); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci qcom_smd_signal_channel(channel); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci channel->state = SMD_CHANNEL_CLOSED; 40762306a36Sopenharmony_ci channel->pkt_size = 0; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/* 41162306a36Sopenharmony_ci * Set the callback for a channel, with appropriate locking 41262306a36Sopenharmony_ci */ 41362306a36Sopenharmony_cistatic void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel, 41462306a36Sopenharmony_ci rpmsg_rx_cb_t cb) 41562306a36Sopenharmony_ci{ 41662306a36Sopenharmony_ci struct rpmsg_endpoint *ept = &channel->qsept->ept; 41762306a36Sopenharmony_ci unsigned long flags; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci spin_lock_irqsave(&channel->recv_lock, flags); 42062306a36Sopenharmony_ci ept->cb = cb; 42162306a36Sopenharmony_ci spin_unlock_irqrestore(&channel->recv_lock, flags); 42262306a36Sopenharmony_ci}; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci/* 42562306a36Sopenharmony_ci * Calculate the amount of data available in the rx fifo 42662306a36Sopenharmony_ci */ 42762306a36Sopenharmony_cistatic size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) 42862306a36Sopenharmony_ci{ 42962306a36Sopenharmony_ci unsigned head; 43062306a36Sopenharmony_ci unsigned tail; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci head = GET_RX_CHANNEL_INFO(channel, head); 43362306a36Sopenharmony_ci tail = GET_RX_CHANNEL_INFO(channel, tail); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci return (head - tail) & (channel->fifo_size - 1); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci/* 43962306a36Sopenharmony_ci * Set tx channel state and inform the remote processor 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_cistatic void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, 44262306a36Sopenharmony_ci int state) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct qcom_smd_edge *edge = channel->edge; 44562306a36Sopenharmony_ci bool is_open = state == SMD_CHANNEL_OPENED; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci if (channel->state == state) 44862306a36Sopenharmony_ci return; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fDSR, is_open); 45362306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fCTS, is_open); 45462306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fCD, is_open); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci SET_TX_CHANNEL_INFO(channel, state, state); 45762306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci channel->state = state; 46062306a36Sopenharmony_ci qcom_smd_signal_channel(channel); 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci/* 46462306a36Sopenharmony_ci * Copy count bytes of data using 32bit accesses, if that's required. 46562306a36Sopenharmony_ci */ 46662306a36Sopenharmony_cistatic void smd_copy_to_fifo(void __iomem *dst, 46762306a36Sopenharmony_ci const void *src, 46862306a36Sopenharmony_ci size_t count, 46962306a36Sopenharmony_ci bool word_aligned) 47062306a36Sopenharmony_ci{ 47162306a36Sopenharmony_ci if (word_aligned) { 47262306a36Sopenharmony_ci __iowrite32_copy(dst, src, count / sizeof(u32)); 47362306a36Sopenharmony_ci } else { 47462306a36Sopenharmony_ci memcpy_toio(dst, src, count); 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci/* 47962306a36Sopenharmony_ci * Copy count bytes of data using 32bit accesses, if that is required. 48062306a36Sopenharmony_ci */ 48162306a36Sopenharmony_cistatic void smd_copy_from_fifo(void *dst, 48262306a36Sopenharmony_ci const void __iomem *src, 48362306a36Sopenharmony_ci size_t count, 48462306a36Sopenharmony_ci bool word_aligned) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci if (word_aligned) { 48762306a36Sopenharmony_ci __ioread32_copy(dst, src, count / sizeof(u32)); 48862306a36Sopenharmony_ci } else { 48962306a36Sopenharmony_ci memcpy_fromio(dst, src, count); 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci/* 49462306a36Sopenharmony_ci * Read count bytes of data from the rx fifo into buf, but don't advance the 49562306a36Sopenharmony_ci * tail. 49662306a36Sopenharmony_ci */ 49762306a36Sopenharmony_cistatic size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel, 49862306a36Sopenharmony_ci void *buf, size_t count) 49962306a36Sopenharmony_ci{ 50062306a36Sopenharmony_ci bool word_aligned; 50162306a36Sopenharmony_ci unsigned tail; 50262306a36Sopenharmony_ci size_t len; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci word_aligned = channel->info_word; 50562306a36Sopenharmony_ci tail = GET_RX_CHANNEL_INFO(channel, tail); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci len = min_t(size_t, count, channel->fifo_size - tail); 50862306a36Sopenharmony_ci if (len) { 50962306a36Sopenharmony_ci smd_copy_from_fifo(buf, 51062306a36Sopenharmony_ci channel->rx_fifo + tail, 51162306a36Sopenharmony_ci len, 51262306a36Sopenharmony_ci word_aligned); 51362306a36Sopenharmony_ci } 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci if (len != count) { 51662306a36Sopenharmony_ci smd_copy_from_fifo(buf + len, 51762306a36Sopenharmony_ci channel->rx_fifo, 51862306a36Sopenharmony_ci count - len, 51962306a36Sopenharmony_ci word_aligned); 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci return count; 52362306a36Sopenharmony_ci} 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci/* 52662306a36Sopenharmony_ci * Advance the rx tail by count bytes. 52762306a36Sopenharmony_ci */ 52862306a36Sopenharmony_cistatic void qcom_smd_channel_advance(struct qcom_smd_channel *channel, 52962306a36Sopenharmony_ci size_t count) 53062306a36Sopenharmony_ci{ 53162306a36Sopenharmony_ci unsigned tail; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci tail = GET_RX_CHANNEL_INFO(channel, tail); 53462306a36Sopenharmony_ci tail += count; 53562306a36Sopenharmony_ci tail &= (channel->fifo_size - 1); 53662306a36Sopenharmony_ci SET_RX_CHANNEL_INFO(channel, tail, tail); 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci/* 54062306a36Sopenharmony_ci * Read out a single packet from the rx fifo and deliver it to the device 54162306a36Sopenharmony_ci */ 54262306a36Sopenharmony_cistatic int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) 54362306a36Sopenharmony_ci{ 54462306a36Sopenharmony_ci struct rpmsg_endpoint *ept = &channel->qsept->ept; 54562306a36Sopenharmony_ci unsigned tail; 54662306a36Sopenharmony_ci size_t len; 54762306a36Sopenharmony_ci void *ptr; 54862306a36Sopenharmony_ci int ret; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci tail = GET_RX_CHANNEL_INFO(channel, tail); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci /* Use bounce buffer if the data wraps */ 55362306a36Sopenharmony_ci if (tail + channel->pkt_size >= channel->fifo_size) { 55462306a36Sopenharmony_ci ptr = channel->bounce_buffer; 55562306a36Sopenharmony_ci len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size); 55662306a36Sopenharmony_ci } else { 55762306a36Sopenharmony_ci ptr = channel->rx_fifo + tail; 55862306a36Sopenharmony_ci len = channel->pkt_size; 55962306a36Sopenharmony_ci } 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY); 56262306a36Sopenharmony_ci if (ret < 0) 56362306a36Sopenharmony_ci return ret; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* Only forward the tail if the client consumed the data */ 56662306a36Sopenharmony_ci qcom_smd_channel_advance(channel, len); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci channel->pkt_size = 0; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci return 0; 57162306a36Sopenharmony_ci} 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci/* 57462306a36Sopenharmony_ci * Per channel interrupt handling 57562306a36Sopenharmony_ci */ 57662306a36Sopenharmony_cistatic bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) 57762306a36Sopenharmony_ci{ 57862306a36Sopenharmony_ci bool need_state_scan = false; 57962306a36Sopenharmony_ci int remote_state; 58062306a36Sopenharmony_ci __le32 pktlen; 58162306a36Sopenharmony_ci int avail; 58262306a36Sopenharmony_ci int ret; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci /* Handle state changes */ 58562306a36Sopenharmony_ci remote_state = GET_RX_CHANNEL_INFO(channel, state); 58662306a36Sopenharmony_ci if (remote_state != channel->remote_state) { 58762306a36Sopenharmony_ci channel->remote_state = remote_state; 58862306a36Sopenharmony_ci need_state_scan = true; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci wake_up_interruptible_all(&channel->state_change_event); 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci /* Indicate that we have seen any state change */ 59362306a36Sopenharmony_ci SET_RX_CHANNEL_FLAG(channel, fSTATE, 0); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci /* Signal waiting qcom_smd_send() about the interrupt */ 59662306a36Sopenharmony_ci if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) 59762306a36Sopenharmony_ci wake_up_interruptible_all(&channel->fblockread_event); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci /* Don't consume any data until we've opened the channel */ 60062306a36Sopenharmony_ci if (channel->state != SMD_CHANNEL_OPENED) 60162306a36Sopenharmony_ci goto out; 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci /* Indicate that we've seen the new data */ 60462306a36Sopenharmony_ci SET_RX_CHANNEL_FLAG(channel, fHEAD, 0); 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci /* Consume data */ 60762306a36Sopenharmony_ci for (;;) { 60862306a36Sopenharmony_ci avail = qcom_smd_channel_get_rx_avail(channel); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) { 61162306a36Sopenharmony_ci qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); 61262306a36Sopenharmony_ci qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); 61362306a36Sopenharmony_ci channel->pkt_size = le32_to_cpu(pktlen); 61462306a36Sopenharmony_ci } else if (channel->pkt_size && avail >= channel->pkt_size) { 61562306a36Sopenharmony_ci ret = qcom_smd_channel_recv_single(channel); 61662306a36Sopenharmony_ci if (ret) 61762306a36Sopenharmony_ci break; 61862306a36Sopenharmony_ci } else { 61962306a36Sopenharmony_ci break; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci /* Indicate that we have seen and updated tail */ 62462306a36Sopenharmony_ci SET_RX_CHANNEL_FLAG(channel, fTAIL, 1); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci /* Signal the remote that we've consumed the data (if requested) */ 62762306a36Sopenharmony_ci if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) { 62862306a36Sopenharmony_ci /* Ensure ordering of channel info updates */ 62962306a36Sopenharmony_ci wmb(); 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci qcom_smd_signal_channel(channel); 63262306a36Sopenharmony_ci } 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ciout: 63562306a36Sopenharmony_ci return need_state_scan; 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci/* 63962306a36Sopenharmony_ci * The edge interrupts are triggered by the remote processor on state changes, 64062306a36Sopenharmony_ci * channel info updates or when new channels are created. 64162306a36Sopenharmony_ci */ 64262306a36Sopenharmony_cistatic irqreturn_t qcom_smd_edge_intr(int irq, void *data) 64362306a36Sopenharmony_ci{ 64462306a36Sopenharmony_ci struct qcom_smd_edge *edge = data; 64562306a36Sopenharmony_ci struct qcom_smd_channel *channel; 64662306a36Sopenharmony_ci unsigned available; 64762306a36Sopenharmony_ci bool kick_scanner = false; 64862306a36Sopenharmony_ci bool kick_state = false; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci /* 65162306a36Sopenharmony_ci * Handle state changes or data on each of the channels on this edge 65262306a36Sopenharmony_ci */ 65362306a36Sopenharmony_ci spin_lock(&edge->channels_lock); 65462306a36Sopenharmony_ci list_for_each_entry(channel, &edge->channels, list) { 65562306a36Sopenharmony_ci spin_lock(&channel->recv_lock); 65662306a36Sopenharmony_ci kick_state |= qcom_smd_channel_intr(channel); 65762306a36Sopenharmony_ci spin_unlock(&channel->recv_lock); 65862306a36Sopenharmony_ci } 65962306a36Sopenharmony_ci spin_unlock(&edge->channels_lock); 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci /* 66262306a36Sopenharmony_ci * Creating a new channel requires allocating an smem entry, so we only 66362306a36Sopenharmony_ci * have to scan if the amount of available space in smem have changed 66462306a36Sopenharmony_ci * since last scan. 66562306a36Sopenharmony_ci */ 66662306a36Sopenharmony_ci available = qcom_smem_get_free_space(edge->remote_pid); 66762306a36Sopenharmony_ci if (available != edge->smem_available) { 66862306a36Sopenharmony_ci edge->smem_available = available; 66962306a36Sopenharmony_ci kick_scanner = true; 67062306a36Sopenharmony_ci } 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci if (kick_scanner) 67362306a36Sopenharmony_ci schedule_work(&edge->scan_work); 67462306a36Sopenharmony_ci if (kick_state) 67562306a36Sopenharmony_ci schedule_work(&edge->state_work); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci return IRQ_HANDLED; 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci/* 68162306a36Sopenharmony_ci * Calculate how much space is available in the tx fifo. 68262306a36Sopenharmony_ci */ 68362306a36Sopenharmony_cistatic size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel) 68462306a36Sopenharmony_ci{ 68562306a36Sopenharmony_ci unsigned head; 68662306a36Sopenharmony_ci unsigned tail; 68762306a36Sopenharmony_ci unsigned mask = channel->fifo_size - 1; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci head = GET_TX_CHANNEL_INFO(channel, head); 69062306a36Sopenharmony_ci tail = GET_TX_CHANNEL_INFO(channel, tail); 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci return mask - ((head - tail) & mask); 69362306a36Sopenharmony_ci} 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci/* 69662306a36Sopenharmony_ci * Write count bytes of data into channel, possibly wrapping in the ring buffer 69762306a36Sopenharmony_ci */ 69862306a36Sopenharmony_cistatic int qcom_smd_write_fifo(struct qcom_smd_channel *channel, 69962306a36Sopenharmony_ci const void *data, 70062306a36Sopenharmony_ci size_t count) 70162306a36Sopenharmony_ci{ 70262306a36Sopenharmony_ci bool word_aligned; 70362306a36Sopenharmony_ci unsigned head; 70462306a36Sopenharmony_ci size_t len; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci word_aligned = channel->info_word; 70762306a36Sopenharmony_ci head = GET_TX_CHANNEL_INFO(channel, head); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci len = min_t(size_t, count, channel->fifo_size - head); 71062306a36Sopenharmony_ci if (len) { 71162306a36Sopenharmony_ci smd_copy_to_fifo(channel->tx_fifo + head, 71262306a36Sopenharmony_ci data, 71362306a36Sopenharmony_ci len, 71462306a36Sopenharmony_ci word_aligned); 71562306a36Sopenharmony_ci } 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci if (len != count) { 71862306a36Sopenharmony_ci smd_copy_to_fifo(channel->tx_fifo, 71962306a36Sopenharmony_ci data + len, 72062306a36Sopenharmony_ci count - len, 72162306a36Sopenharmony_ci word_aligned); 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci head += count; 72562306a36Sopenharmony_ci head &= (channel->fifo_size - 1); 72662306a36Sopenharmony_ci SET_TX_CHANNEL_INFO(channel, head, head); 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci return count; 72962306a36Sopenharmony_ci} 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci/** 73262306a36Sopenharmony_ci * __qcom_smd_send - write data to smd channel 73362306a36Sopenharmony_ci * @channel: channel handle 73462306a36Sopenharmony_ci * @data: buffer of data to write 73562306a36Sopenharmony_ci * @len: number of bytes to write 73662306a36Sopenharmony_ci * @wait: flag to indicate if write can wait 73762306a36Sopenharmony_ci * 73862306a36Sopenharmony_ci * This is a blocking write of len bytes into the channel's tx ring buffer and 73962306a36Sopenharmony_ci * signal the remote end. It will sleep until there is enough space available 74062306a36Sopenharmony_ci * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid 74162306a36Sopenharmony_ci * polling. 74262306a36Sopenharmony_ci */ 74362306a36Sopenharmony_cistatic int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data, 74462306a36Sopenharmony_ci int len, bool wait) 74562306a36Sopenharmony_ci{ 74662306a36Sopenharmony_ci __le32 hdr[5] = { cpu_to_le32(len), }; 74762306a36Sopenharmony_ci int tlen = sizeof(hdr) + len; 74862306a36Sopenharmony_ci unsigned long flags; 74962306a36Sopenharmony_ci int ret; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci /* Word aligned channels only accept word size aligned data */ 75262306a36Sopenharmony_ci if (channel->info_word && len % 4) 75362306a36Sopenharmony_ci return -EINVAL; 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci /* Reject packets that are too big */ 75662306a36Sopenharmony_ci if (tlen >= channel->fifo_size) 75762306a36Sopenharmony_ci return -EINVAL; 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci /* Highlight the fact that if we enter the loop below we might sleep */ 76062306a36Sopenharmony_ci if (wait) 76162306a36Sopenharmony_ci might_sleep(); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci spin_lock_irqsave(&channel->tx_lock, flags); 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci while (qcom_smd_get_tx_avail(channel) < tlen && 76662306a36Sopenharmony_ci channel->state == SMD_CHANNEL_OPENED) { 76762306a36Sopenharmony_ci if (!wait) { 76862306a36Sopenharmony_ci ret = -EAGAIN; 76962306a36Sopenharmony_ci goto out_unlock; 77062306a36Sopenharmony_ci } 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0); 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci /* Wait without holding the tx_lock */ 77562306a36Sopenharmony_ci spin_unlock_irqrestore(&channel->tx_lock, flags); 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci ret = wait_event_interruptible(channel->fblockread_event, 77862306a36Sopenharmony_ci qcom_smd_get_tx_avail(channel) >= tlen || 77962306a36Sopenharmony_ci channel->state != SMD_CHANNEL_OPENED); 78062306a36Sopenharmony_ci if (ret) 78162306a36Sopenharmony_ci return ret; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci spin_lock_irqsave(&channel->tx_lock, flags); 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); 78662306a36Sopenharmony_ci } 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci /* Fail if the channel was closed */ 78962306a36Sopenharmony_ci if (channel->state != SMD_CHANNEL_OPENED) { 79062306a36Sopenharmony_ci ret = -EPIPE; 79162306a36Sopenharmony_ci goto out_unlock; 79262306a36Sopenharmony_ci } 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci qcom_smd_write_fifo(channel, hdr, sizeof(hdr)); 79762306a36Sopenharmony_ci qcom_smd_write_fifo(channel, data, len); 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci SET_TX_CHANNEL_FLAG(channel, fHEAD, 1); 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci /* Ensure ordering of channel info updates */ 80262306a36Sopenharmony_ci wmb(); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci qcom_smd_signal_channel(channel); 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ciout_unlock: 80762306a36Sopenharmony_ci spin_unlock_irqrestore(&channel->tx_lock, flags); 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci return ret; 81062306a36Sopenharmony_ci} 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci/* 81362306a36Sopenharmony_ci * Helper for opening a channel 81462306a36Sopenharmony_ci */ 81562306a36Sopenharmony_cistatic int qcom_smd_channel_open(struct qcom_smd_channel *channel, 81662306a36Sopenharmony_ci rpmsg_rx_cb_t cb) 81762306a36Sopenharmony_ci{ 81862306a36Sopenharmony_ci struct qcom_smd_edge *edge = channel->edge; 81962306a36Sopenharmony_ci size_t bb_size; 82062306a36Sopenharmony_ci int ret; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci /* 82362306a36Sopenharmony_ci * Packets are maximum 4k, but reduce if the fifo is smaller 82462306a36Sopenharmony_ci */ 82562306a36Sopenharmony_ci bb_size = min(channel->fifo_size, SZ_4K); 82662306a36Sopenharmony_ci channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL); 82762306a36Sopenharmony_ci if (!channel->bounce_buffer) 82862306a36Sopenharmony_ci return -ENOMEM; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci qcom_smd_channel_set_callback(channel, cb); 83162306a36Sopenharmony_ci qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING); 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci /* Wait for remote to enter opening or opened */ 83462306a36Sopenharmony_ci ret = wait_event_interruptible_timeout(channel->state_change_event, 83562306a36Sopenharmony_ci channel->remote_state == SMD_CHANNEL_OPENING || 83662306a36Sopenharmony_ci channel->remote_state == SMD_CHANNEL_OPENED, 83762306a36Sopenharmony_ci HZ); 83862306a36Sopenharmony_ci if (!ret) { 83962306a36Sopenharmony_ci dev_err(&edge->dev, "remote side did not enter opening state\n"); 84062306a36Sopenharmony_ci goto out_close_timeout; 84162306a36Sopenharmony_ci } 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED); 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci /* Wait for remote to enter opened */ 84662306a36Sopenharmony_ci ret = wait_event_interruptible_timeout(channel->state_change_event, 84762306a36Sopenharmony_ci channel->remote_state == SMD_CHANNEL_OPENED, 84862306a36Sopenharmony_ci HZ); 84962306a36Sopenharmony_ci if (!ret) { 85062306a36Sopenharmony_ci dev_err(&edge->dev, "remote side did not enter open state\n"); 85162306a36Sopenharmony_ci goto out_close_timeout; 85262306a36Sopenharmony_ci } 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci return 0; 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ciout_close_timeout: 85762306a36Sopenharmony_ci qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); 85862306a36Sopenharmony_ci return -ETIMEDOUT; 85962306a36Sopenharmony_ci} 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_ci/* 86262306a36Sopenharmony_ci * Helper for closing and resetting a channel 86362306a36Sopenharmony_ci */ 86462306a36Sopenharmony_cistatic void qcom_smd_channel_close(struct qcom_smd_channel *channel) 86562306a36Sopenharmony_ci{ 86662306a36Sopenharmony_ci qcom_smd_channel_set_callback(channel, NULL); 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci kfree(channel->bounce_buffer); 86962306a36Sopenharmony_ci channel->bounce_buffer = NULL; 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); 87262306a36Sopenharmony_ci qcom_smd_channel_reset(channel); 87362306a36Sopenharmony_ci} 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_cistatic struct qcom_smd_channel * 87662306a36Sopenharmony_ciqcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name) 87762306a36Sopenharmony_ci{ 87862306a36Sopenharmony_ci struct qcom_smd_channel *channel; 87962306a36Sopenharmony_ci struct qcom_smd_channel *ret = NULL; 88062306a36Sopenharmony_ci unsigned long flags; 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_ci spin_lock_irqsave(&edge->channels_lock, flags); 88362306a36Sopenharmony_ci list_for_each_entry(channel, &edge->channels, list) { 88462306a36Sopenharmony_ci if (!strcmp(channel->name, name)) { 88562306a36Sopenharmony_ci ret = channel; 88662306a36Sopenharmony_ci break; 88762306a36Sopenharmony_ci } 88862306a36Sopenharmony_ci } 88962306a36Sopenharmony_ci spin_unlock_irqrestore(&edge->channels_lock, flags); 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci return ret; 89262306a36Sopenharmony_ci} 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_cistatic void __ept_release(struct kref *kref) 89562306a36Sopenharmony_ci{ 89662306a36Sopenharmony_ci struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint, 89762306a36Sopenharmony_ci refcount); 89862306a36Sopenharmony_ci kfree(to_smd_endpoint(ept)); 89962306a36Sopenharmony_ci} 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_cistatic struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev, 90262306a36Sopenharmony_ci rpmsg_rx_cb_t cb, void *priv, 90362306a36Sopenharmony_ci struct rpmsg_channel_info chinfo) 90462306a36Sopenharmony_ci{ 90562306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept; 90662306a36Sopenharmony_ci struct qcom_smd_channel *channel; 90762306a36Sopenharmony_ci struct qcom_smd_device *qsdev = to_smd_device(rpdev); 90862306a36Sopenharmony_ci struct qcom_smd_edge *edge = qsdev->edge; 90962306a36Sopenharmony_ci struct rpmsg_endpoint *ept; 91062306a36Sopenharmony_ci const char *name = chinfo.name; 91162306a36Sopenharmony_ci int ret; 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci /* Wait up to HZ for the channel to appear */ 91462306a36Sopenharmony_ci ret = wait_event_interruptible_timeout(edge->new_channel_event, 91562306a36Sopenharmony_ci (channel = qcom_smd_find_channel(edge, name)) != NULL, 91662306a36Sopenharmony_ci HZ); 91762306a36Sopenharmony_ci if (!ret) 91862306a36Sopenharmony_ci return NULL; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci if (channel->state != SMD_CHANNEL_CLOSED) { 92162306a36Sopenharmony_ci dev_err(&rpdev->dev, "channel %s is busy\n", channel->name); 92262306a36Sopenharmony_ci return NULL; 92362306a36Sopenharmony_ci } 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci qsept = kzalloc(sizeof(*qsept), GFP_KERNEL); 92662306a36Sopenharmony_ci if (!qsept) 92762306a36Sopenharmony_ci return NULL; 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci ept = &qsept->ept; 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci kref_init(&ept->refcount); 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci ept->rpdev = rpdev; 93462306a36Sopenharmony_ci ept->cb = cb; 93562306a36Sopenharmony_ci ept->priv = priv; 93662306a36Sopenharmony_ci ept->ops = &qcom_smd_endpoint_ops; 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci channel->qsept = qsept; 93962306a36Sopenharmony_ci qsept->qsch = channel; 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci ret = qcom_smd_channel_open(channel, cb); 94262306a36Sopenharmony_ci if (ret) 94362306a36Sopenharmony_ci goto free_ept; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci return ept; 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_cifree_ept: 94862306a36Sopenharmony_ci channel->qsept = NULL; 94962306a36Sopenharmony_ci kref_put(&ept->refcount, __ept_release); 95062306a36Sopenharmony_ci return NULL; 95162306a36Sopenharmony_ci} 95262306a36Sopenharmony_ci 95362306a36Sopenharmony_cistatic void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept) 95462306a36Sopenharmony_ci{ 95562306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 95662306a36Sopenharmony_ci struct qcom_smd_channel *ch = qsept->qsch; 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_ci qcom_smd_channel_close(ch); 95962306a36Sopenharmony_ci ch->qsept = NULL; 96062306a36Sopenharmony_ci kref_put(&ept->refcount, __ept_release); 96162306a36Sopenharmony_ci} 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_cistatic int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len) 96462306a36Sopenharmony_ci{ 96562306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci return __qcom_smd_send(qsept->qsch, data, len, true); 96862306a36Sopenharmony_ci} 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_cistatic int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len) 97162306a36Sopenharmony_ci{ 97262306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_ci return __qcom_smd_send(qsept->qsch, data, len, false); 97562306a36Sopenharmony_ci} 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_cistatic int qcom_smd_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 97862306a36Sopenharmony_ci{ 97962306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci return __qcom_smd_send(qsept->qsch, data, len, true); 98262306a36Sopenharmony_ci} 98362306a36Sopenharmony_ci 98462306a36Sopenharmony_cistatic int qcom_smd_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 98562306a36Sopenharmony_ci{ 98662306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci return __qcom_smd_send(qsept->qsch, data, len, false); 98962306a36Sopenharmony_ci} 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_cistatic __poll_t qcom_smd_poll(struct rpmsg_endpoint *ept, 99262306a36Sopenharmony_ci struct file *filp, poll_table *wait) 99362306a36Sopenharmony_ci{ 99462306a36Sopenharmony_ci struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); 99562306a36Sopenharmony_ci struct qcom_smd_channel *channel = qsept->qsch; 99662306a36Sopenharmony_ci __poll_t mask = 0; 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci poll_wait(filp, &channel->fblockread_event, wait); 99962306a36Sopenharmony_ci 100062306a36Sopenharmony_ci if (qcom_smd_get_tx_avail(channel) > 20) 100162306a36Sopenharmony_ci mask |= EPOLLOUT | EPOLLWRNORM; 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ci return mask; 100462306a36Sopenharmony_ci} 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci/* 100762306a36Sopenharmony_ci * Finds the device_node for the smd child interested in this channel. 100862306a36Sopenharmony_ci */ 100962306a36Sopenharmony_cistatic struct device_node *qcom_smd_match_channel(struct device_node *edge_node, 101062306a36Sopenharmony_ci const char *channel) 101162306a36Sopenharmony_ci{ 101262306a36Sopenharmony_ci struct device_node *child; 101362306a36Sopenharmony_ci const char *name; 101462306a36Sopenharmony_ci const char *key; 101562306a36Sopenharmony_ci int ret; 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_ci for_each_available_child_of_node(edge_node, child) { 101862306a36Sopenharmony_ci key = "qcom,smd-channels"; 101962306a36Sopenharmony_ci ret = of_property_read_string(child, key, &name); 102062306a36Sopenharmony_ci if (ret) 102162306a36Sopenharmony_ci continue; 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci if (strcmp(name, channel) == 0) 102462306a36Sopenharmony_ci return child; 102562306a36Sopenharmony_ci } 102662306a36Sopenharmony_ci 102762306a36Sopenharmony_ci return NULL; 102862306a36Sopenharmony_ci} 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_cistatic int qcom_smd_announce_create(struct rpmsg_device *rpdev) 103162306a36Sopenharmony_ci{ 103262306a36Sopenharmony_ci struct qcom_smd_endpoint *qept = to_smd_endpoint(rpdev->ept); 103362306a36Sopenharmony_ci struct qcom_smd_channel *channel = qept->qsch; 103462306a36Sopenharmony_ci unsigned long flags; 103562306a36Sopenharmony_ci bool kick_state; 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci spin_lock_irqsave(&channel->recv_lock, flags); 103862306a36Sopenharmony_ci kick_state = qcom_smd_channel_intr(channel); 103962306a36Sopenharmony_ci spin_unlock_irqrestore(&channel->recv_lock, flags); 104062306a36Sopenharmony_ci 104162306a36Sopenharmony_ci if (kick_state) 104262306a36Sopenharmony_ci schedule_work(&channel->edge->state_work); 104362306a36Sopenharmony_ci 104462306a36Sopenharmony_ci return 0; 104562306a36Sopenharmony_ci} 104662306a36Sopenharmony_ci 104762306a36Sopenharmony_cistatic const struct rpmsg_device_ops qcom_smd_device_ops = { 104862306a36Sopenharmony_ci .create_ept = qcom_smd_create_ept, 104962306a36Sopenharmony_ci .announce_create = qcom_smd_announce_create, 105062306a36Sopenharmony_ci}; 105162306a36Sopenharmony_ci 105262306a36Sopenharmony_cistatic const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = { 105362306a36Sopenharmony_ci .destroy_ept = qcom_smd_destroy_ept, 105462306a36Sopenharmony_ci .send = qcom_smd_send, 105562306a36Sopenharmony_ci .sendto = qcom_smd_sendto, 105662306a36Sopenharmony_ci .trysend = qcom_smd_trysend, 105762306a36Sopenharmony_ci .trysendto = qcom_smd_trysendto, 105862306a36Sopenharmony_ci .poll = qcom_smd_poll, 105962306a36Sopenharmony_ci}; 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_cistatic void qcom_smd_release_device(struct device *dev) 106262306a36Sopenharmony_ci{ 106362306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 106462306a36Sopenharmony_ci struct qcom_smd_device *qsdev = to_smd_device(rpdev); 106562306a36Sopenharmony_ci 106662306a36Sopenharmony_ci kfree(qsdev); 106762306a36Sopenharmony_ci} 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci/* 107062306a36Sopenharmony_ci * Create a smd client device for channel that is being opened. 107162306a36Sopenharmony_ci */ 107262306a36Sopenharmony_cistatic int qcom_smd_create_device(struct qcom_smd_channel *channel) 107362306a36Sopenharmony_ci{ 107462306a36Sopenharmony_ci struct qcom_smd_device *qsdev; 107562306a36Sopenharmony_ci struct rpmsg_device *rpdev; 107662306a36Sopenharmony_ci struct qcom_smd_edge *edge = channel->edge; 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ci dev_dbg(&edge->dev, "registering '%s'\n", channel->name); 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); 108162306a36Sopenharmony_ci if (!qsdev) 108262306a36Sopenharmony_ci return -ENOMEM; 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci /* Link qsdev to our SMD edge */ 108562306a36Sopenharmony_ci qsdev->edge = edge; 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci /* Assign callbacks for rpmsg_device */ 108862306a36Sopenharmony_ci qsdev->rpdev.ops = &qcom_smd_device_ops; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci /* Assign public information to the rpmsg_device */ 109162306a36Sopenharmony_ci rpdev = &qsdev->rpdev; 109262306a36Sopenharmony_ci strscpy_pad(rpdev->id.name, channel->name, RPMSG_NAME_SIZE); 109362306a36Sopenharmony_ci rpdev->src = RPMSG_ADDR_ANY; 109462306a36Sopenharmony_ci rpdev->dst = RPMSG_ADDR_ANY; 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_ci rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name); 109762306a36Sopenharmony_ci rpdev->dev.parent = &edge->dev; 109862306a36Sopenharmony_ci rpdev->dev.release = qcom_smd_release_device; 109962306a36Sopenharmony_ci 110062306a36Sopenharmony_ci return rpmsg_register_device(rpdev); 110162306a36Sopenharmony_ci} 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_cistatic int qcom_smd_create_chrdev(struct qcom_smd_edge *edge) 110462306a36Sopenharmony_ci{ 110562306a36Sopenharmony_ci struct qcom_smd_device *qsdev; 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_ci qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); 110862306a36Sopenharmony_ci if (!qsdev) 110962306a36Sopenharmony_ci return -ENOMEM; 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_ci qsdev->edge = edge; 111262306a36Sopenharmony_ci qsdev->rpdev.ops = &qcom_smd_device_ops; 111362306a36Sopenharmony_ci qsdev->rpdev.dev.parent = &edge->dev; 111462306a36Sopenharmony_ci qsdev->rpdev.dev.release = qcom_smd_release_device; 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci return rpmsg_ctrldev_register_device(&qsdev->rpdev); 111762306a36Sopenharmony_ci} 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_ci/* 112062306a36Sopenharmony_ci * Allocate the qcom_smd_channel object for a newly found smd channel, 112162306a36Sopenharmony_ci * retrieving and validating the smem items involved. 112262306a36Sopenharmony_ci */ 112362306a36Sopenharmony_cistatic struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge, 112462306a36Sopenharmony_ci unsigned smem_info_item, 112562306a36Sopenharmony_ci unsigned smem_fifo_item, 112662306a36Sopenharmony_ci char *name) 112762306a36Sopenharmony_ci{ 112862306a36Sopenharmony_ci struct qcom_smd_channel *channel; 112962306a36Sopenharmony_ci size_t fifo_size; 113062306a36Sopenharmony_ci size_t info_size; 113162306a36Sopenharmony_ci void *fifo_base; 113262306a36Sopenharmony_ci void *info; 113362306a36Sopenharmony_ci int ret; 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ci channel = kzalloc(sizeof(*channel), GFP_KERNEL); 113662306a36Sopenharmony_ci if (!channel) 113762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_ci channel->edge = edge; 114062306a36Sopenharmony_ci channel->name = kstrdup(name, GFP_KERNEL); 114162306a36Sopenharmony_ci if (!channel->name) { 114262306a36Sopenharmony_ci ret = -ENOMEM; 114362306a36Sopenharmony_ci goto free_channel; 114462306a36Sopenharmony_ci } 114562306a36Sopenharmony_ci 114662306a36Sopenharmony_ci spin_lock_init(&channel->tx_lock); 114762306a36Sopenharmony_ci spin_lock_init(&channel->recv_lock); 114862306a36Sopenharmony_ci init_waitqueue_head(&channel->fblockread_event); 114962306a36Sopenharmony_ci init_waitqueue_head(&channel->state_change_event); 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size); 115262306a36Sopenharmony_ci if (IS_ERR(info)) { 115362306a36Sopenharmony_ci ret = PTR_ERR(info); 115462306a36Sopenharmony_ci goto free_name_and_channel; 115562306a36Sopenharmony_ci } 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci /* 115862306a36Sopenharmony_ci * Use the size of the item to figure out which channel info struct to 115962306a36Sopenharmony_ci * use. 116062306a36Sopenharmony_ci */ 116162306a36Sopenharmony_ci if (info_size == 2 * sizeof(struct smd_channel_info_word)) { 116262306a36Sopenharmony_ci channel->info_word = info; 116362306a36Sopenharmony_ci } else if (info_size == 2 * sizeof(struct smd_channel_info)) { 116462306a36Sopenharmony_ci channel->info = info; 116562306a36Sopenharmony_ci } else { 116662306a36Sopenharmony_ci dev_err(&edge->dev, 116762306a36Sopenharmony_ci "channel info of size %zu not supported\n", info_size); 116862306a36Sopenharmony_ci ret = -EINVAL; 116962306a36Sopenharmony_ci goto free_name_and_channel; 117062306a36Sopenharmony_ci } 117162306a36Sopenharmony_ci 117262306a36Sopenharmony_ci fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size); 117362306a36Sopenharmony_ci if (IS_ERR(fifo_base)) { 117462306a36Sopenharmony_ci ret = PTR_ERR(fifo_base); 117562306a36Sopenharmony_ci goto free_name_and_channel; 117662306a36Sopenharmony_ci } 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci /* The channel consist of a rx and tx fifo of equal size */ 117962306a36Sopenharmony_ci fifo_size /= 2; 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n", 118262306a36Sopenharmony_ci name, info_size, fifo_size); 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci channel->tx_fifo = fifo_base; 118562306a36Sopenharmony_ci channel->rx_fifo = fifo_base + fifo_size; 118662306a36Sopenharmony_ci channel->fifo_size = fifo_size; 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_ci qcom_smd_channel_reset(channel); 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci return channel; 119162306a36Sopenharmony_ci 119262306a36Sopenharmony_cifree_name_and_channel: 119362306a36Sopenharmony_ci kfree(channel->name); 119462306a36Sopenharmony_cifree_channel: 119562306a36Sopenharmony_ci kfree(channel); 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_ci return ERR_PTR(ret); 119862306a36Sopenharmony_ci} 119962306a36Sopenharmony_ci 120062306a36Sopenharmony_ci/* 120162306a36Sopenharmony_ci * Scans the allocation table for any newly allocated channels, calls 120262306a36Sopenharmony_ci * qcom_smd_create_channel() to create representations of these and add 120362306a36Sopenharmony_ci * them to the edge's list of channels. 120462306a36Sopenharmony_ci */ 120562306a36Sopenharmony_cistatic void qcom_channel_scan_worker(struct work_struct *work) 120662306a36Sopenharmony_ci{ 120762306a36Sopenharmony_ci struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work); 120862306a36Sopenharmony_ci struct qcom_smd_alloc_entry *alloc_tbl; 120962306a36Sopenharmony_ci struct qcom_smd_alloc_entry *entry; 121062306a36Sopenharmony_ci struct qcom_smd_channel *channel; 121162306a36Sopenharmony_ci unsigned long flags; 121262306a36Sopenharmony_ci unsigned fifo_id; 121362306a36Sopenharmony_ci unsigned info_id; 121462306a36Sopenharmony_ci int tbl; 121562306a36Sopenharmony_ci int i; 121662306a36Sopenharmony_ci u32 eflags, cid; 121762306a36Sopenharmony_ci 121862306a36Sopenharmony_ci for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) { 121962306a36Sopenharmony_ci alloc_tbl = qcom_smem_get(edge->remote_pid, 122062306a36Sopenharmony_ci smem_items[tbl].alloc_tbl_id, NULL); 122162306a36Sopenharmony_ci if (IS_ERR(alloc_tbl)) 122262306a36Sopenharmony_ci continue; 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_ci for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) { 122562306a36Sopenharmony_ci entry = &alloc_tbl[i]; 122662306a36Sopenharmony_ci eflags = le32_to_cpu(entry->flags); 122762306a36Sopenharmony_ci if (test_bit(i, edge->allocated[tbl])) 122862306a36Sopenharmony_ci continue; 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci if (entry->ref_count == 0) 123162306a36Sopenharmony_ci continue; 123262306a36Sopenharmony_ci 123362306a36Sopenharmony_ci if (!entry->name[0]) 123462306a36Sopenharmony_ci continue; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci if (!(eflags & SMD_CHANNEL_FLAGS_PACKET)) 123762306a36Sopenharmony_ci continue; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) 124062306a36Sopenharmony_ci continue; 124162306a36Sopenharmony_ci 124262306a36Sopenharmony_ci cid = le32_to_cpu(entry->cid); 124362306a36Sopenharmony_ci info_id = smem_items[tbl].info_base_id + cid; 124462306a36Sopenharmony_ci fifo_id = smem_items[tbl].fifo_base_id + cid; 124562306a36Sopenharmony_ci 124662306a36Sopenharmony_ci channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name); 124762306a36Sopenharmony_ci if (IS_ERR(channel)) 124862306a36Sopenharmony_ci continue; 124962306a36Sopenharmony_ci 125062306a36Sopenharmony_ci spin_lock_irqsave(&edge->channels_lock, flags); 125162306a36Sopenharmony_ci list_add(&channel->list, &edge->channels); 125262306a36Sopenharmony_ci spin_unlock_irqrestore(&edge->channels_lock, flags); 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name); 125562306a36Sopenharmony_ci set_bit(i, edge->allocated[tbl]); 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci wake_up_interruptible_all(&edge->new_channel_event); 125862306a36Sopenharmony_ci } 125962306a36Sopenharmony_ci } 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_ci schedule_work(&edge->state_work); 126262306a36Sopenharmony_ci} 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci/* 126562306a36Sopenharmony_ci * This per edge worker scans smem for any new channels and register these. It 126662306a36Sopenharmony_ci * then scans all registered channels for state changes that should be handled 126762306a36Sopenharmony_ci * by creating or destroying smd client devices for the registered channels. 126862306a36Sopenharmony_ci * 126962306a36Sopenharmony_ci * LOCKING: edge->channels_lock only needs to cover the list operations, as the 127062306a36Sopenharmony_ci * worker is killed before any channels are deallocated 127162306a36Sopenharmony_ci */ 127262306a36Sopenharmony_cistatic void qcom_channel_state_worker(struct work_struct *work) 127362306a36Sopenharmony_ci{ 127462306a36Sopenharmony_ci struct qcom_smd_channel *channel; 127562306a36Sopenharmony_ci struct qcom_smd_edge *edge = container_of(work, 127662306a36Sopenharmony_ci struct qcom_smd_edge, 127762306a36Sopenharmony_ci state_work); 127862306a36Sopenharmony_ci struct rpmsg_channel_info chinfo; 127962306a36Sopenharmony_ci unsigned remote_state; 128062306a36Sopenharmony_ci unsigned long flags; 128162306a36Sopenharmony_ci 128262306a36Sopenharmony_ci /* 128362306a36Sopenharmony_ci * Register a device for any closed channel where the remote processor 128462306a36Sopenharmony_ci * is showing interest in opening the channel. 128562306a36Sopenharmony_ci */ 128662306a36Sopenharmony_ci spin_lock_irqsave(&edge->channels_lock, flags); 128762306a36Sopenharmony_ci list_for_each_entry(channel, &edge->channels, list) { 128862306a36Sopenharmony_ci if (channel->state != SMD_CHANNEL_CLOSED) 128962306a36Sopenharmony_ci continue; 129062306a36Sopenharmony_ci 129162306a36Sopenharmony_ci /* 129262306a36Sopenharmony_ci * Always open rpm_requests, even when already opened which is 129362306a36Sopenharmony_ci * required on some SoCs like msm8953. 129462306a36Sopenharmony_ci */ 129562306a36Sopenharmony_ci remote_state = GET_RX_CHANNEL_INFO(channel, state); 129662306a36Sopenharmony_ci if (remote_state != SMD_CHANNEL_OPENING && 129762306a36Sopenharmony_ci remote_state != SMD_CHANNEL_OPENED && 129862306a36Sopenharmony_ci strcmp(channel->name, "rpm_requests")) 129962306a36Sopenharmony_ci continue; 130062306a36Sopenharmony_ci 130162306a36Sopenharmony_ci if (channel->registered) 130262306a36Sopenharmony_ci continue; 130362306a36Sopenharmony_ci 130462306a36Sopenharmony_ci spin_unlock_irqrestore(&edge->channels_lock, flags); 130562306a36Sopenharmony_ci qcom_smd_create_device(channel); 130662306a36Sopenharmony_ci spin_lock_irqsave(&edge->channels_lock, flags); 130762306a36Sopenharmony_ci channel->registered = true; 130862306a36Sopenharmony_ci } 130962306a36Sopenharmony_ci 131062306a36Sopenharmony_ci /* 131162306a36Sopenharmony_ci * Unregister the device for any channel that is opened where the 131262306a36Sopenharmony_ci * remote processor is closing the channel. 131362306a36Sopenharmony_ci */ 131462306a36Sopenharmony_ci list_for_each_entry(channel, &edge->channels, list) { 131562306a36Sopenharmony_ci if (channel->state != SMD_CHANNEL_OPENING && 131662306a36Sopenharmony_ci channel->state != SMD_CHANNEL_OPENED) 131762306a36Sopenharmony_ci continue; 131862306a36Sopenharmony_ci 131962306a36Sopenharmony_ci remote_state = GET_RX_CHANNEL_INFO(channel, state); 132062306a36Sopenharmony_ci if (remote_state == SMD_CHANNEL_OPENING || 132162306a36Sopenharmony_ci remote_state == SMD_CHANNEL_OPENED) 132262306a36Sopenharmony_ci continue; 132362306a36Sopenharmony_ci 132462306a36Sopenharmony_ci spin_unlock_irqrestore(&edge->channels_lock, flags); 132562306a36Sopenharmony_ci 132662306a36Sopenharmony_ci strscpy_pad(chinfo.name, channel->name, sizeof(chinfo.name)); 132762306a36Sopenharmony_ci chinfo.src = RPMSG_ADDR_ANY; 132862306a36Sopenharmony_ci chinfo.dst = RPMSG_ADDR_ANY; 132962306a36Sopenharmony_ci rpmsg_unregister_device(&edge->dev, &chinfo); 133062306a36Sopenharmony_ci channel->registered = false; 133162306a36Sopenharmony_ci spin_lock_irqsave(&edge->channels_lock, flags); 133262306a36Sopenharmony_ci } 133362306a36Sopenharmony_ci spin_unlock_irqrestore(&edge->channels_lock, flags); 133462306a36Sopenharmony_ci} 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci/* 133762306a36Sopenharmony_ci * Parses an of_node describing an edge. 133862306a36Sopenharmony_ci */ 133962306a36Sopenharmony_cistatic int qcom_smd_parse_edge(struct device *dev, 134062306a36Sopenharmony_ci struct device_node *node, 134162306a36Sopenharmony_ci struct qcom_smd_edge *edge) 134262306a36Sopenharmony_ci{ 134362306a36Sopenharmony_ci struct device_node *syscon_np; 134462306a36Sopenharmony_ci const char *key; 134562306a36Sopenharmony_ci int irq; 134662306a36Sopenharmony_ci int ret; 134762306a36Sopenharmony_ci 134862306a36Sopenharmony_ci INIT_LIST_HEAD(&edge->channels); 134962306a36Sopenharmony_ci spin_lock_init(&edge->channels_lock); 135062306a36Sopenharmony_ci 135162306a36Sopenharmony_ci INIT_WORK(&edge->scan_work, qcom_channel_scan_worker); 135262306a36Sopenharmony_ci INIT_WORK(&edge->state_work, qcom_channel_state_worker); 135362306a36Sopenharmony_ci 135462306a36Sopenharmony_ci edge->of_node = of_node_get(node); 135562306a36Sopenharmony_ci 135662306a36Sopenharmony_ci key = "qcom,smd-edge"; 135762306a36Sopenharmony_ci ret = of_property_read_u32(node, key, &edge->edge_id); 135862306a36Sopenharmony_ci if (ret) { 135962306a36Sopenharmony_ci dev_err(dev, "edge missing %s property\n", key); 136062306a36Sopenharmony_ci goto put_node; 136162306a36Sopenharmony_ci } 136262306a36Sopenharmony_ci 136362306a36Sopenharmony_ci edge->remote_pid = QCOM_SMEM_HOST_ANY; 136462306a36Sopenharmony_ci key = "qcom,remote-pid"; 136562306a36Sopenharmony_ci of_property_read_u32(node, key, &edge->remote_pid); 136662306a36Sopenharmony_ci 136762306a36Sopenharmony_ci edge->mbox_client.dev = dev; 136862306a36Sopenharmony_ci edge->mbox_client.knows_txdone = true; 136962306a36Sopenharmony_ci edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0); 137062306a36Sopenharmony_ci if (IS_ERR(edge->mbox_chan)) { 137162306a36Sopenharmony_ci if (PTR_ERR(edge->mbox_chan) != -ENODEV) { 137262306a36Sopenharmony_ci ret = PTR_ERR(edge->mbox_chan); 137362306a36Sopenharmony_ci goto put_node; 137462306a36Sopenharmony_ci } 137562306a36Sopenharmony_ci 137662306a36Sopenharmony_ci edge->mbox_chan = NULL; 137762306a36Sopenharmony_ci 137862306a36Sopenharmony_ci syscon_np = of_parse_phandle(node, "qcom,ipc", 0); 137962306a36Sopenharmony_ci if (!syscon_np) { 138062306a36Sopenharmony_ci dev_err(dev, "no qcom,ipc node\n"); 138162306a36Sopenharmony_ci ret = -ENODEV; 138262306a36Sopenharmony_ci goto put_node; 138362306a36Sopenharmony_ci } 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci edge->ipc_regmap = syscon_node_to_regmap(syscon_np); 138662306a36Sopenharmony_ci of_node_put(syscon_np); 138762306a36Sopenharmony_ci if (IS_ERR(edge->ipc_regmap)) { 138862306a36Sopenharmony_ci ret = PTR_ERR(edge->ipc_regmap); 138962306a36Sopenharmony_ci goto put_node; 139062306a36Sopenharmony_ci } 139162306a36Sopenharmony_ci 139262306a36Sopenharmony_ci key = "qcom,ipc"; 139362306a36Sopenharmony_ci ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); 139462306a36Sopenharmony_ci if (ret < 0) { 139562306a36Sopenharmony_ci dev_err(dev, "no offset in %s\n", key); 139662306a36Sopenharmony_ci goto put_node; 139762306a36Sopenharmony_ci } 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); 140062306a36Sopenharmony_ci if (ret < 0) { 140162306a36Sopenharmony_ci dev_err(dev, "no bit in %s\n", key); 140262306a36Sopenharmony_ci goto put_node; 140362306a36Sopenharmony_ci } 140462306a36Sopenharmony_ci } 140562306a36Sopenharmony_ci 140662306a36Sopenharmony_ci ret = of_property_read_string(node, "label", &edge->name); 140762306a36Sopenharmony_ci if (ret < 0) 140862306a36Sopenharmony_ci edge->name = node->name; 140962306a36Sopenharmony_ci 141062306a36Sopenharmony_ci irq = irq_of_parse_and_map(node, 0); 141162306a36Sopenharmony_ci if (!irq) { 141262306a36Sopenharmony_ci dev_err(dev, "required smd interrupt missing\n"); 141362306a36Sopenharmony_ci ret = -EINVAL; 141462306a36Sopenharmony_ci goto put_node; 141562306a36Sopenharmony_ci } 141662306a36Sopenharmony_ci 141762306a36Sopenharmony_ci ret = devm_request_irq(dev, irq, 141862306a36Sopenharmony_ci qcom_smd_edge_intr, IRQF_TRIGGER_RISING, 141962306a36Sopenharmony_ci node->name, edge); 142062306a36Sopenharmony_ci if (ret) { 142162306a36Sopenharmony_ci dev_err(dev, "failed to request smd irq\n"); 142262306a36Sopenharmony_ci goto put_node; 142362306a36Sopenharmony_ci } 142462306a36Sopenharmony_ci 142562306a36Sopenharmony_ci edge->irq = irq; 142662306a36Sopenharmony_ci 142762306a36Sopenharmony_ci return 0; 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_ciput_node: 143062306a36Sopenharmony_ci of_node_put(node); 143162306a36Sopenharmony_ci edge->of_node = NULL; 143262306a36Sopenharmony_ci 143362306a36Sopenharmony_ci return ret; 143462306a36Sopenharmony_ci} 143562306a36Sopenharmony_ci 143662306a36Sopenharmony_ci/* 143762306a36Sopenharmony_ci * Release function for an edge. 143862306a36Sopenharmony_ci * Reset the state of each associated channel and free the edge context. 143962306a36Sopenharmony_ci */ 144062306a36Sopenharmony_cistatic void qcom_smd_edge_release(struct device *dev) 144162306a36Sopenharmony_ci{ 144262306a36Sopenharmony_ci struct qcom_smd_channel *channel, *tmp; 144362306a36Sopenharmony_ci struct qcom_smd_edge *edge = to_smd_edge(dev); 144462306a36Sopenharmony_ci 144562306a36Sopenharmony_ci list_for_each_entry_safe(channel, tmp, &edge->channels, list) { 144662306a36Sopenharmony_ci list_del(&channel->list); 144762306a36Sopenharmony_ci kfree(channel->name); 144862306a36Sopenharmony_ci kfree(channel); 144962306a36Sopenharmony_ci } 145062306a36Sopenharmony_ci 145162306a36Sopenharmony_ci kfree(edge); 145262306a36Sopenharmony_ci} 145362306a36Sopenharmony_ci 145462306a36Sopenharmony_cistatic ssize_t rpmsg_name_show(struct device *dev, 145562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 145662306a36Sopenharmony_ci{ 145762306a36Sopenharmony_ci struct qcom_smd_edge *edge = to_smd_edge(dev); 145862306a36Sopenharmony_ci 145962306a36Sopenharmony_ci return sprintf(buf, "%s\n", edge->name); 146062306a36Sopenharmony_ci} 146162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(rpmsg_name); 146262306a36Sopenharmony_ci 146362306a36Sopenharmony_cistatic struct attribute *qcom_smd_edge_attrs[] = { 146462306a36Sopenharmony_ci &dev_attr_rpmsg_name.attr, 146562306a36Sopenharmony_ci NULL 146662306a36Sopenharmony_ci}; 146762306a36Sopenharmony_ciATTRIBUTE_GROUPS(qcom_smd_edge); 146862306a36Sopenharmony_ci 146962306a36Sopenharmony_ci/** 147062306a36Sopenharmony_ci * qcom_smd_register_edge() - register an edge based on an device_node 147162306a36Sopenharmony_ci * @parent: parent device for the edge 147262306a36Sopenharmony_ci * @node: device_node describing the edge 147362306a36Sopenharmony_ci * 147462306a36Sopenharmony_ci * Return: an edge reference, or negative ERR_PTR() on failure. 147562306a36Sopenharmony_ci */ 147662306a36Sopenharmony_cistruct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, 147762306a36Sopenharmony_ci struct device_node *node) 147862306a36Sopenharmony_ci{ 147962306a36Sopenharmony_ci struct qcom_smd_edge *edge; 148062306a36Sopenharmony_ci int ret; 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_ci if (!qcom_smem_is_available()) 148362306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 148462306a36Sopenharmony_ci 148562306a36Sopenharmony_ci edge = kzalloc(sizeof(*edge), GFP_KERNEL); 148662306a36Sopenharmony_ci if (!edge) 148762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 148862306a36Sopenharmony_ci 148962306a36Sopenharmony_ci init_waitqueue_head(&edge->new_channel_event); 149062306a36Sopenharmony_ci 149162306a36Sopenharmony_ci edge->dev.parent = parent; 149262306a36Sopenharmony_ci edge->dev.release = qcom_smd_edge_release; 149362306a36Sopenharmony_ci edge->dev.of_node = node; 149462306a36Sopenharmony_ci edge->dev.groups = qcom_smd_edge_groups; 149562306a36Sopenharmony_ci dev_set_name(&edge->dev, "%s:%pOFn", dev_name(parent), node); 149662306a36Sopenharmony_ci ret = device_register(&edge->dev); 149762306a36Sopenharmony_ci if (ret) { 149862306a36Sopenharmony_ci pr_err("failed to register smd edge\n"); 149962306a36Sopenharmony_ci put_device(&edge->dev); 150062306a36Sopenharmony_ci return ERR_PTR(ret); 150162306a36Sopenharmony_ci } 150262306a36Sopenharmony_ci 150362306a36Sopenharmony_ci ret = qcom_smd_parse_edge(&edge->dev, node, edge); 150462306a36Sopenharmony_ci if (ret) { 150562306a36Sopenharmony_ci dev_err(&edge->dev, "failed to parse smd edge\n"); 150662306a36Sopenharmony_ci goto unregister_dev; 150762306a36Sopenharmony_ci } 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci ret = qcom_smd_create_chrdev(edge); 151062306a36Sopenharmony_ci if (ret) { 151162306a36Sopenharmony_ci dev_err(&edge->dev, "failed to register chrdev for edge\n"); 151262306a36Sopenharmony_ci goto unregister_dev; 151362306a36Sopenharmony_ci } 151462306a36Sopenharmony_ci 151562306a36Sopenharmony_ci schedule_work(&edge->scan_work); 151662306a36Sopenharmony_ci 151762306a36Sopenharmony_ci return edge; 151862306a36Sopenharmony_ci 151962306a36Sopenharmony_ciunregister_dev: 152062306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(edge->mbox_chan)) 152162306a36Sopenharmony_ci mbox_free_channel(edge->mbox_chan); 152262306a36Sopenharmony_ci 152362306a36Sopenharmony_ci device_unregister(&edge->dev); 152462306a36Sopenharmony_ci return ERR_PTR(ret); 152562306a36Sopenharmony_ci} 152662306a36Sopenharmony_ciEXPORT_SYMBOL(qcom_smd_register_edge); 152762306a36Sopenharmony_ci 152862306a36Sopenharmony_cistatic int qcom_smd_remove_device(struct device *dev, void *data) 152962306a36Sopenharmony_ci{ 153062306a36Sopenharmony_ci device_unregister(dev); 153162306a36Sopenharmony_ci 153262306a36Sopenharmony_ci return 0; 153362306a36Sopenharmony_ci} 153462306a36Sopenharmony_ci 153562306a36Sopenharmony_ci/** 153662306a36Sopenharmony_ci * qcom_smd_unregister_edge() - release an edge and its children 153762306a36Sopenharmony_ci * @edge: edge reference acquired from qcom_smd_register_edge 153862306a36Sopenharmony_ci */ 153962306a36Sopenharmony_civoid qcom_smd_unregister_edge(struct qcom_smd_edge *edge) 154062306a36Sopenharmony_ci{ 154162306a36Sopenharmony_ci int ret; 154262306a36Sopenharmony_ci 154362306a36Sopenharmony_ci disable_irq(edge->irq); 154462306a36Sopenharmony_ci cancel_work_sync(&edge->scan_work); 154562306a36Sopenharmony_ci cancel_work_sync(&edge->state_work); 154662306a36Sopenharmony_ci 154762306a36Sopenharmony_ci ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device); 154862306a36Sopenharmony_ci if (ret) 154962306a36Sopenharmony_ci dev_warn(&edge->dev, "can't remove smd device: %d\n", ret); 155062306a36Sopenharmony_ci 155162306a36Sopenharmony_ci mbox_free_channel(edge->mbox_chan); 155262306a36Sopenharmony_ci device_unregister(&edge->dev); 155362306a36Sopenharmony_ci} 155462306a36Sopenharmony_ciEXPORT_SYMBOL(qcom_smd_unregister_edge); 155562306a36Sopenharmony_ci 155662306a36Sopenharmony_cistatic int qcom_smd_probe(struct platform_device *pdev) 155762306a36Sopenharmony_ci{ 155862306a36Sopenharmony_ci struct device_node *node; 155962306a36Sopenharmony_ci 156062306a36Sopenharmony_ci if (!qcom_smem_is_available()) 156162306a36Sopenharmony_ci return -EPROBE_DEFER; 156262306a36Sopenharmony_ci 156362306a36Sopenharmony_ci for_each_available_child_of_node(pdev->dev.of_node, node) 156462306a36Sopenharmony_ci qcom_smd_register_edge(&pdev->dev, node); 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci return 0; 156762306a36Sopenharmony_ci} 156862306a36Sopenharmony_ci 156962306a36Sopenharmony_cistatic int qcom_smd_remove_edge(struct device *dev, void *data) 157062306a36Sopenharmony_ci{ 157162306a36Sopenharmony_ci struct qcom_smd_edge *edge = to_smd_edge(dev); 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci qcom_smd_unregister_edge(edge); 157462306a36Sopenharmony_ci 157562306a36Sopenharmony_ci return 0; 157662306a36Sopenharmony_ci} 157762306a36Sopenharmony_ci 157862306a36Sopenharmony_ci/* 157962306a36Sopenharmony_ci * Shut down all smd clients by making sure that each edge stops processing 158062306a36Sopenharmony_ci * events and scanning for new channels, then call destroy on the devices. 158162306a36Sopenharmony_ci */ 158262306a36Sopenharmony_cistatic void qcom_smd_remove(struct platform_device *pdev) 158362306a36Sopenharmony_ci{ 158462306a36Sopenharmony_ci /* 158562306a36Sopenharmony_ci * qcom_smd_remove_edge always returns zero, so there is no need to 158662306a36Sopenharmony_ci * check the return value of device_for_each_child. 158762306a36Sopenharmony_ci */ 158862306a36Sopenharmony_ci device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge); 158962306a36Sopenharmony_ci} 159062306a36Sopenharmony_ci 159162306a36Sopenharmony_cistatic const struct of_device_id qcom_smd_of_match[] = { 159262306a36Sopenharmony_ci { .compatible = "qcom,smd" }, 159362306a36Sopenharmony_ci {} 159462306a36Sopenharmony_ci}; 159562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_smd_of_match); 159662306a36Sopenharmony_ci 159762306a36Sopenharmony_cistatic struct platform_driver qcom_smd_driver = { 159862306a36Sopenharmony_ci .probe = qcom_smd_probe, 159962306a36Sopenharmony_ci .remove_new = qcom_smd_remove, 160062306a36Sopenharmony_ci .driver = { 160162306a36Sopenharmony_ci .name = "qcom-smd", 160262306a36Sopenharmony_ci .of_match_table = qcom_smd_of_match, 160362306a36Sopenharmony_ci }, 160462306a36Sopenharmony_ci}; 160562306a36Sopenharmony_ci 160662306a36Sopenharmony_cistatic int __init qcom_smd_init(void) 160762306a36Sopenharmony_ci{ 160862306a36Sopenharmony_ci return platform_driver_register(&qcom_smd_driver); 160962306a36Sopenharmony_ci} 161062306a36Sopenharmony_ciarch_initcall(qcom_smd_init); 161162306a36Sopenharmony_ci 161262306a36Sopenharmony_cistatic void __exit qcom_smd_exit(void) 161362306a36Sopenharmony_ci{ 161462306a36Sopenharmony_ci platform_driver_unregister(&qcom_smd_driver); 161562306a36Sopenharmony_ci} 161662306a36Sopenharmony_cimodule_exit(qcom_smd_exit); 161762306a36Sopenharmony_ci 161862306a36Sopenharmony_ciMODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 161962306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Shared Memory Driver"); 162062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1621