summaryrefslogtreecommitdiff
path: root/decoders/lpc/pd.py
blob: 095c8b576f412a44be95e5d9a74e5554fab60a05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2012-2013 Uwe Hermann <uwe@hermann-uwe.de>
##
## 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
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## 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, see <http://www.gnu.org/licenses/>.
##

import sigrokdecode as srd

# ...
fields = {
    # START field (indicates start or stop of a transaction)
    'START': {
        0b0000: 'Start of cycle for a target',
        0b0001: 'Reserved',
        0b0010: 'Grant for bus master 0',
        0b0011: 'Grant for bus master 1',
        0b0100: 'Reserved',
        0b0101: 'Reserved',
        0b0110: 'Reserved',
        0b0111: 'Reserved',
        0b1000: 'Reserved',
        0b1001: 'Reserved',
        0b1010: 'Reserved',
        0b1011: 'Reserved',
        0b1100: 'Reserved',
        0b1101: 'Start of cycle for a Firmware Memory Read cycle',
        0b1110: 'Start of cycle for a Firmware Memory Write cycle',
        0b1111: 'Stop/abort (end of a cycle for a target)',
    },
    # Cycle type / direction field
    # Bit 0 (LAD[0]) is unused, should always be 0.
    # Neither host nor peripheral are allowed to drive 0b11x0.
    'CT_DR': {
        0b0000: 'I/O read',
        0b0010: 'I/O write',
        0b0100: 'Memory read',
        0b0110: 'Memory write',
        0b1000: 'DMA read',
        0b1010: 'DMA write',
        0b1100: 'Reserved / not allowed',
        0b1110: 'Reserved / not allowed',
    },
    # SIZE field (determines how many bytes are to be transferred)
    # Bits[3:2] are reserved, must be driven to 0b00.
    # Neither host nor peripheral are allowed to drive 0b0010.
    'SIZE': {
        0b0000: '8 bits (1 byte)',
        0b0001: '16 bits (2 bytes)',
        0b0010: 'Reserved / not allowed',
        0b0011: '32 bits (4 bytes)',
    },
    # CHANNEL field (bits[2:0] contain the DMA channel number)
    'CHANNEL': {
        0b0000: '0',
        0b0001: '1',
        0b0010: '2',
        0b0011: '3',
        0b0100: '4',
        0b0101: '5',
        0b0110: '6',
        0b0111: '7',
    },
    # SYNC field (used to add wait states)
    'SYNC': {
        0b0000: 'Ready',
        0b0001: 'Reserved',
        0b0010: 'Reserved',
        0b0011: 'Reserved',
        0b0100: 'Reserved',
        0b0101: 'Short wait',
        0b0110: 'Long wait',
        0b0111: 'Reserved',
        0b1000: 'Reserved',
        0b1001: 'Ready more (DMA only)',
        0b1010: 'Error',
        0b1011: 'Reserved',
        0b1100: 'Reserved',
        0b1101: 'Reserved',
        0b1110: 'Reserved',
        0b1111: 'Reserved',
    },
}

