diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 582f88d01a7d..57e071f19245 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -124,7 +124,7 @@ config IPA_EMULATION shall be connected via PCIE to X86 machine. config USB_BAM - bool "USB BAM Driver" + tristate "USB BAM Driver" depends on SPS && USB_GADGET help Enabling this option adds USB BAM Driver. diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index 4d446de54966..9142c42092b2 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_GSI) += gsi/ obj-$(CONFIG_IPA3) += ipa/ obj-$(CONFIG_MSM_GENI_SE) += msm-geni-se.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..b2f004157375 --- /dev/null +++ b/drivers/platform/msm/usb_bam.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_THRESHOLD 512 +#define USB_BAM_MAX_STR_LEN 50 +#define DBG_MAX_MSG 512UL +#define DBG_MSG_LEN 160UL +#define TIME_BUF_LEN 17 +#define DBG_EVENT_LEN 143 + +#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); \ + 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__) + +enum usb_bam_event_type { + USB_BAM_EVENT_WAKEUP_PIPE = 0, /* Wake a pipe */ + USB_BAM_EVENT_WAKEUP, /* Wake a bam (first pipe waked) */ + USB_BAM_EVENT_INACTIVITY, /* Inactivity on all pipes */ +}; + +struct usb_bam_sps_type { + struct sps_pipe **sps_pipes; + struct sps_connect *sps_connections; +}; + +/* + * struct usb_bam_event_info: suspend/resume event information. + * @type: usb bam event type. + * @event: holds event data. + * @callback: suspend/resume callback. + * @param: port num (for suspend) or NULL (for resume). + * @event_w: holds work queue parameters. + */ +struct usb_bam_event_info { + enum usb_bam_event_type type; + struct sps_register_event event; + int (*callback)(void *ptr); + void *param; + struct work_struct event_w; +}; + +/* + * struct usb_bam_pipe_connect: pipe connection information + * between USB/HSIC BAM and another BAM. USB/HSIC BAM can be + * either src BAM or dst BAM + * @name: pipe description. + * @mem_type: type of memory used for BAM FIFOs + * @src_phy_addr: src bam physical address. + * @src_pipe_index: src bam pipe index. + * @dst_phy_addr: dst bam physical address. + * @dst_pipe_index: dst bam pipe index. + * @data_fifo_base_offset: data fifo offset. + * @data_fifo_size: data fifo size. + * @desc_fifo_base_offset: descriptor fifo offset. + * @desc_fifo_size: descriptor fifo size. + * @data_mem_buf: data fifo buffer. + * @desc_mem_buf: descriptor fifo buffer. + * @event: event for wakeup. + * @enabled: true if pipe is enabled. + * @suspended: true if pipe is suspended. + * @cons_stopped: true is pipe has consumer requests stopped. + * @prod_stopped: true if pipe has producer requests stopped. + * @priv: private data to return upon activity_notify + * or inactivity_notify callbacks. + * @activity_notify: callback to invoke on activity on one of the in pipes. + * @inactivity_notify: callback to invoke on inactivity on all pipes. + * @start: callback to invoke to enqueue transfers on a pipe. + * @stop: callback to invoke on dequeue transfers on a pipe. + * @start_stop_param: param for the start/stop callbacks. + */ +struct usb_bam_pipe_connect { + const char *name; + u32 pipe_num; + enum usb_pipe_mem_type mem_type; + enum usb_bam_pipe_dir dir; + enum usb_ctrl bam_type; + enum peer_bam peer_bam; + enum usb_bam_pipe_type pipe_type; + u32 src_phy_addr; + u32 src_pipe_index; + u32 dst_phy_addr; + u32 dst_pipe_index; + u32 data_fifo_base_offset; + u32 data_fifo_size; + u32 desc_fifo_base_offset; + u32 desc_fifo_size; + struct sps_mem_buffer data_mem_buf; + struct sps_mem_buffer desc_mem_buf; + struct usb_bam_event_info event; + bool enabled; + bool suspended; + bool cons_stopped; + bool prod_stopped; + void *priv; + int (*activity_notify)(void *priv); + int (*inactivity_notify)(void *priv); + void (*start)(void *ptr, enum usb_bam_pipe_dir); + void (*stop)(void *ptr, enum usb_bam_pipe_dir); + void *start_stop_param; + bool reset_pipe_after_lpm; +}; + +/** + * struct msm_usb_bam_data: pipe connection information + * between USB/HSIC BAM and another BAM. USB/HSIC BAM can be + * either src BAM or dst BAM + * @usb_bam_num_pipes: max number of pipes to use. + * @active_conn_num: number of active pipe connections. + * @usb_bam_fifo_baseaddr: base address for bam pipe's data and descriptor + * fifos. This can be on chip memory (ocimem) or usb + * private memory. + * @reset_on_connect: BAM must be reset before its first pipe connect + * @reset_on_disconnect: BAM must be reset after its last pipe disconnect + * @disable_clk_gating: Disable clock gating + * @override_threshold: Override the default threshold value for Read/Write + * event generation by the BAM towards another BAM. + * @max_mbps_highspeed: Maximum Mbits per seconds that the USB core + * can work at in bam2bam mode when connected to HS host. + * @max_mbps_superspeed: Maximum Mbits per seconds that the USB core + * can work at in bam2bam mode when connected to SS host. + */ +struct msm_usb_bam_data { + u8 max_connections; + int usb_bam_num_pipes; + phys_addr_t usb_bam_fifo_baseaddr; + bool reset_on_connect; + bool reset_on_disconnect; + bool disable_clk_gating; + u32 override_threshold; + u32 max_mbps_highspeed; + u32 max_mbps_superspeed; + enum usb_ctrl bam_type; +}; + +/* + * 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 platform 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: the handle/device of the sps driver. + * @pipes_enabled_per_bam: 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; + 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; + struct msm_usb_bam_data *usb_bam_data; + spinlock_t usb_bam_lock; +}; + +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 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 struct { + char buf[DBG_MAX_MSG][DBG_MSG_LEN]; /* buffer */ + unsigned int 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); +} + +static inline enum usb_ctrl get_bam_type_from_core_name(const char *name) +{ + return USB_CTRL_UNUSED; +} + +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_warn("%s: Bam has no connected pipes\n", __func__); + 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 sps_mem_buffer *data_buf = &(pipe_connect->data_mem_buf); + struct sps_mem_buffer *desc_buf = &(pipe_connect->desc_mem_buf); + struct device *dev = &ctx->usb_bam_pdev->dev; + struct sg_table data_sgt, desc_sgt; + dma_addr_t data_iova, desc_iova; + + pr_debug("%s: data_fifo size:%x desc_fifo_size:%x\n", + __func__, pipe_connect->data_fifo_size, + pipe_connect->desc_fifo_size); + + if (dev->parent) + dev = dev->parent; + + 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 + + ctx->usb_bam_data->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); + data_buf->iova = dma_map_resource(dev, data_buf->phys_base, + data_buf->size, DMA_BIDIRECTIONAL, 0); + if (dma_mapping_error(dev, data_buf->iova)) + log_event_err("%s(): oci_mem: err mapping data_buf\n", + __func__); + log_event_dbg("%s: data_buf:%s virt:%pK, phys:%lx, iova:%lx\n", + __func__, dev_name(dev), data_buf->base, + (unsigned long)data_buf->phys_base, data_buf->iova); + + desc_buf->phys_base = pipe_connect->desc_fifo_base_offset + + ctx->usb_bam_data->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); + desc_buf->iova = dma_map_resource(dev, desc_buf->phys_base, + desc_buf->size, + DMA_BIDIRECTIONAL, 0); + if (dma_mapping_error(dev, desc_buf->iova)) + log_event_err("%s(): oci_mem: err mapping desc_buf\n", + __func__); + + log_event_dbg("%s: desc_buf:%s virt:%pK, phys:%lx, iova:%lx\n", + __func__, dev_name(dev), desc_buf->base, + (unsigned long)desc_buf->phys_base, desc_buf->iova); + 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->base = dma_alloc_attrs(dev, + pipe_connect->data_fifo_size, + &data_iova, GFP_KERNEL, + DMA_ATTR_FORCE_CONTIGUOUS); + if (!data_buf->base) { + log_event_err("%s: data_fifo: dma_alloc_attr failed\n", + __func__); + ret = -ENOMEM; + goto err_exit; + } + memset(data_buf->base, 0, pipe_connect->data_fifo_size); + + data_buf->iova = data_iova; + dma_get_sgtable(dev, &data_sgt, data_buf->base, data_buf->iova, + pipe_connect->data_fifo_size); + data_buf->phys_base = page_to_phys(sg_page(data_sgt.sgl)); + sg_free_table(&data_sgt); + log_event_dbg("%s: data_buf:%s virt:%pK, phys:%lx, iova:%lx\n", + __func__, dev_name(dev), data_buf->base, + (unsigned long)data_buf->phys_base, data_buf->iova); + + desc_buf->size = pipe_connect->desc_fifo_size; + desc_buf->base = dma_alloc_attrs(dev, + pipe_connect->desc_fifo_size, + &desc_iova, GFP_KERNEL, + DMA_ATTR_FORCE_CONTIGUOUS); + if (!desc_buf->base) { + log_event_err("%s: desc_fifo: dma_alloc_attr failed\n", + __func__); + dma_free_attrs(dev, pipe_connect->data_fifo_size, + data_buf->base, data_buf->iova, + DMA_ATTR_FORCE_CONTIGUOUS); + ret = -ENOMEM; + goto err_exit; + } + memset(desc_buf->base, 0, pipe_connect->desc_fifo_size); + desc_buf->iova = desc_iova; + dma_get_sgtable(dev, &desc_sgt, desc_buf->base, desc_buf->iova, + desc_buf->size); + desc_buf->phys_base = page_to_phys(sg_page(desc_sgt.sgl)); + sg_free_table(&desc_sgt); + log_event_dbg("%s: desc_buf:%s virt:%pK, phys:%lx, iova:%lx\n", + __func__, dev_name(dev), desc_buf->base, + (unsigned long)desc_buf->phys_base, desc_buf->iova); + 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]; + struct device *dev = &ctx->usb_bam_pdev->dev; + u32 data_fifo_size; + + pr_debug("%s(): data size:%x desc size:%x\n", + __func__, sps_connection->data.size, + sps_connection->desc.size); + + if (dev->parent) + dev = dev->parent; + + switch (pipe_connect->mem_type) { + case SYSTEM_MEM: + log_event_dbg("%s: Freeing system memory used by PIPE\n", + __func__); + if (sps_connection->data.iova) { + data_fifo_size = sps_connection->data.size; + dma_free_attrs(dev, data_fifo_size, + sps_connection->data.base, + sps_connection->data.iova, + DMA_ATTR_FORCE_CONTIGUOUS); + sps_connection->data.iova = 0; + sps_connection->data.phys_base = 0; + pipe_connect->data_mem_buf.base = NULL; + } + if (sps_connection->desc.iova) { + dma_free_attrs(dev, sps_connection->desc.size, + sps_connection->desc.base, + sps_connection->desc.iova, + DMA_ATTR_FORCE_CONTIGUOUS); + sps_connection->desc.iova = 0; + 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) { + if (sps_connection->data.iova) { + dma_unmap_resource(dev, + sps_connection->data.iova, + sps_connection->data.size, + DMA_BIDIRECTIONAL, 0); + sps_connection->data.iova = 0; + } + iounmap(sps_connection->data.base); + sps_connection->data.base = NULL; + pipe_connect->data_mem_buf.base = NULL; + } + if (sps_connection->desc.base) { + if (sps_connection->desc.iova) { + dma_unmap_resource(dev, + sps_connection->desc.iova, + sps_connection->desc.size, + DMA_BIDIRECTIONAL, 0); + sps_connection->desc.iova = 0; + } + 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, + unsigned long iova) +{ + 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; + sps_connection->dest_iova = iova; + } else { + sps_connection->mode = SPS_MODE_DEST; + *usb_pipe_idx = pipe_connect->dst_pipe_index; + sps_connection->source_iova = iova; + } + + 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 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; +} + +int get_qdss_bam_info(enum usb_ctrl cur_bam, u8 idx, + phys_addr_t *p_addr, u32 *bam_size) +{ + int ret = 0; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[cur_bam]; + struct usb_bam_pipe_connect *pipe_connect = + &ctx->usb_bam_connections[idx]; + unsigned long peer_bam_handle; + + ret = sps_phy2h(pipe_connect->src_phy_addr, &peer_bam_handle); + if (ret) { + log_event_err("%s: sps_phy2h failed (src BAM) %d\n", + __func__, ret); + return ret; + } + + ret = sps_get_bam_addr(peer_bam_handle, p_addr, bam_size); + if (ret) { + log_event_err("%s: sps_get_bam_addr failed%d\n", + __func__, ret); + return ret; + } + + return 0; +} + +int usb_bam_connect(enum usb_ctrl cur_bam, int idx, u32 *bam_pipe_idx, + unsigned long iova) +{ + 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; + + if (pipe_connect->enabled) { + pr_warn("%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; + } + + 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 ((ctx->usb_bam_data->reset_on_connect) && + (ctx->pipes_enabled_per_bam == 0)) { + spin_unlock(&ctx->usb_bam_lock); + sps_device_reset(ctx->h_bam); + spin_lock(&ctx->usb_bam_lock); + } + spin_unlock(&ctx->usb_bam_lock); + + ret = connect_pipe(cur_bam, idx, bam_pipe_idx, iova); + 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 = true; + spin_lock(&ctx->usb_bam_lock); + ctx->pipes_enabled_per_bam += 1; + spin_unlock(&ctx->usb_bam_lock); + + return 0; +} + +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; + } + + *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__); + + /* 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); + + 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 + * overridden 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 %pK\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); + + 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) +{ + 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; + + 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 = false; + 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 (ctx->usb_bam_data->reset_on_disconnect + && !ctx->pipes_enabled_per_bam) + sps_device_reset(ctx->h_bam); + + /* This function is directly called by USB Transport drivers + * to disconnect pipes. Drop runtime usage count here. + */ + 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; +} + +static void usb_bam_sps_events(enum sps_callback_case sps_cb_case, void *user) +{ + int i; + struct usb_bam_ctx_type *ctx = user; + 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); + + ctx->is_bam_inactivity = true; + + 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_data *usb_bam_dt_to_data( + struct platform_device *pdev, u32 usb_addr) +{ + struct msm_usb_bam_data *usb_bam_data; + struct device_node *node = pdev->dev.of_node; + int rc = 0; + u8 i = 0; + u32 bam = USB_CTRL_UNUSED; + u32 addr = 0; + u32 threshold, max_connections = 0; + static struct usb_bam_pipe_connect *usb_bam_connections; + + usb_bam_data = devm_kzalloc(&pdev->dev, sizeof(*usb_bam_data), + GFP_KERNEL); + if (!usb_bam_data) + return NULL; + + usb_bam_data->bam_type = bam; + + usb_bam_data->reset_on_connect = of_property_read_bool(node, + "qcom,reset-bam-on-connect"); + + usb_bam_data->reset_on_disconnect = of_property_read_bool(node, + "qcom,reset-bam-on-disconnect"); + + rc = of_property_read_u32(node, "qcom,usb-bam-num-pipes", + &usb_bam_data->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", + &usb_bam_data->max_mbps_highspeed); + if (rc) + usb_bam_data->max_mbps_highspeed = 0; + + rc = of_property_read_u32(node, "qcom,usb-bam-max-mbps-superspeed", + &usb_bam_data->max_mbps_superspeed); + if (rc) + usb_bam_data->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 + usb_bam_data->usb_bam_fifo_baseaddr = addr; + + usb_bam_data->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) + usb_bam_data->override_threshold = USB_THRESHOLD; + else + usb_bam_data->override_threshold = threshold; + + 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; + + 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 (!usb_bam_data->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 usb_bam_data; +err: + log_event_err("%s: failed\n", __func__); + return NULL; +} + +static int usb_bam_init(struct platform_device *pdev) +{ + int ret; + struct usb_bam_ctx_type *ctx = dev_get_drvdata(&pdev->dev); + enum usb_ctrl bam_type = ctx->usb_bam_data->bam_type; + struct sps_bam_props props; + struct device *dev; + + memset(&props, 0, sizeof(props)); + + pr_debug("%s\n", __func__); + + props.phys_addr = ctx->io_res->start; + props.virt_size = resource_size(ctx->io_res); + props.irq = ctx->irq; + props.summing_threshold = ctx->usb_bam_data->override_threshold; + props.event_threshold = ctx->usb_bam_data->override_threshold; + props.num_pipes = ctx->usb_bam_data->usb_bam_num_pipes; + props.callback = usb_bam_sps_events; + props.user = &msm_usb_bam[bam_type]; + + if (ctx->usb_bam_data->disable_clk_gating) + props.options |= SPS_BAM_NO_LOCAL_CLK_GATING; + + dev = &ctx->usb_bam_pdev->dev; + if (dev && dev->parent && device_property_present(dev->parent, "iommus") + && !device_property_present(dev->parent, + "qcom,smmu-s1-bypass")) { + pr_info("%s: setting SPS_BAM_SMMU_EN flag with (%s)\n", + __func__, dev_name(dev)); + props.options |= SPS_BAM_SMMU_EN; + } + + 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 usb_bam_ctx_type *ctx = dev_get_drvdata(&pdev->dev); + + ret = usb_bam_init(pdev); + if (ret) + 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 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) + 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 void usb_bam_unregister_panic_hdlr(void) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + &usb_bam_panic_blk); +} + +static int usb_bam_probe(struct platform_device *pdev) +{ + int ret, i, irq; + struct resource *io_res; + enum usb_ctrl bam_type; + struct usb_bam_ctx_type *ctx; + struct msm_usb_bam_data *usb_bam_data; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + 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; + } + + /* specify BAM physical address to be filled in BAM connections */ + usb_bam_data = usb_bam_dt_to_data(pdev, io_res->start); + if (!usb_bam_data) + return -EINVAL; + + bam_type = usb_bam_data->bam_type; + ctx = &msm_usb_bam[bam_type]; + dev_set_drvdata(&pdev->dev, ctx); + + ctx->usb_bam_pdev = pdev; + ctx->irq = irq; + ctx->io_res = io_res; + ctx->usb_bam_data = usb_bam_data; + + for (i = 0; i < ctx->max_connections; i++) { + ctx->usb_bam_connections[i].enabled = false; + INIT_WORK(&ctx->usb_bam_connections[i].event.event_w, + usb_bam_work); + } + + 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(&ctx->usb_bam_lock); + + usb_bam_register_panic_hdlr(); + return ret; +} + +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, 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, 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].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); + +enum usb_ctrl usb_bam_get_bam_type(const char *core_name) +{ + enum usb_ctrl 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); + +static int usb_bam_remove(struct platform_device *pdev) +{ + struct usb_bam_ctx_type *ctx = dev_get_drvdata(&pdev->dev); + + usb_bam_unregister_panic_hdlr(); + sps_deregister_bam_device(ctx->h_bam); + destroy_workqueue(ctx->usb_bam_wq); + + return 0; +} + +static const struct of_device_id usb_bam_dt_match[] = { + { .compatible = "qcom,usb-bam-msm", + }, + {} +}; +MODULE_DEVICE_TABLE(of, usb_bam_dt_match); + +static struct platform_driver usb_bam_driver = { + .probe = usb_bam_probe, + .remove = usb_bam_remove, + .driver = { + .name = "usb_bam", + .of_match_table = usb_bam_dt_match, + }, +}; + +static int __init init(void) +{ + return platform_driver_register(&usb_bam_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + platform_driver_unregister(&usb_bam_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION("MSM USB BAM DRIVER"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb_bam.h b/include/linux/usb_bam.h new file mode 100644 index 000000000000..2f8681a5273c --- /dev/null +++ b/include/linux/usb_bam.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011-2017, 2019 The Linux Foundation. All rights reserved. + */ + +#ifndef _USB_BAM_H_ +#define _USB_BAM_H_ + +#include +#include +#include +#include + +#define MAX_BAMS NUM_CTRL /* Bam per USB controllers */ + +/* Supported USB controllers*/ +enum usb_ctrl { + USB_CTRL_UNUSED = 0, + NUM_CTRL, +}; + +enum peer_bam { + QDSS_P_BAM = 0, + MAX_PEER_BAMS, +}; + +enum usb_bam_pipe_dir { + USB_TO_PEER_PERIPHERAL, + PEER_PERIPHERAL_TO_USB, +}; + +enum usb_pipe_mem_type { + SPS_PIPE_MEM = 0, /* Default, SPS dedicated pipe memory */ + SYSTEM_MEM, /* System RAM, requires allocation */ + OCI_MEM, /* Shared memory among peripherals */ +}; + +enum usb_bam_pipe_type { + USB_BAM_PIPE_BAM2BAM = 0, /* Connection is BAM2BAM (default) */ + USB_BAM_PIPE_SYS2BAM, /* Connection is SYS2BAM or BAM2SYS + * depending on usb_bam_pipe_dir + */ + USB_BAM_MAX_PIPE_TYPES, +}; + +#if IS_ENABLED(CONFIG_USB_BAM) +/** + * Connect USB-to-Peripheral SPS connection. + * + * This function returns the allocated pipe number. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @bam_pipe_idx - allocated pipe index. + * + * @iova - IPA address of USB peer BAM (i.e. QDSS BAM) + * + * @return 0 on success, negative value on error + * + */ +int usb_bam_connect(enum usb_ctrl bam_type, int idx, u32 *bam_pipe_idx, + unsigned long iova); + +/** + * Register a wakeup callback from peer BAM. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @callback - the callback function + * + * @return 0 on success, negative value on error + */ +int usb_bam_register_wake_cb(enum usb_ctrl bam_type, u8 idx, + int (*callback)(void *), void *param); + +/** + * Register callbacks for start/stop of transfers. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index + * + * @start - the callback function that will be called in USB + * driver to start transfers + * @stop - the callback function that will be called in USB + * driver to stop transfers + * + * @param - context that the caller can supply + * + * @return 0 on success, negative value on error + */ +int usb_bam_register_start_stop_cbs(enum usb_ctrl bam_type, + u8 idx, + void (*start)(void *, enum usb_bam_pipe_dir), + void (*stop)(void *, enum usb_bam_pipe_dir), + void *param); + +/** + * Disconnect USB-to-Periperal SPS connection. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @return 0 on success, negative value on error + */ +int usb_bam_disconnect_pipe(enum usb_ctrl bam_type, u8 idx); + +/** + * Returns usb bam connection parameters. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Connection index. + * + * @usb_bam_pipe_idx - Usb bam pipe index. + * + * @desc_fifo - Descriptor fifo parameters. + * + * @data_fifo - Data fifo parameters. + * + * @return pipe index on success, negative value on error. + */ +int get_bam2bam_connection_info(enum usb_ctrl bam_type, u8 idx, + u32 *usb_bam_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type); + +/** + * Returns usb bam connection parameters for qdss pipe. + * @usb_bam_handle - Usb bam handle. + * @usb_bam_pipe_idx - Usb bam pipe index. + * @peer_pipe_idx - Peer pipe index. + * @desc_fifo - Descriptor fifo parameters. + * @data_fifo - Data fifo parameters. + * @return pipe index on success, negative value on error. + */ +int get_qdss_bam_connection_info( + unsigned long *usb_bam_handle, u32 *usb_bam_pipe_idx, + u32 *peer_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type); + +/* + * Indicates if the client of the USB BAM is ready to start + * sending/receiving transfers. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @client - Usb pipe peer (a2, ipa, qdss...) + * + * @dir - In (from peer to usb) or out (from usb to peer) + * + * @num - Pipe number. + * + * @return 0 on success, negative value on error + */ +int usb_bam_get_connection_idx(enum usb_ctrl bam_type, enum peer_bam client, + enum usb_bam_pipe_dir dir, u32 num); + +/* + * return the usb controller bam type used for the supplied connection index + * + * @core_name - Core name (ssusb/hsusb/hsic). + * + * @return usb control bam type + */ +enum usb_ctrl usb_bam_get_bam_type(const char *core_name); + +/* + * Indicates the type of connection the USB side of the connection is. + * + * @bam_type - USB BAM type - dwc3/CI/hsic + * + * @idx - Pipe number. + * + * @type - Type of connection + * + * @return 0 on success, negative value on error + */ +int usb_bam_get_pipe_type(enum usb_ctrl bam_type, + u8 idx, enum usb_bam_pipe_type *type); + +/* Allocates memory for data fifo and descriptor fifos. */ +int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx); + +/* Frees memory for data fifo and descriptor fifos. */ +int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx); +int get_qdss_bam_info(enum usb_ctrl cur_bam, u8 idx, + phys_addr_t *p_addr, u32 *bam_size); +#else +static inline int usb_bam_connect(enum usb_ctrl bam, u8 idx, u32 *bam_pipe_idx, + unsigned long iova) +{ + return -ENODEV; +} + +static inline int usb_bam_register_wake_cb(enum usb_ctrl bam_type, u8 idx, + int (*callback)(void *), void *param) +{ + return -ENODEV; +} + +static inline int usb_bam_register_start_stop_cbs(enum usb_ctrl bam, u8 idx, + void (*start)(void *, enum usb_bam_pipe_dir), + void (*stop)(void *, enum usb_bam_pipe_dir), + void *param) +{ + return -ENODEV; +} + +static inline int usb_bam_disconnect_pipe(enum usb_ctrl bam_type, u8 idx) +{ + return -ENODEV; +} + +static inline int get_bam2bam_connection_info(enum usb_ctrl bam_type, u8 idx, + u32 *usb_bam_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type) +{ + return -ENODEV; +} + +static inline int get_qdss_bam_connection_info( + unsigned long *usb_bam_handle, u32 *usb_bam_pipe_idx, + u32 *peer_pipe_idx, struct sps_mem_buffer *desc_fifo, + struct sps_mem_buffer *data_fifo, enum usb_pipe_mem_type *mem_type) +{ + return -ENODEV; +} + +static inline int usb_bam_get_connection_idx(enum usb_ctrl bam_type, + enum peer_bam client, enum usb_bam_pipe_dir dir, u32 num) +{ + return -ENODEV; +} + +static inline enum usb_ctrl usb_bam_get_bam_type(const char *core_nam) +{ + return -ENODEV; +} + +static inline int usb_bam_get_pipe_type(enum usb_ctrl bam_type, u8 idx, + enum usb_bam_pipe_type *type) +{ + return -ENODEV; +} + +static inline int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + return false; +} + +static inline int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx) +{ + return false; +} + +static inline int get_qdss_bam_info(enum usb_ctrl cur_bam, u8 idx, + phys_addr_t *p_addr, u32 *bam_size) +{ + return false; +} +#endif + +/* CONFIG_PM */ +#ifdef CONFIG_PM +static inline int get_pm_runtime_counter(struct device *dev) +{ + return atomic_read(&dev->power.usage_count); +} +#else +/* !CONFIG_PM */ +static inline int get_pm_runtime_counter(struct device *dev) +{ return -EOPNOTSUPP; } +#endif +#endif /* _USB_BAM_H_ */