Add support for the Mediatek PCIe controller which can be found on MT7623A/N, MT2701 and MT8521p platforms. Signed-off-by: Ryder Lee <ryder.lee@xxxxxxxxxxxx> --- drivers/pci/host/Kconfig | 11 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-mediatek.c | 611 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 623 insertions(+) create mode 100644 drivers/pci/host/pcie-mediatek.c diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index f7c1d4d..cf13b5d 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -174,6 +174,17 @@ config PCIE_ROCKCHIP There is 1 internal PCIe port available to support GEN2 with 4 slots. +config PCIE_MEDIATEK + bool "Mediatek PCIe Controller for MT7623 SoCs families" + depends on (ARM && ARCH_MEDIATEK) || COMPILE_TEST + depends on OF + depends on PCI + select PCIEPORTBUS + help + Say Y here if you want to enable PCIe controller support on MT7623 A/N + series SoCs. There is a one root complex with 3 root ports available. + Each port supports Gen2 lane x1. + config VMD depends on PCI_MSI && X86_64 && SRCU tristate "Intel Volume Management Device Driver" diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 4d36866..265adff 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCIE_ROCKCHIP) += pcie-rockchip.o +obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o obj-$(CONFIG_VMD) += vmd.o # The following drivers are for devices that use the generic ACPI diff --git a/drivers/pci/host/pcie-mediatek.c b/drivers/pci/host/pcie-mediatek.c new file mode 100644 index 0000000..98e84d9 --- /dev/null +++ b/drivers/pci/host/pcie-mediatek.c @@ -0,0 +1,611 @@ +/* + * PCIe host controller driver for Mediatek MT7623 SoCs families + * + * Copyright (c) 2017 MediaTek Inc. + * Author: Ryder Lee <ryder.lee@xxxxxxxxxxxx> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/of_platform.h> +#include <linux/pci.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +/* PCIe shared registers */ +#define PCIE_SYS_CFG 0x00 +#define PCIE_INT_ENABLE 0x0c +#define PCIE_CFG_ADDR 0x20 +#define PCIE_CFG_DATA 0x24 + +/* PCIe per port registers */ +#define PCIE_BAR0_SETUP 0x10 +#define PCIE_BAR1_SETUP 0x14 +#define PCIE_BAR0_MEM_BASE 0x18 +#define PCIE_CLASS 0x34 +#define PCIE_LINK_STATUS 0x50 + +#define PCIE_PORT_INT_EN(x) BIT(20 + (x)) +#define PCIE_PORT_PERST(x) BIT(1 + (x)) +#define PCIE_PORT_LINKUP BIT(0) +#define PCIE_BAR_MAP_MAX GENMASK(31, 16) + +#define PCIE_BAR_ENABLE BIT(0) +#define PCIE_REVISION_ID BIT(0) +#define PCIE_CLASS_CODE (0x60400 << 8) +#define PCIE_CONF_REG(regn) (((regn) & GENMASK(7, 2)) | \ + ((((regn) >> 8) & GENMASK(3, 0)) << 24)) +#define PCIE_CONF_FUN(fun) (((fun) << 8) & GENMASK(10, 8)) +#define PCIE_CONF_DEV(dev) (((dev) << 11) & GENMASK(15, 11)) +#define PCIE_CONF_BUS(bus) (((bus) << 16) & GENMASK(23, 16)) +#define PCIE_CONF_ADDR(regn, fun, dev, bus) \ + (PCIE_CONF_REG(regn) | PCIE_CONF_FUN(fun) | \ + PCIE_CONF_DEV(dev) | PCIE_CONF_BUS(bus)) + +/* Mediatek specific configuration registers */ +#define PCIE_FTS_NUM 0x70c +#define PCIE_FTS_NUM_MASK GENMASK(15, 8) +#define PCIE_FTS_NUM_L0(x) ((x) & 0xff << 8) + +#define PCIE_FC_CREDIT 0x73c +#define PCIE_FC_CREDIT_MASK (GENMASK(31, 31) | GENMASK(28, 16)) +#define PCIE_FC_CREDIT_VAL(x) ((x) << 16) + +/** + * struct mtk_pcie_port - PCIe port information + * @dev: pointer to root port device + * @base: IO mapped register base + * @list: port list + * @pcie: pointer to PCIe host info + * @reset: pointer to RC reset control + * @regs: port memory region + * @sys_ck: root port clock + * @phy: pointer to phy control block + * @irq: IRQ number + * @lane: lane count + * @index: port index + */ +struct mtk_pcie_port { + struct device *dev; + void __iomem *base; + struct list_head list; + struct mtk_pcie *pcie; + struct reset_control *reset; + struct resource regs; + struct clk *sys_ck; + struct phy *phy; + int irq; + u32 lane; + u32 index; +}; + +/** + * struct mtk_pcie - PCIe host information + * @dev: pointer to PCIe device + * @base: IO mapped register Base + * @free_ck: free-run reference clock + * @resources: bus resources + * @ports: pointer to PCIe port information + */ +struct mtk_pcie { + struct device *dev; + void __iomem *base; + struct clk *free_ck; + struct list_head resources; + struct list_head ports; +}; + +static inline bool mtk_pcie_link_is_up(struct mtk_pcie_port *port) +{ + return !!(readl_relaxed(port->base + PCIE_LINK_STATUS) & + PCIE_PORT_LINKUP); +} + +static bool mtk_pcie_valid_device(struct mtk_pcie *pcie, + struct pci_bus *bus, int devfn) +{ + struct mtk_pcie_port *port; + struct pci_dev *dev; + struct pci_bus *pbus; + + /* if there is no link, then there is no device */ + list_for_each_entry(port, &pcie->ports, list) { + if (bus->number == 0 && port->index == PCI_SLOT(devfn) && + mtk_pcie_link_is_up(port)) { + return true; + } else if (bus->number != 0) { + pbus = bus; + do { + dev = pbus->self; + if (port->index == PCI_SLOT(dev->devfn) && + mtk_pcie_link_is_up(port)) { + return true; + } + pbus = dev->bus; + } while (dev->bus->number != 0); + } + } + + return false; +} + +static void mtk_pcie_port_free(struct mtk_pcie_port *port) +{ + struct mtk_pcie *pcie = port->pcie; + struct device *dev = pcie->dev; + + devm_iounmap(dev, port->base); + devm_release_mem_region(dev, port->regs.start, + resource_size(&port->regs)); + list_del(&port->list); + devm_kfree(dev, port); +} + +static int mtk_pcie_hw_rd_cfg(struct mtk_pcie *pcie, u32 bus, u32 devfn, + int where, int size, u32 *val) +{ + writel(PCIE_CONF_ADDR(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus), + pcie->base + PCIE_CFG_ADDR); + + *val = 0; + + switch (size) { + case 1: + *val = readb(pcie->base + PCIE_CFG_DATA + (where & 3)); + break; + case 2: + *val = readw(pcie->base + PCIE_CFG_DATA + (where & 2)); + break; + case 4: + *val = readl(pcie->base + PCIE_CFG_DATA); + break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int mtk_pcie_hw_wr_cfg(struct mtk_pcie *pcie, u32 bus, u32 devfn, + int where, int size, u32 val) + +{ + writel(PCIE_CONF_ADDR(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus), + pcie->base + PCIE_CFG_ADDR); + + switch (size) { + case 1: + writeb(val, pcie->base + PCIE_CFG_DATA + (where & 3)); + break; + case 2: + writew(val, pcie->base + PCIE_CFG_DATA + (where & 2)); + break; + case 4: + writel(val, pcie->base + PCIE_CFG_DATA); + break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int mtk_pcie_read_config(struct pci_bus *bus, u32 devfn, + int where, int size, u32 *val) +{ + struct mtk_pcie *pcie = bus->sysdata; + u32 bn = bus->number; + + if (!mtk_pcie_valid_device(pcie, bus, devfn)) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + return mtk_pcie_hw_rd_cfg(pcie, bn, devfn, where, size, val); +} + +static int mtk_pcie_write_config(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + struct mtk_pcie *pcie = bus->sysdata; + u32 bn = bus->number; + + if (!mtk_pcie_valid_device(pcie, bus, devfn)) + return PCIBIOS_DEVICE_NOT_FOUND; + + return mtk_pcie_hw_wr_cfg(pcie, bn, devfn, where, size, val); +} + +static struct pci_ops mtk_pcie_ops = { + .read = mtk_pcie_read_config, + .write = mtk_pcie_write_config, +}; + +static void mtk_pcie_configure_rc(struct mtk_pcie_port *port) +{ + struct mtk_pcie *pcie = port->pcie; + u32 val; + + /* enable interrupt */ + val = readl(pcie->base + PCIE_INT_ENABLE); + val |= PCIE_PORT_INT_EN(port->index); + writel(val, pcie->base + PCIE_INT_ENABLE); + + /* map to all DDR region. We need to set it before cfg operation. */ + writel(PCIE_BAR_MAP_MAX | PCIE_BAR_ENABLE, + port->base + PCIE_BAR0_SETUP); + + /* configure class Code and revision ID */ + writel(PCIE_CLASS_CODE | PCIE_REVISION_ID, + port->base + PCIE_CLASS); + + /* configure FC credit */ + mtk_pcie_hw_rd_cfg(pcie, 0, (port->index << 3), + PCIE_FC_CREDIT, 4, &val); + val &= ~PCIE_FC_CREDIT_MASK; + val |= PCIE_FC_CREDIT_VAL(0x806c); + mtk_pcie_hw_wr_cfg(pcie, 0, (port->index << 3), + PCIE_FC_CREDIT, 4, val); + + /* configure RC FTS number to 250 when it leaves L0s */ + mtk_pcie_hw_rd_cfg(pcie, 0, (port->index << 3), + PCIE_FTS_NUM, 4, &val); + val &= ~PCIE_FTS_NUM_MASK; + val |= PCIE_FTS_NUM_L0(0x50); + mtk_pcie_hw_wr_cfg(pcie, 0, (port->index << 3), + PCIE_FTS_NUM, 4, val); +} + +static void mtk_pcie_assert_ports(struct mtk_pcie_port *port) +{ + struct mtk_pcie *pcie = port->pcie; + u32 val; + + /* assert port PERST_N */ + val = readl(pcie->base + PCIE_SYS_CFG); + val |= PCIE_PORT_PERST(port->index); + writel(val, pcie->base + PCIE_SYS_CFG); + + /* de-assert port PERST_N */ + val = readl(pcie->base + PCIE_SYS_CFG); + val &= ~PCIE_PORT_PERST(port->index); + writel(val, pcie->base + PCIE_SYS_CFG); + + /* + * at least 100ms delay because PCIe v2.0 need more time to + * train from Gen1 to Gen2 + */ + msleep(100); +} + +static int mtk_pcie_enable_ports(struct mtk_pcie *pcie) +{ + struct device *dev = pcie->dev; + struct mtk_pcie_port *port, *tmp; + int err, linkup = 0; + + list_for_each_entry_safe(port, tmp, &pcie->ports, list) { + err = clk_prepare_enable(port->sys_ck); + if (err) { + dev_err(dev, "failed to enable port%d clock\n", + port->index); + continue; + } + + /* assert RC */ + reset_control_assert(port->reset); + /* de-assert RC */ + reset_control_deassert(port->reset); + + /* power on PHY */ + err = phy_power_on(port->phy); + if (err) { + dev_err(dev, "failed to power on port%d phy\n", + port->index); + goto err_phy_on; + } + + mtk_pcie_assert_ports(port); + + /* if link up, then setup root port configuration space */ + if (mtk_pcie_link_is_up(port)) { + mtk_pcie_configure_rc(port); + linkup++; + continue; + } + + dev_info(dev, "Port%d link down\n", port->index); + + phy_power_off(port->phy); +err_phy_on: + clk_disable_unprepare(port->sys_ck); + mtk_pcie_port_free(port); + } + + return linkup; +} + +static int mtk_pcie_get_port_resource(struct mtk_pcie_port *port, + struct device_node *node) +{ + struct device *dev = port->pcie->dev; + struct platform_device *pdev = to_platform_device(dev); + struct platform_device *plat_dev; + char name[10]; + int err; + + err = of_address_to_resource(node, 0, &port->regs); + if (err) { + dev_err(dev, "failed to parse address: %d\n", err); + return err; + } + + port->base = devm_ioremap_resource(dev, &port->regs); + if (IS_ERR(port->base)) { + dev_err(dev, "failed to map port%d base\n", port->index); + return PTR_ERR(port->base); + } + + plat_dev = of_find_device_by_node(node); + if (!plat_dev) { + plat_dev = of_platform_device_create( + node, NULL, + platform_bus_type.dev_root); + if (!plat_dev) + return -EPROBE_DEFER; + } + + port->dev = &plat_dev->dev; + + port->irq = platform_get_irq(pdev, port->index); + if (!port->irq) { + dev_err(dev, "failed to get irq\n"); + return -ENODEV; + } + + port->sys_ck = devm_clk_get(port->dev, "sys_ck"); + if (IS_ERR(port->sys_ck)) { + dev_err(port->dev, "failed to get port%d clock\n", port->index); + return PTR_ERR(port->sys_ck); + } + + port->reset = devm_reset_control_get(port->dev, "pcie-reset"); + if (IS_ERR(port->reset)) { + dev_err(port->dev, "failed to get port%d reset control\n", + port->index); + return PTR_ERR(port->reset); + } + + snprintf(name, sizeof(name), "pcie-phy%d", port->index); + port->phy = devm_of_phy_get(port->dev, node, name); + if (IS_ERR(port->phy)) { + dev_err(port->dev, "failed to get port%d phy\n", port->index); + return PTR_ERR(port->phy); + } + + return 0; +} + +static int mtk_pcie_parse_and_add_res(struct mtk_pcie *pcie) +{ + struct device *dev = pcie->dev; + struct platform_device *pdev = to_platform_device(dev); + struct device_node *node = dev->of_node, *child; + struct resource_entry *win, *tmp; + struct resource *regs; + resource_size_t iobase; + int err; + + /* parse shared resources */ + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pcie->base = devm_ioremap_resource(dev, regs); + if (IS_ERR(pcie->base)) { + dev_err(dev, "failed to get PCIe base\n"); + return PTR_ERR(pcie->base); + } + + pcie->free_ck = devm_clk_get(dev, "free_ck"); + if (IS_ERR(pcie->free_ck)) { + dev_err(dev, "failed to get free_ck\n"); + return PTR_ERR(pcie->free_ck); + } + + err = of_pci_get_host_bridge_resources(node, 0, 0xff, &pcie->resources, + &iobase); + if (err) + return err; + + err = devm_request_pci_bus_resources(dev, &pcie->resources); + if (err) + return err; + + resource_list_for_each_entry_safe(win, tmp, &pcie->resources) { + struct resource *res = win->res; + + switch (resource_type(res)) { + case IORESOURCE_IO: + err = pci_remap_iospace(res, iobase); + if (err) { + dev_warn(dev, "failed to map resource %pR\n", + res); + resource_list_destroy_entry(win); + } + break; + } + } + + /* parse port resources */ + for_each_child_of_node(node, child) { + struct mtk_pcie_port *port; + int index; + + err = of_pci_get_devfn(child); + if (err < 0) { + dev_err(pcie->dev, "failed to parse devfn: %d\n", err); + return err; + } + + index = PCI_SLOT(err); + if (index < 1) { + dev_err(dev, "invalid port number: %d\n", index); + return -EINVAL; + } + + index--; + + if (!of_device_is_available(child)) + continue; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + err = of_property_read_u32(child, "num-lanes", &port->lane); + if (err) { + dev_err(dev, "missing num-lanes property\n"); + return err; + } + + port->index = index; + port->pcie = pcie; + + err = mtk_pcie_get_port_resource(port, child); + if (err) + return err; + + INIT_LIST_HEAD(&port->list); + list_add_tail(&port->list, &pcie->ports); + } + + return 0; +} + +/* + * This IP lacks interrupt status register to check or map INTx from + * different devices at the same time. + */ +static int __init mtk_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + struct mtk_pcie *pcie = dev->bus->sysdata; + struct mtk_pcie_port *port; + + list_for_each_entry(port, &pcie->ports, list) + if (port->index == slot) + return port->irq; + + return -1; +} + +static int mtk_pcie_register_ports(struct mtk_pcie *pcie) +{ + struct pci_bus *bus, *child; + + bus = pci_scan_root_bus(pcie->dev, 0, &mtk_pcie_ops, pcie, + &pcie->resources); + if (!bus) { + dev_err(pcie->dev, "failed to create root bus\n"); + return -ENOMEM; + } + + if (!pci_has_flag(PCI_PROBE_ONLY)) { + pci_fixup_irqs(pci_common_swizzle, mtk_pcie_map_irq); + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + } + + pci_bus_add_devices(bus); + + return 0; +} + +static int mtk_pcie_probe(struct platform_device *pdev) +{ + struct mtk_pcie *pcie; + int err; + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &pdev->dev; + platform_set_drvdata(pdev, pcie); + + /* + * parse PCI ranges, configuration bus range and + * request their resources + */ + INIT_LIST_HEAD(&pcie->ports); + INIT_LIST_HEAD(&pcie->resources); + + err = mtk_pcie_parse_and_add_res(pcie); + if (err) + goto err_parse; + + pm_runtime_enable(pcie->dev); + err = pm_runtime_get_sync(pcie->dev); + if (err) + goto err_pm; + + err = clk_prepare_enable(pcie->free_ck); + if (err) { + dev_err(pcie->dev, "failed to enable free_ck\n"); + goto err_free_ck; + } + + /* power on PCIe ports */ + err = mtk_pcie_enable_ports(pcie); + if (!err) + goto err_enable; + + /* register PCIe ports */ + err = mtk_pcie_register_ports(pcie); + if (err) + goto err_enable; + + return 0; + +err_enable: + clk_disable_unprepare(pcie->free_ck); +err_free_ck: + pm_runtime_put_sync(pcie->dev); +err_pm: + pm_runtime_disable(pcie->dev); +err_parse: + pci_free_resource_list(&pcie->resources); + + return err; +} + +static const struct of_device_id mtk_pcie_ids[] = { + { .compatible = "mediatek,mt7623-pcie"}, + { .compatible = "mediatek,mt2701-pcie"}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_pcie_ids); + +static struct platform_driver mtk_pcie_driver = { + .probe = mtk_pcie_probe, + .driver = { + .name = "mtk-pcie", + .of_match_table = mtk_pcie_ids, + }, +}; + +builtin_platform_driver(mtk_pcie_driver); + +MODULE_DESCRIPTION("Mediatek PCIe host driver for MT7623 SoCs families"); +MODULE_LICENSE("GPL v2"); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html