summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
authorMayank Rana <mrana@codeaurora.org>2016-03-11 15:39:40 -0800
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:23:31 -0700
commitf6e4c0aed16f79ec3c8a716fe8d04af1f138e6ad (patch)
treec012152551ad76eef47a1a8460c68cd863635234 /drivers/platform
parent8e51c8ce31f462c5c28bff61c8dfe3ea169c0eac (diff)
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 <mrana@codeaurora.org>
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/msm/Kconfig8
-rw-r--r--drivers/platform/msm/Makefile1
-rw-r--r--drivers/platform/msm/usb_bam.c3528
3 files changed, 3537 insertions, 0 deletions
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 <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/stat.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/usb/msm_hsusb.h>
+#include <linux/msm-sps.h>
+#include <linux/ipa.h>
+#include <linux/usb_bam.h>
+#include <linux/workqueue.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+
+#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: <bam_name> <ms>,<bam_name> <ms>,...\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");