From f6e4c0aed16f79ec3c8a716fe8d04af1f138e6ad Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Fri, 11 Mar 2016 15:39:40 -0800 Subject: [PATCH] platform: msm: Add snapshot of USB BAM driver Add USB BAM driver used which allows USB BAM to communicate with other other peripherals (e.g QDSS or IPA) BAM. This snapshot is taken as of msm-3.18 commit d5809484bb1b. Signed-off-by: Mayank Rana --- .../bindings/platform/msm/usb-bam.txt | 114 + drivers/platform/msm/Kconfig | 8 + drivers/platform/msm/Makefile | 1 + drivers/platform/msm/usb_bam.c | 3528 +++++++++++++++++ include/linux/usb_bam.h | 529 +++ 5 files changed, 4180 insertions(+) create mode 100644 Documentation/devicetree/bindings/platform/msm/usb-bam.txt create mode 100644 drivers/platform/msm/usb_bam.c create mode 100644 include/linux/usb_bam.h diff --git a/Documentation/devicetree/bindings/platform/msm/usb-bam.txt b/Documentation/devicetree/bindings/platform/msm/usb-bam.txt new file mode 100644 index 000000000000..7d2e31ec84f5 --- /dev/null +++ b/Documentation/devicetree/bindings/platform/msm/usb-bam.txt @@ -0,0 +1,114 @@ +MSM USB Bus Access Manager (BAM) + +This describes the device used to interface the USB controller +with the Smart Peripheral Subsystem (SPS). The BAM serves to +connect USB directly with other peer peripherals in the system +and is statically configured with a number of unidirectional pipes. + +Required properties: +- compatible: should be "qcom,usb-bam-msm" +- reg : pair of physical base addresses and region size of BAM device +- interrupts: IRQ line for BAM device +- qcom,usb-bam-num-pipes: max number of pipes that can be used +- qcom,bam-type: BAM type can be one of + 0 - SSUSB_BAM + 1 - HSUSB_BAM + 2 - HSIC_BAM + +Optional properties: +- qcom,usb-bam-fifo-baseaddr: base address for bam pipe's data and descriptor + fifos. This can be on chip memory (ocimem). This + property is required if sub-node's mem-type is ocimem or usb private mem. +- qcom,ignore-core-reset-ack: If present then BAM ignores ACK from USB core + while performing PIPE RESET +- qcom,disable-clk-gating: If present then disable BAM clock gating. +- qcom,usb-bam-override-threshold: If present then the default 512 byte threshold + is overridden. This threshold configures the threshold value for Read/Write + event generation by the BAM towards another BAM. +- qcom,usb-bam-max-mbps-highspeed: max mbps in high speed connection + for either rx or tx direction. +- qcom,usb-bam-max-mbps-superspeed: max mbps in super speed connection + for either rx or tx direction. +- qcom,bam-mode: BAM mode can be one of. + 0 - BAM_MODE_DEVICE + 1 - BAM_MODE_HOST + (If not set will be set by default to BAM_MODE_DEVICE) +- qcom,reset-bam-on-connect: If present then BAM is RESET before connecting + pipe. This may be required if BAM peripheral is also reset before connect. +- qcom,reset-bam-on-disconnect: If present then BAM is RESET after disconnecting pipes. +- qcom,enable-hsusb-bam-on-boot: If present then BAM is enabled at bootup itself. + +A number of USB BAM pipe parameters are represented as sub-nodes: + +Subnode Required: +- label: a string describing uniquely the usb bam pipe. The string can be + constracted as follows: ---. + core options: hsusb, ssusb/dwc3, hsic + peer options: qdss, ipa + direction options: in (from peer to usb), out (from usb to peer) + pipe num options: 0..127 +- qcom,usb-bam-mem-type: Type of memory used by this PIPE. Can be one of + 0 - Uses SPS's dedicated pipe memory + 1 - System RAM allocated by driver + 2 - OCI memory residing @ 'qcom,usb-bam-fifo-baseaddr' +- qcom,dir: pipe direction + 0 - from usb (out) + 1 - to usb (in) +- qcom,pipe-num: pipe number +- qcom,peer-bam: peer BAM can be one of + 0 - QDSS_P_BAM + 1 - IPA_P_BAM +- qcom,data-fifo-size: data fifo size +- qcom,descriptor-fifo-size: descriptor fifo size + +Optional Properties for Subnode: +- qcom,peer-bam-physical-address: peer BAM's physical address. + Not specified for IPA and used only for qdss connection +- qcom,dst-bam-pipe-index: destination BAM pipe index +- qcom,src-bam-pipe-index: source BAM pipe index +- qcom,data-fifo-offset: data fifo offset address +- qcom,descriptor-fifo-offset: descriptor fifo offset address +- qcom,pipe-connection-type: type of pipe connection. Can be one of + 0 - BAM2BAM (default if not specified) + 1 - SYS2BAM (only supported on UL) + +Example USB BAM controller device node: + + qcom,usbbam@f9a44000 { + compatible = "qcom,usb-bam-msm"; + reg = <0xf9a44000 0x11000>; + interrupts = <0 135 0>; + qcom,usb-bam-num-pipes = <16>; + qcom,ignore-core-reset-ack; + qcom,disable-clk-gating; + qcom,usb-bam-max-mbps-highspeed = <400>; + qcom,usb-bam-max-mbps-superspeed = <3600>; + qcom,bam-type = <1>; + qcom,bam-mode = <0>; + + qcom,pipe0 { + label = "hsusb-ipa-out-0"; + qcom,usb-bam-mem-type = <0>; + qcom,dir = <0>; + qcom,pipe-num = <0>; + qcom,peer-bam = <2>; + qcom,src-bam-pipe-index = <1>; + qcom,data-fifo-offset = <0x2200>; + qcom,data-fifo-size = <0x1e00>; + qcom,descriptor-fifo-offset = <0x2100>; + qcom,descriptor-fifo-size = <0x100>; + }; + qcom,pipe1 { + label = "hsusb-ipa-in-0"; + qcom,usb-bam-mem-type = <0>; + qcom,dir = <1>; + qcom,pipe-num = <0>; + qcom,peer-bam = <2>; + qcom,dst-bam-pipe-index = <0>; + qcom,data-fifo-offset = <0x300>; + qcom,data-fifo-size = <0x1e00>; + qcom,descriptor-fifo-offset = <0>; + qcom,descriptor-fifo-size = <0x300>; + }; + }; + diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 29aa3d7f38d1..19510e5c2279 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -190,4 +190,12 @@ config SEEMP_CORE anomalies in various activities. It records task activities in a log and rates the actions according to whether a typical user would use the tools. + +config USB_BAM + bool "USB BAM Driver" + depends on SPS && USB_GADGET + help + Enabling this option adds USB BAM Driver. + USB BAM driver was added to supports SPS Peripheral-to-Peripheral + transfers between the USB and other peripheral. endmenu diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index 7c6d5dd279d0..c33f5e53c1b3 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_GPIO_USB_DETECT) += gpio-usbdetect.o obj-$(CONFIG_MSM_11AD) += msm_11ad/ obj-$(CONFIG_SEEMP_CORE) += seemp_core/ obj-$(CONFIG_SSM) += ssm.o +obj-$(CONFIG_USB_BAM) += usb_bam.o diff --git a/drivers/platform/msm/usb_bam.c b/drivers/platform/msm/usb_bam.c new file mode 100644 index 000000000000..43188c9d690e --- /dev/null +++ b/drivers/platform/msm/usb_bam.c @@ -0,0 +1,3528 @@ +/* Copyright (c) 2011-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 + +#define USB_THRESHOLD 512 +#define USB_BAM_MAX_STR_LEN 50 +#define USB_BAM_TIMEOUT (10*HZ) +#define DBG_MAX_MSG 512UL +#define DBG_MSG_LEN 160UL +#define TIME_BUF_LEN 17 +#define DBG_EVENT_LEN 143 + +#define USB_BAM_NR_PORTS 4 + +/* Additional memory to be allocated than required data fifo size */ +#define DATA_FIFO_EXTRA_MEM_ALLOC_SIZE 512 + +#define ARRAY_INDEX_FROM_ADDR(base, addr) ((addr) - (base)) + +#define ENABLE_EVENT_LOG 1 +static unsigned int enable_event_log = ENABLE_EVENT_LOG; +module_param(enable_event_log, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_event_log, "enable event logging in debug buffer"); + +#define LOGLEVEL_NONE 8 +#define LOGLEVEL_DEBUG 7 +#define LOGLEVEL_ERR 3 + +#define log_event(log_level, x...) \ +do { \ + unsigned long flags; \ + char *buf; \ + if (log_level == LOGLEVEL_DEBUG) \ + pr_debug(x); \ + else if (log_level == LOGLEVEL_ERR) \ + pr_err(x); \ + if (enable_event_log) { \ + write_lock_irqsave(&usb_bam_dbg.lck, flags); \ + buf = usb_bam_dbg.buf[usb_bam_dbg.idx]; \ + put_timestamp(buf); \ + snprintf(&buf[TIME_BUF_LEN - 1], DBG_EVENT_LEN, x); \ + usb_bam_dbg.idx = (usb_bam_dbg.idx + 1) % DBG_MAX_MSG; \ + write_unlock_irqrestore(&usb_bam_dbg.lck, flags); \ + } \ +} while (0) + +#define log_event_none(x, ...) log_event(LOGLEVEL_NONE, x, ##__VA_ARGS__) +#define log_event_dbg(x, ...) log_event(LOGLEVEL_DEBUG, x, ##__VA_ARGS__) +#define log_event_err(x, ...) log_event(LOGLEVEL_ERR, x, ##__VA_ARGS__) + +struct usb_bam_sps_type { + struct sps_pipe **sps_pipes; + struct sps_connect *sps_connections; +}; + +/** +* struct usb_bam_ctx_type - represents the usb bam driver entity +* @usb_bam_sps: holds the sps pipes the usb bam driver holds +* against the sps driver. +* @usb_bam_pdev: the platfrom device that represents the usb bam. +* @usb_bam_wq: Worqueue used for managing states of reset against +* a peer bam. +* @max_connections: The maximum number of pipes that are configured +* in the platform data. +* @h_bam: This array stores for each BAM ("ssusb", "hsusb" or "hsic") the +* handle/device of the sps driver. +* @pipes_enabled_per_bam: This array stores for each BAM +* ("ssusb", "hsusb" or "hsic") the number of pipes currently enabled. +* @inactivity_timer_ms: The timeout configuration per each bam for inactivity +* timer feature. +* @is_bam_inactivity: Is there no activity on all pipes belongs to a +* specific bam. (no activity = no data is pulled or pushed +* from/into ones of the pipes). +* @usb_bam_connections: array (allocated on probe) having all BAM connections +* @usb_bam_lock: to protect fields of ctx or usb_bam_connections +*/ +struct usb_bam_ctx_type { + struct usb_bam_sps_type usb_bam_sps; + struct resource *io_res; + void __iomem *regs; + int irq; + struct platform_device *usb_bam_pdev; + struct workqueue_struct *usb_bam_wq; + u8 max_connections; + unsigned long h_bam; + u8 pipes_enabled_per_bam; + u32 inactivity_timer_ms; + bool is_bam_inactivity; + struct usb_bam_pipe_connect *usb_bam_connections; + spinlock_t usb_bam_lock; +}; + +static char *bam_enable_strings[MAX_BAMS] = { + [DWC3_CTRL] = "ssusb", + [CI_CTRL] = "hsusb", + [HSIC_CTRL] = "hsic", +}; + +/* + * CI_CTRL & DWC3_CTRL shouldn't be used simultaneously + * since both share the same prod & cons rm resourses + */ +static enum ipa_client_type ipa_rm_resource_prod[MAX_BAMS] = { + [CI_CTRL] = IPA_RM_RESOURCE_USB_PROD, + [HSIC_CTRL] = IPA_RM_RESOURCE_HSIC_PROD, + [DWC3_CTRL] = IPA_RM_RESOURCE_USB_PROD, +}; + +static enum ipa_client_type ipa_rm_resource_cons[MAX_BAMS] = { + [CI_CTRL] = IPA_RM_RESOURCE_USB_CONS, + [HSIC_CTRL] = IPA_RM_RESOURCE_HSIC_CONS, + [DWC3_CTRL] = IPA_RM_RESOURCE_USB_CONS, +}; + +static int usb_cons_request_resource(void); +static int usb_cons_release_resource(void); +static int ss_usb_cons_request_resource(void); +static int ss_usb_cons_release_resource(void); +static int hsic_cons_request_resource(void); +static int hsic_cons_release_resource(void); + +static int (*request_resource_cb[MAX_BAMS])(void) = { + [CI_CTRL] = usb_cons_request_resource, + [HSIC_CTRL] = hsic_cons_request_resource, + [DWC3_CTRL] = ss_usb_cons_request_resource, +}; + +static int (*release_resource_cb[MAX_BAMS])(void) = { + [CI_CTRL] = usb_cons_release_resource, + [HSIC_CTRL] = hsic_cons_release_resource, + [DWC3_CTRL] = ss_usb_cons_release_resource, +}; + +struct usb_bam_ipa_handshake_info { + enum ipa_rm_event cur_prod_state; + enum ipa_rm_event cur_cons_state; + + enum usb_bam_mode cur_bam_mode; + enum usb_ctrl bam_type; + int connect_complete; + int bus_suspend; + bool disconnected; + bool in_lpm; + u8 prod_pipes_enabled_per_bam; + + int (*wake_cb)(void *); + void *wake_param; + + u32 suspend_src_idx[USB_BAM_NR_PORTS]; + u32 suspend_dst_idx[USB_BAM_NR_PORTS]; + u32 resume_src_idx[USB_BAM_NR_PORTS]; + u32 resume_dst_idx[USB_BAM_NR_PORTS]; + + u32 pipes_to_suspend; + u32 pipes_suspended; + u32 pipes_resumed; + + struct completion prod_avail; + struct completion prod_released; + + struct mutex suspend_resume_mutex; + struct work_struct resume_work; + struct work_struct finish_suspend_work; +}; + +struct usb_bam_host_info { + struct device *dev; + bool in_lpm; +}; + +static spinlock_t usb_bam_ipa_handshake_info_lock; +static struct usb_bam_ipa_handshake_info info[MAX_BAMS]; + +static struct usb_bam_ctx_type msm_usb_bam[MAX_BAMS]; +/* USB bam type used as a peer of the qdss in bam2bam mode */ +static enum usb_ctrl qdss_usb_bam_type; + +static struct usb_bam_host_info host_info[MAX_BAMS]; + +static int __usb_bam_register_wake_cb(enum usb_ctrl bam_type, int idx, + int (*callback)(void *user), + void *param, bool trigger_cb_per_pipe); +static void wait_for_prod_release(enum usb_ctrl cur_bam); +static void usb_bam_start_suspend(struct usb_bam_ipa_handshake_info *info_ptr); + +static struct { + char buf[DBG_MAX_MSG][DBG_MSG_LEN]; /* buffer */ + unsigned idx; /* index */ + rwlock_t lck; /* lock */ +} __maybe_unused usb_bam_dbg = { + .idx = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/*put_timestamp - writes time stamp to buffer */ +static void __maybe_unused put_timestamp(char *tbuf) +{ + unsigned long long t; + unsigned long nanosec_rem; + + t = cpu_clock(smp_processor_id()); + nanosec_rem = do_div(t, 1000000000)/1000; + snprintf(tbuf, TIME_BUF_LEN, "[%5lu.%06lu]: ", (unsigned long)t, + nanosec_rem); +} + +void msm_bam_set_hsic_host_dev(struct device *dev) +{ + if (dev) { + /* Hold the device until allowing lpm */ + info[HSIC_CTRL].in_lpm = false; + log_event_dbg("%s: Getting hsic device %p\n", __func__, dev); + pm_runtime_get(dev); + } else if (host_info[HSIC_CTRL].dev) { + log_event_dbg("%s: Try Putting hsic device %p, lpm:%d\n", + __func__, host_info[HSIC_CTRL].dev, + info[HSIC_CTRL].in_lpm); + /* Just release previous device if not already done */ + if (!info[HSIC_CTRL].in_lpm) { + info[HSIC_CTRL].in_lpm = true; + pm_runtime_put(host_info[HSIC_CTRL].dev); + } + } + + host_info[HSIC_CTRL].dev = dev; + host_info[HSIC_CTRL].in_lpm = false; +} + +void msm_bam_set_usb_host_dev(struct device *dev) +{ + host_info[CI_CTRL].dev = dev; + host_info[CI_CTRL].in_lpm = false; +} + +static int get_bam_type_from_core_name(const char *name) +{ + if (strnstr(name, bam_enable_strings[DWC3_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "dwc3", USB_BAM_MAX_STR_LEN)) + return DWC3_CTRL; + else if (strnstr(name, bam_enable_strings[HSIC_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "ci13xxx_msm_hsic", USB_BAM_MAX_STR_LEN)) + return HSIC_CTRL; + else if (strnstr(name, bam_enable_strings[CI_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "ci", USB_BAM_MAX_STR_LEN)) + return CI_CTRL; + + log_event_err("%s: invalid BAM name(%s)\n", __func__, name); + return -EINVAL; +} + +static void usb_bam_set_inactivity_timer(enum usb_ctrl bam) +{ + struct sps_timer_ctrl timer_ctrl; + struct usb_bam_pipe_connect *pipe_connect; + struct sps_pipe *pipe = NULL; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + int i; + + log_event_dbg("%s: enter\n", __func__); + + /* + * Since we configure global incativity timer for all pipes + * and not per each pipe, it is enough to use some pipe + * handle associated with this bam, so just find the first one. + * This pipe handle is required due to SPS driver API we use below. + */ + for (i = 0; i < ctx->max_connections; i++) { + pipe_connect = &ctx->usb_bam_connections[i]; + if (pipe_connect->bam_type == bam && pipe_connect->enabled) { + pipe = ctx->usb_bam_sps.sps_pipes[i]; + break; + } + } + + if (!pipe) { + pr_warning("%s: Bam %s has no connected pipes\n", __func__, + bam_enable_strings[bam]); + return; + } + + timer_ctrl.op = SPS_TIMER_OP_CONFIG; + timer_ctrl.mode = SPS_TIMER_MODE_ONESHOT; + timer_ctrl.timeout_msec = ctx->inactivity_timer_ms; + sps_timer_ctrl(pipe, &timer_ctrl, NULL); + + timer_ctrl.op = SPS_TIMER_OP_RESET; + sps_timer_ctrl(pipe, &timer_ctrl, NULL); +} + +static int usb_bam_alloc_buffer(struct usb_bam_pipe_connect *pipe_connect) +{ + int ret = 0; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[pipe_connect->bam_type]; + struct msm_usb_bam_platform_data *pdata = + ctx->usb_bam_pdev->dev.platform_data; + struct sps_mem_buffer *data_buf = &(pipe_connect->data_mem_buf); + struct sps_mem_buffer *desc_buf = &(pipe_connect->desc_mem_buf); + + pr_debug("%s: data_fifo size:%x desc_fifo_size:%x\n", + __func__, pipe_connect->data_fifo_size, + pipe_connect->desc_fifo_size); + switch (pipe_connect->mem_type) { + case SPS_PIPE_MEM: + log_event_dbg("%s: USB BAM using SPS pipe memory\n", __func__); + ret = sps_setup_bam2bam_fifo(data_buf, + pipe_connect->data_fifo_base_offset, + pipe_connect->data_fifo_size, 1); + if (ret) { + log_event_err("%s: data fifo setup failure %d\n", + __func__, ret); + goto err_exit; + } + + ret = sps_setup_bam2bam_fifo(desc_buf, + pipe_connect->desc_fifo_base_offset, + pipe_connect->desc_fifo_size, 1); + if (ret) { + log_event_err("%s: desc. fifo setup failure %d\n", + __func__, ret); + goto err_exit; + } + break; + case OCI_MEM: + if (pipe_connect->mem_type == OCI_MEM) + log_event_dbg("%s: USB BAM using ocimem\n", __func__); + + if (data_buf->base) { + log_event_err("%s: Already allocated OCI Memory\n", + __func__); + break; + } + + data_buf->phys_base = pipe_connect->data_fifo_base_offset + + pdata->usb_bam_fifo_baseaddr; + data_buf->size = pipe_connect->data_fifo_size; + data_buf->base = ioremap(data_buf->phys_base, data_buf->size); + if (!data_buf->base) { + log_event_err("%s: ioremap failed for data fifo\n", + __func__); + ret = -ENOMEM; + goto err_exit; + } + memset_io(data_buf->base, 0, data_buf->size); + + desc_buf->phys_base = pipe_connect->desc_fifo_base_offset + + pdata->usb_bam_fifo_baseaddr; + desc_buf->size = pipe_connect->desc_fifo_size; + desc_buf->base = ioremap(desc_buf->phys_base, desc_buf->size); + if (!desc_buf->base) { + log_event_err("%s: ioremap failed for desc fifo\n", + __func__); + iounmap(data_buf->base); + ret = -ENOMEM; + goto err_exit; + } + memset_io(desc_buf->base, 0, desc_buf->size); + break; + case SYSTEM_MEM: + log_event_dbg("%s: USB BAM using system memory\n", __func__); + + if (data_buf->base) { + log_event_err("%s: Already allocated memory\n", + __func__); + break; + } + + /* BAM would use system memory, allocate FIFOs */ + data_buf->size = pipe_connect->data_fifo_size; + /* On platforms which use CI controller, USB HW can fetch + * additional 128 bytes at the end of circular buffer when + * AXI prefetch is enabled and hence requirement is to + * allocate 512 bytes more than required length. + */ + if (pipe_connect->bam_type == CI_CTRL) + data_buf->base = + dma_alloc_coherent(&ctx->usb_bam_pdev->dev, + (pipe_connect->data_fifo_size + + DATA_FIFO_EXTRA_MEM_ALLOC_SIZE), + &(data_buf->phys_base), + GFP_KERNEL); + else + data_buf->base = + dma_alloc_coherent(&ctx->usb_bam_pdev->dev, + pipe_connect->data_fifo_size, + &(data_buf->phys_base), + GFP_KERNEL); + if (!data_buf->base) { + log_event_err("%s: dma_alloc_coherent failed for data fifo\n", + __func__); + ret = -ENOMEM; + goto err_exit; + } + memset(data_buf->base, 0, pipe_connect->data_fifo_size); + + desc_buf->size = pipe_connect->desc_fifo_size; + desc_buf->base = dma_alloc_coherent(&ctx->usb_bam_pdev->dev, + pipe_connect->desc_fifo_size, + &(desc_buf->phys_base), + GFP_KERNEL); + if (!desc_buf->base) { + log_event_err("%s: dma_alloc_coherent failed for desc fifo\n", + __func__); + if (pipe_connect->bam_type == CI_CTRL) + dma_free_coherent(&ctx->usb_bam_pdev->dev, + (pipe_connect->data_fifo_size + + DATA_FIFO_EXTRA_MEM_ALLOC_SIZE), + data_buf->base, + data_buf->phys_base); + else + dma_free_coherent(&ctx->usb_bam_pdev->dev, + pipe_connect->data_fifo_size, + data_buf->base, + data_buf->phys_base); + ret = -ENOMEM; + goto err_exit; + } + memset(desc_buf->base, 0, pipe_connect->desc_fifo_size); + break; + default: + log_event_err("%s: invalid mem type\n", __func__); + ret = -EINVAL; + } + + return ret; + +err_exit: + return ret; +} + +int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + + ret = usb_bam_alloc_buffer(pipe_connect); + if (ret) { + log_event_err("%s(): Error(%d) allocating buffer\n", + __func__, ret); + return ret; + } + return 0; +} + +int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + struct sps_connect *sps_connection = + &ctx->usb_bam_sps.sps_connections[idx]; + + pr_debug("%s(): data size:%x desc size:%x\n", + __func__, sps_connection->data.size, + sps_connection->desc.size); + + switch (pipe_connect->mem_type) { + case SYSTEM_MEM: + log_event_dbg("%s: Freeing system memory used by PIPE\n", + __func__); + if (sps_connection->data.phys_base) { + if (cur_bam == CI_CTRL) + dma_free_coherent(&ctx->usb_bam_pdev->dev, + (sps_connection->data.size + + DATA_FIFO_EXTRA_MEM_ALLOC_SIZE), + sps_connection->data.base, + sps_connection->data.phys_base); + else + dma_free_coherent(&ctx->usb_bam_pdev->dev, + sps_connection->data.size, + sps_connection->data.base, + sps_connection->data.phys_base); + sps_connection->data.phys_base = 0; + pipe_connect->data_mem_buf.base = NULL; + } + if (sps_connection->desc.phys_base) { + dma_free_coherent(&ctx->usb_bam_pdev->dev, + sps_connection->desc.size, + sps_connection->desc.base, + sps_connection->desc.phys_base); + sps_connection->desc.phys_base = 0; + pipe_connect->desc_mem_buf.base = NULL; + } + break; + case OCI_MEM: + log_event_dbg("Freeing oci memory used by BAM PIPE\n"); + if (sps_connection->data.base) { + iounmap(sps_connection->data.base); + sps_connection->data.base = NULL; + pipe_connect->data_mem_buf.base = NULL; + } + if (sps_connection->desc.base) { + iounmap(sps_connection->desc.base); + sps_connection->desc.base = NULL; + pipe_connect->desc_mem_buf.base = NULL; + } + break; + case SPS_PIPE_MEM: + log_event_dbg("%s: nothing to be be\n", __func__); + break; + } + + return 0; +} + +static int connect_pipe(enum usb_ctrl cur_bam, u8 idx, u32 *usb_pipe_idx) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_sps_type usb_bam_sps = ctx->usb_bam_sps; + struct sps_pipe **pipe = &(usb_bam_sps.sps_pipes[idx]); + struct sps_connect *sps_connection = &usb_bam_sps.sps_connections[idx]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + enum usb_bam_pipe_dir dir = pipe_connect->dir; + struct sps_mem_buffer *data_buf = &(pipe_connect->data_mem_buf); + struct sps_mem_buffer *desc_buf = &(pipe_connect->desc_mem_buf); + + *pipe = sps_alloc_endpoint(); + if (*pipe == NULL) { + log_event_err("%s: sps_alloc_endpoint failed\n", __func__); + return -ENOMEM; + } + + ret = sps_get_config(*pipe, sps_connection); + if (ret) { + log_event_err("%s: tx get config failed %d\n", __func__, ret); + goto free_sps_endpoint; + } + + ret = sps_phy2h(pipe_connect->src_phy_addr, &(sps_connection->source)); + if (ret) { + log_event_err("%s: sps_phy2h failed (src BAM) %d\n", + __func__, ret); + goto free_sps_endpoint; + } + + sps_connection->src_pipe_index = pipe_connect->src_pipe_index; + ret = sps_phy2h(pipe_connect->dst_phy_addr, + &(sps_connection->destination)); + if (ret) { + log_event_err("%s: sps_phy2h failed (dst BAM) %d\n", + __func__, ret); + goto free_sps_endpoint; + } + sps_connection->dest_pipe_index = pipe_connect->dst_pipe_index; + + if (dir == USB_TO_PEER_PERIPHERAL) { + sps_connection->mode = SPS_MODE_SRC; + *usb_pipe_idx = pipe_connect->src_pipe_index; + } else { + sps_connection->mode = SPS_MODE_DEST; + *usb_pipe_idx = pipe_connect->dst_pipe_index; + } + + sps_connection->data = *data_buf; + sps_connection->desc = *desc_buf; + sps_connection->event_thresh = 16; + sps_connection->options = SPS_O_AUTO_ENABLE; + + ret = sps_connect(*pipe, sps_connection); + if (ret < 0) { + log_event_err("%s: sps_connect failed %d\n", __func__, ret); + goto error; + } + + return 0; + +error: + sps_disconnect(*pipe); +free_sps_endpoint: + sps_free_endpoint(*pipe); + return ret; +} + +static int connect_pipe_sys2bam_ipa(enum usb_ctrl cur_bam, u8 idx, + struct usb_bam_connect_ipa_params *ipa_params) +{ + int ret; + enum usb_bam_pipe_dir dir = ipa_params->dir; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + struct ipa_sys_connect_params sys_in_params; + unsigned long usb_handle; + phys_addr_t usb_phy_addr; + u32 clnt_hdl = 0; + + memset(&sys_in_params, 0, sizeof(sys_in_params)); + + if (dir == USB_TO_PEER_PERIPHERAL) { + usb_phy_addr = pipe_connect->src_phy_addr; + sys_in_params.client = ipa_params->src_client; + ipa_params->ipa_cons_ep_idx = + ipa_get_ep_mapping(sys_in_params.client); + } else { + usb_phy_addr = pipe_connect->dst_phy_addr; + sys_in_params.client = ipa_params->dst_client; + ipa_params->ipa_prod_ep_idx = + ipa_get_ep_mapping(sys_in_params.client); + } + + log_event_dbg("%s(): ipa_prod_ep_idx:%d ipa_cons_ep_idx:%d\n", + __func__, ipa_params->ipa_prod_ep_idx, + ipa_params->ipa_cons_ep_idx); + + /* Get HSUSB / HSIC bam handle */ + ret = sps_phy2h(usb_phy_addr, &usb_handle); + if (ret) { + log_event_err("%s: sps_phy2h failed (HSUSB/HSIC BAM) %d\n", + __func__, ret); + return ret; + } + + pipe_connect->activity_notify = ipa_params->activity_notify; + pipe_connect->inactivity_notify = ipa_params->inactivity_notify; + pipe_connect->priv = ipa_params->priv; + + /* IPA sys connection params */ + sys_in_params.desc_fifo_sz = pipe_connect->desc_fifo_size; + sys_in_params.priv = ipa_params->priv; + sys_in_params.notify = ipa_params->notify; + sys_in_params.skip_ep_cfg = ipa_params->skip_ep_cfg; + sys_in_params.keep_ipa_awake = ipa_params->keep_ipa_awake; + memcpy(&sys_in_params.ipa_ep_cfg, &ipa_params->ipa_ep_cfg, + sizeof(struct ipa_ep_cfg)); + + ret = ipa_setup_sys_pipe(&sys_in_params, &clnt_hdl); + if (ret) { + log_event_err("%s: ipa_connect failed\n", __func__); + return ret; + } + pipe_connect->ipa_clnt_hdl = clnt_hdl; + if (dir == USB_TO_PEER_PERIPHERAL) + ipa_params->cons_clnt_hdl = clnt_hdl; + else + ipa_params->prod_clnt_hdl = clnt_hdl; + + return 0; +} + +static int connect_pipe_bam2bam_ipa(enum usb_ctrl cur_bam, u8 idx, + struct usb_bam_connect_ipa_params *ipa_params) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_sps_type usb_bam_sps = ctx->usb_bam_sps; + enum usb_bam_pipe_dir dir = ipa_params->dir; + struct sps_pipe **pipe = &(usb_bam_sps.sps_pipes[idx]); + struct sps_connect *sps_connection = &usb_bam_sps.sps_connections[idx]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + struct sps_mem_buffer *data_buf = &(pipe_connect->data_mem_buf); + struct sps_mem_buffer *desc_buf = &(pipe_connect->desc_mem_buf); + struct ipa_connect_params ipa_in_params; + struct ipa_sps_params sps_out_params; + u32 usb_phy_addr; + unsigned long usb_handle; + u32 clnt_hdl = 0; + + memset(&ipa_in_params, 0, sizeof(ipa_in_params)); + memset(&sps_out_params, 0, sizeof(sps_out_params)); + + if (dir == USB_TO_PEER_PERIPHERAL) { + usb_phy_addr = pipe_connect->src_phy_addr; + ipa_in_params.client_ep_idx = pipe_connect->src_pipe_index; + ipa_in_params.client = ipa_params->src_client; + } else { + usb_phy_addr = pipe_connect->dst_phy_addr; + ipa_in_params.client_ep_idx = pipe_connect->dst_pipe_index; + ipa_in_params.client = ipa_params->dst_client; + } + /* Get HSUSB / HSIC bam handle */ + ret = sps_phy2h(usb_phy_addr, &usb_handle); + if (ret) { + log_event_err("%s: sps_phy2h failed (HSUSB/HSIC BAM) %d\n", + __func__, ret); + return ret; + } + + pipe_connect->activity_notify = ipa_params->activity_notify; + pipe_connect->inactivity_notify = ipa_params->inactivity_notify; + pipe_connect->priv = ipa_params->priv; + pipe_connect->reset_pipe_after_lpm = ipa_params->reset_pipe_after_lpm; + + /* IPA input parameters */ + ipa_in_params.client_bam_hdl = usb_handle; + ipa_in_params.desc_fifo_sz = pipe_connect->desc_fifo_size; + ipa_in_params.data_fifo_sz = pipe_connect->data_fifo_size; + ipa_in_params.notify = ipa_params->notify; + ipa_in_params.priv = ipa_params->priv; + ipa_in_params.skip_ep_cfg = ipa_params->skip_ep_cfg; + ipa_in_params.keep_ipa_awake = ipa_params->keep_ipa_awake; + + ipa_in_params.desc = pipe_connect->desc_mem_buf; + ipa_in_params.data = pipe_connect->data_mem_buf; + + memcpy(&ipa_in_params.ipa_ep_cfg, &ipa_params->ipa_ep_cfg, + sizeof(struct ipa_ep_cfg)); + + ret = ipa_connect(&ipa_in_params, &sps_out_params, &clnt_hdl); + if (ret) { + log_event_err("%s: ipa_connect failed\n", __func__); + return ret; + } + pipe_connect->ipa_clnt_hdl = clnt_hdl; + + *pipe = sps_alloc_endpoint(); + if (*pipe == NULL) { + log_event_err("%s: sps_alloc_endpoint failed\n", __func__); + ret = -ENOMEM; + goto disconnect_ipa; + } + + ret = sps_get_config(*pipe, sps_connection); + if (ret) { + log_event_err("%s: tx get config failed %d\n", __func__ , ret); + goto free_sps_endpoints; + } + + if (dir == USB_TO_PEER_PERIPHERAL) { + /* USB src IPA dest */ + sps_connection->mode = SPS_MODE_SRC; + ipa_params->cons_clnt_hdl = clnt_hdl; + sps_connection->source = usb_handle; + sps_connection->destination = sps_out_params.ipa_bam_hdl; + sps_connection->src_pipe_index = pipe_connect->src_pipe_index; + sps_connection->dest_pipe_index = sps_out_params.ipa_ep_idx; + ipa_params->ipa_cons_ep_idx = sps_out_params.ipa_ep_idx; + *(ipa_params->src_pipe) = sps_connection->src_pipe_index; + pipe_connect->dst_pipe_index = sps_out_params.ipa_ep_idx; + log_event_dbg("%s: BAM pipe usb[%x]->ipa[%x] connection\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + sps_connection->options = SPS_O_NO_DISABLE; + } else { + /* IPA src, USB dest */ + sps_connection->mode = SPS_MODE_DEST; + ipa_params->prod_clnt_hdl = clnt_hdl; + sps_connection->source = sps_out_params.ipa_bam_hdl; + sps_connection->destination = usb_handle; + sps_connection->src_pipe_index = sps_out_params.ipa_ep_idx; + ipa_params->ipa_prod_ep_idx = sps_out_params.ipa_ep_idx; + sps_connection->dest_pipe_index = pipe_connect->dst_pipe_index; + *(ipa_params->dst_pipe) = sps_connection->dest_pipe_index; + pipe_connect->src_pipe_index = sps_out_params.ipa_ep_idx; + log_event_dbg("%s: BAM pipe ipa[%x]->usb[%x] connection\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + sps_connection->options = 0; + } + + sps_connection->data = *data_buf; + sps_connection->desc = *desc_buf; + sps_connection->event_thresh = 16; + sps_connection->options |= SPS_O_AUTO_ENABLE; + + ret = sps_connect(*pipe, sps_connection); + if (ret < 0) { + log_event_err("%s: sps_connect failed %d\n", __func__, ret); + goto error; + } + + return 0; + +error: + sps_disconnect(*pipe); +free_sps_endpoints: + sps_free_endpoint(*pipe); +disconnect_ipa: + ipa_disconnect(clnt_hdl); + return ret; +} + +static int disconnect_pipe(enum usb_ctrl cur_bam, u8 idx) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct sps_pipe *pipe = ctx->usb_bam_sps.sps_pipes[idx]; + struct sps_connect *sps_connection = + &ctx->usb_bam_sps.sps_connections[idx]; + + sps_disconnect(pipe); + sps_free_endpoint(pipe); + ctx->usb_bam_sps.sps_pipes[idx] = NULL; + sps_connection->options &= ~SPS_O_AUTO_ENABLE; + + return 0; +} + +static bool _hsic_host_bam_resume_core(void) +{ + log_event_dbg("%s: enter\n", __func__); + + /* Exit from "full suspend" in case of hsic host */ + if (host_info[HSIC_CTRL].dev && info[HSIC_CTRL].in_lpm) { + log_event_dbg("%s: Getting hsic device %p\n", __func__, + host_info[HSIC_CTRL].dev); + pm_runtime_get(host_info[HSIC_CTRL].dev); + info[HSIC_CTRL].in_lpm = false; + return true; + } + return false; +} + +static void _hsic_host_bam_suspend_core(void) +{ + log_event_dbg("%s: enter\n", __func__); + + if (host_info[HSIC_CTRL].dev && !info[HSIC_CTRL].in_lpm) { + log_event_dbg("%s: Putting hsic host device %p\n", __func__, + host_info[HSIC_CTRL].dev); + pm_runtime_put(host_info[HSIC_CTRL].dev); + info[HSIC_CTRL].in_lpm = true; + } +} + +static void usb_bam_suspend_core(enum usb_ctrl bam_type, + enum usb_bam_mode bam_mode, + bool disconnect) +{ + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if ((bam_mode == USB_BAM_DEVICE) || (bam_type != HSIC_CTRL)) { + log_event_err("%s: Invalid BAM type %d\n", __func__, bam_type); + return; + } + + _hsic_host_bam_suspend_core(); + return; +} + +static bool usb_bam_resume_core(enum usb_ctrl bam_type, + enum usb_bam_mode bam_mode) +{ + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if ((bam_mode == USB_BAM_DEVICE) || (bam_type != HSIC_CTRL)) { + log_event_err("%s: Invalid BAM type %d\n", __func__, bam_type); + return false; + } + + return _hsic_host_bam_resume_core(); +} + +/** + * usb_bam_disconnect_ipa_prod() - disconnects the USB consumer i.e. IPA producer. + * @ipa_params: USB IPA related parameters + * @cur_bam: USB controller used for BAM functionality + * + * It performs disconnect with IPA driver for IPA producer pipe and + * with SPS driver for USB BAM consumer pipe. This API also takes care + * of SYS2BAM and BAM2BAM IPA disconnect functionality. + * + * Return: 0 in case of success, errno otherwise. + */ +static int usb_bam_disconnect_ipa_prod( + struct usb_bam_connect_ipa_params *ipa_params, + enum usb_ctrl cur_bam) +{ + int ret; + u8 idx = 0; + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + + idx = ipa_params->dst_idx; + pipe_connect = &ctx->usb_bam_connections[idx]; + pipe_connect->activity_notify = NULL; + pipe_connect->inactivity_notify = NULL; + pipe_connect->priv = NULL; + + /* close IPA -> USB pipe */ + if (pipe_connect->pipe_type == USB_BAM_PIPE_BAM2BAM) { + ret = ipa_disconnect(ipa_params->prod_clnt_hdl); + if (ret) { + log_event_err("%s: dst pipe disconnection failure\n", + __func__); + return ret; + } + + ret = usb_bam_disconnect_pipe(cur_bam, idx); + if (ret) { + log_event_err("%s: failure to disconnect pipe %d\n", + __func__, idx); + return ret; + } + } else { + ret = ipa_teardown_sys_pipe(ipa_params->prod_clnt_hdl); + if (ret) { + log_event_err("%s: dst pipe disconnection failure\n", + __func__); + return ret; + } + + pipe_connect->enabled = false; + spin_lock(&ctx->usb_bam_lock); + if (ctx->pipes_enabled_per_bam == 0) + log_event_err("%s: wrong pipes enabled counter for bam=%d\n", + __func__, pipe_connect->bam_type); + else + ctx->pipes_enabled_per_bam -= 1; + spin_unlock(&ctx->usb_bam_lock); + } + + return 0; +} + +/** + * usb_bam_disconnect_ipa_cons() - disconnects the USB producer i.e. IPA consumer. + * @ipa_params: USB IPA related parameters + * @cur_bam: USB controller used for BAM functionality + * + * It performs disconnect with IPA driver for IPA consumer pipe and + * with SPS driver for USB BAM producer pipe. This API also takes care + * of SYS2BAM and BAM2BAM IPA disconnect functionality. + * + * Return: 0 in case of success, errno otherwise. + */ +static int usb_bam_disconnect_ipa_cons( + struct usb_bam_connect_ipa_params *ipa_params, + enum usb_ctrl cur_bam) +{ + int ret; + u8 idx = 0; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect; + struct sps_pipe *pipe; + u32 timeout = 10, pipe_empty; + struct usb_bam_sps_type usb_bam_sps = ctx->usb_bam_sps; + struct sps_connect *sps_connection; + bool inject_zlt = true; + + idx = ipa_params->src_idx; + pipe = ctx->usb_bam_sps.sps_pipes[idx]; + pipe_connect = &ctx->usb_bam_connections[idx]; + sps_connection = &usb_bam_sps.sps_connections[idx]; + + pipe_connect->activity_notify = NULL; + pipe_connect->inactivity_notify = NULL; + pipe_connect->priv = NULL; + + /* + * On some platforms, there is a chance that flow control + * is disabled from IPA side, due to this IPA core may not + * consume data from USB. Hence notify IPA to enable flow + * control and then check sps pipe is empty or not before + * processing USB->IPA pipes disconnect. + */ + ipa_clear_endpoint_delay(ipa_params->cons_clnt_hdl); +retry: + /* Make sure pipe is empty before disconnecting it */ + while (1) { + ret = sps_is_pipe_empty(pipe, &pipe_empty); + if (ret) { + log_event_err("%s: sps_is_pipe_empty failed with %d\n", + __func__, ret); + return ret; + } + if (pipe_empty || !--timeout) + break; + + /* Check again */ + usleep_range(1000, 2000); + } + + if (!pipe_empty) { + if (inject_zlt) { + pr_debug("%s: Inject ZLT\n", __func__); + log_event_dbg("%s: Inject ZLT\n", __func__); + inject_zlt = false; + sps_pipe_inject_zlt(sps_connection->destination, + sps_connection->dest_pipe_index); + timeout = 10; + goto retry; + } + log_event_err("%s: src pipe(USB) not empty, wait timed out!\n", + __func__); + sps_get_bam_debug_info(ctx->h_bam, 93, + (SPS_BAM_PIPE(0) | SPS_BAM_PIPE(1)), 0, 2); + ipa_bam_reg_dump(); + panic("%s:SPS pipe not empty for USB->IPA\n", __func__); + } + + /* Do the release handshake with the IPA via RM */ + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].connect_complete = 0; + info[cur_bam].disconnected = 1; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + /* Start release handshake on the last USB BAM producer pipe */ + if (info[cur_bam].prod_pipes_enabled_per_bam == 1) + wait_for_prod_release(cur_bam); + + /* close USB -> IPA pipe */ + if (pipe_connect->pipe_type == USB_BAM_PIPE_BAM2BAM) { + ret = ipa_disconnect(ipa_params->cons_clnt_hdl); + if (ret) { + log_event_err("%s: src pipe disconnection failure\n", + __func__); + return ret; + } + + ret = usb_bam_disconnect_pipe(cur_bam, idx); + if (ret) { + log_event_err("%s: failure to disconnect pipe %d\n", + __func__, idx); + return ret; + } + } else { + ret = ipa_teardown_sys_pipe(ipa_params->cons_clnt_hdl); + if (ret) { + log_event_err("%s: src pipe disconnection failure\n", + __func__); + return ret; + } + + pipe_connect->enabled = false; + spin_lock(&ctx->usb_bam_lock); + if (ctx->pipes_enabled_per_bam == 0) + log_event_err("%s: wrong pipes enabled counter for bam=%d\n", + __func__, pipe_connect->bam_type); + else + ctx->pipes_enabled_per_bam -= 1; + spin_unlock(&ctx->usb_bam_lock); + } + + pipe_connect->ipa_clnt_hdl = -1; + info[cur_bam].prod_pipes_enabled_per_bam -= 1; + + return 0; +} + +int usb_bam_connect(enum usb_ctrl cur_bam, int idx, u32 *bam_pipe_idx) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + struct device *bam_dev = &ctx->usb_bam_pdev->dev; + struct msm_usb_bam_platform_data *pdata = bam_dev->platform_data; + enum usb_bam_mode cur_mode; + + if (pipe_connect->enabled) { + pr_warning("%s: connection %d was already established\n" + , __func__, idx); + return 0; + } + + if (!bam_pipe_idx) { + log_event_err("%s: invalid bam_pipe_idx\n", __func__); + return -EINVAL; + } + if (idx < 0 || idx > ctx->max_connections) { + log_event_err("idx is wrong %d\n", idx); + return -EINVAL; + } + + cur_mode = pipe_connect->bam_mode; + + log_event_dbg("%s: PM Runtime GET %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_get_sync(bam_dev); + + spin_lock(&ctx->usb_bam_lock); + /* Check if BAM requires RESET before connect and reset of first pipe */ + if ((pdata->reset_on_connect == true) && + (ctx->pipes_enabled_per_bam == 0)) { + spin_unlock(&ctx->usb_bam_lock); + + if (cur_bam == CI_CTRL) + msm_hw_bam_disable(1); + + sps_device_reset(ctx->h_bam); + + if (cur_bam == CI_CTRL) + msm_hw_bam_disable(0); + + spin_lock(&ctx->usb_bam_lock); + } + spin_unlock(&ctx->usb_bam_lock); + + /* Set the BAM mode (host/device) according to connected pipe */ + info[cur_bam].cur_bam_mode = pipe_connect->bam_mode; + + ret = connect_pipe(cur_bam, idx, bam_pipe_idx); + if (ret) { + log_event_err("%s: pipe connection[%d] failure\n", + __func__, idx); + log_event_dbg("%s: err, PM RT PUT %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_put_sync(bam_dev); + return ret; + } + log_event_dbg("%s: pipe connection[%d] success\n", __func__, idx); + pipe_connect->enabled = 1; + spin_lock(&ctx->usb_bam_lock); + ctx->pipes_enabled_per_bam += 1; + spin_unlock(&ctx->usb_bam_lock); + + return 0; +} + +static int __sps_reset_pipe(enum usb_ctrl bam_type, + struct sps_pipe *pipe, u32 idx) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct sps_connect *sps_connection = + &ctx->usb_bam_sps.sps_connections[idx]; + + ret = sps_disconnect(pipe); + if (ret) { + log_event_err("%s: sps_disconnect() failed %d\n", + __func__, ret); + return ret; + } + + ret = sps_connect(pipe, sps_connection); + if (ret < 0) { + log_event_err("%s: sps_connect() failed %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static void reset_pipe_for_resume(struct usb_bam_pipe_connect *pipe_connect) +{ + int ret; + enum usb_ctrl bam_type = pipe_connect->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + u32 idx = ARRAY_INDEX_FROM_ADDR(ctx->usb_bam_connections, pipe_connect); + struct sps_pipe *pipe = ctx->usb_bam_sps.sps_pipes[idx]; + + if (!pipe_connect->reset_pipe_after_lpm || + pipe_connect->pipe_type != USB_BAM_PIPE_BAM2BAM) { + log_event_dbg("No need to reset pipe %d\n", idx); + return; + } + + ret = __sps_reset_pipe(bam_type, pipe, idx); + if (ret) { + log_event_err("%s failed to reset the USB sps pipe\n", + __func__); + return; + } + + ret = ipa_reset_endpoint(pipe_connect->ipa_clnt_hdl); + if (ret) { + log_event_err("%s failed to reset the IPA pipe\n", __func__); + return; + } + log_event_dbg("%s: USB/IPA pipes reset after resume\n", __func__); +} + +/* Stop PROD transfers in case they were started */ +static void stop_prod_transfers(struct usb_bam_pipe_connect *pipe_connect) +{ + if (pipe_connect->stop && !pipe_connect->prod_stopped) { + log_event_dbg("%s: Stop PROD transfers on\n", __func__); + pipe_connect->stop(pipe_connect->start_stop_param, + USB_TO_PEER_PERIPHERAL); + pipe_connect->prod_stopped = true; + } +} + +static void start_prod_transfers(struct usb_bam_pipe_connect *pipe_connect) +{ + log_event_err("%s: Starting PROD\n", __func__); + if (pipe_connect->start && pipe_connect->prod_stopped) { + log_event_dbg("%s: Enqueue PROD transfer\n", __func__); + pipe_connect->start(pipe_connect->start_stop_param, + USB_TO_PEER_PERIPHERAL); + pipe_connect->prod_stopped = false; + } +} + +static void start_cons_transfers(struct usb_bam_pipe_connect *pipe_connect) +{ + /* Start CONS transfer */ + if (pipe_connect->start && pipe_connect->cons_stopped) { + log_event_dbg("%s: Enqueue CONS transfer\n", __func__); + pipe_connect->start(pipe_connect->start_stop_param, + PEER_PERIPHERAL_TO_USB); + pipe_connect->cons_stopped = 0; + } +} + +/* Stop CONS transfers in case they were started */ +static void stop_cons_transfers(struct usb_bam_pipe_connect *pipe_connect) +{ + if (pipe_connect->stop && !pipe_connect->cons_stopped) { + log_event_dbg("%s: Stop CONS transfers\n", __func__); + pipe_connect->stop(pipe_connect->start_stop_param, + PEER_PERIPHERAL_TO_USB); + pipe_connect->cons_stopped = 1; + } +} + +static void resume_suspended_pipes(enum usb_ctrl cur_bam) +{ + u32 idx, dst_idx; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect; + + log_event_dbg("Resuming: suspend pipes =%d\n", + info[cur_bam].pipes_suspended); + + while (info[cur_bam].pipes_suspended >= 1) { + idx = info[cur_bam].pipes_suspended - 1; + dst_idx = info[cur_bam].resume_dst_idx[idx]; + pipe_connect = &ctx->usb_bam_connections[dst_idx]; + if (pipe_connect->cons_stopped) { + log_event_dbg("%s: Starting CONS on %d\n", __func__, + dst_idx); + start_cons_transfers(pipe_connect); + } + + log_event_dbg("%s: Starting PROD on %d\n", __func__, dst_idx); + start_prod_transfers(pipe_connect); + info[cur_bam].pipes_suspended--; + info[cur_bam].pipes_resumed++; + /* Suspend was aborted, renew pm_runtime vote */ + log_event_dbg("%s: PM Runtime GET %d, count: %d\n", __func__, + idx, get_pm_runtime_counter(&ctx->usb_bam_pdev->dev)); + pm_runtime_get(&ctx->usb_bam_pdev->dev); + } +} + +static inline int all_pipes_suspended(enum usb_ctrl cur_bam) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + + log_event_dbg("%s: pipes_suspended=%d pipes_enabled_per_bam=%d\n", + __func__, info[cur_bam].pipes_suspended, + ctx->pipes_enabled_per_bam); + + return info[cur_bam].pipes_suspended == ctx->pipes_enabled_per_bam; +} + +static void usb_bam_finish_suspend(enum usb_ctrl cur_bam) +{ + int ret, bam2bam; + u32 cons_empty, idx, dst_idx; + struct sps_pipe *cons_pipe; + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct device *bam_dev = &ctx->usb_bam_pdev->dev; + + mutex_lock(&info[cur_bam].suspend_resume_mutex); + + spin_lock(&usb_bam_ipa_handshake_info_lock); + /* If cable was disconnected, let disconnection seq do everything */ + if (info[cur_bam].disconnected || all_pipes_suspended(cur_bam)) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + log_event_dbg("%s: Cable disconnected\n", __func__); + return; + } + log_event_dbg("%s: bam:%s RT GET: %d\n", __func__, + bam_enable_strings[cur_bam], get_pm_runtime_counter(bam_dev)); + pm_runtime_get(bam_dev); + + /* If resume was called don't finish this work */ + if (!info[cur_bam].bus_suspend) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + log_event_dbg("%s: Bus resume in progress\n", __func__); + goto no_lpm; + } + + /* Go over all pipes, stop and suspend them, and go to lpm */ + while (!all_pipes_suspended(cur_bam)) { + idx = info[cur_bam].pipes_suspended; + dst_idx = info[cur_bam].suspend_dst_idx[idx]; + cons_pipe = ctx->usb_bam_sps.sps_pipes[dst_idx]; + pipe_connect = &ctx->usb_bam_connections[dst_idx]; + + log_event_dbg("pipes_suspended=%d pipes_to_suspend=%d\n", + info[cur_bam].pipes_suspended, + info[cur_bam].pipes_to_suspend); + + bam2bam = (pipe_connect->pipe_type == USB_BAM_PIPE_BAM2BAM); + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + if (bam2bam) { + ret = sps_is_pipe_empty(cons_pipe, &cons_empty); + if (ret) { + log_event_err("%s: sps_is_pipe_empty failed with %d\n", + __func__, ret); + goto no_lpm; + } + } + + spin_lock(&usb_bam_ipa_handshake_info_lock); + /* Stop CONS transfers and go to lpm if no more data in the */ + /* pipes */ + if (cons_empty) { + log_event_dbg("%s: Stopping CONS transfers on dst_idx=%d\n" + , __func__, dst_idx); + stop_cons_transfers(pipe_connect); + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + log_event_dbg("%s: Suspending pipe\n", __func__); + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].resume_src_idx[idx] = + info[cur_bam].suspend_src_idx[idx]; + info[cur_bam].resume_dst_idx[idx] = + info[cur_bam].suspend_dst_idx[idx]; + info[cur_bam].pipes_suspended++; + + log_event_dbg("%s: PM Runtime PUT %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_put(&ctx->usb_bam_pdev->dev); + } else { + log_event_dbg("%s: Pipe is not empty, not going to LPM\n", + __func__); + spin_unlock(&usb_bam_ipa_handshake_info_lock); + goto no_lpm; + } + } + info[cur_bam].pipes_to_suspend = 0; + info[cur_bam].pipes_resumed = 0; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + /* ACK on the last pipe */ + if (info[cur_bam].pipes_suspended == ctx->pipes_enabled_per_bam && + info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_RELEASED) { + ipa_rm_notify_completion( + IPA_RM_RESOURCE_RELEASED, + ipa_rm_resource_cons[cur_bam]); + } + + log_event_dbg("%s: Starting LPM on Bus Suspend, RT PUT:%d\n", __func__, + get_pm_runtime_counter(bam_dev)); + /* Put to match _get at the beginning of this routine */ + pm_runtime_put_sync(bam_dev); + + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + + return; + +no_lpm: + + spin_lock(&usb_bam_ipa_handshake_info_lock); + resume_suspended_pipes(cur_bam); + info[cur_bam].pipes_resumed = 0; + info[cur_bam].pipes_to_suspend = 0; + info[cur_bam].pipes_suspended = 0; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + /* Finish the handshake. Resume Sequence will start automatically + by the data in the pipes */ + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_RELEASED) + ipa_rm_notify_completion(IPA_RM_RESOURCE_RELEASED, + ipa_rm_resource_cons[cur_bam]); + + /* Put to match _get at the beginning of this routine */ + pm_runtime_put(bam_dev); + + mutex_unlock(&info[cur_bam].suspend_resume_mutex); +} + +void usb_bam_finish_suspend_(struct work_struct *w) +{ + enum usb_ctrl cur_bam; + struct usb_bam_ipa_handshake_info *info_ptr; + + info_ptr = container_of(w, struct usb_bam_ipa_handshake_info, + finish_suspend_work); + cur_bam = info_ptr->cur_bam_mode; + + log_event_dbg("%s: Finishing suspend sequence(BAM=%s)\n", __func__, + bam_enable_strings[cur_bam]); + usb_bam_finish_suspend(cur_bam); +} + +static void usb_prod_notify_cb(void *user_data, enum ipa_rm_event event, + unsigned long data) +{ + enum usb_ctrl *cur_bam = (void *)user_data; + + switch (event) { + case IPA_RM_RESOURCE_GRANTED: + log_event_dbg("%s: %s_PROD resource granted\n", + __func__, bam_enable_strings[*cur_bam]); + info[*cur_bam].cur_prod_state = IPA_RM_RESOURCE_GRANTED; + complete_all(&info[*cur_bam].prod_avail); + break; + case IPA_RM_RESOURCE_RELEASED: + log_event_dbg("%s: %s_PROD resource released\n", + __func__, bam_enable_strings[*cur_bam]); + info[*cur_bam].cur_prod_state = IPA_RM_RESOURCE_RELEASED; + complete_all(&info[*cur_bam].prod_released); + break; + default: + break; + } + return; +} + +/** + * usb_bam_resume_host: vote for hsic host core resume. + * + * NOTE: This function should be called in a context that hold + * usb_bam_lock. + */ +static void usb_bam_resume_host(enum usb_ctrl bam_type) +{ + int i; + struct usb_bam_pipe_connect *pipe_iter; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if (usb_bam_resume_core(bam_type, USB_BAM_HOST)) { + for (i = 0; i < ctx->max_connections; i++) { + pipe_iter = &ctx->usb_bam_connections[i]; + if (pipe_iter->enabled && pipe_iter->suspended) + pipe_iter->suspended = false; + } + } +} + +static int cons_request_resource(enum usb_ctrl cur_bam) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + int ret = -EINPROGRESS; + + log_event_dbg("%s: Request %s_CONS resource\n", + __func__, bam_enable_strings[cur_bam]); + + spin_lock(&ctx->usb_bam_lock); + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].cur_cons_state = IPA_RM_RESOURCE_GRANTED; + + switch (info[cur_bam].cur_bam_mode) { + case USB_BAM_DEVICE: + if (ctx->pipes_enabled_per_bam && + info[cur_bam].connect_complete) { + if (!all_pipes_suspended(cur_bam) && + !info[cur_bam].bus_suspend) { + log_event_dbg("%s: ACK on cons_request\n", + __func__); + ret = 0; + } else if (info[cur_bam].bus_suspend) { + info[cur_bam].bus_suspend = 0; + log_event_dbg("%s: Wake up host\n", __func__); + if (info[cur_bam].wake_cb) + info[cur_bam].wake_cb( + info[cur_bam].wake_param); + } + } + + break; + case USB_BAM_HOST: + /* + * Vote for hsic resume, however the core + * resume may not be completed yet or on the other hand + * hsic core might already be resumed, due to a vote + * by other driver, in this case we will just renew our + * vote here. + */ + usb_bam_resume_host(cur_bam); + + /* + * Return sucess if there are pipes connected + * and hsic core is actually not in lpm. + * If in lpm, grant will occur on resume + * finish (see msm_bam_hsic_notify_on_resume) + */ + if (ctx->pipes_enabled_per_bam && !host_info[cur_bam].in_lpm) + ret = 0; + + break; + + default: + break; + } + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + spin_unlock(&ctx->usb_bam_lock); + + if (ret == -EINPROGRESS) + log_event_dbg("%s: EINPROGRESS on cons_request\n", __func__); + + return ret; +} + +static int ss_usb_cons_request_resource(void) +{ + return cons_request_resource(DWC3_CTRL); +} + + +static int usb_cons_request_resource(void) +{ + return cons_request_resource(CI_CTRL); +} + +static int hsic_cons_request_resource(void) +{ + return cons_request_resource(HSIC_CTRL); +} + +static int cons_release_resource(enum usb_ctrl cur_bam) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + + log_event_dbg("%s: Release %s_CONS resource\n", + __func__, bam_enable_strings[cur_bam]); + + info[cur_bam].cur_cons_state = IPA_RM_RESOURCE_RELEASED; + + spin_lock(&ctx->usb_bam_lock); + if (!ctx->pipes_enabled_per_bam) { + spin_unlock(&ctx->usb_bam_lock); + log_event_dbg("%s: ACK on cons_release\n", __func__); + return 0; + } + spin_unlock(&ctx->usb_bam_lock); + + if (info[cur_bam].cur_bam_mode == USB_BAM_DEVICE) { + spin_lock(&usb_bam_ipa_handshake_info_lock); + if (info[cur_bam].bus_suspend) { + queue_work(ctx->usb_bam_wq, + &info[cur_bam].finish_suspend_work); + } + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + log_event_dbg("%s: EINPROGRESS cons_release\n", __func__); + return -EINPROGRESS; + } else if (info[cur_bam].cur_bam_mode == USB_BAM_HOST) { + /* + * Allow to go to lpm for now. Actual state will be checked + * in msm_bam_hsic_lpm_ok() / msm_bam_lpm_ok() just before + * going to lpm. + */ + usb_bam_suspend_core(cur_bam, info[cur_bam].cur_bam_mode, 1); + } + + return 0; +} + +static int hsic_cons_release_resource(void) +{ + return cons_release_resource(HSIC_CTRL); +} + +static int usb_cons_release_resource(void) +{ + return cons_release_resource(CI_CTRL); +} + +static int ss_usb_cons_release_resource(void) +{ + return cons_release_resource(DWC3_CTRL); +} + +static void usb_bam_ipa_create_resources(enum usb_ctrl cur_bam) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct msm_usb_bam_platform_data *pdata = + ctx->usb_bam_pdev->dev.platform_data; + struct ipa_rm_create_params usb_prod_create_params; + struct ipa_rm_create_params usb_cons_create_params; + int ret; + + /* Create USB/HSIC_PROD entity */ + memset(&usb_prod_create_params, 0, sizeof(usb_prod_create_params)); + usb_prod_create_params.name = ipa_rm_resource_prod[cur_bam]; + usb_prod_create_params.reg_params.notify_cb = usb_prod_notify_cb; + usb_prod_create_params.reg_params.user_data = &pdata->bam_type; + usb_prod_create_params.floor_voltage = IPA_VOLTAGE_SVS; + ret = ipa_rm_create_resource(&usb_prod_create_params); + if (ret) { + log_event_err("%s: Failed to create USB_PROD resource\n", + __func__); + return; + } + + /* Create USB_CONS entity */ + memset(&usb_cons_create_params, 0, sizeof(usb_cons_create_params)); + usb_cons_create_params.name = ipa_rm_resource_cons[cur_bam]; + usb_cons_create_params.request_resource = request_resource_cb[cur_bam]; + usb_cons_create_params.release_resource = release_resource_cb[cur_bam]; + usb_cons_create_params.floor_voltage = IPA_VOLTAGE_SVS; + ret = ipa_rm_create_resource(&usb_cons_create_params); + if (ret) { + log_event_err("%s: Failed to create USB_CONS resource\n", + __func__); + return; + } +} + +static void wait_for_prod_granted(enum usb_ctrl cur_bam) +{ + int ret; + + log_event_dbg("%s Request %s_PROD_RES\n", __func__, + bam_enable_strings[cur_bam]); + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_GRANTED) + log_event_dbg("%s: CONS already granted for some reason\n", + __func__); + if (info[cur_bam].cur_prod_state == IPA_RM_RESOURCE_GRANTED) + log_event_dbg("%s: PROD already granted for some reason\n", + __func__); + + init_completion(&info[cur_bam].prod_avail); + + ret = ipa_rm_request_resource(ipa_rm_resource_prod[cur_bam]); + if (!ret) { + info[cur_bam].cur_prod_state = IPA_RM_RESOURCE_GRANTED; + complete_all(&info[cur_bam].prod_avail); + log_event_dbg("%s: PROD_GRANTED without wait\n", __func__); + } else if (ret == -EINPROGRESS) { + log_event_dbg("%s: Waiting for PROD_GRANTED\n", __func__); + if (!wait_for_completion_timeout(&info[cur_bam].prod_avail, + USB_BAM_TIMEOUT)) + log_event_err("%s: Timeout wainting for PROD_GRANTED\n", + __func__); + } else + log_event_err("%s: ipa_rm_request_resource ret =%d\n", + __func__, ret); +} + +void notify_usb_connected(enum usb_ctrl cur_bam) +{ + log_event_dbg("%s: enter\n", __func__); + + spin_lock(&usb_bam_ipa_handshake_info_lock); + if (info[cur_bam].cur_bam_mode == USB_BAM_DEVICE) + info[cur_bam].connect_complete = 1; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_GRANTED) { + log_event_dbg("%s: Notify %s CONS_GRANTED\n", __func__, + bam_enable_strings[cur_bam]); + ipa_rm_notify_completion(IPA_RM_RESOURCE_GRANTED, + ipa_rm_resource_cons[cur_bam]); + } +} + +static void wait_for_prod_release(enum usb_ctrl cur_bam) +{ + int ret; + + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_RELEASED) + log_event_dbg("%s consumer already released\n", __func__); + if (info[cur_bam].cur_prod_state == IPA_RM_RESOURCE_RELEASED) + log_event_dbg("%s producer already released\n", __func__); + + init_completion(&info[cur_bam].prod_released); + log_event_dbg("%s: Releasing %s_PROD\n", __func__, + bam_enable_strings[cur_bam]); + ret = ipa_rm_release_resource(ipa_rm_resource_prod[cur_bam]); + if (!ret) { + log_event_dbg("%s: Released without waiting\n", __func__); + info[cur_bam].cur_prod_state = IPA_RM_RESOURCE_RELEASED; + complete_all(&info[cur_bam].prod_released); + } else if (ret == -EINPROGRESS) { + log_event_dbg("%s: Waiting for PROD_RELEASED\n", __func__); + if (!wait_for_completion_timeout(&info[cur_bam].prod_released, + USB_BAM_TIMEOUT)) + log_event_err("%s: Timeout waiting for PROD_RELEASED\n", + __func__); + } else { + log_event_err("%s: ipa_rm_request_resource ret =%d\n", + __func__, ret); + } +} + +static bool check_pipes_empty(enum usb_ctrl bam_type, u8 src_idx, u8 dst_idx) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct sps_pipe *prod_pipe, *cons_pipe; + struct usb_bam_pipe_connect *prod_pipe_connect, *cons_pipe_connect; + u32 prod_empty, cons_empty; + + prod_pipe_connect = &ctx->usb_bam_connections[src_idx]; + cons_pipe_connect = &ctx->usb_bam_connections[dst_idx]; + if (!prod_pipe_connect->enabled || !cons_pipe_connect->enabled) { + log_event_err("%s: pipes are not enabled dst=%d src=%d\n", + __func__, prod_pipe_connect->enabled, + cons_pipe_connect->enabled); + } + + /* If we have any remaints in the pipes we don't go to sleep */ + prod_pipe = ctx->usb_bam_sps.sps_pipes[src_idx]; + cons_pipe = ctx->usb_bam_sps.sps_pipes[dst_idx]; + log_event_dbg("prod_pipe=%p, cons_pipe=%p\n", prod_pipe, cons_pipe); + + if (!cons_pipe || (!prod_pipe && + prod_pipe_connect->pipe_type == USB_BAM_PIPE_BAM2BAM)) { + log_event_err("Missing a pipe!\n"); + return false; + } + + if (prod_pipe && sps_is_pipe_empty(prod_pipe, &prod_empty)) { + log_event_err("sps_is_pipe_empty(prod) failed\n"); + return false; + } else { + prod_empty = true; + } + + if (sps_is_pipe_empty(cons_pipe, &cons_empty)) { + log_event_err("sps_is_pipe_empty(cons) failed\n"); + return false; + } + + if (!prod_empty || !cons_empty) { + log_event_err("pipes not empty prod=%d cond=%d\n", + prod_empty, cons_empty); + return false; + } + + return true; + +} + +void usb_bam_suspend(enum usb_ctrl cur_bam, + struct usb_bam_connect_ipa_params *ipa_params) +{ + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + enum usb_bam_mode bam_mode; + u8 src_idx, dst_idx; + + log_event_dbg("%s: enter\n", __func__); + + if (!ipa_params) { + log_event_err("%s: Invalid ipa params\n", __func__); + return; + } + + src_idx = ipa_params->src_idx; + dst_idx = ipa_params->dst_idx; + + if (src_idx >= ctx->max_connections || + dst_idx >= ctx->max_connections) { + log_event_err("%s: Invalid connection index src=%d dst=%d\n", + __func__, src_idx, dst_idx); + } + + pipe_connect = &ctx->usb_bam_connections[src_idx]; + bam_mode = pipe_connect->bam_mode; + if (bam_mode != USB_BAM_DEVICE) + return; + + log_event_dbg("%s: Starting suspend sequence(BAM=%s)\n", __func__, + bam_enable_strings[cur_bam]); + + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].bus_suspend = 1; + + /* If cable was disconnected, let disconnection seq do everything */ + if (info[cur_bam].disconnected) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + log_event_dbg("%s: Cable disconnected\n", __func__); + return; + } + + log_event_dbg("%s: Adding src=%d dst=%d in pipes_to_suspend=%d\n", + __func__, src_idx, + dst_idx, info[cur_bam].pipes_to_suspend); + info[cur_bam].suspend_src_idx[info[cur_bam].pipes_to_suspend] = src_idx; + info[cur_bam].suspend_dst_idx[info[cur_bam].pipes_to_suspend] = dst_idx; + info[cur_bam].pipes_to_suspend++; + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + usb_bam_start_suspend(&info[cur_bam]); +} + +static void usb_bam_start_suspend(struct usb_bam_ipa_handshake_info *info_ptr) +{ + struct usb_bam_pipe_connect *pipe_connect; + enum usb_ctrl cur_bam = info_ptr->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + u8 src_idx, dst_idx; + int pipes_to_suspend; + + cur_bam = info_ptr->bam_type; + log_event_dbg("%s: Starting suspend sequence(BAM=%s)\n", __func__, + bam_enable_strings[cur_bam]); + + mutex_lock(&info[cur_bam].suspend_resume_mutex); + + spin_lock(&usb_bam_ipa_handshake_info_lock); + /* If cable was disconnected, let disconnection seq do everything */ + if (info[cur_bam].disconnected) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + log_event_dbg("%s: Cable disconnected\n", __func__); + return; + } + + pipes_to_suspend = info[cur_bam].pipes_to_suspend; + if (!info[cur_bam].bus_suspend || !pipes_to_suspend) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + log_event_dbg("%s: Resume started, not suspending\n", __func__); + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + return; + } + + src_idx = info[cur_bam].suspend_src_idx[pipes_to_suspend - 1]; + dst_idx = info[cur_bam].suspend_dst_idx[pipes_to_suspend - 1]; + + pipe_connect = &ctx->usb_bam_connections[dst_idx]; + stop_prod_transfers(pipe_connect); + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + /* Don't start LPM seq if data in the pipes */ + if (!check_pipes_empty(cur_bam, src_idx, dst_idx)) { + start_prod_transfers(pipe_connect); + info[cur_bam].pipes_to_suspend = 0; + info[cur_bam].bus_suspend = 0; + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + return; + } + + spin_lock(&usb_bam_ipa_handshake_info_lock); + + /* Start release handshake on the last pipe */ + if (info[cur_bam].pipes_to_suspend * 2 == ctx->pipes_enabled_per_bam) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + wait_for_prod_release(cur_bam); + } else { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + } + + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_RELEASED) + usb_bam_finish_suspend(cur_bam); + else + log_event_dbg("Consumer not released yet\n"); +} + +static void usb_bam_finish_resume(struct work_struct *w) +{ + /* TODO: Change this when HSIC device support is introduced */ + enum usb_ctrl cur_bam; + struct usb_bam_ipa_handshake_info *info_ptr; + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_ctx_type *ctx; + struct device *bam_dev; + u32 idx, dst_idx, suspended; + + info_ptr = container_of(w, struct usb_bam_ipa_handshake_info, + resume_work); + cur_bam = info_ptr->bam_type; + ctx = &msm_usb_bam[cur_bam]; + bam_dev = &ctx->usb_bam_pdev->dev; + + log_event_dbg("%s: enter bam=%s, RT GET: %d\n", __func__, + bam_enable_strings[cur_bam], get_pm_runtime_counter(bam_dev)); + + pm_runtime_get_sync(bam_dev); + + mutex_lock(&info[cur_bam].suspend_resume_mutex); + + /* Suspend or disconnect happened in the meantime */ + spin_lock(&usb_bam_ipa_handshake_info_lock); + if (info[cur_bam].bus_suspend || info[cur_bam].disconnected) { + spin_unlock(&usb_bam_ipa_handshake_info_lock); + log_event_dbg("%s: Bus suspended, not resuming, RT PUT: %d\n", + __func__, get_pm_runtime_counter(bam_dev)); + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + pm_runtime_put_sync(bam_dev); + return; + } + info[cur_bam].pipes_to_suspend = 0; + + log_event_dbg("Resuming: pipes_suspended =%d\n", + info[cur_bam].pipes_suspended); + + suspended = info[cur_bam].pipes_suspended; + while (suspended >= 1) { + idx = suspended - 1; + dst_idx = info[cur_bam].resume_dst_idx[idx]; + pipe_connect = &ctx->usb_bam_connections[dst_idx]; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + reset_pipe_for_resume(pipe_connect); + spin_lock(&usb_bam_ipa_handshake_info_lock); + if (pipe_connect->cons_stopped) { + log_event_dbg("%s: Starting CONS on %d\n", __func__, + dst_idx); + start_cons_transfers(pipe_connect); + } + suspended--; + } + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_GRANTED) { + log_event_dbg("%s: Notify CONS_GRANTED\n", __func__); + ipa_rm_notify_completion(IPA_RM_RESOURCE_GRANTED, + ipa_rm_resource_cons[cur_bam]); + } + spin_unlock(&usb_bam_ipa_handshake_info_lock); + + /* Start handshake for the first pipe resumed */ + if (info[cur_bam].pipes_resumed == 0) + wait_for_prod_granted(cur_bam); + + spin_lock(&usb_bam_ipa_handshake_info_lock); + while (info[cur_bam].pipes_suspended >= 1) { + idx = info[cur_bam].pipes_suspended - 1; + dst_idx = info[cur_bam].resume_dst_idx[idx]; + pipe_connect = &ctx->usb_bam_connections[dst_idx]; + log_event_dbg("%s: Starting PROD on %d\n", __func__, dst_idx); + start_prod_transfers(pipe_connect); + info[cur_bam].pipes_suspended--; + info[cur_bam].pipes_resumed++; + log_event_dbg("%s: PM Runtime GET %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_get(&ctx->usb_bam_pdev->dev); + } + + if (info[cur_bam].pipes_resumed == ctx->pipes_enabled_per_bam) { + info[cur_bam].pipes_resumed = 0; + if (info[cur_bam].cur_cons_state == IPA_RM_RESOURCE_GRANTED) { + log_event_dbg("%s: Notify CONS_GRANTED\n", __func__); + ipa_rm_notify_completion(IPA_RM_RESOURCE_GRANTED, + ipa_rm_resource_cons[cur_bam]); + } + } + + spin_unlock(&usb_bam_ipa_handshake_info_lock); + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + log_event_dbg("%s: done..PM Runtime PUT %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + /* Put to match _get at the beginning of this routine */ + pm_runtime_put(&ctx->usb_bam_pdev->dev); +} + +void usb_bam_resume(enum usb_ctrl cur_bam, + struct usb_bam_connect_ipa_params *ipa_params) +{ + u8 src_idx, dst_idx; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect; + + log_event_dbg("%s: Resuming\n", __func__); + + if (!ipa_params) { + log_event_err("%s: Invalid ipa params\n", __func__); + return; + } + + src_idx = ipa_params->src_idx; + dst_idx = ipa_params->dst_idx; + + if (src_idx >= ctx->max_connections || + dst_idx >= ctx->max_connections) { + log_event_err("%s: Invalid connection index src=%d dst=%d\n", + __func__, src_idx, dst_idx); + return; + } + + pipe_connect = &ctx->usb_bam_connections[src_idx]; + log_event_dbg("%s: bam=%s mode =%d\n", __func__, + bam_enable_strings[cur_bam], pipe_connect->bam_mode); + if (pipe_connect->bam_mode != USB_BAM_DEVICE) + return; + + info[cur_bam].in_lpm = false; + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].bus_suspend = 0; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + queue_work(ctx->usb_bam_wq, &info[cur_bam].resume_work); +} + +static void _msm_bam_wait_for_host_prod_granted(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + spin_lock(&ctx->usb_bam_lock); + + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + ctx->is_bam_inactivity = false; + + /* Get back to resume state including wakeup ipa */ + usb_bam_resume_core(bam_type, USB_BAM_HOST); + + /* Ensure getting the producer resource */ + wait_for_prod_granted(bam_type); + + spin_unlock(&ctx->usb_bam_lock); + +} + +void msm_bam_wait_for_hsic_host_prod_granted(void) +{ + log_event_dbg("%s: start\n", __func__); + _msm_bam_wait_for_host_prod_granted(HSIC_CTRL); +} + +static void _msm_bam_host_notify_on_resume(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + spin_lock(&ctx->usb_bam_lock); + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + host_info[bam_type].in_lpm = false; + + /* HSIC resume completed. Notify CONS grant if CONS was requested */ + notify_usb_connected(bam_type); + + /* + * This function is called to notify the usb bam driver + * that the hsic core and hsic bam hw are fully resumed + * and clocked on. Therefore we can now set the inactivity + * timer to the hsic bam hw. + */ + if (ctx->inactivity_timer_ms) + usb_bam_set_inactivity_timer(bam_type); + + spin_unlock(&ctx->usb_bam_lock); +} + +static bool msm_bam_host_lpm_ok(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_iter; + int i; + + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if (!host_info[bam_type].dev) + return true; + + log_event_dbg("%s: Starting hsic full suspend sequence\n", + __func__); + + /* + * Start low power mode by releasing the device only if the resources + * were indeed released and we are still in inactivity state (wakeup + * event has not occurred while we were waiting for resources release + */ + spin_lock(&ctx->usb_bam_lock); + + if (info[bam_type].cur_cons_state == IPA_RM_RESOURCE_RELEASED && + info[bam_type].cur_prod_state == IPA_RM_RESOURCE_RELEASED && + ctx->is_bam_inactivity && info[bam_type].in_lpm) { + + pr_debug("%s(): checking HSIC Host pipe state\n", __func__); + if (!msm_bam_hsic_host_pipe_empty()) { + log_event_err("%s(): HSIC HOST Pipe is not empty\n", + __func__); + spin_unlock(&ctx->usb_bam_lock); + return false; + } + + /* HSIC host will go now to lpm */ + log_event_dbg("%s: vote for suspend hsic %p\n", + __func__, host_info[bam_type].dev); + + for (i = 0; i < ctx->max_connections; i++) { + pipe_iter = &ctx->usb_bam_connections[i]; + if (pipe_iter->bam_type == bam_type && + pipe_iter->enabled && !pipe_iter->suspended) + pipe_iter->suspended = true; + } + + host_info[bam_type].in_lpm = true; + spin_unlock(&ctx->usb_bam_lock); + + return true; + } + + /* We don't allow lpm, therefore renew our vote here */ + if (info[bam_type].in_lpm) { + log_event_dbg("%s: Not allow lpm while ref count=0\n", + __func__); + log_event_dbg("%s: inactivity=%d, c_s=%d p_s=%d\n", __func__, + ctx->is_bam_inactivity, info[bam_type].cur_cons_state, + info[bam_type].cur_prod_state); + pm_runtime_get(host_info[bam_type].dev); + info[bam_type].in_lpm = false; + spin_unlock(&ctx->usb_bam_lock); + } else { + spin_unlock(&ctx->usb_bam_lock); + } + + return false; +} + +void msm_bam_hsic_host_notify_on_resume(void) +{ + _msm_bam_host_notify_on_resume(HSIC_CTRL); +} + +static int usb_bam_set_ipa_perf(enum usb_ctrl cur_bam, + enum usb_bam_pipe_dir dir, + enum usb_device_speed usb_connection_speed) +{ + int ret; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct ipa_rm_perf_profile ipa_rm_perf_prof; + struct msm_usb_bam_platform_data *pdata = + ctx->usb_bam_pdev->dev.platform_data; + + if (usb_connection_speed == USB_SPEED_SUPER) + ipa_rm_perf_prof.max_supported_bandwidth_mbps = + pdata->max_mbps_superspeed; + else + /* Bam2Bam is supported only for SS and HS (HW limitation) */ + ipa_rm_perf_prof.max_supported_bandwidth_mbps = + pdata->max_mbps_highspeed; + + /* + * Having a max mbps property in dtsi file is a must + * for target with IPA capability. + */ + if (!ipa_rm_perf_prof.max_supported_bandwidth_mbps) { + log_event_err("%s: Max mbps is required for speed %d\n", + __func__, usb_connection_speed); + return -EINVAL; + } + + if (dir == USB_TO_PEER_PERIPHERAL) { + log_event_dbg("%s: vote ipa_perf resource=%d perf=%d mbps\n", + __func__, ipa_rm_resource_prod[cur_bam], + ipa_rm_perf_prof.max_supported_bandwidth_mbps); + ret = ipa_rm_set_perf_profile(ipa_rm_resource_prod[cur_bam], + &ipa_rm_perf_prof); + } else { + log_event_dbg("%s: vote ipa_perf resource=%d perf=%d mbps\n", + __func__, ipa_rm_resource_cons[cur_bam], + ipa_rm_perf_prof.max_supported_bandwidth_mbps); + ret = ipa_rm_set_perf_profile(ipa_rm_resource_cons[cur_bam], + &ipa_rm_perf_prof); + } + + return ret; +} + +int usb_bam_connect_ipa(enum usb_ctrl cur_bam, + struct usb_bam_connect_ipa_params *ipa_params) +{ + u8 idx; + enum usb_bam_mode cur_mode; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect; + struct device *bam_dev = &ctx->usb_bam_pdev->dev; + struct msm_usb_bam_platform_data *pdata = bam_dev->platform_data; + int ret; + bool bam2bam, is_dpl; + + log_event_dbg("%s: start\n", __func__); + + if (!ipa_params) { + log_event_err("%s: Invalid ipa params\n", + __func__); + return -EINVAL; + } + + if (ipa_params->dir == USB_TO_PEER_PERIPHERAL) + idx = ipa_params->src_idx; + else + idx = ipa_params->dst_idx; + + if (idx >= ctx->max_connections) { + log_event_err("%s: Invalid connection index\n", + __func__); + return -EINVAL; + } + pipe_connect = &ctx->usb_bam_connections[idx]; + + if (pipe_connect->enabled) { + log_event_err("%s: connection %d was already established\n", + __func__, idx); + return 0; + } + + ret = usb_bam_set_ipa_perf(pipe_connect->bam_type, ipa_params->dir, + ipa_params->usb_connection_speed); + if (ret) { + log_event_err("%s: call to usb_bam_set_ipa_perf failed %d\n", + __func__, ret); + return ret; + } + + log_event_dbg("%s: enter\n", __func__); + + cur_mode = pipe_connect->bam_mode; + bam2bam = (pipe_connect->pipe_type == USB_BAM_PIPE_BAM2BAM); + + if (ipa_params->dst_client == IPA_CLIENT_USB_DPL_CONS) + is_dpl = true; + else + is_dpl = false; + + /* Set the BAM mode (host/device) according to connected pipe */ + info[cur_bam].cur_bam_mode = pipe_connect->bam_mode; + + if (cur_mode == USB_BAM_DEVICE) { + mutex_lock(&info[cur_bam].suspend_resume_mutex); + + spin_lock(&ctx->usb_bam_lock); + if (ctx->pipes_enabled_per_bam == 0) { + spin_unlock(&ctx->usb_bam_lock); + spin_lock(&usb_bam_ipa_handshake_info_lock); + info[cur_bam].connect_complete = 0; + info[cur_bam].disconnected = 0; + info[cur_bam].bus_suspend = 0; + info[cur_bam].pipes_suspended = 0; + info[cur_bam].pipes_to_suspend = 0; + info[cur_bam].pipes_resumed = 0; + spin_unlock(&usb_bam_ipa_handshake_info_lock); + } else { + spin_unlock(&ctx->usb_bam_lock); + } + pipe_connect->cons_stopped = 0; + pipe_connect->prod_stopped = 0; + } + + log_event_dbg("%s: PM Runtime GET %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_get_sync(bam_dev); + + /* Check if BAM requires RESET before connect and reset first pipe */ + spin_lock(&ctx->usb_bam_lock); + if (pdata->reset_on_connect && !ctx->pipes_enabled_per_bam) { + spin_unlock(&ctx->usb_bam_lock); + + if (cur_bam == CI_CTRL) + msm_hw_bam_disable(1); + + sps_device_reset(ctx->h_bam); + + if (cur_bam == CI_CTRL) + msm_hw_bam_disable(0); + + /* On re-connect assume out from lpm for HOST BAM */ + if (cur_mode == USB_BAM_HOST) + usb_bam_resume_core(cur_bam, cur_mode); + + /* On re-connect assume out from lpm for all BAMs */ + info[cur_bam].in_lpm = false; + } else { + spin_unlock(&ctx->usb_bam_lock); + if (!ctx->pipes_enabled_per_bam) + pr_debug("No BAM reset on connect, just pipe reset\n"); + } + + if (ipa_params->dir == USB_TO_PEER_PERIPHERAL) { + if (info[cur_bam].prod_pipes_enabled_per_bam == 0) + wait_for_prod_granted(cur_bam); + info[cur_bam].prod_pipes_enabled_per_bam += 1; + } + + if (bam2bam) + ret = connect_pipe_bam2bam_ipa(cur_bam, idx, ipa_params); + else + ret = connect_pipe_sys2bam_ipa(cur_bam, idx, ipa_params); + if (ret) { + log_event_err("%s: pipe connection failure RT PUT: %d\n", + __func__, get_pm_runtime_counter(bam_dev)); + pm_runtime_put_sync(bam_dev); + if (cur_mode == USB_BAM_DEVICE) + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + return ret; + } + log_event_dbg("%s: pipe connection success\n", __func__); + spin_lock(&ctx->usb_bam_lock); + pipe_connect->enabled = 1; + pipe_connect->suspended = 0; + + /* Set global inactivity timer upon first pipe connection */ + if (!ctx->pipes_enabled_per_bam && ctx->inactivity_timer_ms && + pipe_connect->inactivity_notify && bam2bam) + usb_bam_set_inactivity_timer(cur_bam); + + ctx->pipes_enabled_per_bam += 1; + + /* + * Notify USB connected on the first two pipes connected for + * tethered function's producer and consumer only. Current + * understanding is that there won't be more than 3 pipes used + * in USB BAM2BAM IPA mode i.e. 2 consumers and 1 producer. + * If more producer and consumer pipe are being used, this + * logic is required to be revisited here. + */ + if (ctx->pipes_enabled_per_bam >= 2 && + ipa_params->dir == PEER_PERIPHERAL_TO_USB && !is_dpl) + notify_usb_connected(cur_bam); + spin_unlock(&ctx->usb_bam_lock); + + if (cur_mode == USB_BAM_DEVICE) + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + + log_event_dbg("%s: done\n", __func__); + + return 0; +} +EXPORT_SYMBOL(usb_bam_connect_ipa); + +int usb_bam_get_pipe_type(enum usb_ctrl bam_type, u8 idx, + enum usb_bam_pipe_type *type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + + if (idx >= ctx->max_connections) { + log_event_err("%s: Invalid connection index\n", __func__); + return -EINVAL; + } + if (!type) { + log_event_err("%s: null pointer provided for type\n", __func__); + return -EINVAL; + } else { + *type = pipe_connect->pipe_type; + } + + return 0; +} +EXPORT_SYMBOL(usb_bam_get_pipe_type); + +static void usb_bam_work(struct work_struct *w) +{ + int i; + struct usb_bam_event_info *event_info = + container_of(w, struct usb_bam_event_info, event_w); + struct usb_bam_pipe_connect *pipe_connect = + container_of(event_info, struct usb_bam_pipe_connect, event); + struct usb_bam_ctx_type *ctx = &msm_usb_bam[pipe_connect->bam_type]; + struct usb_bam_pipe_connect *pipe_iter; + int (*callback)(void *priv); + void *param = NULL; + + switch (event_info->type) { + case USB_BAM_EVENT_WAKEUP: + case USB_BAM_EVENT_WAKEUP_PIPE: + + log_event_dbg("%s received USB_BAM_EVENT_WAKEUP\n", __func__); + + /* + * Make sure the PROD resource is granted before + * wakeup hsic host class driver (done by the callback below) + */ + if (pipe_connect->peer_bam == IPA_P_BAM && + pipe_connect->bam_mode == USB_BAM_HOST && + info[pipe_connect->bam_type].cur_prod_state + != IPA_RM_RESOURCE_GRANTED) { + wait_for_prod_granted(pipe_connect->bam_type); + } + + /* + * Check if need to resume the hsic host. + * On one hand, since we got the wakeup interrupt + * the hsic bam clocks are already enabled, so no need + * to actualluy resume the hardware... However, we still need + * to update the usb bam driver state (to set in_lpm=false), + * and to wake ipa and to hold again the hsic host + * device again to avoid it going to low poer mode next time + * until we complete releasing the hsic consumer and producer + * resources against the ipa resource manager. + */ + spin_lock(&ctx->usb_bam_lock); + if (pipe_connect->bam_mode == USB_BAM_HOST) + usb_bam_resume_host(pipe_connect->bam_type); + spin_unlock(&ctx->usb_bam_lock); + + /* Notify about wakeup / activity of the bam */ + if (event_info->callback) + event_info->callback(event_info->param); + + /* + * Reset inactivity timer counter if this pipe's bam + * has inactivity timeout. + */ + spin_lock(&ctx->usb_bam_lock); + if (ctx->inactivity_timer_ms) + usb_bam_set_inactivity_timer(pipe_connect->bam_type); + spin_unlock(&ctx->usb_bam_lock); + + if (pipe_connect->bam_mode == USB_BAM_DEVICE) { + /* A2 wakeup not from LPM (CONS was up) */ + wait_for_prod_granted(pipe_connect->bam_type); + if (pipe_connect->start) { + log_event_dbg("%s: Enqueue PROD transfer\n", + __func__); + pipe_connect->start( + pipe_connect->start_stop_param, + USB_TO_PEER_PERIPHERAL); + } + } + + break; + + case USB_BAM_EVENT_INACTIVITY: + + log_event_dbg("%s received USB_BAM_EVENT_INACTIVITY\n", + __func__); + + /* + * Since event info is one structure per pipe, it might be + * overriden when we will register the wakeup events below, + * and still we want ot register the wakeup events before we + * notify on the inactivity in order to identify the next + * activity as soon as possible. + */ + callback = event_info->callback; + param = event_info->param; + + /* + * Upon inactivity, configure wakeup irq for all pipes + * that are into the usb bam. + */ + spin_lock(&ctx->usb_bam_lock); + for (i = 0; i < ctx->max_connections; i++) { + pipe_iter = &ctx->usb_bam_connections[i]; + if (pipe_iter->bam_type == pipe_connect->bam_type && + pipe_iter->dir == PEER_PERIPHERAL_TO_USB && + pipe_iter->enabled) { + log_event_dbg("%s: Register wakeup on pipe %p\n", + __func__, pipe_iter); + __usb_bam_register_wake_cb( + pipe_connect->bam_type, i, + pipe_iter->activity_notify, + pipe_iter->priv, + false); + } + } + spin_unlock(&ctx->usb_bam_lock); + + /* Notify about the inactivity to the USB class driver */ + if (callback) + callback(param); + + wait_for_prod_release(pipe_connect->bam_type); + log_event_dbg("%s: complete wait on hsic producer s=%d\n", + __func__, info[pipe_connect->bam_type].cur_prod_state); + + /* + * Allow to go to lpm for now if also consumer is down. + * If consumer is up, we will wait to the release consumer + * notification. + */ + if (host_info[pipe_connect->bam_type].dev && + info[pipe_connect->bam_type].cur_cons_state == + IPA_RM_RESOURCE_RELEASED && + !info[pipe_connect->bam_type].in_lpm) { + usb_bam_suspend_core(pipe_connect->bam_type, + pipe_connect->bam_mode, 1); + } + + break; + default: + log_event_err("%s: unknown usb bam event type %d\n", __func__, + event_info->type); + } +} + +static void usb_bam_wake_cb(struct sps_event_notify *notify) +{ + struct usb_bam_event_info *event_info = + (struct usb_bam_event_info *)notify->user; + struct usb_bam_pipe_connect *pipe_connect = + container_of(event_info, + struct usb_bam_pipe_connect, + event); + enum usb_ctrl bam = pipe_connect->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + + spin_lock(&ctx->usb_bam_lock); + + if (event_info->type == USB_BAM_EVENT_WAKEUP_PIPE) + queue_work(ctx->usb_bam_wq, &event_info->event_w); + else if (event_info->type == USB_BAM_EVENT_WAKEUP && + ctx->is_bam_inactivity) { + + /* + * Sps wake event is per pipe, so usb_bam_wake_cb is + * called per pipe. However, we want to filter the wake + * event to be wake event per all the pipes. + * Therefore, the first pipe that awaked will be considered + * as global bam wake event. + */ + ctx->is_bam_inactivity = false; + + queue_work(ctx->usb_bam_wq, &event_info->event_w); + } + + spin_unlock(&ctx->usb_bam_lock); +} + +static int __usb_bam_register_wake_cb(enum usb_ctrl bam_type, int idx, + int (*callback)(void *user), void *param, + bool trigger_cb_per_pipe) +{ + struct sps_pipe *pipe; + struct sps_connect *sps_connection; + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_event_info *wake_event_info; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + int ret; + + if (idx < 0 || idx > ctx->max_connections) { + log_event_err("%s:idx is wrong %d\n", __func__, idx); + return -EINVAL; + } + pipe = ctx->usb_bam_sps.sps_pipes[idx]; + sps_connection = &ctx->usb_bam_sps.sps_connections[idx]; + pipe_connect = &ctx->usb_bam_connections[idx]; + wake_event_info = &pipe_connect->event; + + wake_event_info->type = (trigger_cb_per_pipe ? + USB_BAM_EVENT_WAKEUP_PIPE : + USB_BAM_EVENT_WAKEUP); + wake_event_info->param = param; + wake_event_info->callback = callback; + wake_event_info->event.mode = SPS_TRIGGER_CALLBACK; + wake_event_info->event.xfer_done = NULL; + wake_event_info->event.callback = callback ? usb_bam_wake_cb : NULL; + wake_event_info->event.user = wake_event_info; + wake_event_info->event.options = SPS_O_WAKEUP; + ret = sps_register_event(pipe, &wake_event_info->event); + if (ret) { + log_event_err("%s: sps_register_event() failed %d\n", + __func__, ret); + return ret; + } + + sps_connection->options = callback ? + (SPS_O_AUTO_ENABLE | SPS_O_WAKEUP | SPS_O_WAKEUP_IS_ONESHOT) : + SPS_O_AUTO_ENABLE; + ret = sps_set_config(pipe, sps_connection); + if (ret) { + log_event_err("%s: sps_set_config() failed %d\n", + __func__, ret); + return ret; + } + log_event_dbg("%s: success\n", __func__); + return 0; +} + +int usb_bam_register_wake_cb(enum usb_ctrl bam_type, u8 idx, + int (*callback)(void *user), void *param) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + + info[pipe_connect->bam_type].wake_cb = callback; + info[pipe_connect->bam_type].wake_param = param; + return __usb_bam_register_wake_cb(bam_type, idx, callback, param, true); +} + +int usb_bam_register_start_stop_cbs(enum usb_ctrl bam_type, u8 dst_idx, + void (*start)(void *, enum usb_bam_pipe_dir), + void (*stop)(void *, enum usb_bam_pipe_dir), void *param) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[dst_idx]; + + log_event_dbg("%s: Register for %d\n", __func__, dst_idx); + pipe_connect->start = start; + pipe_connect->stop = stop; + pipe_connect->start_stop_param = param; + + return 0; +} + +int usb_bam_disconnect_pipe(enum usb_ctrl bam_type, u8 idx) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_connect; + struct device *bam_dev = &ctx->usb_bam_pdev->dev; + int ret; + struct msm_usb_bam_platform_data *pdata = + ctx->usb_bam_pdev->dev.platform_data; + + pipe_connect = &ctx->usb_bam_connections[idx]; + + if (!pipe_connect->enabled) { + log_event_err("%s: connection %d isn't enabled\n", + __func__, idx); + return 0; + } + + ret = disconnect_pipe(bam_type, idx); + if (ret) { + log_event_err("%s: src pipe disconnection failure\n", __func__); + return ret; + } + + pipe_connect->enabled = 0; + spin_lock(&ctx->usb_bam_lock); + if (!ctx->pipes_enabled_per_bam) { + log_event_err("%s: wrong pipes enabled counter for bam_type=%d\n", + __func__, bam_type); + } else { + ctx->pipes_enabled_per_bam -= 1; + } + spin_unlock(&ctx->usb_bam_lock); + + log_event_dbg("%s: success disconnecting pipe %d\n", __func__, idx); + + if (pdata->reset_on_disconnect && !ctx->pipes_enabled_per_bam) { + if (bam_type == CI_CTRL) + msm_hw_bam_disable(1); + + sps_device_reset(ctx->h_bam); + + if (bam_type == CI_CTRL) + msm_hw_bam_disable(0); + /* Enable usb irq here which is disabled in function drivers + * during disconnect after BAM reset. + */ + if (bam_type == CI_CTRL) + msm_usb_irq_disable(false); + } + /* This function is directly called by USB Transport drivers + * to disconnect pipes. Drop runtime usage count here. For + * IPA, caller takes care of it + */ + if (pipe_connect->peer_bam != IPA_P_BAM) { + log_event_dbg("%s: PM Runtime PUT %d, count: %d\n", + __func__, idx, get_pm_runtime_counter(bam_dev)); + pm_runtime_put_sync(bam_dev); + } + + return 0; +} + +/** + * is_ipa_hanlde_valid: Check if ipa_handle is valid or not + * @ipa_handle: IPA Handle for producer or consumer + * + * Returns true is ipa handle is valid. + */ +static bool is_ipa_handle_valid(u32 ipa_handle) +{ + + return (ipa_handle != -1); +} + +int usb_bam_disconnect_ipa(enum usb_ctrl cur_bam, + struct usb_bam_connect_ipa_params *ipa_params) +{ + int ret = 0, pipes_disconncted = 0; + u8 idx = 0; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect; + struct device *bam_dev = &ctx->usb_bam_pdev->dev; + enum usb_bam_mode bam_mode; + + if (!is_ipa_handle_valid(ipa_params->prod_clnt_hdl) && + !is_ipa_handle_valid(ipa_params->cons_clnt_hdl)) { + log_event_err("%s: Both IPA handles are invalid.\n", __func__); + return -EINVAL; + } + + log_event_dbg("%s: Starting disconnect sequence\n", __func__); + log_event_dbg("%s(): prod_clnt_hdl:%d cons_clnt_hdl:%d\n", __func__, + ipa_params->prod_clnt_hdl, ipa_params->cons_clnt_hdl); + if (is_ipa_handle_valid(ipa_params->prod_clnt_hdl)) + idx = ipa_params->dst_idx; + if (is_ipa_handle_valid(ipa_params->cons_clnt_hdl)) + idx = ipa_params->src_idx; + pipe_connect = &ctx->usb_bam_connections[idx]; + bam_mode = pipe_connect->bam_mode; + + if (bam_mode != USB_BAM_DEVICE) + return -EINVAL; + + mutex_lock(&info[cur_bam].suspend_resume_mutex); + /* Delay USB core to go into lpm before we finish our handshake */ + if (is_ipa_handle_valid(ipa_params->prod_clnt_hdl)) { + ret = usb_bam_disconnect_ipa_prod(ipa_params, cur_bam); + if (ret) + goto out; + pipes_disconncted++; + } + + if (is_ipa_handle_valid(ipa_params->cons_clnt_hdl)) { + ret = usb_bam_disconnect_ipa_cons(ipa_params, cur_bam); + if (ret) + goto out; + pipes_disconncted++; + } + + /* Notify CONS release on the last cons pipe released */ + if (!ctx->pipes_enabled_per_bam) { + if (info[cur_bam].cur_cons_state == + IPA_RM_RESOURCE_RELEASED) { + log_event_dbg("%s: Notify CONS_RELEASED\n", __func__); + ipa_rm_notify_completion( + IPA_RM_RESOURCE_RELEASED, + ipa_rm_resource_cons[cur_bam]); + } + } + +out: + /* Pipes are connected one by one, but can get disconnected in pairs */ + while (pipes_disconncted--) { + if (!info[cur_bam].pipes_suspended) { + log_event_dbg("%s: PM Runtime PUT %d, count: %d\n", + __func__, pipes_disconncted, + get_pm_runtime_counter(bam_dev)); + pm_runtime_put_sync(&ctx->usb_bam_pdev->dev); + } + } + + mutex_unlock(&info[cur_bam].suspend_resume_mutex); + + return ret; +} +EXPORT_SYMBOL(usb_bam_disconnect_ipa); + +static void usb_bam_sps_events(enum sps_callback_case sps_cb_case, void *user) +{ + int i; + int bam; + struct usb_bam_ctx_type *ctx; + struct usb_bam_pipe_connect *pipe_connect; + struct usb_bam_event_info *event_info; + + switch (sps_cb_case) { + case SPS_CALLBACK_BAM_TIMER_IRQ: + + log_event_dbg("%s: received SPS_CALLBACK_BAM_TIMER_IRQ\n", + __func__); + + spin_lock(&ctx->usb_bam_lock); + + bam = get_bam_type_from_core_name((char *)user); + if (bam < 0 || bam >= MAX_BAMS) { + log_event_err("%s: Invalid bam, type=%d ,name=%s\n", + __func__, bam, (char *)user); + spin_unlock(&ctx->usb_bam_lock); + return; + } + ctx = &msm_usb_bam[bam]; + + ctx->is_bam_inactivity = true; + log_event_dbg("%s: Inactivity happened on bam=%s,%d\n", + __func__, (char *)user, bam); + + for (i = 0; i < ctx->max_connections; i++) { + pipe_connect = &ctx->usb_bam_connections[i]; + + /* + * Notify inactivity once, Since it is global + * for all pipes on bam. Notify only if we have + * connected pipes. + */ + if (pipe_connect->enabled) { + event_info = &pipe_connect->event; + event_info->type = USB_BAM_EVENT_INACTIVITY; + event_info->param = pipe_connect->priv; + event_info->callback = + pipe_connect->inactivity_notify; + queue_work(ctx->usb_bam_wq, + &event_info->event_w); + break; + } + } + + spin_unlock(&ctx->usb_bam_lock); + + break; + default: + log_event_dbg("%s: received sps_cb_case=%d\n", __func__, + (int)sps_cb_case); + } +} + +static struct msm_usb_bam_platform_data *usb_bam_dt_to_pdata( + struct platform_device *pdev, u32 usb_addr) +{ + struct msm_usb_bam_platform_data *pdata; + struct device_node *node = pdev->dev.of_node; + int rc = 0; + u8 i = 0; + u32 bam, bam_mode; + u32 addr; + u32 threshold, max_connections = 0; + static struct usb_bam_pipe_connect *usb_bam_connections; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + return NULL; + } + + rc = of_property_read_u32(node, "qcom,bam-type", &bam); + if (rc) { + log_event_err("%s: bam type is missing in device tree\n", + __func__); + return NULL; + } + if (bam >= MAX_BAMS) { + log_event_err("%s: Invalid bam type %d in device tree\n", + __func__, bam); + return NULL; + } + pdata->bam_type = bam; + + rc = of_property_read_u32(node, "qcom,bam-mode", &bam_mode); + if (rc) { + pr_debug("%s: bam mode is missing in device tree\n", + __func__); + /* Default to DEVICE if bam_mode is not specified */ + bam_mode = USB_BAM_DEVICE; + } + + pdata->reset_on_connect = of_property_read_bool(node, + "qcom,reset-bam-on-connect"); + + pdata->reset_on_disconnect = of_property_read_bool(node, + "qcom,reset-bam-on-disconnect"); + + rc = of_property_read_u32(node, "qcom,usb-bam-num-pipes", + &pdata->usb_bam_num_pipes); + if (rc) { + log_event_err("Invalid usb bam num pipes property\n"); + return NULL; + } + + rc = of_property_read_u32(node, "qcom,usb-bam-max-mbps-highspeed", + &pdata->max_mbps_highspeed); + if (rc) + pdata->max_mbps_highspeed = 0; + + rc = of_property_read_u32(node, "qcom,usb-bam-max-mbps-superspeed", + &pdata->max_mbps_superspeed); + if (rc) + pdata->max_mbps_superspeed = 0; + + rc = of_property_read_u32(node, "qcom,usb-bam-fifo-baseaddr", &addr); + if (rc) + pr_debug("%s: Invalid usb base address property\n", __func__); + else + pdata->usb_bam_fifo_baseaddr = addr; + + pdata->ignore_core_reset_ack = of_property_read_bool(node, + "qcom,ignore-core-reset-ack"); + + pdata->disable_clk_gating = of_property_read_bool(node, + "qcom,disable-clk-gating"); + + rc = of_property_read_u32(node, "qcom,usb-bam-override-threshold", + &threshold); + if (rc) + pdata->override_threshold = USB_THRESHOLD; + else + pdata->override_threshold = threshold; + + pdata->enable_hsusb_bam_on_boot = of_property_read_bool(node, + "qcom,enable-hsusb-bam-on-boot"); + + for_each_child_of_node(pdev->dev.of_node, node) + max_connections++; + + if (!max_connections) { + log_event_err("%s: error: max_connections is zero\n", __func__); + goto err; + } + + usb_bam_connections = devm_kzalloc(&pdev->dev, max_connections * + sizeof(struct usb_bam_pipe_connect), GFP_KERNEL); + + if (!usb_bam_connections) { + log_event_err("%s: devm_kzalloc failed(%d)\n", + __func__, __LINE__); + return NULL; + } + + /* retrieve device tree parameters */ + for_each_child_of_node(pdev->dev.of_node, node) { + usb_bam_connections[i].bam_type = bam; + usb_bam_connections[i].bam_mode = bam_mode; + + rc = of_property_read_string(node, "label", + &usb_bam_connections[i].name); + if (rc) + goto err; + + rc = of_property_read_u32(node, "qcom,usb-bam-mem-type", + &usb_bam_connections[i].mem_type); + if (rc) + goto err; + + if (usb_bam_connections[i].mem_type == OCI_MEM) { + if (!pdata->usb_bam_fifo_baseaddr) { + log_event_err("%s: base address is missing\n", + __func__); + goto err; + } + } + rc = of_property_read_u32(node, "qcom,peer-bam", + &usb_bam_connections[i].peer_bam); + if (rc) { + log_event_err("%s: peer bam is missing in device tree\n", + __func__); + goto err; + } + /* + * Store USB bam_type to be used with QDSS. As only one device + * bam is currently supported, check the same in DT connections + */ + if (usb_bam_connections[i].peer_bam == QDSS_P_BAM) { + if (qdss_usb_bam_type) { + log_event_err("%s: overriding QDSS pipe!, update DT\n", + __func__); + } + qdss_usb_bam_type = usb_bam_connections[i].bam_type; + } + + rc = of_property_read_u32(node, "qcom,dir", + &usb_bam_connections[i].dir); + if (rc) { + log_event_err("%s: direction is missing in device tree\n", + __func__); + goto err; + } + + rc = of_property_read_u32(node, "qcom,pipe-num", + &usb_bam_connections[i].pipe_num); + if (rc) { + log_event_err("%s: pipe num is missing in device tree\n", + __func__); + goto err; + } + + rc = of_property_read_u32(node, "qcom,pipe-connection-type", + &usb_bam_connections[i].pipe_type); + if (rc) + pr_debug("%s: pipe type is defaulting to bam2bam\n", + __func__); + + of_property_read_u32(node, "qcom,peer-bam-physical-address", + &addr); + if (usb_bam_connections[i].dir == USB_TO_PEER_PERIPHERAL) { + usb_bam_connections[i].src_phy_addr = usb_addr; + usb_bam_connections[i].dst_phy_addr = addr; + } else { + usb_bam_connections[i].src_phy_addr = addr; + usb_bam_connections[i].dst_phy_addr = usb_addr; + } + + of_property_read_u32(node, "qcom,src-bam-pipe-index", + &usb_bam_connections[i].src_pipe_index); + + of_property_read_u32(node, "qcom,dst-bam-pipe-index", + &usb_bam_connections[i].dst_pipe_index); + + of_property_read_u32(node, "qcom,data-fifo-offset", + &usb_bam_connections[i].data_fifo_base_offset); + + rc = of_property_read_u32(node, "qcom,data-fifo-size", + &usb_bam_connections[i].data_fifo_size); + if (rc) + goto err; + + of_property_read_u32(node, "qcom,descriptor-fifo-offset", + &usb_bam_connections[i].desc_fifo_base_offset); + + rc = of_property_read_u32(node, "qcom,descriptor-fifo-size", + &usb_bam_connections[i].desc_fifo_size); + if (rc) + goto err; + i++; + } + + msm_usb_bam[bam].usb_bam_connections = usb_bam_connections; + msm_usb_bam[bam].max_connections = max_connections; + + return pdata; +err: + log_event_err("%s: failed\n", __func__); + return NULL; +} + +static int usb_bam_init(struct platform_device *pdev) +{ + int ret; + struct msm_usb_bam_platform_data *pdata = pdev->dev.platform_data; + enum usb_ctrl bam_type = pdata->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct sps_bam_props props; + + memset(&props, 0, sizeof(props)); + + pr_debug("%s: usb_bam_init - %s\n", __func__, + bam_enable_strings[bam_type]); + + props.phys_addr = ctx->io_res->start; + props.virt_addr = ctx->regs; + props.virt_size = resource_size(ctx->io_res); + props.irq = ctx->irq; + props.summing_threshold = pdata->override_threshold; + props.event_threshold = pdata->override_threshold; + props.num_pipes = pdata->usb_bam_num_pipes; + props.callback = usb_bam_sps_events; + props.user = bam_enable_strings[bam_type]; + + /* + * HSUSB and HSIC Cores don't support RESET ACK signal to BAMs + * Hence, let BAM to ignore acknowledge from USB while resetting PIPE + */ + if (pdata->ignore_core_reset_ack && bam_type != DWC3_CTRL) + props.options = SPS_BAM_NO_EXT_P_RST; + + if (pdata->disable_clk_gating) + props.options |= SPS_BAM_NO_LOCAL_CLK_GATING; + + /* + * HSUSB BAM is not NDP BAM and it must be enabled early before + * starting peripheral controller to avoid switching USB core mode + * from legacy to BAM with ongoing data transfers. + */ + if (pdata->enable_hsusb_bam_on_boot && bam_type == CI_CTRL) { + pr_debug("Register and enable HSUSB BAM\n"); + props.options |= SPS_BAM_OPT_ENABLE_AT_BOOT; + } + ret = sps_register_bam_device(&props, &ctx->h_bam); + + if (ret < 0) { + log_event_err("%s: register bam error %d\n", __func__, ret); + return -EFAULT; + } + + return 0; +} + +static int enable_usb_bam(struct platform_device *pdev) +{ + int ret; + struct msm_usb_bam_platform_data *pdata = pdev->dev.platform_data; + enum usb_ctrl bam_type = pdata->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + ret = usb_bam_init(pdev); + if (ret) { + log_event_err("failed to init bam %s\n", + bam_enable_strings[bam_type]); + return ret; + } + + ctx->usb_bam_sps.sps_pipes = devm_kzalloc(&pdev->dev, + ctx->max_connections * sizeof(struct sps_pipe *), + GFP_KERNEL); + + if (!ctx->usb_bam_sps.sps_pipes) { + log_event_err("%s: failed to allocate sps_pipes\n", __func__); + return -ENOMEM; + } + + ctx->usb_bam_sps.sps_connections = devm_kzalloc(&pdev->dev, + ctx->max_connections * sizeof(struct sps_connect), + GFP_KERNEL); + if (!ctx->usb_bam_sps.sps_connections) { + log_event_err("%s: failed to allocate sps_connections\n", + __func__); + return -ENOMEM; + } + + return 0; +} + +static ssize_t +usb_bam_show_inactivity_timer(struct device *dev, struct device_attribute *attr, + char *buf) +{ + char *buff = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(bam_enable_strings); i++) { + buff += snprintf(buff, PAGE_SIZE, "%s: %dms\n", + bam_enable_strings[i], + msm_usb_bam[i].inactivity_timer_ms); + } + + return buff - buf; +} + +static ssize_t usb_bam_store_inactivity_timer(struct device *dev, + struct device_attribute *attr, + const char *buff, size_t count) +{ + char buf[USB_BAM_MAX_STR_LEN]; + char *trimmed_buf, *bam_str, *bam_name, *timer; + int timer_d; + int bam; + + if (strnstr(buff, "help", USB_BAM_MAX_STR_LEN)) { + pr_info("Usage: , ,...\n"); + pr_info("\tbam_name: [%s, %s, %s]\n", + bam_enable_strings[DWC3_CTRL], + bam_enable_strings[CI_CTRL], + bam_enable_strings[HSIC_CTRL]); + pr_info("\tms: time in ms. Use 0 to disable timer\n"); + return count; + } + + strlcpy(buf, buff, sizeof(buf)); + trimmed_buf = strim(buf); + + while (trimmed_buf) { + bam_str = strsep(&trimmed_buf, ","); + if (bam_str) { + bam_name = strsep(&bam_str, " "); + bam = get_bam_type_from_core_name(bam_name); + if (bam < 0 || bam >= MAX_BAMS) { + log_event_err("%s: Invalid bam, type=%d ,name=%s\n", + __func__, bam, bam_name); + return -EINVAL; + } + + timer = strsep(&bam_str, " "); + + if (!timer) + continue; + + sscanf(timer, "%d", &timer_d); + + /* Apply new timer setting if bam has running pipes */ + if (msm_usb_bam[bam].inactivity_timer_ms != timer_d) { + msm_usb_bam[bam].inactivity_timer_ms = timer_d; + if (msm_usb_bam[bam].pipes_enabled_per_bam > 0 + && !info[bam].in_lpm) + usb_bam_set_inactivity_timer(bam); + } + } + } + + return count; +} + +static DEVICE_ATTR(inactivity_timer, S_IWUSR | S_IRUSR, + usb_bam_show_inactivity_timer, + usb_bam_store_inactivity_timer); + +static int usb_bam_panic_notifier(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int i; + struct usb_bam_ctx_type *ctx; + + for (i = 0; i < MAX_BAMS; i++) { + ctx = &msm_usb_bam[i]; + if (ctx->h_bam) + break; + } + + if (i == MAX_BAMS) + goto fail; + + if (!ctx->pipes_enabled_per_bam || info[i].pipes_suspended) + goto fail; + + pr_err("%s: dump usb bam registers here in call back!\n", + __func__); + sps_get_bam_debug_info(ctx->h_bam, 93, + (SPS_BAM_PIPE(0) | SPS_BAM_PIPE(1)), 0, 2); + +fail: + return NOTIFY_DONE; +} + +static struct notifier_block usb_bam_panic_blk = { + .notifier_call = usb_bam_panic_notifier, +}; + +void usb_bam_register_panic_hdlr(void) +{ + atomic_notifier_chain_register(&panic_notifier_list, + &usb_bam_panic_blk); +} + +static int usb_bam_probe(struct platform_device *pdev) +{ + int ret, i, irq; + struct resource *io_res; + void __iomem *regs; + enum usb_ctrl bam_type; + struct usb_bam_ctx_type *ctx; + struct msm_usb_bam_platform_data *pdata; + + dev_dbg(&pdev->dev, "usb_bam_probe\n"); + + ret = device_create_file(&pdev->dev, &dev_attr_inactivity_timer); + if (ret) { + dev_err(&pdev->dev, "failed to create fs node\n"); + return ret; + } + + io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!io_res) { + dev_err(&pdev->dev, "missing BAM memory resource\n"); + return -ENODEV; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + return irq; + } + + regs = devm_ioremap(&pdev->dev, io_res->start, resource_size(io_res)); + if (!regs) { + log_event_err("%s: ioremap failed\n", __func__); + return -ENOMEM; + } + + /* specify BAM physical address to be filled in BAM connections */ + pdata = usb_bam_dt_to_pdata(pdev, io_res->start); + if (!pdata) + return -EINVAL; + + pdev->dev.platform_data = pdata; + bam_type = pdata->bam_type; + ctx = &msm_usb_bam[bam_type]; + + ctx->usb_bam_pdev = pdev; + ctx->irq = irq; + ctx->regs = regs; + ctx->io_res = io_res; + + for (i = 0; i < ctx->max_connections; i++) { + ctx->usb_bam_connections[i].enabled = 0; + INIT_WORK(&ctx->usb_bam_connections[i].event.event_w, + usb_bam_work); + } + + init_completion(&info[bam_type].prod_avail); + complete(&info[bam_type].prod_avail); + init_completion(&info[bam_type].prod_released); + complete(&info[bam_type].prod_released); + info[bam_type].cur_prod_state = IPA_RM_RESOURCE_RELEASED; + info[bam_type].cur_cons_state = IPA_RM_RESOURCE_RELEASED; + info[bam_type].bam_type = bam_type; + INIT_WORK(&info[bam_type].resume_work, usb_bam_finish_resume); + INIT_WORK(&info[bam_type].finish_suspend_work, usb_bam_finish_suspend_); + mutex_init(&info[bam_type].suspend_resume_mutex); + + ctx->usb_bam_wq = alloc_workqueue("usb_bam_wq", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!ctx->usb_bam_wq) { + log_event_err("unable to create workqueue usb_bam_wq\n"); + return -ENOMEM; + } + + ret = enable_usb_bam(pdev); + if (ret) { + destroy_workqueue(ctx->usb_bam_wq); + return ret; + } + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + spin_lock_init(&usb_bam_ipa_handshake_info_lock); + if (ipa_get_transport_type() == IPA_TRANSPORT_TYPE_SPS && + ipa_is_ready()) + usb_bam_ipa_create_resources(bam_type); + spin_lock_init(&ctx->usb_bam_lock); + + usb_bam_register_panic_hdlr(); + return ret; +} + +bool usb_bam_get_prod_granted(enum usb_ctrl bam_type, u8 idx) +{ + return (info[bam_type].cur_prod_state == IPA_RM_RESOURCE_GRANTED); +} +EXPORT_SYMBOL(usb_bam_get_prod_granted); + +int get_bam2bam_connection_info(enum usb_ctrl bam_type, u8 idx, + u32 *usb_bam_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + enum usb_bam_pipe_dir dir = pipe_connect->dir; + + if (dir == USB_TO_PEER_PERIPHERAL) + *usb_bam_pipe_idx = pipe_connect->src_pipe_index; + else + *usb_bam_pipe_idx = pipe_connect->dst_pipe_index; + + if (data_fifo) + memcpy(data_fifo, &pipe_connect->data_mem_buf, + sizeof(struct sps_mem_buffer)); + if (desc_fifo) + memcpy(desc_fifo, &pipe_connect->desc_mem_buf, + sizeof(struct sps_mem_buffer)); + if (mem_type) + *mem_type = pipe_connect->mem_type; + + return 0; +} +EXPORT_SYMBOL(get_bam2bam_connection_info); + +int get_qdss_bam_connection_info(unsigned long *usb_bam_handle, + u32 *usb_bam_pipe_idx, u32 *peer_pipe_idx, + struct sps_mem_buffer *desc_fifo, struct sps_mem_buffer *data_fifo, + enum usb_pipe_mem_type *mem_type) +{ + u8 idx; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[qdss_usb_bam_type]; + struct sps_connect *sps_connection; + + /* QDSS uses only one pipe */ + idx = usb_bam_get_connection_idx(qdss_usb_bam_type, QDSS_P_BAM, + PEER_PERIPHERAL_TO_USB, USB_BAM_DEVICE, 0); + + get_bam2bam_connection_info(qdss_usb_bam_type, idx, usb_bam_pipe_idx, + desc_fifo, data_fifo, mem_type); + + + sps_connection = &ctx->usb_bam_sps.sps_connections[idx]; + *usb_bam_handle = sps_connection->destination; + *peer_pipe_idx = sps_connection->src_pipe_index; + + return 0; +} +EXPORT_SYMBOL(get_qdss_bam_connection_info); + +int usb_bam_get_connection_idx(enum usb_ctrl bam_type, enum peer_bam client, + enum usb_bam_pipe_dir dir, enum usb_bam_mode bam_mode, u32 num) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + u8 i; + + for (i = 0; i < ctx->max_connections; i++) { + if (ctx->usb_bam_connections[i].peer_bam == client && + ctx->usb_bam_connections[i].dir == dir && + ctx->usb_bam_connections[i].bam_mode == bam_mode && + ctx->usb_bam_connections[i].pipe_num == num) { + log_event_dbg("%s: index %d was found\n", __func__, i); + return i; + } + } + + log_event_err("%s: failed for %d\n", __func__, bam_type); + return -ENODEV; +} +EXPORT_SYMBOL(usb_bam_get_connection_idx); + +int usb_bam_get_bam_type(const char *core_name) +{ + int bam_type = get_bam_type_from_core_name(core_name); + + if (bam_type < 0 || bam_type >= MAX_BAMS) { + log_event_err("%s: Invalid bam, type=%d, name=%s\n", + __func__, bam_type, core_name); + return -EINVAL; + } + + return bam_type; +} +EXPORT_SYMBOL(usb_bam_get_bam_type); + +bool msm_usb_bam_enable(enum usb_ctrl bam, bool bam_enable) +{ + struct msm_usb_bam_platform_data *pdata; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + + if (!ctx->usb_bam_pdev) + return 0; + + pdata = ctx->usb_bam_pdev->dev.platform_data; + if ((bam != CI_CTRL) || !(bam_enable || + pdata->enable_hsusb_bam_on_boot)) + return 0; + + msm_hw_bam_disable(1); + sps_device_reset(ctx->h_bam); + msm_hw_bam_disable(0); + + return 0; +} +EXPORT_SYMBOL(msm_usb_bam_enable); + +/** + * msm_bam_hsic_host_pipe_empty - Check all HSIC host BAM pipe state + * + * return true if all BAM pipe used for HSIC Host mode is empty. + */ +bool msm_bam_hsic_host_pipe_empty(void) +{ + struct usb_bam_pipe_connect *pipe_connect; + struct sps_pipe *pipe = NULL; + enum usb_ctrl bam = HSIC_CTRL; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + int i, ret; + u32 status; + + pr_debug("%s: enter\n", __func__); + + for (i = 0; i < ctx->max_connections; i++) { + pipe_connect = &ctx->usb_bam_connections[i]; + if (pipe_connect->enabled) { + pipe = ctx->usb_bam_sps.sps_pipes[i]; + ret = sps_is_pipe_empty(pipe, &status); + if (ret) { + log_event_err("%s(): sps_is_pipe_empty() failed\n", + __func__); + log_event_err("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + WARN_ON(1); + } + + if (!status) { + log_event_err("%s(): pipe is not empty.\n", + __func__); + log_event_err("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + return false; + } else { + pr_debug("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + } + } + + } + + if (!pipe) + log_event_err("%s: Bam %s has no connected pipes\n", __func__, + bam_enable_strings[bam]); + + return true; +} +EXPORT_SYMBOL(msm_bam_hsic_host_pipe_empty); + +bool msm_bam_hsic_lpm_ok(void) +{ + log_event_dbg("%s: enter\n", __func__); + + if (info[HSIC_CTRL].cur_bam_mode == USB_BAM_HOST) + return msm_bam_host_lpm_ok(HSIC_CTRL); + + /* RuntimPM is used to manage device mode LPM */ + return 0; +} +EXPORT_SYMBOL(msm_bam_hsic_lpm_ok); + +static int usb_bam_remove(struct platform_device *pdev) +{ + struct msm_usb_bam_platform_data *pdata = pdev->dev.platform_data; + enum usb_ctrl bam_type = pdata->bam_type; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + destroy_workqueue(ctx->usb_bam_wq); + + return 0; +} + +static const struct of_device_id usb_bam_dt_match[] = { + { .compatible = "qcom,usb-bam-msm", + }, + {} +}; +MODULE_DEVICE_TABLE(of, usb_bam_dt_match); + +static struct platform_driver usb_bam_driver = { + .probe = usb_bam_probe, + .remove = usb_bam_remove, + .driver = { + .name = "usb_bam", + .of_match_table = usb_bam_dt_match, + }, +}; + +static int __init init(void) +{ + return platform_driver_register(&usb_bam_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + platform_driver_unregister(&usb_bam_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION("MSM USB BAM DRIVER"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb_bam.h b/include/linux/usb_bam.h new file mode 100644 index 000000000000..ebc2fe898bef --- /dev/null +++ b/include/linux/usb_bam.h @@ -0,0 +1,529 @@ +/* Copyright (c) 2011-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. + */ + +#ifndef _USB_BAM_H_ +#define _USB_BAM_H_ +#include +#include +#include + +#define MAX_BAMS NUM_CTRL /* Bam per USB controllers */ + + +enum usb_bam_mode { + USB_BAM_DEVICE = 0, + USB_BAM_HOST, +}; + +enum peer_bam { + QDSS_P_BAM = 0, + IPA_P_BAM, + MAX_PEER_BAMS, +}; + +enum usb_bam_pipe_dir { + USB_TO_PEER_PERIPHERAL, + PEER_PERIPHERAL_TO_USB, +}; + +enum usb_pipe_mem_type { + SPS_PIPE_MEM = 0, /* Default, SPS dedicated pipe memory */ + SYSTEM_MEM, /* System RAM, requires allocation */ + OCI_MEM, /* Shared memory among peripherals */ +}; + +enum usb_bam_event_type { + USB_BAM_EVENT_WAKEUP_PIPE = 0, /* Wake a pipe */ + USB_BAM_EVENT_WAKEUP, /* Wake a bam (first pipe waked) */ + USB_BAM_EVENT_INACTIVITY, /* Inactivity on all pipes */ +}; + +enum usb_bam_pipe_type { + USB_BAM_PIPE_BAM2BAM = 0, /* Connection is BAM2BAM (default) */ + USB_BAM_PIPE_SYS2BAM, /* Connection is SYS2BAM or BAM2SYS + * depending on usb_bam_pipe_dir + */ + USB_BAM_MAX_PIPE_TYPES, +}; + +/** +* struct usb_bam_connect_ipa_params: Connect Bam pipe to IPA peer infromation. +* @ src_idx: Source pipe index in usb bam pipes lists. +* @ dst_idx: Destination pipe index in usb bam pipes lists. +* @ src_pipe: The source pipe index in the sps level. +* @ dst_pipe: The destination pipe index in the sps level. +* @ keep_ipa_awake: When true, IPA will not be clock gated. +* @ ipa_cons_ep_idx: The pipe index on the IPA peer bam side, consumer. +* @ ipa_prod_ep_idx: The pipe index on the IPA peer bam side, producer. +* @ prod_clnt_hdl: Producer client handle returned by IPA driver +* @ cons_clnt_hdl: Consumer client handle returned by IPA driver +* @ src_client: Source IPA client type. +* @ dst_client: Destination IPA client type. +* @ ipa_ep_cfg: Configuration of IPA end-point (see struct ipa_ep_cfg) +* @priv: Callback cookie to the notify event. +* @notify: Callback on data path event by IPA (see enum ipa_dp_evt_type) +* This call back gets back the priv cookie. +* for Bam2Bam mode, this callback is in the tethering bridge. +* @ activity_notify: Callback to be notified on and data being pushed into the +* USB consumer pipe. +* @ inactivity_notify: Callback to be notified on inactivity of all the current +* open pipes between the USB bam and its peer. +* @ skip_ep_cfg: boolean field that determines if Apps-processor +* should or should not confiugre this end-point. +* (Please see struct teth_bridge_init_params) +* @ reset_pipe_after_lpm: bool to indicate if IPA should reset pipe after LPM. +* @ usb_connection_speed: The actual speed the USB core currently works at. +*/ +struct usb_bam_connect_ipa_params { + u8 src_idx; + u8 dst_idx; + u32 *src_pipe; + u32 *dst_pipe; + bool keep_ipa_awake; + enum usb_bam_pipe_dir dir; + /* Parameters for Port Mapper */ + u32 ipa_cons_ep_idx; + u32 ipa_prod_ep_idx; + /* client handle assigned by IPA to client */ + u32 prod_clnt_hdl; + u32 cons_clnt_hdl; + /* params assigned by the CD */ + enum ipa_client_type src_client; + enum ipa_client_type dst_client; + struct ipa_ep_cfg ipa_ep_cfg; + void *priv; + void (*notify)(void *priv, enum ipa_dp_evt_type evt, + unsigned long data); + int (*activity_notify)(void *priv); + int (*inactivity_notify)(void *priv); + bool skip_ep_cfg; + bool reset_pipe_after_lpm; + enum usb_device_speed usb_connection_speed; +}; + +/** +* struct usb_bam_event_info: suspend/resume event information. +* @type: usb bam event type. +* @event: holds event data. +* @callback: suspend/resume callback. +* @param: port num (for suspend) or NULL (for resume). +* @event_w: holds work queue parameters. +*/ +struct usb_bam_event_info { + enum usb_bam_event_type type; + struct sps_register_event event; + int (*callback)(void *); + void *param; + struct work_struct event_w; +}; + +/** +* struct usb_bam_pipe_connect: pipe connection information +* between USB/HSIC BAM and another BAM. USB/HSIC BAM can be +* either src BAM or dst BAM +* @name: pipe description. +* @mem_type: type of memory used for BAM FIFOs +* @src_phy_addr: src bam physical address. +* @src_pipe_index: src bam pipe index. +* @dst_phy_addr: dst bam physical address. +* @dst_pipe_index: dst bam pipe index. +* @data_fifo_base_offset: data fifo offset. +* @data_fifo_size: data fifo size. +* @desc_fifo_base_offset: descriptor fifo offset. +* @desc_fifo_size: descriptor fifo size. +* @data_mem_buf: data fifo buffer. +* @desc_mem_buf: descriptor fifo buffer. +* @event: event for wakeup. +* @enabled: true if pipe is enabled. +* @suspended: true if pipe is suspended. +* @cons_stopped: true is pipe has consumer requests stopped. +* @prod_stopped: true if pipe has producer requests stopped. +* @ipa_clnt_hdl : pipe handle to ipa api. +* @priv: private data to return upon activity_notify +* or inactivity_notify callbacks. +* @activity_notify: callback to invoke on activity on one of the in pipes. +* @inactivity_notify: callback to invoke on inactivity on all pipes. +* @start: callback to invoke to enqueue transfers on a pipe. +* @stop: callback to invoke on dequeue transfers on a pipe. +* @start_stop_param: param for the start/stop callbacks. +*/ +struct usb_bam_pipe_connect { + const char *name; + u32 pipe_num; + enum usb_pipe_mem_type mem_type; + enum usb_bam_pipe_dir dir; + enum usb_ctrl bam_type; + enum usb_bam_mode bam_mode; + enum peer_bam peer_bam; + enum usb_bam_pipe_type pipe_type; + u32 src_phy_addr; + u32 src_pipe_index; + u32 dst_phy_addr; + u32 dst_pipe_index; + u32 data_fifo_base_offset; + u32 data_fifo_size; + u32 desc_fifo_base_offset; + u32 desc_fifo_size; + struct sps_mem_buffer data_mem_buf; + struct sps_mem_buffer desc_mem_buf; + struct usb_bam_event_info event; + bool enabled; + bool suspended; + bool cons_stopped; + bool prod_stopped; + int ipa_clnt_hdl; + void *priv; + int (*activity_notify)(void *priv); + int (*inactivity_notify)(void *priv); + void (*start)(void *, enum usb_bam_pipe_dir); + void (*stop)(void *, enum usb_bam_pipe_dir); + void *start_stop_param; + bool reset_pipe_after_lpm; +}; + +/** + * struct msm_usb_bam_platform_data: pipe connection information + * between USB/HSIC BAM and another BAM. USB/HSIC BAM can be + * either src BAM or dst BAM + * @usb_bam_num_pipes: max number of pipes to use. + * @active_conn_num: number of active pipe connections. + * @usb_bam_fifo_baseaddr: base address for bam pipe's data and descriptor + * fifos. This can be on chip memory (ocimem) or usb + * private memory. + * @ignore_core_reset_ack: BAM can ignore ACK from USB core during PIPE RESET + * @reset_on_connect: BAM must be reset before its first pipe connect + * @reset_on_disconnect: BAM must be reset after its last pipe disconnect + * @disable_clk_gating: Disable clock gating + * @override_threshold: Override the default threshold value for Read/Write + * event generation by the BAM towards another BAM. + * @max_mbps_highspeed: Maximum Mbits per seconds that the USB core + * can work at in bam2bam mode when connected to HS host. + * @max_mbps_superspeed: Maximum Mbits per seconds that the USB core + * can work at in bam2bam mode when connected to SS host. + * @enable_hsusb_bam_on_boot: Enable HSUSB BAM (non-NDP) on bootup itself + */ +struct msm_usb_bam_platform_data { + u8 max_connections; + int usb_bam_num_pipes; + phys_addr_t usb_bam_fifo_baseaddr; + bool ignore_core_reset_ack; + bool reset_on_connect; + bool reset_on_disconnect; + bool disable_clk_gating; + u32 override_threshold; + u32 max_mbps_highspeed; + u32 max_mbps_superspeed; + bool enable_hsusb_bam_on_boot; + enum usb_ctrl bam_type; +}; + +#ifdef CONFIG_USB_BAM +/** + * Connect USB-to-Peripheral SPS connection. + * + * This function returns the allocated pipe number. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @bam_pipe_idx - allocated pipe index. + * + * @return 0 on success, negative value on error + * + */ +int usb_bam_connect(enum usb_ctrl bam_type, int idx, u32 *bam_pipe_idx); + +/** + * Connect USB-to-IPA SPS connection. + * + * This function returns the allocated pipes number and clnt + * handles. Assumes that the user first connects producer pipes + * and only after that consumer pipes, since that's the correct + * sequence for the handshake with the IPA. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @ipa_params - in/out parameters + * + * @return 0 on success, negative value on error + */ +int usb_bam_connect_ipa(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params); + +/** + * Disconnect USB-to-IPA SPS connection. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @ipa_params - in/out parameters + * + * @return 0 on success, negative value on error + */ +int usb_bam_disconnect_ipa(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params); + +/** + * Register a wakeup callback from peer BAM. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @callback - the callback function + * + * @return 0 on success, negative value on error + */ +int usb_bam_register_wake_cb(enum usb_ctrl bam_type, u8 idx, + int (*callback)(void *), void *param); + +/** + * Register callbacks for start/stop of transfers. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index + * + * @start - the callback function that will be called in USB + * driver to start transfers + * @stop - the callback function that will be called in USB + * driver to stop transfers + * + * @param - context that the caller can supply + * + * @return 0 on success, negative value on error + */ +int usb_bam_register_start_stop_cbs(enum usb_ctrl bam_type, + u8 idx, + void (*start)(void *, enum usb_bam_pipe_dir), + void (*stop)(void *, enum usb_bam_pipe_dir), + void *param); + +/** + * Start usb suspend sequence + * + * @ipa_params - in/out parameters + * + * @bam_type - USB BAM type - dwc3/CI/hsic + */ +void usb_bam_suspend(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params); + +/** + * Start usb resume sequence + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @ipa_params - in/out parameters + */ +void usb_bam_resume(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params); +/** + * Disconnect USB-to-Periperal SPS connection. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @return 0 on success, negative value on error + */ +int usb_bam_disconnect_pipe(enum usb_ctrl bam_type, u8 idx); + +/** + * Returns usb bam connection parameters. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @usb_bam_pipe_idx - Usb bam pipe index. + * + * @desc_fifo - Descriptor fifo parameters. + * + * @data_fifo - Data fifo parameters. + * + * @return pipe index on success, negative value on error. + */ +int get_bam2bam_connection_info(enum usb_ctrl bam_type, u8 idx, + u32 *usb_bam_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type); + +/** + * Returns usb bam connection parameters for qdss pipe. + * @usb_bam_handle - Usb bam handle. + * @usb_bam_pipe_idx - Usb bam pipe index. + * @peer_pipe_idx - Peer pipe index. + * @desc_fifo - Descriptor fifo parameters. + * @data_fifo - Data fifo parameters. + * @return pipe index on success, negative value on error. + */ +int get_qdss_bam_connection_info( + unsigned long *usb_bam_handle, u32 *usb_bam_pipe_idx, + u32 *peer_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type); + +/** +* Indicates if the client of the USB BAM is ready to start +* sending/receiving transfers. +* +*@bam_type - USB BAM type - dwc3/CI/hsic +* +* @client - Usb pipe peer (a2, ipa, qdss...) +* +* @dir - In (from peer to usb) or out (from usb to peer) +* +* @num - Pipe number. +* +* @return 0 on success, negative value on error +*/ +int usb_bam_get_connection_idx(enum usb_ctrl bam_type, enum peer_bam client, + enum usb_bam_pipe_dir dir, enum usb_bam_mode bam_mode, u32 num); + +/** +* return the usb controller bam type used for the supplied connection index +* +* @core_name - Core name (ssusb/hsusb/hsic). +* +* @return usb control bam type +*/ +int usb_bam_get_bam_type(const char *core_name); + +/** +* Indicates the type of connection the USB side of the connection is. +* +* @bam_type - USB BAM type - dwc3/CI/hsic +* +* @idx - Pipe number. +* +* @type - Type of connection +* +* @return 0 on success, negative value on error +*/ +int usb_bam_get_pipe_type(enum usb_ctrl bam_type, + u8 idx, enum usb_bam_pipe_type *type); + +/** +* Indicates whether USB producer is granted to IPA resource manager. +* +* @return true when producer granted, false when prodcuer is released. +*/ +bool usb_bam_get_prod_granted(enum usb_ctrl bam_type, u8 idx); + +/** +* Allocates memory for data fifo and descriptor fifos. +*/ +int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx); + +/** +* Frees memory for data fifo and descriptor fifos. +*/ +int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx); + +#else +static inline int usb_bam_connect(enum usb_ctrl bam, u8 idx, u32 *bam_pipe_idx) +{ + return -ENODEV; +} + +static inline int usb_bam_connect_ipa(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params) +{ + return -ENODEV; +} + +static inline int usb_bam_disconnect_ipa(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params) +{ + return -ENODEV; +} + +static inline void usb_bam_wait_for_cons_granted( + struct usb_bam_connect_ipa_params *ipa_params) +{ + return; +} + +static inline int usb_bam_register_wake_cb(enum usb_ctrl bam_type, u8 idx, + int (*callback)(void *), void *param) +{ + return -ENODEV; +} + +static inline int usb_bam_register_start_stop_cbs(enum usb_ctrl bam, u8 idx, + void (*start)(void *, enum usb_bam_pipe_dir), + void (*stop)(void *, enum usb_bam_pipe_dir), + void *param) +{ + return -ENODEV; +} + +static inline void usb_bam_suspend(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params){} + +static inline void usb_bam_resume(enum usb_ctrl bam_type, + struct usb_bam_connect_ipa_params *ipa_params) {} + +static inline int usb_bam_disconnect_pipe(enum usb_ctrl bam_type, u8 idx) +{ + return -ENODEV; +} + +static inline int get_bam2bam_connection_info(enum usb_ctrl bam_type, u8 idx, + u32 *usb_bam_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type) +{ + return -ENODEV; +} + +static inline int get_qdss_bam_connection_info( + unsigned long *usb_bam_handle, u32 *usb_bam_pipe_idx, + u32 *peer_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type) +{ + return -ENODEV; +} + +static inline int usb_bam_get_connection_idx(enum usb_ctrl bam_type, + enum peer_bam client, enum usb_bam_pipe_dir dir, + enum usb_bam_mode bam_mode, u32 num) +{ + return -ENODEV; +} + +static inline int usb_bam_get_bam_type(const char *core_nam) +{ + return -ENODEV; +} + +static inline int usb_bam_get_pipe_type(enum usb_ctrl bam_type, u8 idx, + enum usb_bam_pipe_type *type) +{ + return -ENODEV; +} + +static inline bool usb_bam_get_prod_granted(enum usb_ctrl bam_type, u8 idx) +{ + return false; +} + +int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + return false; +} + +int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + return false; +} + +#endif +#endif /* _USB_BAM_H_ */