Skip to content

Commit adfa239

Browse files
Add CompositeEMAC support for Nuvoton M480 (#459)
* Nuvoton M480: use CompositeEMAC * Fix some bugs, EMAC test passing! * Fix LwIP tests * Final cleanup * Clarify comments
1 parent 069ef73 commit adfa239

File tree

19 files changed

+836
-672
lines changed

19 files changed

+836
-672
lines changed

connectivity/drivers/emac/TARGET_NUVOTON_EMAC/CMakeLists.txt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ elseif("M460" IN_LIST MBED_TARGET_LABELS)
99
add_subdirectory(TARGET_M460)
1010
endif()
1111

12-
target_include_directories(mbed-emac
13-
PUBLIC
14-
.
15-
)
12+
if(NOT "M480" IN_LIST MBED_TARGET_LABELS)
13+
target_include_directories(mbed-emac
14+
PUBLIC
15+
.
16+
)
1617

17-
target_sources(mbed-emac
18-
PRIVATE
19-
numaker_emac.cpp
20-
)
18+
target_sources(mbed-emac
19+
PRIVATE
20+
numaker_emac.cpp
21+
)
22+
endif()

connectivity/drivers/emac/TARGET_NUVOTON_EMAC/TARGET_M480/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ target_include_directories(mbed-emac
88

99
target_sources(mbed-emac
1010
PRIVATE
11-
m480_eth.c
11+
NuvotonM480EthMAC.cpp
12+
m480_eth_pins.c
1213
)
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
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

Comments
 (0)