1
+ /* Copyright (c) 2025 Jamie Smith
2
+ * SPDX-License-Identifier: Apache-2.0
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include " NuvotonM480EthMAC.h"
18
+
19
+ #include < mbed_power_mgmt.h>
20
+ #include < mbed_error.h>
21
+
22
+ #include " m480_eth_pins.h"
23
+
24
+ namespace mbed {
25
+
26
+ void NuvotonM480EthMAC::MACDriver::writeMACAddress (size_t index, MACAddress macAddress) {
27
+ // Find the registers to write the MAC into. Sadly they didn't use an array...
28
+ volatile uint32_t * highReg = (&base->CAM0M ) + 2 * index ;
29
+ volatile uint32_t * lowReg = (&base->CAM0L ) + 2 * index ;
30
+
31
+ // Write the MAC into the registers.
32
+ *highReg = (static_cast <uint32_t >(macAddress[0 ]) << 24 ) | (static_cast <uint32_t >(macAddress[1 ]) << 16 ) | (static_cast <uint32_t >(macAddress[2 ]) << 8 ) | macAddress[3 ];
33
+ *lowReg = (static_cast <uint32_t >(macAddress[4 ]) << 24 ) | (static_cast <uint32_t >(macAddress[5 ]) << 16 );
34
+
35
+ // Mark the address as valid
36
+ base->CAMEN |= (1 << index );
37
+ }
38
+
39
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::init () {
40
+ sleep_manager_lock_deep_sleep ();
41
+ nu_eth_clk_and_pin_init ();
42
+
43
+ // Reset MAC
44
+ base->CTL = EMAC_CTL_RST_Msk;
45
+ while (base->CTL & EMAC_CTL_RST_Msk) {}
46
+
47
+ // Reset class vars
48
+ numMulticastSubscriptions = 0 ;
49
+ passAllMcastEnabled = false ;
50
+ promiscuousEnabled = false ;
51
+
52
+ /* Configure the MAC interrupt enable register. Note that we need to enable interrupts for all types
53
+ * of Rx errors, so that we know when any Rx descriptor has been freed up by the DMA. */
54
+ base->INTEN = EMAC_INTEN_RXIEN_Msk |
55
+ EMAC_INTEN_TXIEN_Msk |
56
+ EMAC_INTEN_RXGDIEN_Msk |
57
+ EMAC_INTEN_TXCPIEN_Msk |
58
+ EMAC_INTEN_RXBEIEN_Msk |
59
+ EMAC_INTEN_TXBEIEN_Msk |
60
+ EMAC_INTEN_CRCEIEN_Msk |
61
+ EMAC_INTEN_RXOVIEN_Msk |
62
+ EMAC_INTEN_ALIEIEN_Msk |
63
+ EMAC_INTEN_RPIEN_Msk |
64
+ EMAC_INTEN_MFLEIEN_Msk;
65
+
66
+ /* Enable interrupts. */
67
+ NVIC_SetVector (EMAC_RX_IRQn, reinterpret_cast <uint32_t >(&NuvotonM480EthMAC::rxIrqHandler));
68
+ NVIC_EnableIRQ (EMAC_RX_IRQn);
69
+ NVIC_SetVector (EMAC_TX_IRQn, reinterpret_cast <uint32_t >(&NuvotonM480EthMAC::txIrqHandler));
70
+ NVIC_EnableIRQ (EMAC_TX_IRQn);
71
+
72
+ /* Configure the MAC control register. */
73
+ base->CTL = EMAC_CTL_STRIPCRC_Msk | EMAC_CTL_RMIIEN_Msk;
74
+
75
+ /* Accept broadcast packets without using the address filter */
76
+ base->CAMCTL = EMAC_CAMCTL_CMPEN_Msk |
77
+ EMAC_CAMCTL_ABP_Msk;
78
+
79
+ // Maximum frame length.
80
+ // This apparently includes the CRC, so we need to set this 4 bytes higher than the MTU of 1514 bytes
81
+ // or 1514 byte packets get rejected
82
+ base->MRFL = 1518 ;
83
+
84
+ /* Set RX FIFO threshold as 8 words */
85
+ base->FIFOCTL = 0x00200100 ;
86
+
87
+ return ErrCode::SUCCESS;
88
+ }
89
+
90
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::deinit () {
91
+ NVIC_DisableIRQ (EMAC_RX_IRQn);
92
+ NVIC_DisableIRQ (EMAC_TX_IRQn);
93
+
94
+ nu_eth_clk_and_pin_deinit ();
95
+
96
+ sleep_manager_unlock_deep_sleep ();
97
+
98
+ return ErrCode::SUCCESS;
99
+ }
100
+
101
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::enable (LinkSpeed speed, Duplex duplex) {
102
+ if (speed == LinkSpeed::LINK_100MBIT) {
103
+ base->CTL |= EMAC_CTL_OPMODE_Msk;
104
+ }
105
+ else {
106
+ base->CTL &= ~EMAC_CTL_OPMODE_Msk;
107
+ }
108
+
109
+ if (duplex == Duplex::FULL) {
110
+ base->CTL |= EMAC_CTL_FUDUP_Msk;
111
+ }
112
+ else {
113
+ base->CTL &= ~EMAC_CTL_FUDUP_Msk;
114
+ }
115
+
116
+ base->CTL |= EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk;
117
+
118
+ return ErrCode::SUCCESS;
119
+ }
120
+
121
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::disable () {
122
+ base->CTL &= ~(EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk);
123
+
124
+ return ErrCode::SUCCESS;
125
+ }
126
+
127
+ void NuvotonM480EthMAC::MACDriver::setOwnMACAddr (const MACAddress &ownAddress) {
128
+ writeMACAddress (0 , ownAddress);
129
+ }
130
+
131
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::mdioRead (uint8_t devAddr, uint8_t regAddr, uint16_t &result) {
132
+ base->MIIMCTL = (devAddr << EMAC_MIIMCTL_PHYADDR_Pos) | regAddr | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_MDCON_Msk;
133
+ while (base->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk);
134
+ result = base->MIIMDAT ;
135
+
136
+ return ErrCode::SUCCESS;
137
+ }
138
+
139
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::mdioWrite (uint8_t devAddr, uint8_t regAddr, uint16_t data) {
140
+ base->MIIMDAT = data;
141
+ base->MIIMCTL = (devAddr << EMAC_MIIMCTL_PHYADDR_Pos) | regAddr | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_WRITE_Msk | EMAC_MIIMCTL_MDCON_Msk;
142
+
143
+ while (base->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk);
144
+ return ErrCode::SUCCESS;
145
+ }
146
+
147
+ PinName NuvotonM480EthMAC::MACDriver::getPhyResetPin () {
148
+ return nu_eth_get_phy_reset_pin ();
149
+ }
150
+
151
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::addMcastMAC (MACAddress mac) {
152
+ if (numMulticastSubscriptions >= 14 ) {
153
+ // 14 is the max we can handle in hardware
154
+ return ErrCode::OUT_OF_MEMORY;
155
+ }
156
+ // We use MAC slots 1 through 14 for the multicast subscriptions
157
+ ++numMulticastSubscriptions;
158
+ writeMACAddress (numMulticastSubscriptions, mac);
159
+
160
+ return ErrCode::SUCCESS;
161
+ }
162
+
163
+ CompositeEMAC::ErrCode NuvotonM480EthMAC::MACDriver::clearMcastFilter () {
164
+ // Disable all MAC addresses except CAM0, which is our own unicast MAC
165
+ base->CAMEN = 1 ;
166
+
167
+ return ErrCode::SUCCESS;
168
+ }
169
+
170
+ void NuvotonM480EthMAC::MACDriver::setPassAllMcast (bool pass) {
171
+ passAllMcastEnabled = pass;
172
+ if (pass) {
173
+ base->CAMCTL |= EMAC_CAMCTL_AMP_Msk;
174
+ }
175
+ else if (!promiscuousEnabled){
176
+ base->CAMCTL &= ~EMAC_CAMCTL_AMP_Msk;
177
+ }
178
+ }
179
+
180
+ void NuvotonM480EthMAC::MACDriver::setPromiscuous (bool enable) {
181
+ promiscuousEnabled = enable;
182
+
183
+ // To enable promiscuous mode on this MAC, we need to enable pass all multicast and pass all unicast.
184
+ if (enable) {
185
+ base->CAMCTL |= EMAC_CAMCTL_AMP_Msk | EMAC_CAMCTL_AUP_Msk;
186
+ }
187
+ else {
188
+ base->CAMCTL &= ~EMAC_CAMCTL_AUP_Msk;
189
+
190
+ // Only disable the AMP bit if we aren't in pass-all-mcast mode
191
+ if (!passAllMcastEnabled) {
192
+ base->CAMCTL &= ~EMAC_CAMCTL_AMP_Msk;
193
+ }
194
+ }
195
+ }
196
+
197
+ void NuvotonM480EthMAC::TxDMA::startDMA () {
198
+ // Set linked list base address
199
+ base->TXDSA = reinterpret_cast <uint32_t >(&txDescs[0 ]);
200
+ }
201
+
202
+ void NuvotonM480EthMAC::TxDMA::stopDMA () {
203
+ // No specific disable for DMA. DMA will get disabled when the MAC is disabled.
204
+ }
205
+
206
+ bool NuvotonM480EthMAC::TxDMA::descOwnedByDMA (size_t descIdx) {
207
+ return txDescs[descIdx].EMAC_OWN ;
208
+ }
209
+
210
+ bool NuvotonM480EthMAC::TxDMA::isDMAReadableBuffer (uint8_t const *start, size_t size) const {
211
+ // No restrictions on what DMA can read
212
+ return true ;
213
+ }
214
+
215
+ void NuvotonM480EthMAC::TxDMA::giveToDMA (size_t descIdx, uint8_t const *buffer, size_t len, bool firstDesc,
216
+ bool lastDesc) {
217
+
218
+ // Populate Tx descriptor fields
219
+ txDescs[descIdx].PADEN = true ;
220
+ txDescs[descIdx].CRCAPP = true ;
221
+ txDescs[descIdx].INTEN = true ;
222
+ txDescs[descIdx].TXBSA = buffer;
223
+ txDescs[descIdx].TBC = len;
224
+ txDescs[descIdx].NTXDSA = &txDescs[(descIdx + 1 ) % TX_NUM_DESCS];
225
+
226
+ // Give to DMA
227
+ txDescs[descIdx].EMAC_OWN = true ;
228
+
229
+ // Tell DMA to start writing if stopped
230
+ base->TXST = 1 ;
231
+ }
232
+
233
+ void NuvotonM480EthMAC::RxDMA::startDMA () {
234
+ // Set linked list base address
235
+ base->RXDSA = reinterpret_cast <uint32_t >(&rxDescs[0 ]);
236
+ }
237
+
238
+ void NuvotonM480EthMAC::RxDMA::stopDMA () {
239
+ // No specific disable for DMA. DMA will get disabled when the MAC is disabled.
240
+ }
241
+
242
+ bool NuvotonM480EthMAC::RxDMA::descOwnedByDMA (size_t descIdx) {
243
+ return rxDescs[descIdx].EMAC_OWN ;
244
+ }
245
+
246
+ // The M480 EMAC enforces a 1:1 descriptor to packet relationship, so every desc is always a first and last desc.
247
+ bool NuvotonM480EthMAC::RxDMA::isFirstDesc (size_t descIdx) {
248
+ return true ;
249
+ }
250
+ bool NuvotonM480EthMAC::RxDMA::isLastDesc (size_t descIdx) {
251
+ return true ;
252
+ }
253
+
254
+ bool NuvotonM480EthMAC::RxDMA::isErrorDesc (size_t descIdx) {
255
+ // If it's not a good frame, then it's an error.
256
+ return !(rxDescs[descIdx].RXGDIF );
257
+ }
258
+
259
+ void NuvotonM480EthMAC::RxDMA::returnDescriptor (size_t descIdx, uint8_t *buffer) {
260
+ // Populate descriptor
261
+ rxDescs[descIdx].RXBSA = buffer;
262
+ rxDescs[descIdx].NRXDSA = &rxDescs[(descIdx + 1 ) % RX_NUM_DESCS];
263
+
264
+ // Give to DMA
265
+ rxDescs[descIdx].EMAC_OWN = true ;
266
+
267
+ // Tell DMA to start receiving if stopped
268
+ base->RXST = 1 ;
269
+ }
270
+
271
+ size_t NuvotonM480EthMAC::RxDMA::getTotalLen (size_t firstDescIdx, size_t lastDescIdx) {
272
+ return rxDescs[firstDescIdx].RBC ;
273
+ }
274
+
275
+ NuvotonM480EthMAC * NuvotonM480EthMAC::instance = nullptr ;
276
+
277
+ NuvotonM480EthMAC::NuvotonM480EthMAC ():
278
+ CompositeEMAC (txDMA, rxDMA, macDriver),
279
+ // Note: we can't use the "EMAC" symbol directly because it conflicts with the EMAC class. So we have to
280
+ // use the integer address and cast it instead.
281
+ base (reinterpret_cast <EMAC_T *>(EMAC_BASE)),
282
+ txDMA (base),
283
+ rxDMA (base),
284
+ macDriver (base)
285
+ {
286
+ instance = this ;
287
+ }
288
+
289
+ void NuvotonM480EthMAC::txIrqHandler () {
290
+ const auto base = instance->base ;
291
+ if (base->INTSTS & EMAC_INTSTS_TXBEIF_Msk) {
292
+ MBED_ERROR (MBED_MAKE_ERROR (MBED_MODULE_DRIVER_ETHERNET, EIO), \
293
+ " M480 EMAC: Hardware reports fatal DMA Tx bus error\n " );
294
+ }
295
+
296
+ if (base->INTSTS & EMAC_INTSTS_TXCPIF_Msk) {
297
+ // Transmission complete
298
+ instance->txISR ();
299
+
300
+ // Clear flag
301
+ base->INTSTS = EMAC_INTSTS_TXCPIF_Msk;
302
+ }
303
+
304
+ // Clear general Tx interrupt flag
305
+ base->INTSTS = EMAC_INTSTS_TXIF_Msk;
306
+ }
307
+
308
+ void NuvotonM480EthMAC::rxIrqHandler () {
309
+ const auto base = instance->base ;
310
+ if (base->INTSTS & EMAC_INTSTS_RXBEIF_Msk) {
311
+ MBED_ERROR (MBED_MAKE_ERROR (MBED_MODULE_DRIVER_ETHERNET, EIO), \
312
+ " M480 EMAC: Hardware reports fatal DMA Rx bus error\n " );
313
+ }
314
+
315
+ if (base->INTSTS & EMAC_INTSTS_RXIF_Msk) {
316
+ // Frames(s) received (good or otherwise)
317
+ instance->rxISR ();
318
+
319
+ // Clear flags
320
+ base->INTSTS = EMAC_INTSTS_RXIF_Msk |
321
+ EMAC_INTSTS_CRCEIF_Msk |
322
+ EMAC_INTSTS_RXOVIF_Msk |
323
+ EMAC_INTSTS_LPIF_Msk |
324
+ EMAC_INTSTS_RXGDIF_Msk |
325
+ EMAC_INTSTS_RPIF_Msk |
326
+ EMAC_INTSTS_MFLEIF_Msk;
327
+ }
328
+
329
+ // Clear general Tx interrupt flag
330
+ base->INTSTS = EMAC_INTSTS_TXIF_Msk;
331
+ }
332
+ }
333
+
334
+ // Provide default EMAC driver
335
+ MBED_WEAK EMAC &EMAC::get_default_instance ()
336
+ {
337
+ static mbed::NuvotonM480EthMAC emac;
338
+ return emac;
339
+ }
0 commit comments