1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Amlogic AXG PCIE PHY driver |
4 | * |
5 | * Copyright (C) 2020 Remi Pommarel <repk@triplefau.lt> |
6 | */ |
7 | #include <linux/mod_devicetable.h> |
8 | #include <linux/module.h> |
9 | #include <linux/phy/phy.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/reset.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/bitfield.h> |
14 | #include <dt-bindings/phy/phy.h> |
15 | |
16 | #define MESON_PCIE_REG0 0x00 |
17 | #define MESON_PCIE_COMMON_CLK BIT(4) |
18 | #define MESON_PCIE_PORT_SEL GENMASK(3, 2) |
19 | #define MESON_PCIE_CLK BIT(1) |
20 | #define MESON_PCIE_POWERDOWN BIT(0) |
21 | |
22 | #define MESON_PCIE_TWO_X1 FIELD_PREP(MESON_PCIE_PORT_SEL, 0x3) |
23 | #define MESON_PCIE_COMMON_REF_CLK FIELD_PREP(MESON_PCIE_COMMON_CLK, 0x1) |
24 | #define MESON_PCIE_PHY_INIT (MESON_PCIE_TWO_X1 | \ |
25 | MESON_PCIE_COMMON_REF_CLK) |
26 | #define MESON_PCIE_RESET_DELAY 500 |
27 | |
28 | struct phy_axg_pcie_priv { |
29 | struct phy *phy; |
30 | struct phy *analog; |
31 | struct regmap *regmap; |
32 | struct reset_control *reset; |
33 | }; |
34 | |
35 | static const struct regmap_config phy_axg_pcie_regmap_conf = { |
36 | .reg_bits = 8, |
37 | .val_bits = 32, |
38 | .reg_stride = 4, |
39 | .max_register = MESON_PCIE_REG0, |
40 | }; |
41 | |
42 | static int phy_axg_pcie_power_on(struct phy *phy) |
43 | { |
44 | struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); |
45 | int ret; |
46 | |
47 | ret = phy_power_on(phy: priv->analog); |
48 | if (ret != 0) |
49 | return ret; |
50 | |
51 | regmap_update_bits(map: priv->regmap, MESON_PCIE_REG0, |
52 | MESON_PCIE_POWERDOWN, val: 0); |
53 | return 0; |
54 | } |
55 | |
56 | static int phy_axg_pcie_power_off(struct phy *phy) |
57 | { |
58 | struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); |
59 | int ret; |
60 | |
61 | ret = phy_power_off(phy: priv->analog); |
62 | if (ret != 0) |
63 | return ret; |
64 | |
65 | regmap_update_bits(map: priv->regmap, MESON_PCIE_REG0, |
66 | MESON_PCIE_POWERDOWN, val: 1); |
67 | return 0; |
68 | } |
69 | |
70 | static int phy_axg_pcie_init(struct phy *phy) |
71 | { |
72 | struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); |
73 | int ret; |
74 | |
75 | ret = phy_init(phy: priv->analog); |
76 | if (ret != 0) |
77 | return ret; |
78 | |
79 | regmap_write(map: priv->regmap, MESON_PCIE_REG0, MESON_PCIE_PHY_INIT); |
80 | return reset_control_reset(rstc: priv->reset); |
81 | } |
82 | |
83 | static int phy_axg_pcie_exit(struct phy *phy) |
84 | { |
85 | struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); |
86 | int ret; |
87 | |
88 | ret = phy_exit(phy: priv->analog); |
89 | if (ret != 0) |
90 | return ret; |
91 | |
92 | return reset_control_reset(rstc: priv->reset); |
93 | } |
94 | |
95 | static int phy_axg_pcie_reset(struct phy *phy) |
96 | { |
97 | struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); |
98 | int ret = 0; |
99 | |
100 | ret = phy_reset(phy: priv->analog); |
101 | if (ret != 0) |
102 | goto out; |
103 | |
104 | ret = reset_control_assert(rstc: priv->reset); |
105 | if (ret != 0) |
106 | goto out; |
107 | udelay(MESON_PCIE_RESET_DELAY); |
108 | |
109 | ret = reset_control_deassert(rstc: priv->reset); |
110 | if (ret != 0) |
111 | goto out; |
112 | udelay(MESON_PCIE_RESET_DELAY); |
113 | |
114 | out: |
115 | return ret; |
116 | } |
117 | |
118 | static const struct phy_ops phy_axg_pcie_ops = { |
119 | .init = phy_axg_pcie_init, |
120 | .exit = phy_axg_pcie_exit, |
121 | .power_on = phy_axg_pcie_power_on, |
122 | .power_off = phy_axg_pcie_power_off, |
123 | .reset = phy_axg_pcie_reset, |
124 | .owner = THIS_MODULE, |
125 | }; |
126 | |
127 | static int phy_axg_pcie_probe(struct platform_device *pdev) |
128 | { |
129 | struct phy_provider *pphy; |
130 | struct device *dev = &pdev->dev; |
131 | struct phy_axg_pcie_priv *priv; |
132 | struct device_node *np = dev->of_node; |
133 | void __iomem *base; |
134 | int ret; |
135 | |
136 | priv = devm_kmalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
137 | if (!priv) |
138 | return -ENOMEM; |
139 | |
140 | priv->phy = devm_phy_create(dev, node: np, ops: &phy_axg_pcie_ops); |
141 | if (IS_ERR(ptr: priv->phy)) { |
142 | ret = PTR_ERR(ptr: priv->phy); |
143 | if (ret != -EPROBE_DEFER) |
144 | dev_err(dev, "failed to create PHY\n" ); |
145 | return ret; |
146 | } |
147 | |
148 | base = devm_platform_ioremap_resource(pdev, index: 0); |
149 | if (IS_ERR(ptr: base)) |
150 | return PTR_ERR(ptr: base); |
151 | |
152 | priv->regmap = devm_regmap_init_mmio(dev, base, |
153 | &phy_axg_pcie_regmap_conf); |
154 | if (IS_ERR(ptr: priv->regmap)) |
155 | return PTR_ERR(ptr: priv->regmap); |
156 | |
157 | priv->reset = devm_reset_control_array_get_exclusive(dev); |
158 | if (IS_ERR(ptr: priv->reset)) |
159 | return PTR_ERR(ptr: priv->reset); |
160 | |
161 | priv->analog = devm_phy_get(dev, string: "analog" ); |
162 | if (IS_ERR(ptr: priv->analog)) |
163 | return PTR_ERR(ptr: priv->analog); |
164 | |
165 | phy_set_drvdata(phy: priv->phy, data: priv); |
166 | dev_set_drvdata(dev, data: priv); |
167 | pphy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
168 | |
169 | return PTR_ERR_OR_ZERO(ptr: pphy); |
170 | } |
171 | |
172 | static const struct of_device_id phy_axg_pcie_of_match[] = { |
173 | { |
174 | .compatible = "amlogic,axg-pcie-phy" , |
175 | }, |
176 | { }, |
177 | }; |
178 | MODULE_DEVICE_TABLE(of, phy_axg_pcie_of_match); |
179 | |
180 | static struct platform_driver phy_axg_pcie_driver = { |
181 | .probe = phy_axg_pcie_probe, |
182 | .driver = { |
183 | .name = "phy-axg-pcie" , |
184 | .of_match_table = phy_axg_pcie_of_match, |
185 | }, |
186 | }; |
187 | module_platform_driver(phy_axg_pcie_driver); |
188 | |
189 | MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>" ); |
190 | MODULE_DESCRIPTION("Amlogic AXG PCIE PHY driver" ); |
191 | MODULE_LICENSE("GPL v2" ); |
192 | |