]> sigrok.org Git - libsigrokdecode.git/blobdiff - decoders/usb_power_delivery/pd.py
usb_power_delivery: PPS support
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
index 312cf52a3143c5436d516bc1059504f8ff844022..d6818c116fbd681099964e9b1c56912c1880a8ad 100644 (file)
@@ -2,6 +2,7 @@
 ## This file is part of the libsigrokdecode project.
 ##
 ## Copyright (C) 2015 Google, Inc
+## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
 ## GNU General Public License for more details.
 ##
 ## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+## along with this program; if not, see <http://www.gnu.org/licenses/>.
 ##
 
 import sigrokdecode as srd
 import struct
 import zlib   # for crc32
 
-# BMC encoding with a 600Khz datarate
+# BMC encoding with a 600kHz datarate
 UI_US = 1000000/600000.0
 
 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
@@ -30,31 +30,31 @@ THRESHOLD_US = (UI_US + 2 * UI_US) / 2
 
 # Control Message type
 CTRL_TYPES = {
-    0: "reserved",
-    1: "GOOD CRC",
-    2: "GOTO MIN",
-    3: "ACCEPT",
-    4: "REJECT",
-    5: "PING",
-    6: "PS RDY",
-    7: "GET SOURCE CAP",
-    8: "GET SINK CAP",
-    9: "DR SWAP",
-    10: "PR SWAP",
-    11: "VCONN SWAP",
-    12: "WAIT",
-    13: "SOFT RESET",
-    14: "reserved",
-    15: "reserved"
+    0: 'reserved',
+    1: 'GOOD CRC',
+    2: 'GOTO MIN',
+    3: 'ACCEPT',
+    4: 'REJECT',
+    5: 'PING',
+    6: 'PS RDY',
+    7: 'GET SOURCE CAP',
+    8: 'GET SINK CAP',
+    9: 'DR SWAP',
+    10: 'PR SWAP',
+    11: 'VCONN SWAP',
+    12: 'WAIT',
+    13: 'SOFT RESET',
+    14: 'reserved',
+    15: 'reserved'
 }
 
 # Data message type
 DATA_TYPES = {
-    1: "SOURCE CAP",
-    2: "REQUEST",
-    3: "BIST",
-    4: "SINK CAP",
-    15: "VDM"
+    1: 'SOURCE CAP',
+    2: 'REQUEST',
+    3: 'BIST',
+    4: 'SINK CAP',
+    15: 'VDM'
 }
 
 # 4b5b encoding of the symbols
@@ -102,14 +102,23 @@ EOP = 0x16
 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
 HRST_CODES = [RST1, RST1, RST1, RST2]
 
