This patch adds ACPI support for HiSilicon PCIe Host Bridge controller Signed-off-by: Liudongdong <liudongdong3@xxxxxxxxxx> Signed-off-by: Gabriele Paoloni <gabriele.paoloni@xxxxxxxxxx> --- MAINTAINERS | 8 ++ arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/pci.c | 1 + arch/arm64/kernel/pci_acpi_hisi.c | 211 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 arch/arm64/kernel/pci_acpi_hisi.c diff --git a/MAINTAINERS b/MAINTAINERS index 5f46784..b84f359 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7943,6 +7943,14 @@ F: include/linux/pci* F: arch/x86/pci/ F: arch/x86/kernel/quirks.c +PCI ACPI DRIVER FOR HISILICON HIP05 +M: Dongdong Liu <liudongdong3@xxxxxxxxxx> +M: Gabriele Paoloni <gabriele.paoloni@xxxxxxxxxx> +L: linux-pci@xxxxxxxxxxxxxxx +L: linux-arm-kernel@xxxxxxxxxxxxxxxxxxx +S: Maintained +F: arch/arm64/kernel/pci_acpi_hisi.c + PCI DRIVER FOR ARM VERSATILE PLATFORM M: Rob Herring <robh@xxxxxxxxxx> L: linux-pci@xxxxxxxxxxxxxxx diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 22dc9bc..1c89d07 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -36,6 +36,7 @@ arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o arm64-obj-$(CONFIG_PCI) += pci.o arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o arm64-obj-$(CONFIG_ACPI) += acpi.o +arm64-obj-$(CONFIG_ACPI) += pci_acpi_hisi.o obj-y += $(arm64-obj-y) vdso/ obj-m += $(arm64-obj-m) diff --git a/arch/arm64/kernel/pci.c b/arch/arm64/kernel/pci.c index d60edb4..391e743 100644 --- a/arch/arm64/kernel/pci.c +++ b/arch/arm64/kernel/pci.c @@ -236,6 +236,7 @@ static struct acpi_pci_root_ops acpi_pci_root_ops = { * Host Bridge controllers that are non ECAM compliant */ static struct acpi_scan_handler *quirks_array[] = { + &pci_root_hisi_handler, 0 }; diff --git a/arch/arm64/kernel/pci_acpi_hisi.c b/arch/arm64/kernel/pci_acpi_hisi.c new file mode 100644 index 0000000..c528604 --- /dev/null +++ b/arch/arm64/kernel/pci_acpi_hisi.c @@ -0,0 +1,211 @@ +/* + * PCIe host controller driver for HiSilicon Hip05 SoC + * + * Copyright (C) 2015 HiSilicon Co., Ltd. http://www.hisilicon.com + * + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/pci-acpi.h> +#include <linux/acpi.h> +#include <linux/ecam.h> + +#include "pci_quirks.h" + + +#define MAX_PCIE_PORT_NUM 4 +static int acpi_pci_root_hisi_add(struct acpi_device *device, + const struct acpi_device_id *not_used); + +static void acpi_pci_root_hisi_remove(struct acpi_device *device); + + +static const struct acpi_device_id root_hisi_device_ids[] = { + {"HISI0080", 0}, + {"", 0}, +}; + +struct acpi_scan_handler pci_root_hisi_handler = { + .ids = root_hisi_device_ids, + .attach = acpi_pci_root_hisi_add, + .detach = acpi_pci_root_hisi_remove, +}; + +static void __iomem *rc_base[MAX_PCIE_PORT_NUM]; + +static void __iomem * +pci_mcfg_dev_base(struct pci_bus *bus, unsigned int devfn, int offset) +{ + struct pci_mmcfg_region *cfg; + + cfg = pci_mmconfig_lookup(pci_domain_nr(bus), bus->number); + if (cfg && cfg->virt) + return cfg->virt + + (PCI_MMCFG_BUS_OFFSET(bus->number) | (devfn << 12)) + + offset; + return NULL; +} + + +/* Hip05 PCIe host only supports 32-bit config access */ +static int hisi_pcie_cfg_read(void __iomem *addr, int where, int size, + u32 *val) +{ + u32 reg; + u32 reg_val; + void *walker = ®_val; + + walker += (where & 0x3); + reg = where & ~0x3; + reg_val = readl(addr + reg); + + if (size == 1) + *val = *(u8 __force *) walker; + else if (size == 2) + *val = *(u16 __force *) walker; + else if (size != 4) + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} + + +/* Hip05 PCIe host only supports 32-bit config access */ +static int hisi_pcie_cfg_write(void __iomem *addr, int where, int size, + u32 val) +{ + u32 reg_val; + u32 reg; + void *walker = ®_val; + + walker += (where & 0x3); + reg = where & ~0x3; + if (size == 4) + writel(val, addr + reg); + else if (size == 2) { + reg_val = readl(addr + reg); + *(u16 __force *) walker = val; + writel(reg_val, addr + reg); + } else if (size == 1) { + reg_val = readl(addr + reg); + *(u8 __force *) walker = val; + writel(reg_val, addr + reg); + } else + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} + +/* +* hip05 support ECAM to access EP config space +* but not support RC +*/ +static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 *value) +{ + struct acpi_pci_root *root = bus->sysdata; + int ret; + + if (bus->number == root->secondary.start) + ret = hisi_pcie_cfg_read(rc_base[root->segment], where, size, + value); + else + ret = pci_generic_config_read(bus, devfn, where, size, value); + + return ret; +} + +static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 value) +{ + struct acpi_pci_root *root = bus->sysdata; + int ret; + + if (bus->number == root->secondary.start) + ret = hisi_pcie_cfg_write(rc_base[root->segment], where, size, + value); + else + ret = pci_generic_config_write(bus, devfn, where, size, value); + return ret; +} + +static struct pci_ops pci_root_ops = { + .map_bus = pci_mcfg_dev_base, + .read = pci_read, + .write = pci_write, +}; + + +/* +* Sample DSDT (PCIe Root bus) +* +* Device (RC0) +*{ +* Name (_HID, "HISI0081") +* Name (_CID, "HISI0080") +* Name(_SEG, 0) +* Name (_DSD, Package () { +* ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), +* Package () { +* Package () {"rc-base", 0xb0070000} +* Package () {"rc-size", 0x10000} +* }) +*} +*Device (PCI0) +*{ +* Name (_HID, "PNP0A08") // PCI Express Root Bridge +* Name (_CID, "PNP0A03") // Compatible PCI Root Bridge +*..... +*} +* +*use segment to distinguish pcie host controller +*0-pcie0 +*1-pcie1 +*/ +static int acpi_pci_root_hisi_add(struct acpi_device *device, + const struct acpi_device_id *not_used) +{ + u32 base; + u32 size; + int ret; + unsigned long long segment; + acpi_status status; + acpi_handle handle = device->handle; + + ret = fwnode_property_read_u32(&device->fwnode, "rc-base", &base); + if (ret) { + dev_err(&device->dev, "can't get rc-base\n"); + return ret; + } + + ret = fwnode_property_read_u32(&device->fwnode, "rc-size", &size); + if (ret) { + dev_err(&device->dev, "can't get rc-size\n"); + return ret; + } + + status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL, + &segment); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + dev_err(&device->dev, "can't evaluate _SEG\n"); + return -ENODEV; + } + + rc_base[segment] = ioremap(base, size); + set_quirk_pci_ops(&pci_root_ops); + + return 0; +} + +static void acpi_pci_root_hisi_remove(struct acpi_device *device) +{ + unset_quirk_pci_ops(); +} -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html