1+ #ifndef KLIB_NXP_LPC178X_EMAC_HPP
2+ #define KLIB_NXP_LPC178X_EMAC_HPP
3+
4+ #include < klib/delay.hpp>
5+
6+ #include < klib/multispan.hpp>
7+ #include < klib/io/core_clock.hpp>
8+ #include < klib/io/bus/ethernet.hpp>
9+
10+ #include < io/port.hpp>
11+ #include < io/clocks.hpp>
12+ #include < io/power.hpp>
13+
14+ namespace klib ::core::lpc178x::io {
15+ template <typename Emac, klib::io::ethernet::mii Mii, uint32_t MaxFrameLength = 1536 >
16+ class emac {
17+ protected:
18+ // make sure we have a valid media independent interface we support
19+ static_assert (
20+ (Mii == klib::io::ethernet::mii::mii) || (Mii == klib::io::ethernet::mii::rmii),
21+ " Invalid media independent interface requested"
22+ );
23+
24+ static_assert (
25+ MaxFrameLength <= 0xffff , " Invalid max frame length"
26+ );
27+
28+ constexpr static uint32_t get_clk_divider (uint32_t clock) {
29+ // all the available dividers and frequencies
30+ constexpr std::array<std::array<uint8_t , 2 >, 15 > dividers = {{
31+ {160 , 0b1111 },
32+ {150 , 0b1110 },
33+ {140 , 0b1101 },
34+ {130 , 0b1100 },
35+ {120 , 0b1011 },
36+ {100 , 0b1010 },
37+ {90 , 0b1001 },
38+ {80 , 0b1000 },
39+ {70 , 0b0111 },
40+ {50 , 0b0110 },
41+ {35 , 0b0101 },
42+ {25 , 0b0100 },
43+ {20 , 0b0011 },
44+ {15 , 0b0010 },
45+ {10 , 0b0000 }
46+ }};
47+
48+ // get the frequency in mhz
49+ const uint32_t mhz = clock / 1'000'000 ;
50+
51+ // search if the frequency in the table is higher than the current frequency
52+ for (const auto & pair : dividers) {
53+ if (mhz <= pair[0 ]) {
54+ return pair[1 ];
55+ }
56+ }
57+
58+ // return the highest divider if we cannot find it
59+ return dividers[0 ][1 ];
60+ }
61+
62+ /* *
63+ * @brief Wait until the phy is done with our request or until
64+ * the timeout has passed
65+ *
66+ * @param timeout
67+ * @return true
68+ * @return false
69+ */
70+ static bool phy_wait_not_busy (const klib::time::ms timeout) {
71+ const auto time = klib::io::systick<>::get_runtime ();
72+
73+ // wait until the read is done or a timeout
74+ while ((klib::io::systick<>::get_runtime () - time) < timeout) {
75+ // check if the read is done
76+ if (!(Emac::port->MIND & 0x1 )) {
77+ return false ;
78+ }
79+ }
80+
81+ return true ;
82+ }
83+
84+ public:
85+ static void init (const klib::io::ethernet::mac_address& mac) {
86+ // enable power to the peripheral
87+ target::io::power_control::enable<Emac>();
88+
89+ // setup all the io pins based on the media independent interface
90+ if constexpr (Mii == klib::io::ethernet::mii::mii) {
91+ io::detail::pins::set_peripheral<typename Emac::mii::txd0::pin, typename Emac::mii::txd0::periph>();
92+ io::detail::pins::set_peripheral<typename Emac::mii::txd1::pin, typename Emac::mii::txd1::periph>();
93+ io::detail::pins::set_peripheral<typename Emac::mii::txd2::pin, typename Emac::mii::txd2::periph>();
94+ io::detail::pins::set_peripheral<typename Emac::mii::txd3::pin, typename Emac::mii::txd3::periph>();
95+ io::detail::pins::set_peripheral<typename Emac::mii::tx_en::pin, typename Emac::mii::tx_en::periph>();
96+ io::detail::pins::set_peripheral<typename Emac::mii::tx_er::pin, typename Emac::mii::tx_er::periph>();
97+ io::detail::pins::set_peripheral<typename Emac::mii::tx_clk::pin, typename Emac::mii::tx_clk::periph>();
98+ io::detail::pins::set_peripheral<typename Emac::mii::col::pin, typename Emac::mii::col::periph>();
99+ io::detail::pins::set_peripheral<typename Emac::mii::crs::pin, typename Emac::mii::crs::periph>();
100+ io::detail::pins::set_peripheral<typename Emac::mii::rxd0::pin, typename Emac::mii::rxd0::periph>();
101+ io::detail::pins::set_peripheral<typename Emac::mii::rxd1::pin, typename Emac::mii::rxd1::periph>();
102+ io::detail::pins::set_peripheral<typename Emac::mii::rxd2::pin, typename Emac::mii::rxd2::periph>();
103+ io::detail::pins::set_peripheral<typename Emac::mii::rxd3::pin, typename Emac::mii::rxd3::periph>();
104+ io::detail::pins::set_peripheral<typename Emac::mii::rx_dv::pin, typename Emac::mii::rx_dv::periph>();
105+ io::detail::pins::set_peripheral<typename Emac::mii::rx_er::pin, typename Emac::mii::rx_er::periph>();
106+ io::detail::pins::set_peripheral<typename Emac::mii::rx_clk::pin, typename Emac::mii::rx_clk::periph>();
107+ }
108+ else {
109+ io::detail::pins::set_peripheral<typename Emac::rmii::txd0::pin, typename Emac::rmii::txd0::periph>();
110+ io::detail::pins::set_peripheral<typename Emac::rmii::txd1::pin, typename Emac::rmii::txd1::periph>();
111+ io::detail::pins::set_peripheral<typename Emac::rmii::tx_en::pin, typename Emac::rmii::tx_en::periph>();
112+ io::detail::pins::set_peripheral<typename Emac::rmii::crs::pin, typename Emac::rmii::crs::periph>();
113+ io::detail::pins::set_peripheral<typename Emac::rmii::rxd0::pin, typename Emac::rmii::rxd0::periph>();
114+ io::detail::pins::set_peripheral<typename Emac::rmii::rxd1::pin, typename Emac::rmii::rxd1::periph>();
115+ io::detail::pins::set_peripheral<typename Emac::rmii::rx_er::pin, typename Emac::rmii::rx_er::periph>();
116+ io::detail::pins::set_peripheral<typename Emac::rmii::ref_clk::pin, typename Emac::rmii::ref_clk::periph>();
117+ }
118+
119+ // setup the miim io
120+ io::detail::pins::set_peripheral<typename Emac::mdc::pin, typename Emac::mdc::periph>();
121+ io::detail::pins::set_peripheral<typename Emac::mdio::pin, typename Emac::mdio::periph>();
122+
123+ // reset the rx and tx side in the mac register
124+ Emac::port->MAC1 = (
125+ (0x1 << 8 ) | (0x1 << 9 ) | (0x1 << 10 ) |
126+ (0x1 << 11 ) | (0x1 << 14 ) | (0x1 << 15 )
127+ );
128+
129+ // reset the rx and tx datapath in the command register
130+ Emac::port->COMMAND |= (0b111 << 3 ) | (0x1 << 6 );
131+
132+ // wait a little bit
133+ klib::delay<klib::busy_wait>(klib::time::us (100 ));
134+
135+ // pass all receive frames
136+ Emac::port->MAC1 = (0x1 << 1 );
137+
138+ // enable crc and pad out frames
139+ Emac::port->MAC2 = (0x1 << 4 ) | (0x1 << 5 );
140+
141+ // set the maximum frame length
142+ Emac::port->MAXF = MaxFrameLength;
143+
144+ // set the default collision windows and retry register
145+ Emac::port->CLRT = 0x370f ;
146+
147+ // set the recommended back to back inter packet gap
148+ Emac::port->IPGR = 0x12 ;
149+
150+ // configure the miim
151+ // check what divider to use for the current frequency
152+ const uint32_t divider = get_clk_divider (klib::io::clock::get ());
153+
154+ // reset the miim interface
155+ Emac::port->MCFG = (0x1 << 15 ) | (divider << 2 );
156+
157+ // give the miim some time to reset
158+ klib::delay<klib::busy_wait>(klib::time::us (100 ));
159+
160+ // enable the miim interface
161+ Emac::port->MCFG = (divider << 2 );
162+
163+ // configure the RMII or MII interface
164+ if constexpr (Mii == klib::io::ethernet::mii::mii) {
165+ // TODO: add support for MII
166+ }
167+ else {
168+ // set the mac command register to enable RMII and prevent
169+ // runt frames being filtered out
170+ Emac::port->COMMAND = (0x1 << 9 ) | (0x1 << 6 ) | (0x1 << 7 );
171+ }
172+
173+ // configure the mac address
174+ Emac::port->SA0 = mac[0 ] << 8 | mac[1 ];
175+ Emac::port->SA1 = mac[2 ] << 8 | mac[3 ];
176+ Emac::port->SA2 = mac[4 ] << 8 | mac[5 ];
177+
178+ // TODO: add descriptor initalisation
179+ }
180+
181+ /* *
182+ * @brief Configure the emac after the link status has changed
183+ *
184+ * @param config
185+ */
186+ static void configure (const klib::io::ethernet::link_config config) {
187+ // configure the modes we received from the phy
188+ if (config.full_duplex ) {
189+ // enable full duplex on the MAC
190+ Emac::port->MAC2 |= 0x1 ;
191+
192+ // enable full duplex in the command register
193+ Emac::port->COMMAND |= (0x1 << 10 );
194+
195+ // change the packet to packet gap register
196+ Emac::port->IPGT = 0x15 ;
197+ }
198+ else {
199+ // enable full duplex on the MAC
200+ Emac::port->MAC2 &= (~0x1 );
201+
202+ // enable full duplex in the command register
203+ Emac::port->COMMAND &= (~(0x1 << 10 ));
204+
205+ // change the packet to packet gap register
206+ Emac::port->IPGT = 0x12 ;
207+ }
208+
209+ // configure the speed
210+ Emac::port->SUPP = (
211+ (config.link_speed == klib::io::ethernet::speed::mbps_10) ?
212+ 0x0 : (0x1 << 8 )
213+ );
214+ }
215+
216+ public:
217+ // miim interface to write to the phy
218+ class miim {
219+ public:
220+ /* *
221+ * @brief Read a uint16_t from the phy using the phy address and the register address
222+ *
223+ * @param phy
224+ * @param reg
225+ * @param timeout
226+ * @return uint16_t
227+ */
228+ static uint16_t read (const uint8_t phy, const uint16_t reg, const klib::time::ms timeout = {50 }) {
229+ // write the register we want to access on the phy
230+ Emac::port->MADR = ((phy & 0x1f ) << 8 ) | (reg & 0x1f );
231+
232+ // trigger a read on the phy
233+ Emac::port->MCMD = 0x1 ;
234+
235+ // wait until the read is done or a 50 ms timeout
236+ phy_wait_not_busy (timeout);
237+
238+ // stop any read that is still active
239+ Emac::port->MCMD = 0x0 ;
240+
241+ // return the result
242+ return Emac::port->MRDD ;
243+ }
244+
245+ /* *
246+ * @brief Write a uint16_t to the phy using the phy address and the register address
247+ *
248+ * @param phy
249+ * @param reg
250+ * @param value
251+ * @param timeout
252+ */
253+ static void write (const uint8_t phy, const uint16_t reg, const uint16_t value, const klib::time::ms timeout = {50 }) {
254+ // write the register we want to access on the phy
255+ Emac::port->MADR = ((phy & 0x1f ) << 8 ) | (reg & 0x1f );
256+
257+ // write the value to the phy
258+ Emac::port->MWTD = value;
259+
260+ // wait until the read is done or a 50 ms timeout
261+ phy_wait_not_busy (timeout);
262+ }
263+ };
264+ };
265+ }
266+
267+ #endif
0 commit comments