12ce7f2f4SSergei Shtylyov // SPDX-License-Identifier: GPL-2.0 22ce7f2f4SSergei Shtylyov /* 32ce7f2f4SSergei Shtylyov * Renesas R-Car Gen3 PCIe PHY driver 42ce7f2f4SSergei Shtylyov * 52ce7f2f4SSergei Shtylyov * Copyright (C) 2018 Cogent Embedded, Inc. 62ce7f2f4SSergei Shtylyov */ 72ce7f2f4SSergei Shtylyov 82ce7f2f4SSergei Shtylyov #include <linux/clk.h> 92ce7f2f4SSergei Shtylyov #include <linux/io.h> 102ce7f2f4SSergei Shtylyov #include <linux/module.h> 112ce7f2f4SSergei Shtylyov #include <linux/of.h> 122ce7f2f4SSergei Shtylyov #include <linux/phy/phy.h> 132ce7f2f4SSergei Shtylyov #include <linux/of_device.h> 142ce7f2f4SSergei Shtylyov #include <linux/platform_device.h> 152ce7f2f4SSergei Shtylyov #include <linux/spinlock.h> 162ce7f2f4SSergei Shtylyov 172ce7f2f4SSergei Shtylyov #define PHY_CTRL 0x4000 /* R8A77980 only */ 182ce7f2f4SSergei Shtylyov 192ce7f2f4SSergei Shtylyov /* PHY control register (PHY_CTRL) */ 202ce7f2f4SSergei Shtylyov #define PHY_CTRL_PHY_PWDN BIT(2) 212ce7f2f4SSergei Shtylyov 222ce7f2f4SSergei Shtylyov struct rcar_gen3_phy { 232ce7f2f4SSergei Shtylyov struct phy *phy; 242ce7f2f4SSergei Shtylyov spinlock_t lock; 252ce7f2f4SSergei Shtylyov void __iomem *base; 262ce7f2f4SSergei Shtylyov }; 272ce7f2f4SSergei Shtylyov 282ce7f2f4SSergei Shtylyov static void rcar_gen3_phy_pcie_modify_reg(struct phy *p, unsigned int reg, 292ce7f2f4SSergei Shtylyov u32 clear, u32 set) 302ce7f2f4SSergei Shtylyov { 312ce7f2f4SSergei Shtylyov struct rcar_gen3_phy *phy = phy_get_drvdata(p); 322ce7f2f4SSergei Shtylyov void __iomem *base = phy->base; 332ce7f2f4SSergei Shtylyov unsigned long flags; 342ce7f2f4SSergei Shtylyov u32 value; 352ce7f2f4SSergei Shtylyov 362ce7f2f4SSergei Shtylyov spin_lock_irqsave(&phy->lock, flags); 372ce7f2f4SSergei Shtylyov 382ce7f2f4SSergei Shtylyov value = readl(base + reg); 392ce7f2f4SSergei Shtylyov value &= ~clear; 402ce7f2f4SSergei Shtylyov value |= set; 412ce7f2f4SSergei Shtylyov writel(value, base + reg); 422ce7f2f4SSergei Shtylyov 432ce7f2f4SSergei Shtylyov spin_unlock_irqrestore(&phy->lock, flags); 442ce7f2f4SSergei Shtylyov } 452ce7f2f4SSergei Shtylyov 462ce7f2f4SSergei Shtylyov static int r8a77980_phy_pcie_power_on(struct phy *p) 472ce7f2f4SSergei Shtylyov { 482ce7f2f4SSergei Shtylyov /* Power on the PCIe PHY */ 492ce7f2f4SSergei Shtylyov rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, PHY_CTRL_PHY_PWDN, 0); 502ce7f2f4SSergei Shtylyov 512ce7f2f4SSergei Shtylyov return 0; 522ce7f2f4SSergei Shtylyov } 532ce7f2f4SSergei Shtylyov 542ce7f2f4SSergei Shtylyov static int r8a77980_phy_pcie_power_off(struct phy *p) 552ce7f2f4SSergei Shtylyov { 562ce7f2f4SSergei Shtylyov /* Power off the PCIe PHY */ 572ce7f2f4SSergei Shtylyov rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, 0, PHY_CTRL_PHY_PWDN); 582ce7f2f4SSergei Shtylyov 592ce7f2f4SSergei Shtylyov return 0; 602ce7f2f4SSergei Shtylyov } 612ce7f2f4SSergei Shtylyov 622ce7f2f4SSergei Shtylyov static const struct phy_ops r8a77980_phy_pcie_ops = { 632ce7f2f4SSergei Shtylyov .power_on = r8a77980_phy_pcie_power_on, 642ce7f2f4SSergei Shtylyov .power_off = r8a77980_phy_pcie_power_off, 652ce7f2f4SSergei Shtylyov .owner = THIS_MODULE, 662ce7f2f4SSergei Shtylyov }; 672ce7f2f4SSergei Shtylyov 682ce7f2f4SSergei Shtylyov static const struct of_device_id rcar_gen3_phy_pcie_match_table[] = { 692ce7f2f4SSergei Shtylyov { .compatible = "renesas,r8a77980-pcie-phy" }, 702ce7f2f4SSergei Shtylyov { } 712ce7f2f4SSergei Shtylyov }; 722ce7f2f4SSergei Shtylyov MODULE_DEVICE_TABLE(of, rcar_gen3_phy_pcie_match_table); 732ce7f2f4SSergei Shtylyov 742ce7f2f4SSergei Shtylyov static int rcar_gen3_phy_pcie_probe(struct platform_device *pdev) 752ce7f2f4SSergei Shtylyov { 762ce7f2f4SSergei Shtylyov struct device *dev = &pdev->dev; 772ce7f2f4SSergei Shtylyov struct phy_provider *provider; 782ce7f2f4SSergei Shtylyov struct rcar_gen3_phy *phy; 792ce7f2f4SSergei Shtylyov void __iomem *base; 802ce7f2f4SSergei Shtylyov int error; 812ce7f2f4SSergei Shtylyov 822ce7f2f4SSergei Shtylyov if (!dev->of_node) { 832ce7f2f4SSergei Shtylyov dev_err(dev, 842ce7f2f4SSergei Shtylyov "This driver must only be instantiated from the device tree\n"); 852ce7f2f4SSergei Shtylyov return -EINVAL; 862ce7f2f4SSergei Shtylyov } 872ce7f2f4SSergei Shtylyov 880b5604afSChunfeng Yun base = devm_platform_ioremap_resource(pdev, 0); 892ce7f2f4SSergei Shtylyov if (IS_ERR(base)) 902ce7f2f4SSergei Shtylyov return PTR_ERR(base); 912ce7f2f4SSergei Shtylyov 922ce7f2f4SSergei Shtylyov phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); 932ce7f2f4SSergei Shtylyov if (!phy) 942ce7f2f4SSergei Shtylyov return -ENOMEM; 952ce7f2f4SSergei Shtylyov 962ce7f2f4SSergei Shtylyov spin_lock_init(&phy->lock); 972ce7f2f4SSergei Shtylyov 982ce7f2f4SSergei Shtylyov phy->base = base; 992ce7f2f4SSergei Shtylyov 1002ce7f2f4SSergei Shtylyov /* 1012ce7f2f4SSergei Shtylyov * devm_phy_create() will call pm_runtime_enable(&phy->dev); 1022ce7f2f4SSergei Shtylyov * And then, phy-core will manage runtime PM for this device. 1032ce7f2f4SSergei Shtylyov */ 1042ce7f2f4SSergei Shtylyov pm_runtime_enable(dev); 1052ce7f2f4SSergei Shtylyov 1062ce7f2f4SSergei Shtylyov phy->phy = devm_phy_create(dev, NULL, &r8a77980_phy_pcie_ops); 1072ce7f2f4SSergei Shtylyov if (IS_ERR(phy->phy)) { 1082ce7f2f4SSergei Shtylyov dev_err(dev, "Failed to create PCIe PHY\n"); 1092ce7f2f4SSergei Shtylyov error = PTR_ERR(phy->phy); 1102ce7f2f4SSergei Shtylyov goto error; 1112ce7f2f4SSergei Shtylyov } 1122ce7f2f4SSergei Shtylyov phy_set_drvdata(phy->phy, phy); 1132ce7f2f4SSergei Shtylyov 1142ce7f2f4SSergei Shtylyov provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 1152ce7f2f4SSergei Shtylyov if (IS_ERR(provider)) { 1162ce7f2f4SSergei Shtylyov dev_err(dev, "Failed to register PHY provider\n"); 1172ce7f2f4SSergei Shtylyov error = PTR_ERR(provider); 1182ce7f2f4SSergei Shtylyov goto error; 1192ce7f2f4SSergei Shtylyov } 1202ce7f2f4SSergei Shtylyov 1212ce7f2f4SSergei Shtylyov return 0; 1222ce7f2f4SSergei Shtylyov 1232ce7f2f4SSergei Shtylyov error: 1242ce7f2f4SSergei Shtylyov pm_runtime_disable(dev); 1252ce7f2f4SSergei Shtylyov 1262ce7f2f4SSergei Shtylyov return error; 1272ce7f2f4SSergei Shtylyov } 1282ce7f2f4SSergei Shtylyov 129*aba5c6f3SUwe Kleine-König static void rcar_gen3_phy_pcie_remove(struct platform_device *pdev) 1302ce7f2f4SSergei Shtylyov { 1312ce7f2f4SSergei Shtylyov pm_runtime_disable(&pdev->dev); 1322ce7f2f4SSergei Shtylyov }; 1332ce7f2f4SSergei Shtylyov 1342ce7f2f4SSergei Shtylyov static struct platform_driver rcar_gen3_phy_driver = { 1352ce7f2f4SSergei Shtylyov .driver = { 1362ce7f2f4SSergei Shtylyov .name = "phy_rcar_gen3_pcie", 1372ce7f2f4SSergei Shtylyov .of_match_table = rcar_gen3_phy_pcie_match_table, 1382ce7f2f4SSergei Shtylyov }, 1392ce7f2f4SSergei Shtylyov .probe = rcar_gen3_phy_pcie_probe, 140*aba5c6f3SUwe Kleine-König .remove_new = rcar_gen3_phy_pcie_remove, 1412ce7f2f4SSergei Shtylyov }; 1422ce7f2f4SSergei Shtylyov 1432ce7f2f4SSergei Shtylyov module_platform_driver(rcar_gen3_phy_driver); 1442ce7f2f4SSergei Shtylyov 1452ce7f2f4SSergei Shtylyov MODULE_LICENSE("GPL v2"); 1462ce7f2f4SSergei Shtylyov MODULE_DESCRIPTION("Renesas R-Car Gen3 PCIe PHY"); 1472ce7f2f4SSergei Shtylyov MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>"); 148