1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Renesas R-Car Gen3 PCIe PHY driver |
4 | * |
5 | * Copyright (C) 2018 Cogent Embedded, Inc. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/io.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/spinlock.h> |
15 | |
16 | #define PHY_CTRL 0x4000 /* R8A77980 only */ |
17 | |
18 | /* PHY control register (PHY_CTRL) */ |
19 | #define PHY_CTRL_PHY_PWDN BIT(2) |
20 | |
21 | struct rcar_gen3_phy { |
22 | struct phy *phy; |
23 | spinlock_t lock; |
24 | void __iomem *base; |
25 | }; |
26 | |
27 | static void rcar_gen3_phy_pcie_modify_reg(struct phy *p, unsigned int reg, |
28 | u32 clear, u32 set) |
29 | { |
30 | struct rcar_gen3_phy *phy = phy_get_drvdata(phy: p); |
31 | void __iomem *base = phy->base; |
32 | unsigned long flags; |
33 | u32 value; |
34 | |
35 | spin_lock_irqsave(&phy->lock, flags); |
36 | |
37 | value = readl(addr: base + reg); |
38 | value &= ~clear; |
39 | value |= set; |
40 | writel(val: value, addr: base + reg); |
41 | |
42 | spin_unlock_irqrestore(lock: &phy->lock, flags); |
43 | } |
44 | |
45 | static int r8a77980_phy_pcie_power_on(struct phy *p) |
46 | { |
47 | /* Power on the PCIe PHY */ |
48 | rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, PHY_CTRL_PHY_PWDN, set: 0); |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int r8a77980_phy_pcie_power_off(struct phy *p) |
54 | { |
55 | /* Power off the PCIe PHY */ |
56 | rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, clear: 0, PHY_CTRL_PHY_PWDN); |
57 | |
58 | return 0; |
59 | } |
60 | |
61 | static const struct phy_ops r8a77980_phy_pcie_ops = { |
62 | .power_on = r8a77980_phy_pcie_power_on, |
63 | .power_off = r8a77980_phy_pcie_power_off, |
64 | .owner = THIS_MODULE, |
65 | }; |
66 | |
67 | static const struct of_device_id rcar_gen3_phy_pcie_match_table[] = { |
68 | { .compatible = "renesas,r8a77980-pcie-phy" }, |
69 | { } |
70 | }; |
71 | MODULE_DEVICE_TABLE(of, rcar_gen3_phy_pcie_match_table); |
72 | |
73 | static int rcar_gen3_phy_pcie_probe(struct platform_device *pdev) |
74 | { |
75 | struct device *dev = &pdev->dev; |
76 | struct phy_provider *provider; |
77 | struct rcar_gen3_phy *phy; |
78 | void __iomem *base; |
79 | int error; |
80 | |
81 | if (!dev->of_node) { |
82 | dev_err(dev, |
83 | "This driver must only be instantiated from the device tree\n" ); |
84 | return -EINVAL; |
85 | } |
86 | |
87 | base = devm_platform_ioremap_resource(pdev, index: 0); |
88 | if (IS_ERR(ptr: base)) |
89 | return PTR_ERR(ptr: base); |
90 | |
91 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
92 | if (!phy) |
93 | return -ENOMEM; |
94 | |
95 | spin_lock_init(&phy->lock); |
96 | |
97 | phy->base = base; |
98 | |
99 | /* |
100 | * devm_phy_create() will call pm_runtime_enable(&phy->dev); |
101 | * And then, phy-core will manage runtime PM for this device. |
102 | */ |
103 | pm_runtime_enable(dev); |
104 | |
105 | phy->phy = devm_phy_create(dev, NULL, ops: &r8a77980_phy_pcie_ops); |
106 | if (IS_ERR(ptr: phy->phy)) { |
107 | dev_err(dev, "Failed to create PCIe PHY\n" ); |
108 | error = PTR_ERR(ptr: phy->phy); |
109 | goto error; |
110 | } |
111 | phy_set_drvdata(phy: phy->phy, data: phy); |
112 | |
113 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
114 | if (IS_ERR(ptr: provider)) { |
115 | dev_err(dev, "Failed to register PHY provider\n" ); |
116 | error = PTR_ERR(ptr: provider); |
117 | goto error; |
118 | } |
119 | |
120 | return 0; |
121 | |
122 | error: |
123 | pm_runtime_disable(dev); |
124 | |
125 | return error; |
126 | } |
127 | |
128 | static void rcar_gen3_phy_pcie_remove(struct platform_device *pdev) |
129 | { |
130 | pm_runtime_disable(dev: &pdev->dev); |
131 | }; |
132 | |
133 | static struct platform_driver rcar_gen3_phy_driver = { |
134 | .driver = { |
135 | .name = "phy_rcar_gen3_pcie" , |
136 | .of_match_table = rcar_gen3_phy_pcie_match_table, |
137 | }, |
138 | .probe = rcar_gen3_phy_pcie_probe, |
139 | .remove_new = rcar_gen3_phy_pcie_remove, |
140 | }; |
141 | |
142 | module_platform_driver(rcar_gen3_phy_driver); |
143 | |
144 | MODULE_LICENSE("GPL v2" ); |
145 | MODULE_DESCRIPTION("Renesas R-Car Gen3 PCIe PHY" ); |
146 | MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>" ); |
147 | |