// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "../ipa_common_i.h" #include "ipa_i.h" #define IPA_MPM_DRV_NAME "ipa_mpm" #define IPA_MPM_DBG(fmt, args...) \ do { \ pr_debug(IPA_MPM_DRV_NAME " %s:%d " fmt, \ __func__, __LINE__, ## args); \ IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ IPA_MPM_DRV_NAME " %s:%d " fmt, ## args); \ IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ IPA_MPM_DRV_NAME " %s:%d " fmt, ## args); \ } while (0) #define IPA_MPM_DBG_LOW(fmt, args...) \ do { \ pr_debug(IPA_MPM_DRV_NAME " %s:%d " fmt, \ __func__, __LINE__, ## args); \ IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ IPA_MPM_DRV_NAME " %s:%d " fmt, ## args); \ } while (0) #define IPA_MPM_ERR(fmt, args...) \ do { \ pr_err(IPA_MPM_DRV_NAME " %s:%d " fmt, \ __func__, __LINE__, ## args); \ IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ IPA_MPM_DRV_NAME " %s:%d " fmt, ## args); \ IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ IPA_MPM_DRV_NAME " %s:%d " fmt, ## args); \ } while (0) #define IPA_MPM_FUNC_ENTRY() \ IPA_MPM_DBG("ENTRY\n") #define IPA_MPM_FUNC_EXIT() \ IPA_MPM_DBG("EXIT\n") #define IPA_MPM_MAX_MHIP_CHAN 3 #define IPA_MPM_NUM_RING_DESC 6 #define IPA_MPM_RING_LEN IPA_MPM_NUM_RING_DESC #define IPA_MPM_MHI_HOST_UL_CHANNEL 4 #define IPA_MPM_MHI_HOST_DL_CHANNEL 5 #define TETH_AGGR_TIME_LIMIT 10000 /* 10ms */ #define TETH_AGGR_BYTE_LIMIT 24 #define TETH_AGGR_DL_BYTE_LIMIT 16 #define TRE_BUFF_SIZE 32768 #define IPA_HOLB_TMR_EN 0x1 #define IPA_HOLB_TMR_DIS 0x0 #define RNDIS_IPA_DFLT_RT_HDL 0 #define IPA_POLL_FOR_EMPTINESS_NUM 50 #define IPA_POLL_FOR_EMPTINESS_SLEEP_USEC 20 #define IPA_CHANNEL_STOP_IN_PROC_TO_MSEC 5 #define IPA_CHANNEL_STOP_IN_PROC_SLEEP_USEC 200 #define IPA_MHIP_HOLB_TMO 31 /* value to match granularity on ipa HW 4.5 */ #define IPA_MPM_FLOW_CTRL_ADD 1 #define IPA_MPM_FLOW_CTRL_DELETE 0 enum mhip_re_type { MHIP_RE_XFER = 0x2, MHIP_RE_NOP = 0x4, }; enum ipa_mpm_mhi_ch_id_type { IPA_MPM_MHIP_CH_ID_0, IPA_MPM_MHIP_CH_ID_1, IPA_MPM_MHIP_CH_ID_2, IPA_MPM_MHIP_CH_ID_MAX, }; enum ipa_mpm_dma_data_direction { DMA_HIPA_BIDIRECTIONAL = 0, DMA_TO_HIPA = 1, DMA_FROM_HIPA = 2, DMA_HIPA_NONE = 3, }; enum ipa_mpm_ipa_teth_client_type { IPA_MPM_MHIP_USB, IPA_MPM_MHIP_WIFI, }; enum ipa_mpm_mhip_client_type { IPA_MPM_MHIP_INIT, /* USB RMNET CLIENT */ IPA_MPM_MHIP_USB_RMNET, /* USB RNDIS / WIFI CLIENT */ IPA_MPM_MHIP_TETH, /* USB DPL CLIENT */ IPA_MPM_MHIP_USB_DPL, IPA_MPM_MHIP_NONE, }; enum ipa_mpm_clk_vote_type { CLK_ON, CLK_OFF, }; enum mhip_status_type { MHIP_STATUS_SUCCESS, MHIP_STATUS_NO_OP, MHIP_STATUS_FAIL, MHIP_STATUS_BAD_STATE, MHIP_STATUS_EP_NOT_FOUND, MHIP_STATUS_EP_NOT_READY, }; enum mhip_smmu_domain_type { MHIP_SMMU_DOMAIN_IPA, MHIP_SMMU_DOMAIN_PCIE, MHIP_SMMU_DOMAIN_NONE, }; enum ipa_mpm_start_stop_type { MPM_MHIP_STOP, MPM_MHIP_START, }; /* each pair of UL/DL channels are defined below */ static const struct mhi_device_id mhi_driver_match_table[] = { { .chan = "IP_HW_MHIP_0" }, /* for rndis/Wifi teth pipes */ { .chan = "IP_HW_MHIP_1" }, /* for MHIP rmnet */ { .chan = "IP_HW_ADPL" }, /* ADPL/ODL DL pipe */ }; static const char *ipa_mpm_mhip_chan_str[IPA_MPM_MHIP_CH_ID_MAX] = { __stringify(IPA_MPM_MHIP_TETH), __stringify(IPA_MPM_MHIP_USB_RMNET), __stringify(IPA_MPM_MHIP_USB_DPL), }; /* * MHI PRIME GSI Descriptor format that Host IPA uses. */ struct __packed mhi_p_desc { uint64_t buffer_ptr; uint16_t buff_len; uint16_t resvd1; uint16_t chain : 1; uint16_t resvd4 : 7; uint16_t ieob : 1; uint16_t ieot : 1; uint16_t bei : 1; uint16_t sct : 1; uint16_t resvd3 : 4; uint8_t re_type; uint8_t resvd2; }; /* * MHI PRIME Channel Context and Event Context Array * Information that is sent to Device IPA. */ struct ipa_mpm_channel_context_type { u32 chstate : 8; u32 reserved1 : 24; u32 chtype; u32 erindex; u64 rbase; u64 rlen; u64 reserved2; u64 reserved3; } __packed; struct ipa_mpm_event_context_type { u32 reserved1 : 8; u32 update_rp_modc : 8; u32 update_rp_intmodt : 16; u32 ertype; u32 update_rp_addr; u64 rbase; u64 rlen; u32 buff_size : 16; u32 reserved2 : 16; u32 reserved3; u64 reserved4; } __packed; struct ipa_mpm_pipes_info_type { enum ipa_client_type ipa_client; struct ipa_ep_cfg ep_cfg; }; struct ipa_mpm_channel_type { struct ipa_mpm_pipes_info_type dl_cons; struct ipa_mpm_pipes_info_type ul_prod; enum ipa_mpm_mhip_client_type mhip_client; }; static struct ipa_mpm_channel_type ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_MAX]; /* For configuring IPA_CLIENT_MHI_PRIME_TETH_CONS */ static struct ipa_ep_cfg mhip_dl_teth_ep_cfg = { .mode = { .mode = IPA_BASIC, .dst = IPA_CLIENT_MHI_PRIME_TETH_CONS, }, .hdr = { .hdr_len = 4, .hdr_ofst_metadata_valid = 1, .hdr_ofst_metadata = 1, .hdr_ofst_pkt_size_valid = 1, .hdr_ofst_pkt_size = 2, }, .hdr_ext = { .hdr_total_len_or_pad_valid = true, .hdr_payload_len_inc_padding = true, }, .aggr = { .aggr_en = IPA_ENABLE_DEAGGR, .aggr = IPA_QCMAP, .aggr_byte_limit = TETH_AGGR_DL_BYTE_LIMIT, .aggr_time_limit = TETH_AGGR_TIME_LIMIT, }, }; static struct ipa_ep_cfg mhip_ul_teth_ep_cfg = { .mode = { .mode = IPA_BASIC, .dst = IPA_CLIENT_MHI_PRIME_TETH_PROD, }, .hdr = { .hdr_len = 4, .hdr_ofst_metadata_valid = 1, .hdr_ofst_metadata = 0, .hdr_ofst_pkt_size_valid = 1, .hdr_ofst_pkt_size = 2, }, .hdr_ext = { .hdr_total_len_or_pad_valid = true, .hdr_payload_len_inc_padding = true, }, .aggr = { .aggr_en = IPA_ENABLE_AGGR, .aggr = IPA_QCMAP, .aggr_byte_limit = TETH_AGGR_BYTE_LIMIT, .aggr_time_limit = TETH_AGGR_TIME_LIMIT, }, }; /* WARNING!! Temporary for rndis intgration only */ /* For configuring IPA_CLIENT_MHIP_RMNET_PROD */ static struct ipa_ep_cfg mhip_dl_rmnet_ep_cfg = { .mode = { .mode = IPA_DMA, .dst = IPA_CLIENT_USB_CONS, }, }; /* For configuring IPA_CLIENT_MHIP_RMNET_CONS */ static struct ipa_ep_cfg mhip_ul_rmnet_ep_cfg = { .mode = { .mode = IPA_DMA, .dst = IPA_CLIENT_USB_CONS, }, }; /* For configuring IPA_CLIENT_MHIP_DPL_PROD using USB*/ static struct ipa_ep_cfg mhip_dl_dpl_ep_cfg = { .mode = { .mode = IPA_DMA, .dst = IPA_CLIENT_USB_DPL_CONS, }, }; struct ipa_mpm_iova_addr { dma_addr_t base; unsigned int size; }; struct ipa_mpm_dev_info { struct platform_device *pdev; struct device *dev; bool ipa_smmu_enabled; bool pcie_smmu_enabled; struct ipa_mpm_iova_addr ctrl; struct ipa_mpm_iova_addr data; u32 chdb_base; u32 erdb_base; bool is_cache_coherent; }; struct ipa_mpm_event_props { u16 id; phys_addr_t device_db; struct ipa_mpm_event_context_type ev_ctx; }; struct ipa_mpm_channel_props { u16 id; phys_addr_t device_db; struct ipa_mpm_channel_context_type ch_ctx; }; enum ipa_mpm_gsi_state { GSI_ERR, GSI_INIT, GSI_ALLOCATED, GSI_STARTED, GSI_STOPPED, }; enum ipa_mpm_remote_state { MPM_MHIP_REMOTE_STOP, MPM_MHIP_REMOTE_START, MPM_MHIP_REMOTE_ERR, }; struct ipa_mpm_channel { struct ipa_mpm_channel_props chan_props; struct ipa_mpm_event_props evt_props; enum ipa_mpm_gsi_state gsi_state; dma_addr_t db_host_iova; dma_addr_t db_device_iova; }; enum ipa_mpm_teth_state { IPA_MPM_TETH_INIT = 0, IPA_MPM_TETH_INPROGRESS, IPA_MPM_TETH_CONNECTED, }; enum ipa_mpm_mhip_chan { IPA_MPM_MHIP_CHAN_UL, IPA_MPM_MHIP_CHAN_DL, IPA_MPM_MHIP_CHAN_BOTH, }; struct ipa_mpm_clk_cnt_type { atomic_t pcie_clk_cnt; atomic_t ipa_clk_cnt; }; struct producer_rings { struct mhi_p_desc *tr_va; struct mhi_p_desc *er_va; void *tr_buff_va[IPA_MPM_RING_LEN]; dma_addr_t tr_pa; dma_addr_t er_pa; dma_addr_t tr_buff_c_iova[IPA_MPM_RING_LEN]; /* * The iova generated for AP CB, * used only for dma_map_single to flush the cache. */ dma_addr_t ap_iova_er; dma_addr_t ap_iova_tr; dma_addr_t ap_iova_buff[IPA_MPM_RING_LEN]; }; struct ipa_mpm_mhi_driver { struct mhi_device *mhi_dev; struct producer_rings ul_prod_ring; struct producer_rings dl_prod_ring; struct ipa_mpm_channel ul_prod; struct ipa_mpm_channel dl_cons; enum ipa_mpm_mhip_client_type mhip_client; enum ipa_mpm_teth_state teth_state; bool init_complete; /* General MPM mutex to protect concurrent update of MPM GSI states */ struct mutex mutex; /* * Mutex to protect mhi_dev update/ access, for concurrency such as * 5G SSR and USB disconnect/connect. */ struct mutex mhi_mutex; bool in_lpm; struct ipa_mpm_clk_cnt_type clk_cnt; enum ipa_mpm_remote_state remote_state; }; struct ipa_mpm_context { struct ipa_mpm_dev_info dev_info; struct ipa_mpm_mhi_driver md[IPA_MPM_MAX_MHIP_CHAN]; struct mutex mutex; atomic_t probe_cnt; atomic_t pcie_clk_total_cnt; atomic_t ipa_clk_total_cnt; atomic_t flow_ctrl_mask; atomic_t adpl_over_usb_available; struct device *parent_pdev; struct ipa_smmu_cb_ctx carved_smmu_cb; struct device *mhi_parent_dev; }; #define IPA_MPM_DESC_SIZE (sizeof(struct mhi_p_desc)) #define IPA_MPM_RING_TOTAL_SIZE (IPA_MPM_RING_LEN * IPA_MPM_DESC_SIZE) /* WA: Make the IPA_MPM_PAGE_SIZE from 16k (next power of ring size) to * 32k. This is to make sure IOMMU map happens for the same size * for all TR/ER and doorbells. */ #define IPA_MPM_PAGE_SIZE TRE_BUFF_SIZE static struct ipa_mpm_context *ipa_mpm_ctx; static struct platform_device *m_pdev; static int ipa_mpm_mhi_probe_cb(struct mhi_device *, const struct mhi_device_id *); static void ipa_mpm_mhi_remove_cb(struct mhi_device *); static void ipa_mpm_mhi_status_cb(struct mhi_device *, enum MHI_CB); static void ipa_mpm_change_teth_state(int probe_id, enum ipa_mpm_teth_state ip_state); static void ipa_mpm_change_gsi_state(int probe_id, enum ipa_mpm_mhip_chan mhip_chan, enum ipa_mpm_gsi_state next_state); static int ipa_mpm_probe(struct platform_device *pdev); static int ipa_mpm_vote_unvote_pcie_clk(enum ipa_mpm_clk_vote_type vote, int probe_id, bool is_force, bool *is_acted); static void ipa_mpm_vote_unvote_ipa_clk(enum ipa_mpm_clk_vote_type vote, int probe_id); static enum mhip_status_type ipa_mpm_start_stop_mhip_chan( enum ipa_mpm_mhip_chan mhip_chan, int probe_id, enum ipa_mpm_start_stop_type start_stop); static int ipa_mpm_start_mhip_holb_tmo(u32 clnt_hdl); static struct mhi_driver mhi_driver = { .id_table = mhi_driver_match_table, .probe = ipa_mpm_mhi_probe_cb, .remove = ipa_mpm_mhi_remove_cb, .status_cb = ipa_mpm_mhi_status_cb, .driver = { .name = IPA_MPM_DRV_NAME, .owner = THIS_MODULE, }, }; static void ipa_mpm_ipa3_delayed_probe(struct work_struct *work) { (void)ipa_mpm_probe(m_pdev); } static DECLARE_WORK(ipa_mpm_ipa3_scheduled_probe, ipa_mpm_ipa3_delayed_probe); static void ipa_mpm_ipa3_ready_cb(void *user_data) { struct platform_device *pdev = (struct platform_device *)(user_data); m_pdev = pdev; IPA_MPM_DBG("IPA ready callback has been triggered\n"); schedule_work(&ipa_mpm_ipa3_scheduled_probe); } static void ipa_mpm_gsi_evt_ring_err_cb(struct gsi_evt_err_notify *err_data) { IPA_MPM_ERR("GSI EVT RING ERROR, not expected..\n"); ipa_assert(); } static void ipa_mpm_gsi_chan_err_cb(struct gsi_chan_err_notify *err_data) { IPA_MPM_ERR("GSI CHAN ERROR, not expected..\n"); ipa_assert(); } static int ipa_mpm_set_dma_mode(enum ipa_client_type src_pipe, enum ipa_client_type dst_pipe, bool reset) { int result = 0; struct ipa_ep_cfg ep_cfg = { { 0 } }; IPA_MPM_FUNC_ENTRY(); IPA_MPM_DBG("DMA from %d to %d reset=%d\n", src_pipe, dst_pipe, reset); /* Reset to basic if reset = 1, otherwise set to DMA */ if (reset) ep_cfg.mode.mode = IPA_BASIC; else ep_cfg.mode.mode = IPA_DMA; ep_cfg.mode.dst = dst_pipe; ep_cfg.seq.set_dynamic = true; result = ipa_cfg_ep(ipa_get_ep_mapping(src_pipe), &ep_cfg); IPA_MPM_FUNC_EXIT(); return result; } static int ipa_mpm_start_mhip_holb_tmo(u32 clnt_hdl) { struct ipa_ep_cfg_holb holb_cfg; memset(&holb_cfg, 0, sizeof(holb_cfg)); holb_cfg.en = IPA_HOLB_TMR_EN; /* 31 ms timer, which is less than tag timeout */ holb_cfg.tmr_val = IPA_MHIP_HOLB_TMO; return ipa3_cfg_ep_holb(clnt_hdl, &holb_cfg); } /** * ipa_mpm_smmu_map() - SMMU maps ring and the buffer pointer. * @va_addr: virtual address that needs to be mapped * @sz: size of the address to be mapped * @dir: ipa_mpm_dma_data_direction * @ap_cb_iova: iova for AP context bank * * This function SMMU maps both ring and the buffer pointer. * The ring pointers will be aligned to ring size and * the buffer pointers should be aligned to buffer size. * * Returns: iova of the mapped address */ static dma_addr_t ipa_mpm_smmu_map(void *va_addr, int sz, int dir, dma_addr_t *ap_cb_iova) { struct iommu_domain *ipa_smmu_domain, *pcie_smmu_domain; phys_addr_t phys_addr; dma_addr_t iova; int smmu_enabled; unsigned long iova_p; phys_addr_t pa_p; u32 size_p; int prot = IOMMU_READ | IOMMU_WRITE; struct ipa_smmu_cb_ctx *cb = &ipa_mpm_ctx->carved_smmu_cb; unsigned long carved_iova = roundup(cb->next_addr, IPA_MPM_PAGE_SIZE); int ret = 0; /* check cache coherent */ if (ipa_mpm_ctx->dev_info.is_cache_coherent) { IPA_MPM_DBG_LOW("enable cache coherent\n"); prot |= IOMMU_CACHE; } if (carved_iova >= cb->va_end) { IPA_MPM_ERR("running out of carved_iova %lx\n", carved_iova); ipa_assert(); } /* * Both Host IPA and PCIE SMMU should be enabled or disabled * for proceed. * If SMMU Enabled => iova == pa * If SMMU Disabled => iova == iommu mapped iova * dma_map_single ensures cache is flushed and the memory is not * touched again until dma_unmap_single() is called */ smmu_enabled = (ipa_mpm_ctx->dev_info.ipa_smmu_enabled && ipa_mpm_ctx->dev_info.pcie_smmu_enabled) ? 1 : 0; if (smmu_enabled) { /* Map the phys addr to both PCIE and IPA AP CB * from the carved out common iova range. */ ipa_smmu_domain = ipa3_get_smmu_domain(); if (!ipa_smmu_domain) { IPA_MPM_ERR("invalid IPA smmu domain\n"); ipa_assert(); } if (!ipa_mpm_ctx->mhi_parent_dev) { IPA_MPM_ERR("invalid PCIE SMMU domain\n"); ipa_assert(); } phys_addr = virt_to_phys((void *) va_addr); IPA_SMMU_ROUND_TO_PAGE(carved_iova, phys_addr, sz, iova_p, pa_p, size_p); /* Flush the cache with dma_map_single for IPA AP CB */ *ap_cb_iova = dma_map_single(ipa3_ctx->pdev, va_addr, size_p, dir); if (dma_mapping_error(ipa3_ctx->pdev, *ap_cb_iova)) { IPA_MPM_ERR("dma_map_single failure for entry\n"); goto fail_dma_mapping; } ret = ipa3_iommu_map(ipa_smmu_domain, iova_p, pa_p, size_p, prot); if (ret) { IPA_MPM_ERR("IPA IOMMU returned failure, ret = %d\n", ret); ipa_assert(); } pcie_smmu_domain = iommu_get_domain_for_dev( ipa_mpm_ctx->mhi_parent_dev); if (!pcie_smmu_domain) { IPA_MPM_ERR("invalid pcie smmu domain\n"); ipa_assert(); } ret = iommu_map(pcie_smmu_domain, iova_p, pa_p, size_p, prot); if (ret) { IPA_MPM_ERR("PCIe IOMMU returned failure, ret = %d\n", ret); ipa_assert(); } cb->next_addr = iova_p + size_p; iova = iova_p; } else { iova = dma_map_single(ipa3_ctx->pdev, va_addr, IPA_MPM_RING_TOTAL_SIZE, dir); if (dma_mapping_error(ipa3_ctx->pdev, iova)) { IPA_MPM_ERR("dma_map_single failure for entry\n"); goto fail_dma_mapping; } *ap_cb_iova = iova; } return iova; fail_dma_mapping: iova = 0; ipa_assert(); return iova; } /** * ipa_mpm_smmu_unmap() - SMMU unmaps ring and the buffer pointer. * @va_addr: virtual address that needs to be mapped * @sz: size of the address to be mapped * @dir: ipa_mpm_dma_data_direction * @ap_cb_iova: iova for AP context bank * * This function SMMU unmaps both ring and the buffer pointer. * The ring pointers will be aligned to ring size and * the buffer pointers should be aligned to buffer size. * * Return: none */ static void ipa_mpm_smmu_unmap(dma_addr_t carved_iova, int sz, int dir, dma_addr_t ap_cb_iova) { unsigned long iova_p; unsigned long pa_p; u32 size_p = 0; struct iommu_domain *ipa_smmu_domain, *pcie_smmu_domain; struct ipa_smmu_cb_ctx *cb = &ipa_mpm_ctx->carved_smmu_cb; int smmu_enabled = (ipa_mpm_ctx->dev_info.ipa_smmu_enabled && ipa_mpm_ctx->dev_info.pcie_smmu_enabled) ? 1 : 0; if (carved_iova <= 0) { IPA_MPM_ERR("carved_iova is zero/negative\n"); WARN_ON(1); return; } if (smmu_enabled) { ipa_smmu_domain = ipa3_get_smmu_domain(); if (!ipa_smmu_domain) { IPA_MPM_ERR("invalid IPA smmu domain\n"); ipa_assert(); } if (!ipa_mpm_ctx->mhi_parent_dev) { IPA_MPM_ERR("invalid PCIE SMMU domain\n"); ipa_assert(); } IPA_SMMU_ROUND_TO_PAGE(carved_iova, carved_iova, sz, iova_p, pa_p, size_p); pcie_smmu_domain = iommu_get_domain_for_dev( ipa_mpm_ctx->mhi_parent_dev); if (pcie_smmu_domain) { iommu_unmap(pcie_smmu_domain, iova_p, size_p); } else { IPA_MPM_ERR("invalid PCIE SMMU domain\n"); ipa_assert(); } iommu_unmap(ipa_smmu_domain, iova_p, size_p); cb->next_addr -= size_p; dma_unmap_single(ipa3_ctx->pdev, ap_cb_iova, IPA_MPM_RING_TOTAL_SIZE, dir); } else { dma_unmap_single(ipa3_ctx->pdev, ap_cb_iova, IPA_MPM_RING_TOTAL_SIZE, dir); } } static u32 ipa_mpm_smmu_map_doorbell(enum mhip_smmu_domain_type smmu_domain, u32 pa_addr) { /* * Doorbells are already in PA, map these to * PCIE/IPA doman if SMMUs are enabled. */ struct iommu_domain *ipa_smmu_domain, *pcie_smmu_domain; int smmu_enabled; unsigned long iova_p; phys_addr_t pa_p; u32 size_p; int ret = 0; int prot = IOMMU_READ | IOMMU_WRITE; struct ipa_smmu_cb_ctx *cb = &ipa_mpm_ctx->carved_smmu_cb; unsigned long carved_iova = roundup(cb->next_addr, IPA_MPM_PAGE_SIZE); u32 iova = 0; u64 offset = 0; /* check cache coherent */ if (ipa_mpm_ctx->dev_info.is_cache_coherent) { IPA_MPM_DBG(" enable cache coherent\n"); prot |= IOMMU_CACHE; } if (carved_iova >= cb->va_end) { IPA_MPM_ERR("running out of carved_iova %lx\n", carved_iova); ipa_assert(); } smmu_enabled = (ipa_mpm_ctx->dev_info.ipa_smmu_enabled && ipa_mpm_ctx->dev_info.pcie_smmu_enabled) ? 1 : 0; if (smmu_enabled) { IPA_SMMU_ROUND_TO_PAGE(carved_iova, pa_addr, IPA_MPM_PAGE_SIZE, iova_p, pa_p, size_p); if (smmu_domain == MHIP_SMMU_DOMAIN_IPA) { ipa_smmu_domain = ipa3_get_smmu_domain(); if (!ipa_smmu_domain) { IPA_MPM_ERR("invalid IPA smmu domain\n"); ipa_assert(); } ret = ipa3_iommu_map(ipa_smmu_domain, iova_p, pa_p, size_p, prot); if (ret) { IPA_MPM_ERR("IPA doorbell mapping failed\n"); ipa_assert(); } offset = pa_addr - pa_p; } else if (smmu_domain == MHIP_SMMU_DOMAIN_PCIE) { pcie_smmu_domain = iommu_get_domain_for_dev( ipa_mpm_ctx->mhi_parent_dev); if (!pcie_smmu_domain) { IPA_MPM_ERR("invalid IPA smmu domain\n"); ipa_assert(); } ret = iommu_map(pcie_smmu_domain, iova_p, pa_p, size_p, prot); if (ret) { IPA_MPM_ERR("PCIe doorbell mapping failed\n"); ipa_assert(); } offset = pa_addr - pa_p; } iova = iova_p + offset; cb->next_addr = iova_p + IPA_MPM_PAGE_SIZE; } else { iova = pa_addr; } return iova; } static void ipa_mpm_smmu_unmap_doorbell(enum mhip_smmu_domain_type smmu_domain, dma_addr_t iova) { /* * Doorbells are already in PA, map these to * PCIE/IPA doman if SMMUs are enabled. */ struct iommu_domain *ipa_smmu_domain, *pcie_smmu_domain; int smmu_enabled; unsigned long iova_p; phys_addr_t pa_p; u32 size_p; struct ipa_smmu_cb_ctx *cb = &ipa_mpm_ctx->carved_smmu_cb; smmu_enabled = (ipa_mpm_ctx->dev_info.ipa_smmu_enabled && ipa_mpm_ctx->dev_info.pcie_smmu_enabled) ? 1 : 0; if (smmu_enabled) { IPA_SMMU_ROUND_TO_PAGE(iova, iova, IPA_MPM_PAGE_SIZE, iova_p, pa_p, size_p); if (smmu_domain == MHIP_SMMU_DOMAIN_IPA) { ipa_smmu_domain = ipa3_get_smmu_domain(); if (ipa_smmu_domain) { iommu_unmap(ipa_smmu_domain, iova_p, size_p); } else { IPA_MPM_ERR("invalid IPA smmu domain\n"); ipa_assert(); } } else if (smmu_domain == MHIP_SMMU_DOMAIN_PCIE) { pcie_smmu_domain = iommu_get_domain_for_dev( ipa_mpm_ctx->mhi_parent_dev); if (pcie_smmu_domain) { iommu_unmap(pcie_smmu_domain, iova_p, size_p); } else { IPA_MPM_ERR("invalid PCIE smmu domain\n"); ipa_assert(); } cb->next_addr -= IPA_MPM_PAGE_SIZE; } } } static int get_idx_from_id(const struct mhi_device_id *id) { return (id - mhi_driver_match_table); } static void get_ipa3_client(int id, enum ipa_client_type *ul_prod, enum ipa_client_type *dl_cons) { IPA_MPM_FUNC_ENTRY(); if (id >= IPA_MPM_MHIP_CH_ID_MAX) { *ul_prod = IPA_CLIENT_MAX; *dl_cons = IPA_CLIENT_MAX; } else { *ul_prod = ipa_mpm_pipes[id].ul_prod.ipa_client; *dl_cons = ipa_mpm_pipes[id].dl_cons.ipa_client; } IPA_MPM_FUNC_EXIT(); } static int ipa_mpm_connect_mhip_gsi_pipe(enum ipa_client_type mhip_client, int mhi_idx, struct ipa_req_chan_out_params *out_params) { int ipa_ep_idx; int res; struct mhi_p_desc *er_ring_va, *tr_ring_va; void *buff_va; dma_addr_t er_carved_iova, tr_carved_iova; dma_addr_t ap_cb_tr_iova, ap_cb_er_iova, ap_cb_buff_iova; struct ipa_request_gsi_channel_params gsi_params; int dir; int i, k; int result; struct ipa3_ep_context *ep; if (mhip_client == IPA_CLIENT_MAX) goto fail_gen; if ((mhi_idx < IPA_MPM_MHIP_CH_ID_0) || (mhi_idx >= IPA_MPM_MHIP_CH_ID_MAX)) goto fail_gen; ipa_ep_idx = ipa3_get_ep_mapping(mhip_client); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to find channel EP.\n"); goto fail_gen; } ep = &ipa3_ctx->ep[ipa_ep_idx]; if (ep->valid == 1) { IPAERR("EP %d already allocated.\n", ipa_ep_idx); return 0; } IPA_MPM_DBG("connecting client %d (ep: %d)\n", mhip_client, ipa_ep_idx); IPA_MPM_FUNC_ENTRY(); if (IPA_MPM_RING_TOTAL_SIZE > PAGE_SIZE) { IPA_MPM_ERR("Ring Size / allocation mismatch\n"); ipa_assert(); } /* Only ring need alignment, separate from buffer */ er_ring_va = (struct mhi_p_desc *) get_zeroed_page(GFP_KERNEL); if (!er_ring_va) goto fail_evt_alloc; tr_ring_va = (struct mhi_p_desc *) get_zeroed_page(GFP_KERNEL); if (!tr_ring_va) goto fail_tr_alloc; tr_ring_va[0].re_type = MHIP_RE_NOP; dir = IPA_CLIENT_IS_PROD(mhip_client) ? DMA_TO_HIPA : DMA_FROM_HIPA; /* allocate transfer ring elements */ for (i = 1, k = 1; i < IPA_MPM_RING_LEN; i++, k++) { buff_va = kzalloc(TRE_BUFF_SIZE, GFP_KERNEL); if (!buff_va) goto fail_buff_alloc; tr_ring_va[i].buffer_ptr = ipa_mpm_smmu_map(buff_va, TRE_BUFF_SIZE, dir, &ap_cb_buff_iova); if (!tr_ring_va[i].buffer_ptr) goto fail_smmu_map_ring; tr_ring_va[i].buff_len = TRE_BUFF_SIZE; tr_ring_va[i].chain = 0; tr_ring_va[i].ieob = 0; tr_ring_va[i].ieot = 0; tr_ring_va[i].bei = 0; tr_ring_va[i].sct = 0; tr_ring_va[i].re_type = MHIP_RE_XFER; if (IPA_CLIENT_IS_PROD(mhip_client)) { ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_va[k] = buff_va; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_c_iova[k] = tr_ring_va[i].buffer_ptr; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_buff[k] = ap_cb_buff_iova; } else { ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_va[k] = buff_va; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_c_iova[k] = tr_ring_va[i].buffer_ptr; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_buff[k] = ap_cb_buff_iova; } } tr_carved_iova = ipa_mpm_smmu_map(tr_ring_va, PAGE_SIZE, dir, &ap_cb_tr_iova); if (!tr_carved_iova) goto fail_smmu_map_ring; er_carved_iova = ipa_mpm_smmu_map(er_ring_va, PAGE_SIZE, dir, &ap_cb_er_iova); if (!er_carved_iova) goto fail_smmu_map_ring; /* Store Producer channel rings */ if (IPA_CLIENT_IS_PROD(mhip_client)) { /* Device UL */ ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_va = er_ring_va; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_va = tr_ring_va; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_pa = er_carved_iova; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_pa = tr_carved_iova; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_tr = ap_cb_tr_iova; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_er = ap_cb_er_iova; } else { /* Host UL */ ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_va = er_ring_va; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_va = tr_ring_va; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_pa = er_carved_iova; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_pa = tr_carved_iova; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_tr = ap_cb_tr_iova; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_er = ap_cb_er_iova; } memset(&gsi_params, 0, sizeof(struct ipa_request_gsi_channel_params)); if (IPA_CLIENT_IS_PROD(mhip_client)) gsi_params.ipa_ep_cfg = ipa_mpm_pipes[mhi_idx].dl_cons.ep_cfg; else gsi_params.ipa_ep_cfg = ipa_mpm_pipes[mhi_idx].ul_prod.ep_cfg; gsi_params.client = mhip_client; gsi_params.skip_ep_cfg = false; /* * RP update address = Device channel DB address * CLIENT_PROD -> Host DL * CLIENT_CONS -> Host UL */ if (IPA_CLIENT_IS_PROD(mhip_client)) { gsi_params.evt_ring_params.rp_update_addr = ipa_mpm_smmu_map_doorbell( MHIP_SMMU_DOMAIN_IPA, ipa_mpm_ctx->md[mhi_idx].dl_cons.chan_props.device_db); if (gsi_params.evt_ring_params.rp_update_addr == 0) goto fail_smmu_map_db; ipa_mpm_ctx->md[mhi_idx].dl_cons.db_host_iova = gsi_params.evt_ring_params.rp_update_addr; gsi_params.evt_ring_params.ring_base_addr = ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_pa; gsi_params.chan_params.ring_base_addr = ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_pa; } else { gsi_params.evt_ring_params.rp_update_addr = ipa_mpm_smmu_map_doorbell( MHIP_SMMU_DOMAIN_IPA, ipa_mpm_ctx->md[mhi_idx].ul_prod.chan_props.device_db); if (gsi_params.evt_ring_params.rp_update_addr == 0) goto fail_smmu_map_db; ipa_mpm_ctx->md[mhi_idx].ul_prod.db_host_iova = gsi_params.evt_ring_params.rp_update_addr; gsi_params.evt_ring_params.ring_base_addr = ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_pa; gsi_params.chan_params.ring_base_addr = ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_pa; } /* Fill Event ring params */ gsi_params.evt_ring_params.intf = GSI_EVT_CHTYPE_MHIP_EV; gsi_params.evt_ring_params.intr = GSI_INTR_MSI; gsi_params.evt_ring_params.re_size = GSI_EVT_RING_RE_SIZE_16B; gsi_params.evt_ring_params.ring_len = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; gsi_params.evt_ring_params.ring_base_vaddr = NULL; gsi_params.evt_ring_params.int_modt = 0; gsi_params.evt_ring_params.int_modc = 0; gsi_params.evt_ring_params.intvec = 0; gsi_params.evt_ring_params.msi_addr = 0; gsi_params.evt_ring_params.exclusive = true; gsi_params.evt_ring_params.err_cb = ipa_mpm_gsi_evt_ring_err_cb; gsi_params.evt_ring_params.user_data = NULL; /* Evt Scratch Params */ /* Disable the Moderation for ringing doorbells */ gsi_params.evt_scratch.mhip.rp_mod_threshold = 1; gsi_params.evt_scratch.mhip.rp_mod_timer = 0; gsi_params.evt_scratch.mhip.rp_mod_counter = 0; gsi_params.evt_scratch.mhip.rp_mod_timer_id = 0; gsi_params.evt_scratch.mhip.rp_mod_timer_running = 0; gsi_params.evt_scratch.mhip.fixed_buffer_sz = TRE_BUFF_SIZE; if (IPA_CLIENT_IS_PROD(mhip_client)) gsi_params.evt_scratch.mhip.rp_mod_threshold = 4; /* Channel Params */ gsi_params.chan_params.prot = GSI_CHAN_PROT_MHIP; gsi_params.chan_params.dir = IPA_CLIENT_IS_PROD(mhip_client) ? GSI_CHAN_DIR_TO_GSI : GSI_CHAN_DIR_FROM_GSI; /* chan_id is set in ipa3_request_gsi_channel() */ gsi_params.chan_params.re_size = GSI_CHAN_RE_SIZE_16B; gsi_params.chan_params.ring_len = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; gsi_params.chan_params.ring_base_vaddr = NULL; gsi_params.chan_params.use_db_eng = GSI_CHAN_DIRECT_MODE; gsi_params.chan_params.max_prefetch = GSI_ONE_PREFETCH_SEG; gsi_params.chan_params.low_weight = 1; gsi_params.chan_params.xfer_cb = NULL; gsi_params.chan_params.err_cb = ipa_mpm_gsi_chan_err_cb; gsi_params.chan_params.chan_user_data = NULL; /* Channel scratch */ gsi_params.chan_scratch.mhip.assert_bit_40 = 0; gsi_params.chan_scratch.mhip.host_channel = 1; res = ipa3_request_gsi_channel(&gsi_params, out_params); if (res) { IPA_MPM_ERR("failed to allocate GSI channel res=%d\n", res); goto fail_alloc_channel; } if (IPA_CLIENT_IS_CONS(mhip_client)) { /* * Enable HOLB timer one time after bootup/SSR. * The HOLB timeout drops the packets on MHIP if * there is a stall on MHIP TX pipe greater than * configured timeout. */ result = ipa_mpm_start_mhip_holb_tmo(ipa_ep_idx); if (result) { IPA_MPM_ERR("HOLB config failed for %d, fail = %d\n", ipa_ep_idx, result); goto fail_alloc_channel; } } if (IPA_CLIENT_IS_PROD(mhip_client)) ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_DL, GSI_ALLOCATED); else ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_UL, GSI_ALLOCATED); result = ipa3_start_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("start MHIP channel %d failed\n", mhip_client); if (IPA_CLIENT_IS_PROD(mhip_client)) ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_DL, GSI_ERR); else ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_UL, GSI_ERR); goto fail_start_channel; } if (IPA_CLIENT_IS_PROD(mhip_client)) ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_DL, GSI_STARTED); else ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_UL, GSI_STARTED); /* Fill in the Device Context params */ if (IPA_CLIENT_IS_PROD(mhip_client)) { /* This is the DL channel :: Device -> Host */ ipa_mpm_ctx->md[mhi_idx].dl_cons.evt_props.ev_ctx.rbase = ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_pa; ipa_mpm_ctx->md[mhi_idx].dl_cons.chan_props.ch_ctx.rbase = ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_pa; } else { ipa_mpm_ctx->md[mhi_idx].ul_prod.evt_props.ev_ctx.rbase = ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_pa; ipa_mpm_ctx->md[mhi_idx].ul_prod.chan_props.ch_ctx.rbase = ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_pa; } IPA_MPM_FUNC_EXIT(); return 0; fail_start_channel: ipa3_disable_data_path(ipa_ep_idx); ipa3_stop_gsi_channel(ipa_ep_idx); fail_alloc_channel: ipa3_release_gsi_channel(ipa_ep_idx); fail_smmu_map_db: fail_smmu_map_ring: fail_tr_alloc: fail_evt_alloc: fail_buff_alloc: ipa_assert(); fail_gen: return -EFAULT; } static void ipa_mpm_clean_mhip_chan(int mhi_idx, enum ipa_client_type mhip_client) { int dir; int i; int ipa_ep_idx; int result; IPA_MPM_FUNC_ENTRY(); if (mhip_client == IPA_CLIENT_MAX) return; if ((mhi_idx < IPA_MPM_MHIP_CH_ID_0) || (mhi_idx >= IPA_MPM_MHIP_CH_ID_MAX)) return; dir = IPA_CLIENT_IS_PROD(mhip_client) ? DMA_TO_HIPA : DMA_FROM_HIPA; ipa_ep_idx = ipa3_get_ep_mapping(mhip_client); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to find channel EP.\n"); return; } /* For the uplink channels, enable HOLB. */ if (IPA_CLIENT_IS_CONS(mhip_client)) ipa3_disable_data_path(ipa_ep_idx); /* Release channel */ result = ipa3_stop_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("Stop channel for MHIP_Client = %d failed\n", mhip_client); goto fail_chan; } result = ipa3_reset_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("Reset channel for MHIP_Client = %d failed\n", mhip_client); goto fail_chan; } result = ipa3_reset_gsi_event_ring(ipa_ep_idx); if (result) { IPA_MPM_ERR("Reset ev ring for MHIP_Client = %d failed\n", mhip_client); goto fail_chan; } result = ipa3_release_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("Release tr ring for MHIP_Client = %d failed\n", mhip_client); if (IPA_CLIENT_IS_PROD(mhip_client)) ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_DL, GSI_ERR); else ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_UL, GSI_ERR); goto fail_chan; } if (IPA_CLIENT_IS_PROD(mhip_client)) ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_DL, GSI_INIT); else ipa_mpm_change_gsi_state(mhi_idx, IPA_MPM_MHIP_CHAN_UL, GSI_INIT); memset(&ipa3_ctx->ep[ipa_ep_idx], 0, sizeof(struct ipa3_ep_context)); /* Unmap Doorbells */ if (IPA_CLIENT_IS_PROD(mhip_client)) { ipa_mpm_smmu_unmap_doorbell(MHIP_SMMU_DOMAIN_PCIE, ipa_mpm_ctx->md[mhi_idx].dl_cons.db_device_iova); ipa_mpm_smmu_unmap_doorbell(MHIP_SMMU_DOMAIN_IPA, ipa_mpm_ctx->md[mhi_idx].dl_cons.db_host_iova); ipa_mpm_ctx->md[mhi_idx].dl_cons.db_host_iova = 0; ipa_mpm_ctx->md[mhi_idx].dl_cons.db_device_iova = 0; } else { ipa_mpm_smmu_unmap_doorbell(MHIP_SMMU_DOMAIN_PCIE, ipa_mpm_ctx->md[mhi_idx].ul_prod.db_device_iova); ipa_mpm_smmu_unmap_doorbell(MHIP_SMMU_DOMAIN_IPA, ipa_mpm_ctx->md[mhi_idx].ul_prod.db_host_iova); ipa_mpm_ctx->md[mhi_idx].ul_prod.db_host_iova = 0; ipa_mpm_ctx->md[mhi_idx].ul_prod.db_device_iova = 0; } /* deallocate/Unmap transfer ring buffers */ for (i = 1; i < IPA_MPM_RING_LEN; i++) { if (IPA_CLIENT_IS_PROD(mhip_client)) { ipa_mpm_smmu_unmap( (dma_addr_t) ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_c_iova[i], TRE_BUFF_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_buff[i]); ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_c_iova[i] = 0; kfree( ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_va[i]); ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_va[i] = NULL; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_buff[i] = 0; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_buff_c_iova[i] = 0; } else { ipa_mpm_smmu_unmap( (dma_addr_t) ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_c_iova[i], TRE_BUFF_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_buff[i] ); ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_c_iova[i] = 0; kfree( ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_va[i]); ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_va[i] = NULL; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_buff[i] = 0; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_buff_c_iova[i] = 0; } } /* deallocate/Unmap rings */ if (IPA_CLIENT_IS_PROD(mhip_client)) { ipa_mpm_smmu_unmap( ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_pa, IPA_MPM_PAGE_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_er); ipa_mpm_smmu_unmap( ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_pa, IPA_MPM_PAGE_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_tr); if (ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_va) { free_page((unsigned long) ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_va); ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.er_va = NULL; } if (ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_va) { free_page((unsigned long) ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_va); ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.tr_va = NULL; } ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_er = 0; ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_tr = 0; } else { ipa_mpm_smmu_unmap( ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_pa, IPA_MPM_PAGE_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].dl_prod_ring.ap_iova_tr); ipa_mpm_smmu_unmap( ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_pa, IPA_MPM_PAGE_SIZE, dir, ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_er); ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_pa = 0; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_pa = 0; if (ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_va) { free_page((unsigned long) ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_va); ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.er_va = NULL; } if (ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_va) { free_page((unsigned long) ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_va); ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.tr_va = NULL; } ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_er = 0; ipa_mpm_ctx->md[mhi_idx].ul_prod_ring.ap_iova_tr = 0; } IPA_MPM_FUNC_EXIT(); return; fail_chan: ipa_assert(); } /* round addresses for closest page per SMMU requirements */ static inline void ipa_mpm_smmu_round_to_page(uint64_t iova, uint64_t pa, uint64_t size, unsigned long *iova_p, phys_addr_t *pa_p, u32 *size_p) { *iova_p = rounddown(iova, PAGE_SIZE); *pa_p = rounddown(pa, PAGE_SIZE); *size_p = roundup(size + pa - *pa_p, PAGE_SIZE); } static int __ipa_mpm_configure_mhi_device(struct ipa_mpm_channel *ch, int mhi_idx, int dir) { struct mhi_buf ch_config[2]; int ret; IPA_MPM_FUNC_ENTRY(); if (ch == NULL) { IPA_MPM_ERR("ch config is NULL\n"); return -EINVAL; } /* Populate CCA */ ch_config[0].buf = &ch->chan_props.ch_ctx; ch_config[0].len = sizeof(ch->chan_props.ch_ctx); ch_config[0].name = "CCA"; /* populate ECA */ ch_config[1].buf = &ch->evt_props.ev_ctx; ch_config[1].len = sizeof(ch->evt_props.ev_ctx); ch_config[1].name = "ECA"; IPA_MPM_DBG("Configuring MHI PRIME device for mhi_idx %d\n", mhi_idx); ret = mhi_device_configure(ipa_mpm_ctx->md[mhi_idx].mhi_dev, dir, ch_config, 2); if (ret) { IPA_MPM_ERR("mhi_device_configure failed\n"); return -EINVAL; } IPA_MPM_FUNC_EXIT(); return 0; } static void ipa_mpm_mhip_shutdown(int mhip_idx) { enum ipa_client_type ul_prod_chan, dl_cons_chan; IPA_MPM_FUNC_ENTRY(); get_ipa3_client(mhip_idx, &ul_prod_chan, &dl_cons_chan); if (mhip_idx != IPA_MPM_MHIP_CH_ID_2) /* For DPL, stop only DL channel */ ipa_mpm_clean_mhip_chan(mhip_idx, ul_prod_chan); ipa_mpm_clean_mhip_chan(mhip_idx, dl_cons_chan); if (!ipa_mpm_ctx->md[mhip_idx].in_lpm) { ipa_mpm_vote_unvote_ipa_clk(CLK_OFF, mhip_idx); /* while in modem shutdown scenarios such as SSR, no explicit * PCIe vote is needed. */ ipa_mpm_ctx->md[mhip_idx].in_lpm = true; } mutex_lock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); ipa_mpm_ctx->md[mhip_idx].mhi_dev = NULL; mutex_unlock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); IPA_MPM_FUNC_EXIT(); } /** * @ipa_mpm_vote_unvote_pcie_clk - Vote/Unvote PCIe Clock per probe_id * Returns if success or failure. * @ipa_mpm_clk_vote_type - Vote or Unvote for PCIe Clock * @probe_id - MHI probe_id per client. * @is_force - Forcebly casts vote - should be true only in probe. * @is_acted - Output param - This indicates the clk is actually voted or not * The flag output is checked only when we vote for clocks. * Return value: PCIe clock voting is success or failure. */ static int ipa_mpm_vote_unvote_pcie_clk(enum ipa_mpm_clk_vote_type vote, int probe_id, bool is_force, bool *is_acted) { int result = 0; if (probe_id >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("probe_id not found\n"); return -EINVAL; } if (vote > CLK_OFF) { IPA_MPM_ERR("Invalid vote\n"); return -EINVAL; } if (!is_acted) { IPA_MPM_ERR("Invalid clk_vote ptr\n"); return -EFAULT; } mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (ipa_mpm_ctx->md[probe_id].mhi_dev == NULL) { IPA_MPM_ERR("MHI not initialized yet\n"); *is_acted = false; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); return 0; } if (!ipa_mpm_ctx->md[probe_id].init_complete && !is_force) { /* * SSR might be in progress, dont have to vote/unvote for * IPA clocks as it will be taken care in remove_cb/subsequent * probe. */ IPA_MPM_DBG("SSR in progress, return\n"); *is_acted = false; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); return 0; } mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); IPA_MPM_DBG("PCIe clock vote/unvote = %d probe_id = %d clk_cnt = %d\n", vote, probe_id, atomic_read(&ipa_mpm_ctx->md[probe_id].clk_cnt.pcie_clk_cnt)); if (vote == CLK_ON) { result = mhi_device_get_sync( ipa_mpm_ctx->md[probe_id].mhi_dev, MHI_VOTE_BUS | MHI_VOTE_DEVICE); if (result) { IPA_MPM_ERR("mhi_sync_get failed for probe_id %d\n", result, probe_id); *is_acted = false; return result; } IPA_MPM_DBG("probe_id %d PCIE clock now ON\n", probe_id); atomic_inc(&ipa_mpm_ctx->md[probe_id].clk_cnt.pcie_clk_cnt); atomic_inc(&ipa_mpm_ctx->pcie_clk_total_cnt); } else { if ((atomic_read( &ipa_mpm_ctx->md[probe_id].clk_cnt.pcie_clk_cnt) == 0)) { IPA_MPM_DBG("probe_id %d PCIE clock already devoted\n", probe_id); WARN_ON(1); *is_acted = true; return 0; } mhi_device_put(ipa_mpm_ctx->md[probe_id].mhi_dev, MHI_VOTE_BUS | MHI_VOTE_DEVICE); IPA_MPM_DBG("probe_id %d PCIE clock off\n", probe_id); atomic_dec(&ipa_mpm_ctx->md[probe_id].clk_cnt.pcie_clk_cnt); atomic_dec(&ipa_mpm_ctx->pcie_clk_total_cnt); } *is_acted = true; return result; } /* * Turning on/OFF IPA Clock is done only once- for all clients */ static void ipa_mpm_vote_unvote_ipa_clk(enum ipa_mpm_clk_vote_type vote, int probe_id) { if (vote > CLK_OFF) return; IPA_MPM_DBG("IPA clock vote/unvote = %d probe_id = %d clk_cnt = %d\n", vote, probe_id, atomic_read(&ipa_mpm_ctx->md[probe_id].clk_cnt.ipa_clk_cnt)); if (vote == CLK_ON) { IPA_ACTIVE_CLIENTS_INC_SPECIAL(ipa_mpm_mhip_chan_str[probe_id]); IPA_MPM_DBG("IPA clock now ON for probe_id %d\n", probe_id); atomic_inc(&ipa_mpm_ctx->md[probe_id].clk_cnt.ipa_clk_cnt); atomic_inc(&ipa_mpm_ctx->ipa_clk_total_cnt); } else { if ((atomic_read (&ipa_mpm_ctx->md[probe_id].clk_cnt.ipa_clk_cnt) == 0)) { IPA_MPM_DBG("probe_id %d IPA clock count < 0\n", probe_id); WARN_ON(1); return; } IPA_ACTIVE_CLIENTS_DEC_SPECIAL(ipa_mpm_mhip_chan_str[probe_id]); IPA_MPM_DBG("probe_id %d IPA clock off\n", probe_id); atomic_dec(&ipa_mpm_ctx->md[probe_id].clk_cnt.ipa_clk_cnt); atomic_dec(&ipa_mpm_ctx->ipa_clk_total_cnt); } } /** * @ipa_mpm_start_stop_remote_mhip_chan - Start/Stop Remote device side MHIP * channels. * @ipa_mpm_clk_vote_type - Vote or Unvote for PCIe Clock * @probe_id - MHI probe_id per client. * @ipa_mpm_start_stop_type - Start/Stop remote channels. * @is_force - Forcebly casts remote channels to be started/stopped. * should be true only in probe. * Return value: 0 if success or error value. */ static int ipa_mpm_start_stop_remote_mhip_chan( int probe_id, enum ipa_mpm_start_stop_type start_stop, bool is_force) { int ret = 0; struct mhi_device *mhi_dev = ipa_mpm_ctx->md[probe_id].mhi_dev; /* Sanity check to make sure Remote channels can be started. * If probe in progress, mhi_prepare_for_transfer will start * the remote channels so no need to start it from here. */ mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (!ipa_mpm_ctx->md[probe_id].init_complete && !is_force) { IPA_MPM_ERR("MHI not initialized yet, probe in progress\n"); mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); return ret; } /* For error state, expect modem SSR to recover from error */ if (ipa_mpm_ctx->md[probe_id].remote_state == MPM_MHIP_REMOTE_ERR) { IPA_MPM_ERR("Remote channels in err state for %d\n", probe_id); mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); return -EFAULT; } mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (start_stop == MPM_MHIP_START) { if (ipa_mpm_ctx->md[probe_id].remote_state == MPM_MHIP_REMOTE_START) { IPA_MPM_DBG("Remote channel already started for %d\n", probe_id); } else { ret = mhi_resume_transfer(mhi_dev); mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (ret) ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_ERR; else ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_START; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); } } else { if (ipa_mpm_ctx->md[probe_id].remote_state == MPM_MHIP_REMOTE_STOP) { IPA_MPM_DBG("Remote channel already stopped for %d\n", probe_id); } else { ret = mhi_pause_transfer(mhi_dev); mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (ret) ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_ERR; else ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_STOP; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); } } return ret; } static enum mhip_status_type ipa_mpm_start_stop_mhip_chan( enum ipa_mpm_mhip_chan mhip_chan, int probe_id, enum ipa_mpm_start_stop_type start_stop) { int ipa_ep_idx; struct ipa3_ep_context *ep; bool is_start; enum ipa_client_type ul_chan, dl_chan; u32 source_pipe_bitmask = 0; enum gsi_status gsi_res = GSI_STATUS_SUCCESS; int result; IPA_MPM_FUNC_ENTRY(); if (mhip_chan > IPA_MPM_MHIP_CHAN_BOTH) { IPA_MPM_ERR("MHI not initialized yet\n"); return MHIP_STATUS_FAIL; } if (probe_id >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("MHI not initialized yet\n"); return MHIP_STATUS_FAIL; } get_ipa3_client(probe_id, &ul_chan, &dl_chan); if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { ipa_ep_idx = ipa3_get_ep_mapping(ul_chan); } else if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { ipa_ep_idx = ipa3_get_ep_mapping(dl_chan); } else if (mhip_chan == IPA_MPM_MHIP_CHAN_BOTH) { ipa_ep_idx = ipa3_get_ep_mapping(ul_chan); ipa_ep_idx = ipa3_get_ep_mapping(dl_chan); } if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to get EP# for idx %d\n", ipa_ep_idx); return MHIP_STATUS_EP_NOT_FOUND; } mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); if (!ipa_mpm_ctx->md[probe_id].init_complete) { IPA_MPM_ERR("MHIP probe %d not initialized\n", probe_id); mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); return MHIP_STATUS_EP_NOT_READY; } mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); ep = &ipa3_ctx->ep[ipa_ep_idx]; if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { IPA_MPM_DBG("current GSI state = %d, action = %d\n", ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state, start_stop); if (ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state < GSI_ALLOCATED) { IPA_MPM_ERR("GSI chan is not allocated yet\n"); return MHIP_STATUS_EP_NOT_READY; } } else if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { IPA_MPM_DBG("current GSI state = %d, action = %d\n", ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state, start_stop); if (ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state < GSI_ALLOCATED) { IPA_MPM_ERR("GSI chan is not allocated yet\n"); return MHIP_STATUS_EP_NOT_READY; } } is_start = (start_stop == MPM_MHIP_START) ? true : false; if (is_start) { if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { if (ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state == GSI_STARTED) { IPA_MPM_ERR("GSI chan is already started\n"); return MHIP_STATUS_NO_OP; } } if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { if (ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state == GSI_STARTED) { IPA_MPM_ERR("GSI chan is already started\n"); return MHIP_STATUS_NO_OP; } } /* Start GSI channel */ gsi_res = ipa3_start_gsi_channel(ipa_ep_idx); if (gsi_res != GSI_STATUS_SUCCESS) { IPA_MPM_ERR("Error starting channel: err = %d\n", gsi_res); goto gsi_chan_fail; } else { ipa_mpm_change_gsi_state(probe_id, mhip_chan, GSI_STARTED); } } else { if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { if (ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state == GSI_STOPPED) { IPA_MPM_ERR("GSI chan is already stopped\n"); return MHIP_STATUS_NO_OP; } else if (ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state != GSI_STARTED) { IPA_MPM_ERR("GSI chan isn't already started\n"); return MHIP_STATUS_NO_OP; } } if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { if (ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state == GSI_STOPPED) { IPA_MPM_ERR("GSI chan is already stopped\n"); return MHIP_STATUS_NO_OP; } else if (ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state != GSI_STARTED) { IPA_MPM_ERR("GSI chan isn't already started\n"); return MHIP_STATUS_NO_OP; } } if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { source_pipe_bitmask = 1 << ipa3_get_ep_mapping(ep->client); /* First Stop UL GSI channel before unvote PCIe clock */ result = ipa3_stop_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("UL chan stop failed\n"); goto gsi_chan_fail; } else { ipa_mpm_change_gsi_state(probe_id, mhip_chan, GSI_STOPPED); } } if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { result = ipa3_stop_gsi_channel(ipa_ep_idx); if (result) { IPA_MPM_ERR("Fail to stop DL channel\n"); goto gsi_chan_fail; } else { ipa_mpm_change_gsi_state(probe_id, mhip_chan, GSI_STOPPED); } } } IPA_MPM_FUNC_EXIT(); return MHIP_STATUS_SUCCESS; gsi_chan_fail: ipa3_disable_data_path(ipa_ep_idx); ipa_mpm_change_gsi_state(probe_id, mhip_chan, GSI_ERR); ipa_assert(); return MHIP_STATUS_FAIL; } int ipa_mpm_notify_wan_state(struct wan_ioctl_notify_wan_state *state) { int probe_id = IPA_MPM_MHIP_CH_ID_MAX; int i; static enum mhip_status_type status; int ret = 0; enum ipa_mpm_mhip_client_type mhip_client = IPA_MPM_MHIP_TETH; bool is_acted = true; const struct ipa_gsi_ep_config *ep_cfg; uint32_t flow_ctrl_mask = 0; if (!state) return -EPERM; if (!ipa3_is_mhip_offload_enabled()) return -EPERM; for (i = 0; i < IPA_MPM_MHIP_CH_ID_MAX; i++) { if (ipa_mpm_pipes[i].mhip_client == mhip_client) { probe_id = i; break; } } if (probe_id == IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("Unknown probe_id\n"); return -EPERM; } IPA_MPM_DBG("WAN backhaul available for probe_id = %d\n", probe_id); if (state->up) { /* Start UL MHIP channel for offloading tethering connection */ ret = ipa_mpm_vote_unvote_pcie_clk(CLK_ON, probe_id, false, &is_acted); if (ret) { IPA_MPM_ERR("Err %d cloking on PCIe clk %d\n", ret); return ret; } /* * Make sure to start Device side channels before * starting Host side UL channels. This is to make * sure device side access host side only after * Host IPA gets voted. */ ret = ipa_mpm_start_stop_remote_mhip_chan(probe_id, MPM_MHIP_START, false); if (ret) { /* * This can fail only when modem is in SSR state. * Eventually there would be a remove callback, * so return a failure. */ IPA_MPM_ERR("MHIP remote chan start fail = %d\n", ret); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); return ret; } IPA_MPM_DBG("MHIP remote channels are started\n"); /* * Update flow control monitoring end point info. * This info will be used to set delay on the end points upon * hitting RED water mark. */ ep_cfg = ipa3_get_gsi_ep_info(IPA_CLIENT_WLAN2_PROD); if (!ep_cfg) IPA_MPM_ERR("ep = %d not allocated yet\n", IPA_CLIENT_WLAN2_PROD); else flow_ctrl_mask |= 1 << (ep_cfg->ipa_gsi_chan_num); ep_cfg = ipa3_get_gsi_ep_info(IPA_CLIENT_USB_PROD); if (!ep_cfg) IPA_MPM_ERR("ep = %d not allocated yet\n", IPA_CLIENT_USB_PROD); else flow_ctrl_mask |= 1 << (ep_cfg->ipa_gsi_chan_num); atomic_set(&ipa_mpm_ctx->flow_ctrl_mask, flow_ctrl_mask); ret = ipa3_uc_send_update_flow_control(flow_ctrl_mask, IPA_MPM_FLOW_CTRL_ADD); if (ret) IPA_MPM_ERR("Err = %d setting uc flow control\n", ret); status = ipa_mpm_start_stop_mhip_chan( IPA_MPM_MHIP_CHAN_UL, probe_id, MPM_MHIP_START); switch (status) { case MHIP_STATUS_SUCCESS: ipa_mpm_ctx->md[probe_id].teth_state = IPA_MPM_TETH_CONNECTED; break; case MHIP_STATUS_EP_NOT_READY: case MHIP_STATUS_NO_OP: IPA_MPM_DBG("UL chan already start, status = %d\n", status); if (is_acted) { return ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); } break; case MHIP_STATUS_FAIL: case MHIP_STATUS_BAD_STATE: case MHIP_STATUS_EP_NOT_FOUND: IPA_MPM_ERR("UL chan start err =%d\n", status); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); ipa_assert(); return -EFAULT; default: IPA_MPM_ERR("Err not found\n"); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); ret = -EFAULT; break; } ipa_mpm_ctx->md[probe_id].mhip_client = mhip_client; } else { /* * Update flow control monitoring end point info. * This info will be used to reset delay on the end points. */ flow_ctrl_mask = atomic_read(&ipa_mpm_ctx->flow_ctrl_mask); ret = ipa3_uc_send_update_flow_control(flow_ctrl_mask, IPA_MPM_FLOW_CTRL_DELETE); flow_ctrl_mask = 0; atomic_set(&ipa_mpm_ctx->flow_ctrl_mask, 0); if (ret) { IPA_MPM_ERR("Err = %d resetting uc flow control\n", ret); ipa_assert(); } /* * Make sure to stop Device side channels before * stopping Host side UL channels. This is to make * sure device side doesn't access host IPA after * Host IPA gets devoted. */ ret = ipa_mpm_start_stop_remote_mhip_chan(probe_id, MPM_MHIP_STOP, false); if (ret) { /* * This can fail only when modem is in SSR state. * Eventually there would be a remove callback, * so return a failure. */ IPA_MPM_ERR("MHIP remote chan stop fail = %d\n", ret); return ret; } IPA_MPM_DBG("MHIP remote channels are stopped\n"); status = ipa_mpm_start_stop_mhip_chan( IPA_MPM_MHIP_CHAN_UL, probe_id, MPM_MHIP_STOP); switch (status) { case MHIP_STATUS_SUCCESS: ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_INIT); break; case MHIP_STATUS_NO_OP: case MHIP_STATUS_EP_NOT_READY: IPA_MPM_DBG("UL chan already stop, status = %d\n", status); break; case MHIP_STATUS_FAIL: case MHIP_STATUS_BAD_STATE: case MHIP_STATUS_EP_NOT_FOUND: IPA_MPM_ERR("UL chan cant be stopped err =%d\n", status); ipa_assert(); return -EFAULT; default: IPA_MPM_ERR("Err not found\n"); return -EFAULT; } /* Stop UL MHIP channel for offloading tethering connection */ ret = ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); if (ret) { IPA_MPM_ERR("Error cloking off PCIe clk, err = %d\n", ret); return ret; } ipa_mpm_ctx->md[probe_id].mhip_client = IPA_MPM_MHIP_NONE; } return ret; } static void ipa_mpm_change_gsi_state(int probe_id, enum ipa_mpm_mhip_chan mhip_chan, enum ipa_mpm_gsi_state next_state) { if (probe_id >= IPA_MPM_MHIP_CH_ID_MAX) return; if (mhip_chan == IPA_MPM_MHIP_CHAN_UL) { mutex_lock(&ipa_mpm_ctx->md[probe_id].mutex); ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state = next_state; IPA_MPM_DBG("GSI next_state = %d\n", ipa_mpm_ctx->md[probe_id].ul_prod.gsi_state); mutex_unlock(&ipa_mpm_ctx->md[probe_id].mutex); } if (mhip_chan == IPA_MPM_MHIP_CHAN_DL) { mutex_lock(&ipa_mpm_ctx->md[probe_id].mutex); ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state = next_state; IPA_MPM_DBG("GSI next_state = %d\n", ipa_mpm_ctx->md[probe_id].dl_cons.gsi_state); mutex_unlock(&ipa_mpm_ctx->md[probe_id].mutex); } } static void ipa_mpm_change_teth_state(int probe_id, enum ipa_mpm_teth_state next_state) { enum ipa_mpm_teth_state curr_state; if (probe_id >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("Unknown probe_id\n"); return; } curr_state = ipa_mpm_ctx->md[probe_id].teth_state; IPA_MPM_DBG("curr_state = %d, ip_state = %d mhip_s\n", curr_state, next_state); switch (curr_state) { case IPA_MPM_TETH_INIT: if (next_state == IPA_MPM_TETH_CONNECTED) next_state = IPA_MPM_TETH_INPROGRESS; break; case IPA_MPM_TETH_INPROGRESS: break; case IPA_MPM_TETH_CONNECTED: break; default: IPA_MPM_ERR("No change in state\n"); break; } ipa_mpm_ctx->md[probe_id].teth_state = next_state; IPA_MPM_DBG("next_state = %d\n", next_state); } static void ipa_mpm_read_channel(enum ipa_client_type chan) { struct gsi_chan_info chan_info; int ipa_ep_idx; struct ipa3_ep_context *ep; int res; ipa_ep_idx = ipa3_get_ep_mapping(chan); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPAERR("failed to get idx"); return; } ep = &ipa3_ctx->ep[ipa_ep_idx]; IPA_MPM_DBG("Reading channel for chan %d, ep = %d, gsi_chan_hdl = %d\n", chan, ep, ep->gsi_chan_hdl); res = ipa3_get_gsi_chan_info(&chan_info, ep->gsi_chan_hdl); if (res) IPA_MPM_ERR("Reading of channel failed for ep %d\n", ep); } /* ipa_mpm_mhi_probe_cb is received for each MHI'/MHI channel * Currently we have 4 MHI channels. */ static int ipa_mpm_mhi_probe_cb(struct mhi_device *mhi_dev, const struct mhi_device_id *mhi_id) { struct ipa_mpm_channel *ch; int ret; enum ipa_client_type ul_prod, dl_cons; int probe_id; struct ipa_req_chan_out_params ul_out_params, dl_out_params; void __iomem *db_addr; int ipa_ep_idx; struct ipa3_ep_context *ep; u32 evt_ring_db_addr_low, evt_ring_db_addr_high; u32 wp_addr; int pipe_idx; bool is_acted = true; uint64_t flow_ctrl_mask = 0; bool add_delete = false; IPA_MPM_FUNC_ENTRY(); if (ipa_mpm_ctx == NULL) { IPA_MPM_ERR("ipa_mpm_ctx is NULL not expected, returning..\n"); return -ENOMEM; } probe_id = get_idx_from_id(mhi_id); if (probe_id >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("chan=%pK is not supported for now\n", mhi_id); return -EPERM; } if (ipa_mpm_ctx->md[probe_id].init_complete) { IPA_MPM_ERR("Probe initialization already done, returning\n"); return 0; } IPA_MPM_DBG("Received probe for id=%d\n", probe_id); get_ipa3_client(probe_id, &ul_prod, &dl_cons); /* Vote for IPA clock for first time in initialization seq. * IPA clock will be devoted when MHI enters LPM * PCIe clock will be voted / devoted with every channel probe * we receive. * ul_prod = Host -> Device * dl_cons = Device -> Host */ ipa_mpm_ctx->md[probe_id].mhi_dev = mhi_dev; ipa_mpm_ctx->mhi_parent_dev = ipa_mpm_ctx->md[probe_id].mhi_dev->dev.parent; mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_STOP; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); ret = ipa_mpm_vote_unvote_pcie_clk(CLK_ON, probe_id, true, &is_acted); if (ret) { IPA_MPM_ERR("Err %d voitng PCIe clocks\n", ret); return -EPERM; } ipa_mpm_vote_unvote_ipa_clk(CLK_ON, probe_id); ipa_mpm_ctx->md[probe_id].in_lpm = false; IPA_MPM_DBG("ul chan = %d, dl_chan = %d\n", ul_prod, dl_cons); /* * Set up MHI' pipes for Device IPA filling in * Channel Context and Event Context. * These params will be sent to Device side. * UL CHAN = HOST -> Device * DL CHAN = Device -> HOST * per channel a TRE and EV is allocated. * for a UL channel - * IPA HOST PROD TRE -> IPA DEVICE CONS EV * IPA HOST PROD EV -> IPA DEVICE CONS TRE * for a DL channel - * IPA Device PROD TRE -> IPA HOST CONS EV * IPA Device PROD EV -> IPA HOST CONS TRE */ if (ul_prod != IPA_CLIENT_MAX) { /* store UL properties */ ch = &ipa_mpm_ctx->md[probe_id].ul_prod; /* Store Channel properties */ ch->chan_props.id = mhi_dev->ul_chan_id; ch->chan_props.device_db = ipa_mpm_ctx->dev_info.chdb_base + ch->chan_props.id * 8; /* Fill Channel Conext to be sent to Device side */ ch->chan_props.ch_ctx.chtype = IPA_MPM_MHI_HOST_UL_CHANNEL; ch->chan_props.ch_ctx.erindex = mhi_dev->ul_event_id; ch->chan_props.ch_ctx.rlen = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; /* Store Event properties */ ch->evt_props.ev_ctx.update_rp_modc = 0; ch->evt_props.ev_ctx.update_rp_intmodt = 0; ch->evt_props.ev_ctx.ertype = 1; ch->evt_props.ev_ctx.rlen = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; ch->evt_props.ev_ctx.buff_size = TRE_BUFF_SIZE; ch->evt_props.device_db = ipa_mpm_ctx->dev_info.erdb_base + ch->chan_props.ch_ctx.erindex * 8; /* connect Host GSI pipes with MHI' protocol */ ret = ipa_mpm_connect_mhip_gsi_pipe(ul_prod, probe_id, &ul_out_params); if (ret) { IPA_MPM_ERR("failed connecting MPM client %d\n", ul_prod); goto fail_gsi_setup; } ch->evt_props.ev_ctx.update_rp_addr = ipa_mpm_smmu_map_doorbell( MHIP_SMMU_DOMAIN_PCIE, ul_out_params.db_reg_phs_addr_lsb); if (ch->evt_props.ev_ctx.update_rp_addr == 0) ipa_assert(); ipa_mpm_ctx->md[probe_id].ul_prod.db_device_iova = ch->evt_props.ev_ctx.update_rp_addr; ret = __ipa_mpm_configure_mhi_device( ch, probe_id, DMA_TO_HIPA); if (ret) { IPA_MPM_ERR("configure_mhi_dev fail %d\n", ret); goto fail_smmu; } } if (dl_cons != IPA_CLIENT_MAX) { /* store DL channel properties */ ch = &ipa_mpm_ctx->md[probe_id].dl_cons; /* Store Channel properties */ ch->chan_props.id = mhi_dev->dl_chan_id; ch->chan_props.device_db = ipa_mpm_ctx->dev_info.chdb_base + ch->chan_props.id * 8; /* Fill Channel Conext to be be sent to Dev side */ ch->chan_props.ch_ctx.chstate = 1; ch->chan_props.ch_ctx.chtype = IPA_MPM_MHI_HOST_DL_CHANNEL; ch->chan_props.ch_ctx.erindex = mhi_dev->dl_event_id; ch->chan_props.ch_ctx.rlen = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; /* Store Event properties */ ch->evt_props.ev_ctx.update_rp_modc = 0; ch->evt_props.ev_ctx.update_rp_intmodt = 0; ch->evt_props.ev_ctx.ertype = 1; ch->evt_props.ev_ctx.rlen = (IPA_MPM_RING_LEN) * GSI_EVT_RING_RE_SIZE_16B; ch->evt_props.ev_ctx.buff_size = TRE_BUFF_SIZE; ch->evt_props.device_db = ipa_mpm_ctx->dev_info.erdb_base + ch->chan_props.ch_ctx.erindex * 8; /* connect Host GSI pipes with MHI' protocol */ ret = ipa_mpm_connect_mhip_gsi_pipe(dl_cons, probe_id, &dl_out_params); if (ret) { IPA_MPM_ERR("connecting MPM client = %d failed\n", dl_cons); goto fail_gsi_setup; } ch->evt_props.ev_ctx.update_rp_addr = ipa_mpm_smmu_map_doorbell( MHIP_SMMU_DOMAIN_PCIE, dl_out_params.db_reg_phs_addr_lsb); if (ch->evt_props.ev_ctx.update_rp_addr == 0) ipa_assert(); ipa_mpm_ctx->md[probe_id].dl_cons.db_device_iova = ch->evt_props.ev_ctx.update_rp_addr; ret = __ipa_mpm_configure_mhi_device(ch, probe_id, DMA_FROM_HIPA); if (ret) { IPA_MPM_ERR("mpm_config_mhi_dev failed %d\n", ret); goto fail_smmu; } } ret = mhi_prepare_for_transfer(ipa_mpm_ctx->md[probe_id].mhi_dev); if (ret) { IPA_MPM_ERR("mhi_prepare_for_transfer failed %d\n", ret); WARN_ON(1); /* * WA to handle prepare_for_tx failures. * Though prepare for transfer fails, indicate success * to MHI driver. remove_cb will be called eventually when * Device side comes from where pending cleanup happens. */ mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); atomic_inc(&ipa_mpm_ctx->probe_cnt); ipa_mpm_ctx->md[probe_id].init_complete = false; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); IPA_MPM_FUNC_EXIT(); return 0; } /* mhi_prepare_for_transfer translates to starting remote channels */ mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); ipa_mpm_ctx->md[probe_id].remote_state = MPM_MHIP_REMOTE_START; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); /* * Ring initial channel db - Host Side UL and Device side DL channel. * To ring doorbell, write "WP" into doorbell register. * This WP should be set to 1 element less than ring max. */ /* Ring UL PRODUCER TRANSFER RING (HOST IPA -> DEVICE IPA) Doorbell */ if (ul_prod != IPA_CLIENT_MAX) { IPA_MPM_DBG("Host UL TR PA DB = 0X%0x\n", ul_out_params.db_reg_phs_addr_lsb); db_addr = ioremap( (phys_addr_t)(ul_out_params.db_reg_phs_addr_lsb), 4); wp_addr = ipa_mpm_ctx->md[probe_id].ul_prod_ring.tr_pa + ((IPA_MPM_RING_LEN - 1) * GSI_CHAN_RE_SIZE_16B); iowrite32(wp_addr, db_addr); IPA_MPM_DBG("Host UL TR DB = 0X%pK, wp_addr = 0X%0x", db_addr, wp_addr); iounmap(db_addr); ipa_mpm_read_channel(ul_prod); /* Ring UL PRODUCER EVENT RING (HOST IPA -> DEVICE IPA) Doorbell * Ring the event DB to a value outside the * ring range such that rp and wp never meet. */ ipa_ep_idx = ipa3_get_ep_mapping(ul_prod); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to alloc EP.\n"); goto fail_start_channel; } ep = &ipa3_ctx->ep[ipa_ep_idx]; IPA_MPM_DBG("for ep_idx %d , gsi_evt_ring_hdl = %ld\n", ipa_ep_idx, ep->gsi_evt_ring_hdl); gsi_query_evt_ring_db_addr(ep->gsi_evt_ring_hdl, &evt_ring_db_addr_low, &evt_ring_db_addr_high); IPA_MPM_DBG("Host UL ER PA DB = 0X%0x\n", evt_ring_db_addr_low); db_addr = ioremap((phys_addr_t)(evt_ring_db_addr_low), 4); wp_addr = ipa_mpm_ctx->md[probe_id].ul_prod_ring.er_pa + ((IPA_MPM_RING_LEN + 1) * GSI_EVT_RING_RE_SIZE_16B); IPA_MPM_DBG("Host UL ER DB = 0X%pK, wp_addr = 0X%0x", db_addr, wp_addr); iowrite32(wp_addr, db_addr); iounmap(db_addr); /* Ring DEVICE IPA DL CONSUMER Event Doorbell */ db_addr = ioremap((phys_addr_t) (ipa_mpm_ctx->md[probe_id].ul_prod.evt_props.device_db), 4); wp_addr = ipa_mpm_ctx->md[probe_id].ul_prod_ring.tr_pa + ((IPA_MPM_RING_LEN + 1) * GSI_EVT_RING_RE_SIZE_16B); iowrite32(wp_addr, db_addr); iounmap(db_addr); } /* Ring DL PRODUCER (DEVICE IPA -> HOST IPA) Doorbell */ if (dl_cons != IPA_CLIENT_MAX) { db_addr = ioremap((phys_addr_t) (ipa_mpm_ctx->md[probe_id].dl_cons.chan_props.device_db), 4); wp_addr = ipa_mpm_ctx->md[probe_id].dl_prod_ring.tr_pa + ((IPA_MPM_RING_LEN - 1) * GSI_CHAN_RE_SIZE_16B); IPA_MPM_DBG("Device DL TR DB = 0X%pK, wp_addr = 0X%0x", db_addr, wp_addr); iowrite32(wp_addr, db_addr); iounmap(db_addr); /* * Ring event ring DB on Device side. * ipa_mpm should ring the event DB to a value outside the * ring range such that rp and wp never meet. */ db_addr = ioremap( (phys_addr_t) (ipa_mpm_ctx->md[probe_id].dl_cons.evt_props.device_db), 4); wp_addr = ipa_mpm_ctx->md[probe_id].dl_prod_ring.er_pa + ((IPA_MPM_RING_LEN + 1) * GSI_EVT_RING_RE_SIZE_16B); iowrite32(wp_addr, db_addr); IPA_MPM_DBG("Device UL ER DB = 0X%pK,wp_addr = 0X%0x", db_addr, wp_addr); iounmap(db_addr); /* Ring DL EVENT RING CONSUMER (DEVICE IPA CONSUMER) Doorbell */ ipa_ep_idx = ipa3_get_ep_mapping(dl_cons); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to alloc EP.\n"); goto fail_start_channel; } ep = &ipa3_ctx->ep[ipa_ep_idx]; gsi_query_evt_ring_db_addr(ep->gsi_evt_ring_hdl, &evt_ring_db_addr_low, &evt_ring_db_addr_high); IPA_MPM_DBG("Host DL ER PA DB = 0X%0x\n", evt_ring_db_addr_low); db_addr = ioremap((phys_addr_t)(evt_ring_db_addr_low), 4); wp_addr = ipa_mpm_ctx->md[probe_id].dl_prod_ring.tr_pa + ((IPA_MPM_RING_LEN + 1) * GSI_EVT_RING_RE_SIZE_16B); iowrite32(wp_addr, db_addr); IPA_MPM_DBG("Host DL ER DB = 0X%pK, wp_addr = 0X%0x", db_addr, wp_addr); iounmap(db_addr); } /* Check if TETH connection is in progress. * If teth isn't started by now, then Stop UL channel. */ switch (ipa_mpm_ctx->md[probe_id].teth_state) { case IPA_MPM_TETH_INIT: /* * Make sure to stop Device side channels before * stopping Host side UL channels. This is to make * sure Device side doesn't access host side IPA if * Host IPA gets unvoted. */ ret = ipa_mpm_start_stop_remote_mhip_chan(probe_id, MPM_MHIP_STOP, true); if (ret) { /* * This can fail only when modem is in SSR. * Eventually there would be a remove callback, * so return a failure. */ IPA_MPM_ERR("MHIP remote chan stop fail = %d\n", ret); return ret; } if (ul_prod != IPA_CLIENT_MAX) { /* No teth started yet, disable UL channel */ ipa_ep_idx = ipa3_get_ep_mapping(ul_prod); if (ipa_ep_idx == IPA_EP_NOT_ALLOCATED) { IPA_MPM_ERR("fail to alloc EP.\n"); goto fail_stop_channel; } ret = ipa3_stop_gsi_channel(ipa_ep_idx); if (ret) { IPA_MPM_ERR("MHIP Stop channel err = %d\n", ret); goto fail_stop_channel; } ipa_mpm_change_gsi_state(probe_id, IPA_MPM_MHIP_CHAN_UL, GSI_STOPPED); } if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, true, &is_acted); break; case IPA_MPM_TETH_INPROGRESS: case IPA_MPM_TETH_CONNECTED: IPA_MPM_DBG("UL channel is already started, continue\n"); ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_CONNECTED); /* Lift the delay for rmnet USB prod pipe */ if (probe_id == IPA_MPM_MHIP_CH_ID_1) { pipe_idx = ipa3_get_ep_mapping(IPA_CLIENT_USB_PROD); ipa3_xdci_ep_delay_rm(pipe_idx); } break; default: IPA_MPM_DBG("No op for UL channel, in teth state = %d", ipa_mpm_ctx->md[probe_id].teth_state); break; } atomic_inc(&ipa_mpm_ctx->probe_cnt); /* Check if ODL/USB DPL pipe is connected before probe */ if (probe_id == IPA_MPM_MHIP_CH_ID_2) { if (ipa3_is_odl_connected()) ret = ipa_mpm_set_dma_mode( IPA_CLIENT_MHI_PRIME_DPL_PROD, IPA_CLIENT_ODL_DPL_CONS, false); else if (atomic_read(&ipa_mpm_ctx->adpl_over_usb_available)) ret = ipa_mpm_set_dma_mode( IPA_CLIENT_MHI_PRIME_DPL_PROD, IPA_CLIENT_USB_DPL_CONS, false); if (ret) IPA_MPM_ERR("DPL DMA to ODL/USB failed, ret = %d\n", ret); } mutex_lock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); ipa_mpm_ctx->md[probe_id].init_complete = true; mutex_unlock(&ipa_mpm_ctx->md[probe_id].mhi_mutex); /* Update Flow control Monitoring, only for the teth UL Prod pipes */ if (probe_id == IPA_MPM_MHIP_CH_ID_0) { ipa_ep_idx = ipa3_get_ep_mapping(ul_prod); ep = &ipa3_ctx->ep[ipa_ep_idx]; ret = ipa3_uc_send_enable_flow_control(ep->gsi_chan_hdl, IPA_MPM_RING_LEN / 4); if (ret) { IPA_MPM_ERR("Err %d flow control enable\n", ret); goto fail_flow_control; } IPA_MPM_DBG("Flow Control enabled for %d", probe_id); flow_ctrl_mask = atomic_read(&ipa_mpm_ctx->flow_ctrl_mask); add_delete = flow_ctrl_mask > 0 ? 1 : 0; ret = ipa3_uc_send_update_flow_control(flow_ctrl_mask, add_delete); if (ret) { IPA_MPM_ERR("Err %d flow control update\n", ret); goto fail_flow_control; } IPA_MPM_DBG("Flow Control updated for %d", probe_id); } IPA_MPM_FUNC_EXIT(); return 0; fail_gsi_setup: fail_start_channel: fail_stop_channel: fail_smmu: fail_flow_control: if (ipa_mpm_ctx->dev_info.ipa_smmu_enabled) IPA_MPM_DBG("SMMU failed\n"); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, true, &is_acted); ipa_mpm_vote_unvote_ipa_clk(CLK_OFF, probe_id); ipa_assert(); return ret; } static void ipa_mpm_init_mhip_channel_info(void) { /* IPA_MPM_MHIP_CH_ID_0 => MHIP TETH PIPES */ ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_0].dl_cons.ipa_client = IPA_CLIENT_MHI_PRIME_TETH_PROD; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_0].dl_cons.ep_cfg = mhip_dl_teth_ep_cfg; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_0].ul_prod.ipa_client = IPA_CLIENT_MHI_PRIME_TETH_CONS; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_0].ul_prod.ep_cfg = mhip_ul_teth_ep_cfg; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_0].mhip_client = IPA_MPM_MHIP_TETH; /* IPA_MPM_MHIP_CH_ID_1 => MHIP RMNET PIPES */ ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_1].dl_cons.ipa_client = IPA_CLIENT_MHI_PRIME_RMNET_PROD; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_1].dl_cons.ep_cfg = mhip_dl_rmnet_ep_cfg; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_1].ul_prod.ipa_client = IPA_CLIENT_MHI_PRIME_RMNET_CONS; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_1].ul_prod.ep_cfg = mhip_ul_rmnet_ep_cfg; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_1].mhip_client = IPA_MPM_MHIP_USB_RMNET; /* IPA_MPM_MHIP_CH_ID_2 => MHIP ADPL PIPE */ ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_2].dl_cons.ipa_client = IPA_CLIENT_MHI_PRIME_DPL_PROD; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_2].dl_cons.ep_cfg = mhip_dl_dpl_ep_cfg; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_2].ul_prod.ipa_client = IPA_CLIENT_MAX; ipa_mpm_pipes[IPA_MPM_MHIP_CH_ID_2].mhip_client = IPA_MPM_MHIP_USB_DPL; } static void ipa_mpm_mhi_remove_cb(struct mhi_device *mhi_dev) { int mhip_idx; IPA_MPM_FUNC_ENTRY(); for (mhip_idx = 0; mhip_idx < IPA_MPM_MHIP_CH_ID_MAX; mhip_idx++) { if (mhi_dev == ipa_mpm_ctx->md[mhip_idx].mhi_dev) break; } if (mhip_idx >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_DBG("remove_cb for mhip_idx = %d not probed before\n", mhip_idx); return; } IPA_MPM_DBG("remove_cb for mhip_idx = %d", mhip_idx); mutex_lock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); ipa_mpm_ctx->md[mhip_idx].init_complete = false; mutex_unlock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); if (mhip_idx == IPA_MPM_MHIP_CH_ID_0) ipa3_uc_send_disable_flow_control(); ipa_mpm_mhip_shutdown(mhip_idx); atomic_dec(&ipa_mpm_ctx->probe_cnt); if (atomic_read(&ipa_mpm_ctx->probe_cnt) == 0) { /* Last probe done, reset Everything here */ ipa_mpm_ctx->mhi_parent_dev = NULL; ipa_mpm_ctx->carved_smmu_cb.next_addr = ipa_mpm_ctx->carved_smmu_cb.va_start; atomic_set(&ipa_mpm_ctx->pcie_clk_total_cnt, 0); for (mhip_idx = 0; mhip_idx < IPA_MPM_MHIP_CH_ID_MAX; mhip_idx++) { atomic_set( &ipa_mpm_ctx->md[mhip_idx].clk_cnt.pcie_clk_cnt, 0); } } IPA_MPM_FUNC_EXIT(); } static void ipa_mpm_mhi_status_cb(struct mhi_device *mhi_dev, enum MHI_CB mhi_cb) { int mhip_idx; enum mhip_status_type status; IPA_MPM_DBG("%d\n", mhi_cb); for (mhip_idx = 0; mhip_idx < IPA_MPM_MHIP_CH_ID_MAX; mhip_idx++) { if (mhi_dev == ipa_mpm_ctx->md[mhip_idx].mhi_dev) break; } if (mhip_idx >= IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_DBG("ignoring secondary callbacks\n"); return; } mutex_lock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); if (!ipa_mpm_ctx->md[mhip_idx].init_complete) { /* * SSR might be in progress, dont have to vote/unvote for * IPA clocks as it will be taken care in remove_cb/subsequent * probe. */ IPA_MPM_DBG("SSR in progress, return\n"); mutex_unlock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); return; } mutex_unlock(&ipa_mpm_ctx->md[mhip_idx].mhi_mutex); switch (mhi_cb) { case MHI_CB_IDLE: break; case MHI_CB_LPM_ENTER: if (!ipa_mpm_ctx->md[mhip_idx].in_lpm) { status = ipa_mpm_start_stop_mhip_chan( IPA_MPM_MHIP_CHAN_DL, mhip_idx, MPM_MHIP_STOP); IPA_MPM_DBG("status = %d\n", status); ipa_mpm_vote_unvote_ipa_clk(CLK_OFF, mhip_idx); ipa_mpm_ctx->md[mhip_idx].in_lpm = true; } else { IPA_MPM_DBG("Already in lpm\n"); } break; case MHI_CB_LPM_EXIT: if (ipa_mpm_ctx->md[mhip_idx].in_lpm) { ipa_mpm_vote_unvote_ipa_clk(CLK_ON, mhip_idx); status = ipa_mpm_start_stop_mhip_chan( IPA_MPM_MHIP_CHAN_DL, mhip_idx, MPM_MHIP_START); IPA_MPM_DBG("status = %d\n", status); ipa_mpm_ctx->md[mhip_idx].in_lpm = false; } else { IPA_MPM_DBG("Already out of lpm\n"); } break; case MHI_CB_EE_RDDM: case MHI_CB_PENDING_DATA: case MHI_CB_SYS_ERROR: case MHI_CB_FATAL_ERROR: case MHI_CB_EE_MISSION_MODE: case MHI_CB_DTR_SIGNAL: IPA_MPM_ERR("unexpected event %d\n", mhi_cb); break; } } static void ipa_mpm_mhip_map_prot(enum ipa_usb_teth_prot prot, enum ipa_mpm_mhip_client_type *mhip_client) { switch (prot) { case IPA_USB_RNDIS: *mhip_client = IPA_MPM_MHIP_TETH; break; case IPA_USB_RMNET: *mhip_client = IPA_MPM_MHIP_USB_RMNET; break; case IPA_USB_DIAG: *mhip_client = IPA_MPM_MHIP_USB_DPL; break; default: *mhip_client = IPA_MPM_MHIP_NONE; break; } IPA_MPM_DBG("Mapped xdci prot %d -> MHIP prot %d\n", prot, *mhip_client); } int ipa_mpm_mhip_xdci_pipe_enable(enum ipa_usb_teth_prot xdci_teth_prot) { int probe_id = IPA_MPM_MHIP_CH_ID_MAX; int i; enum ipa_mpm_mhip_client_type mhip_client; enum mhip_status_type status; int pipe_idx; bool is_acted = true; int ret = 0; if (ipa_mpm_ctx == NULL) { IPA_MPM_ERR("MPM not platform probed yet, returning ..\n"); return 0; } ipa_mpm_mhip_map_prot(xdci_teth_prot, &mhip_client); for (i = 0; i < IPA_MPM_MHIP_CH_ID_MAX; i++) { if (ipa_mpm_pipes[i].mhip_client == mhip_client) { probe_id = i; break; } } if (probe_id == IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("Unknown probe_id\n"); return 0; } IPA_MPM_DBG("Connect xdci prot %d -> mhip_client = %d probe_id = %d\n", xdci_teth_prot, mhip_client, probe_id); ipa_mpm_ctx->md[probe_id].mhip_client = mhip_client; ret = ipa_mpm_vote_unvote_pcie_clk(CLK_ON, probe_id, false, &is_acted); if (ret) { IPA_MPM_ERR("Error cloking on PCIe clk, err = %d\n", ret); return ret; } /* * Make sure to start Device side channels before * starting Host side UL channels. This is to make * sure device side access host side IPA only when * Host IPA gets voted. */ ret = ipa_mpm_start_stop_remote_mhip_chan(probe_id, MPM_MHIP_START, false); if (ret) { /* * This can fail only when modem is in SSR state. * Eventually there would be a remove callback, * so return a failure. Dont have to unvote PCIE here. */ IPA_MPM_ERR("MHIP remote chan start fail = %d\n", ret); return ret; } IPA_MPM_DBG("MHIP remote channel start success\n"); switch (mhip_client) { case IPA_MPM_MHIP_USB_RMNET: ipa_mpm_set_dma_mode(IPA_CLIENT_USB_PROD, IPA_CLIENT_MHI_PRIME_RMNET_CONS, false); break; case IPA_MPM_MHIP_USB_DPL: IPA_MPM_DBG("connecting DPL prot %d\n", mhip_client); ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_CONNECTED); atomic_set(&ipa_mpm_ctx->adpl_over_usb_available, 1); return 0; default: IPA_MPM_DBG("mhip_client = %d not processed\n", mhip_client); if (is_acted) { ret = ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); if (ret) { IPA_MPM_ERR("Err unvoting PCIe clk, err = %d\n", ret); return ret; } } return 0; } if (mhip_client != IPA_MPM_MHIP_USB_DPL) /* Start UL MHIP channel for offloading teth connection */ status = ipa_mpm_start_stop_mhip_chan(IPA_MPM_MHIP_CHAN_UL, probe_id, MPM_MHIP_START); switch (status) { case MHIP_STATUS_SUCCESS: case MHIP_STATUS_NO_OP: ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_CONNECTED); pipe_idx = ipa3_get_ep_mapping(IPA_CLIENT_USB_PROD); /* Lift the delay for rmnet USB prod pipe */ ipa3_xdci_ep_delay_rm(pipe_idx); if (status == MHIP_STATUS_NO_OP && is_acted) { /* Channels already have been started, * we can devote for pcie clocks */ ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); } break; case MHIP_STATUS_EP_NOT_READY: if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_INPROGRESS); break; case MHIP_STATUS_FAIL: case MHIP_STATUS_BAD_STATE: case MHIP_STATUS_EP_NOT_FOUND: IPA_MPM_ERR("UL chan cant be started err =%d\n", status); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); ret = -EFAULT; break; default: if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); IPA_MPM_ERR("Err not found\n"); break; } return ret; } int ipa_mpm_mhip_xdci_pipe_disable(enum ipa_usb_teth_prot xdci_teth_prot) { int probe_id = IPA_MPM_MHIP_CH_ID_MAX; int i; enum ipa_mpm_mhip_client_type mhip_client; enum mhip_status_type status; int ret = 0; bool is_acted = true; if (ipa_mpm_ctx == NULL) { IPA_MPM_ERR("MPM not platform probed, returning ..\n"); return 0; } ipa_mpm_mhip_map_prot(xdci_teth_prot, &mhip_client); for (i = 0; i < IPA_MPM_MHIP_CH_ID_MAX; i++) { if (ipa_mpm_pipes[i].mhip_client == mhip_client) { probe_id = i; break; } } if (probe_id == IPA_MPM_MHIP_CH_ID_MAX) { IPA_MPM_ERR("Invalid probe_id\n"); return 0; } IPA_MPM_ERR("xdci disconnect prot %d mhip_client = %d probe_id = %d\n", xdci_teth_prot, mhip_client, probe_id); /* * Make sure to stop Device side channels before * stopping Host side UL channels. This is to make * sure device side doesn't access host side IPA if * Host IPA gets unvoted. */ ret = ipa_mpm_start_stop_remote_mhip_chan(probe_id, MPM_MHIP_STOP, false); if (ret) { /* * This can fail only when modem is in SSR state. * Eventually there would be a remove callback, * so return a failure. */ IPA_MPM_ERR("MHIP remote chan stop fail = %d\n", ret); return ret; } IPA_MPM_DBG("MHIP remote channels are stopped\n"); switch (mhip_client) { case IPA_MPM_MHIP_USB_RMNET: ret = ipa_mpm_set_dma_mode(IPA_CLIENT_USB_PROD, IPA_CLIENT_APPS_LAN_CONS, true); if (ret) { IPA_MPM_ERR("failed to reset dma mode\n"); return ret; } break; case IPA_MPM_MHIP_TETH: IPA_MPM_DBG("Rndis Disconnect, wait for wan_state ioctl\n"); return 0; case IPA_MPM_MHIP_USB_DPL: IPA_MPM_DBG("Teth Disconnecting for DPL\n"); /* change teth state only if ODL is disconnected */ if (!ipa3_is_odl_connected()) { ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_INIT); ipa_mpm_ctx->md[probe_id].mhip_client = IPA_MPM_MHIP_NONE; } ret = ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); if (ret) IPA_MPM_ERR("Error clking off PCIe clk err%d\n", ret); atomic_set(&ipa_mpm_ctx->adpl_over_usb_available, 0); return ret; default: IPA_MPM_ERR("mhip_client = %d not supported\n", mhip_client); return 0; } status = ipa_mpm_start_stop_mhip_chan(IPA_MPM_MHIP_CHAN_UL, probe_id, MPM_MHIP_STOP); switch (status) { case MHIP_STATUS_SUCCESS: case MHIP_STATUS_NO_OP: case MHIP_STATUS_EP_NOT_READY: ipa_mpm_change_teth_state(probe_id, IPA_MPM_TETH_INIT); break; case MHIP_STATUS_FAIL: case MHIP_STATUS_BAD_STATE: case MHIP_STATUS_EP_NOT_FOUND: IPA_MPM_ERR("UL chan cant be started err =%d\n", status); ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); return -EFAULT; default: IPA_MPM_ERR("Err not found\n"); break; } ret = ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, probe_id, false, &is_acted); if (ret) { IPA_MPM_ERR("Error cloking off PCIe clk, err = %d\n", ret); return ret; } ipa_mpm_ctx->md[probe_id].mhip_client = IPA_MPM_MHIP_NONE; return ret; } static int ipa_mpm_populate_smmu_info(struct platform_device *pdev) { struct ipa_smmu_in_params smmu_in; struct ipa_smmu_out_params smmu_out; u32 carved_iova_ap_mapping[2]; struct ipa_smmu_cb_ctx *cb; struct ipa_smmu_cb_ctx *ap_cb = ipa3_get_smmu_ctx(IPA_SMMU_CB_AP); int ret = 0; if (ipa_mpm_ctx->carved_smmu_cb.valid) { IPA_MPM_DBG("SMMU Context allocated, returning ..\n"); return ret; } cb = &ipa_mpm_ctx->carved_smmu_cb; /* get IPA SMMU enabled status */ smmu_in.smmu_client = IPA_SMMU_AP_CLIENT; if (ipa_get_smmu_params(&smmu_in, &smmu_out)) ipa_mpm_ctx->dev_info.ipa_smmu_enabled = false; else ipa_mpm_ctx->dev_info.ipa_smmu_enabled = smmu_out.smmu_enable; /* get cache_coherent enable or not */ ipa_mpm_ctx->dev_info.is_cache_coherent = ap_cb->is_cache_coherent; if (of_property_read_u32_array(pdev->dev.of_node, "qcom,iova-mapping", carved_iova_ap_mapping, 2)) { IPA_MPM_ERR("failed to read of_node %s\n", "qcom,mpm-iova-mapping"); return -EINVAL; } ipa_mpm_ctx->dev_info.pcie_smmu_enabled = true; if (ipa_mpm_ctx->dev_info.ipa_smmu_enabled != ipa_mpm_ctx->dev_info.pcie_smmu_enabled) { IPA_MPM_DBG("PCIE/IPA SMMU config mismatch\n"); return -EINVAL; } cb->va_start = carved_iova_ap_mapping[0]; cb->va_size = carved_iova_ap_mapping[1]; cb->va_end = cb->va_start + cb->va_size; if (cb->va_end >= ap_cb->va_start) { IPA_MPM_ERR("MPM iommu and AP overlap addr 0x%lx\n", cb->va_start); ipa_assert(); return -EFAULT; } cb->dev = ipa_mpm_ctx->dev_info.dev; cb->valid = true; cb->next_addr = cb->va_start; if (dma_set_mask_and_coherent(ipa_mpm_ctx->dev_info.dev, DMA_BIT_MASK(64))) { IPA_MPM_ERR("setting DMA mask to 64 failed.\n"); return -EINVAL; } return ret; } static int ipa_mpm_probe(struct platform_device *pdev) { int ret = 0; int i = 0; int idx = 0; IPA_MPM_FUNC_ENTRY(); if (ipa_mpm_ctx) { IPA_MPM_DBG("MPM is already probed, returning\n"); return 0; } ret = ipa_register_ipa_ready_cb(ipa_mpm_ipa3_ready_cb, (void *)pdev); /* * If we received -EEXIST, IPA has initialized. So we need * to continue the probing process. */ if (!ret) { IPA_MPM_DBG("IPA not ready yet, registering callback\n"); return ret; } IPA_MPM_DBG("IPA is ready, continue with probe\n"); ipa_mpm_ctx = kzalloc(sizeof(*ipa_mpm_ctx), GFP_KERNEL); if (!ipa_mpm_ctx) return -ENOMEM; for (i = 0; i < IPA_MPM_MHIP_CH_ID_MAX; i++) { mutex_init(&ipa_mpm_ctx->md[i].mutex); mutex_init(&ipa_mpm_ctx->md[i].mhi_mutex); } ipa_mpm_ctx->dev_info.pdev = pdev; ipa_mpm_ctx->dev_info.dev = &pdev->dev; ipa_mpm_init_mhip_channel_info(); if (of_property_read_u32(pdev->dev.of_node, "qcom,mhi-chdb-base", &ipa_mpm_ctx->dev_info.chdb_base)) { IPA_MPM_ERR("failed to read qcom,mhi-chdb-base\n"); goto fail_probe; } IPA_MPM_DBG("chdb-base=0x%x\n", ipa_mpm_ctx->dev_info.chdb_base); if (of_property_read_u32(pdev->dev.of_node, "qcom,mhi-erdb-base", &ipa_mpm_ctx->dev_info.erdb_base)) { IPA_MPM_ERR("failed to read qcom,mhi-erdb-base\n"); goto fail_probe; } IPA_MPM_DBG("erdb-base=0x%x\n", ipa_mpm_ctx->dev_info.erdb_base); ret = ipa_mpm_populate_smmu_info(pdev); if (ret) { IPA_MPM_DBG("SMMU Config failed\n"); goto fail_probe; } atomic_set(&ipa_mpm_ctx->ipa_clk_total_cnt, 0); atomic_set(&ipa_mpm_ctx->pcie_clk_total_cnt, 0); atomic_set(&ipa_mpm_ctx->flow_ctrl_mask, 0); for (idx = 0; idx < IPA_MPM_MHIP_CH_ID_MAX; idx++) { ipa_mpm_ctx->md[idx].ul_prod.gsi_state = GSI_INIT; ipa_mpm_ctx->md[idx].dl_cons.gsi_state = GSI_INIT; atomic_set(&ipa_mpm_ctx->md[idx].clk_cnt.ipa_clk_cnt, 0); atomic_set(&ipa_mpm_ctx->md[idx].clk_cnt.pcie_clk_cnt, 0); } ret = mhi_driver_register(&mhi_driver); if (ret) { IPA_MPM_ERR("mhi_driver_register failed %d\n", ret); goto fail_probe; } IPA_MPM_FUNC_EXIT(); return 0; fail_probe: kfree(ipa_mpm_ctx); ipa_mpm_ctx = NULL; return -EFAULT; } static int ipa_mpm_remove(struct platform_device *pdev) { IPA_MPM_FUNC_ENTRY(); mhi_driver_unregister(&mhi_driver); IPA_MPM_FUNC_EXIT(); return 0; } static const struct of_device_id ipa_mpm_dt_match[] = { { .compatible = "qcom,ipa-mpm" }, {}, }; MODULE_DEVICE_TABLE(of, ipa_mpm_dt_match); static struct platform_driver ipa_ipa_mpm_driver = { .driver = { .name = "ipa_mpm", .of_match_table = ipa_mpm_dt_match, }, .probe = ipa_mpm_probe, .remove = ipa_mpm_remove, }; /** * ipa_mpm_init() - Registers ipa_mpm as a platform device for a APQ * * This function is called after bootup for APQ device. * ipa_mpm will register itself as a platform device, and probe * function will get called. * * Return: None */ static int __init ipa_mpm_init(void) { IPA_MPM_DBG("register ipa_mpm platform device\n"); return platform_driver_register(&ipa_ipa_mpm_driver); } static void __exit ipa_mpm_exit(void) { IPA_MPM_DBG("unregister ipa_mpm platform device\n"); platform_driver_unregister(&ipa_ipa_mpm_driver); } /** * ipa3_is_mhip_offload_enabled() - check if IPA MPM module was initialized * successfully. If it is initialized, MHIP is enabled for teth * * Return value: 1 for yes; 0 for no */ int ipa3_is_mhip_offload_enabled(void) { if (ipa_mpm_ctx == NULL) return 0; else return 1; } int ipa_mpm_panic_handler(char *buf, int size) { int i; int cnt = 0; cnt = scnprintf(buf, size, "\n---- MHIP Active Clients Table ----\n"); cnt += scnprintf(buf + cnt, size - cnt, "Total PCIe active clients count: %d\n", atomic_read(&ipa_mpm_ctx->pcie_clk_total_cnt)); cnt += scnprintf(buf + cnt, size - cnt, "Total IPA active clients count: %d\n", atomic_read(&ipa_mpm_ctx->ipa_clk_total_cnt)); for (i = 0; i < IPA_MPM_MHIP_CH_ID_MAX; i++) { cnt += scnprintf(buf + cnt, size - cnt, "client id: %d ipa vote cnt: %d pcie vote cnt\n", i, atomic_read(&ipa_mpm_ctx->md[i].clk_cnt.ipa_clk_cnt), atomic_read(&ipa_mpm_ctx->md[i].clk_cnt.pcie_clk_cnt)); } return cnt; } /** * ipa3_get_mhip_gsi_stats() - Query MHIP gsi stats from uc * @stats: [inout] stats blob from client populated by driver * * Returns: 0 on success, negative on failure * * @note Cannot be called from atomic context * */ int ipa3_get_mhip_gsi_stats(struct ipa_uc_dbg_ring_stats *stats) { int i; if (!ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio) { IPAERR("bad parms NULL mhip_gsi_stats_mmio\n"); return -EINVAL; } IPA_ACTIVE_CLIENTS_INC_SIMPLE(); for (i = 0; i < MAX_MHIP_CHANNELS; i++) { stats->ring[i].ringFull = ioread32( ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio + i * IPA3_UC_DEBUG_STATS_OFF + IPA3_UC_DEBUG_STATS_RINGFULL_OFF); stats->ring[i].ringEmpty = ioread32( ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio + i * IPA3_UC_DEBUG_STATS_OFF + IPA3_UC_DEBUG_STATS_RINGEMPTY_OFF); stats->ring[i].ringUsageHigh = ioread32( ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio + i * IPA3_UC_DEBUG_STATS_OFF + IPA3_UC_DEBUG_STATS_RINGUSAGEHIGH_OFF); stats->ring[i].ringUsageLow = ioread32( ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio + i * IPA3_UC_DEBUG_STATS_OFF + IPA3_UC_DEBUG_STATS_RINGUSAGELOW_OFF); stats->ring[i].RingUtilCount = ioread32( ipa3_ctx->mhip_ctx.dbg_stats.uc_dbg_stats_mmio + i * IPA3_UC_DEBUG_STATS_OFF + IPA3_UC_DEBUG_STATS_RINGUTILCOUNT_OFF); } IPA_ACTIVE_CLIENTS_DEC_SIMPLE(); return 0; } /** * ipa3_mpm_enable_adpl_over_odl() - Enable or disable ADPL over ODL * @enable: true for enable, false for disable * * Returns: 0 on success, negative on failure * */ int ipa3_mpm_enable_adpl_over_odl(bool enable) { int ret; bool is_acted = true; IPA_MPM_FUNC_ENTRY(); if (!ipa3_is_mhip_offload_enabled()) { IPA_MPM_ERR("mpm ctx is NULL\n"); return -EPERM; } if (enable) { /* inc clk count and set DMA to ODL */ IPA_MPM_DBG("mpm enabling ADPL over ODL\n"); ret = ipa_mpm_vote_unvote_pcie_clk(CLK_ON, IPA_MPM_MHIP_CH_ID_2, false, &is_acted); if (ret) { IPA_MPM_ERR("Err %d cloking on PCIe clk\n", ret); return ret; } ret = ipa_mpm_set_dma_mode(IPA_CLIENT_MHI_PRIME_DPL_PROD, IPA_CLIENT_ODL_DPL_CONS, false); if (ret) { IPA_MPM_ERR("MPM failed to set dma mode to ODL\n"); if (is_acted) ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, IPA_MPM_MHIP_CH_ID_2, false, &is_acted); return ret; } ipa_mpm_change_teth_state(IPA_MPM_MHIP_CH_ID_2, IPA_MPM_TETH_CONNECTED); } else { /* dec clk count and set DMA to USB */ IPA_MPM_DBG("mpm disabling ADPL over ODL\n"); ret = ipa_mpm_vote_unvote_pcie_clk(CLK_OFF, IPA_MPM_MHIP_CH_ID_2, false, &is_acted); if (ret) { IPA_MPM_ERR("Err %d cloking off PCIe clk\n", ret); return ret; } ret = ipa_mpm_set_dma_mode(IPA_CLIENT_MHI_PRIME_DPL_PROD, IPA_CLIENT_USB_DPL_CONS, false); if (ret) { IPA_MPM_ERR("MPM failed to set dma mode to USB\n"); if (ipa_mpm_vote_unvote_pcie_clk(CLK_ON, IPA_MPM_MHIP_CH_ID_2, false, &is_acted)) IPA_MPM_ERR("Err clocking on pcie\n"); return ret; } /* If USB is not available then reset teth state */ if (atomic_read(&ipa_mpm_ctx->adpl_over_usb_available)) { IPA_MPM_DBG("mpm enabling ADPL over USB\n"); } else { ipa_mpm_change_teth_state(IPA_MPM_MHIP_CH_ID_2, IPA_MPM_TETH_INIT); IPA_MPM_DBG("USB disconnected. ADPL on standby\n"); } } IPA_MPM_FUNC_EXIT(); return ret; } late_initcall(ipa_mpm_init); module_exit(ipa_mpm_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MHI Proxy Manager Driver");