Skip to content

Commit 566e825

Browse files
xdarklightkhilman
authored andcommitted
net: stmmac: add a glue driver for the Amlogic Meson 8b / GXBB DWMAC
The Ethernet controller available in Meson8b and GXBB SoCs is a Synopsys DesignWare MAC IP core which is already supported by the stmmac driver. In addition to the standard stmmac driver some Meson8b / GXBB specific registers have to be configured for the PHY clocks. These SoC specific registers are called PRG_ETHERNET_ADDR0 and PRG_ETHERNET_ADDR1 in the datasheet. These registers are not backwards compatible with those on Meson 6b, which is why a new glue driver is introduced. This worked for many boards because the bootloader programs the PRG_ETHERNET registers correctly. Additionally the meson6-dwmac driver only sets bit 1 of PRG_ETHERNET_ADDR0 which (according to the datasheet) is only used during reset. Currently all configuration values can be determined automatically, based on the configured phy-mode (which is mandatory for the stmmac driver). If required the tx-delay and the mux clock (so it supports the MPLL2 clock as well) can be made configurable in the future. Signed-off-by: Martin Blumenstingl <[email protected]> Tested-by: Kevin Hilman <[email protected]> Acked-by: David S. Miller <[email protected]> Signed-off-by: Kevin Hilman <[email protected]>
1 parent 1b0acbf commit 566e825

File tree

3 files changed

+328
-4
lines changed

3 files changed

+328
-4
lines changed

drivers/net/ethernet/stmicro/stmmac/Kconfig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ config DWMAC_LPC18XX
6161
config DWMAC_MESON
6262
tristate "Amlogic Meson dwmac support"
6363
default ARCH_MESON
64-
depends on OF && (ARCH_MESON || COMPILE_TEST)
64+
depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
6565
help
6666
Support for Ethernet controller on Amlogic Meson SoCs.
6767

6868
This selects the Amlogic Meson SoC glue layer support for
69-
the stmmac device driver. This driver is used for Meson6 and
70-
Meson8 SoCs.
69+
the stmmac device driver. This driver is used for Meson6,
70+
Meson8, Meson8b and GXBB SoCs.
7171

7272
config DWMAC_ROCKCHIP
7373
tristate "Rockchip dwmac support"

