|  | /* | 
|  | * Rockchip PCIe PHY driver | 
|  | * | 
|  | * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com> | 
|  | * Copyright (C) 2016 ROCKCHIP, Inc. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License as published by | 
|  | * the Free Software Foundation; either version 2 of the License. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/mfd/syscon.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/of_platform.h> | 
|  | #include <linux/phy/phy.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/reset.h> | 
|  |  | 
|  | /* | 
|  | * 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_A_STATUS     0x30 | 
|  | #define PHY_LANE_B_STATUS     0x31 | 
|  | #define PHY_LANE_C_STATUS     0x32 | 
|  | #define PHY_LANE_D_STATUS     0x33 | 
|  | #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_data { | 
|  | unsigned int pcie_conf; | 
|  | unsigned int pcie_status; | 
|  | unsigned int pcie_laneoff; | 
|  | }; | 
|  |  | 
|  | struct rockchip_pcie_phy { | 
|  | struct rockchip_pcie_data *phy_data; | 
|  | struct regmap *reg_base; | 
|  | struct reset_control *phy_rst; | 
|  | struct clk *clk_pciephy_ref; | 
|  | }; | 
|  |  | 
|  | static inline void phy_wr_cfg(struct rockchip_pcie_phy *rk_phy, | 
|  | u32 addr, u32 data) | 
|  | { | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(data, | 
|  | PHY_CFG_DATA_MASK, | 
|  | PHY_CFG_DATA_SHIFT) | | 
|  | HIWORD_UPDATE(addr, | 
|  | PHY_CFG_ADDR_MASK, | 
|  | PHY_CFG_ADDR_SHIFT)); | 
|  | udelay(1); | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(PHY_CFG_WR_ENABLE, | 
|  | PHY_CFG_WR_MASK, | 
|  | PHY_CFG_WR_SHIFT)); | 
|  | udelay(1); | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(PHY_CFG_WR_DISABLE, | 
|  | PHY_CFG_WR_MASK, | 
|  | PHY_CFG_WR_SHIFT)); | 
|  | } | 
|  |  | 
|  | static inline u32 phy_rd_cfg(struct rockchip_pcie_phy *rk_phy, | 
|  | u32 addr) | 
|  | { | 
|  | u32 val; | 
|  |  | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(addr, | 
|  | PHY_CFG_RD_MASK, | 
|  | PHY_CFG_ADDR_SHIFT)); | 
|  | regmap_read(rk_phy->reg_base, | 
|  | rk_phy->phy_data->pcie_status, | 
|  | &val); | 
|  | return val; | 
|  | } | 
|  |  | 
|  | static int rockchip_pcie_phy_power_off(struct phy *phy) | 
|  | { | 
|  | struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); | 
|  | int err = 0; | 
|  |  | 
|  | err = reset_control_assert(rk_phy->phy_rst); | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "assert phy_rst err %d\n", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rockchip_pcie_phy_power_on(struct phy *phy) | 
|  | { | 
|  | struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); | 
|  | int err = 0; | 
|  | u32 status; | 
|  | unsigned long timeout; | 
|  |  | 
|  | err = reset_control_deassert(rk_phy->phy_rst); | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "deassert phy_rst err %d\n", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(PHY_CFG_PLL_LOCK, | 
|  | PHY_CFG_ADDR_MASK, | 
|  | PHY_CFG_ADDR_SHIFT)); | 
|  |  | 
|  | /* | 
|  | * No documented timeout value for phy operation below, | 
|  | * so we make it large enough here. And we use loop-break | 
|  | * method which should not be harmful. | 
|  | */ | 
|  | timeout = jiffies + msecs_to_jiffies(1000); | 
|  |  | 
|  | err = -EINVAL; | 
|  | while (time_before(jiffies, timeout)) { | 
|  | regmap_read(rk_phy->reg_base, | 
|  | rk_phy->phy_data->pcie_status, | 
|  | &status); | 
|  | if (status & PHY_PLL_LOCKED) { | 
|  | dev_dbg(&phy->dev, "pll locked!\n"); | 
|  | err = 0; | 
|  | break; | 
|  | } | 
|  | msleep(20); | 
|  | } | 
|  |  | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "pll lock timeout!\n"); | 
|  | goto err_pll_lock; | 
|  | } | 
|  |  | 
|  | phy_wr_cfg(rk_phy, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE); | 
|  | phy_wr_cfg(rk_phy, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M); | 
|  |  | 
|  | err = -ETIMEDOUT; | 
|  | while (time_before(jiffies, timeout)) { | 
|  | regmap_read(rk_phy->reg_base, | 
|  | rk_phy->phy_data->pcie_status, | 
|  | &status); | 
|  | if (!(status & PHY_PLL_OUTPUT)) { | 
|  | dev_dbg(&phy->dev, "pll output enable done!\n"); | 
|  | err = 0; | 
|  | break; | 
|  | } | 
|  | msleep(20); | 
|  | } | 
|  |  | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "pll output enable timeout!\n"); | 
|  | goto err_pll_lock; | 
|  | } | 
|  |  | 
|  | regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, | 
|  | HIWORD_UPDATE(PHY_CFG_PLL_LOCK, | 
|  | PHY_CFG_ADDR_MASK, | 
|  | PHY_CFG_ADDR_SHIFT)); | 
|  | err = -EINVAL; | 
|  | while (time_before(jiffies, timeout)) { | 
|  | regmap_read(rk_phy->reg_base, | 
|  | rk_phy->phy_data->pcie_status, | 
|  | &status); | 
|  | if (status & PHY_PLL_LOCKED) { | 
|  | dev_dbg(&phy->dev, "pll relocked!\n"); | 
|  | err = 0; | 
|  | break; | 
|  | } | 
|  | msleep(20); | 
|  | } | 
|  |  | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "pll relock timeout!\n"); | 
|  | goto err_pll_lock; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_pll_lock: | 
|  | reset_control_assert(rk_phy->phy_rst); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int rockchip_pcie_phy_init(struct phy *phy) | 
|  | { | 
|  | struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); | 
|  | int err = 0; | 
|  |  | 
|  | err = clk_prepare_enable(rk_phy->clk_pciephy_ref); | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "Fail to enable pcie ref clock.\n"); | 
|  | goto err_refclk; | 
|  | } | 
|  |  | 
|  | err = reset_control_assert(rk_phy->phy_rst); | 
|  | if (err) { | 
|  | dev_err(&phy->dev, "assert phy_rst err %d\n", err); | 
|  | goto err_reset; | 
|  | } | 
|  |  | 
|  | return err; | 
|  |  | 
|  | err_reset: | 
|  | clk_disable_unprepare(rk_phy->clk_pciephy_ref); | 
|  | err_refclk: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int rockchip_pcie_phy_exit(struct phy *phy) | 
|  | { | 
|  | struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); | 
|  |  | 
|  | clk_disable_unprepare(rk_phy->clk_pciephy_ref); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct phy_ops ops = { | 
|  | .init		= rockchip_pcie_phy_init, | 
|  | .exit		= rockchip_pcie_phy_exit, | 
|  | .power_on	= rockchip_pcie_phy_power_on, | 
|  | .power_off	= rockchip_pcie_phy_power_off, | 
|  | .owner		= THIS_MODULE, | 
|  | }; | 
|  |  | 
|  | static const struct rockchip_pcie_data rk3399_pcie_data = { | 
|  | .pcie_conf = 0xe220, | 
|  | .pcie_status = 0xe2a4, | 
|  | .pcie_laneoff = 0xe214, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id rockchip_pcie_phy_dt_ids[] = { | 
|  | { | 
|  | .compatible = "rockchip,rk3399-pcie-phy", | 
|  | .data = &rk3399_pcie_data, | 
|  | }, | 
|  | {} | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(of, rockchip_pcie_phy_dt_ids); | 
|  |  | 
|  | static int rockchip_pcie_phy_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct rockchip_pcie_phy *rk_phy; | 
|  | struct phy *generic_phy; | 
|  | struct phy_provider *phy_provider; | 
|  | struct regmap *grf; | 
|  | const struct of_device_id *of_id; | 
|  |  | 
|  | grf = syscon_node_to_regmap(dev->parent->of_node); | 
|  | if (IS_ERR(grf)) { | 
|  | dev_err(dev, "Cannot find GRF syscon\n"); | 
|  | return PTR_ERR(grf); | 
|  | } | 
|  |  | 
|  | rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); | 
|  | if (!rk_phy) | 
|  | return -ENOMEM; | 
|  |  | 
|  | of_id = of_match_device(rockchip_pcie_phy_dt_ids, &pdev->dev); | 
|  | if (!of_id) | 
|  | return -EINVAL; | 
|  |  | 
|  | rk_phy->phy_data = (struct rockchip_pcie_data *)of_id->data; | 
|  | rk_phy->reg_base = grf; | 
|  |  | 
|  | rk_phy->phy_rst = devm_reset_control_get(dev, "phy"); | 
|  | if (IS_ERR(rk_phy->phy_rst)) { | 
|  | if (PTR_ERR(rk_phy->phy_rst) != -EPROBE_DEFER) | 
|  | dev_err(dev, | 
|  | "missing phy property for reset controller\n"); | 
|  | return PTR_ERR(rk_phy->phy_rst); | 
|  | } | 
|  |  | 
|  | rk_phy->clk_pciephy_ref = devm_clk_get(dev, "refclk"); | 
|  | if (IS_ERR(rk_phy->clk_pciephy_ref)) { | 
|  | dev_err(dev, "refclk not found.\n"); | 
|  | return PTR_ERR(rk_phy->clk_pciephy_ref); | 
|  | } | 
|  |  | 
|  | generic_phy = devm_phy_create(dev, dev->of_node, &ops); | 
|  | if (IS_ERR(generic_phy)) { | 
|  | dev_err(dev, "failed to create PHY\n"); | 
|  | return PTR_ERR(generic_phy); | 
|  | } | 
|  |  | 
|  | phy_set_drvdata(generic_phy, rk_phy); | 
|  | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | 
|  |  | 
|  | return PTR_ERR_OR_ZERO(phy_provider); | 
|  | } | 
|  |  | 
|  | static struct platform_driver rockchip_pcie_driver = { | 
|  | .probe		= rockchip_pcie_phy_probe, | 
|  | .driver		= { | 
|  | .name	= "rockchip-pcie-phy", | 
|  | .of_match_table = rockchip_pcie_phy_dt_ids, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(rockchip_pcie_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>"); | 
|  | MODULE_DESCRIPTION("Rockchip PCIe PHY driver"); | 
|  | MODULE_LICENSE("GPL v2"); |