// SPDX-License-Identifier: GPL-2.0+
/*
 * Amlogic DesignWare based PCIe host controller driver
 *
 * Copyright (c) 2021 BayLibre, SAS
 * Author: Neil Armstrong <narmstrong@baylibre.com>
 *
 * Based on pcie_dw_rockchip.c
 * Copyright (c) 2021 Rockchip, Inc.
 */

#include <clk.h>
#include <dm.h>
#include <generic-phy.h>
#include <pci.h>
#include <power-domain.h>
#include <reset.h>
#include <syscon.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <asm-generic/gpio.h>
#include <dm/device_compat.h>
#include <linux/iopoll.h>
#include <linux/delay.h>
#include <linux/log2.h>
#include <linux/bitfield.h>

#include "pcie_dw_common.h"

DECLARE_GLOBAL_DATA_PTR;

/**
 * struct meson_pcie - Amlogic Meson DW PCIe controller state
 *
 * @pci: The common PCIe DW structure
 * @meson_cfg_base: The base address of vendor regs
 * @phy
 * @clk_port
 * @clk_general
 * @clk_pclk
 * @rsts
 * @rst_gpio: The #PERST signal for slot
 */
struct meson_pcie {
	/* Must be first member of the struct */
	struct pcie_dw dw;
	void *meson_cfg_base;
	struct phy phy;
	struct clk clk_port;
	struct clk clk_general;
	struct clk clk_pclk;
	struct reset_ctl_bulk rsts;
	struct gpio_desc rst_gpio;
};

#define PCI_EXP_DEVCTL_PAYLOAD	0x00e0	/* Max_Payload_Size */

#define PCIE_CAP_MAX_PAYLOAD_SIZE(x)	((x) << 5)
#define PCIE_CAP_MAX_READ_REQ_SIZE(x)	((x) << 12)

/* PCIe specific config registers */
#define PCIE_CFG0			0x0
#define APP_LTSSM_ENABLE		BIT(7)

#define PCIE_CFG_STATUS12		0x30
#define IS_SMLH_LINK_UP(x)		((x) & (1 << 6))
#define IS_RDLH_LINK_UP(x)		((x) & (1 << 16))
#define IS_LTSSM_UP(x)			((((x) >> 10) & 0x1f) == 0x11)

#define PCIE_CFG_STATUS17		0x44
#define PM_CURRENT_STATE(x)		(((x) >> 7) & 0x1)

#define WAIT_LINKUP_TIMEOUT		4000
#define PORT_CLK_RATE			100000000UL
#define MAX_PAYLOAD_SIZE		256
#define MAX_READ_REQ_SIZE		256
#define PCIE_RESET_DELAY		500
#define PCIE_SHARED_RESET		1
#define PCIE_NORMAL_RESET		0

enum pcie_data_rate {
	PCIE_GEN1,
	PCIE_GEN2,
	PCIE_GEN3,
	PCIE_GEN4
};

/* Parameters for the waiting for #perst signal */
#define PERST_WAIT_US			1000000

static inline u32 meson_cfg_readl(struct meson_pcie *priv, u32 reg)
{
	return readl(priv->meson_cfg_base + reg);
}

static inline void meson_cfg_writel(struct meson_pcie *priv, u32 val, u32 reg)
{
	writel(val, priv->meson_cfg_base + reg);
}

/**
 * meson_pcie_configure() - Configure link
 *
 * @meson_pcie: Pointer to the PCI controller state
 *
 * Configure the link mode and width
 */
static void meson_pcie_configure(struct meson_pcie *priv)
{
	u32 val;

	dw_pcie_dbi_write_enable(&priv->dw, true);

	val = readl(priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);
	val &= ~PORT_LINK_FAST_LINK_MODE;
	val |= PORT_LINK_DLL_LINK_EN;
	val &= ~PORT_LINK_MODE_MASK;
	writel(val, priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);

	dw_pcie_link_set_max_link_width(&priv->dw, 1);

	dw_pcie_dbi_write_enable(&priv->dw, false);
}

static inline void meson_pcie_enable_ltssm(struct meson_pcie *priv)
{
	u32 val;

	val = meson_cfg_readl(priv, PCIE_CFG0);
	val |= APP_LTSSM_ENABLE;
	meson_cfg_writel(priv, val, PCIE_CFG0);
}

