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>
11 #include <linux/phy/phy.h>
12 #include <linux/phy.h>
13 #include <linux/platform_device.h>
15 #define MAX_A38X_COMPHY 6
16 #define MAX_A38X_PORTS 3
18 #define COMPHY_CFG1 0x00
19 #define COMPHY_CFG1_GEN_TX(x) ((x) << 26)
20 #define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15)
21 #define COMPHY_CFG1_GEN_RX(x) ((x) << 22)
22 #define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15)
23 #define GEN_SGMII_1_25GBPS 6
24 #define GEN_SGMII_3_125GBPS 8
26 #define COMPHY_STAT1 0x18
27 #define COMPHY_STAT1_PLL_RDY_TX BIT(3)
28 #define COMPHY_STAT1_PLL_RDY_RX BIT(2)
30 #define COMPHY_SELECTOR 0xfc
34 struct a38x_comphy_lane
{
36 struct a38x_comphy
*priv
;
45 struct a38x_comphy_lane lane
[MAX_A38X_COMPHY
];
48 static const u8 gbe_mux
[MAX_A38X_COMPHY
][MAX_A38X_PORTS
] = {
57 static void a38x_comphy_set_reg(struct a38x_comphy_lane
*lane
,
58 unsigned int offset
, u32 mask
, u32 value
)
62 val
= readl_relaxed(lane
->base
+ offset
) & ~mask
;
63 writel(val
| value
, lane
->base
+ offset
);
66 static void a38x_comphy_set_speed(struct a38x_comphy_lane
*lane
,
67 unsigned int gen_tx
, unsigned int gen_rx
)
69 a38x_comphy_set_reg(lane
, COMPHY_CFG1
,
70 COMPHY_CFG1_GEN_TX_MSK
| COMPHY_CFG1_GEN_RX_MSK
,
71 COMPHY_CFG1_GEN_TX(gen_tx
) |
72 COMPHY_CFG1_GEN_RX(gen_rx
));
75 static int a38x_comphy_poll(struct a38x_comphy_lane
*lane
,
76 unsigned int offset
, u32 mask
, u32 value
)
81 ret
= readl_relaxed_poll_timeout_atomic(lane
->base
+ offset
, val
,
82 (val
& mask
) == value
,
86 dev_err(lane
->priv
->dev
,
87 "comphy%u: timed out waiting for status\n", lane
->n
);
93 * We only support changing the speed for comphys configured for GBE.
94 * Since that is all we do, we only poll for PLL ready status.
96 static int a38x_comphy_set_mode(struct phy
*phy
, enum phy_mode mode
, int sub
)
98 struct a38x_comphy_lane
*lane
= phy_get_drvdata(phy
);
101 if (mode
!= PHY_MODE_ETHERNET
)
105 case PHY_INTERFACE_MODE_SGMII
:
106 case PHY_INTERFACE_MODE_1000BASEX
:
107 gen
= GEN_SGMII_1_25GBPS
;
110 case PHY_INTERFACE_MODE_2500BASEX
:
111 gen
= GEN_SGMII_3_125GBPS
;
118 a38x_comphy_set_speed(lane
, gen
, gen
);
120 return a38x_comphy_poll(lane
, COMPHY_STAT1
,
121 COMPHY_STAT1_PLL_RDY_TX
|
122 COMPHY_STAT1_PLL_RDY_RX
,
123 COMPHY_STAT1_PLL_RDY_TX
|
124 COMPHY_STAT1_PLL_RDY_RX
);
127 static const struct phy_ops a38x_comphy_ops
= {
128 .set_mode
= a38x_comphy_set_mode
,
129 .owner
= THIS_MODULE
,
132 static struct phy
*a38x_comphy_xlate(struct device
*dev
,
133 struct of_phandle_args
*args
)
135 struct a38x_comphy_lane
*lane
;
139 if (WARN_ON(args
->args
[0] >= MAX_A38X_PORTS
))
140 return ERR_PTR(-EINVAL
);
142 phy
= of_phy_simple_xlate(dev
, args
);
146 lane
= phy_get_drvdata(phy
);
148 return ERR_PTR(-EBUSY
);
150 lane
->port
= args
->args
[0];
152 val
= readl_relaxed(lane
->priv
->base
+ COMPHY_SELECTOR
);
153 val
= (val
>> (4 * lane
->n
)) & 0xf;
155 if (!gbe_mux
[lane
->n
][lane
->port
] ||
156 val
!= gbe_mux
[lane
->n
][lane
->port
]) {
157 dev_warn(lane
->priv
->dev
,
158 "comphy%u: not configured for GBE\n", lane
->n
);
159 phy
= ERR_PTR(-EINVAL
);
165 static int a38x_comphy_probe(struct platform_device
*pdev
)
167 struct phy_provider
*provider
;
168 struct device_node
*child
;
169 struct a38x_comphy
*priv
;
170 struct resource
*res
;
173 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
177 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
178 base
= devm_ioremap_resource(&pdev
->dev
, res
);
180 return PTR_ERR(base
);
182 priv
->dev
= &pdev
->dev
;
185 for_each_available_child_of_node(pdev
->dev
.of_node
, child
) {
190 ret
= of_property_read_u32(child
, "reg", &val
);
192 dev_err(&pdev
->dev
, "missing 'reg' property (%d)\n",
197 if (val
>= MAX_A38X_COMPHY
|| priv
->lane
[val
].base
) {
198 dev_err(&pdev
->dev
, "invalid 'reg' property\n");
202 phy
= devm_phy_create(&pdev
->dev
, child
, &a38x_comphy_ops
);
208 priv
->lane
[val
].base
= base
+ 0x28 * val
;
209 priv
->lane
[val
].priv
= priv
;
210 priv
->lane
[val
].n
= val
;
211 priv
->lane
[val
].port
= -1;
212 phy_set_drvdata(phy
, &priv
->lane
[val
]);
215 dev_set_drvdata(&pdev
->dev
, priv
);
217 provider
= devm_of_phy_provider_register(&pdev
->dev
, a38x_comphy_xlate
);
219 return PTR_ERR_OR_ZERO(provider
);
222 static const struct of_device_id a38x_comphy_of_match_table
[] = {
223 { .compatible
= "marvell,armada-380-comphy" },
226 MODULE_DEVICE_TABLE(of
, a38x_comphy_of_match_table
);
228 static struct platform_driver a38x_comphy_driver
= {
229 .probe
= a38x_comphy_probe
,
231 .name
= "armada-38x-comphy",
232 .of_match_table
= a38x_comphy_of_match_table
,
235 module_platform_driver(a38x_comphy_driver
);
237 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
238 MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs");
239 MODULE_LICENSE("GPL v2");