class Decoder(srd.Decoder):
    api_version = 3
    id = 'lpc'
    name = 'LPC'
    longname = 'Low Pin Count'
    desc = 'Protocol for low-bandwidth devices on PC mainboards.'
    license = 'gplv2+'
    inputs = ['logic']
    outputs = []
    tags = ['PC']
    channels = (
        {'id': 'lframe', 'name': 'LFRAME#', 'desc': 'Frame'},
        {'id': 'lclk',   'name': 'LCLK',    'desc': 'Clock'},
        {'id': 'lad0',   'name': 'LAD[0]',  'desc': 'Addr/control/data 0'},
        {'id': 'lad1',   'name': 'LAD[1]',  'desc': 'Addr/control/data 1'},
        {'id': 'lad2',   'name': 'LAD[2]',  'desc': 'Addr/control/data 2'},
        {'id': 'lad3',   'name': 'LAD[3]',  'desc': 'Addr/control/data 3'},
    )
    optional_channels = (
        {'id': 'lreset', 'name': 'LRESET#', 'desc': 'Reset'},
        {'id': 'ldrq',   'name': 'LDRQ#',   'desc': 'Encoded DMA / bus master request'},
        {'id': 'serirq', 'name': 'SERIRQ',  'desc': 'Serialized IRQ'},
        {'id': 'clkrun', 'name': 'CLKRUN#', 'desc': 'Clock run'},
        {'id': 'lpme',   'name': 'LPME#',   'desc': 'LPC power management event'},
        {'id': 'lpcpd',  'name': 'LPCPD#',  'desc': 'Power down'},
        {'id': 'lsmi',   'name': 'LSMI#',   'desc': 'System Management Interrupt'},
    )
    annotations = (
        ('warnings', 'Warnings'),
        ('start', 'Start'),
        ('cycle-type', 'Cycle-type/direction'),
        ('addr', 'Address'),
        ('tar1', 'Turn-around cycle 1'),
        ('sync', 'Sync'),
        ('data', 'Data'),
        ('tar2', 'Turn-around cycle 2'),
    )
    annotation_rows = (
        ('data', 'Data', (1, 2, 3, 4, 5, 6, 7)),
        ('warnings', 'Warnings', (0,)),
    )

    def __init__(self):
        self.reset()

    def reset(self):
        self.state = 'IDLE'
        self.oldlclk = -1
        self.lad = -1
        self.addr = 0
        self.cur_nibble = 0
        self.cycle_type = -1
        self.databyte = 0
        self.tarcount = 0
        self.synccount = 0
        self.oldpins = None
        self.ss_block = self.es_block = None

    def start(self):
        self.out_ann = self.register(srd.OUTPUT_ANN)

    def putb(self, data):
        self.put(self.ss_block, self.es_block, self.out_ann, data)

    def handle_get_start(self, lad, lad_bits, lframe):
        # LAD[3:0]: START field (1 clock cycle).

        # The last value of LAD[3:0] before LFRAME# gets de-asserted is what
        # the peripherals must use. However, the host can keep LFRAME# asserted
        # multiple clocks, and we output all START fields that occur, even
        # though the peripherals are supposed to ignore all but the last one.
        self.es_block = self.samplenum
        self.putb([1, [fields['START'][lad], 'START', 'St', 'S']])
        self.ss_block = self.samplenum

        # Output a warning if LAD[3:0] changes while LFRAME# is low.
        # TODO
        if (self.lad != -1 and self.lad != lad):
            self.putb([0, ['LAD[3:0] changed while LFRAME# was asserted']])

        # LFRAME# is asserted (low). Wait until it gets de-asserted again
        # (the host is allowed to keep it asserted multiple clocks).
        if lframe != 1:
            return

        self.start_field = self.lad
        self.state = 'GET CT/DR'

    def handle_get_ct_dr(self, lad, lad_bits):
        # LAD[3:0]: Cycle type / direction field (1 clock cycle).

        self.cycle_type = fields['CT_DR'].get(lad, 'Reserved / unknown')

        # TODO: Warning/error on invalid cycle types.
        if 'Reserved' in self.cycle_type:
            self.putb([0, ['Invalid cycle type (%s)' % lad_bits]])

        self.es_block = self.samplenum
        self.putb([2, ['Cycle type: %s' % self.cycle_type]])
        self.ss_block = self.samplenum

        self.state = 'GET ADDR'
        self.addr = 0
        self.cur_nibble = 0

    def handle_get_addr(self, lad, lad_bits):
        # LAD[3:0]: ADDR field (4/8/0 clock cycles).

        # I/O cycles: 4 ADDR clocks. Memory cycles: 8 ADDR clocks.
        # DMA cycles: no ADDR clocks at all.
        if self.cycle_type in ('I/O read', 'I/O write'):
            addr_nibbles = 4 # Address is 16bits.
        elif self.cycle_type in ('Memory read', 'Memory write'):
            addr_nibbles = 8 # Address is 32bits.
        else:
            addr_nibbles = 0 # TODO: How to handle later on?

        # Addresses are driven MSN-first.
        offset = ((addr_nibbles - 1) - self.cur_nibble) * 4
        self.addr |= (lad << offset)

        # Continue if we haven't seen all ADDR cycles, yet.
        if (self.cur_nibble < addr_nibbles - 1):
            self.cur_nibble += 1
            return

        self.es_block = self.samplenum
        s = 'Address: 0x%%0%dx' % addr_nibbles
        self.putb([3, [s % self.addr]])
        self.ss_block = self.samplenum

        self.state = 'GET TAR'
        self.tar_count = 0

    def handle_get_tar(self, lad, lad_bits):
        # LAD[3:0]: First TAR (turn-around) field (2 clock cycles).

        self.es_block = self.samplenum
        self.putb([4, ['TAR, cycle %d: %s' % (self.tarcount, lad_bits)]])
        self.ss_block = self.samplenum

        # On the first TAR clock cycle LAD[3:0] is driven to 1111 by
        # either the host or peripheral. On the second clock cycle,
        # the host or peripheral tri-states LAD[3:0], but its value
        # should still be 1111, due to pull-ups on the LAD lines.
        if lad_bits != '1111':
            self.putb([0, ['TAR, cycle %d: %s (expected 1111)' % \
                           (self.tarcount, lad_bits)]])

        if (self.tarcount != 1):
            self.tarcount += 1
            return

        self.tarcount = 0
        self.state = 'GET SYNC'

    def handle_get_sync(self, lad, lad_bits):
        # LAD[3:0]: SYNC field (1-n clock cycles).

        self.sync_val = lad_bits
        self.cycle_type = fields['SYNC'].get(lad, 'Reserved / unknown')

        # TODO: Warnings if reserved value are seen?
        if 'Reserved' in self.cycle_type:
            self.putb([0, ['SYNC, cycle %d: %s (reserved value)' % \
                           (self.synccount, self.sync_val)]])

        self.es_block = self.samplenum
        self.putb([5, ['SYNC, cycle %d: %s' % (self.synccount, self.sync_val)]])
        self.ss_block = self.samplenum

        # TODO

        self.cycle_count = 0
        self.state = 'GET DATA'

    def handle_get_data(self, lad, lad_bits):
        # LAD[3:0]: DATA field (2 clock cycles).

        # Data is driven LSN-first.
        if (self.cycle_count == 0):
            self.databyte = lad
        elif (self.cycle_count == 1):
            self.databyte |= (lad << 4)
        else:
            raise Exception('Invalid cycle_count: %d' % self.cycle_count)

        if (self.cycle_count != 1):
            self.cycle_count += 1
            return

        self.es_block = self.samplenum
        self.putb([6, ['DATA: 0x%02x' % self.databyte]])
        self.ss_block = self.samplenum

        self.cycle_count = 0
        self.state = 'GET TAR2'

    def handle_get_tar2(self, lad, lad_bits):
        # LAD[3:0]: Second TAR field (2 clock cycles).

        self.es_block = self.samplenum
        self.putb([7, ['TAR, cycle %d: %s' % (self.tarcount, lad_bits)]])
        self.ss_block = self.samplenum

        # On the first TAR clock cycle LAD[3:0] is driven to 1111 by
        # either the host or peripheral. On the second clock cycle,
        # the host or peripheral tri-states LAD[3:0], but its value
        # should still be 1111, due to pull-ups on the LAD lines.
        if lad_bits != '1111':
            self.putb([0, ['Warning: TAR, cycle %d: %s (expected 1111)'
                           % (self.tarcount, lad_bits)]])

        if (self.tarcount != 1):
            self.tarcount += 1
            return

        self.tarcount = 0
        self.state = 'IDLE'

    def decode(self):
        while True:
            # TODO: Come up with more appropriate self.wait() conditions.
            pins = self.wait()

            # If none of the pins changed, there's nothing to do.
            if self.oldpins == pins:
                continue

            # Store current pin values for the next round.
            self.oldpins = pins

            # Get individual pin values into local variables.
            (lframe, lclk, lad0, lad1, lad2, lad3) = pins[:6]
            (lreset, ldrq, serirq, clkrun, lpme, lpcpd, lsmi) = pins[6:]

            # Only look at the signals upon rising LCLK edges. The LPC clock
            # is the same as the PCI clock (which is sampled at rising edges).
            if not (self.oldlclk == 0 and lclk == 1):
                self.oldlclk = lclk
                continue

            # Store LAD[3:0] bit values (one nibble) in local variables.
            # Most (but not all) states need this.
            if self.state != 'IDLE':
                lad = (lad3 << 3) | (lad2 << 2) | (lad1 << 1) | lad0
                lad_bits = '{:04b}'.format(lad)
                # self.putb([0, ['LAD: %s' % lad_bits]])

            # TODO: Only memory read/write is currently supported/tested.

            # State machine
            if self.state == 'IDLE':
                # A valid LPC cycle starts with LFRAME# being asserted (low).
                if lframe != 0:
                    continue
                self.ss_block = self.samplenum
                self.state = 'GET START'
                self.lad = -1
            elif self.state == 'GET START':
                self.handle_get_start(lad, lad_bits, lframe)
            elif self.state == 'GET CT/DR':
                self.handle_get_ct_dr(lad, lad_bits)
            elif self.state == 'GET ADDR':
                self.handle_get_addr(lad, lad_bits)
            elif self.state == 'GET TAR':
                self.handle_get_tar(lad, lad_bits)
            elif self.state == 'GET SYNC':
                self.handle_get_sync(lad, lad_bits)
            elif self.state == 'GET DATA':
                self.handle_get_data(lad, lad_bits)
            elif self.state == 'GET TAR2':
                self.handle_get_tar2(lad, lad_bits)