text: // SPDX-License-Identifier: (GPL-2.0-only)
  /*
   * Rockchip PCIe PHY driver
   *
   * Copyright (C) 2020 Amarula Solutions(India)
   * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com>
   * Copyright (C) 2016 ROCKCHIP, Inc.
   */
  
  #include <common.h>
  #include <clk.h>
  #include <dm.h>
  #include <asm/global_data.h>
  #include <dm/device_compat.h>
  #include <generic-phy.h>
  #include <reset.h>
  #include <syscon.h>
  #include <asm/gpio.h>
  #include <asm/io.h>
  #include <linux/iopoll.h>
  #include <asm/arch-rockchip/clock.h>
  
  DECLARE_GLOBAL_DATA_PTR;
  
  /*
   * The higher 16-bit of this register is used for write protection
   * only if BIT(x + 16) set to 1 the BIT(x) can be written.
   */
  #define HIWORD_UPDATE(val, mask, shift) \
  		((val) << (shift) | (mask) << ((shift) + 16))
  
  #define PHY_MAX_LANE_NUM      4
  #define PHY_CFG_DATA_SHIFT    7
  #define PHY_CFG_ADDR_SHIFT    1
  #define PHY_CFG_DATA_MASK     0xf
  #define PHY_CFG_ADDR_MASK     0x3f
  #define PHY_CFG_RD_MASK       0x3ff
  #define PHY_CFG_WR_ENABLE     1
  #define PHY_CFG_WR_DISABLE    1
  #define PHY_CFG_WR_SHIFT      0
  #define PHY_CFG_WR_MASK       1
  #define PHY_CFG_PLL_LOCK      0x10
  #define PHY_CFG_CLK_TEST      0x10
  #define PHY_CFG_CLK_SCC       0x12
  #define PHY_CFG_SEPE_RATE     BIT(3)
  #define PHY_CFG_PLL_100M      BIT(3)
  #define PHY_PLL_LOCKED        BIT(9)
  #define PHY_PLL_OUTPUT        BIT(10)
  #define PHY_LANE_RX_DET_SHIFT 11
  #define PHY_LANE_RX_DET_TH    0x1
  #define PHY_LANE_IDLE_OFF     0x1
  #define PHY_LANE_IDLE_MASK    0x1
  #define PHY_LANE_IDLE_A_SHIFT 3
  #define PHY_LANE_IDLE_B_SHIFT 4
  #define PHY_LANE_IDLE_C_SHIFT 5
  #define PHY_LANE_IDLE_D_SHIFT 6
  
  struct rockchip_pcie_phy_data {
  	unsigned int pcie_conf;
  	unsigned int pcie_status;
  	unsigned int pcie_laneoff;
  };
  
  struct rockchip_pcie_phy {
  	void *reg_base;
  	struct clk refclk;
  	struct reset_ctl phy_rst;
  	const struct rockchip_pcie_phy_data *data;
  };
  
  static void phy_wr_cfg(struct rockchip_pcie_phy *priv, u32 addr, u32 data)
  {
  	u32 reg;
  
  	reg = HIWORD_UPDATE(data, PHY_CFG_DATA_MASK, PHY_CFG_DATA_SHIFT);
  	reg |= HIWORD_UPDATE(addr, PHY_CFG_ADDR_MASK, PHY_CFG_ADDR_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_conf);
  
  	udelay(1);
  
  	reg = HIWORD_UPDATE(PHY_CFG_WR_ENABLE,
  			    PHY_CFG_WR_MASK,
  			    PHY_CFG_WR_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_conf);
  
  	udelay(1);
  
  	reg = HIWORD_UPDATE(PHY_CFG_WR_DISABLE,
  			    PHY_CFG_WR_MASK,
  			    PHY_CFG_WR_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_conf);
  }
  
  static int rockchip_pcie_phy_power_on(struct phy *phy)
  {
  	struct rockchip_pcie_phy *priv = dev_get_priv(phy->dev);
  	int ret = 0;
  	u32 reg, status;
  
  	ret = reset_deassert(&priv->phy_rst);
  	if (ret) {
  		dev_err(phy->dev, "failed to assert phy reset\n");
  		return ret;
  	}
  
  	reg = HIWORD_UPDATE(PHY_CFG_PLL_LOCK,
  			    PHY_CFG_ADDR_MASK,
  			    PHY_CFG_ADDR_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_conf);
  
  	reg = HIWORD_UPDATE(!PHY_LANE_IDLE_OFF,
  			    PHY_LANE_IDLE_MASK,
  			    PHY_LANE_IDLE_A_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_laneoff);
  
  	ret = -EINVAL;
  	ret = readl_poll_sleep_timeout(priv->reg_base + priv->data->pcie_status,
  				       status,
  				       status & PHY_PLL_LOCKED,
  				       20 * 1000,
  				       50);
  	if (ret) {
  		dev_err(phy->dev, "pll lock timeout!\n");
  		goto err_pll_lock;
  	}
  
  	phy_wr_cfg(priv, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE);
  	phy_wr_cfg(priv, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M);
  
  	ret = -ETIMEDOUT;
  	ret = readl_poll_sleep_timeout(priv->reg_base + priv->data->pcie_status,
  				       status,
  				       !(status & PHY_PLL_OUTPUT),
  				       20 * 1000,
  				       50);
  	if (ret) {
  		dev_err(phy->dev, "pll output enable timeout!\n");
  		goto err_pll_lock;
  	}
  
  	reg = HIWORD_UPDATE(PHY_CFG_PLL_LOCK,
  			    PHY_CFG_ADDR_MASK,
  			    PHY_CFG_ADDR_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_conf);
  
  	ret = -EINVAL;
  	ret = readl_poll_sleep_timeout(priv->reg_base + priv->data->pcie_status,
  				       status,
  				       status & PHY_PLL_LOCKED,
  				       20 * 1000,
  				       50);
  	if (ret) {
  		dev_err(phy->dev, "pll relock timeout!\n");
  		goto err_pll_lock;
  	}
  
  	return 0;
  
  err_pll_lock:
  	reset_assert(&priv->phy_rst);
  	return ret;
  }
  
  static int rockchip_pcie_phy_power_off(struct phy *phy)
  {
  	struct rockchip_pcie_phy *priv = dev_get_priv(phy->dev);
  	int ret;
  	u32 reg;
  
  	reg = HIWORD_UPDATE(PHY_LANE_IDLE_OFF,
  			    PHY_LANE_IDLE_MASK,
  			    PHY_LANE_IDLE_A_SHIFT);
  	writel(reg, priv->reg_base + priv->data->pcie_laneoff);
  
  	ret = reset_assert(&priv->phy_rst);
  	if (ret) {
  		dev_err(phy->dev, "failed to assert phy reset\n");
  		return ret;
  	}
  
  	return 0;
  }
  
  static int rockchip_pcie_phy_init(struct phy *phy)
  {
  	struct rockchip_pcie_phy *priv = dev_get_priv(phy->dev);
  	int ret;
  
  	ret = clk_enable(&priv->refclk);
  	if (ret) {
  		dev_err(phy->dev, "failed to enable refclk clock\n");
  		return ret;
  	}
  
  	ret = reset_assert(&priv->phy_rst);
  	if (ret) {
  		dev_err(phy->dev, "failed to assert phy reset\n");
  		goto err_reset;
  	}
  
  	return 0;
  
  err_reset:
  	clk_disable(&priv->refclk);
  	return ret;
  }
  
  static int rockchip_pcie_phy_exit(struct phy *phy)
  {
  	struct rockchip_pcie_phy *priv = dev_get_priv(phy->dev);
  
  	clk_disable(&priv->refclk);
  
  	return 0;
  }
  
  static struct phy_ops rockchip_pcie_phy_ops = {
  	.init = rockchip_pcie_phy_init,
  	.power_on = rockchip_pcie_phy_power_on,
  	.power_off = rockchip_pcie_phy_power_off,
  	.exit = rockchip_pcie_phy_exit,
  };
  
  static int rockchip_pcie_phy_probe(struct udevice *dev)
  {
  	struct rockchip_pcie_phy *priv = dev_get_priv(dev);
  	int ret;
  
  	priv->data = (const struct rockchip_pcie_phy_data *)
  						dev_get_driver_data(dev);
  	if (!priv->data)
  		return -EINVAL;
  
  	priv->reg_base = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
  
  	ret = clk_get_by_name(dev, "refclk", &priv->refclk);
  	if (ret) {
  		dev_err(dev, "failed to get refclk clock phandle\n");
  		return ret;
  	}
  
  	ret = reset_get_by_name(dev, "phy", &priv->phy_rst);
  	if (ret) {
  		dev_err(dev, "failed to get phy reset phandle\n");
  		return ret;
  	}
  
  	return 0;
  }
  
  static const struct rockchip_pcie_phy_data rk3399_pcie_data = {
  	.pcie_conf = 0xe220,
  	.pcie_status = 0xe2a4,
  	.pcie_laneoff = 0xe214,
  };
  
  static const struct udevice_id rockchip_pcie_phy_ids[] = {
  	{
  		.compatible = "rockchip,rk3399-pcie-phy",
  		.data = (ulong)&rk3399_pcie_data,
  	},
  	{ /* sentile */ }
  };
  
  U_BOOT_DRIVER(rockchip_pcie_phy) = {
  	.name	= "rockchip_pcie_phy",
  	.id	= UCLASS_PHY,
  	.of_match = rockchip_pcie_phy_ids,
  	.ops = &rockchip_pcie_phy_ops,
  	.probe = rockchip_pcie_phy_probe,
  	.priv_auto	= sizeof(struct rockchip_pcie_phy),
  };
  