diff options
Diffstat (limited to 'drivers/pci/pci-rcar-gen4.c')
-rw-r--r-- | drivers/pci/pci-rcar-gen4.c | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/drivers/pci/pci-rcar-gen4.c b/drivers/pci/pci-rcar-gen4.c new file mode 100644 index 00000000000..87cd69f989d --- /dev/null +++ b/drivers/pci/pci-rcar-gen4.c @@ -0,0 +1,565 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PCIe controller driver for Renesas R-Car Gen4 Series SoCs + * Copyright (C) 2025 Marek Vasut <marek.vasut+renesas@mailbox.org> + * Based on Linux kernel driver + * Copyright (C) 2022-2023 Renesas Electronics Corporation + * + * The r8a779g0 (R-Car V4H) controller requires a specific firmware to be + * provided, to initialize the PHY. Otherwise, the PCIe controller will not + * work. + */ + +#include <asm-generic/gpio.h> +#include <asm/arch/gpio.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <clk.h> +#include <command.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <env.h> +#include <log.h> +#include <reset.h> + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iopoll.h> + +#include "pcie_dw_common.h" + +/* Renesas-specific */ +/* PCIe Mode Setting Register 0 */ +#define PCIEMSR0 0x0000 +#define APP_SRIS_MODE BIT(6) +#define DEVICE_TYPE_EP 0 +#define DEVICE_TYPE_RC BIT(4) +#define BIFUR_MOD_SET_ON BIT(0) + +/* PCIe Interrupt Status 0 */ +#define PCIEINTSTS0 0x0084 + +/* PCIe Interrupt Status 0 Enable */ +#define PCIEINTSTS0EN 0x0310 +#define MSI_CTRL_INT BIT(26) +#define SMLH_LINK_UP BIT(7) +#define RDLH_LINK_UP BIT(6) + +/* PCIe DMA Interrupt Status Enable */ +#define PCIEDMAINTSTSEN 0x0314 +#define PCIEDMAINTSTSEN_INIT GENMASK(15, 0) + +/* Port Logic Registers 89 */ +#define PRTLGC89 0x0b70 + +/* Port Logic Registers 90 */ +#define PRTLGC90 0x0b74 + +/* PCIe Reset Control Register 1 */ +#define PCIERSTCTRL1 0x0014 +#define APP_HOLD_PHY_RST BIT(16) +#define APP_LTSSM_ENABLE BIT(0) + +/* PCIe Power Management Control */ +#define PCIEPWRMNGCTRL 0x0070 +#define APP_CLK_REQ_N BIT(11) +#define APP_CLK_PM_EN BIT(10) + +#define RCAR_NUM_SPEED_CHANGE_RETRIES 10 +#define RCAR_MAX_LINK_SPEED 4 + +#define RCAR_GEN4_PCIE_EP_FUNC_DBI_OFFSET 0x1000 +#define RCAR_GEN4_PCIE_EP_FUNC_DBI2_OFFSET 0x800 + +#define RCAR_GEN4_PCIE_FIRMWARE_NAME "rcar_gen4_pcie.bin" +#define RCAR_GEN4_PCIE_FIRMWARE_BASE_ADDR 0xc000 + +#define PCIE_T_PVPERL_MS 100 + +/** + * struct rcar_gen4_pcie - Renesas R-Car Gen4 DW PCIe controller state + * + * @rcar: The common PCIe DW structure + * @pwr_rst: The PWR reset of the PCIe core + * @core_clk: The core clock of the PCIe core + * @ref_clk: The reference clock of the PCIe core and possibly bus + * @pe_rst: PERST GPIO + * @app_base: The base address of application register space + * @dbi2_base: The base address of DBI2 register space + * @phy_base: The base address of PHY register space + * @max_link_speed: Maximum PCIe link speed supported by the setup + * @num_lanes: Number of PCIe lanes used by the setup + * @firmware: PHY firmware + * @firmware_size: PHY firmware size in Bytes + */ +struct rcar_gen4_pcie { + /* Must be first member of the struct */ + struct pcie_dw dw; + struct reset_ctl pwr_rst; + struct clk *core_clk; + struct clk *ref_clk; + struct gpio_desc pe_rst; + void *app_base; + void *dbi2_base; + void *phy_base; + u32 max_link_speed; + u32 num_lanes; + u16 *firmware; + u32 firmware_size; +}; + +/* Common */ +static bool rcar_gen4_pcie_link_up(struct rcar_gen4_pcie *rcar) +{ + u32 val, mask; + + val = readl(rcar->app_base + PCIEINTSTS0); + mask = RDLH_LINK_UP | SMLH_LINK_UP; + + return (val & mask) == mask; +} + +/* + * Manually initiate the speed change. Return 0 if change succeeded; otherwise + * -ETIMEDOUT. + */ +static int rcar_gen4_pcie_speed_change(struct rcar_gen4_pcie *rcar) +{ + u32 val; + int i; + + clrbits_le32(rcar->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL, + PORT_LOGIC_SPEED_CHANGE); + + setbits_le32(rcar->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL, + PORT_LOGIC_SPEED_CHANGE); + + for (i = 0; i < RCAR_NUM_SPEED_CHANGE_RETRIES; i++) { + val = readl(rcar->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL); + if (!(val & PORT_LOGIC_SPEED_CHANGE)) + return 0; + mdelay(10); + } + + return -ETIMEDOUT; +} + +/* + * SoC datasheet suggests checking port logic register bits during firmware + * write. If read returns non-zero value, then this function returns -EAGAIN + * indicating that the write needs to be done again. If read returns zero, + * then return 0 to indicate success. + */ +static int rcar_gen4_pcie_reg_test_bit(struct rcar_gen4_pcie *rcar, + u32 offset, u32 mask) +{ + if (readl(rcar->dw.dbi_base + offset) & mask) + return -EAGAIN; + + return 0; +} + +static int rcar_gen4_pcie_download_phy_firmware(struct rcar_gen4_pcie *rcar) +{ + /* The check_addr values are magical numbers in the datasheet */ + static const u32 check_addr[] = { + 0x00101018, + 0x00101118, + 0x00101021, + 0x00101121, + }; + unsigned int i, timeout; + u32 data; + int ret; + + for (i = 0; i < rcar->firmware_size / 2; i++) { + data = rcar->firmware[i]; + timeout = 100; + do { + writel(RCAR_GEN4_PCIE_FIRMWARE_BASE_ADDR + i, rcar->dw.dbi_base + PRTLGC89); + writel(data, rcar->dw.dbi_base + PRTLGC90); + if (!rcar_gen4_pcie_reg_test_bit(rcar, PRTLGC89, BIT(30))) + break; + if (!(--timeout)) + return -ETIMEDOUT; + udelay(100); + } while (1); + } + + setbits_le32(rcar->phy_base + 0x0f8, BIT(17)); + + for (i = 0; i < ARRAY_SIZE(check_addr); i++) { + timeout = 100; + do { + writel(check_addr[i], rcar->dw.dbi_base + PRTLGC89); + ret = rcar_gen4_pcie_reg_test_bit(rcar, PRTLGC89, BIT(30)); + ret |= rcar_gen4_pcie_reg_test_bit(rcar, PRTLGC90, BIT(0)); + if (!ret) + break; + if (!(--timeout)) + return -ETIMEDOUT; + udelay(100); + } while (1); + } + + return ret; +} + +static int rcar_gen4_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable) +{ + u32 val; + int ret; + + if (!enable) { + clrbits_le32(rcar->app_base + PCIERSTCTRL1, APP_LTSSM_ENABLE); + return 0; + } + + setbits_le32(rcar->dw.dbi_base + PCIE_PORT_FORCE, + PORT_FORCE_DO_DESKEW_FOR_SRIS); + + setbits_le32(rcar->app_base + PCIEMSR0, APP_SRIS_MODE); + + /* + * The R-Car Gen4 datasheet doesn't describe the PHY registers' name. + * But, the initialization procedure describes these offsets. So, + * this driver has magical offset numbers. + */ + clrsetbits_le32(rcar->phy_base + 0x700, BIT(28), 0); + clrsetbits_le32(rcar->phy_base + 0x700, BIT(20), 0); + clrsetbits_le32(rcar->phy_base + 0x700, BIT(12), 0); + clrsetbits_le32(rcar->phy_base + 0x700, BIT(4), 0); + + clrsetbits_le32(rcar->phy_base + 0x148, GENMASK(23, 22), BIT(22)); + clrsetbits_le32(rcar->phy_base + 0x148, GENMASK(18, 16), GENMASK(17, 16)); + clrsetbits_le32(rcar->phy_base + 0x148, GENMASK(7, 6), BIT(6)); + clrsetbits_le32(rcar->phy_base + 0x148, GENMASK(2, 0), GENMASK(11, 0)); + clrsetbits_le32(rcar->phy_base + 0x1d4, GENMASK(16, 15), GENMASK(16, 15)); + clrsetbits_le32(rcar->phy_base + 0x514, BIT(26), BIT(26)); + clrsetbits_le32(rcar->phy_base + 0x0f8, BIT(16), 0); + clrsetbits_le32(rcar->phy_base + 0x0f8, BIT(19), BIT(19)); + + clrbits_le32(rcar->app_base + PCIERSTCTRL1, APP_HOLD_PHY_RST); + + ret = readl_poll_timeout(rcar->phy_base + 0x0f8, val, !(val & BIT(18)), 10000); + if (ret < 0) + return ret; + + ret = rcar_gen4_pcie_download_phy_firmware(rcar); + if (ret) + return ret; + + setbits_le32(rcar->app_base + PCIERSTCTRL1, APP_LTSSM_ENABLE); + + return 0; +} + +/* + * Enable LTSSM of this controller and manually initiate the speed change. + * Always return 0. + */ +static int rcar_gen4_pcie_start_link(struct rcar_gen4_pcie *rcar) +{ + int i, ret; + + ret = rcar_gen4_pcie_ltssm_control(rcar, true); + if (ret) + return ret; + + /* + * Require direct speed change with retrying here if the max_link_speed + * is PCIe Gen2 or higher. + */ + if (rcar->max_link_speed == LINK_SPEED_GEN_1) + return 0; + + for (i = 0; i < RCAR_MAX_LINK_SPEED; i++) { + /* It may not be connected in EP mode yet. So, break the loop */ + if (rcar_gen4_pcie_speed_change(rcar)) + break; + } + + return 0; +} + +static void rcar_gen4_pcie_additional_common_init(struct rcar_gen4_pcie *rcar) +{ + clrsetbits_le32(rcar->dw.dbi_base + PCIE_PORT_LANE_SKEW, + PORT_LANE_SKEW_INSERT_MASK, + (rcar->num_lanes < 4) ? BIT(6) : 0); + + setbits_le32(rcar->app_base + PCIEPWRMNGCTRL, + APP_CLK_REQ_N | APP_CLK_PM_EN); +} + +static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar) +{ + int ret; + + ret = clk_prepare_enable(rcar->core_clk); + if (ret) + return ret; + + ret = reset_assert(&rcar->pwr_rst); + if (ret) + goto err_unprepare; + + setbits_le32(rcar->app_base + PCIEMSR0, + DEVICE_TYPE_RC | + ((rcar->num_lanes < 4) ? BIFUR_MOD_SET_ON : 0)); + + ret = reset_deassert(&rcar->pwr_rst); + if (ret) + goto err_unprepare; + + rcar_gen4_pcie_additional_common_init(rcar); + + return 0; + +err_unprepare: + clk_disable_unprepare(rcar->core_clk); + + return ret; +} + +/* Host mode */ +static int rcar_gen4_pcie_host_init(struct udevice *dev) +{ + struct rcar_gen4_pcie *rcar = dev_get_priv(dev); + int ret; + + dm_gpio_set_value(&rcar->pe_rst, 1); + + ret = rcar_gen4_pcie_common_init(rcar); + if (ret) + return ret; + + /* + * According to the section 3.5.7.2 "RC Mode" in DWC PCIe Dual Mode + * Rev.5.20a and 3.5.6.1 "RC mode" in DWC PCIe RC databook v5.20a, we + * should disable two BARs to avoid unnecessary memory assignment + * during device enumeration. + */ + writel(0x0, rcar->dbi2_base + PCI_BASE_ADDRESS_0); + writel(0x0, rcar->dbi2_base + PCI_BASE_ADDRESS_1); + + /* Disable MSI interrupt signal */ + clrbits_le32(rcar->app_base + PCIEINTSTS0EN, MSI_CTRL_INT); + + mdelay(PCIE_T_PVPERL_MS); /* pe_rst requires 100msec delay */ + + dm_gpio_set_value(&rcar->pe_rst, 0); + + return 0; +} + +static int rcar_gen4_pcie_load_firmware(struct rcar_gen4_pcie *rcar) +{ + ulong addr, size; + int ret; + + /* + * Run user specified firmware loading script, which loads the + * firmware from whichever location the user decides it should + * load the firmware from, by whatever means the user decides. + */ + ret = run_command_list("run renesas_rcar_gen4_load_firmware", -1, 0); + if (ret) { + printf("Firmware loading script 'renesas_rcar_gen4_load_firmware' not defined or failed.\n"); + goto fail; + } + + /* Find out where the firmware got loaded and how long it is. */ + addr = env_get_hex("renesas_rcar_gen4_load_firmware_addr", 0); + size = env_get_hex("renesas_rcar_gen4_load_firmware_size", 0); + + /* + * Clear the variables set by the firmware loading script, as + * their content would become stale once this function exits. + */ + env_set("renesas_rcar_gen4_load_firmware_addr", NULL); + env_set("renesas_rcar_gen4_load_firmware_size", NULL); + + if (!addr || !size) { + printf("Firmware address (%lx) or size (%lx) are invalid.\n", addr, size); + goto fail; + } + + /* Create local copy of the loaded firmware. */ + rcar->firmware = (u16 *)memdup((void *)addr, size); + if (!rcar->firmware) + return -ENOMEM; + + rcar->firmware_size = size; + + return 0; + +fail: + printf("Define 'renesas_rcar_gen4_load_firmware' script which loads the R-Car\n" + "Gen4 PCIe controller firmware from storage into memory and sets these\n" + "two environment variables:\n" + " renesas_rcar_gen4_load_firmware_addr ... address of firmware in memory\n" + " renesas_rcar_gen4_load_firmware_size ... length of firmware in bytes\n" + "\n" + "Example:\n" + " => env set renesas_rcar_gen4_load_firmware 'env set renesas_rcar_gen4_load_firmware_addr 0x54000000 && load mmc 0:1 ${renesas_rcar_gen4_load_firmware_addr} lib/firmware/rcar_gen4_pcie.bin && env set renesas_rcar_gen4_load_firmware_size ${filesize}'\n" + ); + return -EINVAL; +} + +/** + * rcar_gen4_pcie_probe() - Probe the PCIe bus for active link + * + * @dev: A pointer to the device being operated on + * + * Probe for an active link on the PCIe bus and configure the controller + * to enable this port. + * + * Return: 0 on success, else -ENODEV + */ +static int rcar_gen4_pcie_probe(struct udevice *dev) +{ + struct rcar_gen4_pcie *rcar = dev_get_priv(dev); + struct udevice *ctlr = pci_get_controller(dev); + struct pci_controller *hose = dev_get_uclass_priv(ctlr); + int ret; + + ret = rcar_gen4_pcie_load_firmware(rcar); + if (ret) + return ret; + + rcar->dw.first_busno = dev_seq(dev); + rcar->dw.dev = dev; + + ret = reset_get_by_name(dev, "pwr", &rcar->pwr_rst); + if (ret) + return ret; + + rcar->core_clk = devm_clk_get(dev, "core"); + if (IS_ERR(rcar->core_clk)) + return PTR_ERR(rcar->core_clk); + + rcar->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(rcar->ref_clk)) + return PTR_ERR(rcar->ref_clk); + + ret = clk_prepare_enable(rcar->ref_clk); + if (ret) + return ret; + + ret = gpio_request_by_name(dev, "reset-gpios", 0, &rcar->pe_rst, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); + if (ret) + return ret; + + ret = rcar_gen4_pcie_host_init(dev); + if (ret) + return ret; + + pcie_dw_setup_host(&rcar->dw); + + dw_pcie_dbi_write_enable(&rcar->dw, true); + + dw_pcie_link_set_max_link_width(&rcar->dw, rcar->num_lanes); + + ret = rcar_gen4_pcie_start_link(rcar); + if (ret) + return ret; + + dw_pcie_dbi_write_enable(&rcar->dw, false); + + if (!rcar_gen4_pcie_link_up(rcar)) { + printf("PCIE-%d: Link down\n", dev_seq(dev)); + return -ENODEV; + } + + printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", dev_seq(dev), + pcie_dw_get_link_speed(&rcar->dw), + pcie_dw_get_link_width(&rcar->dw), + hose->first_busno); + + pcie_dw_prog_outbound_atu_unroll(&rcar->dw, PCIE_ATU_REGION_INDEX0, + PCIE_ATU_TYPE_MEM, + rcar->dw.mem.phys_start, + rcar->dw.mem.bus_start, rcar->dw.mem.size); + + return 0; +} + +/** + * rcar_gen4_pcie_of_to_plat() - Translate from DT to device state + * + * @dev: A pointer to the device being operated on + * + * Translate relevant data from the device tree pertaining to device @dev into + * state that the driver will later make use of. This state is stored in the + * device's private data structure. + * + * Return: 0 on success, else -EINVAL + */ +static int rcar_gen4_pcie_of_to_plat(struct udevice *dev) +{ + struct rcar_gen4_pcie *rcar = dev_get_priv(dev); + + /* Get the controller base address */ + rcar->dw.dbi_base = (void *)dev_read_addr_name(dev, "dbi"); + if ((fdt_addr_t)rcar->dw.dbi_base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Get the config space base address and size */ + rcar->dw.cfg_base = (void *)dev_read_addr_size_name(dev, "config", + &rcar->dw.cfg_size); + if ((fdt_addr_t)rcar->dw.cfg_base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Get the iATU base address and size */ + rcar->dw.atu_base = (void *)dev_read_addr_name(dev, "atu"); + if ((fdt_addr_t)rcar->dw.atu_base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Get the PHY base address and size */ + rcar->phy_base = (void *)dev_read_addr_name(dev, "phy"); + if ((fdt_addr_t)rcar->phy_base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Get the app base address and size */ + rcar->app_base = (void *)dev_read_addr_name(dev, "app"); + if ((fdt_addr_t)rcar->app_base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Get the dbi2 base address and size */ + rcar->dbi2_base = (void *)dev_read_addr_name(dev, "dbi2"); + if ((fdt_addr_t)rcar->dbi2_base == FDT_ADDR_T_NONE) + return -EINVAL; + + rcar->max_link_speed = + clamp(dev_read_u32_default(dev, "max-link-speed", + LINK_SPEED_GEN_4), + LINK_SPEED_GEN_1, RCAR_MAX_LINK_SPEED); + + rcar->num_lanes = dev_read_u32_default(dev, "num-lanes", 4); + + return 0; +} + +static const struct dm_pci_ops rcar_gen4_pcie_ops = { + .read_config = pcie_dw_read_config, + .write_config = pcie_dw_write_config, +}; + +static const struct udevice_id rcar_gen4_pcie_ids[] = { + { .compatible = "renesas,rcar-gen4-pcie" }, + { } +}; + +U_BOOT_DRIVER(rcar_gen4_pcie) = { + .name = "rcar_gen4_pcie", + .id = UCLASS_PCI, + .of_match = rcar_gen4_pcie_ids, + .ops = &rcar_gen4_pcie_ops, + .of_to_plat = rcar_gen4_pcie_of_to_plat, + .probe = rcar_gen4_pcie_probe, + .priv_auto = sizeof(struct rcar_gen4_pcie), +}; |