Skip to content

Commit 4a1121f

Browse files
committed
Start of lpc178x emac implementation
1 parent 41230a3 commit 4a1121f

2 files changed

Lines changed: 380 additions & 0 deletions

File tree

targets/chip/lpc1788/io/emac.hpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#ifndef KLIB_LPC1788_EMAC_HPP
2+
#define KLIB_LPC1788_EMAC_HPP
3+
4+
#include <klib/io/peripheral.hpp>
5+
#include <targets/core/nxp/lpc178x/emac.hpp>
6+
#include "pins.hpp"
7+
8+
namespace klib::lpc1788::io::periph::detail::emac {
9+
enum class mode {
10+
txd0,
11+
txd1,
12+
txd2,
13+
txd3,
14+
15+
rxd0,
16+
rxd1,
17+
rxd2,
18+
rxd3,
19+
20+
crs,
21+
col,
22+
tx_en,
23+
tx_er,
24+
rx_dv,
25+
rx_er,
26+
27+
tx_clk,
28+
rx_clk,
29+
ref_clk,
30+
31+
mdc,
32+
mdio
33+
};
34+
35+
template <typename Pin, mode Type, typename Periph>
36+
struct emac {
37+
// pin of the peripheral
38+
using pin = Pin;
39+
40+
// type of the pin
41+
constexpr static mode type = Type;
42+
43+
// alternate function
44+
using periph = Periph;
45+
};
46+
}
47+
48+
namespace klib::lpc1788::io::periph::lqfp_208 {
49+
struct emac0 {
50+
// peripheral id (e.g emac0, emac1)
51+
constexpr static uint32_t id = 0;
52+
53+
// interrupt id (including the arm vector table)
54+
constexpr static uint32_t interrupt_id = (16 + 28);
55+
56+
// peripheral clock bit position
57+
constexpr static uint32_t clock_id = 30;
58+
59+
// base dma id
60+
constexpr static uint32_t dma_id = 0;
61+
62+
// port to the EMAC hardware
63+
static inline ETHERNET_Type *const port = ETHERNET;
64+
65+
struct mii {
66+
// configuration of the pins
67+
using txd0 = detail::emac::emac<pins::package::lqfp_208::p196, detail::emac::mode::txd0, core::lpc178x::io::detail::alternate::func_1>;
68+
using txd1 = detail::emac::emac<pins::package::lqfp_208::p194, detail::emac::mode::txd1, core::lpc178x::io::detail::alternate::func_1>;
69+
using txd2 = detail::emac::emac<pins::package::lqfp_208::p185, detail::emac::mode::txd2, core::lpc178x::io::detail::alternate::func_1>;
70+
using txd3 = detail::emac::emac<pins::package::lqfp_208::p177, detail::emac::mode::txd3, core::lpc178x::io::detail::alternate::func_1>;
71+
72+
using tx_en = detail::emac::emac<pins::package::lqfp_208::p192, detail::emac::mode::tx_en, core::lpc178x::io::detail::alternate::func_1>;
73+
using tx_er = detail::emac::emac<pins::package::lqfp_208::p156, detail::emac::mode::tx_er, core::lpc178x::io::detail::alternate::func_1>;
74+
using tx_clk = detail::emac::emac<pins::package::lqfp_208::p171, detail::emac::mode::tx_clk, core::lpc178x::io::detail::alternate::func_1>;
75+
76+
using col = detail::emac::emac<pins::package::lqfp_208::p153, detail::emac::mode::col, core::lpc178x::io::detail::alternate::func_1>;
77+
using crs = detail::emac::emac<pins::package::lqfp_208::p190, detail::emac::mode::crs, core::lpc178x::io::detail::alternate::func_1>;
78+
79+
using rxd0 = detail::emac::emac<pins::package::lqfp_208::p188, detail::emac::mode::rxd0, core::lpc178x::io::detail::alternate::func_1>;
80+
using rxd1 = detail::emac::emac<pins::package::lqfp_208::p186, detail::emac::mode::rxd1, core::lpc178x::io::detail::alternate::func_1>;
81+
using rxd2 = detail::emac::emac<pins::package::lqfp_208::p163, detail::emac::mode::rxd2, core::lpc178x::io::detail::alternate::func_1>;
82+
using rxd3 = detail::emac::emac<pins::package::lqfp_208::p157, detail::emac::mode::rxd3, core::lpc178x::io::detail::alternate::func_1>;
83+
84+
using rx_dv = detail::emac::emac<pins::package::lqfp_208::p147, detail::emac::mode::rx_dv, core::lpc178x::io::detail::alternate::func_1>;
85+
using rx_er = detail::emac::emac<pins::package::lqfp_208::p184, detail::emac::mode::rx_er, core::lpc178x::io::detail::alternate::func_1>;
86+
using rx_clk = detail::emac::emac<pins::package::lqfp_208::p182, detail::emac::mode::rx_clk, core::lpc178x::io::detail::alternate::func_1>;
87+
};
88+
89+
struct rmii {
90+
// configuration of the pins
91+
using txd0 = detail::emac::emac<pins::package::lqfp_208::p196, detail::emac::mode::txd0, core::lpc178x::io::detail::alternate::func_1>;
92+
using txd1 = detail::emac::emac<pins::package::lqfp_208::p194, detail::emac::mode::txd1, core::lpc178x::io::detail::alternate::func_1>;
93+
using rxd0 = detail::emac::emac<pins::package::lqfp_208::p188, detail::emac::mode::rxd0, core::lpc178x::io::detail::alternate::func_1>;
94+
using rxd1 = detail::emac::emac<pins::package::lqfp_208::p186, detail::emac::mode::rxd1, core::lpc178x::io::detail::alternate::func_1>;
95+
96+
using tx_en = detail::emac::emac<pins::package::lqfp_208::p192, detail::emac::mode::tx_en, core::lpc178x::io::detail::alternate::func_1>;
97+
using rx_er = detail::emac::emac<pins::package::lqfp_208::p184, detail::emac::mode::rx_er, core::lpc178x::io::detail::alternate::func_1>;
98+
using crs = detail::emac::emac<pins::package::lqfp_208::p190, detail::emac::mode::crs, core::lpc178x::io::detail::alternate::func_1>;
99+
using ref_clk = detail::emac::emac<pins::package::lqfp_208::p182, detail::emac::mode::ref_clk, core::lpc178x::io::detail::alternate::func_1>;
100+
};
101+
102+
// configuration for the MIIM
103+
using mdc = detail::emac::emac<pins::package::lqfp_208::p180, detail::emac::mode::mdc, core::lpc178x::io::detail::alternate::func_1>;
104+
using mdio = detail::emac::emac<pins::package::lqfp_208::p178, detail::emac::mode::mdio, core::lpc178x::io::detail::alternate::func_1>;
105+
};
106+
}
107+
108+
namespace klib::lpc1788::io {
109+
template <typename Emac, klib::io::ethernet::mii Mii>
110+
using emac = klib::core::lpc178x::io::emac<Emac, Mii>;
111+
}
112+
113+
#endif

targets/core/nxp/lpc178x/emac.hpp

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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

Comments
 (0)