diff --git a/Documentation/devicetree/bindings/arm/msm/glink_spi_xprt.txt b/Documentation/devicetree/bindings/arm/msm/glink_spi_xprt.txt new file mode 100644 index 000000000000..0a78eb6b91fd --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/glink_spi_xprt.txt @@ -0,0 +1,44 @@ +Qualcomm Technologies, Inc. G-link SPI Transport + +Required properties: +-compatible : should be "qcom,glink-spi-xprt". +-label : the name of the subsystem this link connects to. + +Optional properties: +-qcom,remote-fifo-config: Reference to the FIFO configuratio in the remote + processor. +-qcom,qos-config: Reference to the qos configuration elements.It depends on + ramp-time. +-qcom,ramp-time: Worst case time in microseconds to transition to this power + state. Power states are numbered by array index position. + +Example: + + glink_spi_xprt_wdsp: qcom,glink-spi-xprt-wdsp { + compatible = "qcom,glink-spi-xprt"; + label = "wdsp"; + qcom,remote-fifo-config = <&glink_fifo_wdsp>; + qcom,qos-config = <&glink_qos_wdsp>; + qcom,ramp-time = <0x10>, + <0x20>, + <0x30>, + <0x40>; + }; + + glink_fifo_wdsp: qcom,glink-fifo-config-wdsp { + compatible = "qcom,glink-fifo-config"; + qcom,out-read-idx-reg = <0x12000>; + qcom,out-write-idx-reg = <0x12004>; + qcom,in-read-idx-reg = <0x1200C>; + qcom,in-write-idx-reg = <0x12010>; + }; + + glink_qos_wdsp: qcom,glink-qos-config-wdsp { + compatible = "qcom,glink-qos-config"; + qcom,flow-info = <0x80 0x0>, + <0x70 0x1>, + <0x60 0x2>, + <0x50 0x3>; + qcom,mtu-size = <0x800>; + qcom,tput-stats-cycle = <0xa>; + }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 3f8aa534c220..c45cbfa8a786 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -104,6 +104,16 @@ config MSM_GLINK_SMEM_NATIVE_XPRT transport to only connecting with entities internal to the System-on-Chip. +config MSM_GLINK_SPI_XPRT + depends on MSM_GLINK + tristate "Generic Link (G-Link) SPI Transport" + help + G-Link SPI Transport is a Transport plug-in developed over SPI + bus. This transport plug-in performs marshaling of G-Link + commands & data to the appropriate SPI bus wire format and + allows for G-Link communication with remote subsystems that are + external to the System-on-Chip. + config MSM_SPCOM depends on MSM_GLINK bool "Secure Processor Communication over GLINK" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f8450a4868ad..269b72c68b68 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_MSM_GLINK) += glink.o glink_debugfs.o glink_ssr.o obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o obj-$(CONFIG_MSM_GLINK_SMD_XPRT) += glink_smd_xprt.o obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT)+= glink_smem_native_xprt.o +obj-$(CONFIG_MSM_GLINK_SPI_XPRT) += glink_spi_xprt.o obj-$(CONFIG_MSM_SMEM_LOGGING) += smem_log.o obj-$(CONFIG_MSM_SYSMON_GLINK_COMM) += sysmon-glink.o sysmon-qmi.o obj-$(CONFIG_ARCH_MSM8996) += kryo-l2-accessors.o diff --git a/drivers/soc/qcom/glink.c b/drivers/soc/qcom/glink.c index 464fe17158cf..57e58a57fab7 100644 --- a/drivers/soc/qcom/glink.c +++ b/drivers/soc/qcom/glink.c @@ -372,10 +372,10 @@ static struct channel_ctx *ch_name_to_ch_ctx_create( const char *name); static void ch_push_remote_rx_intent(struct channel_ctx *ctx, size_t size, - uint32_t riid); + uint32_t riid, void *cookie); static int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size, - uint32_t *riid_ptr, size_t *intent_size); + uint32_t *riid_ptr, size_t *intent_size, void **cookie); static struct glink_core_rx_intent *ch_push_local_rx_intent( struct channel_ctx *ctx, const void *pkt_priv, size_t size); @@ -1139,11 +1139,12 @@ bool ch_check_duplicate_riid(struct channel_ctx *ctx, int riid) * @ctx: Local channel context * @size: Size of Intent * @riid_ptr: Pointer to return value of remote intent ID + * @cookie: Transport-specific cookie to return * * This functions searches for an RX intent that is >= to the requested size. */ int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size, - uint32_t *riid_ptr, size_t *intent_size) + uint32_t *riid_ptr, size_t *intent_size, void **cookie) { struct glink_core_rx_intent *intent; struct glink_core_rx_intent *intent_tmp; @@ -1177,6 +1178,7 @@ int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size, intent->intent_size); *riid_ptr = intent->id; *intent_size = intent->intent_size; + *cookie = intent->cookie; kfree(intent); spin_unlock_irqrestore( &ctx->rmt_rx_intent_lst_lock_lhc2, flags); @@ -1192,11 +1194,12 @@ int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size, * @ctx: Local channel context * @size: Size of Intent * @riid: Remote intent ID + * @cookie: Transport-specific cookie to cache * * This functions adds a remote RX intent to the remote RX intent list. */ void ch_push_remote_rx_intent(struct channel_ctx *ctx, size_t size, - uint32_t riid) + uint32_t riid, void *cookie) { struct glink_core_rx_intent *intent; unsigned long flags; @@ -1225,6 +1228,7 @@ void ch_push_remote_rx_intent(struct channel_ctx *ctx, size_t size, } intent->id = riid; intent->intent_size = size; + intent->cookie = cookie; spin_lock_irqsave(&ctx->rmt_rx_intent_lst_lock_lhc2, flags); list_add_tail(&intent->list, &ctx->rmt_rx_intent_list); @@ -2794,6 +2798,7 @@ static int glink_tx_common(void *handle, void *pkt_priv, bool is_atomic = tx_flags & (GLINK_TX_SINGLE_THREADED | GLINK_TX_ATOMIC); unsigned long flags; + void *cookie = NULL; if (!size) return -EINVAL; @@ -2826,7 +2831,7 @@ static int glink_tx_common(void *handle, void *pkt_priv, } /* find matching rx intent (first-fit algorithm for now) */ - if (ch_pop_remote_rx_intent(ctx, size, &riid, &intent_size)) { + if (ch_pop_remote_rx_intent(ctx, size, &riid, &intent_size, &cookie)) { if (!(tx_flags & GLINK_TX_REQ_INTENT)) { /* no rx intent available */ GLINK_ERR_CH(ctx, @@ -2856,7 +2861,7 @@ static int glink_tx_common(void *handle, void *pkt_priv, } while (ch_pop_remote_rx_intent(ctx, size, &riid, - &intent_size)) { + &intent_size, &cookie)) { rwref_get(&ctx->ch_state_lhb2); rwref_read_put(&ctx->ch_state_lhb2); if (is_atomic) { @@ -2928,7 +2933,7 @@ static int glink_tx_common(void *handle, void *pkt_priv, is_atomic ? GFP_ATOMIC : GFP_KERNEL); if (!tx_info) { GLINK_ERR_CH(ctx, "%s: No memory for allocation\n", __func__); - ch_push_remote_rx_intent(ctx, intent_size, riid); + ch_push_remote_rx_intent(ctx, intent_size, riid, cookie); rwref_read_put(&ctx->ch_state_lhb2); return -ENOMEM; } @@ -2946,6 +2951,7 @@ static int glink_tx_common(void *handle, void *pkt_priv, tx_info->vprovider = vbuf_provider; tx_info->pprovider = pbuf_provider; tx_info->intent_size = intent_size; + tx_info->cookie = cookie; /* schedule packet for transmit */ if ((tx_flags & GLINK_TX_SINGLE_THREADED) && @@ -3577,6 +3583,10 @@ int glink_xprt_name_to_id(const char *name, uint16_t *id) *id = SMEM_XPRT_ID; return 0; } + if (!strcmp(name, "spi")) { + *id = SPIV2_XPRT_ID; + return 0; + } if (!strcmp(name, "smd_trans")) { *id = SMD_TRANS_XPRT_ID; return 0; @@ -4844,7 +4854,35 @@ static void glink_core_remote_rx_intent_put(struct glink_transport_if *if_ptr, return; } - ch_push_remote_rx_intent(ctx, size, riid); + ch_push_remote_rx_intent(ctx, size, riid, NULL); + rwref_put(&ctx->ch_state_lhb2); +} + +/** + * glink_core_remote_rx_intent_put_cookie() - Receive remove intent + * + * @if_ptr: Pointer to transport instance + * @rcid: Remote Channel ID + * @riid: Remote Intent ID + * @size: Size of the remote intent ID + * @cookie: Transport-specific cookie to cache + */ +static void glink_core_remote_rx_intent_put_cookie( + struct glink_transport_if *if_ptr, + uint32_t rcid, uint32_t riid, size_t size, void *cookie) +{ + struct channel_ctx *ctx; + + ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid); + if (!ctx) { + /* unknown rcid received - this shouldn't happen */ + GLINK_ERR_XPRT(if_ptr->glink_core_priv, + "%s: invalid rcid received %u\n", __func__, + (unsigned)rcid); + return; + } + + ch_push_remote_rx_intent(ctx, size, riid, cookie); rwref_put(&ctx->ch_state_lhb2); } @@ -5050,6 +5088,7 @@ void glink_core_rx_cmd_tx_done(struct glink_transport_if *if_ptr, struct glink_core_tx_pkt *tx_pkt; unsigned long flags; size_t intent_size; + void *cookie; ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid); if (!ctx) { @@ -5082,11 +5121,12 @@ void glink_core_rx_cmd_tx_done(struct glink_transport_if *if_ptr, ctx->notify_tx_done(ctx, ctx->user_priv, tx_pkt->pkt_priv, tx_pkt->data ? tx_pkt->data : tx_pkt->iovec); intent_size = tx_pkt->intent_size; + cookie = tx_pkt->cookie; ch_remove_tx_pending_remote_done(ctx, tx_pkt); spin_unlock_irqrestore(&ctx->tx_lists_lock_lhc3, flags); if (reuse) - ch_push_remote_rx_intent(ctx, intent_size, riid); + ch_push_remote_rx_intent(ctx, intent_size, riid, cookie); rwref_put(&ctx->ch_state_lhb2); } @@ -5525,6 +5565,8 @@ static struct glink_core_if core_impl = { .rx_get_pkt_ctx = glink_core_rx_get_pkt_ctx, .rx_put_pkt_ctx = glink_core_rx_put_pkt_ctx, .rx_cmd_remote_rx_intent_put = glink_core_remote_rx_intent_put, + .rx_cmd_remote_rx_intent_put_cookie = + glink_core_remote_rx_intent_put_cookie, .rx_cmd_remote_rx_intent_req = glink_core_rx_cmd_remote_rx_intent_req, .rx_cmd_rx_intent_req_ack = glink_core_rx_cmd_rx_intent_req_ack, .rx_cmd_tx_done = glink_core_rx_cmd_tx_done, diff --git a/drivers/soc/qcom/glink_core_if.h b/drivers/soc/qcom/glink_core_if.h index 93c59d9c4aa1..14113305a50e 100644 --- a/drivers/soc/qcom/glink_core_if.h +++ b/drivers/soc/qcom/glink_core_if.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -64,6 +64,7 @@ struct glink_core_version { * iovec: Pointer to vector buffer if the transport passes a vector buffer * vprovider: Virtual address-space buffer provider for a vector buffer * pprovider: Physical address-space buffer provider for a vector buffer + * cookie: Private transport specific cookie * pkt_priv: G-Link core owned packet-private data * list: G-Link core owned list node * bounce_buf: Pointer to the temporary/internal bounce buffer @@ -78,6 +79,7 @@ struct glink_core_rx_intent { void *iovec; void * (*vprovider)(void *iovec, size_t offset, size_t *size); void * (*pprovider)(void *iovec, size_t offset, size_t *size); + void *cookie; /* G-Link-Core-owned elements - please ignore */ struct list_head list; @@ -151,6 +153,9 @@ struct glink_core_if { struct glink_core_rx_intent *intent_ptr, bool complete); void (*rx_cmd_remote_rx_intent_put)(struct glink_transport_if *if_ptr, uint32_t rcid, uint32_t riid, size_t size); + void (*rx_cmd_remote_rx_intent_put_cookie)( + struct glink_transport_if *if_ptr, uint32_t rcid, + uint32_t riid, size_t size, void *cookie); void (*rx_cmd_tx_done)(struct glink_transport_if *if_ptr, uint32_t rcid, uint32_t riid, bool reuse); void (*rx_cmd_remote_rx_intent_req)(struct glink_transport_if *if_ptr, diff --git a/drivers/soc/qcom/glink_spi_xprt.c b/drivers/soc/qcom/glink_spi_xprt.c new file mode 100644 index 000000000000..6c91ac54821d --- /dev/null +++ b/drivers/soc/qcom/glink_spi_xprt.c @@ -0,0 +1,2192 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "glink_core_if.h" +#include "glink_private.h" +#include "glink_xprt_if.h" + +#define XPRT_NAME "spi" +#define FIFO_ALIGNMENT 16 +#define FIFO_FULL_RESERVE 8 +#define TX_BLOCKED_CMD_RESERVE 16 +#define TRACER_PKT_FEATURE BIT(2) +#define DEFAULT_FIFO_SIZE 1024 +#define SHORT_PKT_SIZE 16 +#define XPRT_ALIGNMENT 4 + +#define MAX_INACTIVE_CYCLES 50 +#define POLL_INTERVAL_US 500 + +#define ACTIVE_TX BIT(0) +#define ACTIVE_RX BIT(1) + +#define ID_MASK 0xFFFFFF +/** + * enum command_types - definition of the types of commands sent/received + * @VERSION_CMD: Version and feature set supported + * @VERSION_ACK_CMD: Response for @VERSION_CMD + * @OPEN_CMD: Open a channel + * @CLOSE_CMD: Close a channel + * @OPEN_ACK_CMD: Response to @OPEN_CMD + * @CLOSE_ACK_CMD: Response for @CLOSE_CMD + * @RX_INTENT_CMD: RX intent for a channel is queued + * @RX_DONE_CMD: Use of RX intent for a channel is complete + * @RX_DONE_W_REUSE_CMD: Same as @RX_DONE but also reuse the used intent + * @RX_INTENT_REQ_CMD: Request to have RX intent queued + * @RX_INTENT_REQ_ACK_CMD: Response for @RX_INTENT_REQ_CMD + * @TX_DATA_CMD: Start of a data transfer + * @TX_DATA_CONT_CMD: Continuation or end of a data transfer + * @READ_NOTIF_CMD: Request for a notification when this cmd is read + * @SIGNALS_CMD: Sideband signals + * @TRACER_PKT_CMD: Start of a Tracer Packet Command + * @TRACER_PKT_CONT_CMD: Continuation or end of a Tracer Packet Command + * @TX_SHORT_DATA_CMD: Transmit short packets + */ +enum command_types { + VERSION_CMD, + VERSION_ACK_CMD, + OPEN_CMD, + CLOSE_CMD, + OPEN_ACK_CMD, + CLOSE_ACK_CMD, + RX_INTENT_CMD, + RX_DONE_CMD, + RX_DONE_W_REUSE_CMD, + RX_INTENT_REQ_CMD, + RX_INTENT_REQ_ACK_CMD, + TX_DATA_CMD, + TX_DATA_CONT_CMD, + READ_NOTIF_CMD, + SIGNALS_CMD, + TRACER_PKT_CMD, + TRACER_PKT_CONT_CMD, + TX_SHORT_DATA_CMD, +}; + +/** + * struct glink_cmpnt - Component to cache WDSP component and its operations + * @master_dev: Device structure corresponding to WDSP device. + * @master_ops: Operations supported by the WDSP device. + */ +struct glink_cmpnt { + struct device *master_dev; + struct wdsp_mgr_ops *master_ops; +}; + +/** + * struct edge_info - local information for managing a single complete edge + * @xprt_if: The transport interface registered with the + * glink core associated with this edge. + * @xprt_cfg: The transport configuration for the glink core + * assocaited with this edge. + * @subsys_name: Name of the remote subsystem in the edge. + * @spi_dev: Pointer to the connectingSPI Device. + * @fifo_size: Size of the FIFO at the remote end. + * @tx_fifo_start: Base Address of the TX FIFO. + * @tx_fifo_end: End Address of the TX FIFO. + * @rx_fifo_start: Base Address of the RX FIFO. + * @rx_fifo_end: End Address of the RX FIFO. + * @tx_fifo_read_reg_addr: Address of the TX FIFO Read Index Register. + * @tx_fifo_write_reg_addr: Address of the TX FIFO Write Index Register. + * @rx_fifo_read_reg_addr: Address of the RX FIFO Read Index Register. + * @rx_fifo_write_reg_addr: Address of the RX FIFO Write Index Register. + * @kwork: Work to be executed when receiving data. + * @kworker: Handle to the entity processing @kwork. + * @task: Handle to the task context that runs @kworker. + * @use_ref: Active users of this transport grab a + * reference. Used for SSR synchronization. + * @in_ssr: Signals if this transport is in ssr. + * @write_lock: Lock to serialize write/tx operation. + * @tx_blocked_queue: Queue of entities waiting for the remote side to + * signal the resumption of TX. + * @tx_resume_needed: A tx resume signal needs to be sent to the glink + * core. + * @tx_blocked_signal_sent: Flag to indicate the flush signal has already + * been sent, and a response is pending from the + * remote side. Protected by @write_lock. + * @num_pw_states: Size of @ramp_time_us. + * @ramp_time_us: Array of ramp times in microseconds where array + * index position represents a power state. + * @activity_flag: Flag indicating active TX and RX. + * @activity_lock: Lock to synchronize access to activity flag. + * @cmpnt: Component to interface with the remote device. + */ +struct edge_info { + struct list_head list; + struct glink_transport_if xprt_if; + struct glink_core_transport_cfg xprt_cfg; + char subsys_name[GLINK_NAME_SIZE]; + struct spi_device *spi_dev; + + uint32_t fifo_size; + uint32_t tx_fifo_start; + uint32_t tx_fifo_end; + uint32_t rx_fifo_start; + uint32_t rx_fifo_end; + unsigned int tx_fifo_read_reg_addr; + unsigned int tx_fifo_write_reg_addr; + unsigned int rx_fifo_read_reg_addr; + unsigned int rx_fifo_write_reg_addr; + + struct kthread_work kwork; + struct kthread_worker kworker; + struct task_struct *task; + struct srcu_struct use_ref; + bool in_ssr; + struct mutex write_lock; + wait_queue_head_t tx_blocked_queue; + bool tx_resume_needed; + bool tx_blocked_signal_sent; + + uint32_t num_pw_states; + unsigned long *ramp_time_us; + + uint32_t activity_flag; + spinlock_t activity_lock; + + struct glink_cmpnt cmpnt; +}; + +static uint32_t negotiate_features_v1(struct glink_transport_if *if_ptr, + const struct glink_core_version *version, + uint32_t features); +static DEFINE_SPINLOCK(edge_infos_lock); +static LIST_HEAD(edge_infos); +static struct glink_core_version versions[] = { + {1, TRACER_PKT_FEATURE, negotiate_features_v1}, +}; + +/** + * negotiate_features_v1() - determine what features of a version can be used + * @if_ptr: The transport for which features are negotiated for. + * @version: The version negotiated. + * @features: The set of requested features. + * + * Return: What set of the requested features can be supported. + */ +static uint32_t negotiate_features_v1(struct glink_transport_if *if_ptr, + const struct glink_core_version *version, + uint32_t features) +{ + return features & version->features; +} + +/** + * wdsp_suspend() - Vote for the WDSP device suspend + * @cmpnt: Component to identify the WDSP device. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int wdsp_suspend(struct glink_cmpnt *cmpnt) +{ + if (cmpnt && cmpnt->master_dev && + cmpnt->master_ops && cmpnt->master_ops->suspend) + return cmpnt->master_ops->suspend(cmpnt->master_dev); + else + return -EINVAL; +} + +/** + * wdsp_resume() - Vote for the WDSP device resume + * @cmpnt: Component to identify the WDSP device. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int wdsp_resume(struct glink_cmpnt *cmpnt) +{ + if (cmpnt && cmpnt->master_dev && + cmpnt->master_ops && cmpnt->master_ops->resume) + return cmpnt->master_ops->resume(cmpnt->master_dev); + else + return -EINVAL; +} + +/** + * glink_spi_xprt_set_poll_mode() - Set the transport to polling mode + * @einfo: Edge information corresponding to the transport. + * + * This helper function indicates the start of RX polling. This will + * prevent the system from suspending and keeps polling for RX for a + * pre-defined duration. + */ +static void glink_spi_xprt_set_poll_mode(struct edge_info *einfo) +{ + unsigned long flags; + + spin_lock_irqsave(&einfo->activity_lock, flags); + einfo->activity_flag |= ACTIVE_RX; + spin_unlock_irqrestore(&einfo->activity_lock, flags); + if (!strcmp(einfo->xprt_cfg.edge, "wdsp")) + wdsp_resume(&einfo->cmpnt); +} + +/** + * glink_spi_xprt_set_irq_mode() - Set the transport to IRQ mode + * @einfo: Edge information corresponding to the transport. + * + * This helper indicates the end of RX polling. This will allow the + * system to suspend and new RX data can be handled only through an IRQ. + */ +static void glink_spi_xprt_set_irq_mode(struct edge_info *einfo) +{ + unsigned long flags; + + spin_lock_irqsave(&einfo->activity_lock, flags); + einfo->activity_flag &= ~ACTIVE_RX; + spin_unlock_irqrestore(&einfo->activity_lock, flags); +} + +/** + * glink_spi_xprt_rx_data() - Receive data over SPI bus + * @einfo: Edge from which the data has to be received. + * @src: Source Address of the RX data. + * @dst: Address of the destination RX buffer. + * @size: Size of the RX data. + * + * This function is used to receive data or command as a byte stream from + * the remote subsystem over the SPI bus. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int glink_spi_xprt_rx_data(struct edge_info *einfo, void *src, + void *dst, uint32_t size) +{ + struct wcd_spi_msg spi_msg; + + memset(&spi_msg, 0, sizeof(spi_msg)); + spi_msg.data = dst; + spi_msg.remote_addr = (uint32_t)(size_t)src; + spi_msg.len = (size_t)size; + return wcd_spi_data_read(einfo->spi_dev, &spi_msg); +} + +/** + * glink_spi_xprt_tx_data() - Transmit data over SPI bus + * @einfo: Edge from which the data has to be received. + * @src: Address of the TX buffer. + * @dst: Destination Address of the TX Date. + * @size: Size of the TX data. + * + * This function is used to transmit data or command as a byte stream to + * the remote subsystem over the SPI bus. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int glink_spi_xprt_tx_data(struct edge_info *einfo, void *src, + void *dst, uint32_t size) +{ + struct wcd_spi_msg spi_msg; + + memset(&spi_msg, 0, sizeof(spi_msg)); + spi_msg.data = src; + spi_msg.remote_addr = (uint32_t)(size_t)dst; + spi_msg.len = (size_t)size; + return wcd_spi_data_write(einfo->spi_dev, &spi_msg); +} + +/** + * glink_spi_xprt_reg_read() - Read the TX/RX FIFO Read/Write Index registers + * @einfo: Edge from which the registers have to be read. + * @reg_addr: Address of the register to be read. + * @data: Buffer into which the register data has to be read. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int glink_spi_xprt_reg_read(struct edge_info *einfo, u32 reg_addr, + uint32_t *data) +{ + int rc; + + rc = glink_spi_xprt_rx_data(einfo, (void *)(unsigned long)reg_addr, + data, sizeof(*data)); + if (!rc) + *data = *data & ID_MASK; + return rc; +} + +/** + * glink_spi_xprt_reg_write() - Write the TX/RX FIFO Read/Write Index registers + * @einfo: Edge to which the registers have to be written. + * @reg_addr: Address of the registers to be written. + * @data: Data to be written to the registers. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +static int glink_spi_xprt_reg_write(struct edge_info *einfo, u32 reg_addr, + uint32_t data) +{ + return glink_spi_xprt_tx_data(einfo, &data, + (void *)(unsigned long)reg_addr, sizeof(data)); +} + +/** + * glink_spi_xprt_write_avail() - Available Write Space in the remote side + * @einfo: Edge information corresponding to the remote side. + * + * This function reads the TX FIFO Read & Write Index registers from the + * remote subsystem and calculate the available write space. + * + * Return: 0 on error, available write space on success. + */ +static int glink_spi_xprt_write_avail(struct edge_info *einfo) +{ + uint32_t read_id; + uint32_t write_id; + int write_avail; + int ret; + + ret = glink_spi_xprt_reg_read(einfo, einfo->tx_fifo_read_reg_addr, + &read_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s tx_fifo_read_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->tx_fifo_read_reg_addr); + return 0; + } + + ret = glink_spi_xprt_reg_read(einfo, einfo->tx_fifo_write_reg_addr, + &write_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s tx_fifo_write_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->tx_fifo_write_reg_addr); + return 0; + } + + if (!read_id || !write_id) + return 0; + + if (unlikely(!einfo->tx_fifo_start)) + einfo->tx_fifo_start = write_id; + + if (read_id > write_id) + write_avail = read_id - write_id; + else + write_avail = einfo->fifo_size - (write_id - read_id); + + if (write_avail < FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE) + write_avail = 0; + else + write_avail -= FIFO_FULL_RESERVE + TX_BLOCKED_CMD_RESERVE; + + return write_avail; +} + +/** + * glink_spi_xprt_read_avail() - Available Read Data from the remote side + * @einfo: Edge information corresponding to the remote side. + * + * This function reads the RX FIFO Read & Write Index registers from the + * remote subsystem and calculate the available read data size. + * + * Return: 0 on error, available read data on success. + */ +static int glink_spi_xprt_read_avail(struct edge_info *einfo) +{ + uint32_t read_id; + uint32_t write_id; + int read_avail; + int ret; + + ret = glink_spi_xprt_reg_read(einfo, einfo->rx_fifo_read_reg_addr, + &read_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s rx_fifo_read_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->rx_fifo_read_reg_addr); + return 0; + } + + ret = glink_spi_xprt_reg_read(einfo, einfo->rx_fifo_write_reg_addr, + &write_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s rx_fifo_write_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->rx_fifo_write_reg_addr); + return 0; + } + + if (!read_id || !write_id) + return 0; + + if (unlikely(!einfo->rx_fifo_start)) + einfo->rx_fifo_start = read_id; + + if (read_id <= write_id) + read_avail = write_id - read_id; + else + read_avail = einfo->fifo_size - (read_id - write_id); + return read_avail; +} + +/** + * glink_spi_xprt_rx_cmd() - Receive G-Link commands + * @einfo: Edge information corresponding to the remote side. + * @dst: Destination buffer where the commands have to be read into. + * @size: Size of the data to be read. + * + * This function is used to receive the commands from the RX FIFO. This + * function updates the RX FIFO Read Index after reading the data. + * + * Return: 0 on success, standard Linux error codes on error. + */ +static int glink_spi_xprt_rx_cmd(struct edge_info *einfo, void *dst, + uint32_t size) +{ + uint32_t read_id; + uint32_t size_to_read = size; + uint32_t offset = 0; + int ret; + + ret = glink_spi_xprt_reg_read(einfo, einfo->rx_fifo_read_reg_addr, + &read_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s rx_fifo_read_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->rx_fifo_read_reg_addr); + return ret; + } + + do { + if ((read_id + size_to_read) >= + (einfo->rx_fifo_start + einfo->fifo_size)) + size_to_read = einfo->rx_fifo_start + einfo->fifo_size + - read_id; + ret = glink_spi_xprt_rx_data(einfo, (void *)(size_t)read_id, + dst + offset, size_to_read); + if (ret < 0) { + pr_err("%s: Error %d reading data\n", __func__, ret); + return ret; + } + read_id += size_to_read; + offset += size_to_read; + if (read_id >= (einfo->rx_fifo_start + einfo->fifo_size)) + read_id = einfo->rx_fifo_start; + size_to_read = size - offset; + } while (size_to_read); + + ret = glink_spi_xprt_reg_write(einfo, einfo->rx_fifo_read_reg_addr, + read_id); + if (ret < 0) + pr_err("%s: Error %d writing %s rx_fifo_read_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->rx_fifo_read_reg_addr); + return ret; +} + +/** + * glink_spi_xprt_tx_cmd_safe() - Transmit G-Link commands + * @einfo: Edge information corresponding to the remote subsystem. + * @src: Source buffer containing the G-Link command. + * @size: Size of the command to transmit. + * + * This function is used to transmit the G-Link commands. This function + * must be called with einfo->write_lock locked. + * + * Return: 0 on success, standard Linux error codes on error. + */ +static int glink_spi_xprt_tx_cmd_safe(struct edge_info *einfo, void *src, + uint32_t size) +{ + uint32_t write_id; + uint32_t size_to_write = size; + uint32_t offset = 0; + int ret; + + ret = glink_spi_xprt_reg_read(einfo, einfo->tx_fifo_write_reg_addr, + &write_id); + if (ret < 0) { + pr_err("%s: Error %d reading %s tx_fifo_write_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->tx_fifo_write_reg_addr); + return ret; + } + + do { + if ((write_id + size_to_write) >= + (einfo->tx_fifo_start + einfo->fifo_size)) + size_to_write = einfo->tx_fifo_start + einfo->fifo_size + - write_id; + ret = glink_spi_xprt_tx_data(einfo, src + offset, + (void *)(size_t)write_id, size_to_write); + if (ret < 0) { + pr_err("%s: Error %d writing data\n", __func__, ret); + return ret; + } + write_id += size_to_write; + offset += size_to_write; + if (write_id >= (einfo->tx_fifo_start + einfo->fifo_size)) + write_id = einfo->tx_fifo_start; + size_to_write = size - offset; + } while (size_to_write); + + ret = glink_spi_xprt_reg_write(einfo, einfo->tx_fifo_write_reg_addr, + write_id); + if (ret < 0) + pr_err("%s: Error %d writing %s tx_fifo_write_reg_addr %d\n", + __func__, ret, einfo->xprt_cfg.edge, + einfo->tx_fifo_write_reg_addr); + return ret; +} + +/** + * send_tx_blocked_signal() - Send flow control request message + * @einfo: Edge information corresponding to the remote subsystem. + * + * This function is used to send a message to the remote subsystem indicating + * that the local subsystem is waiting for the write space. The remote + * subsystem on receiving this message will send a resume tx message. + */ +static void send_tx_blocked_signal(struct edge_info *einfo) +{ + struct read_notif_request { + uint16_t cmd; + uint16_t reserved; + uint32_t reserved2; + uint64_t reserved3; + }; + struct read_notif_request read_notif_req = {0}; + + read_notif_req.cmd = READ_NOTIF_CMD; + + if (!einfo->tx_blocked_signal_sent) { + einfo->tx_blocked_signal_sent = true; + glink_spi_xprt_tx_cmd_safe(einfo, &read_notif_req, + sizeof(read_notif_req)); + } +} + +/** + * glink_spi_xprt_tx_cmd() - Transmit G-Link commands + * @einfo: Edge information corresponding to the remote subsystem. + * @src: Source buffer containing the G-Link command. + * @size: Size of the command to transmit. + * + * This function is used to transmit the G-Link commands. This function + * might sleep if the space is not available to transmit the command. + * + * Return: 0 on success, standard Linux error codes on error. + */ +static int glink_spi_xprt_tx_cmd(struct edge_info *einfo, void *src, + uint32_t size) +{ + int ret; + DEFINE_WAIT(wait); + + mutex_lock(&einfo->write_lock); + while (glink_spi_xprt_write_avail(einfo) < size) { + send_tx_blocked_signal(einfo); + prepare_to_wait(&einfo->tx_blocked_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (glink_spi_xprt_write_avail(einfo) < size && + !einfo->in_ssr) { + mutex_unlock(&einfo->write_lock); + schedule(); + mutex_lock(&einfo->write_lock); + } + finish_wait(&einfo->tx_blocked_queue, &wait); + if (einfo->in_ssr) { + mutex_unlock(&einfo->write_lock); + return -EFAULT; + } + } + ret = glink_spi_xprt_tx_cmd_safe(einfo, src, size); + mutex_unlock(&einfo->write_lock); + return ret; +} + +/** + * process_rx_data() - process received data from an edge + * @einfo: The edge the data is received on. + * @cmd_id: ID to specify the type of data. + * @rcid: The remote channel id associated with the data. + * @intend_id: The intent the data should be put in. + * @src: Address of the source buffer from which the data + * is read. + * @frag_size: Size of the data fragment to read. + * @size_remaining: Size of data left to be read in this packet. + */ +static void process_rx_data(struct edge_info *einfo, uint16_t cmd_id, + uint32_t rcid, uint32_t intent_id, void *src, + uint32_t frag_size, uint32_t size_remaining) +{ + struct glink_core_rx_intent *intent; + int rc = 0; + + intent = einfo->xprt_if.glink_core_if_ptr->rx_get_pkt_ctx( + &einfo->xprt_if, rcid, intent_id); + if (intent == NULL) { + GLINK_ERR("%s: no intent for ch %d liid %d\n", __func__, rcid, + intent_id); + return; + } else if (intent->data == NULL) { + GLINK_ERR("%s: intent for ch %d liid %d has no data buff\n", + __func__, rcid, intent_id); + return; + } else if (intent->intent_size - intent->write_offset < frag_size || + intent->write_offset + size_remaining > intent->intent_size) { + GLINK_ERR("%s: rx data size:%d and remaining:%d %s %d %s:%d\n", + __func__, frag_size, size_remaining, + "will overflow ch", rcid, "intent", intent_id); + return; + } + + if (cmd_id == TX_SHORT_DATA_CMD) + memcpy(intent->data + intent->write_offset, src, frag_size); + else + rc = glink_spi_xprt_rx_data(einfo, src, + intent->data + intent->write_offset, frag_size); + if (rc < 0) { + GLINK_ERR("%s: Error %d receiving data %d:%d:%d:%d\n", + __func__, rc, rcid, intent_id, frag_size, + size_remaining); + size_remaining += frag_size; + } else { + intent->write_offset += frag_size; + intent->pkt_size += frag_size; + + if (unlikely((cmd_id == TRACER_PKT_CMD || + cmd_id == TRACER_PKT_CONT_CMD) && !size_remaining)) { + tracer_pkt_log_event(intent->data, GLINK_XPRT_RX); + intent->tracer_pkt = true; + } + } + einfo->xprt_if.glink_core_if_ptr->rx_put_pkt_ctx(&einfo->xprt_if, + rcid, intent, size_remaining ? false : true); +} + +/** + * process_rx_cmd() - Process incoming G-Link commands + * @einfo: Edge information corresponding to the remote subsystem. + * @rx_data: Buffer which contains the G-Link commands to be processed. + * @rx_size: Size of the buffer containing the series of G-Link commands. + * + * This function is used to parse and process a series of G-Link commands + * received in a buffer. + */ +static void process_rx_cmd(struct edge_info *einfo, + void *rx_data, int rx_size) +{ + struct command { + uint16_t id; + uint16_t param1; + uint32_t param2; + uint32_t param3; + uint32_t param4; + }; + struct intent_desc { + uint32_t size; + uint32_t id; + uint64_t addr; + }; + struct rx_desc { + uint32_t size; + uint32_t size_left; + uint64_t addr; + }; + struct rx_short_data_desc { + unsigned char data[SHORT_PKT_SIZE]; + }; + struct command *cmd; + struct intent_desc *intents; + struct rx_desc *rx_descp; + struct rx_short_data_desc *rx_sd_descp; + int offset = 0; + int rcu_id; + uint16_t rcid; + uint16_t name_len; + uint16_t prio; + char *name; + bool granted; + int i; + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + while (offset < rx_size) { + cmd = (struct command *)(rx_data + offset); + offset += sizeof(*cmd); + switch (cmd->id) { + case VERSION_CMD: + if (cmd->param3) + einfo->fifo_size = cmd->param3; + einfo->xprt_if.glink_core_if_ptr->rx_cmd_version( + &einfo->xprt_if, cmd->param1, cmd->param2); + break; + + case VERSION_ACK_CMD: + einfo->xprt_if.glink_core_if_ptr->rx_cmd_version_ack( + &einfo->xprt_if, cmd->param1, cmd->param2); + break; + + case OPEN_CMD: + rcid = cmd->param1; + name_len = (uint16_t)(cmd->param2 & 0xFFFF); + prio = (uint16_t)((cmd->param2 & 0xFFFF0000) >> 16); + name = (char *)(rx_data + offset); + offset += ALIGN(name_len, FIFO_ALIGNMENT); + einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_remote_open( + &einfo->xprt_if, rcid, name, prio); + break; + + case CLOSE_CMD: + einfo->xprt_if.glink_core_if_ptr-> + rx_cmd_ch_remote_close( + &einfo->xprt_if, cmd->param1); + break; + + case OPEN_ACK_CMD: + prio = (uint16_t)(cmd->param2 & 0xFFFF); + einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_open_ack( + &einfo->xprt_if, cmd->param1, prio); + break; + + case CLOSE_ACK_CMD: + einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_close_ack( + &einfo->xprt_if, cmd->param1); + break; + + case RX_INTENT_CMD: + for (i = 0; i < cmd->param2; i++) { + intents = (struct intent_desc *) + (rx_data + offset); + offset += sizeof(*intents); + einfo->xprt_if.glink_core_if_ptr-> + rx_cmd_remote_rx_intent_put_cookie( + &einfo->xprt_if, cmd->param1, + intents->id, intents->size, + (void *)(intents->addr)); + } + break; + + case RX_DONE_CMD: + einfo->xprt_if.glink_core_if_ptr->rx_cmd_tx_done( + &einfo->xprt_if, cmd->param1, cmd->param2, + false); + break; + + case RX_INTENT_REQ_CMD: + einfo->xprt_if.glink_core_if_ptr-> + rx_cmd_remote_rx_intent_req( + &einfo->xprt_if, cmd->param1, + cmd->param2); + break; + + case RX_INTENT_REQ_ACK_CMD: + granted = cmd->param2 == 1 ? true : false; + einfo->xprt_if.glink_core_if_ptr-> + rx_cmd_rx_intent_req_ack(&einfo->xprt_if, + cmd->param1, granted); + break; + + case TX_DATA_CMD: + case TX_DATA_CONT_CMD: + case TRACER_PKT_CMD: + case TRACER_PKT_CONT_CMD: + rx_descp = (struct rx_desc *)(rx_data + offset); + offset += sizeof(*rx_descp); + process_rx_data(einfo, cmd->id, cmd->param1, + cmd->param2, (void *)rx_descp->addr, + rx_descp->size, rx_descp->size_left); + break; + + case TX_SHORT_DATA_CMD: + rx_sd_descp = (struct rx_short_data_desc *) + (rx_data + offset); + offset += sizeof(*rx_sd_descp); + process_rx_data(einfo, cmd->id, cmd->param1, + cmd->param2, (void *)rx_sd_descp->data, + cmd->param3, cmd->param4); + break; + + case READ_NOTIF_CMD: + break; + + case SIGNALS_CMD: + einfo->xprt_if.glink_core_if_ptr->rx_cmd_remote_sigs( + &einfo->xprt_if, cmd->param1, cmd->param2); + break; + + case RX_DONE_W_REUSE_CMD: + einfo->xprt_if.glink_core_if_ptr->rx_cmd_tx_done( + &einfo->xprt_if, cmd->param1, + cmd->param2, true); + break; + + default: + pr_err("Unrecognized command: %d\n", cmd->id); + break; + } + } + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * __rx_worker() - Receive commands on a specific edge + * @einfo: Edge to process commands on. + * + * This function checks the size of data to be received, allocates the + * buffer for that data and reads the data from the remote subsytem + * into that buffer. This function then calls the process_rx_cmd() to + * parse the received G-Link command sequence. This function will also + * poll for the data for a predefined duration for performance reasons. + */ +static void __rx_worker(struct edge_info *einfo) +{ + uint32_t inactive_cycles = 0; + int rx_avail, rc; + void *rx_data; + int rcu_id; + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (unlikely(!einfo->rx_fifo_start)) { + rx_avail = glink_spi_xprt_read_avail(einfo); + if (!rx_avail) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + einfo->in_ssr = false; + einfo->xprt_if.glink_core_if_ptr->link_up(&einfo->xprt_if); + } + + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + glink_spi_xprt_set_poll_mode(einfo); + while (inactive_cycles < MAX_INACTIVE_CYCLES) { + if (einfo->tx_resume_needed && + glink_spi_xprt_write_avail(einfo)) { + einfo->tx_resume_needed = false; + einfo->xprt_if.glink_core_if_ptr->tx_resume( + &einfo->xprt_if); + } + mutex_lock(&einfo->write_lock); + if (einfo->tx_blocked_signal_sent) { + wake_up_all(&einfo->tx_blocked_queue); + einfo->tx_blocked_signal_sent = false; + } + mutex_unlock(&einfo->write_lock); + + rx_avail = glink_spi_xprt_read_avail(einfo); + if (!rx_avail) { + usleep_range(POLL_INTERVAL_US, POLL_INTERVAL_US + 50); + inactive_cycles++; + continue; + } + inactive_cycles = 0; + + rx_data = kzalloc(rx_avail, GFP_KERNEL); + if (!rx_data) + break; + + rc = glink_spi_xprt_rx_cmd(einfo, rx_data, rx_avail); + if (rc < 0) { + GLINK_ERR("%s: Error %d receiving data\n", + __func__, rc); + kfree(rx_data); + break; + } + process_rx_cmd(einfo, rx_data, rx_avail); + kfree(rx_data); + } + glink_spi_xprt_set_irq_mode(einfo); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * rx_worker() - Worker function to process received commands + * @work: kwork associated with the edge to process commands on. + */ +static void rx_worker(struct kthread_work *work) +{ + struct edge_info *einfo; + + einfo = container_of(work, struct edge_info, kwork); + __rx_worker(einfo); +}; + +/** + * tx_cmd_version() - Convert a version cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @version: The version number to encode. + * @features: The features information to encode. + */ +static void tx_cmd_version(struct glink_transport_if *if_ptr, uint32_t version, + uint32_t features) +{ + struct command { + uint16_t id; + uint16_t version; + uint32_t features; + uint32_t fifo_size; + uint32_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + cmd.id = VERSION_CMD; + cmd.version = version; + cmd.features = features; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * tx_cmd_version_ack() - Convert a version ack cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @version: The version number to encode. + * @features: The features information to encode. + */ +static void tx_cmd_version_ack(struct glink_transport_if *if_ptr, + uint32_t version, + uint32_t features) +{ + struct command { + uint16_t id; + uint16_t version; + uint32_t features; + uint32_t fifo_size; + uint32_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + cmd.id = VERSION_ACK_CMD; + cmd.version = version; + cmd.features = features; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * set_version() - Activate a negotiated version and feature set + * @if_ptr: The transport to configure. + * @version: The version to use. + * @features: The features to use. + * + * Return: The supported capabilities of the transport. + */ +static uint32_t set_version(struct glink_transport_if *if_ptr, uint32_t version, + uint32_t features) +{ + struct edge_info *einfo; + uint32_t ret; + int rcu_id; + + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; + } + + ret = GCAP_SIGNALS; + if (features & TRACER_PKT_FEATURE) + ret |= GCAP_TRACER_PKT; + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return ret; +} + +/** + * tx_cmd_ch_open() - Convert a channel open cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @name: The channel name to encode. + * @req_xprt: The transport the core would like to migrate this channel to. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_ch_open(struct glink_transport_if *if_ptr, uint32_t lcid, + const char *name, uint16_t req_xprt) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint16_t length; + uint16_t req_xprt; + uint64_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + uint32_t buf_size; + void *buf; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = OPEN_CMD; + cmd.lcid = lcid; + cmd.length = (uint16_t)(strlen(name) + 1); + cmd.req_xprt = req_xprt; + + buf_size = ALIGN(sizeof(cmd) + cmd.length, FIFO_ALIGNMENT); + + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -ENOMEM; + } + + memcpy(buf, &cmd, sizeof(cmd)); + memcpy(buf + sizeof(cmd), name, cmd.length); + + glink_spi_xprt_tx_cmd(einfo, buf, buf_size); + + kfree(buf); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_cmd_ch_close() - Convert a channel close cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_ch_close(struct glink_transport_if *if_ptr, uint32_t lcid) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t reserved1; + uint64_t reserved2; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = CLOSE_CMD; + cmd.lcid = lcid; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_cmd_ch_remote_open_ack() - Convert a channel open ack cmd to wire format + * and transmit + * @if_ptr: The transport to transmit on. + * @rcid: The remote channel id to encode. + * @xprt_resp: The response to a transport migration request. + */ +static void tx_cmd_ch_remote_open_ack(struct glink_transport_if *if_ptr, + uint32_t rcid, uint16_t xprt_resp) +{ + struct command { + uint16_t id; + uint16_t rcid; + uint16_t reserved1; + uint16_t xprt_resp; + uint64_t reserved2; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + cmd.id = OPEN_ACK_CMD; + cmd.rcid = rcid; + cmd.xprt_resp = xprt_resp; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * tx_cmd_ch_remote_close_ack() - Convert a channel close ack cmd to wire format + * and transmit + * @if_ptr: The transport to transmit on. + * @rcid: The remote channel id to encode. + */ +static void tx_cmd_ch_remote_close_ack(struct glink_transport_if *if_ptr, + uint32_t rcid) +{ + struct command { + uint16_t id; + uint16_t rcid; + uint32_t reserved1; + uint64_t reserved2; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + cmd.id = CLOSE_ACK_CMD; + cmd.rcid = rcid; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * ssr() - Process a subsystem restart notification of a transport + * @if_ptr: The transport to restart + * + * Return: 0 on success or standard Linux error code. + */ +static int ssr(struct glink_transport_if *if_ptr) +{ + struct edge_info *einfo; + + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + einfo->in_ssr = true; + wake_up_all(&einfo->tx_blocked_queue); + + synchronize_srcu(&einfo->use_ref); + einfo->tx_resume_needed = false; + einfo->tx_blocked_signal_sent = false; + einfo->tx_fifo_start = 0; + einfo->rx_fifo_start = 0; + einfo->fifo_size = DEFAULT_FIFO_SIZE; + einfo->xprt_if.glink_core_if_ptr->link_down(&einfo->xprt_if); + + return 0; +} + +/** + * allocate_rx_intent() - Allocate/reserve space for RX Intent + * @if_ptr: The transport the intent is associated with. + * @size: size of intent. + * @intent: Pointer to the intent structure. + * + * Assign "data" with the buffer created, since the transport creates + * a linear buffer and "iovec" with the "intent" itself, so that + * the data can be passed to a client that receives only vector buffer. + * Note that returning NULL for the pointer is valid (it means that space has + * been reserved, but the actual pointer will be provided later). + * + * Return: 0 on success or standard Linux error code. + */ +static int allocate_rx_intent(struct glink_transport_if *if_ptr, size_t size, + struct glink_core_rx_intent *intent) +{ + void *t; + + t = kzalloc(size, GFP_KERNEL); + if (!t) + return -ENOMEM; + + intent->data = t; + intent->iovec = (void *)intent; + intent->vprovider = rx_linear_vbuf_provider; + intent->pprovider = NULL; + return 0; +} + +/** + * deallocate_rx_intent() - Deallocate space created for RX Intent + * @if_ptr: The transport the intent is associated with. + * @intent: Pointer to the intent structure. + * + * Return: 0 on success or standard Linux error code. + */ +static int deallocate_rx_intent(struct glink_transport_if *if_ptr, + struct glink_core_rx_intent *intent) +{ + if (!intent || !intent->data) + return -EINVAL; + + kfree(intent->data); + intent->data = NULL; + intent->iovec = NULL; + intent->vprovider = NULL; + return 0; +} + +/** + * tx_cmd_local_rx_intent() - Convert an rx intent cmd to wire format and + * transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @size: The intent size to encode. + * @liid: The local intent id to encode. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_local_rx_intent(struct glink_transport_if *if_ptr, + uint32_t lcid, size_t size, uint32_t liid) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t count; + uint64_t reserved; + uint32_t size; + uint32_t liid; + uint64_t addr; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + if (size > UINT_MAX) { + pr_err("%s: size %zu is too large to encode\n", __func__, size); + return -EMSGSIZE; + } + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = RX_INTENT_CMD; + cmd.lcid = lcid; + cmd.count = 1; + cmd.size = size; + cmd.liid = liid; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_cmd_local_rx_done() - Convert an rx done cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @liid: The local intent id to encode. + * @reuse: Reuse the consumed intent. + */ +static void tx_cmd_local_rx_done(struct glink_transport_if *if_ptr, + uint32_t lcid, uint32_t liid, bool reuse) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t liid; + uint64_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return; + } + + cmd.id = reuse ? RX_DONE_W_REUSE_CMD : RX_DONE_CMD; + cmd.lcid = lcid; + cmd.liid = liid; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + srcu_read_unlock(&einfo->use_ref, rcu_id); +} + +/** + * tx_cmd_rx_intent_req() - Convert an rx intent request cmd to wire format and + * transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @size: The requested intent size to encode. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_rx_intent_req(struct glink_transport_if *if_ptr, + uint32_t lcid, size_t size) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t size; + uint64_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + if (size > UINT_MAX) { + pr_err("%s: size %zu is too large to encode\n", __func__, size); + return -EMSGSIZE; + } + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = RX_INTENT_REQ_CMD, + cmd.lcid = lcid; + cmd.size = size; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_cmd_rx_intent_req_ack() - Convert an rx intent request ack cmd to wire + * format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @granted: The request response to encode. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_remote_rx_intent_req_ack(struct glink_transport_if *if_ptr, + uint32_t lcid, bool granted) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t response; + uint64_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = RX_INTENT_REQ_ACK_CMD, + cmd.lcid = lcid; + if (granted) + cmd.response = 1; + else + cmd.response = 0; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_cmd_set_sigs() - Convert a signals ack cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @sigs: The signals to encode. + * + * Return: 0 on success or standard Linux error code. + */ +static int tx_cmd_set_sigs(struct glink_transport_if *if_ptr, uint32_t lcid, + uint32_t sigs) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t sigs; + uint64_t reserved; + }; + struct command cmd; + struct edge_info *einfo; + int rcu_id; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = SIGNALS_CMD, + cmd.lcid = lcid; + cmd.sigs = sigs; + + glink_spi_xprt_tx_cmd(einfo, &cmd, sizeof(cmd)); + + srcu_read_unlock(&einfo->use_ref, rcu_id); + return 0; +} + +/** + * tx_data() - convert a data/tracer_pkt to wire format and transmit + * @if_ptr: The transport to transmit on. + * @cmd_id: The command ID to transmit. + * @lcid: The local channel id to encode. + * @pctx: The data to encode. + * + * Return: Number of bytes written or standard Linux error code. + */ +static int tx_data(struct glink_transport_if *if_ptr, uint16_t cmd_id, + uint32_t lcid, struct glink_core_tx_pkt *pctx) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t riid; + uint64_t reserved; + uint32_t size; + uint32_t size_left; + uint64_t addr; + }; + struct command cmd; + struct edge_info *einfo; + uint32_t size; + void *data_start, *dst = NULL; + size_t tx_size = 0; + int rcu_id; + + if (pctx->size < pctx->size_remaining) { + GLINK_ERR("%s: size remaining exceeds size. Resetting.\n", + __func__); + pctx->size_remaining = pctx->size; + } + if (!pctx->size_remaining) + return 0; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + if (cmd_id == TX_DATA_CMD) { + if (pctx->size_remaining == pctx->size) + cmd.id = TX_DATA_CMD; + else + cmd.id = TX_DATA_CONT_CMD; + } else { + if (pctx->size_remaining == pctx->size) + cmd.id = TRACER_PKT_CMD; + else + cmd.id = TRACER_PKT_CONT_CMD; + } + cmd.lcid = lcid; + cmd.riid = pctx->riid; + data_start = get_tx_vaddr(pctx, pctx->size - pctx->size_remaining, + &tx_size); + if (unlikely(!data_start)) { + GLINK_ERR("%s: invalid data_start\n", __func__); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EINVAL; + } + if (tx_size & (XPRT_ALIGNMENT - 1)) + tx_size = ALIGN(tx_size - SHORT_PKT_SIZE, XPRT_ALIGNMENT); + if (likely(pctx->cookie)) + dst = pctx->cookie + (pctx->size - pctx->size_remaining); + + mutex_lock(&einfo->write_lock); + size = glink_spi_xprt_write_avail(einfo); + /* Need enough space to write the command */ + if (size <= sizeof(cmd)) { + einfo->tx_resume_needed = true; + mutex_unlock(&einfo->write_lock); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EAGAIN; + } + cmd.addr = 0; + cmd.size = tx_size; + pctx->size_remaining -= tx_size; + cmd.size_left = pctx->size_remaining; + if (cmd.id == TRACER_PKT_CMD) + tracer_pkt_log_event((void *)(pctx->data), GLINK_XPRT_TX); + + if (!strcmp(einfo->xprt_cfg.edge, "wdsp")) + wdsp_resume(&einfo->cmpnt); + glink_spi_xprt_tx_data(einfo, data_start, dst, tx_size); + glink_spi_xprt_tx_cmd_safe(einfo, &cmd, sizeof(cmd)); + GLINK_DBG("%s %s: lcid[%u] riid[%u] cmd[%d], size[%d], size_left[%d]\n", + "", __func__, cmd.lcid, cmd.riid, cmd.id, cmd.size, + cmd.size_left); + mutex_unlock(&einfo->write_lock); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return cmd.size; +} + +/** + * tx_short_data() - Tansmit a short packet in band along with command + * @if_ptr: The transport to transmit on. + * @cmd_id: The command ID to transmit. + * @lcid: The local channel id to encode. + * @pctx: The data to encode. + * + * Return: Number of bytes written or standard Linux error code. + */ +static int tx_short_data(struct glink_transport_if *if_ptr, + uint32_t lcid, struct glink_core_tx_pkt *pctx) +{ + struct command { + uint16_t id; + uint16_t lcid; + uint32_t riid; + uint32_t size; + uint32_t size_left; + unsigned char data[SHORT_PKT_SIZE]; + }; + struct command cmd; + struct edge_info *einfo; + uint32_t size; + void *data_start; + size_t tx_size = 0; + int rcu_id; + + if (pctx->size < pctx->size_remaining) { + GLINK_ERR("%s: size remaining exceeds size. Resetting.\n", + __func__); + pctx->size_remaining = pctx->size; + } + if (!pctx->size_remaining) + return 0; + + memset(&cmd, 0, sizeof(cmd)); + einfo = container_of(if_ptr, struct edge_info, xprt_if); + + rcu_id = srcu_read_lock(&einfo->use_ref); + if (einfo->in_ssr) { + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EFAULT; + } + + cmd.id = TX_SHORT_DATA_CMD; + cmd.lcid = lcid; + cmd.riid = pctx->riid; + data_start = get_tx_vaddr(pctx, pctx->size - pctx->size_remaining, + &tx_size); + if (unlikely(!data_start || tx_size > SHORT_PKT_SIZE)) { + GLINK_ERR("%s: invalid data_start %p or tx_size %zu\n", + __func__, data_start, tx_size); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EINVAL; + } + + mutex_lock(&einfo->write_lock); + size = glink_spi_xprt_write_avail(einfo); + /* Need enough space to write the command */ + if (size <= sizeof(cmd)) { + einfo->tx_resume_needed = true; + mutex_unlock(&einfo->write_lock); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return -EAGAIN; + } + cmd.size = tx_size; + pctx->size_remaining -= tx_size; + cmd.size_left = pctx->size_remaining; + memcpy(cmd.data, data_start, tx_size); + if (!strcmp(einfo->xprt_cfg.edge, "wdsp")) + wdsp_resume(&einfo->cmpnt); + glink_spi_xprt_tx_cmd_safe(einfo, &cmd, sizeof(cmd)); + GLINK_DBG("%s %s: lcid[%u] riid[%u] cmd[%d], size[%d], size_left[%d]\n", + "", __func__, cmd.lcid, cmd.riid, cmd.id, cmd.size, + cmd.size_left); + mutex_unlock(&einfo->write_lock); + srcu_read_unlock(&einfo->use_ref, rcu_id); + return cmd.size; +} + +/** + * tx() - convert a data transmit cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @pctx: The data to encode. + * + * Return: Number of bytes written or standard Linux error code. + */ +static int tx(struct glink_transport_if *if_ptr, uint32_t lcid, + struct glink_core_tx_pkt *pctx) +{ + if (pctx->size_remaining <= SHORT_PKT_SIZE) + return tx_short_data(if_ptr, lcid, pctx); + return tx_data(if_ptr, TX_DATA_CMD, lcid, pctx); +} + +/** + * tx_cmd_tracer_pkt() - convert a tracer packet cmd to wire format and transmit + * @if_ptr: The transport to transmit on. + * @lcid: The local channel id to encode. + * @pctx: The data to encode. + * + * Return: Number of bytes written or standard Linux error code. + */ +static int tx_cmd_tracer_pkt(struct glink_transport_if *if_ptr, uint32_t lcid, + struct glink_core_tx_pkt *pctx) +{ + return tx_data(if_ptr, TRACER_PKT_CMD, lcid, pctx); +} + +/** + * int wait_link_down() - Check status of read/write indices + * @if_ptr: The transport to check + * + * Return: 1 if indices are all zero, 0 otherwise + */ +static int wait_link_down(struct glink_transport_if *if_ptr) +{ + return 0; +} + +/** + * get_power_vote_ramp_time() - Get the ramp time required for the power + * votes to be applied + * @if_ptr: The transport interface on which power voting is requested. + * @state: The power state for which ramp time is required. + * + * Return: The ramp time specific to the power state, standard error otherwise. + */ +static unsigned long get_power_vote_ramp_time( + struct glink_transport_if *if_ptr, uint32_t state) +{ + return 0; +} + +/** + * power_vote() - Update the power votes to meet qos requirement + * @if_ptr: The transport interface on which power voting is requested. + * @state: The power state for which the voting should be done. + * + * Return: 0 on Success, standard error otherwise. + */ +static int power_vote(struct glink_transport_if *if_ptr, uint32_t state) +{ + unsigned long flags; + struct edge_info *einfo; + + einfo = container_of(if_ptr, struct edge_info, xprt_if); + spin_lock_irqsave(&einfo->activity_lock, flags); + einfo->activity_flag |= ACTIVE_TX; + spin_unlock_irqrestore(&einfo->activity_lock, flags); + return 0; +} + +/** + * power_unvote() - Remove the all the power votes + * @if_ptr: The transport interface on which power voting is requested. + * + * Return: 0 on Success, standard error otherwise. + */ +static int power_unvote(struct glink_transport_if *if_ptr) +{ + unsigned long flags; + struct edge_info *einfo; + + einfo = container_of(if_ptr, struct edge_info, xprt_if); + spin_lock_irqsave(&einfo->activity_lock, flags); + einfo->activity_flag &= ~ACTIVE_TX; + spin_unlock_irqrestore(&einfo->activity_lock, flags); + return 0; +} + +static int glink_wdsp_cmpnt_init(struct device *dev, void *priv_data) +{ + return 0; +} + +static int glink_wdsp_cmpnt_deinit(struct device *dev, void *priv_data) +{ + return 0; +} + +static int glink_wdsp_cmpnt_event_handler(struct device *dev, + void *priv_data, enum wdsp_event_type event, void *data) +{ + struct edge_info *einfo = dev_get_drvdata(dev); + struct glink_cmpnt *cmpnt = &einfo->cmpnt; + struct device *sdev; + struct spi_device *spi_dev; + + switch (event) { + case WDSP_EVENT_PRE_BOOTUP: + if (cmpnt && cmpnt->master_dev && + cmpnt->master_ops && + cmpnt->master_ops->get_dev_for_cmpnt) + sdev = cmpnt->master_ops->get_dev_for_cmpnt( + cmpnt->master_dev, WDSP_CMPNT_TRANSPORT); + else + sdev = NULL; + + if (!sdev) { + dev_err(dev, "%s: Failed to get transport device\n", + __func__); + break; + } + + spi_dev = to_spi_device(sdev); + einfo->spi_dev = spi_dev; + break; + case WDSP_EVENT_IPC1_INTR: + queue_kthread_work(&einfo->kworker, &einfo->kwork); + break; + default: + pr_debug("%s: unhandled event %d", __func__, event); + break; + } + + return 0; +} + +/* glink_wdsp_cmpnt_ops - Callback operations registered wtih WDSP framework */ +static struct wdsp_cmpnt_ops glink_wdsp_cmpnt_ops = { + .init = glink_wdsp_cmpnt_init, + .deinit = glink_wdsp_cmpnt_deinit, + .event_handler = glink_wdsp_cmpnt_event_handler, +}; + +static int glink_component_bind(struct device *dev, struct device *master, + void *data) +{ + struct edge_info *einfo = dev_get_drvdata(dev); + struct glink_cmpnt *cmpnt = &einfo->cmpnt; + int ret = 0; + + cmpnt->master_dev = master; + cmpnt->master_ops = data; + + if (cmpnt->master_ops && cmpnt->master_ops->register_cmpnt_ops) + ret = cmpnt->master_ops->register_cmpnt_ops(master, dev, einfo, + &glink_wdsp_cmpnt_ops); + else + ret = -EINVAL; + + if (ret) + dev_err(dev, "%s: register_cmpnt_ops failed, err = %d\n", + __func__, ret); + return ret; +} + +static void glink_component_unbind(struct device *dev, struct device *master, + void *data) +{ + struct edge_info *einfo = dev_get_drvdata(dev); + struct glink_cmpnt *cmpnt = &einfo->cmpnt; + + cmpnt->master_dev = NULL; + cmpnt->master_ops = NULL; +} + +static const struct component_ops glink_component_ops = { + .bind = glink_component_bind, + .unbind = glink_component_unbind, +}; + +/** + * init_xprt_if() - Initialize the xprt_if for an edge + * @einfo: The edge to initialize. + */ +static void init_xprt_if(struct edge_info *einfo) +{ + einfo->xprt_if.tx_cmd_version = tx_cmd_version; + einfo->xprt_if.tx_cmd_version_ack = tx_cmd_version_ack; + einfo->xprt_if.set_version = set_version; + einfo->xprt_if.tx_cmd_ch_open = tx_cmd_ch_open; + einfo->xprt_if.tx_cmd_ch_close = tx_cmd_ch_close; + einfo->xprt_if.tx_cmd_ch_remote_open_ack = tx_cmd_ch_remote_open_ack; + einfo->xprt_if.tx_cmd_ch_remote_close_ack = tx_cmd_ch_remote_close_ack; + einfo->xprt_if.ssr = ssr; + einfo->xprt_if.allocate_rx_intent = allocate_rx_intent; + einfo->xprt_if.deallocate_rx_intent = deallocate_rx_intent; + einfo->xprt_if.tx_cmd_local_rx_intent = tx_cmd_local_rx_intent; + einfo->xprt_if.tx_cmd_local_rx_done = tx_cmd_local_rx_done; + einfo->xprt_if.tx = tx; + einfo->xprt_if.tx_cmd_rx_intent_req = tx_cmd_rx_intent_req; + einfo->xprt_if.tx_cmd_remote_rx_intent_req_ack = + tx_cmd_remote_rx_intent_req_ack; + einfo->xprt_if.tx_cmd_set_sigs = tx_cmd_set_sigs; + einfo->xprt_if.wait_link_down = wait_link_down; + einfo->xprt_if.tx_cmd_tracer_pkt = tx_cmd_tracer_pkt; + einfo->xprt_if.get_power_vote_ramp_time = get_power_vote_ramp_time; + einfo->xprt_if.power_vote = power_vote; + einfo->xprt_if.power_unvote = power_unvote; +} + +/** + * init_xprt_cfg() - Initialize the xprt_cfg for an edge + * @einfo: The edge to initialize. + * @name: The name of the remote side this edge communicates to. + */ +static void init_xprt_cfg(struct edge_info *einfo, const char *name) +{ + einfo->xprt_cfg.name = XPRT_NAME; + einfo->xprt_cfg.edge = name; + einfo->xprt_cfg.versions = versions; + einfo->xprt_cfg.versions_entries = ARRAY_SIZE(versions); + einfo->xprt_cfg.max_cid = SZ_64K; + einfo->xprt_cfg.max_iid = SZ_2G; +} + +/** + * parse_qos_dt_params() - Parse the power states from DT + * @dev: Reference to the platform device for a specific edge. + * @einfo: Edge information for the edge probe function is called. + * + * Return: 0 on success, standard error code otherwise. + */ +static int parse_qos_dt_params(struct device_node *node, + struct edge_info *einfo) +{ + int rc; + int i; + char *key; + uint32_t *arr32; + uint32_t num_states; + + key = "qcom,ramp-time"; + if (!of_find_property(node, key, &num_states)) + return -ENODEV; + + num_states /= sizeof(uint32_t); + + einfo->num_pw_states = num_states; + + arr32 = kmalloc_array(num_states, sizeof(uint32_t), GFP_KERNEL); + if (!arr32) + return -ENOMEM; + + einfo->ramp_time_us = kmalloc_array(num_states, sizeof(unsigned long), + GFP_KERNEL); + if (!einfo->ramp_time_us) { + rc = -ENOMEM; + goto mem_alloc_fail; + } + + rc = of_property_read_u32_array(node, key, arr32, num_states); + if (rc) { + rc = -ENODEV; + goto invalid_key; + } + for (i = 0; i < num_states; i++) + einfo->ramp_time_us[i] = arr32[i]; + + kfree(arr32); + return 0; + +invalid_key: + kfree(einfo->ramp_time_us); +mem_alloc_fail: + kfree(arr32); + return rc; +} + +/** + * parse_qos_dt_params() - Parse any remote FIFO configuration + * @node: Reference to the platform device for a specific edge. + * @einfo: Edge information for the edge probe function is called. + * + * Return: 0 on success, standard error code otherwise. + */ +static int parse_remote_fifo_cfg(struct device_node *node, + struct edge_info *einfo) +{ + int rc; + char *key; + + key = "qcom,out-read-idx-reg"; + rc = of_property_read_u32(node, key, &einfo->tx_fifo_read_reg_addr); + if (rc) + goto key_error; + + key = "qcom,out-write-idx-reg"; + rc = of_property_read_u32(node, key, &einfo->tx_fifo_write_reg_addr); + if (rc) + goto key_error; + + key = "qcom,in-read-idx-reg"; + rc = of_property_read_u32(node, key, &einfo->rx_fifo_read_reg_addr); + if (rc) + goto key_error; + + key = "qcom,in-write-idx-reg"; + rc = of_property_read_u32(node, key, &einfo->rx_fifo_write_reg_addr); + if (rc) + goto key_error; + return 0; + +key_error: + pr_err("%s: Error %d parsing key %s\n", __func__, rc, key); + return rc; +} + +static int glink_spi_probe(struct platform_device *pdev) +{ + struct device_node *node; + struct device_node *phandle_node; + struct edge_info *einfo; + int rc; + char *key; + const char *subsys_name; + unsigned long flags; + + node = pdev->dev.of_node; + + einfo = kzalloc(sizeof(*einfo), GFP_KERNEL); + if (!einfo) { + rc = -ENOMEM; + goto edge_info_alloc_fail; + } + + key = "label"; + subsys_name = of_get_property(node, key, NULL); + if (!subsys_name) { + pr_err("%s: missing key %s\n", __func__, key); + rc = -ENODEV; + goto missing_key; + } + strlcpy(einfo->subsys_name, subsys_name, sizeof(einfo->subsys_name)); + + init_xprt_cfg(einfo, subsys_name); + init_xprt_if(einfo); + + einfo->in_ssr = true; + einfo->fifo_size = DEFAULT_FIFO_SIZE; + init_kthread_work(&einfo->kwork, rx_worker); + init_kthread_worker(&einfo->kworker); + init_srcu_struct(&einfo->use_ref); + mutex_init(&einfo->write_lock); + init_waitqueue_head(&einfo->tx_blocked_queue); + spin_lock_init(&einfo->activity_lock); + + spin_lock_irqsave(&edge_infos_lock, flags); + list_add_tail(&einfo->list, &edge_infos); + spin_unlock_irqrestore(&edge_infos_lock, flags); + + einfo->task = kthread_run(kthread_worker_fn, &einfo->kworker, + "spi_%s", subsys_name); + if (IS_ERR(einfo->task)) { + rc = PTR_ERR(einfo->task); + pr_err("%s: kthread run failed %d\n", __func__, rc); + goto kthread_fail; + } + + key = "qcom,remote-fifo-config"; + phandle_node = of_parse_phandle(node, key, 0); + if (phandle_node) + parse_remote_fifo_cfg(phandle_node, einfo); + + key = "qcom,qos-config"; + phandle_node = of_parse_phandle(node, key, 0); + if (phandle_node && !(of_get_glink_core_qos_cfg(phandle_node, + &einfo->xprt_cfg))) + parse_qos_dt_params(node, einfo); + + rc = glink_core_register_transport(&einfo->xprt_if, &einfo->xprt_cfg); + if (rc == -EPROBE_DEFER) + goto reg_xprt_fail; + if (rc) { + pr_err("%s: glink core register transport failed: %d\n", + __func__, rc); + goto reg_xprt_fail; + } + + dev_set_drvdata(&pdev->dev, einfo); + if (!strcmp(einfo->xprt_cfg.edge, "wdsp")) { + rc = component_add(&pdev->dev, &glink_component_ops); + if (rc) { + pr_err("%s: component_add failed, err = %d\n", + __func__, rc); + rc = -ENODEV; + goto reg_cmpnt_fail; + } + } + return 0; + +reg_cmpnt_fail: + dev_set_drvdata(&pdev->dev, NULL); + glink_core_unregister_transport(&einfo->xprt_if); +reg_xprt_fail: + flush_kthread_worker(&einfo->kworker); + kthread_stop(einfo->task); + einfo->task = NULL; +kthread_fail: + spin_lock_irqsave(&edge_infos_lock, flags); + list_del(&einfo->list); + spin_unlock_irqrestore(&edge_infos_lock, flags); +missing_key: + kfree(einfo); +edge_info_alloc_fail: + return rc; +} + +static int glink_spi_remove(struct platform_device *pdev) +{ + struct edge_info *einfo; + unsigned long flags; + + einfo = (struct edge_info *)dev_get_drvdata(&pdev->dev); + glink_core_unregister_transport(&einfo->xprt_if); + flush_kthread_worker(&einfo->kworker); + kthread_stop(einfo->task); + einfo->task = NULL; + spin_lock_irqsave(&edge_infos_lock, flags); + list_del(&einfo->list); + spin_unlock_irqrestore(&edge_infos_lock, flags); + kfree(einfo); + return 0; +} + +static int glink_spi_resume(struct platform_device *pdev) +{ + return 0; +} + +static int glink_spi_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned long flags; + struct edge_info *einfo; + bool suspend; + int rc = -EBUSY; + + einfo = (struct edge_info *)dev_get_drvdata(&pdev->dev); + if (strcmp(einfo->xprt_cfg.edge, "wdsp")) + return 0; + + spin_lock_irqsave(&einfo->activity_lock, flags); + suspend = !(einfo->activity_flag); + spin_unlock_irqrestore(&einfo->activity_lock, flags); + if (suspend) + rc = wdsp_suspend(&einfo->cmpnt); + if (rc < 0) + pr_err("%s: Could not suspend activity_flag %d, rc %d\n", + __func__, einfo->activity_flag, rc); + return rc; +} + +static const struct of_device_id spi_match_table[] = { + { .compatible = "qcom,glink-spi-xprt" }, + {}, +}; + +static struct platform_driver glink_spi_driver = { + .probe = glink_spi_probe, + .remove = glink_spi_remove, + .resume = glink_spi_resume, + .suspend = glink_spi_suspend, + .driver = { + .name = "msm_glink_spi_xprt", + .owner = THIS_MODULE, + .of_match_table = spi_match_table, + }, +}; + +static int __init glink_spi_xprt_init(void) +{ + int rc; + + rc = platform_driver_register(&glink_spi_driver); + if (rc) + pr_err("%s: glink_spi register failed %d\n", __func__, rc); + + return rc; +} +module_init(glink_spi_xprt_init); + +static void __exit glink_spi_xprt_exit(void) +{ + platform_driver_unregister(&glink_spi_driver); +} +module_exit(glink_spi_xprt_exit); + +MODULE_DESCRIPTION("MSM G-Link SPI Transport"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/glink_xprt_if.h b/drivers/soc/qcom/glink_xprt_if.h index 6242e867fe72..f4d5a3b303db 100644 --- a/drivers/soc/qcom/glink_xprt_if.h +++ b/drivers/soc/qcom/glink_xprt_if.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -28,6 +28,7 @@ enum buf_type { enum xprt_ids { SMEM_XPRT_ID = 100, + SPIV2_XPRT_ID = SMEM_XPRT_ID, SMD_TRANS_XPRT_ID = 200, LLOOP_XPRT_ID = 300, MOCK_XPRT_HIGH_ID = 390, @@ -56,6 +57,7 @@ enum xprt_ids { * @iovec: Pointer to the vector buffer packet. * @vprovider: Packet-specific virtual buffer provider function. * @pprovider: Packet-specific physical buffer provider function. + * @cookie: Transport-specific cookie * @pkt_ref: Active references to the packet. */ struct glink_core_tx_pkt { @@ -73,6 +75,7 @@ struct glink_core_tx_pkt { void *iovec; void * (*vprovider)(void *iovec, size_t offset, size_t *size); void * (*pprovider)(void *iovec, size_t offset, size_t *size); + void *cookie; struct rwref_lock pkt_ref; };