]> sigrok.org Git - libsigrokdecode.git/blob - decoders/st25r39xx_spi/pd.py
srd: add TODO comment on the SIGROKDECODE_DIR env var's motivation
[libsigrokdecode.git] / decoders / st25r39xx_spi / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2019-2020 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
20 import sigrokdecode as srd
21 from collections import namedtuple
22 from common.srdhelper import SrdIntEnum
23 from .lists import *
24
25 Ann = SrdIntEnum.from_str('Ann', 'BURST_READ BURST_WRITE \
26     BURST_READB BURST_WRITEB BURST_READT BURST_WRITET \
27     DIRECTCMD FIFO_WRITE FIFO_READ STATUS WARN')
28
29 Pos = namedtuple('Pos', ['ss', 'es'])
30 Data = namedtuple('Data', ['mosi', 'miso'])
31
32 class Decoder(srd.Decoder):
33     api_version = 3
34     id = 'st25r39xx_spi'
35     name = 'ST25R39xx (SPI mode)'
36     longname = 'STMicroelectronics ST25R39xx'
37     desc = 'High performance NFC universal device and EMVCo reader protocol.'
38     license = 'gplv2+'
39     inputs = ['spi']
40     outputs = []
41     tags = ['IC', 'Wireless/RF']
42     annotations = (
43         ('Read', 'Burst register read'),
44         ('Write', 'Burst register write'),
45         ('ReadB', 'Burst register SpaceB read'),
46         ('WriteB', 'Burst register SpaceB write'),
47         ('ReadT', 'Burst register Test read'),
48         ('WriteT', 'Burst register Test write'),
49         ('Cmd', 'Direct command'),
50         ('FIFOW', 'FIFO write'),
51         ('FIFOR', 'FIFO read'),
52         ('status_reg', 'Status register'),
53         ('warning', 'Warning'),
54     )
55     annotation_rows = (
56         ('regs', 'Regs', (Ann.prefixes('BURST_'))),
57         ('cmds', 'Commands', (Ann.DIRECTCMD,)),
58         ('data', 'Data', (Ann.prefixes('FIFO_'))),
59         ('status', 'Status register', (Ann.STATUS,)),
60         ('warnings', 'Warnings', (Ann.WARN,)),
61     )
62
63     def __init__(self):
64         self.reset()
65
66     def reset(self):
67         self.next()
68         self.requirements_met = True
69         self.cs_was_released = False
70
71     def start(self):
72         self.out_ann = self.register(srd.OUTPUT_ANN)
73
74     def warn(self, pos, msg):
75         '''Put a warning message 'msg' at 'pos'.'''
76         self.put(pos.ss, pos.es, self.out_ann, [Ann.WARN, [msg]])
77
78     def putp(self, pos, ann, msg):
79         '''Put an annotation message 'msg' at 'pos'.'''
80         self.put(pos.ss, pos.es, self.out_ann, [ann, [msg]])
81
82     def putp2(self, pos, ann, msg1, msg2):
83         '''Put an annotation message 'msg' at 'pos'.'''
84         self.put(pos.ss, pos.es, self.out_ann, [ann, [msg1, msg2]])
85
86     def next(self):
87         '''Resets the decoder after a complete command was decoded.'''
88         # 'True' for the first byte after CS# went low.
89         self.first = True
90
91         # The current command, and the minimum and maximum number
92         # of data bytes to follow.
93         self.cmd = None
94         self.min = 0
95         self.max = 0
96
97         # Used to collect the bytes after the command byte
98         # (and the start/end sample number).
99         self.mb = []
100         self.ss_mb = -1
101         self.es_mb = -1
102
103     def mosi_bytes(self):
104         '''Returns the collected MOSI bytes of a multi byte command.'''
105         return [b.mosi for b in self.mb]
106
107     def miso_bytes(self):
108         '''Returns the collected MISO bytes of a multi byte command.'''
109         return [b.miso for b in self.mb]
110
111     def decode_command(self, pos, b):
112         '''Decodes the command byte 'b' at position 'pos' and prepares
113         the decoding of the following data bytes.'''
114         c = self.parse_command(b)
115         if c is None:
116             self.warn(pos, 'Unknown command')
117             return
118
119         self.cmd, self.dat, self.min, self.max = c
120
121         if self.cmd == 'Cmd':
122             self.putp(pos, Ann.DIRECTCMD, self.format_command())
123         else:
124             # Don't output anything now, the command is merged with
125             # the data bytes following it.
126             self.ss_mb = pos.ss
127
128     def format_command(self):
129         '''Returns the label for the current command.'''
130         if self.cmd in ('Write', 'Read', 'WriteB', 'ReadB', 'WriteT', 'ReadT', 'FIFO Write', 'FIFO Read'):
131             return self.cmd
132         if self.cmd == 'Cmd':
133             reg = dir_cmd.get(self.dat, 'Unknown direct command')
134             return '{} {}'.format(self.cmd, reg)
135         else:
136             return 'TODO Cmd {}'.format(self.cmd)
137
138     def parse_command(self, b):
139         '''Parses the command byte.
140         Returns a tuple consisting of:
141         - the name of the command
142         - additional data needed to dissect the following bytes
143         - minimum number of following bytes
144         - maximum number of following bytes (None for infinite)
145         '''
146         addr = b & 0x3F
147         # previous command was 'Space B'
148         if self.cmd == 'Space B':
149             if (b & 0xC0) == 0x00:
150                 return ('WriteB', addr, 1, 99999)
151             if (b & 0xC0) == 0x40:
152                 return ('ReadB', addr, 1, 99999)
153             else:
154                 self.warn(pos, 'Unknown address/command combination')
155         # previous command was 'TestAccess'
156         elif self.cmd == 'TestAccess':
157             if (b & 0xC0) == 0x00:
158                 return ('WriteT', addr, 1, 99999)
159             if (b & 0xC0) == 0x40:
160                 return ('ReadT', addr, 1, 99999)
161             else:
162                 self.warn(pos, 'Unknown address/command combination')
163         else:
164             # Space A regs or other operation modes (except Space B)
165             # Register Write   0b00xxxxxx 0x00 to 0x3F => 'Write'
166             # Register Read    0b01xxxxxx 0x40 to 0x7F => 'Read'
167             if (b <= 0x7F):
168                 if (b & 0xC0) == 0x00:
169                     return ('Write', addr, 1, 99999)
170                 if (b & 0xC0) == 0x40:
171                     return ('Read', addr, 1, 99999)
172                 else:
173                     self.warn(pos, 'Unknown address/command combination')
174             else:
175                 # FIFO Load                 0b10000000 0x80 => 'FIFO Write'
176                 # PT_memory loadA-config    0b10100000 0xA0 => 'Write'
177                 # PT_memory loadF-config    0b10101000 0xA8 => 'Write'
178                 # PT_memory loadTSN data    0b10101100 0xAC => 'Write'
179                 # PT_memory Read            0b10111111 0xBF => 'Read'
180                 # FIFO Read                 0b10011111 0x9F => 'FIFO Read'
181                 # Direct Command            0b11xxx1xx 0xC0 to 0xE8 => 'Cmd'
182                 # Register Space-B Access   0b11111011 0xFB => 'Space B'
183                 # Register Test Access      0b11111100 0xFC => 'TestAccess'
184                 if b == 0x80:
185                     return ('FIFO Write', b, 1, 99999)
186                 if b == 0xA0:
187                     return ('Write', b, 1, 99999)
188                 if b == 0xA8:
189                     return ('Write', b, 1, 99999)
190                 if b == 0xAC:
191                     return ('Write', b, 1, 99999)
192                 if b == 0xBF:
193                     return ('Read', b, 1, 99999)
194                 if b == 0x9F:
195                     return ('FIFO Read', b, 1, 99999)
196                 if (b >= 0x0C and b <= 0xE8) :
197                     return ('Cmd', b, 0, 0)
198                 if b == 0xFB:
199                     return ('Space B', b, 0, 0)
200                 if b == 0xFC:
201                     return ('TestAccess', b, 0, 0)
202                 else:
203                     self.warn(pos, 'Unknown address/command combination')
204
205     def decode_reg(self, pos, ann, regid, data):
206         '''Decodes a register.
207         pos   -- start and end sample numbers of the register
208         ann   -- the annotation number that is used to output the register.
209         regid -- may be either an integer used as a key for the 'regs'
210                  dictionary, or a string directly containing a register name.'
211         data  -- the register content.
212         '''
213         if type(regid) == int:
214             if (ann == Ann.FIFO_READ) or (ann == Ann.FIFO_WRITE):
215                 name = ''
216             elif (ann == Ann.BURST_READB) or (ann == Ann.BURST_WRITEB):
217                 # Get the name of the register.
218                 if regid not in regsSpaceB:
219                     self.warn(pos, 'Unknown register SpaceB')
220                     return
221                 name = '{} ({:02X})'.format(regsSpaceB[regid], regid)
222             elif (ann == Ann.BURST_READT) or (ann == Ann.BURST_WRITET):
223                 # Get the name of the register.
224                 if regid not in regsTest:
225                     self.warn(pos, 'Unknown register Test')
226                     return
227                 name = '{} ({:02X})'.format(regsTest[regid], regid)
228             else:
229                 # Get the name of the register.
230                 if regid not in regsSpaceA:
231                     self.warn(pos, 'Unknown register SpaceA')
232                     return
233                 name = '{} ({:02X})'.format(regsSpaceA[regid], regid)
234         else:
235             name = regid
236
237         if regid == 'STATUS' and ann == Ann.STATUS:
238             label = 'Status'
239             self.decode_status_reg(pos, ann, data, label)
240         else:
241             label = '{}: {}'.format(self.format_command(), name)
242         self.decode_mb_data(pos, ann, data, label)
243
244     def decode_status_reg(self, pos, ann, data, label):
245         '''Decodes the data bytes 'data' of a status register at position
246         'pos'. The decoded data is prefixed with 'label'.'''
247
248     def decode_mb_data(self, pos, ann, data, label):
249         '''Decodes the data bytes 'data' of a multibyte command at position
250         'pos'. The decoded data is prefixed with 'label'.'''
251
252         def escape(b):
253             return '{:02X}'.format(b)
254
255         data = ' '.join([escape(b) for b in data])
256         if (ann == Ann.FIFO_WRITE) or (ann == Ann.FIFO_READ):
257             text = '{}{}'.format(label, data)
258         else:
259             text = '{} = {}'.format(label, data)
260         self.putp(pos, ann, text)
261
262     def finish_command(self, pos):
263         '''Decodes the remaining data bytes at position 'pos'.'''
264         if self.cmd == 'Write':
265             self.decode_reg(pos, Ann.BURST_WRITE, self.dat, self.mosi_bytes())
266         elif self.cmd == 'Read':
267             self.decode_reg(pos, Ann.BURST_READ, self.dat, self.miso_bytes())
268         elif self.cmd == 'WriteB':
269             self.decode_reg(pos, Ann.BURST_WRITEB, self.dat, self.mosi_bytes())
270         elif self.cmd == 'ReadB':
271             self.decode_reg(pos, Ann.BURST_READB, self.dat, self.miso_bytes())
272         elif self.cmd == 'WriteT':
273             self.decode_reg(pos, Ann.BURST_WRITET, self.dat, self.mosi_bytes())
274         elif self.cmd == 'ReadT':
275             self.decode_reg(pos, Ann.BURST_READT, self.dat, self.miso_bytes())
276         elif self.cmd == 'FIFO Write':
277             self.decode_reg(pos, Ann.FIFO_WRITE, self.dat, self.mosi_bytes())
278         elif self.cmd == 'FIFO Read':
279             self.decode_reg(pos, Ann.FIFO_READ, self.dat, self.miso_bytes())
280         elif self.cmd == 'Cmd':
281             self.decode_reg(pos, Ann.DIRECTCMD, self.dat, self.mosi_bytes())
282         else:
283             self.warn(pos, 'Unhandled command {}'.format(self.cmd))
284
285     def decode(self, ss, es, data):
286         if not self.requirements_met:
287             return
288
289         ptype, data1, data2 = data
290
291         if ptype == 'CS-CHANGE':
292             if data1 is None:
293                 if data2 is None:
294                     self.requirements_met = False
295                     raise ChannelError('CS# pin required.')
296                 elif data2 == 1:
297                     self.cs_was_released = True
298
299             if data1 == 0 and data2 == 1:
300                 # Rising edge, the complete command is transmitted, process
301                 # the bytes that were sent after the command byte.
302                 if self.cmd:
303                     # Check if we got the minimum number of data bytes
304                     # after the command byte.
305                     if len(self.mb) < self.min:
306                         self.warn((ss, ss), 'Missing data bytes')
307                     elif self.mb:
308                         self.finish_command(Pos(self.ss_mb, self.es_mb))
309
310                 self.next()
311                 self.cs_was_released = True
312
313         elif ptype == 'DATA' and self.cs_was_released:
314             mosi, miso = data1, data2
315             pos = Pos(ss, es)
316
317             if miso is None or mosi is None:
318                 self.requirements_met = False
319                 raise ChannelError('Both MISO and MOSI pins are required.')
320
321             if self.first:
322                 # Register Space-B Access   0b11111011 0xFB => 'Space B'
323                 if mosi == 0xFB:
324                     self.first = True
325                     # First MOSI byte 'Space B' command.
326                     self.decode_command(pos, mosi)
327                     # First MISO byte is always the status register.
328                     #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
329                 # Register TestAccess Access   0b11111100 0xFC => 'TestAccess'
330                 elif mosi == 0xFC:
331                     self.first = True
332                     # First MOSI byte 'TestAccess' command.
333                     self.decode_command(pos, mosi)
334                     # First MISO byte is always the status register.
335                     #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
336                 else:
337                     self.first = False
338                     # First MOSI byte is always the command.
339                     self.decode_command(pos, mosi)
340                     # First MISO byte is always the status register.
341                     #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
342             else:
343                 if not self.cmd or len(self.mb) >= self.max:
344                     self.warn(pos, 'Excess byte')
345                 else:
346                     # Collect the bytes after the command byte.
347                     if self.ss_mb == -1:
348                         self.ss_mb = ss
349                     self.es_mb = es
350                     self.mb.append(Data(mosi, miso))