common: Implement Mx25l::erase_all().
[gps-watch.git] / src / common / mx25l.rs
1 /*
2  * Copyright (c) 2020 Tilman Sauerbeck (tilman at code-monkey de)
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 use gpio;
25 use spi;
26 use storage::{Storage, Error};
27
28 const SECTOR_SIZE: usize = 4096;
29 const PAGE_SIZE:   usize = 256;
30
31 pub struct Mx25l {
32     cs_gpio: u32,
33     cs_gpio_pin: u32,
34 }
35
36 enum Command {
37     PP   = 0x02,
38     READ = 0x03,
39     RDSR = 0x05,
40     WREN = 0x06,
41     SE   = 0x20,
42     CE   = 0x60,
43     RDID = 0x9f,
44 }
45
46 const SR_WIP: u8 = 1 << 0;
47
48 impl Mx25l {
49     pub fn new(cs_gpio: u32, cs_gpio_pin: u32) -> Mx25l {
50         Mx25l {
51             cs_gpio: cs_gpio,
52             cs_gpio_pin: cs_gpio_pin,
53         }
54     }
55
56     pub fn read_status(&self) -> u8 {
57         self.with_selected(|| {
58             spi::tx8(spi::SPI0, Command::RDSR as u8);
59
60             spi::tx8(spi::SPI0, 0xff)
61         })
62     }
63
64     pub fn read_id(&self) -> (u8, u16) {
65         self.with_selected(|| {
66             spi::tx8(spi::SPI0, Command::RDID as u8);
67
68             let manufacturer_id = spi::tx8(spi::SPI0, 0xff);
69             let device_id0 = spi::tx8(spi::SPI0, 0xff) as u16;
70             let device_id1 = spi::tx8(spi::SPI0, 0xff) as u16;
71
72             (manufacturer_id, device_id0 | (device_id1 << 8))
73         })
74     }
75
76     pub fn erase_all(&self) {
77         self.write_enable();
78
79         self.with_selected(|| {
80             spi::tx8(spi::SPI0, Command::CE as u8);
81         });
82
83         while self.write_in_progress() {
84         }
85     }
86
87     pub fn erase_sector(&self, address: usize) -> Result<(), Error> {
88         if (address & (SECTOR_SIZE - 1)) != 0 {
89             return Err(Error::UnalignedAddress);
90         }
91
92         self.write_enable();
93
94         self.with_selected(|| {
95             spi::tx8(spi::SPI0, Command::SE as u8);
96
97             spi::tx8(spi::SPI0, (address >> 16) as u8);
98             spi::tx8(spi::SPI0, (address >>  8) as u8);
99             spi::tx8(spi::SPI0, (address >>  0) as u8);
100         });
101
102         while self.write_in_progress() {
103         }
104
105         Ok(())
106     }
107
108     pub fn program_page(&self, address: usize, buffer: &[u8; PAGE_SIZE])
109                         -> Result<(), Error> {
110         if (address & (PAGE_SIZE - 1)) != 0 {
111             return Err(Error::UnalignedAddress);
112         }
113
114         if buffer.iter().all(|&b| b == 0xff) {
115             return Ok(());
116         }
117
118         self.with_selected(|| {
119             spi::tx8(spi::SPI0, Command::PP as u8);
120
121             spi::tx8(spi::SPI0, (address >> 16) as u8);
122             spi::tx8(spi::SPI0, (address >>  8) as u8);
123             spi::tx8(spi::SPI0, (address >>  0) as u8);
124
125             for &b in buffer.iter() {
126                 spi::tx8(spi::SPI0, b);
127             }
128         });
129
130         while self.write_in_progress() {
131         }
132
133         Ok(())
134     }
135
136     fn write_enable(&self) {
137         self.with_selected(|| {
138             spi::tx8(spi::SPI0, Command::WREN as u8);
139         });
140     }
141
142     fn write_in_progress(&self) -> bool {
143         (self.read_status() & SR_WIP) != 0
144     }
145
146     fn with_selected<F, T>(&self, func: F) -> T
147         where F: FnOnce() -> T
148     {
149         gpio::clear(self.cs_gpio, self.cs_gpio_pin);
150
151         let r = func();
152
153         gpio::set(self.cs_gpio, self.cs_gpio_pin);
154
155         r
156     }
157 }
158
159 impl Storage for Mx25l {
160     fn read(&self, address: usize, buffer: &mut [u8]) {
161         self.with_selected(|| {
162             spi::tx8(spi::SPI0, Command::READ as u8);
163
164             spi::tx8(spi::SPI0, (address >> 16) as u8);
165             spi::tx8(spi::SPI0, (address >>  8) as u8);
166             spi::tx8(spi::SPI0, (address >>  0) as u8);
167
168             for i in 0..buffer.len() {
169                 buffer[i] = spi::tx8(spi::SPI0, 0xff);
170             }
171         })
172     }
173
174     fn write(&mut self, address: usize, buffer: &[u8; SECTOR_SIZE])
175              -> Result<(), Error> {
176         if let Err(e) = self.erase_sector(address) {
177             return Err(e);
178         }
179
180         for (i, page_bytes) in buffer.chunks(PAGE_SIZE).enumerate() {
181             let page_address = address + (i * PAGE_SIZE);
182
183             // XXX: Inefficient.
184             let mut ba = [0xff; PAGE_SIZE];
185             ba.copy_from_slice(&page_bytes);
186
187             if let Err(e) = self.program_page(page_address, &ba) {
188                 return Err(e);
189             }
190         }
191
192         Ok(())
193     }
194 }