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