| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. | |
| * Copyright (C) 2019-2024 Linaro Ltd. | |
| */ | |
| #include <linux/dma-mapping.h> | |
| #include <linux/io.h> | |
| #include <linux/iommu.h> | |
| #include <linux/of_address.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/types.h> | |
| #include <linux/soc/qcom/smem.h> | |
| #include "gsi_trans.h" | |
| #include "ipa.h" | |
| #include "ipa_cmd.h" | |
| #include "ipa_data.h" | |
| #include "ipa_mem.h" | |
| #include "ipa_reg.h" | |
| #include "ipa_table.h" | |
| /* "Canary" value placed between memory regions to detect overflow */ | |
| #define IPA_MEM_CANARY_VAL cpu_to_le32(0xdeadbeef) | |
| /* SMEM host id representing the modem. */ | |
| #define QCOM_SMEM_HOST_MODEM 1 | |
| #define SMEM_IPA_FILTER_TABLE 497 | |
| const struct ipa_mem *ipa_mem_find(struct ipa *ipa, enum ipa_mem_id mem_id) | |
| { | |
| u32 i; | |
| for (i = 0; i < ipa->mem_count; i++) { | |
| const struct ipa_mem *mem = &ipa->mem[i]; | |
| if (mem->id == mem_id) | |
| return mem; | |
| } | |
| return NULL; | |
| } | |
| /* Add an immediate command to a transaction that zeroes a memory region */ | |
| static void | |
| ipa_mem_zero_region_add(struct gsi_trans *trans, enum ipa_mem_id mem_id) | |
| { | |
| struct ipa *ipa = container_of(trans->gsi, struct ipa, gsi); | |
| const struct ipa_mem *mem = ipa_mem_find(ipa, mem_id); | |
| dma_addr_t addr = ipa->zero_addr; | |
| if (!mem->size) | |
| return; | |
| ipa_cmd_dma_shared_mem_add(trans, mem->offset, mem->size, addr, true); | |
| } | |
| /** | |
| * ipa_mem_setup() - Set up IPA AP and modem shared memory areas | |
| * @ipa: IPA pointer | |
| * | |
| * Set up the shared memory regions in IPA local memory. This involves | |
| * zero-filling memory regions, and in the case of header memory, telling | |
| * the IPA where it's located. | |
| * | |
| * This function performs the initial setup of this memory. If the modem | |
| * crashes, its regions are re-zeroed in ipa_mem_zero_modem(). | |
| * | |
| * The AP informs the modem where its portions of memory are located | |
| * in a QMI exchange that occurs at modem startup. | |
| * | |
| * There is no need for a matching ipa_mem_teardown() function. | |
| * | |
| * Return: 0 if successful, or a negative error code | |
| */ | |
| int ipa_mem_setup(struct ipa *ipa) | |
| { | |
| dma_addr_t addr = ipa->zero_addr; | |
| const struct ipa_mem *mem; | |
| struct gsi_trans *trans; | |
| const struct reg *reg; | |
| u32 offset; | |
| u16 size; | |
| u32 val; | |
| /* Get a transaction to define the header memory region and to zero | |
| * the processing context and modem memory regions. | |
| */ | |
| trans = ipa_cmd_trans_alloc(ipa, 4); | |
| if (!trans) { | |
| dev_err(ipa->dev, "no transaction for memory setup\n"); | |
| return -EBUSY; | |
| } | |
| /* Initialize IPA-local header memory. The AP header region, if | |
| * present, is contiguous with and follows the modem header region, | |
| * and they are initialized together. | |
| */ | |
| mem = ipa_mem_find(ipa, IPA_MEM_MODEM_HEADER); | |
| offset = mem->offset; | |
| size = mem->size; | |
| mem = ipa_mem_find(ipa, IPA_MEM_AP_HEADER); | |
| if (mem) | |
| size += mem->size; | |
| ipa_cmd_hdr_init_local_add(trans, offset, size, addr); | |
| ipa_mem_zero_region_add(trans, IPA_MEM_MODEM_PROC_CTX); | |
| ipa_mem_zero_region_add(trans, IPA_MEM_AP_PROC_CTX); | |
| ipa_mem_zero_region_add(trans, IPA_MEM_MODEM); | |
| gsi_trans_commit_wait(trans); | |
| /* Tell the hardware where the processing context area is located */ | |
| mem = ipa_mem_find(ipa, IPA_MEM_MODEM_PROC_CTX); | |
| offset = ipa->mem_offset + mem->offset; | |
| reg = ipa_reg(ipa, LOCAL_PKT_PROC_CNTXT); | |
| val = reg_encode(reg, IPA_BASE_ADDR, offset); | |
| iowrite32(val, ipa->reg_virt + reg_offset(reg)); | |
| return 0; | |
| } | |
| /* Is the given memory region ID is valid for the current IPA version? */ | |
| static bool ipa_mem_id_valid(struct ipa *ipa, enum ipa_mem_id mem_id) | |
| { | |
| enum ipa_version version = ipa->version; | |
| switch (mem_id) { | |
| case IPA_MEM_UC_SHARED: | |
| case IPA_MEM_UC_INFO: | |
| case IPA_MEM_V4_FILTER_HASHED: | |
| case IPA_MEM_V4_FILTER: | |
| case IPA_MEM_V6_FILTER_HASHED: | |
| case IPA_MEM_V6_FILTER: | |
| case IPA_MEM_V4_ROUTE_HASHED: | |
| case IPA_MEM_V4_ROUTE: | |
| case IPA_MEM_V6_ROUTE_HASHED: | |
| case IPA_MEM_V6_ROUTE: | |
| case IPA_MEM_MODEM_HEADER: | |
| case IPA_MEM_AP_HEADER: | |
| case IPA_MEM_MODEM_PROC_CTX: | |
| case IPA_MEM_AP_PROC_CTX: | |
| case IPA_MEM_MODEM: | |
| case IPA_MEM_UC_EVENT_RING: | |
| case IPA_MEM_PDN_CONFIG: | |
| case IPA_MEM_STATS_QUOTA_MODEM: | |
| case IPA_MEM_STATS_QUOTA_AP: | |
| case IPA_MEM_END_MARKER: /* pseudo region */ | |
| break; | |
| case IPA_MEM_STATS_TETHERING: | |
| case IPA_MEM_STATS_DROP: | |
| if (version < IPA_VERSION_4_0) | |
| return false; | |
| break; | |
| case IPA_MEM_STATS_V4_FILTER: | |
| case IPA_MEM_STATS_V6_FILTER: | |
| case IPA_MEM_STATS_V4_ROUTE: | |
| case IPA_MEM_STATS_V6_ROUTE: | |
| if (version < IPA_VERSION_4_0 || version > IPA_VERSION_4_2) | |
| return false; | |
| break; | |
| case IPA_MEM_AP_V4_FILTER: | |
| case IPA_MEM_AP_V6_FILTER: | |
| if (version < IPA_VERSION_5_0) | |
| return false; | |
| break; | |
| case IPA_MEM_NAT_TABLE: | |
| case IPA_MEM_STATS_FILTER_ROUTE: | |
| if (version < IPA_VERSION_4_5) | |
| return false; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* Must the given memory region be present in the configuration? */ | |
| static bool ipa_mem_id_required(struct ipa *ipa, enum ipa_mem_id mem_id) | |
| { | |
| switch (mem_id) { | |
| case IPA_MEM_UC_SHARED: | |
| case IPA_MEM_UC_INFO: | |
| case IPA_MEM_V4_FILTER_HASHED: | |
| case IPA_MEM_V4_FILTER: | |
| case IPA_MEM_V6_FILTER_HASHED: | |
| case IPA_MEM_V6_FILTER: | |
| case IPA_MEM_V4_ROUTE_HASHED: | |
| case IPA_MEM_V4_ROUTE: | |
| case IPA_MEM_V6_ROUTE_HASHED: | |
| case IPA_MEM_V6_ROUTE: | |
| case IPA_MEM_MODEM_HEADER: | |
| case IPA_MEM_MODEM_PROC_CTX: | |
| case IPA_MEM_AP_PROC_CTX: | |
| case IPA_MEM_MODEM: | |
| return true; | |
| case IPA_MEM_PDN_CONFIG: | |
| case IPA_MEM_STATS_QUOTA_MODEM: | |
| return ipa->version >= IPA_VERSION_4_0; | |
| case IPA_MEM_STATS_TETHERING: | |
| return ipa->version >= IPA_VERSION_4_0 && | |
| ipa->version != IPA_VERSION_5_0; | |
| default: | |
| return false; /* Anything else is optional */ | |
| } | |
| } | |
| static bool ipa_mem_valid_one(struct ipa *ipa, const struct ipa_mem *mem) | |
| { | |
| enum ipa_mem_id mem_id = mem->id; | |
| struct device *dev = ipa->dev; | |
| u16 size_multiple; | |
| /* Make sure the memory region is valid for this version of IPA */ | |
| if (!ipa_mem_id_valid(ipa, mem_id)) { | |
| dev_err(dev, "region id %u not valid\n", mem_id); | |
| return false; | |
| } | |
| if (!mem->size && !mem->canary_count) { | |
| dev_err(dev, "empty memory region %u\n", mem_id); | |
| return false; | |
| } | |
| /* Other than modem memory, sizes must be a multiple of 8 */ | |
| size_multiple = mem_id == IPA_MEM_MODEM ? 4 : 8; | |
| if (mem->size % size_multiple) | |
| dev_err(dev, "region %u size not a multiple of %u bytes\n", | |
| mem_id, size_multiple); | |
| else if (mem->offset % 8) | |
| dev_err(dev, "region %u offset not 8-byte aligned\n", mem_id); | |
| else if (mem->offset < mem->canary_count * sizeof(__le32)) | |
| dev_err(dev, "region %u offset too small for %hu canaries\n", | |
| mem_id, mem->canary_count); | |
| else if (mem_id == IPA_MEM_END_MARKER && mem->size) | |
| dev_err(dev, "non-zero end marker region size\n"); | |
| else | |
| return true; | |
| return false; | |
| } | |
| /* Verify each defined memory region is valid. */ | |
| static bool ipa_mem_valid(struct ipa *ipa, const struct ipa_mem_data *mem_data) | |
| { | |
| DECLARE_BITMAP(regions, IPA_MEM_COUNT) = { }; | |
| struct device *dev = ipa->dev; | |
| enum ipa_mem_id mem_id; | |
| u32 i; | |
| if (mem_data->local_count > IPA_MEM_COUNT) { | |
| dev_err(dev, "too many memory regions (%u > %u)\n", | |
| mem_data->local_count, IPA_MEM_COUNT); | |
| return false; | |
| } | |
| for (i = 0; i < mem_data->local_count; i++) { | |
| const struct ipa_mem *mem = &mem_data->local[i]; | |
| if (__test_and_set_bit(mem->id, regions)) { | |
| dev_err(dev, "duplicate memory region %u\n", mem->id); | |
| return false; | |
| } | |
| /* Defined regions have non-zero size and/or canary count */ | |
| if (!ipa_mem_valid_one(ipa, mem)) | |
| return false; | |
| } | |
| /* Now see if any required regions are not defined */ | |
| for_each_clear_bit(mem_id, regions, IPA_MEM_COUNT) { | |
| if (ipa_mem_id_required(ipa, mem_id)) | |
| dev_err(dev, "required memory region %u missing\n", | |
| mem_id); | |
| } | |
| return true; | |
| } | |
| /* Do all memory regions fit within the IPA local memory? */ | |
| static bool ipa_mem_size_valid(struct ipa *ipa) | |
| { | |
| struct device *dev = ipa->dev; | |
| u32 limit = ipa->mem_size; | |
| u32 i; | |
| for (i = 0; i < ipa->mem_count; i++) { | |
| const struct ipa_mem *mem = &ipa->mem[i]; | |
| if (mem->offset + mem->size <= limit) | |
| continue; | |
| dev_err(dev, "region %u ends beyond memory limit (0x%08x)\n", | |
| mem->id, limit); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * ipa_mem_config() - Configure IPA shared memory | |
| * @ipa: IPA pointer | |
| * | |
| * Return: 0 if successful, or a negative error code | |
| */ | |
| int ipa_mem_config(struct ipa *ipa) | |
| { | |
| struct device *dev = ipa->dev; | |
| const struct ipa_mem *mem; | |
| const struct reg *reg; | |
| dma_addr_t addr; | |
| u32 mem_size; | |
| void *virt; | |
| u32 val; | |
| u32 i; | |
| /* Check the advertised location and size of the shared memory area */ | |
| reg = ipa_reg(ipa, SHARED_MEM_SIZE); | |
| val = ioread32(ipa->reg_virt + reg_offset(reg)); | |
| /* The fields in the register are in 8 byte units */ | |
| ipa->mem_offset = 8 * reg_decode(reg, MEM_BADDR, val); | |
| /* Make sure the end is within the region's mapped space */ | |
| mem_size = 8 * reg_decode(reg, MEM_SIZE, val); | |
| /* If the sizes don't match, issue a warning */ | |
| if (ipa->mem_offset + mem_size < ipa->mem_size) { | |
| dev_warn(dev, "limiting IPA memory size to 0x%08x\n", | |
| mem_size); | |
| ipa->mem_size = mem_size; | |
| } else if (ipa->mem_offset + mem_size > ipa->mem_size) { | |
| dev_dbg(dev, "ignoring larger reported memory size: 0x%08x\n", | |
| mem_size); | |
| } | |
| /* We know our memory size; make sure regions are all in range */ | |
| if (!ipa_mem_size_valid(ipa)) | |
| return -EINVAL; | |
| /* Prealloc DMA memory for zeroing regions */ | |
| virt = dma_alloc_coherent(dev, IPA_MEM_MAX, &addr, GFP_KERNEL); | |
| if (!virt) | |
| return -ENOMEM; | |
| ipa->zero_addr = addr; | |
| ipa->zero_virt = virt; | |
| ipa->zero_size = IPA_MEM_MAX; | |
| /* For each defined region, write "canary" values in the | |
| * space prior to the region's base address if indicated. | |
| */ | |
| for (i = 0; i < ipa->mem_count; i++) { | |
| u16 canary_count = ipa->mem[i].canary_count; | |
| __le32 *canary; | |
| if (!canary_count) | |
| continue; | |
| /* Write canary values in the space before the region */ | |
| canary = ipa->mem_virt + ipa->mem_offset + ipa->mem[i].offset; | |
| do | |
| *--canary = IPA_MEM_CANARY_VAL; | |
| while (--canary_count); | |
| } | |
| /* Verify the microcontroller ring alignment (if defined) */ | |
| mem = ipa_mem_find(ipa, IPA_MEM_UC_EVENT_RING); | |
| if (mem && mem->offset % 1024) { | |
| dev_err(dev, "microcontroller ring not 1024-byte aligned\n"); | |
| goto err_dma_free; | |
| } | |
| return 0; | |
| err_dma_free: | |
| dma_free_coherent(dev, IPA_MEM_MAX, ipa->zero_virt, ipa->zero_addr); | |
| return -EINVAL; | |
| } | |
| /* Inverse of ipa_mem_config() */ | |
| void ipa_mem_deconfig(struct ipa *ipa) | |
| { | |
| struct device *dev = ipa->dev; | |
| dma_free_coherent(dev, ipa->zero_size, ipa->zero_virt, ipa->zero_addr); | |
| ipa->zero_size = 0; | |
| ipa->zero_virt = NULL; | |
| ipa->zero_addr = 0; | |
| } | |
| /** | |
| * ipa_mem_zero_modem() - Zero IPA-local memory regions owned by the modem | |
| * @ipa: IPA pointer | |
| * | |
| * Zero regions of IPA-local memory used by the modem. These are configured | |
| * (and initially zeroed) by ipa_mem_setup(), but if the modem crashes and | |
| * restarts via SSR we need to re-initialize them. A QMI message tells the | |
| * modem where to find regions of IPA local memory it needs to know about | |
| * (these included). | |
| */ | |
| int ipa_mem_zero_modem(struct ipa *ipa) | |
| { | |
| struct gsi_trans *trans; | |
| /* Get a transaction to zero the modem memory, modem header, | |
| * and modem processing context regions. | |
| */ | |
| trans = ipa_cmd_trans_alloc(ipa, 3); | |
| if (!trans) { | |
| dev_err(ipa->dev, "no transaction to zero modem memory\n"); | |
| return -EBUSY; | |
| } | |
| ipa_mem_zero_region_add(trans, IPA_MEM_MODEM_HEADER); | |
| ipa_mem_zero_region_add(trans, IPA_MEM_MODEM_PROC_CTX); | |
| ipa_mem_zero_region_add(trans, IPA_MEM_MODEM); | |
| gsi_trans_commit_wait(trans); | |
| return 0; | |
| } | |
| /** | |
| * ipa_imem_init() - Initialize IMEM memory used by the IPA | |
| * @ipa: IPA pointer | |
| * @addr: Physical address of the IPA region in IMEM | |
| * @size: Size (bytes) of the IPA region in IMEM | |
| * | |
| * IMEM is a block of shared memory separate from system DRAM, and | |
| * a portion of this memory is available for the IPA to use. The | |
| * modem accesses this memory directly, but the IPA accesses it | |
| * via the IOMMU, using the AP's credentials. | |
| * | |
| * If this region exists (size > 0) we map it for read/write access | |
| * through the IOMMU using the IPA device. | |
| * | |
| * Note: @addr and @size are not guaranteed to be page-aligned. | |
| */ | |
| static int ipa_imem_init(struct ipa *ipa, unsigned long addr, size_t size) | |
| { | |
| struct device *dev = ipa->dev; | |
| struct iommu_domain *domain; | |
| unsigned long iova; | |
| phys_addr_t phys; | |
| int ret; | |
| if (!size) | |
| return 0; /* IMEM memory not used */ | |
| domain = iommu_get_domain_for_dev(dev); | |
| if (!domain) { | |
| dev_err(dev, "no IOMMU domain found for IMEM\n"); | |
| return -EINVAL; | |
| } | |
| /* Align the address down and the size up to page boundaries */ | |
| phys = addr & PAGE_MASK; | |
| size = PAGE_ALIGN(size + addr - phys); | |
| iova = phys; /* We just want a direct mapping */ | |
| ret = iommu_map(domain, iova, phys, size, IOMMU_READ | IOMMU_WRITE, | |
| GFP_KERNEL); | |
| if (ret) | |
| return ret; | |
| ipa->imem_iova = iova; | |
| ipa->imem_size = size; | |
| return 0; | |
| } | |
| static void ipa_imem_exit(struct ipa *ipa) | |
| { | |
| struct device *dev = ipa->dev; | |
| struct iommu_domain *domain; | |
| if (!ipa->imem_size) | |
| return; | |
| domain = iommu_get_domain_for_dev(dev); | |
| if (domain) { | |
| size_t size; | |
| size = iommu_unmap(domain, ipa->imem_iova, ipa->imem_size); | |
| if (size != ipa->imem_size) | |
| dev_warn(dev, "unmapped %zu IMEM bytes, expected %zu\n", | |
| size, ipa->imem_size); | |
| } else { | |
| dev_err(dev, "couldn't get IPA IOMMU domain for IMEM\n"); | |
| } | |
| ipa->imem_size = 0; | |
| ipa->imem_iova = 0; | |
| } | |
| /** | |
| * ipa_smem_init() - Initialize SMEM memory used by the IPA | |
| * @ipa: IPA pointer | |
| * @size: Size (bytes) of SMEM memory region | |
| * | |
| * SMEM is a managed block of shared DRAM, from which numbered "items" | |
| * can be allocated. One item is designated for use by the IPA. | |
| * | |
| * The modem accesses SMEM memory directly, but the IPA accesses it | |
| * via the IOMMU, using the AP's credentials. | |
| * | |
| * If size provided is non-zero, we allocate it and map it for | |
| * access through the IOMMU. | |
| * | |
| * Note: @size and the item address are is not guaranteed to be page-aligned. | |
| */ | |
| static int ipa_smem_init(struct ipa *ipa, size_t size) | |
| { | |
| struct device *dev = ipa->dev; | |
| struct iommu_domain *domain; | |
| unsigned long iova; | |
| phys_addr_t phys; | |
| phys_addr_t addr; | |
| size_t actual; | |
| void *virt; | |
| int ret; | |
| if (!size) | |
| return 0; /* SMEM memory not used */ | |
| /* SMEM is memory shared between the AP and another system entity | |
| * (in this case, the modem). An allocation from SMEM is persistent | |
| * until the AP reboots; there is no way to free an allocated SMEM | |
| * region. Allocation only reserves the space; to use it you need | |
| * to "get" a pointer it (this does not imply reference counting). | |
| * The item might have already been allocated, in which case we | |
| * use it unless the size isn't what we expect. | |
| */ | |
| ret = qcom_smem_alloc(QCOM_SMEM_HOST_MODEM, SMEM_IPA_FILTER_TABLE, size); | |
| if (ret && ret != -EEXIST) { | |
| dev_err(dev, "error %d allocating size %zu SMEM item\n", | |
| ret, size); | |
| return ret; | |
| } | |
| /* Now get the address of the SMEM memory region */ | |
| virt = qcom_smem_get(QCOM_SMEM_HOST_MODEM, SMEM_IPA_FILTER_TABLE, &actual); | |
| if (IS_ERR(virt)) { | |
| ret = PTR_ERR(virt); | |
| dev_err(dev, "error %d getting SMEM item\n", ret); | |
| return ret; | |
| } | |
| /* In case the region was already allocated, verify the size */ | |
| if (ret && actual != size) { | |
| dev_err(dev, "SMEM item has size %zu, expected %zu\n", | |
| actual, size); | |
| return -EINVAL; | |
| } | |
| domain = iommu_get_domain_for_dev(dev); | |
| if (!domain) { | |
| dev_err(dev, "no IOMMU domain found for SMEM\n"); | |
| return -EINVAL; | |
| } | |
| /* Align the address down and the size up to a page boundary */ | |
| addr = qcom_smem_virt_to_phys(virt); | |
| phys = addr & PAGE_MASK; | |
| size = PAGE_ALIGN(size + addr - phys); | |
| iova = phys; /* We just want a direct mapping */ | |
| ret = iommu_map(domain, iova, phys, size, IOMMU_READ | IOMMU_WRITE, | |
| GFP_KERNEL); | |
| if (ret) | |
| return ret; | |
| ipa->smem_iova = iova; | |
| ipa->smem_size = size; | |
| return 0; | |
| } | |
| static void ipa_smem_exit(struct ipa *ipa) | |
| { | |
| struct device *dev = ipa->dev; | |
| struct iommu_domain *domain; | |
| domain = iommu_get_domain_for_dev(dev); | |
| if (domain) { | |
| size_t size; | |
| size = iommu_unmap(domain, ipa->smem_iova, ipa->smem_size); | |
| if (size != ipa->smem_size) | |
| dev_warn(dev, "unmapped %zu SMEM bytes, expected %zu\n", | |
| size, ipa->smem_size); | |
| } else { | |
| dev_err(dev, "couldn't get IPA IOMMU domain for SMEM\n"); | |
| } | |
| ipa->smem_size = 0; | |
| ipa->smem_iova = 0; | |
| } | |
| /* Perform memory region-related initialization */ | |
| int ipa_mem_init(struct ipa *ipa, struct platform_device *pdev, | |
| const struct ipa_mem_data *mem_data) | |
| { | |
| struct device_node *ipa_slice_np; | |
| struct device *dev = &pdev->dev; | |
| u32 imem_base, imem_size; | |
| struct resource *res; | |
| int ret; | |
| /* Make sure the set of defined memory regions is valid */ | |
| if (!ipa_mem_valid(ipa, mem_data)) | |
| return -EINVAL; | |
| ipa->mem_count = mem_data->local_count; | |
| ipa->mem = mem_data->local; | |
| /* Check the route and filter table memory regions */ | |
| if (!ipa_table_mem_valid(ipa, false)) | |
| return -EINVAL; | |
| if (!ipa_table_mem_valid(ipa, true)) | |
| return -EINVAL; | |
| ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); | |
| if (ret) { | |
| dev_err(dev, "error %d setting DMA mask\n", ret); | |
| return ret; | |
| } | |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipa-shared"); | |
| if (!res) { | |
| dev_err(dev, | |
| "DT error getting \"ipa-shared\" memory property\n"); | |
| return -ENODEV; | |
| } | |
| ipa->mem_virt = memremap(res->start, resource_size(res), MEMREMAP_WC); | |
| if (!ipa->mem_virt) { | |
| dev_err(dev, "unable to remap \"ipa-shared\" memory\n"); | |
| return -ENOMEM; | |
| } | |
| ipa->mem_addr = res->start; | |
| ipa->mem_size = resource_size(res); | |
| ipa_slice_np = of_parse_phandle(dev->of_node, "sram", 0); | |
| if (ipa_slice_np) { | |
| struct resource sram_res; | |
| ret = of_address_to_resource(ipa_slice_np, 0, &sram_res); | |
| of_node_put(ipa_slice_np); | |
| if (ret) | |
| goto err_unmap; | |
| imem_base = sram_res.start; | |
| imem_size = resource_size(&sram_res); | |
| } else { | |
| /* Backwards compatibility for DTs lacking | |
| * an explicit reference | |
| */ | |
| imem_base = mem_data->imem_addr; | |
| imem_size = mem_data->imem_size; | |
| } | |
| ret = ipa_imem_init(ipa, imem_base, imem_size); | |
| if (ret) | |
| goto err_unmap; | |
| ret = ipa_smem_init(ipa, mem_data->smem_size); | |
| if (ret) | |
| goto err_imem_exit; | |
| return 0; | |
| err_imem_exit: | |
| ipa_imem_exit(ipa); | |
| err_unmap: | |
| memunmap(ipa->mem_virt); | |
| return ret; | |
| } | |
| /* Inverse of ipa_mem_init() */ | |
| void ipa_mem_exit(struct ipa *ipa) | |
| { | |
| ipa_smem_exit(ipa); | |
| ipa_imem_exit(ipa); | |
| memunmap(ipa->mem_virt); | |
| } |