static int meson_pcie_wait_link_up(struct meson_pcie *priv)
{
	u32 speed_okay = 0;
	u32 cnt = 0;
	u32 state12, state17, smlh_up, ltssm_up, rdlh_up;

	do {
		state12 = meson_cfg_readl(priv, PCIE_CFG_STATUS12);
		state17 = meson_cfg_readl(priv, PCIE_CFG_STATUS17);
		smlh_up = IS_SMLH_LINK_UP(state12);
		rdlh_up = IS_RDLH_LINK_UP(state12);
		ltssm_up = IS_LTSSM_UP(state12);

		if (PM_CURRENT_STATE(state17) < PCIE_GEN3)
			speed_okay = 1;

		if (smlh_up)
			debug("%s: smlh_link_up is on\n", __func__);
		if (rdlh_up)
			debug("%s: rdlh_link_up is on\n", __func__);
		if (ltssm_up)
			debug("%s: ltssm_up is on\n", __func__);
		if (speed_okay)
			debug("%s: speed_okay\n", __func__);

		if (smlh_up && rdlh_up && ltssm_up && speed_okay)
			return 0;

		cnt++;

		udelay(10);
	} while (cnt < WAIT_LINKUP_TIMEOUT);

	printf("%s: error: wait linkup timeout\n", __func__);
	return -EIO;
}

/**
 * meson_pcie_link_up() - Wait for the link to come up
 *
 * @meson_pcie: Pointer to the PCI controller state
 * @cap_speed: Desired link speed
 *
 * Return: 1 (true) for active line and negative (false) for no link (timeout)
 */
static int meson_pcie_link_up(struct meson_pcie *priv, u32 cap_speed)
{
	/* DW link configurations */
	meson_pcie_configure(priv);

	/* Reset the device */
	if (dm_gpio_is_valid(&priv->rst_gpio)) {
		dm_gpio_set_value(&priv->rst_gpio, 1);
		/*
		 * Minimal is 100ms from spec but we see
		 * some wired devices need much more, such as 600ms.
		 * Add a enough delay to cover all cases.
		 */
		udelay(PERST_WAIT_US);
		dm_gpio_set_value(&priv->rst_gpio, 0);
	}

	/* Enable LTSSM */
	meson_pcie_enable_ltssm(priv);

	return meson_pcie_wait_link_up(priv);
}

static int meson_size_to_payload(int size)
{
	/*
	 * dwc supports 2^(val+7) payload size, which val is 0~5 default to 1.
	 * So if input size is not 2^order alignment or less than 2^7 or bigger
	 * than 2^12, just set to default size 2^(1+7).
	 */
	if (!is_power_of_2(size) || size < 128 || size > 4096) {
		debug("%s: payload size %d, set to default 256\n", __func__, size);
		return 1;
	}

	return fls(size) - 8;
}

static void meson_set_max_payload(struct meson_pcie *priv, int size)
{
	u32 val;
	u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
	int max_payload_size = meson_size_to_payload(size);

	dw_pcie_dbi_write_enable(&priv->dw, true);

	val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
	val &= ~PCI_EXP_DEVCTL_PAYLOAD;
	writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);

	val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
	val |= PCIE_CAP_MAX_PAYLOAD_SIZE(max_payload_size);
	writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);

	dw_pcie_dbi_write_enable(&priv->dw, false);
}

static void meson_set_max_rd_req_size(struct meson_pcie *priv, int size)
{
	u32 val;
	u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
	int max_rd_req_size = meson_size_to_payload(size);

	dw_pcie_dbi_write_enable(&priv->dw, true);

	val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
	val &= ~PCI_EXP_DEVCTL_PAYLOAD;
	writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);

	val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
	val |= PCIE_CAP_MAX_READ_REQ_SIZE(max_rd_req_size);
	writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);

	dw_pcie_dbi_write_enable(&priv->dw, false);
}

static int meson_pcie_init_port(struct udevice *dev)
{
	int ret;
	struct meson_pcie *priv = dev_get_priv(dev);

	ret = generic_phy_init(&priv->phy);
	if (ret) {
		dev_err(dev, "failed to init phy (ret=%d)\n", ret);
		return ret;
	}

	ret = generic_phy_power_on(&priv->phy);
	if (ret) {
		dev_err(dev, "failed to power on phy (ret=%d)\n", ret);
		goto err_exit_phy;
	}

	ret = generic_phy_reset(&priv->phy);
	if (ret) {
		dev_err(dev, "failed to reset phy (ret=%d)\n", ret);
		goto err_exit_phy;
	}

	ret = reset_assert_bulk(&priv->rsts);
	if (ret) {
		dev_err(dev, "failed to assert resets (ret=%d)\n", ret);
		goto err_power_off_phy;
	}

	udelay(PCIE_RESET_DELAY);

	ret = reset_deassert_bulk(&priv->rsts);
	if (ret) {
		dev_err(dev, "failed to deassert resets (ret=%d)\n", ret);
		goto err_power_off_phy;
	}

	udelay(PCIE_RESET_DELAY);

	ret = clk_set_rate(&priv->clk_port, PORT_CLK_RATE);
	if (ret) {
		dev_err(dev, "failed to set port clk rate (ret=%d)\n", ret);
		goto err_deassert_bulk;
	}

	ret = clk_enable(&priv->clk_general);
	if (ret) {
		dev_err(dev, "failed to enable clk general (ret=%d)\n", ret);
		goto err_deassert_bulk;
	}

	ret = clk_enable(&priv->clk_pclk);
	if (ret) {
		dev_err(dev, "failed to enable pclk (ret=%d)\n", ret);
		goto err_deassert_bulk;
	}

	meson_set_max_payload(priv, MAX_PAYLOAD_SIZE);
	meson_set_max_rd_req_size(priv, MAX_READ_REQ_SIZE);

	pcie_dw_setup_host(&priv->dw);

	meson_pcie_link_up(priv, LINK_SPEED_GEN_2);

	return 0;
err_deassert_bulk:
	reset_assert_bulk(&priv->rsts);
err_power_off_phy:
	generic_phy_power_off(&priv->phy);
err_exit_phy:
	generic_phy_exit(&priv->phy);

	return ret;
}