+SOP_SEQUENCES = [
+    (SYNC1, SYNC1, SYNC1, SYNC2),
+    (SYNC1, SYNC1, SYNC3, SYNC3),
+    (SYNC1, SYNC3, SYNC1, SYNC3),
+    (SYNC1, RST2,  RST2,  SYNC3),
+    (SYNC1, RST2,  SYNC3, SYNC2),
+    (RST1,  SYNC1, RST1,  SYNC3),
+    (RST1,  RST1,  RST1,   RST2),
+]
 START_OF_PACKETS = {
-    (SYNC1, SYNC1, SYNC1, SYNC2): "SOP",
-    (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
-    (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
-    (SYNC1, RST2,  RST2,  SYNC3): "SOP' Debug",
-    (SYNC1, RST2,  SYNC3, SYNC2): 'SOP" Debug',
-    (RST1,  SYNC1, RST1,  SYNC3): "Cable Reset",
-    (RST1,  RST1,  RST1,   RST2): "Hard Reset",
+    SOP_SEQUENCES[0]: 'SOP',
+    SOP_SEQUENCES[1]: "SOP'",
+    SOP_SEQUENCES[2]: 'SOP"',
+    SOP_SEQUENCES[3]: "SOP' Debug",
+    SOP_SEQUENCES[4]: 'SOP" Debug',
+    SOP_SEQUENCES[5]: 'Cable Reset',
+    SOP_SEQUENCES[6]: 'Hard Reset',
 }
 
 SYM_NAME = [
@@ -139,48 +148,51 @@ SYM_NAME = [
 ]
 
 RDO_FLAGS = {
-    (1 << 24): "no_suspend",
-    (1 << 25): "comm_cap",
-    (1 << 26): "cap_mismatch",
-    (1 << 27): "give_back"
+    (1 << 24): 'no_suspend',
+    (1 << 25): 'comm_cap',
+    (1 << 26): 'cap_mismatch',
+    (1 << 27): 'give_back'
 }
-PDO_TYPE = ["", "BATT:", "VAR:", "<bad>"]
+
 PDO_FLAGS = {
-    (1 << 29): "dual_role_power",
-    (1 << 28): "suspend",
-    (1 << 27): "ext",
-    (1 << 26): "comm_cap",
-    (1 << 25): "dual_role_data"
+    (1 << 29): 'dual_role_power',
+    (1 << 28): 'suspend',
+    (1 << 27): 'ext',
+    (1 << 26): 'comm_cap',
+    (1 << 25): 'dual_role_data'
 }
 
 BIST_MODES = {
-        0: "Receiver",
-        1: "Transmit",
-        2: "Counters",
-        3: "Carrier 0",
-        4: "Carrier 1",
-        5: "Carrier 2",
-        6: "Carrier 3",
-        7: "Eye",
+        0: 'Receiver',
+        1: 'Transmit',
+        2: 'Counters',
+        3: 'Carrier 0',
+        4: 'Carrier 1',
+        5: 'Carrier 2',
+        6: 'Carrier 3',
+        7: 'Eye',
 }
 
 VDM_CMDS = {
-        1: "Disc Ident",
-        2: "Disc SVID",
-        3: "Disc Mode",
-        4: "Enter Mode",
-        5: "Exit Mode",
-        6: "Attention",
+        1: 'Disc Ident',
+        2: 'Disc SVID',
+        3: 'Disc Mode',
+        4: 'Enter Mode',
+        5: 'Exit Mode',
+        6: 'Attention',
         # 16..31: SVID Specific Commands
         # DisplayPort Commands
-        16: "DP Status",
-        17: "DP Configure",
+        16: 'DP Status',
+        17: 'DP Configure',
 }
-VDM_ACK = ["REQ", "ACK", "NAK", "BSY"]
+VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
+
 
+class SamplerateError(Exception):
+    pass
 
 class Decoder(srd.Decoder):
-    api_version = 2
+    api_version = 3
     id = 'usb_power_delivery'
     name = 'USB PD'
     longname = 'USB Power Delivery'
@@ -189,7 +201,10 @@ class Decoder(srd.Decoder):
     inputs = ['logic']
     outputs = ['usb_pd']
     channels = (
-        {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
+        {'id': 'cc1', 'name': 'CC1', 'desc': 'Control channel 1'},
+    )
+    optional_channels = (
+        {'id': 'cc2', 'name': 'CC2', 'desc': 'Control channel 2'},
     )
     options = (
         {'id': 'fulltext', 'desc': 'full text decoding of the packet',
@@ -222,122 +237,121 @@ class Decoder(srd.Decoder):
         ('raw-data', 'RAW binary data'),
     )
 
+    stored_pdos = {}
+
+
     def get_request(self, rdo):
         pos = (rdo >> 28) & 7
-        op_ma = ((rdo >> 10) & 0x3ff) * 10
-        max_ma = (rdo & 0x3ff) * 10
-        flags = ""
-        for f in RDO_FLAGS.keys():
+        op_ma = ((rdo >> 10) & 0x3ff) * 0.01
+        max_ma = (rdo & 0x3ff) * 0.01
+        flags = ''
+        for f in sorted(RDO_FLAGS.keys(), reverse = True):
             if rdo & f:
-                flags += " " + RDO_FLAGS[f]
-        return "[%d]%d/%d mA%s" % (pos, op_ma, max_ma, flags)
-
-    def get_source_cap(self, pdo):
-        t = (pdo >> 30) & 3
-        if t == 0:
-            mv = ((pdo >> 10) & 0x3ff) * 50
-            ma = ((pdo >> 0) & 0x3ff) * 10
-            p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
-        elif t == 1:
-            minv = ((pdo >> 10) & 0x3ff) * 50
-            maxv = ((pdo >> 20) & 0x3ff) * 50
-            mw = ((pdo >> 0) & 0x3ff) * 250
-            p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
-        elif t == 2:
-            minv = ((pdo >> 10) & 0x3ff) * 50
-            maxv = ((pdo >> 20) & 0x3ff) * 50
-            ma = ((pdo >> 0) & 0x3ff) * 10
-            p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
+                flags += ' [' + RDO_FLAGS[f] + ']'
+        if pos in self.stored_pdos.keys():
+            return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, self.stored_pdos[pos], op_ma, max_ma, flags)
         else:
-            p = ""
-        flags = ""
-        for f in PDO_FLAGS.keys():
+            return '(PDO #%d) %gA (operating) / %gA (max)%s' % (pos, op_ma, max_ma, flags)
+
+    def get_source_sink_cap(self, pdo, idx):
+        t1 = (pdo >> 30) & 3
+        if t1 == 0:
+            t_name = 'Fixed'
+            mv = ((pdo >> 10) & 0x3ff) * 0.05
+            ma = ((pdo >> 0) & 0x3ff) * 0.01
+            p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
+            self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
+        elif t1 == 1:
+            t_name = 'Battery'
+            minv = ((pdo >> 10) & 0x3ff) * 0.05
+            maxv = ((pdo >> 20) & 0x3ff) * 0.05
+            mw = ((pdo >> 0) & 0x3ff) * 0.25
+            p = '%g/%gV %gW' % (minv, maxv, mw)
+            self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
+        elif t1 == 2:
+            t_name = 'Variable'
+            minv = ((pdo >> 10) & 0x3ff) * 0.05
+            maxv = ((pdo >> 20) & 0x3ff) * 0.05
+            ma = ((pdo >> 0) & 0x3ff) * 0.01
+            p = '%g/%gV %gA' % (minv, maxv, ma)
+            self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
+        elif t1 == 3:
+            t2 = (pdo >> 28) & 3
+            if t2 == 0:
+                t_name = 'Programmable|PPS'
+                minv = ((pdo >> 8) & 0xff) * 0.1
+                maxv = ((pdo >> 17) & 0xff) * 0.1
+                ma = ((pdo >> 0) & 0xff) * 0.05
+                p = '%g/%gV %gA' % (minv, maxv, ma)
+                if (pdo >> 27) & 0x1:
+                    p += ' [limited]'
+                self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
+            else:
+                t_name = 'Reserved APDO: '+bin(t2)
+                p = '[raw: %s]' % (bin(pdo))
+                self.stored_pdos[idx] = '%s %s' % (t_name, p)
+        flags = ''
+        for f in sorted(PDO_FLAGS.keys(), reverse = True):
             if pdo & f:
-                flags += " " + PDO_FLAGS[f]
-        return "%s%s%s" % (PDO_TYPE[t], p, flags)
-
-    def get_sink_cap(self, pdo):
-        t = (pdo >> 30) & 3
-        if t == 0:
-            mv = ((pdo >> 10) & 0x3ff) * 50
-            ma = ((pdo >> 0) & 0x3ff) * 10
-            p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
-        elif t == 1:
-            minv = ((pdo >> 10) & 0x3ff) * 50
-            maxv = ((pdo >> 20) & 0x3ff) * 50
-            mw = ((pdo >> 0) & 0x3ff) * 250
-            p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
-        elif t == 2:
-            minv = ((pdo >> 10) & 0x3ff) * 50
-            maxv = ((pdo >> 20) & 0x3ff) * 50
-            ma = ((pdo >> 0) & 0x3ff) * 10
-            p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
-        else:
-            p = ""
-        flags = ""
-        for f in PDO_FLAGS.keys():
-            if pdo & f:
-                flags += " " + PDO_FLAGS[f]
-        return "%s%s%s" % (PDO_TYPE[t], p, flags)
+                flags += ' [' + PDO_FLAGS[f] + ']'
+        return '[%s] %s%s' % (t_name, p, flags)
 
     def get_vdm(self, idx, data):
         if idx == 0:    # VDM header
-                vid = data >> 16
-                struct = data & (1 << 15)
-                txt = "VDM"
-                if struct:  # Structured VDM
-                        cmd = data & 0x1f
-                        src = data & (1 << 5)
-                        ack = (data >> 6) & 3
-                        pos = (data >> 8) & 7
-                        ver = (data >> 13) & 3
-                        txt = VDM_ACK[ack] + " "
-                        txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else "cmd?"
-                        txt += " pos %d" % (pos) if pos else " "
-                else:   # Unstructured VDM
-                        txt = "unstruct [%04x]" % (data & 0x7fff)
-                txt += " SVID:%04x" % (vid)
+            vid = data >> 16
+            struct = data & (1 << 15)
+            txt = 'VDM'
+            if struct:  # Structured VDM
+                cmd = data & 0x1f
+                src = data & (1 << 5)
+                ack = (data >> 6) & 3
+                pos = (data >> 8) & 7
+                ver = (data >> 13) & 3
+                txt = VDM_ACK[ack] + ' '
+                txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
+                txt += ' pos %d' % (pos) if pos else ' '
+            else:   # Unstructured VDM
+                txt = 'unstruct [%04x]' % (data & 0x7fff)
+            txt += ' SVID:%04x' % (vid)
         else:   # VDM payload
-                txt = "VDO:%08x" % (data)
+            txt = 'VDO:%08x' % (data)
         return txt
 
     def get_bist(self, idx, data):
         mode = data >> 28
         counter = data & 0xffff
-        mode_name = BIST_MODES[mode] if mode in BIST_MODES else "INVALID"
+        mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
         if mode == 2:
-                mode_name = "Counter[= %d]" % (counter)
+            mode_name = 'Counter[= %d]' % (counter)
         # TODO check all 0 bits are 0 / emit warnings
-        return "mode %s" % (mode_name) if idx == 0 else "invalid BRO"
+        return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
 
     def putpayload(self, s0, s1, idx):
         t = self.head_type()
-        txt = "???"
+        txt = '['+str(idx+1)+'] '
         if t == 2:
-            txt = self.get_request(self.data[idx])
-        elif t == 1:
-            txt = self.get_source_cap(self.data[idx])
-        elif t == 4:
-            txt = self.get_sink_cap(self.data[idx])
+            txt += self.get_request(self.data[idx])
+        elif t == 1 or t == 4:
+            txt += self.get_source_sink_cap(self.data[idx], idx+1)
         elif t == 15:
-            txt = self.get_vdm(idx, self.data[idx])
+            txt += self.get_vdm(idx, self.data[idx])
         elif t == 3:
-            txt = self.get_bist(idx, self.data[idx])
+            txt += self.get_bist(idx, self.data[idx])
         self.putx(s0, s1, [11, [txt, txt]])
-        self.text += " - " + txt
+        self.text += ' - ' + txt
 
     def puthead(self):
         ann_type = 9 if self.head_power_role() else 10
-        role = "SRC" if self.head_power_role() else "SNK"
+        role = 'SRC' if self.head_power_role() else 'SNK'
         if self.head_data_role() != self.head_power_role():
-            role += "/DFP" if self.head_data_role() else "/UFP"
+            role += '/DFP' if self.head_data_role() else '/UFP'
         t = self.head_type()
         if self.head_count() == 0:
             shortm = CTRL_TYPES[t]
         else:
-            shortm = DATA_TYPES[t] if t in DATA_TYPES else "DAT???"
+            shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
 
-        longm = "{:s}[{:d}]:{:s}".format(role, self.head_id(), shortm)
+        longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
         self.putx(0, -1, [ann_type, [longm, shortm]])
         self.text += longm
 
@@ -366,7 +380,7 @@ class Decoder(srd.Decoder):
         self.putx(0, -1, [8, [longm, shortm]])
 
     def compute_crc32(self):
-        bdata = struct.pack("<H"+"I"*len(self.data), self.head & 0xffff,
+        bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
                             *tuple([d & 0xffffffff for d in self.data]))
         return zlib.crc32(bdata)
 
@@ -385,7 +399,7 @@ class Decoder(srd.Decoder):
         i = self.idx
         # Check it's not a truncated packet
         if len(self.bits) - i <= 20:
-            self.putwarn("Truncated", "!")
+            self.putwarn('Truncated', '!')
             return 0x0BAD
         k = [self.get_sym(i), self.get_sym(i+5),
              self.get_sym(i+10), self.get_sym(i+15)]
@@ -402,7 +416,7 @@ class Decoder(srd.Decoder):
     def find_corrupted_sop(self, k):
         # Start of packet are valid even if they have only 3 correct symbols
         # out of 4.
-        for seq in START_OF_PACKETS.keys():
+        for seq in SOP_SEQUENCES:
             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
                 return START_OF_PACKETS[seq]
         return None
@@ -411,7 +425,7 @@ class Decoder(srd.Decoder):
         for i in range(len(self.bits) - 19):
             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
-            sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
+            sym = START_OF_PACKETS.get(k, None)
             if not sym:
                 sym = self.find_corrupted_sop(k)
             # We have an interesting symbol sequence
@@ -424,33 +438,34 @@ class Decoder(srd.Decoder):
                 self.rec_sym(i+10, k[2])
                 self.rec_sym(i+15, k[3])
                 if sym == 'Hard Reset':
-                    self.text += "HRST"
+                    self.text += 'HRST'
                     return -1   # Hard reset
                 elif sym == 'Cable Reset':
-                    self.text += "CRST"
+                    self.text += 'CRST'
                     return -1   # Cable reset
                 else:
                     self.putx(i, i+20, [2, [sym, 'S']])
                 return i+20
         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
-        self.text += "Junk???"
-        self.putwarn("No start of packet found", "XXX")
+        self.text += 'Junk???'
+        self.putwarn('No start of packet found', 'XXX')
         return -1   # No Start Of Packet
 
-    def __init__(self, **kwargs):
+    def __init__(self):
+        self.reset()
+
+    def reset(self):
         self.samplerate = None
         self.idx = 0
         self.packet_seq = 0
-
-        self.samplenum = 0
         self.previous = 0
-        self.oldpins = [0]
         self.startsample = None
         self.bits = []
         self.edges = []
         self.bad = []
         self.half_one = False
         self.start_one = 0
+        self.stored_pdos = {}
 
     def metadata(self, key, value):
         if key == srd.SRD_CONF_SAMPLERATE:
@@ -470,21 +485,19 @@ class Decoder(srd.Decoder):
         )
 
     def us2samples(self, us):
-        if self.samplerate is None:
-            raise Exception("Need the samplerate.")
         return int(us * self.samplerate / 1000000)
 
     def decode_packet(self):
         self.data = []
         self.idx = 0
-        self.text = ""
+        self.text = ''
 
         if len(self.edges) < 50:
             return  # Not a real PD packet
 
         self.packet_seq += 1
         tstamp = float(self.startsample) / self.samplerate
-        self.text += "#%-4d (%8.6fms): " % (self.packet_seq, tstamp*1000)
+        self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
 
         self.idx = self.scan_eop()
         if self.idx < 0:
@@ -508,7 +521,7 @@ class Decoder(srd.Decoder):
         self.crc = self.get_word()
         ccrc = self.compute_crc32()
         if self.crc != ccrc:
-            self.putwarn("Bad CRC %08x != %08x" % (self.crc, ccrc), "CRC!")
+            self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
         self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
 
         # End of Packet
@@ -516,7 +529,7 @@ class Decoder(srd.Decoder):
             self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
             self.idx += 5
         else:
-            self.putwarn("No EOP", "EOP!")
+            self.putwarn('No EOP', 'EOP!')
         # Full text trace
         if self.options['fulltext'] == 'yes':
             self.putx(0, self.idx, [12, [self.text, '...']])
@@ -526,17 +539,14 @@ class Decoder(srd.Decoder):
         bitrate = self.samplerate*len(self.bits) / float(es - ss)
         self.put(es, ss, self.out_bitrate, int(bitrate))
         # Raw binary data (BMC decoded)
-        self.put(es, ss, self.out_binary, (0, bytes(self.bits)))
-
-    def decode(self, ss, es, data):
-        if self.samplerate is None:
-            raise Exception("Cannot decode without samplerate.")
-        for (self.samplenum, pins) in data:
-            # find edges ...
-            if self.oldpins == pins:
-                continue
+        self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
+
+    def decode(self):
+        if not self.samplerate:
+            raise SamplerateError('Cannot decode without samplerate.')
+        while True:
+            pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
 
-            self.oldpins, (cc, ) = pins, pins
 
             # First sample of the packet, just record the start date
             if not self.startsample: