| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * PCIe host controller driver for StarFive JH7110 Soc. |
| * |
| * Copyright (C) 2023 StarFive Technology Co., Ltd. |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_pci.h> |
| #include <linux/pci.h> |
| #include <linux/phy/phy.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regmap.h> |
| #include <linux/reset.h> |
| #include "../../pci.h" |
| |
| #include "pcie-plda.h" |
| |
| #define PCIE_FUNC_NUM 4 |
| |
| /* system control */ |
| #define STG_SYSCON_PCIE0_BASE 0x48 |
| #define STG_SYSCON_PCIE1_BASE 0x1f8 |
| |
| #define STG_SYSCON_AR_OFFSET 0x78 |
| #define STG_SYSCON_AXI4_SLVL_AR_MASK GENMASK(22, 8) |
| #define STG_SYSCON_AXI4_SLVL_PHY_AR(x) FIELD_PREP(GENMASK(20, 17), x) |
| #define STG_SYSCON_AW_OFFSET 0x7c |
| #define STG_SYSCON_AXI4_SLVL_AW_MASK GENMASK(14, 0) |
| #define STG_SYSCON_AXI4_SLVL_PHY_AW(x) FIELD_PREP(GENMASK(12, 9), x) |
| #define STG_SYSCON_CLKREQ BIT(22) |
| #define STG_SYSCON_CKREF_SRC_MASK GENMASK(19, 18) |
| #define STG_SYSCON_RP_NEP_OFFSET 0xe8 |
| #define STG_SYSCON_K_RP_NEP BIT(8) |
| #define STG_SYSCON_LNKSTA_OFFSET 0x170 |
| #define DATA_LINK_ACTIVE BIT(5) |
| |
| /* Parameters for the waiting for link up routine */ |
| #define LINK_WAIT_MAX_RETRIES 10 |
| #define LINK_WAIT_USLEEP_MIN 90000 |
| #define LINK_WAIT_USLEEP_MAX 100000 |
| |
| struct starfive_jh7110_pcie { |
| struct plda_pcie_rp plda; |
| struct reset_control *resets; |
| struct clk_bulk_data *clks; |
| struct regmap *reg_syscon; |
| struct gpio_desc *power_gpio; |
| struct gpio_desc *reset_gpio; |
| struct phy *phy; |
| |
| unsigned int stg_pcie_base; |
| int num_clks; |
| }; |
| |
| /* |
| * JH7110 PCIe port BAR0/1 can be configured as 64-bit prefetchable memory |
| * space. PCIe read and write requests targeting BAR0/1 are routed to so called |
| * 'Bridge Configuration space' in PLDA IP datasheet, which contains the bridge |
| * internal registers, such as interrupt, DMA and ATU registers... |
| * JH7110 can access the Bridge Configuration space by local bus, and don`t |
| * want the bridge internal registers accessed by the DMA from EP devices. |
| * Thus, they are unimplemented and should be hidden here. |
| */ |
| static bool starfive_pcie_hide_rc_bar(struct pci_bus *bus, unsigned int devfn, |
| int offset) |
| { |
| if (pci_is_root_bus(bus) && !devfn && |
| (offset == PCI_BASE_ADDRESS_0 || offset == PCI_BASE_ADDRESS_1)) |
| return true; |
| |
| return false; |
| } |
| |
| static int starfive_pcie_config_write(struct pci_bus *bus, unsigned int devfn, |
| int where, int size, u32 value) |
| { |
| if (starfive_pcie_hide_rc_bar(bus, devfn, where)) |
| return PCIBIOS_SUCCESSFUL; |
| |
| return pci_generic_config_write(bus, devfn, where, size, value); |
| } |
| |
| static int starfive_pcie_config_read(struct pci_bus *bus, unsigned int devfn, |
| int where, int size, u32 *value) |
| { |
| if (starfive_pcie_hide_rc_bar(bus, devfn, where)) { |
| *value = 0; |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| return pci_generic_config_read(bus, devfn, where, size, value); |
| } |
| |
| static int starfive_pcie_parse_dt(struct starfive_jh7110_pcie *pcie, |
| struct device *dev) |
| { |
| int domain_nr; |
| |
| pcie->num_clks = devm_clk_bulk_get_all(dev, &pcie->clks); |
| if (pcie->num_clks < 0) |
| return dev_err_probe(dev, pcie->num_clks, |
| "failed to get pcie clocks\n"); |
| |
| pcie->resets = devm_reset_control_array_get_exclusive(dev); |
| if (IS_ERR(pcie->resets)) |
| return dev_err_probe(dev, PTR_ERR(pcie->resets), |
| "failed to get pcie resets"); |
| |
| pcie->reg_syscon = |
| syscon_regmap_lookup_by_phandle(dev->of_node, |
| "starfive,stg-syscon"); |
| |
| if (IS_ERR(pcie->reg_syscon)) |
| return dev_err_probe(dev, PTR_ERR(pcie->reg_syscon), |
| "failed to parse starfive,stg-syscon\n"); |
| |
| pcie->phy = devm_phy_optional_get(dev, NULL); |
| if (IS_ERR(pcie->phy)) |
| return dev_err_probe(dev, PTR_ERR(pcie->phy), |
| "failed to get pcie phy\n"); |
| |
| /* |
| * The PCIe domain numbers are set to be static in JH7110 DTS. |
| * As the STG system controller defines different bases in PCIe RP0 & |
| * RP1, we use them to identify which controller is doing the hardware |
| * initialization. |
| */ |
| domain_nr = of_get_pci_domain_nr(dev->of_node); |
| |
| if (domain_nr < 0 || domain_nr > 1) |
| return dev_err_probe(dev, -ENODEV, |
| "failed to get valid pcie domain\n"); |
| |
| if (domain_nr == 0) |
| pcie->stg_pcie_base = STG_SYSCON_PCIE0_BASE; |
| else |
| pcie->stg_pcie_base = STG_SYSCON_PCIE1_BASE; |
| |
| pcie->reset_gpio = devm_gpiod_get_optional(dev, "perst", |
| GPIOD_OUT_HIGH); |
| if (IS_ERR(pcie->reset_gpio)) |
| return dev_err_probe(dev, PTR_ERR(pcie->reset_gpio), |
| "failed to get perst-gpio\n"); |
| |
| pcie->power_gpio = devm_gpiod_get_optional(dev, "enable", |
| GPIOD_OUT_LOW); |
| if (IS_ERR(pcie->power_gpio)) |
| return dev_err_probe(dev, PTR_ERR(pcie->power_gpio), |
| "failed to get power-gpio\n"); |
| |
| return 0; |
| } |
| |
| static struct pci_ops starfive_pcie_ops = { |
| .map_bus = plda_pcie_map_bus, |
| .read = starfive_pcie_config_read, |
| .write = starfive_pcie_config_write, |
| }; |
| |
| static int starfive_pcie_clk_rst_init(struct starfive_jh7110_pcie *pcie) |
| { |
| struct device *dev = pcie->plda.dev; |
| int ret; |
| |
| ret = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to enable clocks\n"); |
| |
| ret = reset_control_deassert(pcie->resets); |
| if (ret) { |
| clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); |
| dev_err_probe(dev, ret, "failed to deassert resets\n"); |
| } |
| |
| return ret; |
| } |
| |
| static void starfive_pcie_clk_rst_deinit(struct starfive_jh7110_pcie *pcie) |
| { |
| reset_control_assert(pcie->resets); |
| clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); |
| } |
| |
| static bool starfive_pcie_link_up(struct plda_pcie_rp *plda) |
| { |
| struct starfive_jh7110_pcie *pcie = |
| container_of(plda, struct starfive_jh7110_pcie, plda); |
| int ret; |
| u32 stg_reg_val; |
| |
| ret = regmap_read(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_LNKSTA_OFFSET, |
| &stg_reg_val); |
| if (ret) { |
| dev_err(pcie->plda.dev, "failed to read link status\n"); |
| return false; |
| } |
| |
| return !!(stg_reg_val & DATA_LINK_ACTIVE); |
| } |
| |
| static int starfive_pcie_host_wait_for_link(struct starfive_jh7110_pcie *pcie) |
| { |
| int retries; |
| |
| /* Check if the link is up or not */ |
| for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { |
| if (starfive_pcie_link_up(&pcie->plda)) { |
| dev_info(pcie->plda.dev, "port link up\n"); |
| return 0; |
| } |
| usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int starfive_pcie_enable_phy(struct device *dev, |
| struct starfive_jh7110_pcie *pcie) |
| { |
| int ret; |
| |
| if (!pcie->phy) |
| return 0; |
| |
| ret = phy_init(pcie->phy); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "failed to initialize pcie phy\n"); |
| |
| ret = phy_set_mode(pcie->phy, PHY_MODE_PCIE); |
| if (ret) { |
| dev_err_probe(dev, ret, "failed to set pcie mode\n"); |
| goto err_phy_on; |
| } |
| |
| ret = phy_power_on(pcie->phy); |
| if (ret) { |
| dev_err_probe(dev, ret, "failed to power on pcie phy\n"); |
| goto err_phy_on; |
| } |
| |
| return 0; |
| |
| err_phy_on: |
| phy_exit(pcie->phy); |
| return ret; |
| } |
| |
| static void starfive_pcie_disable_phy(struct starfive_jh7110_pcie *pcie) |
| { |
| phy_power_off(pcie->phy); |
| phy_exit(pcie->phy); |
| } |
| |
| static void starfive_pcie_host_deinit(struct plda_pcie_rp *plda) |
| { |
| struct starfive_jh7110_pcie *pcie = |
| container_of(plda, struct starfive_jh7110_pcie, plda); |
| |
| starfive_pcie_clk_rst_deinit(pcie); |
| if (pcie->power_gpio) |
| gpiod_set_value_cansleep(pcie->power_gpio, 0); |
| starfive_pcie_disable_phy(pcie); |
| } |
| |
| static int starfive_pcie_host_init(struct plda_pcie_rp *plda) |
| { |
| struct starfive_jh7110_pcie *pcie = |
| container_of(plda, struct starfive_jh7110_pcie, plda); |
| struct device *dev = plda->dev; |
| int ret; |
| int i; |
| |
| ret = starfive_pcie_enable_phy(dev, pcie); |
| if (ret) |
| return ret; |
| |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_RP_NEP_OFFSET, |
| STG_SYSCON_K_RP_NEP, STG_SYSCON_K_RP_NEP); |
| |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, |
| STG_SYSCON_CKREF_SRC_MASK, |
| FIELD_PREP(STG_SYSCON_CKREF_SRC_MASK, 2)); |
| |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, |
| STG_SYSCON_CLKREQ, STG_SYSCON_CLKREQ); |
| |
| ret = starfive_pcie_clk_rst_init(pcie); |
| if (ret) |
| return ret; |
| |
| if (pcie->power_gpio) |
| gpiod_set_value_cansleep(pcie->power_gpio, 1); |
| |
| if (pcie->reset_gpio) |
| gpiod_set_value_cansleep(pcie->reset_gpio, 1); |
| |
| /* Disable physical functions except #0 */ |
| for (i = 1; i < PCIE_FUNC_NUM; i++) { |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AR_OFFSET, |
| STG_SYSCON_AXI4_SLVL_AR_MASK, |
| STG_SYSCON_AXI4_SLVL_PHY_AR(i)); |
| |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, |
| STG_SYSCON_AXI4_SLVL_AW_MASK, |
| STG_SYSCON_AXI4_SLVL_PHY_AW(i)); |
| |
| plda_pcie_disable_func(plda); |
| } |
| |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AR_OFFSET, |
| STG_SYSCON_AXI4_SLVL_AR_MASK, 0); |
| regmap_update_bits(pcie->reg_syscon, |
| pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, |
| STG_SYSCON_AXI4_SLVL_AW_MASK, 0); |
| |
| plda_pcie_enable_root_port(plda); |
| plda_pcie_write_rc_bar(plda, 0); |
| |
| /* PCIe PCI Standard Configuration Identification Settings. */ |
| plda_pcie_set_standard_class(plda); |
| |
| /* |
| * The LTR message receiving is enabled by the register "PCIe Message |
| * Reception" as default, but the forward id & addr are uninitialized. |
| * If we do not disable LTR message forwarding here, or set a legal |
| * forwarding address, the kernel will get stuck. |
| * To workaround, disable the LTR message forwarding here before using |
| * this feature. |
| */ |
| plda_pcie_disable_ltr(plda); |
| |
| /* |
| * Enable the prefetchable memory window 64-bit addressing in JH7110. |
| * The 64-bits prefetchable address translation configurations in ATU |
| * can be work after enable the register setting below. |
| */ |
| plda_pcie_set_pref_win_64bit(plda); |
| |
| /* |
| * Ensure that PERST has been asserted for at least 100 ms, |
| * the sleep value is T_PVPERL from PCIe CEM spec r2.0 (Table 2-4) |
| */ |
| msleep(100); |
| if (pcie->reset_gpio) |
| gpiod_set_value_cansleep(pcie->reset_gpio, 0); |
| |
| /* |
| * With a Downstream Port (<=5GT/s), software must wait a minimum |
| * of 100ms following exit from a conventional reset before |
| * sending a configuration request to the device. |
| */ |
| msleep(PCIE_RESET_CONFIG_DEVICE_WAIT_MS); |
| |
| if (starfive_pcie_host_wait_for_link(pcie)) |
| dev_info(dev, "port link down\n"); |
| |
| return 0; |
| } |
| |
| static const struct plda_pcie_host_ops sf_host_ops = { |
| .host_init = starfive_pcie_host_init, |
| .host_deinit = starfive_pcie_host_deinit, |
| }; |
| |
| static const struct plda_event stf_pcie_event = { |
| .intx_event = EVENT_PM_MSI_INT_INTX, |
| .msi_event = EVENT_PM_MSI_INT_MSI |
| }; |
| |
| static int starfive_pcie_probe(struct platform_device *pdev) |
| { |
| struct starfive_jh7110_pcie *pcie; |
| struct device *dev = &pdev->dev; |
| struct plda_pcie_rp *plda; |
| int ret; |
| |
| pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); |
| if (!pcie) |
| return -ENOMEM; |
| |
| plda = &pcie->plda; |
| plda->dev = dev; |
| |
| ret = starfive_pcie_parse_dt(pcie, dev); |
| if (ret) |
| return ret; |
| |
| plda->host_ops = &sf_host_ops; |
| plda->num_events = PLDA_MAX_EVENT_NUM; |
| /* mask doorbell event */ |
| plda->events_bitmap = GENMASK(PLDA_INT_EVENT_NUM - 1, 0) |
| & ~BIT(PLDA_AXI_DOORBELL) |
| & ~BIT(PLDA_PCIE_DOORBELL); |
| plda->events_bitmap <<= PLDA_NUM_DMA_EVENTS; |
| ret = plda_pcie_host_init(&pcie->plda, &starfive_pcie_ops, |
| &stf_pcie_event); |
| if (ret) |
| return ret; |
| |
| pm_runtime_enable(&pdev->dev); |
| pm_runtime_get_sync(&pdev->dev); |
| platform_set_drvdata(pdev, pcie); |
| |
| return 0; |
| } |
| |
| static void starfive_pcie_remove(struct platform_device *pdev) |
| { |
| struct starfive_jh7110_pcie *pcie = platform_get_drvdata(pdev); |
| |
| pm_runtime_put(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| plda_pcie_host_deinit(&pcie->plda); |
| platform_set_drvdata(pdev, NULL); |
| } |
| |
| static int starfive_pcie_suspend_noirq(struct device *dev) |
| { |
| struct starfive_jh7110_pcie *pcie = dev_get_drvdata(dev); |
| |
| clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); |
| starfive_pcie_disable_phy(pcie); |
| |
| return 0; |
| } |
| |
| static int starfive_pcie_resume_noirq(struct device *dev) |
| { |
| struct starfive_jh7110_pcie *pcie = dev_get_drvdata(dev); |
| int ret; |
| |
| ret = starfive_pcie_enable_phy(dev, pcie); |
| if (ret) |
| return ret; |
| |
| ret = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks); |
| if (ret) { |
| dev_err(dev, "failed to enable clocks\n"); |
| starfive_pcie_disable_phy(pcie); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops starfive_pcie_pm_ops = { |
| NOIRQ_SYSTEM_SLEEP_PM_OPS(starfive_pcie_suspend_noirq, |
| starfive_pcie_resume_noirq) |
| }; |
| |
| static const struct of_device_id starfive_pcie_of_match[] = { |
| { .compatible = "starfive,jh7110-pcie", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, starfive_pcie_of_match); |
| |
| static struct platform_driver starfive_pcie_driver = { |
| .driver = { |
| .name = "pcie-starfive", |
| .of_match_table = of_match_ptr(starfive_pcie_of_match), |
| .pm = pm_sleep_ptr(&starfive_pcie_pm_ops), |
| }, |
| .probe = starfive_pcie_probe, |
| .remove_new = starfive_pcie_remove, |
| }; |
| module_platform_driver(starfive_pcie_driver); |
| |
| MODULE_DESCRIPTION("StarFive JH7110 PCIe host driver"); |
| MODULE_LICENSE("GPL v2"); |