// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA SoC - Samsung Abox PCI driver * * Copyright (c) 2019 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ /* #define DEBUG */ #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_S2MPU) #include #elif IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU) #include #endif #include "abox_util.h" #include "abox_pci.h" #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU) #if IS_ENABLED(CONFIG_SOC_S5E9925) #define PCIE_IOMMU_CH_NUM 1 #endif #endif static struct reserved_mem *abox_pci_rmem; static struct abox_pci_data *p_abox_pci_data; static int __init abox_pci_rmem_setup(struct reserved_mem *rmem) { pr_info("%s: size=%pa\n", __func__, &rmem->size); abox_pci_rmem = rmem; return 0; } RESERVEDMEM_OF_DECLARE(abox_rmem, "exynos,abox_pci_rmem", abox_pci_rmem_setup); static void *abox_rmem_pci_phys_addr_vmap(phys_addr_t addr_phys, size_t addr_size) { phys_addr_t phys = addr_phys; size_t size = addr_size; unsigned int num_pages = (unsigned int)DIV_ROUND_UP(size, PAGE_SIZE); pgprot_t prot = pgprot_writecombine(PAGE_KERNEL); struct page **pages, **page; void *vaddr = NULL; pages = kcalloc(num_pages, sizeof(pages[0]), GFP_KERNEL); if (!pages) goto out; for (page = pages; (page - pages < num_pages); page++) { *page = phys_to_page(phys); phys += PAGE_SIZE; } vaddr = vmap(pages, num_pages, VM_MAP, prot); kfree(pages); out: return vaddr; } static void *abox_rmem_pci_vmap(struct reserved_mem *rmem) { phys_addr_t phys = rmem->base; size_t size = rmem->size; return abox_rmem_pci_phys_addr_vmap(phys, size); } static int abox_pci_cfg_gpio(struct device *dev, const char *name) { struct abox_pci_data *data = dev_get_drvdata(dev); struct pinctrl_state *pin_state; int ret = 0; dev_info(dev, "%s(%s)\n", __func__, name); pin_state = pinctrl_lookup_state(data->pinctrl, name); if (IS_ERR(pin_state)) { dev_err(dev, "Couldn't find pinctrl %s\n", name); } else { ret = pinctrl_select_state(data->pinctrl, pin_state); if (ret < 0) dev_err(dev, "Unable to configure pinctrl %s\n", name); } return ret; } bool abox_pci_doorbell_paddr_set(phys_addr_t addr) { struct abox_pci_data *data = p_abox_pci_data; if (data == NULL) return false; dev_info(data->dev, "%s\n", __func__); data->pci_doorbell_base_phys = addr + data->pci_doorbell_offset; data->pci_dram_base = devm_ioremap(data->dev_abox, data->pci_doorbell_base_phys, ABOX_PCI_DOORBELL_SIZE); abox_iommu_map(data->dev_abox, IOVA_VSS_PCI_DOORBELL, data->pci_doorbell_base_phys, ABOX_PCI_DOORBELL_SIZE, data->pci_dram_base); return true; } EXPORT_SYMBOL(abox_pci_doorbell_paddr_set); static int samsung_abox_pci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct abox_pci_data *data; struct device_node *np = dev->of_node; int ret = 0; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; platform_set_drvdata(pdev, data); data->dev = dev; p_abox_pci_data = data; data->dev_abox = pdev->dev.parent; if (!data->dev_abox) { dev_err(dev, "Failed to get abox device\n"); return -EPROBE_DEFER; } data->abox_data = dev_get_drvdata(data->dev_abox); if (!abox_pci_rmem) { struct device_node *np_tmp; np_tmp = of_parse_phandle(dev->of_node, "memory-region", 0); if (np_tmp) abox_pci_rmem = of_reserved_mem_lookup(np_tmp); } if (abox_pci_rmem) { data->pci_dram_base = abox_rmem_pci_vmap(abox_pci_rmem); abox_iommu_map(data->dev_abox, IOVA_VSS_PCI, abox_pci_rmem->base, abox_pci_rmem->size, data->pci_dram_base); memset(data->pci_dram_base, 0x0, abox_pci_rmem->size); #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_S2MPU) ret = (int) exynos_set_dev_stage2_ap("hsi2", 0, abox_pci_rmem->base, /* phys ?? */ abox_pci_rmem->size, ATTR_RW); if (ret < 0) dev_err(dev, "Failed to exynos_set_dev_stage2_ap(%d): %d\n", __LINE__, ret); #elif IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU) ret = pcie_iommu_map(abox_pci_rmem->base, abox_pci_rmem->base, abox_pci_rmem->size, 0, PCIE_IOMMU_CH_NUM); if (ret < 0) dev_err(dev, "Failed to pcie_iommu_map(%d): %d\n", __LINE__, ret); #endif } /* HACK: vts mailbox */ ret = of_samsung_property_read_u32(dev, np, "mailbox", &data->abox_pci_mailbox_base); if (ret) { dev_err(dev, "can't get mailbox base for S2MPU\n"); return -EINVAL; } ret = of_samsung_property_read_u32(dev, np, "doorbell_offset", &data->pci_doorbell_offset); if (ret < 0) { data->pci_doorbell_offset = ABOX_PCI_DOORBELL_OFFSET; ret = 0; } #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_S2MPU) dev_info(dev, "S2MPU (%d)\n", data->abox_pci_mailbox_base); ret = (int) exynos_set_dev_stage2_ap("hsi2", 0, data->abox_pci_mailbox_base, SZ_4K, ATTR_RW); if (ret < 0) { dev_err(dev, "Failed to exynos_set_dev_stage2_ap(%d): %d\n", __LINE__, ret); return -EINVAL; } #elif IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU) dev_info(dev, "SYSMMU (%d)\n", data->abox_pci_mailbox_base); ret = pcie_iommu_map(data->abox_pci_mailbox_base, data->abox_pci_mailbox_base, SZ_4K, 0, PCIE_IOMMU_CH_NUM); if (ret < 0) { dev_err(dev, "Failed to pcie_iommu_map(%d): %d\n", __LINE__, ret); return -EINVAL; } #endif data->pinctrl = devm_pinctrl_get(dev); if (IS_ERR(data->pinctrl)) { dev_err(dev, "Couldn't get pins (%li)\n", PTR_ERR(data->pinctrl)); return -EINVAL; } ret = abox_pci_cfg_gpio(data->dev, "pci_on"); if (ret < 0) dev_err(dev, "Failed to turn on pci gpio cfg(%d): %d\n", __LINE__, ret); dev_info(dev, "%s (%d)\n", __func__, __LINE__); dev_info(data->dev_abox, "%s (%d)\n", __func__, __LINE__); return ret; } static int samsung_abox_pci_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; dev_dbg(dev, "%s\n", __func__); return 0; } static const struct of_device_id samsung_abox_pci_match[] = { { .compatible = "samsung,abox-pci", }, {}, }; MODULE_DEVICE_TABLE(of, samsung_abox_pci_match); struct platform_driver samsung_abox_pci_driver = { .probe = samsung_abox_pci_probe, .remove = samsung_abox_pci_remove, .driver = { .name = "abox-pci", .owner = THIS_MODULE, .of_match_table = of_match_ptr(samsung_abox_pci_match), }, };