2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2019-2021 Benjamin Vernoux <bvernoux@gmail.com>
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.
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.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
19 ## v0.1 - 17 September 2019 B.VERNOUX
20 ### Use ST25R3916 Datasheet DS12484 Rev 1 (January 2019)
21 ## v0.2 - 28 April 2020 B.VERNOUX
22 ### Use ST25R3916 Datasheet DS12484 Rev 2 (December 2019)
23 ## v0.3 - 17 June 2020 B.VERNOUX
24 ### Use ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020)
25 ## v0.4 - 10 Aug 2021 B.VERNOUX
26 ### Fix FIFOR/FIFOW issues with Pulseview (with "Tabular Output View")
27 ### because of FIFO Read/FIFO Write commands, was not returning the
28 ### annotations short name FIFOR/FIFOW
30 import sigrokdecode as srd
31 from collections import namedtuple
32 from common.srdhelper import SrdIntEnum
35 Ann = SrdIntEnum.from_str('Ann', 'BURST_READ BURST_WRITE \
36 BURST_READB BURST_WRITEB BURST_READT BURST_WRITET \
37 DIRECTCMD FIFO_WRITE FIFO_READ STATUS WARN')
39 Pos = namedtuple('Pos', ['ss', 'es'])
40 Data = namedtuple('Data', ['mosi', 'miso'])
42 class Decoder(srd.Decoder):
45 name = 'ST25R39xx (SPI mode)'
46 longname = 'STMicroelectronics ST25R39xx'
47 desc = 'High performance NFC universal device and EMVCo reader protocol.'
51 tags = ['IC', 'Wireless/RF']
53 ('Read', 'Burst register read'),
54 ('Write', 'Burst register write'),
55 ('ReadB', 'Burst register SpaceB read'),
56 ('WriteB', 'Burst register SpaceB write'),
57 ('ReadT', 'Burst register Test read'),
58 ('WriteT', 'Burst register Test write'),
59 ('Cmd', 'Direct command'),
60 ('FIFOW', 'FIFO write'),
61 ('FIFOR', 'FIFO read'),
62 ('status_reg', 'Status register'),
63 ('warning', 'Warning'),
66 ('regs', 'Regs', (Ann.prefixes('BURST_'))),
67 ('cmds', 'Commands', (Ann.DIRECTCMD,)),
68 ('data', 'Data', (Ann.prefixes('FIFO_'))),
69 ('status', 'Status register', (Ann.STATUS,)),
70 ('warnings', 'Warnings', (Ann.WARN,)),
78 self.requirements_met = True
79 self.cs_was_released = False
82 self.out_ann = self.register(srd.OUTPUT_ANN)
84 def warn(self, pos, msg):
85 '''Put a warning message 'msg' at 'pos'.'''
86 self.put(pos.ss, pos.es, self.out_ann, [Ann.WARN, [msg]])
88 def putp(self, pos, ann, msg):
89 '''Put an annotation message 'msg' at 'pos'.'''
90 self.put(pos.ss, pos.es, self.out_ann, [ann, [msg]])
92 def putp2(self, pos, ann, msg1, msg2):
93 '''Put an annotation message 'msg' at 'pos'.'''
94 self.put(pos.ss, pos.es, self.out_ann, [ann, [msg1, msg2]])
97 '''Resets the decoder after a complete command was decoded.'''
98 # 'True' for the first byte after CS# went low.
101 # The current command, and the minimum and maximum number
102 # of data bytes to follow.
107 # Used to collect the bytes after the command byte
108 # (and the start/end sample number).
113 def mosi_bytes(self):
114 '''Returns the collected MOSI bytes of a multi byte command.'''
115 return [b.mosi for b in self.mb]
117 def miso_bytes(self):
118 '''Returns the collected MISO bytes of a multi byte command.'''
119 return [b.miso for b in self.mb]
121 def decode_command(self, pos, b):
122 '''Decodes the command byte 'b' at position 'pos' and prepares
123 the decoding of the following data bytes.'''
124 c = self.parse_command(b)
126 self.warn(pos, 'Unknown command')
129 self.cmd, self.dat, self.min, self.max = c
131 if self.cmd == 'Cmd':
132 self.putp(pos, Ann.DIRECTCMD, self.format_command())
134 # Don't output anything now, the command is merged with
135 # the data bytes following it.
138 def format_command(self):
139 '''Returns the label for the current command.'''
140 if self.cmd in ('Write', 'Read', 'WriteB', 'ReadB', 'WriteT', 'ReadT', 'FIFOW', 'FIFOR'):
142 if self.cmd == 'Cmd':
143 reg = dir_cmd.get(self.dat, 'Unknown direct command')
144 return '{} {}'.format(self.cmd, reg)
146 return 'TODO Cmd {}'.format(self.cmd)
148 def parse_command(self, b):
149 '''Parses the command byte.
150 Returns a tuple consisting of:
151 - the name of the command
152 - additional data needed to dissect the following bytes
153 - minimum number of following bytes
154 - maximum number of following bytes (None for infinite)
157 # previous command was 'Space B'
158 if self.cmd == 'Space B':
159 if (b & 0xC0) == 0x00:
160 return ('WriteB', addr, 1, 99999)
161 if (b & 0xC0) == 0x40:
162 return ('ReadB', addr, 1, 99999)
164 self.warn(pos, 'Unknown address/command combination')
165 # previous command was 'TestAccess'
166 elif self.cmd == 'TestAccess':
167 if (b & 0xC0) == 0x00:
168 return ('WriteT', addr, 1, 99999)
169 if (b & 0xC0) == 0x40:
170 return ('ReadT', addr, 1, 99999)
172 self.warn(pos, 'Unknown address/command combination')
174 # Space A regs or other operation modes (except Space B)
175 # Register Write 0b00xxxxxx 0x00 to 0x3F => 'Write'
176 # Register Read 0b01xxxxxx 0x40 to 0x7F => 'Read'
178 if (b & 0xC0) == 0x00:
179 return ('Write', addr, 1, 99999)
180 if (b & 0xC0) == 0x40:
181 return ('Read', addr, 1, 99999)
183 self.warn(pos, 'Unknown address/command combination')
185 # FIFO Load 0b10000000 0x80 => 'FIFO Write'
186 # PT_memory loadA-config 0b10100000 0xA0 => 'Write'
187 # PT_memory loadF-config 0b10101000 0xA8 => 'Write'
188 # PT_memory loadTSN data 0b10101100 0xAC => 'Write'
189 # PT_memory Read 0b10111111 0xBF => 'Read'
190 # FIFO Read 0b10011111 0x9F => 'FIFO Read'
191 # Direct Command 0b11xxx1xx 0xC0 to 0xE8 => 'Cmd'
192 # Register Space-B Access 0b11111011 0xFB => 'Space B'
193 # Register Test Access 0b11111100 0xFC => 'TestAccess'
195 return ('FIFOW', b, 1, 99999)
197 return ('Write', b, 1, 99999)
199 return ('Write', b, 1, 99999)
201 return ('Write', b, 1, 99999)
203 return ('Read', b, 1, 99999)
205 return ('FIFOR', b, 1, 99999)
206 if (b >= 0x0C and b <= 0xE8) :
207 return ('Cmd', b, 0, 0)
209 return ('Space B', b, 0, 0)
211 return ('TestAccess', b, 0, 0)
213 self.warn(pos, 'Unknown address/command combination')
215 def decode_reg(self, pos, ann, regid, data):
216 '''Decodes a register.
217 pos -- start and end sample numbers of the register
218 ann -- the annotation number that is used to output the register.
219 regid -- may be either an integer used as a key for the 'regs'
220 dictionary, or a string directly containing a register name.'
221 data -- the register content.
223 if type(regid) == int:
224 if (ann == Ann.FIFO_READ) or (ann == Ann.FIFO_WRITE):
226 elif (ann == Ann.BURST_READB) or (ann == Ann.BURST_WRITEB):
227 # Get the name of the register.
228 if regid not in regsSpaceB:
229 self.warn(pos, 'Unknown register SpaceB')
231 name = '{} ({:02X})'.format(regsSpaceB[regid], regid)
232 elif (ann == Ann.BURST_READT) or (ann == Ann.BURST_WRITET):
233 # Get the name of the register.
234 if regid not in regsTest:
235 self.warn(pos, 'Unknown register Test')
237 name = '{} ({:02X})'.format(regsTest[regid], regid)
239 # Get the name of the register.
240 if regid not in regsSpaceA:
241 self.warn(pos, 'Unknown register SpaceA')
243 name = '{} ({:02X})'.format(regsSpaceA[regid], regid)
247 if regid == 'STATUS' and ann == Ann.STATUS:
249 self.decode_status_reg(pos, ann, data, label)
251 label = '{}: {}'.format(self.format_command(), name)
252 self.decode_mb_data(pos, ann, data, label)
254 def decode_status_reg(self, pos, ann, data, label):
255 '''Decodes the data bytes 'data' of a status register at position
256 'pos'. The decoded data is prefixed with 'label'.'''
258 def decode_mb_data(self, pos, ann, data, label):
259 '''Decodes the data bytes 'data' of a multibyte command at position
260 'pos'. The decoded data is prefixed with 'label'.'''
263 return '{:02X}'.format(b)
265 data = ' '.join([escape(b) for b in data])
266 if (ann == Ann.FIFO_WRITE) or (ann == Ann.FIFO_READ):
267 text = '{}{}'.format(label, data)
269 text = '{} = {}'.format(label, data)
270 self.putp(pos, ann, text)
272 def finish_command(self, pos):
273 '''Decodes the remaining data bytes at position 'pos'.'''
274 if self.cmd == 'Write':
275 self.decode_reg(pos, Ann.BURST_WRITE, self.dat, self.mosi_bytes())
276 elif self.cmd == 'Read':
277 self.decode_reg(pos, Ann.BURST_READ, self.dat, self.miso_bytes())
278 elif self.cmd == 'WriteB':
279 self.decode_reg(pos, Ann.BURST_WRITEB, self.dat, self.mosi_bytes())
280 elif self.cmd == 'ReadB':
281 self.decode_reg(pos, Ann.BURST_READB, self.dat, self.miso_bytes())
282 elif self.cmd == 'WriteT':
283 self.decode_reg(pos, Ann.BURST_WRITET, self.dat, self.mosi_bytes())
284 elif self.cmd == 'ReadT':
285 self.decode_reg(pos, Ann.BURST_READT, self.dat, self.miso_bytes())
286 elif self.cmd == 'FIFOW':
287 self.decode_reg(pos, Ann.FIFO_WRITE, self.dat, self.mosi_bytes())
288 elif self.cmd == 'FIFOR':
289 self.decode_reg(pos, Ann.FIFO_READ, self.dat, self.miso_bytes())
290 elif self.cmd == 'Cmd':
291 self.decode_reg(pos, Ann.DIRECTCMD, self.dat, self.mosi_bytes())
293 self.warn(pos, 'Unhandled command {}'.format(self.cmd))
295 def decode(self, ss, es, data):
296 if not self.requirements_met:
299 ptype, data1, data2 = data
301 if ptype == 'CS-CHANGE':
304 self.requirements_met = False
305 raise ChannelError('CS# pin required.')
307 self.cs_was_released = True
309 if data1 == 0 and data2 == 1:
310 # Rising edge, the complete command is transmitted, process
311 # the bytes that were sent after the command byte.
313 # Check if we got the minimum number of data bytes
314 # after the command byte.
315 if len(self.mb) < self.min:
316 self.warn((ss, ss), 'Missing data bytes')
318 self.finish_command(Pos(self.ss_mb, self.es_mb))
321 self.cs_was_released = True
323 elif ptype == 'DATA' and self.cs_was_released:
324 mosi, miso = data1, data2
327 if miso is None or mosi is None:
328 self.requirements_met = False
329 raise ChannelError('Both MISO and MOSI pins are required.')
332 # Register Space-B Access 0b11111011 0xFB => 'Space B'
335 # First MOSI byte 'Space B' command.
336 self.decode_command(pos, mosi)
337 # First MISO byte is always the status register.
338 #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
339 # Register TestAccess Access 0b11111100 0xFC => 'TestAccess'
342 # First MOSI byte 'TestAccess' command.
343 self.decode_command(pos, mosi)
344 # First MISO byte is always the status register.
345 #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
348 # First MOSI byte is always the command.
349 self.decode_command(pos, mosi)
350 # First MISO byte is always the status register.
351 #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
353 if not self.cmd or len(self.mb) >= self.max:
354 self.warn(pos, 'Excess byte')
356 # Collect the bytes after the command byte.
360 self.mb.append(Data(mosi, miso))