static int meson_pcie_parse_dt(struct udevice *dev)
{
	struct meson_pcie *priv = dev_get_priv(dev);
	int ret;

	priv->dw.dbi_base = dev_read_addr_index_ptr(dev, 0);
	if (!priv->dw.dbi_base)
		return -EINVAL;

	dev_dbg(dev, "ELBI address is 0x%p\n", priv->dw.dbi_base);

	priv->meson_cfg_base = dev_read_addr_index_ptr(dev, 1);
	if (!priv->meson_cfg_base)
		return -EINVAL;

	dev_dbg(dev, "CFG address is 0x%p\n", priv->meson_cfg_base);

	ret = gpio_request_by_name(dev, "reset-gpios", 0,
				   &priv->rst_gpio, GPIOD_IS_OUT);
	if (ret) {
		dev_err(dev, "failed to find reset-gpios property\n");
		return ret;
	}

	ret = reset_get_bulk(dev, &priv->rsts);
	if (ret) {
		dev_err(dev, "Can't get reset: %d\n", ret);
		return ret;
	}

	ret = clk_get_by_name(dev, "port", &priv->clk_port);
	if (ret) {
		dev_err(dev, "Can't get port clock: %d\n", ret);
		return ret;
	}

	ret = clk_get_by_name(dev, "general", &priv->clk_general);
	if (ret) {
		dev_err(dev, "Can't get port clock: %d\n", ret);
		return ret;
	}

	ret = clk_get_by_name(dev, "pclk", &priv->clk_pclk);
	if (ret) {
		dev_err(dev, "Can't get port clock: %d\n", ret);
		return ret;
	}

	ret = generic_phy_get_by_index(dev, 0, &priv->phy);
	if (ret) {
		dev_err(dev, "failed to get pcie phy (ret=%d)\n", ret);
		return ret;
	}

	return 0;
}

/**
 * meson_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 meson_pcie_probe(struct udevice *dev)
{
	struct meson_pcie *priv = dev_get_priv(dev);
	struct udevice *ctlr = pci_get_controller(dev);
	struct pci_controller *hose = dev_get_uclass_priv(ctlr);
	int ret = 0;

	priv->dw.first_busno = dev_seq(dev);
	priv->dw.dev = dev;

	ret = meson_pcie_parse_dt(dev);
	if (ret)
		return ret;

	ret = meson_pcie_init_port(dev);
	if (ret) {
		dm_gpio_free(dev, &priv->rst_gpio);
		return ret;
	}

	printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
	       dev_seq(dev), pcie_dw_get_link_speed(&priv->dw),
	       pcie_dw_get_link_width(&priv->dw),
	       hose->first_busno);

	return pcie_dw_prog_outbound_atu_unroll(&priv->dw,
						PCIE_ATU_REGION_INDEX0,
						PCIE_ATU_TYPE_MEM,
						priv->dw.mem.phys_start,
						priv->dw.mem.bus_start,
						priv->dw.mem.size);
}

static const struct dm_pci_ops meson_pcie_ops = {
	.read_config	= pcie_dw_read_config,
	.write_config	= pcie_dw_write_config,
};

static const struct udevice_id meson_pcie_ids[] = {
	{ .compatible = "amlogic,axg-pcie" },
	{ .compatible = "amlogic,g12a-pcie" },
	{ }
};

U_BOOT_DRIVER(meson_dw_pcie) = {
	.name			= "pcie_dw_meson",
	.id			= UCLASS_PCI,
	.of_match		= meson_pcie_ids,
	.ops			= &meson_pcie_ops,
	.probe			= meson_pcie_probe,
	.priv_auto		= sizeof(struct meson_pcie),
};
