1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
use embedded_hal::{blocking::spi::Transfer, digital::v2::OutputPin};

pub mod interfaces {
    use embedded_hal::spi;
    /// Must use SPI mode cpol=0, cpha=0
    pub const SPI_MODE: spi::Mode = spi::Mode {
        polarity: spi::Polarity::IdleLow,
        phase: spi::Phase::CaptureOnFirstTransition,
    };
    /// Max freq = 14 MHz
    pub const SPI_CLOCK_FREQ: u32 = 14_000_000;
}

pub mod opcodes {
    /// 1-byte Instructions
    pub const SETETHRST: u8 = 0b1100_1010;
    pub const SETPKTDEC: u8 = 0b1100_1100;
    pub const SETTXRTS: u8 = 0b1101_0100;
    pub const ENABLERX: u8 = 0b1110_1000;
    /// 3-byte Instructions
    pub const WRXRDPT: u8 = 0b0110_0100; // 8-bit opcode followed by data
    pub const RRXRDPT: u8 = 0b0110_0110; // 8-bit opcode followed by data
    pub const WGPWRPT: u8 = 0b0110_1100; // 8-bit opcode followed by data
    pub const RGPWRPT: u8 = 0b0110_1110; // 8-bit opcode followed by data
    /// N-byte Instructions
    pub const RCRU: u8 = 0b0010_0000;
    pub const WCRU: u8 = 0b0010_0010;
    pub const RRXDATA: u8 = 0b0010_1100; // 8-bit opcode followed by data
    pub const WGPDATA: u8 = 0b0010_1010; // 8-bit opcode followed by data
}

pub mod addrs {
    /// SPI Register Mapping
    /// Note: PSP interface use different address mapping
    // SPI Init Reset Registers
    pub const EUDAST: u8 = 0x16; // 16-bit data
    pub const ESTAT: u8 = 0x1a; // 16-bit data
    pub const ECON2: u8 = 0x6e; // 16-bit data
                                //
    pub const ERXFCON: u8 = 0x34; // 16-bit data
                                  //
    pub const MAADR3: u8 = 0x60; // 16-bit data
    pub const MAADR2: u8 = 0x62; // 16-bit data
    pub const MAADR1: u8 = 0x64; // 16-bit data
                                 // RX Registers
    pub const ERXRDPT: u8 = 0x8a; // 16-bit data
    pub const ERXST: u8 = 0x04; // 16-bit data
    pub const ERXTAIL: u8 = 0x06; // 16-bit data
    pub const EIR: u8 = 0x1c; // 16-bit data
    pub const ECON1: u8 = 0x1e; // 16-bit data
    pub const MAMXFL: u8 = 0x4a; // 16-bit data
                                 // TX Registers
    pub const EGPWRPT: u8 = 0x88; // 16-bit data
    pub const ETXST: u8 = 0x00; // 16-bit data
    pub const ETXSTAT: u8 = 0x12; // 16-bit data
    pub const ETXLEN: u8 = 0x02; // 16-bit data
}

/// Struct for SPI I/O interface on ENC424J600
/// Note: stm32f4xx_hal::spi's pins include: SCK, MISO, MOSI
pub struct SpiPort<SPI: Transfer<u8>, NSS: OutputPin> {
    spi: SPI,
    nss: NSS,
    #[cfg(feature = "cortex-m-cpu")]
    cpu_freq_mhz: f32,
}

pub enum Error {
    OpcodeError,
    TransferError,
}

#[allow(unused_must_use)]
impl<SPI: Transfer<u8>, NSS: OutputPin> SpiPort<SPI, NSS> {
    // TODO: return as Result()
    pub fn new(spi: SPI, mut nss: NSS) -> Self {
        nss.set_high();

        SpiPort {
            spi,
            nss,
            #[cfg(feature = "cortex-m-cpu")]
            cpu_freq_mhz: 0.,
        }
    }

    #[cfg(feature = "cortex-m-cpu")]
    pub fn cpu_freq_mhz(mut self, freq: u32) -> Self {
        self.cpu_freq_mhz = freq as f32;
        self
    }

    pub fn read_reg_8b(&mut self, addr: u8) -> Result<u8, Error> {
        // Using RCRU instruction to read using unbanked (full) address
        let mut buf: [u8; 4] = [0; 4];
        buf[1] = addr;
        self.rw_n(&mut buf, opcodes::RCRU, 2)?;
        Ok(buf[2])
    }

