/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include "ipa_i.h" /* * EP0 (teth) * A2_BAM(1)->(12)DMA_BAM->DMA_BAM(13)->(6)IPA_BAM->IPA_BAM(10)->USB_BAM(0) * A2_BAM(0)<-(15)DMA_BAM<-DMA_BAM(14)<-(7)IPA_BAM<-IPA_BAM(11)<-USB_BAM(1) * * EP2 (emb) * A2_BAM(5)->(16)DMA_BAM->DMA_BAM(17)->(8)IPA_BAM-> * A2_BAM(4)<-(19)DMA_BAM<-DMA_BAM(18)<-(9)IPA_BAM<- */ #define A2_TETHERED_PIPE_UL 0 #define DMA_A2_TETHERED_PIPE_UL 15 #define DMA_IPA_TETHERED_PIPE_UL 14 #define A2_TETHERED_PIPE_DL 1 #define DMA_A2_TETHERED_PIPE_DL 12 #define DMA_IPA_TETHERED_PIPE_DL 13 #define A2_EMBEDDED_PIPE_UL 4 #define DMA_A2_EMBEDDED_PIPE_UL 19 #define DMA_IPA_EMBEDDED_PIPE_UL 18 #define A2_EMBEDDED_PIPE_DL 5 #define DMA_A2_EMBEDDED_PIPE_DL 16 #define DMA_IPA_EMBEDDED_PIPE_DL 17 #define IPA_SMEM_PIPE_MEM_SZ 32768 #define IPA_UL_DATA_FIFO_SZ 0xc00 #define IPA_UL_DESC_FIFO_SZ 0x530 #define IPA_DL_DATA_FIFO_SZ 0x2400 #define IPA_DL_DESC_FIFO_SZ 0x8a0 #define IPA_DL_EMB_DATA_FIFO_SZ 0x1800 #define IPA_DL_EMB_DESC_FIFO_SZ 0x4e8 #define IPA_SMEM_UL_DATA_FIFO_OFST 0 #define IPA_SMEM_UL_DESC_FIFO_OFST 0xc00 #define IPA_SMEM_DL_DATA_FIFO_OFST 0x1130 #define IPA_SMEM_DL_DESC_FIFO_OFST 0x3530 #define IPA_SMEM_UL_EMB_DATA_FIFO_OFST 0x3dd0 #define IPA_SMEM_UL_EMB_DESC_FIFO_OFST 0x49d0 #define IPA_OCIMEM_DL_A2_DATA_FIFO_OFST 0 #define IPA_OCIMEM_DL_A2_DESC_FIFO_OFST (IPA_OCIMEM_DL_A2_DATA_FIFO_OFST + \ IPA_DL_EMB_DATA_FIFO_SZ) #define IPA_OCIMEM_DL_IPA_DATA_FIFO_OFST (IPA_OCIMEM_DL_A2_DESC_FIFO_OFST + \ IPA_DL_EMB_DESC_FIFO_SZ) #define IPA_OCIMEM_DL_IPA_DESC_FIFO_OFST (IPA_OCIMEM_DL_IPA_DATA_FIFO_OFST + \ IPA_DL_EMB_DATA_FIFO_SZ) enum ipa_pipe_type { IPA_DL_FROM_A2, IPA_DL_TO_IPA, IPA_UL_FROM_IPA, IPA_UL_TO_A2, IPA_PIPE_TYPE_MAX }; struct ipa_bridge_pipe_context { struct sps_pipe *pipe; bool ipa_facing; bool valid; }; struct ipa_bridge_context { struct ipa_bridge_pipe_context pipe[IPA_PIPE_TYPE_MAX]; enum ipa_bridge_type type; }; static struct ipa_bridge_context bridge[IPA_BRIDGE_TYPE_MAX]; static void ipa_get_dma_pipe_num(enum ipa_bridge_dir dir, enum ipa_bridge_type type, int *a2, int *ipa) { if (type == IPA_BRIDGE_TYPE_TETHERED) { if (dir == IPA_BRIDGE_DIR_UL) { *a2 = DMA_A2_TETHERED_PIPE_UL; *ipa = DMA_IPA_TETHERED_PIPE_UL; } else { *a2 = DMA_A2_TETHERED_PIPE_DL; *ipa = DMA_IPA_TETHERED_PIPE_DL; } } else { if (dir == IPA_BRIDGE_DIR_UL) { *a2 = DMA_A2_EMBEDDED_PIPE_UL; *ipa = DMA_IPA_EMBEDDED_PIPE_UL; } else { *a2 = DMA_A2_EMBEDDED_PIPE_DL; *ipa = DMA_IPA_EMBEDDED_PIPE_DL; } } } static int ipa_get_desc_fifo_sz(enum ipa_bridge_dir dir, enum ipa_bridge_type type) { int sz; if (type == IPA_BRIDGE_TYPE_TETHERED) { if (dir == IPA_BRIDGE_DIR_UL) sz = IPA_UL_DESC_FIFO_SZ; else sz = IPA_DL_DESC_FIFO_SZ; } else { if (dir == IPA_BRIDGE_DIR_UL) sz = IPA_UL_DESC_FIFO_SZ; else sz = IPA_DL_EMB_DESC_FIFO_SZ; } return sz; } static int ipa_get_data_fifo_sz(enum ipa_bridge_dir dir, enum ipa_bridge_type type) { int sz; if (type == IPA_BRIDGE_TYPE_TETHERED) { if (dir == IPA_BRIDGE_DIR_UL) sz = IPA_UL_DATA_FIFO_SZ; else sz = IPA_DL_DATA_FIFO_SZ; } else { if (dir == IPA_BRIDGE_DIR_UL) sz = IPA_UL_DATA_FIFO_SZ; else sz = IPA_DL_EMB_DATA_FIFO_SZ; } return sz; } static int ipa_get_a2_pipe_num(enum ipa_bridge_dir dir, enum ipa_bridge_type type) { int ep; if (type == IPA_BRIDGE_TYPE_TETHERED) { if (dir == IPA_BRIDGE_DIR_UL) ep = A2_TETHERED_PIPE_UL; else ep = A2_TETHERED_PIPE_DL; } else { if (dir == IPA_BRIDGE_DIR_UL) ep = A2_EMBEDDED_PIPE_UL; else ep = A2_EMBEDDED_PIPE_DL; } return ep; } int ipa_setup_ipa_dma_fifos(enum ipa_bridge_dir dir, enum ipa_bridge_type type, struct sps_mem_buffer *desc, struct sps_mem_buffer *data) { int ret; ret = sps_setup_bam2bam_fifo(data, IPA_OCIMEM_DL_IPA_DATA_FIFO_OFST, ipa_get_data_fifo_sz(dir, type), 1); if (ret) { IPAERR("DAFIFO setup fail %d dir %d type %d\n", ret, dir, type); return ret; } ret = sps_setup_bam2bam_fifo(desc, IPA_OCIMEM_DL_IPA_DESC_FIFO_OFST, ipa_get_desc_fifo_sz(dir, type), 1); if (ret) { IPAERR("DEFIFO setup fail %d dir %d type %d\n", ret, dir, type); return ret; } IPADBG("dir=%d type=%d Dpa=%x Dsz=%u Dva=%p dpa=%x dsz=%u dva=%p\n", dir, type, data->phys_base, data->size, data->base, desc->phys_base, desc->size, desc->base); return 0; } int ipa_setup_a2_dma_fifos(enum ipa_bridge_dir dir, enum ipa_bridge_type type, struct sps_mem_buffer *desc, struct sps_mem_buffer *data) { int ret; if (type == IPA_BRIDGE_TYPE_TETHERED) { if (dir == IPA_BRIDGE_DIR_UL) { desc->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_UL_DESC_FIFO_OFST; desc->phys_base = smem_virt_to_phys(desc->base); desc->size = ipa_get_desc_fifo_sz(dir, type); data->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_UL_DATA_FIFO_OFST; data->phys_base = smem_virt_to_phys(data->base); data->size = ipa_get_data_fifo_sz(dir, type); } else { desc->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_DL_DESC_FIFO_OFST; desc->phys_base = smem_virt_to_phys(desc->base); desc->size = ipa_get_desc_fifo_sz(dir, type); data->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_DL_DATA_FIFO_OFST; data->phys_base = smem_virt_to_phys(data->base); data->size = ipa_get_data_fifo_sz(dir, type); } } else { if (dir == IPA_BRIDGE_DIR_UL) { desc->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_UL_EMB_DESC_FIFO_OFST; desc->phys_base = smem_virt_to_phys(desc->base); desc->size = ipa_get_desc_fifo_sz(dir, type); data->base = ipa_ctx->smem_pipe_mem + IPA_SMEM_UL_EMB_DATA_FIFO_OFST; data->phys_base = smem_virt_to_phys(data->base); data->size = ipa_get_data_fifo_sz(dir, type); } else { ret = sps_setup_bam2bam_fifo(data, IPA_OCIMEM_DL_A2_DATA_FIFO_OFST, ipa_get_data_fifo_sz(dir, type), 1); if (ret) { IPAERR("DAFIFO setup fail %d dir %d type %d\n", ret, dir, type); return ret; } ret = sps_setup_bam2bam_fifo(desc, IPA_OCIMEM_DL_A2_DESC_FIFO_OFST, ipa_get_desc_fifo_sz(dir, type), 1); if (ret) { IPAERR("DEFIFO setup fail %d dir %d type %d\n", ret, dir, type); return ret; } } } IPADBG("dir=%d type=%d Dpa=%x Dsz=%u Dva=%p dpa=%x dsz=%u dva=%p\n", dir, type, data->phys_base, data->size, data->base, desc->phys_base, desc->size, desc->base); return 0; } static int setup_dma_bam_bridge(enum ipa_bridge_dir dir, enum ipa_bridge_type type, struct ipa_sys_connect_params *props, u32 *clnt_hdl) { struct ipa_connect_params ipa_in_params; struct ipa_sps_params sps_out_params; int dma_a2_pipe; int dma_ipa_pipe; struct sps_pipe *pipe; struct sps_pipe *pipe_a2; struct sps_connect _connection; struct sps_connect *connection = &_connection; struct a2_mux_pipe_connection pipe_conn = {0}; enum a2_mux_pipe_direction pipe_dir; u32 dma_hdl = sps_dma_get_bam_handle(); u32 a2_hdl; u32 pa; int ret; memset(&ipa_in_params, 0, sizeof(ipa_in_params)); memset(&sps_out_params, 0, sizeof(sps_out_params)); pipe_dir = (dir == IPA_BRIDGE_DIR_UL) ? IPA_TO_A2 : A2_TO_IPA; ret = ipa_get_a2_mux_pipe_info(pipe_dir, &pipe_conn); if (ret) { IPAERR("ipa_get_a2_mux_pipe_info failed dir=%d type=%d\n", dir, type); goto fail_get_a2_prop; } pa = (dir == IPA_BRIDGE_DIR_UL) ? pipe_conn.dst_phy_addr : pipe_conn.src_phy_addr; ret = sps_phy2h(pa, &a2_hdl); if (ret) { IPAERR("sps_phy2h failed (A2 BAM) %d dir=%d type=%d\n", ret, dir, type); goto fail_get_a2_prop; } ipa_get_dma_pipe_num(dir, type, &dma_a2_pipe, &dma_ipa_pipe); ipa_in_params.ipa_ep_cfg = props->ipa_ep_cfg; ipa_in_params.client = props->client; ipa_in_params.client_bam_hdl = dma_hdl; ipa_in_params.client_ep_idx = dma_ipa_pipe; ipa_in_params.priv = props->priv; ipa_in_params.notify = props->notify; ipa_in_params.desc_fifo_sz = ipa_get_desc_fifo_sz(dir, type); ipa_in_params.data_fifo_sz = ipa_get_data_fifo_sz(dir, type); if (type == IPA_BRIDGE_TYPE_EMBEDDED && dir == IPA_BRIDGE_DIR_DL) { if (ipa_setup_ipa_dma_fifos(dir, type, &ipa_in_params.desc, &ipa_in_params.data)) { IPAERR("fail to setup IPA-DMA FIFOs dir=%d type=%d\n", dir, type); goto fail_get_a2_prop; } } if (ipa_connect(&ipa_in_params, &sps_out_params, clnt_hdl)) { IPAERR("ipa connect failed dir=%d type=%d\n", dir, type); goto fail_get_a2_prop; } pipe = sps_alloc_endpoint(); if (pipe == NULL) { IPAERR("sps_alloc_endpoint failed dir=%d type=%d\n", dir, type); ret = -ENOMEM; goto fail_sps_alloc; } memset(&_connection, 0, sizeof(_connection)); ret = sps_get_config(pipe, connection); if (ret) { IPAERR("sps_get_config failed %d dir=%d type=%d\n", ret, dir, type); goto fail_sps_get_config; } if (dir == IPA_BRIDGE_DIR_DL) { connection->mode = SPS_MODE_SRC; connection->source = dma_hdl; connection->destination = sps_out_params.ipa_bam_hdl; connection->src_pipe_index = dma_ipa_pipe; connection->dest_pipe_index = sps_out_params.ipa_ep_idx; } else { connection->mode = SPS_MODE_DEST; connection->source = sps_out_params.ipa_bam_hdl; connection->destination = dma_hdl; connection->src_pipe_index = sps_out_params.ipa_ep_idx; connection->dest_pipe_index = dma_ipa_pipe; } connection->event_thresh = IPA_EVENT_THRESHOLD; connection->data = sps_out_params.data; connection->desc = sps_out_params.desc; connection->options = SPS_O_AUTO_ENABLE; ret = sps_connect(pipe, connection); if (ret) { IPAERR("sps_connect failed %d dir=%d type=%d\n", ret, dir, type); goto fail_sps_get_config; } if (dir == IPA_BRIDGE_DIR_DL) { bridge[type].pipe[IPA_DL_TO_IPA].pipe = pipe; bridge[type].pipe[IPA_DL_TO_IPA].ipa_facing = true; bridge[type].pipe[IPA_DL_TO_IPA].valid = true; } else { bridge[type].pipe[IPA_UL_FROM_IPA].pipe = pipe; bridge[type].pipe[IPA_UL_FROM_IPA].ipa_facing = true; bridge[type].pipe[IPA_UL_FROM_IPA].valid = true; } IPADBG("dir=%d type=%d (ipa) src(0x%x:%u)->dst(0x%x:%u)\n", dir, type, connection->source, connection->src_pipe_index, connection->destination, connection->dest_pipe_index); pipe_a2 = sps_alloc_endpoint(); if (pipe_a2 == NULL) { IPAERR("sps_alloc_endpoint failed2 dir=%d type=%d\n", dir, type); ret = -ENOMEM; goto fail_sps_alloc_a2; } memset(&_connection, 0, sizeof(_connection)); ret = sps_get_config(pipe_a2, connection); if (ret) { IPAERR("sps_get_config failed2 %d dir=%d type=%d\n", ret, dir, type); goto fail_sps_get_config_a2; } if (dir == IPA_BRIDGE_DIR_DL) { connection->mode = SPS_MODE_DEST; connection->source = a2_hdl; connection->destination = dma_hdl; connection->src_pipe_index = ipa_get_a2_pipe_num(dir, type); connection->dest_pipe_index = dma_a2_pipe; } else { connection->mode = SPS_MODE_SRC; connection->source = dma_hdl; connection->destination = a2_hdl; connection->src_pipe_index = dma_a2_pipe; connection->dest_pipe_index = ipa_get_a2_pipe_num(dir, type); } connection->event_thresh = IPA_EVENT_THRESHOLD; if (ipa_setup_a2_dma_fifos(dir, type, &connection->desc, &connection->data)) { IPAERR("fail to setup A2-DMA FIFOs dir=%d type=%d\n", dir, type); goto fail_sps_get_config_a2; } connection->options = SPS_O_AUTO_ENABLE; ret = sps_connect(pipe_a2, connection); if (ret) { IPAERR("sps_connect failed2 %d dir=%d type=%d\n", ret, dir, type); goto fail_sps_get_config_a2; } if (dir == IPA_BRIDGE_DIR_DL) { bridge[type].pipe[IPA_DL_FROM_A2].pipe = pipe_a2; bridge[type].pipe[IPA_DL_FROM_A2].valid = true; } else { bridge[type].pipe[IPA_UL_TO_A2].pipe = pipe_a2; bridge[type].pipe[IPA_UL_TO_A2].valid = true; } IPADBG("dir=%d type=%d (a2) src(0x%x:%u)->dst(0x%x:%u)\n", dir, type, connection->source, connection->src_pipe_index, connection->destination, connection->dest_pipe_index); return 0; fail_sps_get_config_a2: sps_free_endpoint(pipe_a2); fail_sps_alloc_a2: sps_disconnect(pipe); fail_sps_get_config: sps_free_endpoint(pipe); fail_sps_alloc: ipa_disconnect(*clnt_hdl); fail_get_a2_prop: return ret; } /** * ipa_bridge_init() * * Return codes: 0: success, -ENOMEM: failure */ int ipa_bridge_init(void) { int i; ipa_ctx->smem_pipe_mem = smem_alloc2(SMEM_BAM_PIPE_MEMORY, IPA_SMEM_PIPE_MEM_SZ); if (!ipa_ctx->smem_pipe_mem) { IPAERR("smem alloc failed\n"); return -ENOMEM; } IPADBG("smem_pipe_mem = %p\n", ipa_ctx->smem_pipe_mem); for (i = 0; i < IPA_BRIDGE_TYPE_MAX; i++) bridge[i].type = i; return 0; } /** * ipa_bridge_setup() - setup SW bridge leg * @dir: downlink or uplink (from air interface perspective) * @type: tethered or embedded bridge * @props: bridge leg properties (EP config, callbacks, etc) * @clnt_hdl: [out] handle of IPA EP belonging to bridge leg * * NOTE: IT IS CALLER'S RESPONSIBILITY TO ENSURE BAMs ARE * OPERATIONAL AS LONG AS BRIDGE REMAINS UP * * Return codes: * 0: success * various negative error codes on errors */ int ipa_bridge_setup(enum ipa_bridge_dir dir, enum ipa_bridge_type type, struct ipa_sys_connect_params *props, u32 *clnt_hdl) { int ret; if (props == NULL || clnt_hdl == NULL || type >= IPA_BRIDGE_TYPE_MAX || dir >= IPA_BRIDGE_DIR_MAX || props->client >= IPA_CLIENT_MAX) { IPAERR("Bad param props=%p clnt_hdl=%p type=%d dir=%d\n", props, clnt_hdl, type, dir); return -EINVAL; } ipa_inc_client_enable_clks(); if (setup_dma_bam_bridge(dir, type, props, clnt_hdl)) { IPAERR("fail to setup SYS pipe to IPA dir=%d type=%d\n", dir, type); ret = -EINVAL; goto bail_ipa; } return 0; bail_ipa: ipa_dec_client_disable_clks(); return ret; } EXPORT_SYMBOL(ipa_bridge_setup); /** * ipa_bridge_teardown() - teardown SW bridge leg * @dir: downlink or uplink (from air interface perspective) * @type: tethered or embedded bridge * @clnt_hdl: handle of IPA EP * * Return codes: * 0: success * various negative error codes on errors */ int ipa_bridge_teardown(enum ipa_bridge_dir dir, enum ipa_bridge_type type, u32 clnt_hdl) { struct ipa_bridge_pipe_context *sys; int lo; int hi; if (dir >= IPA_BRIDGE_DIR_MAX || type >= IPA_BRIDGE_TYPE_MAX || clnt_hdl >= IPA_NUM_PIPES || ipa_ctx->ep[clnt_hdl].valid == 0) { IPAERR("Bad param dir=%d type=%d\n", dir, type); return -EINVAL; } if (dir == IPA_BRIDGE_DIR_UL) { lo = IPA_UL_FROM_IPA; hi = IPA_UL_TO_A2; } else { lo = IPA_DL_FROM_A2; hi = IPA_DL_TO_IPA; } for (; lo <= hi; lo++) { sys = &bridge[type].pipe[lo]; if (sys->valid) { if (sys->ipa_facing) ipa_disconnect(clnt_hdl); sps_disconnect(sys->pipe); sps_free_endpoint(sys->pipe); sys->valid = false; } } memset(&ipa_ctx->ep[clnt_hdl], 0, sizeof(struct ipa_ep_context)); ipa_dec_client_disable_clks(); return 0; } EXPORT_SYMBOL(ipa_bridge_teardown); bool ipa_emb_ul_pipes_empty(void) { struct sps_pipe *emb_ipa_ul = ipa_ctx->sys[IPA_A5_LAN_WAN_OUT].ep->ep_hdl; struct sps_pipe *emb_ipa_to_dma = bridge[IPA_BRIDGE_TYPE_EMBEDDED].pipe[IPA_UL_FROM_IPA].pipe; struct sps_pipe *emb_dma_to_a2 = bridge[IPA_BRIDGE_TYPE_EMBEDDED].pipe[IPA_UL_TO_A2].pipe; u32 emb_ipa_ul_empty; u32 emb_ipa_to_dma_empty; u32 emb_dma_to_a2_empty; if (sps_is_pipe_empty(emb_ipa_ul, &emb_ipa_ul_empty)) { IPAERR("emb_ip_ul pipe empty check fail\n"); return false; } if (sps_is_pipe_empty(emb_ipa_to_dma, &emb_ipa_to_dma_empty)) { IPAERR("emb_ipa_to_dma pipe empty check fail\n"); return false; } if (sps_is_pipe_empty(emb_dma_to_a2, &emb_dma_to_a2_empty)) { IPAERR("emb_dma_to_a2 pipe empty check fail\n"); return false; } return emb_ipa_ul_empty && emb_ipa_to_dma_empty && emb_dma_to_a2_empty; } EXPORT_SYMBOL(ipa_emb_ul_pipes_empty);