// 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(1, 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;

	mdelay(1);

	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;

	reset_status(&rcar->pwr_rst);
	mdelay(1);

	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));
		rcar_gen4_pcie_ltssm_control(rcar, false);
		dm_gpio_set_value(&rcar->pe_rst, 1);
		reset_assert(&rcar->pwr_rst);
		clk_disable_unprepare(rcar->ref_clk);
		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_remove() - Stop the PCIe bus active link
 * @dev: A pointer to the device being operated on
 *
 * Stop an active link on the PCIe bus and deconfigure the controller.
 *
 * Return: 0 on success, else -ENODEV
 */
static int rcar_gen4_pcie_remove(struct udevice *dev)
{
	struct rcar_gen4_pcie *rcar = dev_get_priv(dev);

	rcar_gen4_pcie_ltssm_control(rcar, false);
	dm_gpio_set_value(&rcar->pe_rst, 1);
	reset_assert(&rcar->pwr_rst);
	clk_disable_unprepare(rcar->ref_clk);

	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,
	.remove		= rcar_gen4_pcie_remove,
	.priv_auto	= sizeof(struct rcar_gen4_pcie),
	.flags		= DM_FLAG_ACTIVE_DMA,
};