drivers/net/ethernet/stmicro/stmmac/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o ring_mode.o \
99
obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o
1010
obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o
1111
obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o
12-
obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o
12+
obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o dwmac-meson8b.o
1313
obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o
1414
obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-altr-socfpga.o
1515
obj-$(CONFIG_DWMAC_STI) += dwmac-sti.o
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
/*
2+
* Amlogic Meson8b and GXBB DWMAC glue layer
3+
*
4+
* Copyright (C) 2016 Martin Blumenstingl <[email protected]>
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License version 2 as
8+
* published by the Free Software Foundation.
9+
*
10+
* You should have received a copy of the GNU General Public License
11+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
12+
*/
13+
14+
#include <linux/clk.h>
15+
#include <linux/clk-provider.h>
16+
#include <linux/device.h>
17+
#include <linux/ethtool.h>
18+
#include <linux/io.h>
19+
#include <linux/ioport.h>
20+
#include <linux/module.h>
21+
#include <linux/of_net.h>
22+
#include <linux/mfd/syscon.h>
23+
#include <linux/platform_device.h>
24+
#include <linux/stmmac.h>
25+
26+
#include "stmmac_platform.h"
27+
28+
#define PRG_ETH0 0x0
29+
30+
#define PRG_ETH0_RGMII_MODE BIT(0)
31+
32+
/* mux to choose between fclk_div2 (bit unset) and mpll2 (bit set) */
33+
#define PRG_ETH0_CLK_M250_SEL_SHIFT 4
34+
#define PRG_ETH0_CLK_M250_SEL_MASK GENMASK(4, 4)
35+
36+
#define PRG_ETH0_TXDLY_SHIFT 5
37+
#define PRG_ETH0_TXDLY_MASK GENMASK(6, 5)
38+
#define PRG_ETH0_TXDLY_OFF (0x0 << PRG_ETH0_TXDLY_SHIFT)
39+
#define PRG_ETH0_TXDLY_QUARTER (0x1 << PRG_ETH0_TXDLY_SHIFT)
40+
#define PRG_ETH0_TXDLY_HALF (0x2 << PRG_ETH0_TXDLY_SHIFT)
41+
#define PRG_ETH0_TXDLY_THREE_QUARTERS (0x3 << PRG_ETH0_TXDLY_SHIFT)
42+
43+
/* divider for the result of m250_sel */
44+
#define PRG_ETH0_CLK_M250_DIV_SHIFT 7
45+
#define PRG_ETH0_CLK_M250_DIV_WIDTH 3
46+
47+
/* divides the result of m25_sel by either 5 (bit unset) or 10 (bit set) */
48+
#define PRG_ETH0_CLK_M25_DIV_SHIFT 10
49+
#define PRG_ETH0_CLK_M25_DIV_WIDTH 1
50+
51+
#define PRG_ETH0_INVERTED_RMII_CLK BIT(11)
52+
#define PRG_ETH0_TX_AND_PHY_REF_CLK BIT(12)
53+
54+
#define MUX_CLK_NUM_PARENTS 2
55+
56+
struct meson8b_dwmac {
57+
struct platform_device *pdev;
58+
59+
void __iomem *regs;
60+
61+
phy_interface_t phy_mode;
62+
63+
struct clk_mux m250_mux;
64+
struct clk *m250_mux_clk;
65+
struct clk *m250_mux_parent[MUX_CLK_NUM_PARENTS];
66+
67+
struct clk_divider m250_div;
68+
struct clk *m250_div_clk;
69+
70+
struct clk_divider m25_div;
71+
struct clk *m25_div_clk;
72+
};
73+
74+
static void meson8b_dwmac_mask_bits(struct meson8b_dwmac *dwmac, u32 reg,
75+
u32 mask, u32 value)
76+
{
77+
u32 data;
78+
79+
data = readl(dwmac->regs + reg);
80+
data &= ~mask;
81+
data |= (value & mask);
82+
83+
writel(data, dwmac->regs + reg);
84+
}
85+
86+
static int meson8b_init_clk(struct meson8b_dwmac *dwmac)
87+
{
88+
struct clk_init_data init;
89+
int i, ret;
90+
struct device *dev = &dwmac->pdev->dev;
91+
char clk_name[32];
92+
const char *clk_div_parents[1];
93+
const char *mux_parent_names[MUX_CLK_NUM_PARENTS];
94+
static struct clk_div_table clk_25m_div_table[] = {
95+
{ .val = 0, .div = 5 },
96+
{ .val = 1, .div = 10 },
97+
{ /* sentinel */ },
98+
};
99+
100+
/* get the mux parents from DT */
101+
for (i = 0; i < MUX_CLK_NUM_PARENTS; i++) {
102+
char name[16];
103+
104+
snprintf(name, sizeof(name), "clkin%d", i);
105+
dwmac->m250_mux_parent[i] = devm_clk_get(dev, name);
106+
if (IS_ERR(dwmac->m250_mux_parent[i])) {
107+
ret = PTR_ERR(dwmac->m250_mux_parent[i]);
108+
if (ret != -EPROBE_DEFER)
109+
dev_err(dev, "Missing clock %s\n", name);
110+
return ret;
111+
}
112+
113+
mux_parent_names[i] =
114+
__clk_get_name(dwmac->m250_mux_parent[i]);
115+
}
116+
117+
/* create the m250_mux */
118+
snprintf(clk_name, sizeof(clk_name), "%s#m250_sel", dev_name(dev));
119+
init.name = clk_name;
120+
init.ops = &clk_mux_ops;
121+
init.flags = 0;
122+
init.parent_names = mux_parent_names;
123+
init.num_parents = MUX_CLK_NUM_PARENTS;
124+
125+
dwmac->m250_mux.reg = dwmac->regs + PRG_ETH0;
126+
dwmac->m250_mux.shift = PRG_ETH0_CLK_M250_SEL_SHIFT;
127+
dwmac->m250_mux.mask = PRG_ETH0_CLK_M250_SEL_MASK;
128+
dwmac->m250_mux.flags = 0;
129+
dwmac->m250_mux.table = NULL;
130+
dwmac->m250_mux.hw.init = &init;
131+
132+
dwmac->m250_mux_clk = devm_clk_register(dev, &dwmac->m250_mux.hw);
133+
if (WARN_ON(IS_ERR(dwmac->m250_mux_clk)))
134+
return PTR_ERR(dwmac->m250_mux_clk);
135+
136+
/* create the m250_div */
137+
snprintf(clk_name, sizeof(clk_name), "%s#m250_div", dev_name(dev));
138+
init.name = devm_kstrdup(dev, clk_name, GFP_KERNEL);
139+
init.ops = &clk_divider_ops;
140+
init.flags = CLK_SET_RATE_PARENT;
141+
clk_div_parents[0] = __clk_get_name(dwmac->m250_mux_clk);
142+
init.parent_names = clk_div_parents;
143+
init.num_parents = ARRAY_SIZE(clk_div_parents);
144+
145+
dwmac->m250_div.reg = dwmac->regs + PRG_ETH0;
146+
dwmac->m250_div.shift = PRG_ETH0_CLK_M250_DIV_SHIFT;
147+
dwmac->m250_div.width = PRG_ETH0_CLK_M250_DIV_WIDTH;
148+
dwmac->m250_div.hw.init = &init;
149+
dwmac->m250_div.flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO;
150+
151+
dwmac->m250_div_clk = devm_clk_register(dev, &dwmac->m250_div.hw);
152+
if (WARN_ON(IS_ERR(dwmac->m250_div_clk)))
153+
return PTR_ERR(dwmac->m250_div_clk);
154+
155+
/* create the m25_div */
156+
snprintf(clk_name, sizeof(clk_name), "%s#m25_div", dev_name(dev));
157+
init.name = devm_kstrdup(dev, clk_name, GFP_KERNEL);
158+
init.ops = &clk_divider_ops;
159+
init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;
160+
clk_div_parents[0] = __clk_get_name(dwmac->m250_div_clk);
161+
init.parent_names = clk_div_parents;
162+
init.num_parents = ARRAY_SIZE(clk_div_parents);
163+
164+
dwmac->m25_div.reg = dwmac->regs + PRG_ETH0;
165+
dwmac->m25_div.shift = PRG_ETH0_CLK_M25_DIV_SHIFT;
166+
dwmac->m25_div.width = PRG_ETH0_CLK_M25_DIV_WIDTH;
167+
dwmac->m25_div.table = clk_25m_div_table;
168+
dwmac->m25_div.hw.init = &init;
169+
dwmac->m25_div.flags = CLK_DIVIDER_ALLOW_ZERO;
170+
171+
dwmac->m25_div_clk = devm_clk_register(dev, &dwmac->m25_div.hw);
172+
if (WARN_ON(IS_ERR(dwmac->m25_div_clk)))
173+
return PTR_ERR(dwmac->m25_div_clk);
174+
175+
return 0;
176+
}
177+
178+
static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
179+
{
180+
int ret;
181+
unsigned long clk_rate;
182+
183+
switch (dwmac->phy_mode) {
184+
case PHY_INTERFACE_MODE_RGMII:
185+
case PHY_INTERFACE_MODE_RGMII_ID:
186+
case PHY_INTERFACE_MODE_RGMII_RXID:
187+
case PHY_INTERFACE_MODE_RGMII_TXID:
188+
/* Generate a 25MHz clock for the PHY */
189+
clk_rate = 25 * 1000 * 1000;
190+
191+
/* enable RGMII mode */
192+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_RGMII_MODE,
193+
PRG_ETH0_RGMII_MODE);
194+
195+
/* only relevant for RMII mode -> disable in RGMII mode */
196+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
197+
PRG_ETH0_INVERTED_RMII_CLK, 0);
198+
199+
/* TX clock delay - all known boards use a 1/4 cycle delay */
200+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK,
201+
PRG_ETH0_TXDLY_QUARTER);
202+
break;
203+
204+
case PHY_INTERFACE_MODE_RMII:
205+
/* Use the rate of the mux clock for the internal RMII PHY */
206+
clk_rate = clk_get_rate(dwmac->m250_mux_clk);
207+
208+
/* disable RGMII mode -> enables RMII mode */
209+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_RGMII_MODE,
210+
0);
211+
212+
/* invert internal clk_rmii_i to generate 25/2.5 tx_rx_clk */
213+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
214+
PRG_ETH0_INVERTED_RMII_CLK,
215+
PRG_ETH0_INVERTED_RMII_CLK);
216+
217+
/* TX clock delay cannot be configured in RMII mode */
218+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK,
219+
0);
220+
221+
break;
222+
223+
default:
224+
dev_err(&dwmac->pdev->dev, "unsupported phy-mode %s\n",
225+
phy_modes(dwmac->phy_mode));
226+
return -EINVAL;
227+
}
228+
229+
ret = clk_prepare_enable(dwmac->m25_div_clk);
230+
if (ret) {
231+
dev_err(&dwmac->pdev->dev, "failed to enable the PHY clock\n");
232+
return ret;
233+
}
234+
235+
ret = clk_set_rate(dwmac->m25_div_clk, clk_rate);
236+
if (ret) {
237+
clk_disable_unprepare(dwmac->m25_div_clk);
238+
239+
dev_err(&dwmac->pdev->dev, "failed to set PHY clock\n");
240+
return ret;
241+
}
242+
243+
/* enable TX_CLK and PHY_REF_CLK generator */
244+
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TX_AND_PHY_REF_CLK,
245+
PRG_ETH0_TX_AND_PHY_REF_CLK);
246+
247+
return 0;
248+
}
249+
250+
static int meson8b_dwmac_probe(struct platform_device *pdev)
251+
{
252+
struct plat_stmmacenet_data *plat_dat;
253+
struct stmmac_resources stmmac_res;
254+
struct resource *res;
255+
struct meson8b_dwmac *dwmac;
256+
int ret;
257+
258+
ret = stmmac_get_platform_resources(pdev, &stmmac_res);
259+
if (ret)
260+
return ret;
261+
262+
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
263+
if (IS_ERR(plat_dat))
264+
return PTR_ERR(plat_dat);
265+
266+
dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
267+
if (!dwmac)
268+
return -ENOMEM;
269+
270+
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
271+
dwmac->regs = devm_ioremap_resource(&pdev->dev, res);
272+
if (IS_ERR(dwmac->regs))
273+
return PTR_ERR(dwmac->regs);
274+
275+
dwmac->pdev = pdev;
276+
dwmac->phy_mode = of_get_phy_mode(pdev->dev.of_node);
277+
if (dwmac->phy_mode < 0) {
278+
dev_err(&pdev->dev, "missing phy-mode property\n");
279+
return -EINVAL;
280+
}
281+
282+
ret = meson8b_init_clk(dwmac);
283+
if (ret)
284+
return ret;
285+
286+
ret = meson8b_init_prg_eth(dwmac);
287+
if (ret)
288+
return ret;
289+
290+
plat_dat->bsp_priv = dwmac;
291+
292+
return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
293+
}
294+
295+
static int meson8b_dwmac_remove(struct platform_device *pdev)
296+
{
297+
struct meson8b_dwmac *dwmac = get_stmmac_bsp_priv(&pdev->dev);
298+
299+
clk_disable_unprepare(dwmac->m25_div_clk);
300+
301+
return stmmac_pltfr_remove(pdev);
302+
}
303+
304+
static const struct of_device_id meson8b_dwmac_match[] = {
305+
{ .compatible = "amlogic,meson8b-dwmac" },
306+
{ .compatible = "amlogic,meson-gxbb-dwmac" },
307+
{ }
308+
};
309+
MODULE_DEVICE_TABLE(of, meson8b_dwmac_match);
310+
311+
static struct platform_driver meson8b_dwmac_driver = {
312+
.probe = meson8b_dwmac_probe,
313+
.remove = meson8b_dwmac_remove,
314+
.driver = {
315+
.name = "meson8b-dwmac",
316+
.pm = &stmmac_pltfr_pm_ops,
317+
.of_match_table = meson8b_dwmac_match,
318+
},
319+
};
320+
module_platform_driver(meson8b_dwmac_driver);
321+
322+
MODULE_AUTHOR("Martin Blumenstingl <[email protected]>");
323+
MODULE_DESCRIPTION("Amlogic Meson8b and GXBB DWMAC glue layer");
324+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)