]> sigrok.org Git - libsigrokdecode.git/blame - decoders/usb_power_delivery/pd.py
usb_power_delivery: convert double quotes to single quotes
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
CommitLineData
ced6589f
VP
1##
2## This file is part of the libsigrokdecode project.
3##
fddb3114 4## Copyright (C) 2015 Google, Inc
ced6589f
VP
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##
fddb3114
UH
16## You should have received a copy of the GNU General Public License
17## along with this program; if not, write to the Free Software
18## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19##
ced6589f
VP
20
21import sigrokdecode as srd
22import struct
23import zlib # for crc32
24
79065c6f 25# BMC encoding with a 600kHz datarate
ced6589f
VP
26UI_US = 1000000/600000.0
27
28# Threshold to discriminate half-1 from 0 in Binary Mark Conding
29THRESHOLD_US = (UI_US + 2 * UI_US) / 2
30
31# Control Message type
32CTRL_TYPES = {
89b9aaf7
VP
33 0: 'reserved',
34 1: 'GOOD CRC',
35 2: 'GOTO MIN',
36 3: 'ACCEPT',
37 4: 'REJECT',
38 5: 'PING',
39 6: 'PS RDY',
40 7: 'GET SOURCE CAP',
41 8: 'GET SINK CAP',
42 9: 'DR SWAP',
43 10: 'PR SWAP',
44 11: 'VCONN SWAP',
45 12: 'WAIT',
46 13: 'SOFT RESET',
47 14: 'reserved',
48 15: 'reserved'
ced6589f
VP
49}
50
51# Data message type
52DATA_TYPES = {
89b9aaf7
VP
53 1: 'SOURCE CAP',
54 2: 'REQUEST',
55 3: 'BIST',
56 4: 'SINK CAP',
57 15: 'VDM'
ced6589f
VP
58}
59
60# 4b5b encoding of the symbols
61DEC4B5B = [
62 0x10, # Error 00000
63 0x10, # Error 00001
64 0x10, # Error 00010
65 0x10, # Error 00011
66 0x10, # Error 00100
67 0x10, # Error 00101
68 0x13, # Sync-3 00110
69 0x14, # RST-1 00111
70 0x10, # Error 01000
71 0x01, # 1 = 0001 01001
72 0x04, # 4 = 0100 01010
73 0x05, # 5 = 0101 01011
74 0x10, # Error 01100
75 0x16, # EOP 01101
76 0x06, # 6 = 0110 01110
77 0x07, # 7 = 0111 01111
78 0x10, # Error 10000
79 0x12, # Sync-2 10001
80 0x08, # 8 = 1000 10010
81 0x09, # 9 = 1001 10011
82 0x02, # 2 = 0010 10100
83 0x03, # 3 = 0011 10101
84 0x0A, # A = 1010 10110
85 0x0B, # B = 1011 10111
86 0x11, # Sync-1 11000
87 0x15, # RST-2 11001
88 0x0C, # C = 1100 11010
89 0x0D, # D = 1101 11011
90 0x0E, # E = 1110 11100
91 0x0F, # F = 1111 11101
92 0x00, # 0 = 0000 11110
93 0x10, # Error 11111
94]
95SYM_ERR = 0x10
96SYNC1 = 0x11
97SYNC2 = 0x12
98SYNC3 = 0x13
99RST1 = 0x14
100RST2 = 0x15
101EOP = 0x16
102SYNC_CODES = [SYNC1, SYNC2, SYNC3]
103HRST_CODES = [RST1, RST1, RST1, RST2]
104
105START_OF_PACKETS = {
89b9aaf7 106 (SYNC1, SYNC1, SYNC1, SYNC2): 'SOP',
ced6589f
VP
107 (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
108 (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
109 (SYNC1, RST2, RST2, SYNC3): "SOP' Debug",
110 (SYNC1, RST2, SYNC3, SYNC2): 'SOP" Debug',
89b9aaf7
VP
111 (RST1, SYNC1, RST1, SYNC3): 'Cable Reset',
112 (RST1, RST1, RST1, RST2): 'Hard Reset',
ced6589f
VP
113}
114
115SYM_NAME = [
116 ['0x0', '0'],
117 ['0x1', '1'],
118 ['0x2', '2'],
119 ['0x3', '3'],
120 ['0x4', '4'],
121 ['0x5', '5'],
122 ['0x6', '6'],
123 ['0x7', '7'],
124 ['0x8', '8'],
125 ['0x9', '9'],
126 ['0xA', 'A'],
127 ['0xB', 'B'],
128 ['0xC', 'C'],
129 ['0xD', 'D'],
130 ['0xE', 'E'],
131 ['0xF', 'F'],
132 ['ERROR', 'X'],
133 ['SYNC-1', 'S1'],
134 ['SYNC-2', 'S2'],
135 ['SYNC-3', 'S3'],
136 ['RST-1', 'R1'],
137 ['RST-2', 'R2'],
138 ['EOP', '#'],
139]
140
141RDO_FLAGS = {
89b9aaf7
VP
142 (1 << 24): 'no_suspend',
143 (1 << 25): 'comm_cap',
144 (1 << 26): 'cap_mismatch',
145 (1 << 27): 'give_back'
ced6589f 146}
89b9aaf7 147PDO_TYPE = ['', 'BATT:', 'VAR:', '<bad>']
ced6589f 148PDO_FLAGS = {
89b9aaf7
VP
149 (1 << 29): 'dual_role_power',
150 (1 << 28): 'suspend',
151 (1 << 27): 'ext',
152 (1 << 26): 'comm_cap',
153 (1 << 25): 'dual_role_data'
ced6589f
VP
154}
155
156BIST_MODES = {
89b9aaf7
VP
157 0: 'Receiver',
158 1: 'Transmit',
159 2: 'Counters',
160 3: 'Carrier 0',
161 4: 'Carrier 1',
162 5: 'Carrier 2',
163 6: 'Carrier 3',
164 7: 'Eye',
ced6589f
VP
165}
166
167VDM_CMDS = {
89b9aaf7
VP
168 1: 'Disc Ident',
169 2: 'Disc SVID',
170 3: 'Disc Mode',
171 4: 'Enter Mode',
172 5: 'Exit Mode',
173 6: 'Attention',
ced6589f
VP
174 # 16..31: SVID Specific Commands
175 # DisplayPort Commands
89b9aaf7
VP
176 16: 'DP Status',
177 17: 'DP Configure',
ced6589f 178}
89b9aaf7 179VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
ced6589f 180
ced6589f
VP
181class Decoder(srd.Decoder):
182 api_version = 2
183 id = 'usb_power_delivery'
184 name = 'USB PD'
185 longname = 'USB Power Delivery'
186 desc = 'USB Power Delivery protocol.'
187 license = 'gplv2+'
188 inputs = ['logic']
189 outputs = ['usb_pd']
190 channels = (
191 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
192 )
193 options = (
194 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
195 'default': 'no', 'values': ('yes', 'no')},
196 )
197 annotations = (
198 ('type', 'Packet Type'),
199 ('Preamble', 'Preamble'),
200 ('SOP', 'Start of Packet'),
201 ('Head', 'Header'),
202 ('Data', 'Data'),
203 ('CRC', 'Checksum'),
204 ('EOP', 'End Of Packet'),
205 ('Sym', '4b5b symbols'),
206 ('warnings', 'Warnings'),
207 ('src', 'Source Message'),
208 ('snk', 'Sink Message'),
209 ('payload', 'Payload'),
210 ('text', 'Plain text'),
211 )
212 annotation_rows = (
213 ('4B5B', 'symbols', (7, )),
214 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
215 ('payload', 'Payload', (11, )),
216 ('type', 'Type', (0, 9, 10, )),
217 ('warnings', 'Warnings', (8, )),
218 ('text', 'Full text', (12, )),
219 )
220 binary = (
221 ('raw-data', 'RAW binary data'),
222 )
223
224 def get_request(self, rdo):
225 pos = (rdo >> 28) & 7
226 op_ma = ((rdo >> 10) & 0x3ff) * 10
227 max_ma = (rdo & 0x3ff) * 10
89b9aaf7 228 flags = ''
ced6589f
VP
229 for f in RDO_FLAGS.keys():
230 if rdo & f:
89b9aaf7
VP
231 flags += ' ' + RDO_FLAGS[f]
232 return '[%d]%d/%d mA%s' % (pos, op_ma, max_ma, flags)
ced6589f
VP
233
234 def get_source_cap(self, pdo):
235 t = (pdo >> 30) & 3
236 if t == 0:
237 mv = ((pdo >> 10) & 0x3ff) * 50
238 ma = ((pdo >> 0) & 0x3ff) * 10
89b9aaf7 239 p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
ced6589f
VP
240 elif t == 1:
241 minv = ((pdo >> 10) & 0x3ff) * 50
242 maxv = ((pdo >> 20) & 0x3ff) * 50
243 mw = ((pdo >> 0) & 0x3ff) * 250
89b9aaf7 244 p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
ced6589f
VP
245 elif t == 2:
246 minv = ((pdo >> 10) & 0x3ff) * 50
247 maxv = ((pdo >> 20) & 0x3ff) * 50
248 ma = ((pdo >> 0) & 0x3ff) * 10
89b9aaf7 249 p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
ced6589f 250 else:
89b9aaf7
VP
251 p = ''
252 flags = ''
ced6589f
VP
253 for f in PDO_FLAGS.keys():
254 if pdo & f:
89b9aaf7
VP
255 flags += ' ' + PDO_FLAGS[f]
256 return '%s%s%s' % (PDO_TYPE[t], p, flags)
ced6589f
VP
257
258 def get_sink_cap(self, pdo):
259 t = (pdo >> 30) & 3
260 if t == 0:
261 mv = ((pdo >> 10) & 0x3ff) * 50
262 ma = ((pdo >> 0) & 0x3ff) * 10
89b9aaf7 263 p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
ced6589f
VP
264 elif t == 1:
265 minv = ((pdo >> 10) & 0x3ff) * 50
266 maxv = ((pdo >> 20) & 0x3ff) * 50
267 mw = ((pdo >> 0) & 0x3ff) * 250
89b9aaf7 268 p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
ced6589f
VP
269 elif t == 2:
270 minv = ((pdo >> 10) & 0x3ff) * 50
271 maxv = ((pdo >> 20) & 0x3ff) * 50
272 ma = ((pdo >> 0) & 0x3ff) * 10
89b9aaf7 273 p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
ced6589f 274 else:
89b9aaf7
VP
275 p = ''
276 flags = ''
ced6589f
VP
277 for f in PDO_FLAGS.keys():
278 if pdo & f:
89b9aaf7
VP
279 flags += ' ' + PDO_FLAGS[f]
280 return '%s%s%s' % (PDO_TYPE[t], p, flags)
ced6589f
VP
281
282 def get_vdm(self, idx, data):
283 if idx == 0: # VDM header
79065c6f
UH
284 vid = data >> 16
285 struct = data & (1 << 15)
89b9aaf7 286 txt = 'VDM'
79065c6f
UH
287 if struct: # Structured VDM
288 cmd = data & 0x1f
289 src = data & (1 << 5)
290 ack = (data >> 6) & 3
291 pos = (data >> 8) & 7
292 ver = (data >> 13) & 3
89b9aaf7
VP
293 txt = VDM_ACK[ack] + ' '
294 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
295 txt += ' pos %d' % (pos) if pos else ' '
79065c6f 296 else: # Unstructured VDM
89b9aaf7
VP
297 txt = 'unstruct [%04x]' % (data & 0x7fff)
298 txt += ' SVID:%04x' % (vid)
ced6589f 299 else: # VDM payload
89b9aaf7 300 txt = 'VDO:%08x' % (data)
ced6589f
VP
301 return txt
302
303 def get_bist(self, idx, data):
304 mode = data >> 28
305 counter = data & 0xffff
89b9aaf7 306 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
ced6589f 307 if mode == 2:
89b9aaf7 308 mode_name = 'Counter[= %d]' % (counter)
ced6589f 309 # TODO check all 0 bits are 0 / emit warnings
89b9aaf7 310 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
ced6589f
VP
311
312 def putpayload(self, s0, s1, idx):
313 t = self.head_type()
89b9aaf7 314 txt = '???'
ced6589f
VP
315 if t == 2:
316 txt = self.get_request(self.data[idx])
317 elif t == 1:
318 txt = self.get_source_cap(self.data[idx])
319 elif t == 4:
320 txt = self.get_sink_cap(self.data[idx])
321 elif t == 15:
322 txt = self.get_vdm(idx, self.data[idx])
323 elif t == 3:
324 txt = self.get_bist(idx, self.data[idx])
325 self.putx(s0, s1, [11, [txt, txt]])
89b9aaf7 326 self.text += ' - ' + txt
ced6589f
VP
327
328 def puthead(self):
329 ann_type = 9 if self.head_power_role() else 10
89b9aaf7 330 role = 'SRC' if self.head_power_role() else 'SNK'
ced6589f 331 if self.head_data_role() != self.head_power_role():
89b9aaf7 332 role += '/DFP' if self.head_data_role() else '/UFP'
ced6589f
VP
333 t = self.head_type()
334 if self.head_count() == 0:
335 shortm = CTRL_TYPES[t]
336 else:
89b9aaf7 337 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
ced6589f 338
89b9aaf7 339 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
ced6589f
VP
340 self.putx(0, -1, [ann_type, [longm, shortm]])
341 self.text += longm
342
343 def head_id(self):
344 return (self.head >> 9) & 7
345
346 def head_power_role(self):
347 return (self.head >> 8) & 1
348
349 def head_data_role(self):
350 return (self.head >> 5) & 1
351
352 def head_rev(self):
353 return ((self.head >> 6) & 3) + 1
354
355 def head_type(self):
356 return self.head & 0xF
357
358 def head_count(self):
359 return (self.head >> 12) & 7
360
361 def putx(self, s0, s1, data):
362 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
363
364 def putwarn(self, longm, shortm):
365 self.putx(0, -1, [8, [longm, shortm]])
366
367 def compute_crc32(self):
89b9aaf7 368 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
ced6589f
VP
369 *tuple([d & 0xffffffff for d in self.data]))
370 return zlib.crc32(bdata)
371
372 def rec_sym(self, i, sym):
373 self.putx(i, i+5, [7, SYM_NAME[sym]])
374
375 def get_sym(self, i, rec=True):
376 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
377 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
378 sym = DEC4B5B[v]
379 if rec:
380 self.rec_sym(i, sym)
381 return sym
382
383 def get_short(self):
384 i = self.idx
385 # Check it's not a truncated packet
386 if len(self.bits) - i <= 20:
89b9aaf7 387 self.putwarn('Truncated', '!')
ced6589f
VP
388 return 0x0BAD
389 k = [self.get_sym(i), self.get_sym(i+5),
390 self.get_sym(i+10), self.get_sym(i+15)]
391 # TODO check bad symbols
392 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
393 self.idx += 20
394 return val
395
396 def get_word(self):
397 lo = self.get_short()
398 hi = self.get_short()
399 return lo | (hi << 16)
400
401 def find_corrupted_sop(self, k):
402 # Start of packet are valid even if they have only 3 correct symbols
403 # out of 4.
404 for seq in START_OF_PACKETS.keys():
405 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
406 return START_OF_PACKETS[seq]
407 return None
408
409 def scan_eop(self):
410 for i in range(len(self.bits) - 19):
411 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
412 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
413 sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
414 if not sym:
415 sym = self.find_corrupted_sop(k)
416 # We have an interesting symbol sequence
417 if sym:
418 # annotate the preamble
419 self.putx(0, i, [1, ['Preamble', '...']])
420 # annotate each symbol
421 self.rec_sym(i, k[0])
422 self.rec_sym(i+5, k[1])
423 self.rec_sym(i+10, k[2])
424 self.rec_sym(i+15, k[3])
425 if sym == 'Hard Reset':
89b9aaf7 426 self.text += 'HRST'
ced6589f
VP
427 return -1 # Hard reset
428 elif sym == 'Cable Reset':
89b9aaf7 429 self.text += 'CRST'
ced6589f
VP
430 return -1 # Cable reset
431 else:
432 self.putx(i, i+20, [2, [sym, 'S']])
433 return i+20
434 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
89b9aaf7
VP
435 self.text += 'Junk???'
436 self.putwarn('No start of packet found', 'XXX')
ced6589f
VP
437 return -1 # No Start Of Packet
438
439 def __init__(self, **kwargs):
440 self.samplerate = None
441 self.idx = 0
442 self.packet_seq = 0
ced6589f
VP
443 self.samplenum = 0
444 self.previous = 0
445 self.oldpins = [0]
446 self.startsample = None
447 self.bits = []
448 self.edges = []
449 self.bad = []
450 self.half_one = False
451 self.start_one = 0
452
453 def metadata(self, key, value):
454 if key == srd.SRD_CONF_SAMPLERATE:
455 self.samplerate = value
456 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
457 self.maxbit = self.us2samples(3 * UI_US)
458 # duration threshold between half 1 and 0
459 self.threshold = self.us2samples(THRESHOLD_US)
460
461 def start(self):
462 self.out_python = self.register(srd.OUTPUT_PYTHON)
463 self.out_ann = self.register(srd.OUTPUT_ANN)
464 self.out_binary = self.register(srd.OUTPUT_BINARY)
465 self.out_bitrate = self.register(
466 srd.OUTPUT_META,
467 meta=(int, 'Bitrate', 'Bitrate during the packet')
468 )
469
470 def us2samples(self, us):
471 if self.samplerate is None:
89b9aaf7 472 raise Exception('Need the samplerate.')
ced6589f
VP
473 return int(us * self.samplerate / 1000000)
474
475 def decode_packet(self):
476 self.data = []
477 self.idx = 0
89b9aaf7 478 self.text = ''
ced6589f
VP
479
480 if len(self.edges) < 50:
481 return # Not a real PD packet
482
483 self.packet_seq += 1
484 tstamp = float(self.startsample) / self.samplerate
89b9aaf7 485 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
ced6589f
VP
486
487 self.idx = self.scan_eop()
488 if self.idx < 0:
489 # Full text trace of the issue
490 self.putx(0, self.idx, [12, [self.text, '...']])
491 return # No real packet: ABORT
492
493 # Packet header
494 self.head = self.get_short()
495 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
496 self.puthead()
497
498 # Decode data payload
499 for i in range(self.head_count()):
500 self.data.append(self.get_word())
501 self.putx(self.idx-40, self.idx,
502 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
503 self.putpayload(self.idx-40, self.idx, i)
504
505 # CRC check
506 self.crc = self.get_word()
507 ccrc = self.compute_crc32()
508 if self.crc != ccrc:
89b9aaf7 509 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
ced6589f
VP
510 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
511
512 # End of Packet
513 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
514 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
515 self.idx += 5
516 else:
89b9aaf7 517 self.putwarn('No EOP', 'EOP!')
ced6589f
VP
518 # Full text trace
519 if self.options['fulltext'] == 'yes':
520 self.putx(0, self.idx, [12, [self.text, '...']])
521
522 # Meta data for bitrate
523 ss, es = self.edges[0], self.edges[-1]
524 bitrate = self.samplerate*len(self.bits) / float(es - ss)
525 self.put(es, ss, self.out_bitrate, int(bitrate))
526 # Raw binary data (BMC decoded)
527 self.put(es, ss, self.out_binary, (0, bytes(self.bits)))
528
529 def decode(self, ss, es, data):
530 if self.samplerate is None:
89b9aaf7 531 raise Exception('Cannot decode without samplerate.')
ced6589f
VP
532 for (self.samplenum, pins) in data:
533 # find edges ...
534 if self.oldpins == pins:
535 continue
536
537 self.oldpins, (cc, ) = pins, pins
538
539 # First sample of the packet, just record the start date
540 if not self.startsample:
541 self.startsample = self.samplenum
542 self.previous = self.samplenum
543 continue
544
545 diff = self.samplenum - self.previous
546
547 # Large idle: use it as the end of packet
548 if diff > self.maxbit:
549 # the last edge of the packet
550 self.edges.append(self.previous)
551 # Export the packet
552 self.decode_packet()
553 # Reset for next packet
554 self.startsample = self.samplenum
555 self.bits = []
556 self.edges = []
557 self.bad = []
558 self.half_one = False
559 self.start_one = 0
560 else: # add the bit to the packet
561 is_zero = diff > self.threshold
562 if is_zero and not self.half_one:
563 self.bits.append(0)
564 self.edges.append(self.previous)
565 elif not is_zero and self.half_one:
566 self.bits.append(1)
567 self.edges.append(self.start_one)
568 self.half_one = False
569 elif not is_zero and not self.half_one:
570 self.half_one = True
571 self.start_one = self.previous
572 else: # Invalid BMC sequence
573 self.bad.append((self.start_one, self.previous))
574 # TODO try to recover
575 self.bits.append(0)
576 self.edges.append(self.previous)
577 self.half_one = False
578 self.previous = self.samplenum