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