1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2018 Russell King, Deep Blue Solutions Ltd.
5 * Partly derived from CP110 comphy driver by Antoine Tenart
6 * <antoine.tenart@bootlin.com>
8 #include <linux/delay.h>
9 #include <linux/iopoll.h>
10 #include <linux/module.h>
12 #include <linux/phy/phy.h>
13 #include <linux/phy.h>
14 #include <linux/platform_device.h>
16 #define MAX_A38X_COMPHY 6
17 #define MAX_A38X_PORTS 3
19 #define COMPHY_CFG1 0x00
20 #define COMPHY_CFG1_GEN_TX(x) ((x) << 26)
21 #define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15)
22 #define COMPHY_CFG1_GEN_RX(x) ((x) << 22)
23 #define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15)
24 #define GEN_SGMII_1_25GBPS 6
25 #define GEN_SGMII_3_125GBPS 8
27 #define COMPHY_STAT1 0x18
28 #define COMPHY_STAT1_PLL_RDY_TX BIT(3)
29 #define COMPHY_STAT1_PLL_RDY_RX BIT(2)
31 #define COMPHY_SELECTOR 0xfc
35 struct a38x_comphy_lane
{
37 struct a38x_comphy
*priv
;
47 struct a38x_comphy_lane lane
[MAX_A38X_COMPHY
];
51 * Map serdes lanes and gbe ports to serdes mux configuration values:
52 * row index = serdes lane,
53 * column index = gbe port number.
55 static const u8 gbe_mux
[MAX_A38X_COMPHY
][MAX_A38X_PORTS
] = {
64 static void a38x_set_conf(struct a38x_comphy_lane
*lane
, bool enable
)
66 struct a38x_comphy
*priv
= lane
->priv
;
70 conf
= readl_relaxed(priv
->conf
);
72 conf
|= BIT(lane
->port
);
74 conf
&= ~BIT(lane
->port
);
75 writel(conf
, priv
->conf
);
79 static void a38x_comphy_set_reg(struct a38x_comphy_lane
*lane
,
80 unsigned int offset
, u32 mask
, u32 value
)
84 val
= readl_relaxed(lane
->base
+ offset
) & ~mask
;
85 writel(val
| value
, lane
->base
+ offset
);
88 static void a38x_comphy_set_speed(struct a38x_comphy_lane
*lane
,
89 unsigned int gen_tx
, unsigned int gen_rx
)
91 a38x_comphy_set_reg(lane
, COMPHY_CFG1
,
92 COMPHY_CFG1_GEN_TX_MSK
| COMPHY_CFG1_GEN_RX_MSK
,
93 COMPHY_CFG1_GEN_TX(gen_tx
) |
94 COMPHY_CFG1_GEN_RX(gen_rx
));
97 static int a38x_comphy_poll(struct a38x_comphy_lane
*lane
,
98 unsigned int offset
, u32 mask
, u32 value
)
103 ret
= readl_relaxed_poll_timeout_atomic(lane
->base
+ offset
, val
,
104 (val
& mask
) == value
,
108 dev_err(lane
->priv
->dev
,
109 "comphy%u: timed out waiting for status\n", lane
->n
);
115 * We only support changing the speed for comphys configured for GBE.
116 * Since that is all we do, we only poll for PLL ready status.
118 static int a38x_comphy_set_mode(struct phy
*phy
, enum phy_mode mode
, int sub
)
120 struct a38x_comphy_lane
*lane
= phy_get_drvdata(phy
);
124 if (mode
!= PHY_MODE_ETHERNET
)
128 case PHY_INTERFACE_MODE_SGMII
:
129 case PHY_INTERFACE_MODE_1000BASEX
:
130 gen
= GEN_SGMII_1_25GBPS
;
133 case PHY_INTERFACE_MODE_2500BASEX
:
134 gen
= GEN_SGMII_3_125GBPS
;
141 a38x_set_conf(lane
, false);
143 a38x_comphy_set_speed(lane
, gen
, gen
);
145 ret
= a38x_comphy_poll(lane
, COMPHY_STAT1
,
146 COMPHY_STAT1_PLL_RDY_TX
|
147 COMPHY_STAT1_PLL_RDY_RX
,
148 COMPHY_STAT1_PLL_RDY_TX
|
149 COMPHY_STAT1_PLL_RDY_RX
);
152 a38x_set_conf(lane
, true);
157 static const struct phy_ops a38x_comphy_ops
= {
158 .set_mode
= a38x_comphy_set_mode
,
159 .owner
= THIS_MODULE
,
162 static struct phy
*a38x_comphy_xlate(struct device
*dev
,
163 const struct of_phandle_args
*args
)
165 struct a38x_comphy_lane
*lane
;
169 if (WARN_ON(args
->args
[0] >= MAX_A38X_PORTS
))
170 return ERR_PTR(-EINVAL
);
172 phy
= of_phy_simple_xlate(dev
, args
);
176 lane
= phy_get_drvdata(phy
);
178 return ERR_PTR(-EBUSY
);
180 lane
->port
= args
->args
[0];
182 val
= readl_relaxed(lane
->priv
->base
+ COMPHY_SELECTOR
);
183 val
= (val
>> (4 * lane
->n
)) & 0xf;
185 if (!gbe_mux
[lane
->n
][lane
->port
] ||
186 val
!= gbe_mux
[lane
->n
][lane
->port
]) {
187 dev_warn(lane
->priv
->dev
,
188 "comphy%u: not configured for GBE\n", lane
->n
);
189 phy
= ERR_PTR(-EINVAL
);
195 static int a38x_comphy_probe(struct platform_device
*pdev
)
197 struct phy_provider
*provider
;
198 struct device_node
*child
;
199 struct a38x_comphy
*priv
;
200 struct resource
*res
;
203 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
207 base
= devm_platform_ioremap_resource(pdev
, 0);
209 return PTR_ERR(base
);
211 priv
->dev
= &pdev
->dev
;
215 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "conf");
217 priv
->conf
= devm_ioremap_resource(&pdev
->dev
, res
);
218 if (IS_ERR(priv
->conf
))
219 return PTR_ERR(priv
->conf
);
222 for_each_available_child_of_node(pdev
->dev
.of_node
, child
) {
227 ret
= of_property_read_u32(child
, "reg", &val
);
229 dev_err(&pdev
->dev
, "missing 'reg' property (%d)\n",
234 if (val
>= MAX_A38X_COMPHY
|| priv
->lane
[val
].base
) {
235 dev_err(&pdev
->dev
, "invalid 'reg' property\n");
239 phy
= devm_phy_create(&pdev
->dev
, child
, &a38x_comphy_ops
);
245 priv
->lane
[val
].base
= base
+ 0x28 * val
;
246 priv
->lane
[val
].priv
= priv
;
247 priv
->lane
[val
].n
= val
;
248 priv
->lane
[val
].port
= -1;
249 phy_set_drvdata(phy
, &priv
->lane
[val
]);
252 dev_set_drvdata(&pdev
->dev
, priv
);
254 provider
= devm_of_phy_provider_register(&pdev
->dev
, a38x_comphy_xlate
);
256 return PTR_ERR_OR_ZERO(provider
);
259 static const struct of_device_id a38x_comphy_of_match_table
[] = {
260 { .compatible
= "marvell,armada-380-comphy" },
263 MODULE_DEVICE_TABLE(of
, a38x_comphy_of_match_table
);
265 static struct platform_driver a38x_comphy_driver
= {
266 .probe
= a38x_comphy_probe
,
268 .name
= "armada-38x-comphy",
269 .of_match_table
= a38x_comphy_of_match_table
,
272 module_platform_driver(a38x_comphy_driver
);
274 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
275 MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs");
276 MODULE_LICENSE("GPL v2");