opentitanlib/io/
eeprom.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8use super::spi::{SpiError, Target, Transfer};
9
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
11/// Declarations of if and when to switch from single-lane SPI to a faster mode.
12pub enum Switch {
13    Mode111,
14    Mode11N,
15    Mode1NN,
16    ModeNNN,
17}
18
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
20pub enum DataWidth {
21    Single, // Standard SPI
22    Dual,   // Use both COPI and CIPO for data
23    Quad,
24    Octo,
25}
26
27#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
28pub struct Cmd {
29    data: [u8; 8],
30    opcode_len: u8,
31    addr_len: u8,
32    mode: Mode,
33}
34
35#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
36pub enum AddressMode {
37    #[default]
38    Mode3b = 3,
39    Mode4b = 4,
40}
41
42#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
43pub struct Mode {
44    /// The number of no-operation clock cycles between address and data phases.
45    pub dummy_cycles: u8,
46    /// Declarations of if and when to switch from single-lane SPI to a faster mode as declared by
47    /// `width` and `double_transfer_rate`.
48    pub switch: Switch,
49    /// How many lanes to use after the switch (1, 2, 4, or 8).
50    pub width: DataWidth,
51    /// Whether to shift data on both rising and falling clock edges after the switch.
52    pub double_transfer_rate: bool,
53}
54
55impl Mode {
56    /// One-byte opcode, without address
57    pub fn cmd(&self, opcode: u8) -> Cmd {
58        let mut result = Cmd {
59            data: [0u8; 8],
60            opcode_len: 1,
61            addr_len: 0,
62            mode: *self,
63        };
64        result.data[0] = opcode;
65        result
66    }
67    /// One-byte opcode, with address
68    pub fn cmd_addr(&self, opcode: u8, addr: u32, addr_mode: AddressMode) -> Cmd {
69        let mut result = Cmd {
70            data: [0u8; 8],
71            opcode_len: 1,
72            addr_len: addr_mode as u8,
73            mode: *self,
74        };
75        result.data[0] = opcode;
76        result.data[1..1 + result.addr_len as usize]
77            .clone_from_slice(&addr.to_be_bytes()[4 - result.addr_len as usize..]);
78        result
79    }
80    /// Two-byte opcode, without address
81    pub fn cmd2(&self, opcode1: u8, opcode2: u8) -> Cmd {
82        let mut result = Cmd {
83            data: [0u8; 8],
84            opcode_len: 2,
85            addr_len: 0,
86            mode: *self,
87        };
88        result.data[0] = opcode1;
89        result.data[1] = opcode2;
90        result
91    }
92    /// Two-byte opcode, with address
93    pub fn cmd2_addr(&self, opcode1: u8, opcode2: u8, addr: u32, addr_mode: AddressMode) -> Cmd {
94        let mut result = Cmd {
95            data: [0u8; 8],
96            opcode_len: 2,
97            addr_len: addr_mode as u8,
98            mode: *self,
99        };
100        result.data[0] = opcode1;
101        result.data[1] = opcode2;
102        result.data[2..2 + result.addr_len as usize]
103            .clone_from_slice(&addr.to_be_bytes()[4 - result.addr_len as usize..]);
104        result
105    }
106
107    pub fn dummy_cycles(&self, dummy_cycles: u8) -> Mode {
108        Mode {
109            dummy_cycles,
110            switch: self.switch,
111            width: self.width,
112            double_transfer_rate: self.double_transfer_rate,
113        }
114    }
115}
116
117/// Single-wire
118pub const MODE_111: Mode = Mode {
119    dummy_cycles: 0,
120    switch: Switch::Mode111,
121    width: DataWidth::Single,
122    double_transfer_rate: false,
123};
124
125/// Double transfer rate on data phase
126pub const MODE_1S1S1D: Mode = Mode {
127    dummy_cycles: 0,
128    switch: Switch::Mode11N,
129    width: DataWidth::Single,
130    double_transfer_rate: true,
131};
132
133/// Double transfer rate on address and data phase
134pub const MODE_1S1D1D: Mode = Mode {
135    dummy_cycles: 0,
136    switch: Switch::Mode1NN,
137    width: DataWidth::Single,
138    double_transfer_rate: true,
139};
140
141/// Single-wire address, dual-wire data
142pub const MODE_112: Mode = Mode {
143    dummy_cycles: 0,
144    switch: Switch::Mode11N,
145    width: DataWidth::Dual,
146    double_transfer_rate: false,
147};
148
149pub const MODE_122: Mode = Mode {
150    dummy_cycles: 0,
151    switch: Switch::Mode1NN,
152    width: DataWidth::Dual,
153    double_transfer_rate: false,
154};
155
156pub const MODE_222: Mode = Mode {
157    dummy_cycles: 0,
158    switch: Switch::ModeNNN,
159    width: DataWidth::Dual,
160    double_transfer_rate: false,
161};
162
163/// Single-wire address, quad-wire data
164pub const MODE_114: Mode = Mode {
165    dummy_cycles: 0,
166    switch: Switch::Mode11N,
167    width: DataWidth::Quad,
168    double_transfer_rate: false,
169};
170
171pub const MODE_144: Mode = Mode {
172    dummy_cycles: 0,
173    switch: Switch::Mode1NN,
174    width: DataWidth::Quad,
175    double_transfer_rate: false,
176};
177
178pub const MODE_444: Mode = Mode {
179    dummy_cycles: 0,
180    switch: Switch::ModeNNN,
181    width: DataWidth::Quad,
182    double_transfer_rate: false,
183};
184
185impl Cmd {
186    /// Method use to get binary representation of the command for use on "plain" SPI.  Will be
187    /// used in cases where the transport backend does not have specialied EEPROM/Flash
188    /// communication primitives.
189    pub fn to_bytes(&self) -> Result<&[u8]> {
190        if self.mode.switch == Switch::Mode111 && self.mode.dummy_cycles.is_multiple_of(8) {
191            Ok(&self.data
192                [0..(self.opcode_len + self.addr_len + self.mode.dummy_cycles / 8) as usize])
193        } else {
194            Err(SpiError::InvalidOption(
195                "This target does not support the requested mode".to_string(),
196            )
197            .into())
198        }
199    }
200
201    pub fn get_opcode_len(&self) -> u8 {
202        self.opcode_len
203    }
204
205    pub fn get_opcode(&self) -> &[u8] {
206        &self.data[..self.opcode_len as usize]
207    }
208
209    pub fn get_address_len(&self) -> u8 {
210        self.addr_len
211    }
212
213    pub fn get_address(&self) -> u32 {
214        let mut addr_bytes = [0u8; 4];
215        addr_bytes[4 - self.addr_len as usize..].clone_from_slice(
216            &self.data[self.opcode_len as usize..(self.opcode_len + self.addr_len) as usize],
217        );
218        u32::from_be_bytes(addr_bytes)
219    }
220
221    pub fn get_dummy_cycles(&self) -> u8 {
222        self.mode.dummy_cycles
223    }
224
225    pub fn get_switch(&self) -> Switch {
226        self.mode.switch
227    }
228
229    pub fn get_width(&self) -> DataWidth {
230        self.mode.width
231    }
232
233    pub fn get_double_transfer_rate(&self) -> bool {
234        self.mode.double_transfer_rate
235    }
236}
237
238pub enum Transaction<'rd, 'wr> {
239    Command(Cmd),
240    Read(Cmd, &'rd mut [u8]),
241    Write(Cmd, &'wr [u8]),
242    WaitForBusyClear,
243}
244
245pub const READ_STATUS: u8 = 0x05;
246pub const STATUS_WIP: u8 = 0x01;
247
248pub fn default_run_eeprom_transactions<T: Target + ?Sized>(
249    spi: &T,
250    transactions: &mut [Transaction],
251) -> Result<()> {
252    // Default implementation translates into generic SPI read/write, which works as long as
253    // the transport supports generic SPI transfers of sufficint length, and that the mode is
254    // single-data-wire.
255    for transfer in transactions {
256        match transfer {
257            Transaction::Command(cmd) => {
258                spi.run_transaction(&mut [Transfer::Write(cmd.to_bytes()?)])?
259            }
260            Transaction::Read(cmd, rbuf) => {
261                spi.run_transaction(&mut [Transfer::Write(cmd.to_bytes()?), Transfer::Read(rbuf)])?
262            }
263            Transaction::Write(cmd, wbuf) => {
264                spi.run_transaction(&mut [Transfer::Write(cmd.to_bytes()?), Transfer::Write(wbuf)])?
265            }
266            Transaction::WaitForBusyClear => {
267                let mut status = STATUS_WIP;
268                while status & STATUS_WIP != 0 {
269                    spi.run_transaction(&mut [
270                        Transfer::Write(&[READ_STATUS]),
271                        Transfer::Read(std::slice::from_mut(&mut status)),
272                    ])?;
273                }
274            }
275        }
276    }
277    Ok(())
278}