]>
Commit | Line | Data |
---|---|---|
1 | ## | |
2 | ## This file is part of the sigrok project. | |
3 | ## | |
4 | ## Copyright (C) 2011 Uwe Hermann <uwe@hermann-uwe.de> | |
5 | ## | |
6 | ## This program is free software; you can redistribute it and/or modify | |
7 | ## it under the terms of the GNU General Public License as published by | |
8 | ## the Free Software Foundation; either version 2 of the License, or | |
9 | ## (at your option) any later version. | |
10 | ## | |
11 | ## This program is distributed in the hope that it will be useful, | |
12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ## GNU General Public License for more details. | |
15 | ## | |
16 | ## You should have received a copy of the GNU General Public License | |
17 | ## along with this program; if not, write to the Free Software | |
18 | ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ## | |
20 | ||
21 | # | |
22 | # Macronix MX25Lxx05D SPI (NOR) flash chip decoder. | |
23 | # Works for MX25L1605D/MX25L3205D/MX25L6405D. | |
24 | # | |
25 | ||
26 | # | |
27 | # TODO: Description | |
28 | # | |
29 | # Details: | |
30 | # http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf | |
31 | # | |
32 | ||
33 | import sigrokdecode | |
34 | ||
35 | # States | |
36 | IDLE = -1 | |
37 | ||
38 | # Chip commands (also used as additional decoder states). | |
39 | CMD_WREN = 0x06 | |
40 | CMD_WRDI = 0x04 | |
41 | CMD_RDID = 0x9f | |
42 | CMD_RDSR = 0x05 | |
43 | CMD_WRSR = 0x01 | |
44 | CMD_READ = 0x03 | |
45 | CMD_FAST_READ = 0x0b | |
46 | CMD_2READ = 0xbb | |
47 | CMD_SE = 0x20 | |
48 | CMD_BE = 0xd8 | |
49 | CMD_CE = 0x60 | |
50 | CMD_CE2 = 0xc7 | |
51 | CMD_PP = 0x02 | |
52 | CMD_CP = 0xad | |
53 | CMD_DP = 0xb9 | |
54 | # CMD_RDP = 0xab | |
55 | # CMD_RES = 0xab | |
56 | CMD_RDP_RES = 0xab # Note: RDP/RES have the same ID. | |
57 | CMD_REMS = 0x90 | |
58 | CMD_REMS2 = 0xef | |
59 | CMD_ENSO = 0xb1 | |
60 | CMD_EXSO = 0xc1 | |
61 | CMD_RDSCUR = 0x2b | |
62 | CMD_WRSCUR = 0x2f | |
63 | CMD_ESRY = 0x70 | |
64 | CMD_DSRY = 0x80 | |
65 | ||
66 | # TODO: (Short) command names as strings in a dict, too? | |
67 | ||
68 | # Dict which maps command IDs to their description. | |
69 | cmds = { | |
70 | CMD_WREN: 'Write enable', | |
71 | CMD_WRDI: 'Write disable', | |
72 | CMD_RDID: 'Read identification', | |
73 | CMD_RDSR: 'Read status register', | |
74 | CMD_WRSR: 'Write status register', | |
75 | CMD_READ: 'Read data', | |
76 | CMD_FAST_READ: 'Fast read data', | |
77 | CMD_2READ: '2x I/O read', | |
78 | CMD_SE: 'Sector erase', | |
79 | CMD_BE: 'Block erase', | |
80 | CMD_CE: 'Chip erase', | |
81 | CMD_CE2: 'Chip erase', # Alternative command ID | |
82 | CMD_PP: 'Page program', | |
83 | CMD_CP: 'Continuously program mode', | |
84 | CMD_DP: 'Deep power down', | |
85 | # CMD_RDP: 'Release from deep powerdown', | |
86 | # CMD_RES: 'Read electronic ID', | |
87 | CMD_RDP_RES: 'Release from deep powerdown / Read electronic ID', | |
88 | CMD_REMS: 'Read electronic manufacturer & device ID', | |
89 | CMD_REMS2: 'Read ID for 2x I/O mode', | |
90 | CMD_ENSO: 'Enter secured OTP', | |
91 | CMD_EXSO: 'Exit secured OTP', | |
92 | CMD_RDSCUR: 'Read security register', | |
93 | CMD_WRSCUR: 'Write security register', | |
94 | CMD_ESRY: 'Enable SO to output RY/BY#', | |
95 | CMD_DSRY: 'Disable SO to output RY/BY#', | |
96 | } | |
97 | ||
98 | device_name = { | |
99 | 0x14: 'MX25L1605D', | |
100 | 0x15: 'MX25L3205D', | |
101 | 0x16: 'MX25L6405D', | |
102 | } | |
103 | ||
104 | # FIXME: This is just some example input for testing purposes... | |
105 | ||
106 | mosi_packets = [ | |
107 | # REMS | |
108 | {'type': 'D', 'range': (100, 110), 'data': 0x90, 'ann': ''}, | |
109 | {'type': 'D', 'range': (120, 130), 'data': 0xff, 'ann': ''}, | |
110 | {'type': 'D', 'range': (170, 180), 'data': 0xff, 'ann': ''}, | |
111 | {'type': 'D', 'range': (190, 200), 'data': 0x00, 'ann': ''}, | |
112 | {'type': 'D', 'range': (400, 410), 'data': 0xff, 'ann': ''}, | |
113 | {'type': 'D', 'range': (411, 421), 'data': 0xff, 'ann': ''}, | |
114 | # RDID | |
115 | {'type': 'D', 'range': (10, 11), 'data': 0x9f, 'ann': ''}, | |
116 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
117 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
118 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
119 | # SE | |
120 | {'type': 'D', 'range': (10, 11), 'data': 0x20, 'ann': ''}, | |
121 | {'type': 'D', 'range': (10, 11), 'data': 0x12, 'ann': ''}, | |
122 | {'type': 'D', 'range': (10, 11), 'data': 0x34, 'ann': ''}, | |
123 | {'type': 'D', 'range': (10, 11), 'data': 0x56, 'ann': ''}, | |
124 | # SE | |
125 | {'type': 'D', 'range': (10, 11), 'data': 0x20, 'ann': ''}, | |
126 | {'type': 'D', 'range': (10, 11), 'data': 0x44, 'ann': ''}, | |
127 | {'type': 'D', 'range': (10, 11), 'data': 0x55, 'ann': ''}, | |
128 | {'type': 'D', 'range': (10, 11), 'data': 0x66, 'ann': ''}, | |
129 | # WREN | |
130 | {'type': 'D', 'range': (10, 11), 'data': 0x06, 'ann': ''}, | |
131 | ] | |
132 | ||
133 | miso_packets = [ | |
134 | # REMS | |
135 | {'type': 'D', 'range': (100, 110), 'data': 0xff, 'ann': ''}, | |
136 | {'type': 'D', 'range': (120, 130), 'data': 0xff, 'ann': ''}, | |
137 | {'type': 'D', 'range': (170, 180), 'data': 0xff, 'ann': ''}, | |
138 | {'type': 'D', 'range': (190, 200), 'data': 0xff, 'ann': ''}, | |
139 | {'type': 'D', 'range': (400, 410), 'data': 0xc2, 'ann': ''}, | |
140 | {'type': 'D', 'range': (411, 421), 'data': 0x14, 'ann': ''}, | |
141 | # RDID | |
142 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
143 | {'type': 'D', 'range': (10, 11), 'data': 0xc2, 'ann': ''}, | |
144 | {'type': 'D', 'range': (10, 11), 'data': 0x20, 'ann': ''}, | |
145 | {'type': 'D', 'range': (10, 11), 'data': 0x15, 'ann': ''}, | |
146 | # SE | |
147 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
148 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
149 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
150 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
151 | # SE | |
152 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
153 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
154 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
155 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
156 | # WREN | |
157 | {'type': 'D', 'range': (10, 11), 'data': 0xff, 'ann': ''}, | |
158 | ] | |
159 | ||
160 | class Decoder(sigrokdecode.Decoder): | |
161 | id = 'mx25lxx05d' | |
162 | name = 'Macronix MX25Lxx05D' | |
163 | longname = 'Macronix MX25Lxx05D SPI flash chip decoder' | |
164 | desc = 'Macronix MX25Lxx05D SPI flash chip decoder' | |
165 | longdesc = 'TODO' | |
166 | author = 'Uwe Hermann' | |
167 | email = 'uwe@hermann-uwe.de' | |
168 | license = 'gplv2+' | |
169 | inputs = ['spi', 'spi', 'logic'] | |
170 | outputs = ['mx25lxx05d'] | |
171 | probes = [] # TODO: HOLD#, WP#/ACC | |
172 | options = {} # TODO | |
173 | ||
174 | def __init__(self, **kwargs): | |
175 | self.output_protocol = None | |
176 | self.output_annotation = None | |
177 | self.state = IDLE | |
178 | self.cmdstate = 1 # TODO | |
179 | self.out = [] | |
180 | ||
181 | def start(self, metadata): | |
182 | # self.output_protocol = self.output_new(2) | |
183 | self.output_annotation = self.output_new(1) | |
184 | ||
185 | def report(self): | |
186 | pass | |
187 | ||
188 | def handle_wren(self, miso_packet, mosi_packet): | |
189 | self.out += [{'type': self.cmd, 'range': mosi_packet['range'], | |
190 | 'data': None, 'ann': cmds[self.state]}] | |
191 | self.state = IDLE | |
192 | ||
193 | # TODO: Check/display device ID / name | |
194 | def handle_rdid(self, miso_packet, mosi_packet): | |
195 | ## self.state = IDLE | |
196 | ## return # FIXME | |
197 | ||
198 | if self.cmdstate == 1: | |
199 | # Byte 1: Master sends command ID. | |
200 | self.start_sample = mosi_packet['range'][0] | |
201 | o = [] # TODO | |
202 | elif self.cmdstate == 2: | |
203 | # Byte 2: Slave sends the JEDEC manufacturer ID. | |
204 | o = [{'type': self.cmd, 'range': miso_packet['range'], | |
205 | 'data': miso_packet['data'], 'ann': 'Manufacturer ID'}] | |
206 | elif self.cmdstate == 3: | |
207 | # Byte 3: Slave sends the memory type (0x20 for this chip). | |
208 | o = [{'type': self.cmd, 'range': miso_packet['range'], | |
209 | 'data': miso_packet['data'], 'ann': 'Memory type'}] | |
210 | elif self.cmdstate == 4: | |
211 | # Byte 4: Slave sends the device ID. | |
212 | self.device_id = miso_packet['data'] | |
213 | o = [{'type': self.cmd, 'range': miso_packet['range'], | |
214 | 'data': miso_packet['data'], 'ann': 'Device ID'}] | |
215 | ||
216 | if self.cmdstate == 4: | |
217 | # TODO: Check self.device_id is valid & exists in device_names. | |
218 | # TODO: Same device ID? Check! | |
219 | dev = 'Device: Macronix %s' % device_name[self.device_id] | |
220 | o += [{'type': 'RDID', # TODO: self.cmd? | |
221 | 'range': (self.start_sample, miso_packet['range'][1]), | |
222 | 'data': None, # TODO? | |
223 | 'ann': dev}] | |
224 | self.state = IDLE | |
225 | else: | |
226 | self.cmdstate += 1 | |
227 | ||
228 | self.out += o | |
229 | ||
230 | # TODO: Warn/abort if we don't see the necessary amount of bytes. | |
231 | # TODO: Warn if WREN was not seen before. | |
232 | def handle_se(self, miso_packet, mosi_packet): | |
233 | if self.cmdstate == 1: | |
234 | # Byte 1: Master sends command ID. | |
235 | self.addr = 0 | |
236 | self.start_sample = mosi_packet['range'][0] | |
237 | o = [{'type': self.cmd, 'range': mosi_packet['range'], | |
238 | 'data': self.cmd, 'ann': 'Command ID'}] | |
239 | elif self.cmdstate in (2, 3, 4): | |
240 | # Bytes 2/3/4: Master sends address of the sector to erase. | |
241 | # Note: Assumes SPI data is 8 bits wide (it is for MX25Lxx05D). | |
242 | # TODO: LSB-first of MSB-first? | |
243 | self.addr <<= 8 | |
244 | self.addr |= mosi_packet['data'] | |
245 | o = [] # TODO: Output 'Address byte 1' and such fields? | |
246 | ||
247 | if self.cmdstate == 4: | |
248 | o += [{'type': self.cmd, | |
249 | 'range': (self.start_sample, mosi_packet['range'][1]), | |
250 | 'data': '0x%x' % self.addr, 'ann': cmds[self.state]}] | |
251 | # TODO: Max. size depends on chip, check that too if possible. | |
252 | if self.addr % 4096 != 0: | |
253 | # Sector addresses must be 4K-aligned (same for all 3 chips). | |
254 | o += [{'type': self.cmd, # TODO: Type == 'Warning' or such? | |
255 | 'range': (self.start_sample, mosi_packet['range'][1]), | |
256 | 'data': None, 'ann': 'Warning: Invalid sector address!'}] | |
257 | self.state = IDLE | |
258 | else: | |
259 | self.cmdstate += 1 | |
260 | ||
261 | self.out += o | |
262 | ||
263 | def handle_rems(self, miso_packet, mosi_packet): | |
264 | if self.cmdstate == 1: | |
265 | # Byte 1: Master sends command ID. | |
266 | self.start_sample = mosi_packet['range'][0] | |
267 | o = [{'type': self.cmd, 'range': mosi_packet['range'], | |
268 | 'data': self.cmd, 'ann': 'Command ID'}] | |
269 | elif self.cmdstate in (2, 3): | |
270 | # Bytes 2/3: Master sends two dummy bytes. | |
271 | # TODO: Check dummy bytes? Check reply from device? | |
272 | o = [{'type': self.cmd, 'range': mosi_packet['range'], | |
273 | 'data': mosi_packet['data'], 'ann': 'Dummy byte'}] | |
274 | elif self.cmdstate == 4: | |
275 | # Byte 4: Master sends 0x00 or 0x01. | |
276 | # 0x00: Master wants manufacturer ID as first reply byte. | |
277 | # 0x01: Master wants device ID as first reply byte. | |
278 | b = mosi_packet['data'] | |
279 | self.manufacturer_id_first = True if (b == 0x00) else False | |
280 | d = 'manufacturer' if (b == 0x00) else 'device' | |
281 | o = [{'type': self.cmd, 'range': mosi_packet['range'], | |
282 | 'data': b, 'ann': '%s (%s ID first)' % (cmds[self.cmd], d)}] | |
283 | elif self.cmdstate == 5: | |
284 | # Byte 5: Slave sends manufacturer ID (or device ID). | |
285 | self.ids = [miso_packet['data']] | |
286 | o = [] | |
287 | elif self.cmdstate in (5, 6): | |
288 | # Byte 6: Slave sends device ID (or manufacturer ID). | |
289 | self.ids += [miso_packet['data']] | |
290 | ann = 'Manufacturer' if self.manufacturer_id_first else 'Device' | |
291 | o = [{'type': self.cmd, 'range': miso_packet['range'], | |
292 | 'data': '0x%02x' % self.ids[0], 'ann': ann}] | |
293 | ann = 'Device' if self.manufacturer_id_first else 'Manufacturer' | |
294 | o += [{'type': self.cmd, 'range': miso_packet['range'], | |
295 | 'data': '0x%02x' % self.ids[1], 'ann': '%s ID' % ann}] | |
296 | else: | |
297 | # TODO: Error? | |
298 | pass | |
299 | ||
300 | if self.cmdstate == 6: | |
301 | self.end_sample = miso_packet['range'][1] | |
302 | id = self.ids[1] if self.manufacturer_id_first else self.ids[0] | |
303 | dev = 'Device: Macronix %s' % device_name[id] | |
304 | o += [{'type': self.cmd, | |
305 | 'range': (self.start_sample, self.end_sample), | |
306 | 'data': None, # TODO: Both IDs? Which format? | |
307 | 'ann': dev}] | |
308 | self.state = IDLE | |
309 | else: | |
310 | self.cmdstate += 1 | |
311 | ||
312 | self.out += o | |
313 | ||
314 | def decode(self, timeoffset, duration, data): | |
315 | self.out = [] | |
316 | ||
317 | # Iterate over all SPI MISO/MOSI packets. TODO: HOLD#, WP#/ACC? | |
318 | for i in range(len(miso_packets)): | |
319 | ||
320 | p_miso = miso_packets[i] | |
321 | p_mosi = mosi_packets[i] | |
322 | ||
323 | # Assumption: Every p_miso has a p_mosi entry with same range. | |
324 | ||
325 | # For now, skip non-data packets. | |
326 | if p_mosi['type'] != 'D': | |
327 | continue | |
328 | ||
329 | cmd = p_mosi['data'] | |
330 | ||
331 | # If we encountered a known chip command, enter the resp. state. | |
332 | if self.state == IDLE: | |
333 | if cmd in cmds: | |
334 | self.state = cmd | |
335 | self.cmd = cmd # TODO: Eliminate? | |
336 | self.cmdstate = 1 | |
337 | else: | |
338 | pass # TODO | |
339 | ||
340 | # Handle commands. | |
341 | # TODO: Use some generic way to invoke the resp. method. | |
342 | if self.state == CMD_WREN: | |
343 | self.handle_wren(p_miso, p_mosi) | |
344 | elif self.state == CMD_SE: | |
345 | self.handle_se(p_miso, p_mosi) | |
346 | elif self.state == CMD_RDID: | |
347 | self.handle_rdid(p_miso, p_mosi) | |
348 | if self.state == CMD_REMS: | |
349 | self.handle_rems(p_miso, p_mosi) | |
350 | else: | |
351 | pass | |
352 | ||
353 | if self.out != []: | |
354 | # self.put(0, 0, self.output_protocol, out_proto) | |
355 | self.put(0, 0, self.output_annotation, self.out) | |
356 |