1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* DWMAC glue driver for Motorcomm PCI Ethernet controllers
*
* Copyright (c) 2025-2026 Yao Zi <me@ziyao.cc>
*/
#include <linux/bits.h>
#include <linux/dev_printk.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/stmmac.h>
#include "dwmac4.h"
#include "stmmac.h"
#include "stmmac_libpci.h"
#define DRIVER_NAME "dwmac-motorcomm"
#define PCI_VENDOR_ID_MOTORCOMM 0x1f0a
/* Register definition */
#define EPHY_CTRL 0x1004
/* Clearing this bit asserts resets for internal MDIO bus and PHY */
#define EPHY_MDIO_PHY_RESET BIT(0)
#define OOB_WOL_CTRL 0x1010
#define OOB_WOL_CTRL_DIS BIT(0)
#define MGMT_INT_CTRL0 0x1100
#define INT_MODERATION 0x1108
#define INT_MODERATION_RX GENMASK(11, 0)
#define INT_MODERATION_TX GENMASK(27, 16)
#define EFUSE_OP_CTRL_0 0x1500
#define EFUSE_OP_MODE GENMASK(1, 0)
#define EFUSE_OP_ROW_READ 0x1
#define EFUSE_OP_START BIT(2)
#define EFUSE_OP_ADDR GENMASK(15, 8)
#define EFUSE_OP_CTRL_1 0x1504
#define EFUSE_OP_DONE BIT(1)
#define EFUSE_OP_RD_DATA GENMASK(31, 24)
#define SYS_RESET 0x152c
#define SYS_RESET_RESET BIT(31)
#define GMAC_OFFSET 0x2000
/* Constants */
#define EFUSE_READ_TIMEOUT_US 20000
#define EFUSE_PATCH_REGION_OFFSET 18
#define EFUSE_PATCH_MAX_NUM 39
#define EFUSE_ADDR_MACA0LR 0x1520
#define EFUSE_ADDR_MACA0HR 0x1524
struct motorcomm_efuse_patch {
__le16 addr;
__le32 data;
} __packed;
struct dwmac_motorcomm_priv {
void __iomem *base;
};
static int motorcomm_efuse_read_byte(struct dwmac_motorcomm_priv *priv,
u8 offset, u8 *byte)
{
u32 reg;
int ret;
writel(FIELD_PREP(EFUSE_OP_MODE, EFUSE_OP_ROW_READ) |
FIELD_PREP(EFUSE_OP_ADDR, offset) |
EFUSE_OP_START, priv->base + EFUSE_OP_CTRL_0);
ret = readl_poll_timeout(priv->base + EFUSE_OP_CTRL_1,
reg, reg & EFUSE_OP_DONE, 2000,
EFUSE_READ_TIMEOUT_US);
*byte = FIELD_GET(EFUSE_OP_RD_DATA, reg);
return ret;
}
static int motorcomm_efuse_read_patch(struct dwmac_motorcomm_priv *priv,
u8 index,
struct motorcomm_efuse_patch *patch)
{
u8 *p = (u8 *)patch, offset;
int i, ret;
for (i = 0; i < sizeof(*patch); i++) {
offset = EFUSE_PATCH_REGION_OFFSET + sizeof(*patch) * index + i;
ret = motorcomm_efuse_read_byte(priv, offset, &p[i]);
if (ret)
return ret;
}
return 0;
}
static int motorcomm_efuse_get_patch_value(struct dwmac_motorcomm_priv *priv,
u16 addr, u32 *value)
{
struct motorcomm_efuse_patch patch;
int i, ret;
for (i = 0; i < EFUSE_PATCH_MAX_NUM; i++) {
ret = motorcomm_efuse_read_patch(priv, i, &patch);
if (ret)
return ret;
if (patch.addr == 0) {
return -ENOENT;
} else if (le16_to_cpu(patch.addr) == addr) {
*value = le32_to_cpu(patch.data);
return 0;
}
}
return -ENOENT;
}
static int motorcomm_efuse_read_mac(struct device *dev,
struct dwmac_motorcomm_priv *priv, u8 *mac)
{
u32 maca0lr, maca0hr;
int ret;
ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0LR,
&maca0lr);
if (ret)
return dev_err_probe(dev, ret,
"failed to read maca0lr from eFuse\n");
ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0HR,
&maca0hr);
if (ret)
return dev_err_probe(dev, ret,
"failed to read maca0hr from eFuse\n");
mac[0] = FIELD_GET(GENMASK(15, 8), maca0hr);
mac[1] = FIELD_GET(GENMASK(7, 0), maca0hr);
mac[2] = FIELD_GET(GENMASK(31, 24), maca0lr);
mac[3] = FIELD_GET(GENMASK(23, 16), maca0lr);
mac[4] = FIELD_GET(GENMASK(15, 8), maca0lr);
mac[5] = FIELD_GET(GENMASK(7, 0), maca0lr);
return 0;
}
static void motorcomm_deassert_mdio_phy_reset(struct dwmac_motorcomm_priv *priv)
{
u32 reg = readl(priv->base + EPHY_CTRL);
reg |= EPHY_MDIO_PHY_RESET;
writel(reg, priv->base + EPHY_CTRL);
}
static void motorcomm_reset(struct dwmac_motorcomm_priv *priv)
{
u32 reg = readl(priv->base + SYS_RESET);
reg &= ~SYS_RESET_RESET;
writel(reg, priv->base + SYS_RESET);
reg |= SYS_RESET_RESET;
writel(reg, priv->base + SYS_RESET);
motorcomm_deassert_mdio_phy_reset(priv);
}
static void motorcomm_init(struct dwmac_motorcomm_priv *priv)
{
writel(0x0, priv->base + MGMT_INT_CTRL0);
writel(FIELD_PREP(INT_MODERATION_RX, 200) |
FIELD_PREP(INT_MODERATION_TX, 200),
priv->base + INT_MODERATION);
/*
* OOB WOL must be disabled during normal operation, or DMA interrupts
* cannot be delivered to the host.
*/
writel(OOB_WOL_CTRL_DIS, priv->base + OOB_WOL_CTRL);
}
static int motorcomm_resume(struct device *dev, void *bsp_priv)
{
struct dwmac_motorcomm_priv *priv = bsp_priv;
int ret;
ret = stmmac_pci_plat_resume(dev, bsp_priv);
if (ret)
return ret;
/*
* When recovering from D3hot, EPHY_MDIO_PHY_RESET is automatically
* asserted, and must be deasserted for normal operation.
*/
motorcomm_deassert_mdio_phy_reset(priv);
motorcomm_init(priv);
return 0;
}
static struct plat_stmmacenet_data *
motorcomm_default_plat_data(struct pci_dev *pdev)
{
struct plat_stmmacenet_data *plat;
struct device *dev = &pdev->dev;
plat = stmmac_plat_dat_alloc(dev);
if (!plat)
return NULL;
plat->mdio_bus_data = devm_kzalloc(dev, sizeof(*plat->mdio_bus_data),
GFP_KERNEL);
if (!plat->mdio_bus_data)
return NULL;
plat->dma_cfg = devm_kzalloc(dev, sizeof(*plat->dma_cfg), GFP_KERNEL);
if (!plat->dma_cfg)
return NULL;
plat->axi = devm_kzalloc(dev, sizeof(*plat->axi), GFP_KERNEL);
if (!plat->axi)
return NULL;
plat->dma_cfg->pbl = DEFAULT_DMA_PBL;
plat->dma_cfg->pblx8 = true;
plat->dma_cfg->txpbl = 32;
plat->dma_cfg->rxpbl = 32;
plat->dma_cfg->eame = true;
plat->dma_cfg->mixed_burst = true;
plat->axi->axi_wr_osr_lmt = 1;
plat->axi->axi_rd_osr_lmt = 1;
plat->axi->axi_mb = true;
plat->axi->axi_blen_regval = DMA_AXI_BLEN4 | DMA_AXI_BLEN8 |
DMA_AXI_BLEN16 | DMA_AXI_BLEN32;
plat->bus_id = pci_dev_id(pdev);
plat->phy_interface = PHY_INTERFACE_MODE_GMII;
/*
* YT6801 requires an 25MHz clock input/oscillator to function, which
* is likely the source of CSR clock.
*/
plat->clk_csr = STMMAC_CSR_20_35M;
plat->tx_coe = 1;
plat->rx_coe = 1;
plat->clk_ref_rate = 125000000;
plat->core_type = DWMAC_CORE_GMAC4;
plat->suspend = stmmac_pci_plat_suspend;
plat->resume = motorcomm_resume;
plat->flags = STMMAC_FLAG_TSO_EN |
STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
return plat;
}
static void motorcomm_free_irq(void *data)
{
struct pci_dev *pdev = data;
pci_free_irq_vectors(pdev);
}
static int motorcomm_setup_irq(struct pci_dev *pdev,
struct stmmac_resources *res,
struct plat_stmmacenet_data *plat)
{
int ret;
ret = pci_alloc_irq_vectors(pdev, 6, 6, PCI_IRQ_MSIX);
if (ret > 0) {
res->rx_irq[0] = pci_irq_vector(pdev, 0);
res->tx_irq[0] = pci_irq_vector(pdev, 4);
res->irq = pci_irq_vector(pdev, 5);
plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
} else {
dev_info(&pdev->dev, "failed to allocate MSI-X vector: %d\n",
ret);
dev_info(&pdev->dev, "try MSI instead\n");
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"failed to allocate MSI\n");
res->irq = pci_irq_vector(pdev, 0);
}
return devm_add_action_or_reset(&pdev->dev, motorcomm_free_irq, pdev);
}
static int motorcomm_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct plat_stmmacenet_data *plat;
struct dwmac_motorcomm_priv *priv;
struct stmmac_resources res = {};
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
plat = motorcomm_default_plat_data(pdev);
if (!plat)
return -ENOMEM;
plat->bsp_priv = priv;
ret = pcim_enable_device(pdev);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to enable device\n");
priv->base = pcim_iomap_region(pdev, 0, DRIVER_NAME);
if (IS_ERR(priv->base))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->base),
"failed to map IO region\n");
pci_set_master(pdev);
/*
* Some PCIe addons cards based on YT6801 don't deliver MSI(X) with ASPM
* enabled. Sadly there isn't a reliable way to read out OEM of the
* card, so let's disable L1 state unconditionally for safety.
*/
ret = pci_disable_link_state(pdev, PCIE_LINK_STATE_L1);
if (ret)
dev_warn(&pdev->dev, "failed to disable L1 state: %d\n", ret);
motorcomm_reset(priv);
ret = motorcomm_efuse_read_mac(&pdev->dev, priv, res.mac);
if (ret == -ENOENT) {
dev_warn(&pdev->dev, "eFuse contains no valid MAC address\n");
dev_warn(&pdev->dev, "fallback to random MAC address\n");
eth_random_addr(res.mac);
} else if (ret) {
return dev_err_probe(&pdev->dev, ret,
"failed to read MAC address from eFuse\n");
}
ret = motorcomm_setup_irq(pdev, &res, plat);
if (ret)
return dev_err_probe(&pdev->dev, ret, "failed to setup IRQ\n");
motorcomm_init(priv);
res.addr = priv->base + GMAC_OFFSET;
return stmmac_dvr_probe(&pdev->dev, plat, &res);
}
static void motorcomm_remove(struct pci_dev *pdev)
{
stmmac_dvr_remove(&pdev->dev);
}
static const struct pci_device_id dwmac_motorcomm_pci_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_MOTORCOMM, 0x6801) },
{ },
};
MODULE_DEVICE_TABLE(pci, dwmac_motorcomm_pci_id_table);
static struct pci_driver dwmac_motorcomm_pci_driver = {
.name = DRIVER_NAME,
.id_table = dwmac_motorcomm_pci_id_table,
.probe = motorcomm_probe,
.remove = motorcomm_remove,
.driver = {
.pm = &stmmac_simple_pm_ops,
},
};
module_pci_driver(dwmac_motorcomm_pci_driver);
MODULE_DESCRIPTION("DWMAC glue driver for Motorcomm PCI Ethernet controllers");
MODULE_AUTHOR("Yao Zi <me@ziyao.cc>");
MODULE_LICENSE("GPL");
|