// SPDX-License-Identifier: (GPL-2.0-only) /* * Rockchip PCIe PHY driver * * Copyright (C) 2020 Amarula Solutions(India) * Copyright (C) 2016 Shawn Lin * Copyright (C) 2016 ROCKCHIP, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include 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), };