1
2
3
4
5
6
7
8
9
10
11
12
13#include <linux/arm-smccc.h>
14#include <linux/io.h>
15#include <linux/iopoll.h>
16#include <linux/mfd/syscon.h>
17#include <linux/module.h>
18#include <linux/phy.h>
19#include <linux/phy/phy.h>
20#include <linux/platform_device.h>
21
22#define MVEBU_A3700_COMPHY_LANES 3
23#define MVEBU_A3700_COMPHY_PORTS 2
24
25
26#define COMPHY_SIP_POWER_ON 0x82000001
27#define COMPHY_SIP_POWER_OFF 0x82000002
28#define COMPHY_SIP_PLL_LOCK 0x82000003
29
30#define COMPHY_FW_MODE_SATA 0x1
31#define COMPHY_FW_MODE_SGMII 0x2
32#define COMPHY_FW_MODE_HS_SGMII 0x3
33#define COMPHY_FW_MODE_USB3H 0x4
34#define COMPHY_FW_MODE_USB3D 0x5
35#define COMPHY_FW_MODE_PCIE 0x6
36#define COMPHY_FW_MODE_RXAUI 0x7
37#define COMPHY_FW_MODE_XFI 0x8
38#define COMPHY_FW_MODE_SFI 0x9
39#define COMPHY_FW_MODE_USB3 0xa
40
41#define COMPHY_FW_SPEED_1_25G 0
42#define COMPHY_FW_SPEED_2_5G 1
43#define COMPHY_FW_SPEED_3_125G 2
44#define COMPHY_FW_SPEED_5G 3
45#define COMPHY_FW_SPEED_5_15625G 4
46#define COMPHY_FW_SPEED_6G 5
47#define COMPHY_FW_SPEED_10_3125G 6
48#define COMPHY_FW_SPEED_MAX 0x3F
49
50#define COMPHY_FW_MODE(mode) ((mode) << 12)
51#define COMPHY_FW_NET(mode, idx, speed) (COMPHY_FW_MODE(mode) | \
52 ((idx) << 8) | \
53 ((speed) << 2))
54#define COMPHY_FW_PCIE(mode, idx, speed, width) (COMPHY_FW_NET(mode, idx, speed) | \
55 ((width) << 18))
56
57struct mvebu_a3700_comphy_conf {
58 unsigned int lane;
59 enum phy_mode mode;
60 int submode;
61 unsigned int port;
62 u32 fw_mode;
63};
64
65#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _port, _fw) \
66 { \
67 .lane = _lane, \
68 .mode = _mode, \
69 .submode = _smode, \
70 .port = _port, \
71 .fw_mode = _fw, \
72 }
73
74#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _port, _fw) \
75 MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _port, _fw)
76
77#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _port, _fw) \
78 MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _port, _fw)
79
80static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = {
81
82 MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS, 0,
83 COMPHY_FW_MODE_USB3H),
84 MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII, 1,
85 COMPHY_FW_MODE_SGMII),
86 MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX, 1,
87 COMPHY_FW_MODE_HS_SGMII),
88
89 MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, 0,
90 COMPHY_FW_MODE_PCIE),
91 MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII, 0,
92 COMPHY_FW_MODE_SGMII),
93 MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX, 0,
94 COMPHY_FW_MODE_HS_SGMII),
95
96 MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, 0,
97 COMPHY_FW_MODE_SATA),
98 MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS, 0,
99 COMPHY_FW_MODE_USB3H),
100};
101
102struct mvebu_a3700_comphy_lane {
103 struct device *dev;
104 unsigned int id;
105 enum phy_mode mode;
106 int submode;
107 int port;
108};
109
110static int mvebu_a3700_comphy_smc(unsigned long function, unsigned long lane,
111 unsigned long mode)
112{
113 struct arm_smccc_res res;
114
115 arm_smccc_smc(function, lane, mode, 0, 0, 0, 0, 0, &res);
116
117 return res.a0;
118}
119
120static int mvebu_a3700_comphy_get_fw_mode(int lane, int port,
121 enum phy_mode mode,
122 int submode)
123{
124 int i, n = ARRAY_SIZE(mvebu_a3700_comphy_modes);
125
126
127 if (mode == PHY_MODE_INVALID)
128 return -EINVAL;
129
130 for (i = 0; i < n; i++) {
131 if (mvebu_a3700_comphy_modes[i].lane == lane &&
132 mvebu_a3700_comphy_modes[i].port == port &&
133 mvebu_a3700_comphy_modes[i].mode == mode &&
134 mvebu_a3700_comphy_modes[i].submode == submode)
135 break;
136 }
137
138 if (i == n)
139 return -EINVAL;
140
141 return mvebu_a3700_comphy_modes[i].fw_mode;
142}
143
144static int mvebu_a3700_comphy_set_mode(struct phy *phy, enum phy_mode mode,
145 int submode)
146{
147 struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
148 int fw_mode;
149
150 if (submode == PHY_INTERFACE_MODE_1000BASEX)
151 submode = PHY_INTERFACE_MODE_SGMII;
152
153 fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port, mode,
154 submode);
155 if (fw_mode < 0) {
156 dev_err(lane->dev, "invalid COMPHY mode\n");
157 return fw_mode;
158 }
159
160
161 lane->mode = mode;
162 lane->submode = submode;
163
164 return 0;
165}
166
167static int mvebu_a3700_comphy_power_on(struct phy *phy)
168{
169 struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
170 u32 fw_param;
171 int fw_mode;
172
173 fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port,
174 lane->mode, lane->submode);
175 if (fw_mode < 0) {
176 dev_err(lane->dev, "invalid COMPHY mode\n");
177 return fw_mode;
178 }
179
180 switch (lane->mode) {
181 case PHY_MODE_USB_HOST_SS:
182 dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id);
183 fw_param = COMPHY_FW_MODE(fw_mode);
184 break;
185 case PHY_MODE_SATA:
186 dev_dbg(lane->dev, "set lane %d to SATA mode\n", lane->id);
187 fw_param = COMPHY_FW_MODE(fw_mode);
188 break;
189 case PHY_MODE_ETHERNET:
190 switch (lane->submode) {
191 case PHY_INTERFACE_MODE_SGMII:
192 dev_dbg(lane->dev, "set lane %d to SGMII mode\n",
193 lane->id);
194 fw_param = COMPHY_FW_NET(fw_mode, lane->port,
195 COMPHY_FW_SPEED_1_25G);
196 break;
197 case PHY_INTERFACE_MODE_2500BASEX:
198 dev_dbg(lane->dev, "set lane %d to HS SGMII mode\n",
199 lane->id);
200 fw_param = COMPHY_FW_NET(fw_mode, lane->port,
201 COMPHY_FW_SPEED_3_125G);
202 break;
203 default:
204 dev_err(lane->dev, "unsupported PHY submode (%d)\n",
205 lane->submode);
206 return -ENOTSUPP;
207 }
208 break;
209 case PHY_MODE_PCIE:
210 dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id);
211 fw_param = COMPHY_FW_PCIE(fw_mode, lane->port,
212 COMPHY_FW_SPEED_5G,
213 phy->attrs.bus_width);
214 break;
215 default:
216 dev_err(lane->dev, "unsupported PHY mode (%d)\n", lane->mode);
217 return -ENOTSUPP;
218 }
219
220 return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_ON, lane->id, fw_param);
221}
222
223static int mvebu_a3700_comphy_power_off(struct phy *phy)
224{
225 struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
226
227 return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_OFF, lane->id, 0);
228}
229
230static const struct phy_ops mvebu_a3700_comphy_ops = {
231 .power_on = mvebu_a3700_comphy_power_on,
232 .power_off = mvebu_a3700_comphy_power_off,
233 .set_mode = mvebu_a3700_comphy_set_mode,
234 .owner = THIS_MODULE,
235};
236
237static struct phy *mvebu_a3700_comphy_xlate(struct device *dev,
238 struct of_phandle_args *args)
239{
240 struct mvebu_a3700_comphy_lane *lane;
241 struct phy *phy;
242
243 if (WARN_ON(args->args[0] >= MVEBU_A3700_COMPHY_PORTS))
244 return ERR_PTR(-EINVAL);
245
246 phy = of_phy_simple_xlate(dev, args);
247 if (IS_ERR(phy))
248 return phy;
249
250 lane = phy_get_drvdata(phy);
251 lane->port = args->args[0];
252
253 return phy;
254}
255
256static int mvebu_a3700_comphy_probe(struct platform_device *pdev)
257{
258 struct phy_provider *provider;
259 struct device_node *child;
260
261 for_each_available_child_of_node(pdev->dev.of_node, child) {
262 struct mvebu_a3700_comphy_lane *lane;
263 struct phy *phy;
264 int ret;
265 u32 lane_id;
266
267 ret = of_property_read_u32(child, "reg", &lane_id);
268 if (ret < 0) {
269 dev_err(&pdev->dev, "missing 'reg' property (%d)\n",
270 ret);
271 continue;
272 }
273
274 if (lane_id >= MVEBU_A3700_COMPHY_LANES) {
275 dev_err(&pdev->dev, "invalid 'reg' property\n");
276 continue;
277 }
278
279 lane = devm_kzalloc(&pdev->dev, sizeof(*lane), GFP_KERNEL);
280 if (!lane)
281 return -ENOMEM;
282
283 phy = devm_phy_create(&pdev->dev, child,
284 &mvebu_a3700_comphy_ops);
285 if (IS_ERR(phy))
286 return PTR_ERR(phy);
287
288 lane->dev = &pdev->dev;
289 lane->mode = PHY_MODE_INVALID;
290 lane->submode = PHY_INTERFACE_MODE_NA;
291 lane->id = lane_id;
292 lane->port = -1;
293 phy_set_drvdata(phy, lane);
294 }
295
296 provider = devm_of_phy_provider_register(&pdev->dev,
297 mvebu_a3700_comphy_xlate);
298 return PTR_ERR_OR_ZERO(provider);
299}
300
301static const struct of_device_id mvebu_a3700_comphy_of_match_table[] = {
302 { .compatible = "marvell,comphy-a3700" },
303 { },
304};
305MODULE_DEVICE_TABLE(of, mvebu_a3700_comphy_of_match_table);
306
307static struct platform_driver mvebu_a3700_comphy_driver = {
308 .probe = mvebu_a3700_comphy_probe,
309 .driver = {
310 .name = "mvebu-a3700-comphy",
311 .of_match_table = mvebu_a3700_comphy_of_match_table,
312 },
313};
314module_platform_driver(mvebu_a3700_comphy_driver);
315
316MODULE_AUTHOR("Miquèl Raynal <miquel.raynal@bootlin.com>");
317MODULE_DESCRIPTION("Common PHY driver for A3700");
318MODULE_LICENSE("GPL v2");
319