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
;
46 struct a38x_comphy_lane lane
[MAX_A38X_COMPHY
];
49 static const u8 gbe_mux
[MAX_A38X_COMPHY
][MAX_A38X_PORTS
] = {
58 static void a38x_set_conf(struct a38x_comphy_lane
*lane
, bool enable
)
60 struct a38x_comphy
*priv
= lane
->priv
;
64 conf
= readl_relaxed(priv
->conf
);
66 conf
|= BIT(lane
->port
);
68 conf
&= ~BIT(lane
->port
);
69 writel(conf
, priv
->conf
);
73 static void a38x_comphy_set_reg(struct a38x_comphy_lane
*lane
,
74 unsigned int offset
, u32 mask
, u32 value
)
78 val
= readl_relaxed(lane
->base
+ offset
) & ~mask
;
79 writel(val
| value
, lane
->base
+ offset
);
82 static void a38x_comphy_set_speed(struct a38x_comphy_lane
*lane
,
83 unsigned int gen_tx
, unsigned int gen_rx
)
85 a38x_comphy_set_reg(lane
, COMPHY_CFG1
,
86 COMPHY_CFG1_GEN_TX_MSK
| COMPHY_CFG1_GEN_RX_MSK
,
87 COMPHY_CFG1_GEN_TX(gen_tx
) |
88 COMPHY_CFG1_GEN_RX(gen_rx
));
91 static int a38x_comphy_poll(struct a38x_comphy_lane
*lane
,
92 unsigned int offset
, u32 mask
, u32 value
)
97 ret
= readl_relaxed_poll_timeout_atomic(lane
->base
+ offset
, val
,
98 (val
& mask
) == value
,
102 dev_err(lane
->priv
->dev
,
103 "comphy%u: timed out waiting for status\n", lane
->n
);
109 * We only support changing the speed for comphys configured for GBE.
110 * Since that is all we do, we only poll for PLL ready status.
112 static int a38x_comphy_set_mode(struct phy
*phy
, enum phy_mode mode
, int sub
)
114 struct a38x_comphy_lane
*lane
= phy_get_drvdata(phy
);
118 if (mode
!= PHY_MODE_ETHERNET
)
122 case PHY_INTERFACE_MODE_SGMII
:
123 case PHY_INTERFACE_MODE_1000BASEX
:
124 gen
= GEN_SGMII_1_25GBPS
;
127 case PHY_INTERFACE_MODE_2500BASEX
:
128 gen
= GEN_SGMII_3_125GBPS
;
135 a38x_set_conf(lane
, false);
137 a38x_comphy_set_speed(lane
, gen
, gen
);
139 ret
= a38x_comphy_poll(lane
, COMPHY_STAT1
,
140 COMPHY_STAT1_PLL_RDY_TX
|
141 COMPHY_STAT1_PLL_RDY_RX
,
142 COMPHY_STAT1_PLL_RDY_TX
|
143 COMPHY_STAT1_PLL_RDY_RX
);
146 a38x_set_conf(lane
, true);
151 static const struct phy_ops a38x_comphy_ops
= {
152 .set_mode
= a38x_comphy_set_mode
,
153 .owner
= THIS_MODULE
,
156 static struct phy
*a38x_comphy_xlate(struct device
*dev
,
157 struct of_phandle_args
*args
)
159 struct a38x_comphy_lane
*lane
;
163 if (WARN_ON(args
->args
[0] >= MAX_A38X_PORTS
))
164 return ERR_PTR(-EINVAL
);
166 phy
= of_phy_simple_xlate(dev
, args
);
170 lane
= phy_get_drvdata(phy
);
172 return ERR_PTR(-EBUSY
);
174 lane
->port
= args
->args
[0];
176 val
= readl_relaxed(lane
->priv
->base
+ COMPHY_SELECTOR
);
177 val
= (val
>> (4 * lane
->n
)) & 0xf;
179 if (!gbe_mux
[lane
->n
][lane
->port
] ||
180 val
!= gbe_mux
[lane
->n
][lane
->port
]) {
181 dev_warn(lane
->priv
->dev
,
182 "comphy%u: not configured for GBE\n", lane
->n
);
183 phy
= ERR_PTR(-EINVAL
);
189 static int a38x_comphy_probe(struct platform_device
*pdev
)
191 struct phy_provider
*provider
;
192 struct device_node
*child
;
193 struct a38x_comphy
*priv
;
194 struct resource
*res
;
197 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
201 base
= devm_platform_ioremap_resource(pdev
, 0);
203 return PTR_ERR(base
);
205 priv
->dev
= &pdev
->dev
;
209 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "conf");
211 priv
->conf
= devm_ioremap_resource(&pdev
->dev
, res
);
212 if (IS_ERR(priv
->conf
))
213 return PTR_ERR(priv
->conf
);
216 for_each_available_child_of_node(pdev
->dev
.of_node
, child
) {
221 ret
= of_property_read_u32(child
, "reg", &val
);
223 dev_err(&pdev
->dev
, "missing 'reg' property (%d)\n",
228 if (val
>= MAX_A38X_COMPHY
|| priv
->lane
[val
].base
) {
229 dev_err(&pdev
->dev
, "invalid 'reg' property\n");
233 phy
= devm_phy_create(&pdev
->dev
, child
, &a38x_comphy_ops
);
239 priv
->lane
[val
].base
= base
+ 0x28 * val
;
240 priv
->lane
[val
].priv
= priv
;
241 priv
->lane
[val
].n
= val
;
242 priv
->lane
[val
].port
= -1;
243 phy_set_drvdata(phy
, &priv
->lane
[val
]);
246 dev_set_drvdata(&pdev
->dev
, priv
);
248 provider
= devm_of_phy_provider_register(&pdev
->dev
, a38x_comphy_xlate
);
250 return PTR_ERR_OR_ZERO(provider
);
253 static const struct of_device_id a38x_comphy_of_match_table
[] = {
254 { .compatible
= "marvell,armada-380-comphy" },
257 MODULE_DEVICE_TABLE(of
, a38x_comphy_of_match_table
);
259 static struct platform_driver a38x_comphy_driver
= {
260 .probe
= a38x_comphy_probe
,
262 .name
= "armada-38x-comphy",
263 .of_match_table
= a38x_comphy_of_match_table
,
266 module_platform_driver(a38x_comphy_driver
);
268 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
269 MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs");
270 MODULE_LICENSE("GPL v2");