]> sigrok.org Git - libsigrokdecode.git/blame - decoders/st25r39xx_spi/pd.py
st25r39xx_spi: Minor style and description changes
[libsigrokdecode.git] / decoders / st25r39xx_spi / pd.py
CommitLineData
a43eba6a 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##
a43eba6a 19
20import sigrokdecode as srd
21from collections import namedtuple
22from common.srdhelper import SrdIntEnum
23from .lists import *
24
25Ann = 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
29Pos = namedtuple('Pos', ['ss', 'es'])
30Data = namedtuple('Data', ['mosi', 'miso'])
31
32class Decoder(srd.Decoder):
33 api_version = 3
34 id = 'st25r39xx_spi'
575124cd 35 name = 'ST25R39xx (SPI mode)'
a43eba6a 36 longname = 'STMicroelectronics ST25R39xx'
575124cd 37 desc = 'High performance NFC universal device and EMVCo reader protocol.'
a43eba6a 38 license = 'gplv2+'
39 inputs = ['spi']
40 outputs = []
575124cd 41 tags = ['IC', 'Wireless/RF']
a43eba6a 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 = (
575124cd 56 ('regs', 'Regs', (Ann.prefixes('BURST_'))),
a43eba6a 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:
575124cd 116 self.warn(pos, 'Unknown command')
a43eba6a 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':
575124cd 133 reg = dir_cmd.get(self.dat, 'Unknown direct command')
a43eba6a 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:
575124cd 154 self.warn(pos, 'Unknown address/command combination')
a43eba6a 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:
575124cd 162 self.warn(pos, 'Unknown address/command combination')
a43eba6a 163 else:
164 # Space A regs or other operation modes (except Space B)
575124cd
SA
165 # Register Write 0b00xxxxxx 0x00 to 0x3F => 'Write'
166 # Register Read 0b01xxxxxx 0x40 to 0x7F => 'Read'
a43eba6a 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:
575124cd 173 self.warn(pos, 'Unknown address/command combination')
a43eba6a 174 else:
575124cd
SA
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'
a43eba6a 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:
575124cd 203 self.warn(pos, 'Unknown address/command combination')
a43eba6a 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:
575124cd 219 self.warn(pos, 'Unknown register SpaceB')
a43eba6a 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:
575124cd 225 self.warn(pos, 'Unknown register Test')
a43eba6a 226 return
227 name = '{} ({:02X})'.format(regsTest[regid], regid)
228 else:
229 # Get the name of the register.
230 if regid not in regsSpaceA:
575124cd 231 self.warn(pos, 'Unknown register SpaceA')
a43eba6a 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:
575124cd 283 self.warn(pos, 'Unhandled command {}'.format(self.cmd))
a43eba6a 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:
575124cd 306 self.warn((ss, ss), 'Missing data bytes')
a43eba6a 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:
575124cd 322 # Register Space-B Access 0b11111011 0xFB => 'Space B'
a43eba6a 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])
575124cd 329 # Register TestAccess Access 0b11111100 0xFC => 'TestAccess'
a43eba6a 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:
575124cd 344 self.warn(pos, 'Excess byte')
a43eba6a 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))