]> sigrok.org Git - libsigrokdecode.git/blob - decoders/st25r39xx_spi/pd.py
st25r39xx_spi: Fix FIFOR/FIFOW issues with PV's Tabular Output View
[libsigrokdecode.git] / decoders / st25r39xx_spi / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2019-2021 Benjamin Vernoux <bvernoux@gmail.com>
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, see <http://www.gnu.org/licenses/>.
18 ##
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
29
30 import sigrokdecode as srd
31 from collections import namedtuple
32 from common.srdhelper import SrdIntEnum
33 from .lists import *
34
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')
38
39 Pos = namedtuple('Pos', ['ss', 'es'])
40 Data = namedtuple('Data', ['mosi', 'miso'])
41
42 class Decoder(srd.Decoder):
43     api_version = 3
44     id = 'st25r39xx_spi'
45     name = 'ST25R39xx (SPI mode)'
46     longname = 'STMicroelectronics ST25R39xx'
47     desc = 'High performance NFC universal device and EMVCo reader protocol.'
48     license = 'gplv2+'
49     inputs = ['spi']
50     outputs = []
51     tags = ['IC', 'Wireless/RF']
52     annotations = (
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'),
64     )
65     annotation_rows = (
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,)),
71     )
72
73     def __init__(self):
74         self.reset()
75
76     def reset(self):
77         self.next()
78         self.requirements_met = True
79         self.cs_was_released = False
80
81     def start(self):
82         self.out_ann = self.register(srd.OUTPUT_ANN)
83
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]])
87
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]])
91
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]])
95
96     def next(self):
97         '''Resets the decoder after a complete command was decoded.'''
98         # 'True' for the first byte after CS# went low.
99         self.first = True
100
101         # The current command, and the minimum and maximum number
102         # of data bytes to follow.
103         self.cmd = None
104         self.min = 0
105         self.max = 0
106
107         # Used to collect the bytes after the command byte
108         # (and the start/end sample number).
109         self.mb = []
110         self.ss_mb = -1
111         self.es_mb = -1
112
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]
116
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]
120
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)
125         if c is None:
126             self.warn(pos, 'Unknown command')
127             return
128
129         self.cmd, self.dat, self.min, self.max = c
130
131         if self.cmd == 'Cmd':
132             self.putp(pos, Ann.DIRECTCMD, self.format_command())
133         else:
134             # Don't output anything now, the command is merged with
135             # the data bytes following it.
136             self.ss_mb = pos.ss
137
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'):
141             return self.cmd
142         if self.cmd == 'Cmd':
143             reg = dir_cmd.get(self.dat, 'Unknown direct command')
144             return '{} {}'.format(self.cmd, reg)
145         else:
146             return 'TODO Cmd {}'.format(self.cmd)
147
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)
155         '''
156         addr = b & 0x3F
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)
163             else:
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)
171             else:
172                 self.warn(pos, 'Unknown address/command combination')
173         else:
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'
177             if (b <= 0x7F):
178                 if (b & 0xC0) == 0x00:
179                     return ('Write', addr, 1, 99999)
180                 if (b & 0xC0) == 0x40:
181                     return ('Read', addr, 1, 99999)
182                 else:
183                     self.warn(pos, 'Unknown address/command combination')
184             else:
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'
194                 if b == 0x80:
195                     return ('FIFOW', b, 1, 99999)
196                 if b == 0xA0:
197                     return ('Write', b, 1, 99999)
198                 if b == 0xA8:
199                     return ('Write', b, 1, 99999)
200                 if b == 0xAC:
201                     return ('Write', b, 1, 99999)
202                 if b == 0xBF:
203                     return ('Read', b, 1, 99999)
204                 if b == 0x9F:
205                     return ('FIFOR', b, 1, 99999)
206                 if (b >= 0x0C and b <= 0xE8) :
207                     return ('Cmd', b, 0, 0)
208                 if b == 0xFB:
209                     return ('Space B', b, 0, 0)
210                 if b == 0xFC:
211                     return ('TestAccess', b, 0, 0)
212                 else:
213                     self.warn(pos, 'Unknown address/command combination')
214
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.
222         '''
223         if type(regid) == int:
224             if (ann == Ann.FIFO_READ) or (ann == Ann.FIFO_WRITE):
225                 name = ''
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')
230                     return
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')
236                     return
237                 name = '{} ({:02X})'.format(regsTest[regid], regid)
238             else:
239                 # Get the name of the register.
240                 if regid not in regsSpaceA:
241                     self.warn(pos, 'Unknown register SpaceA')
242                     return
243                 name = '{} ({:02X})'.format(regsSpaceA[regid], regid)
244         else:
245             name = regid
246
247         if regid == 'STATUS' and ann == Ann.STATUS:
248             label = 'Status'
249             self.decode_status_reg(pos, ann, data, label)
250         else:
251             label = '{}: {}'.format(self.format_command(), name)
252         self.decode_mb_data(pos, ann, data, label)
253
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'.'''
257
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'.'''
261
262         def escape(b):
263             return '{:02X}'.format(b)
264
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)
268         else:
269             text = '{} = {}'.format(label, data)
270         self.putp(pos, ann, text)
271
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())
292         else:
293             self.warn(pos, 'Unhandled command {}'.format(self.cmd))
294
295     def decode(self, ss, es, data):
296         if not self.requirements_met:
297             return
298
299         ptype, data1, data2 = data
300
301         if ptype == 'CS-CHANGE':
302             if data1 is None:
303                 if data2 is None:
304                     self.requirements_met = False
305                     raise ChannelError('CS# pin required.')
306                 elif data2 == 1:
307                     self.cs_was_released = True
308
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.
312                 if self.cmd:
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')
317                     elif self.mb:
318                         self.finish_command(Pos(self.ss_mb, self.es_mb))
319
320                 self.next()
321                 self.cs_was_released = True
322
323         elif ptype == 'DATA' and self.cs_was_released:
324             mosi, miso = data1, data2
325             pos = Pos(ss, es)
326
327             if miso is None or mosi is None:
328                 self.requirements_met = False
329                 raise ChannelError('Both MISO and MOSI pins are required.')
330
331             if self.first:
332                 # Register Space-B Access   0b11111011 0xFB => 'Space B'
333                 if mosi == 0xFB:
334                     self.first = True
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'
340                 elif mosi == 0xFC:
341                     self.first = True
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])
346                 else:
347                     self.first = False
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])
352             else:
353                 if not self.cmd or len(self.mb) >= self.max:
354                     self.warn(pos, 'Excess byte')
355                 else:
356                     # Collect the bytes after the command byte.
357                     if self.ss_mb == -1:
358                         self.ss_mb = ss
359                     self.es_mb = es
360                     self.mb.append(Data(mosi, miso))