    pub fn read_reg_16b(&mut self, lo_addr: u8) -> Result<u16, Error> {
        // Unless the register can be written with specific opcode,
        // use WCRU instruction to write using unbanked (full) address
        let mut buf: [u8; 4] = [0; 4];
        let mut data_offset = 0; // number of bytes separating
                                 // actual data from opcode
        match lo_addr {
            addrs::ERXRDPT | addrs::EGPWRPT => {}
            _ => {
                buf[1] = lo_addr;
                data_offset = 1;
            }
        }
        self.rw_n(
            &mut buf,
            match lo_addr {
                addrs::ERXRDPT => opcodes::RRXRDPT,
                addrs::EGPWRPT => opcodes::RGPWRPT,
                _ => opcodes::RCRU,
            },
            2 + data_offset, // extra 8-bit lo_addr before data
        )?;
        Ok(buf[data_offset + 1] as u16 | (buf[data_offset + 2] as u16) << 8)
    }

    // Currently requires manual slicing (buf[1..]) for the data read back
    pub fn read_rxdat<'a>(&mut self, buf: &'a mut [u8], data_length: usize) -> Result<(), Error> {
        self.rw_n(buf, opcodes::RRXDATA, data_length)
    }

    // Currently requires actual data to be stored in buf[1..] instead of buf[0..]
    // TODO: Maybe better naming?
    pub fn write_txdat<'a>(&mut self, buf: &'a mut [u8], data_length: usize) -> Result<(), Error> {
        self.rw_n(buf, opcodes::WGPDATA, data_length)
    }

    pub fn write_reg_8b(&mut self, addr: u8, data: u8) -> Result<(), Error> {
        // Using WCRU instruction to write using unbanked (full) address
        let mut buf: [u8; 3] = [0; 3];
        buf[1] = addr;
        buf[2] = data;
        self.rw_n(&mut buf, opcodes::WCRU, 2)
    }

    pub fn write_reg_16b(&mut self, lo_addr: u8, data: u16) -> Result<(), Error> {
        // Unless the register can be written with specific opcode,
        // use WCRU instruction to write using unbanked (full) address
        let mut buf: [u8; 4] = [0; 4];
        let mut data_offset = 0; // number of bytes separating
                                 // actual data from opcode
        match lo_addr {
            addrs::ERXRDPT | addrs::EGPWRPT => {}
            _ => {
                buf[1] = lo_addr;
                data_offset = 1;
            }
        }
        buf[1 + data_offset] = data as u8;
        buf[2 + data_offset] = (data >> 8) as u8;
        self.rw_n(
            &mut buf,
            match lo_addr {
                addrs::ERXRDPT => opcodes::WRXRDPT,
                addrs::EGPWRPT => opcodes::WGPWRPT,
                _ => opcodes::WCRU,
            },
            2 + data_offset, // extra 8-bit lo_addr before data
        )
    }

    pub fn send_opcode(&mut self, opcode: u8) -> Result<(), Error> {
        match opcode {
            opcodes::SETETHRST | opcodes::SETPKTDEC | opcodes::SETTXRTS | opcodes::ENABLERX => {
                let mut buf: [u8; 1] = [0];
                self.rw_n(&mut buf, opcode, 0)
            }
            _ => Err(Error::OpcodeError),
        }
    }

    // TODO: Actual data should start from buf[0], not buf[1]
    // Completes an SPI transfer for reading data to the given buffer,
    // or writing data from the buffer.
    // It sends an 8-bit instruction, followed by either
    // receiving or sending n*8-bit data.
    // The slice of buffer provided must begin with the 8-bit instruction.
    // If n = 0, the transfer will only involve sending the instruction.
    fn rw_n<'a>(&mut self, buf: &'a mut [u8], opcode: u8, data_length: usize) -> Result<(), Error> {
        assert!(buf.len() > data_length);
        // Enable chip select
        self.nss.set_low();
        // >=50ns min. CS_n setup time
        #[cfg(feature = "cortex-m-cpu")]
        cortex_m::asm::delay((0.05 * (self.cpu_freq_mhz + 1.)) as u32);
        // Start writing to SLAVE
        buf[0] = opcode;
        let result = self.spi.transfer(&mut buf[..data_length + 1]);
        match opcode {
            opcodes::RCRU | opcodes::WCRU | opcodes::RRXDATA | opcodes::WGPDATA => {
                // Disable chip select
                // >=50ns min. CS_n hold time
                #[cfg(feature = "cortex-m-cpu")]
                cortex_m::asm::delay((0.05 * (self.cpu_freq_mhz + 1.)) as u32);
                self.nss.set_high();
                // >=20ns min. CS_n disable time
                #[cfg(feature = "cortex-m-cpu")]
                cortex_m::asm::delay((0.02 * (self.cpu_freq_mhz + 1.)) as u32);
            }
            _ => {}
        }
        match result {
            Ok(_) => Ok(()),
            // TODO: Maybe too naive?
            Err(_) => Err(Error::TransferError),
        }
    }
}