| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/platform_device.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/msm-sps.h> |
| #include <linux/sched/clock.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 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, |
| "qti,reset-bam-on-connect"); |
| |
| usb_bam_data->reset_on_disconnect = of_property_read_bool(node, |
| "qti,reset-bam-on-disconnect"); |
| |
| rc = of_property_read_u32(node, "qti,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, "qti,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, "qti,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, "qti,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, |
| "qti,disable-clk-gating"); |
| |
| rc = of_property_read_u32(node, "qti,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, "qti,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, "qti,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, "qti,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, "qti,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, "qti,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, "qti,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, "qti,src-bam-pipe-index", |
| &usb_bam_connections[i].src_pipe_index); |
| |
| of_property_read_u32(node, "qti,dst-bam-pipe-index", |
| &usb_bam_connections[i].dst_pipe_index); |
| |
| of_property_read_u32(node, "qti,data-fifo-offset", |
| &usb_bam_connections[i].data_fifo_base_offset); |
| |
| rc = of_property_read_u32(node, "qti,data-fifo-size", |
| &usb_bam_connections[i].data_fifo_size); |
| if (rc) |
| goto err; |
| |
| of_property_read_u32(node, "qti,descriptor-fifo-offset", |
| &usb_bam_connections[i].desc_fifo_base_offset); |
| |
| rc = of_property_read_u32(node, "qti,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, |
| "qti,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 = "qti,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"); |