]> sigrok.org Git - libsigrokdecode.git/blob - decoders/ac97/pd.py
68eb955baa1e5204496d87ccf0d68adc4ebf1329
[libsigrokdecode.git] / decoders / ac97 / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2017 Gerhard Sittig <gerhard.sittig@gmx.net>
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
20 # This implementation is incomplete. TODO items:
21 # - Support the optional RESET# pin, detect cold and warm reset.
22 # - Split slot values into audio samples of their respective width and
23 #   frequency (either on user provided parameters, or from inspection of
24 #   decoded register access).
25
26 import sigrokdecode as srd
27
28 class ChannelError(Exception):
29     pass
30
31 class Pins:
32     (SYNC, BIT_CLK, SDATA_OUT, SDATA_IN, RESET) = range(5)
33
34 class Ann:
35     (
36         BITS_OUT, BITS_IN,
37         SLOT_OUT_RAW, SLOT_OUT_TAG, SLOT_OUT_ADDR, SLOT_OUT_DATA,
38         SLOT_OUT_03, SLOT_OUT_04, SLOT_OUT_05, SLOT_OUT_06,
39         SLOT_OUT_07, SLOT_OUT_08, SLOT_OUT_09, SLOT_OUT_10,
40         SLOT_OUT_11, SLOT_OUT_IO,
41         SLOT_IN_RAW, SLOT_IN_TAG, SLOT_IN_ADDR, SLOT_IN_DATA,
42         SLOT_IN_03, SLOT_IN_04, SLOT_IN_05, SLOT_IN_06,
43         SLOT_IN_07, SLOT_IN_08, SLOT_IN_09, SLOT_IN_10,
44         SLOT_IN_11, SLOT_IN_IO,
45         WARN, ERROR,
46     ) = range(32)
47     (
48         BIN_FRAME_OUT,
49         BIN_FRAME_IN,
50         BIN_SLOT_RAW_OUT,
51         BIN_SLOT_RAW_IN,
52     ) = range(4)
53
54 class Decoder(srd.Decoder):
55     api_version = 3
56     id = 'ac97'
57     name = "AC '97"
58     longname = "Audio Codec '97"
59     desc = 'Audio and modem control for PC systems.'
60     license = 'gplv2+'
61     inputs = ['logic']
62     outputs = []
63     tags = ['Audio', 'PC']
64     channels = (
65         {'id': 'sync', 'name': 'SYNC', 'desc': 'Frame synchronization'},
66         {'id': 'clk', 'name': 'BIT_CLK', 'desc': 'Data bits clock'},
67     )
68     optional_channels = (
69         {'id': 'out', 'name': 'SDATA_OUT', 'desc': 'Data output'},
70         {'id': 'in', 'name': 'SDATA_IN', 'desc': 'Data input'},
71         {'id': 'rst', 'name': 'RESET#', 'desc': 'Reset line'},
72     )
73     annotations = (
74         ('bit-out', 'Output bits'),
75         ('bit-in', 'Input bits'),
76         ('slot-out-raw', 'Output raw value'),
77         ('slot-out-tag', 'Output TAG'),
78         ('slot-out-cmd-addr', 'Output command address'),
79         ('slot-out-cmd-data', 'Output command data'),
80         ('slot-out-03', 'Output slot 3'),
81         ('slot-out-04', 'Output slot 4'),
82         ('slot-out-05', 'Output slot 5'),
83         ('slot-out-06', 'Output slot 6'),
84         ('slot-out-07', 'Output slot 7'),
85         ('slot-out-08', 'Output slot 8'),
86         ('slot-out-09', 'Output slot 9'),
87         ('slot-out-10', 'Output slot 10'),
88         ('slot-out-11', 'Output slot 11'),
89         ('slot-out-io-ctrl', 'Output I/O control'),
90         ('slot-in-raw', 'Input raw value'),
91         ('slot-in-tag', 'Input TAG'),
92         ('slot-in-sts-addr', 'Input status address'),
93         ('slot-in-sts-data', 'Input status data'),
94         ('slot-in-03', 'Input slot 3'),
95         ('slot-in-04', 'Input slot 4'),
96         ('slot-in-05', 'Input slot 5'),
97         ('slot-in-06', 'Input slot 6'),
98         ('slot-in-07', 'Input slot 7'),
99         ('slot-in-08', 'Input slot 8'),
100         ('slot-in-09', 'Input slot 9'),
101         ('slot-in-10', 'Input slot 10'),
102         ('slot-in-11', 'Input slot 11'),
103         ('slot-in-io-sts', 'Input I/O status'),
104         # TODO: Add more annotation classes:
105         # TAG: 'ready', 'valid', 'id', 'rsv'
106         # CMD ADDR: 'r/w', 'addr', 'unused'
107         # CMD DATA: 'data', 'unused'
108         # 3-11: 'data', 'unused', 'double data'
109         ('warning', 'Warning'),
110         ('error', 'Error'),
111     )
112     annotation_rows = (
113         ('bits-out', 'Output bits', (Ann.BITS_OUT,)),
114         ('slots-out-raw', 'Output numbers', (Ann.SLOT_OUT_RAW,)),
115         ('slots-out', 'Output slots', (
116             Ann.SLOT_OUT_TAG, Ann.SLOT_OUT_ADDR, Ann.SLOT_OUT_DATA,
117             Ann.SLOT_OUT_03, Ann.SLOT_OUT_04, Ann.SLOT_OUT_05, Ann.SLOT_OUT_06,
118             Ann.SLOT_OUT_07, Ann.SLOT_OUT_08, Ann.SLOT_OUT_09, Ann.SLOT_OUT_10,
119             Ann.SLOT_OUT_11, Ann.SLOT_OUT_IO,)),
120         ('bits-in', 'Input bits', (Ann.BITS_IN,)),
121         ('slots-in-raw', 'Input numbers', (Ann.SLOT_IN_RAW,)),
122         ('slots-in', 'Input slots', (
123             Ann.SLOT_IN_TAG, Ann.SLOT_IN_ADDR, Ann.SLOT_IN_DATA,
124             Ann.SLOT_IN_03, Ann.SLOT_IN_04, Ann.SLOT_IN_05, Ann.SLOT_IN_06,
125             Ann.SLOT_IN_07, Ann.SLOT_IN_08, Ann.SLOT_IN_09, Ann.SLOT_IN_10,
126             Ann.SLOT_IN_11, Ann.SLOT_IN_IO,)),
127         ('warnings', 'Warnings', (Ann.WARN,)),
128         ('errors', 'Errors', (Ann.ERROR,)),
129     )
130     binary = (
131         ('frame-out', 'Frame bits, output data'),
132         ('frame-in', 'Frame bits, input data'),
133         ('slot-raw-out', 'Raw slot bits, output data'),
134         ('slot-raw-in', 'Raw slot bits, input data'),
135         # TODO: Which (other) binary classes to implement?
136         # - Are binary annotations per audio slot useful?
137         # - Assume 20bit per slot, in 24bit units? Or assume 16bit
138         #   audio samples? Observe register access and derive width
139         #   of the audio data? Dump channels 3-11 or 1-12?
140     )
141
142     def putx(self, ss, es, cls, data):
143         self.put(ss, es, self.out_ann, [cls, data])
144
145     def putf(self, frombit, bitcount, cls, data):
146         ss = self.frame_ss_list[frombit]
147         es = self.frame_ss_list[frombit + bitcount]
148         self.putx(ss, es, cls, data)
149
150     def putb(self, frombit, bitcount, cls, data):
151         ss = self.frame_ss_list[frombit]
152         es = self.frame_ss_list[frombit + bitcount]
153         self.put(ss, es, self.out_binary, [cls, data])
154
155     def __init__(self):
156         self.reset()
157
158     def reset(self):
159         self.frame_ss_list = None
160         self.frame_slot_lens = [0, 16] + [16 + 20 * i for i in range(1, 13)]
161         self.frame_total_bits = self.frame_slot_lens[-1]
162         self.handle_slots = {
163             0: self.handle_slot_00,
164             1: self.handle_slot_01,
165             2: self.handle_slot_02,
166         }
167
168     def start(self):
169         self.out_binary = self.register(srd.OUTPUT_BINARY)
170         self.out_ann = self.register(srd.OUTPUT_ANN)
171
172     def metadata(self, key, value):
173         if key == srd.SRD_CONF_SAMPLERATE:
174             self.samplerate = value
175
176     def bits_to_int(self, bits):
177         # Convert MSB-first bit sequence to integer value.
178         if not bits:
179             return 0
180         count = len(bits)
181         value = sum([2 ** (count - 1 - i) for i in range(count) if bits[i]])
182         return value
183
184     def bits_to_bin_ann(self, bits):
185         # Convert MSB-first bit sequence to binary annotation data.
186         # It's assumed that the number of bits does not (in useful ways)
187         # fit into an integer, and we need to create an array of bytes
188         # from the data afterwards, anyway. Hence the separate routine
189         # and the conversion of eight bits each.
190         out = []
191         count = len(bits)
192         while count > 0:
193             count -= 8
194             by, bits = bits[:8], bits[8:]
195             by = self.bits_to_int(by)
196             out.append(by)
197         out = bytes(out)
198         return out
199
200     def int_to_nibble_text(self, value, bitcount):
201         # Convert number to hex digits for given bit count.
202         digits = (bitcount + 3) // 4
203         text = '{{:0{:d}x}}'.format(digits).format(value)
204         return text
205
206     def get_bit_field(self, data, size, off, count):
207         shift = size - off - count
208         data >>= shift
209         mask = (1 << count) - 1
210         data &= mask
211         return data
212
213     def flush_frame_bits(self):
214         # Flush raw frame bits to binary annotation.
215         anncls = Ann.BIN_FRAME_OUT
216         data = self.frame_bits_out[:]
217         count = len(data)
218         data = self.bits_to_bin_ann(data)
219         self.putb(0, count, anncls, data)
220
221         anncls = Ann.BIN_FRAME_IN
222         data = self.frame_bits_in[:]
223         count = len(data)
224         data = self.bits_to_bin_ann(data)
225         self.putb(0, count, anncls, data)
226
227     def start_frame(self, ss):
228         # Mark the start of a frame.
229         if self.frame_ss_list:
230             # Flush bits if we had a frame before the frame which is
231             # starting here.
232             self.flush_frame_bits()
233         self.frame_ss_list = [ss]
234         self.frame_bits_out = []
235         self.frame_bits_in = []
236         self.frame_slot_data_out = []
237         self.frame_slot_data_in = []
238         self.have_slots = {True: None, False: None}
239
240     def handle_slot_dummy(self, slotidx, bitidx, bitcount, is_out, data):
241         # Handle slot x, default/fallback handler.
242         # Only process data of slots 1-12 when slot 0 says "valid".
243         if not self.have_slots[is_out]:
244             return
245         if not self.have_slots[is_out][slotidx]:
246             return
247
248         # Emit a naive annotation with just the data bits that we saw
249         # for the slot (hex nibbles for density). For audio data this
250         # can be good enough. Slots with special meaning should not end
251         # up calling the dummy handler.
252         text = self.int_to_nibble_text(data, bitcount)
253         anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG
254         self.putf(bitidx, bitcount, anncls + slotidx, [text])
255
256         # Emit binary output for the data that is contained in slots
257         # which end up calling the default handler. This transparently
258         # should translate to "the slots with audio data", as other
259         # slots which contain management data should have their specific
260         # handler routines. In the present form, this approach might be
261         # good enough to get a (header-less) audio stream for typical
262         # setups where only line-in or line-out are in use.
263         #
264         # TODO: Improve this early prototype implementation. For now the
265         # decoder just exports the upper 16 bits of each audio channel
266         # that happens to be valid. For an improved implementation, it
267         # either takes user provided specs or more smarts like observing
268         # register access (if the capture includes it).
269         anncls = Ann.BIN_SLOT_RAW_OUT if is_out else Ann.BIN_SLOT_RAW_IN
270         data_bin = data >> 4
271         data_bin &= 0xffff
272         data_bin = data_bin.to_bytes(2, byteorder = 'big')
273         self.putb(bitidx, bitcount, anncls, data_bin)
274
275     def handle_slot_00(self, slotidx, bitidx, bitcount, is_out, data):
276         # Handle slot 0, TAG.
277         slotpos = self.frame_slot_lens[slotidx]
278         fieldoff = 0
279         anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG
280
281         fieldlen = 1
282         ready = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
283         text = ['READY: 1', 'READY', 'RDY', 'R'] if ready else ['ready: 0', 'rdy', '-']
284         self.putf(slotpos + fieldoff, fieldlen, anncls, text)
285         fieldoff += fieldlen
286
287         fieldlen = 12
288         valid = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
289         text = ['VALID: {:3x}'.format(valid), '{:3x}'.format(valid)]
290         self.putf(slotpos + fieldoff, fieldlen, anncls, text)
291         have_slots = [True] + [False] * 12
292         for idx in range(12):
293             have_slots[idx + 1] = bool(valid & (1 << (11 - idx)))
294         self.have_slots[is_out] = have_slots
295         fieldoff += fieldlen
296
297         fieldlen = 1
298         rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
299         if rsv != 0:
300             text = ['reserved bit error', 'rsv error', 'rsv']
301             self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
302         fieldoff += fieldlen
303
304         # TODO: Will input slot 0 have a Codec ID, or 3 reserved bits?
305         fieldlen = 2
306         codec = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
307         text = ['CODEC: {:1x}'.format(codec), '{:1x}'.format(codec)]
308         self.putf(slotpos + fieldoff, fieldlen, anncls, text)
309         fieldoff += fieldlen
310
311     def handle_slot_01(self, slotidx, bitidx, bitcount, is_out, data):
312         # Handle slot 1, command/status address.
313         slotpos = self.frame_slot_lens[slotidx]
314         if not self.have_slots[is_out]:
315             return
316         if not self.have_slots[is_out][slotidx]:
317             return
318         fieldoff = 0
319         anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG
320         anncls += slotidx
321
322         fieldlen = 1
323         if is_out:
324             is_read = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
325             text = ['READ', 'RD', 'R'] if is_read else ['WRITE', 'WR', 'W']
326             self.putf(slotpos + fieldoff, fieldlen, anncls, text)
327             # TODO: Check for the "atomic" constraint? Some operations
328             # involve address _and_ data, which cannot be spread across
329             # several frames. Slot 0 and 1 _must_ be provided within the
330             # same frame (the test should occur in the handler for slot
331             # 2 of course, in slot 1 we don't know what will follow).
332         else:
333             rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
334             if rsv != 0:
335                 text = ['reserved bit error', 'rsv error', 'rsv']
336                 self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
337         fieldoff += fieldlen
338
339         fieldlen = 7
340         regaddr = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
341         # TODO: Present 0-63 or 0-126 as the address of the 16bit register?
342         text = ['ADDR: {:2x}'.format(regaddr), '{:2x}'.format(regaddr)]
343         self.putf(slotpos + fieldoff, fieldlen, anncls, text)
344         if regaddr & 0x01:
345             text = ['odd register address', 'odd reg addr', 'odd addr', 'odd']
346             self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
347         fieldoff += fieldlen
348
349         # Strictly speaking there are 10 data request bits and 2 reserved
350         # bits for input slots, and 12 reserved bits for output slots. We
351         # test for 10 and 2 bits, to simplify the logic. Only in case of
352         # non-zero reserved bits for outputs this will result in "a little
353         # strange" an annotation. This is a cosmetic issue, we don't mind.
354         fieldlen = 10
355         reqdata = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
356         if is_out and reqdata != 0:
357             text = ['reserved bit error', 'rsv error', 'rsv']
358             self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
359         if not is_out:
360             text = ['REQ: {:3x}'.format(reqdata), '{:3x}'.format(reqdata)]
361             self.putf(slotpos + fieldoff, fieldlen, anncls, text)
362         fieldoff += fieldlen
363
364         fieldlen = 2
365         rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
366         if rsv != 0:
367             text = ['reserved bit error', 'rsv error', 'rsv']
368             self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
369         fieldoff += fieldlen
370
371     def handle_slot_02(self, slotidx, bitidx, bitcount, is_out, data):
372         # Handle slot 2, command/status data.
373         slotpos = self.frame_slot_lens[slotidx]
374         if not self.have_slots[is_out]:
375             return
376         if not self.have_slots[is_out][slotidx]:
377             return
378         fieldoff = 0
379         anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG
380         anncls += slotidx
381
382         fieldlen = 16
383         rwdata = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
384         # TODO: Check for zero output data when the operation is a read.
385         # TODO: Check for the "atomic" constraint.
386         text = ['DATA: {:4x}'.format(rwdata), '{:4x}'.format(rwdata)]
387         self.putf(slotpos + fieldoff, fieldlen, anncls, text)
388         fieldoff += fieldlen
389
390         fieldlen = 4
391         rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen)
392         if rsv != 0:
393             text = ['reserved bits error', 'rsv error', 'rsv']
394             self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text)
395         fieldoff += fieldlen
396
397     # TODO: Implement other slots.
398     # - 1: cmd/status addr (check status vs command)
399     # - 2: cmd/status data (check status vs command)
400     # - 3-11: audio out/in
401     # - 12: io control/status (modem GPIO(?))
402
403     def handle_slot(self, slotidx, data_out, data_in):
404         # Process a received slot of a frame.
405         func = self.handle_slots.get(slotidx, self.handle_slot_dummy)
406         bitidx = self.frame_slot_lens[slotidx]
407         bitcount = self.frame_slot_lens[slotidx + 1] - bitidx
408         if data_out is not None:
409             func(slotidx, bitidx, bitcount, True, data_out)
410         if data_in is not None:
411             func(slotidx, bitidx, bitcount, False, data_in)
412
413     def handle_bits(self, ss, es, bit_out, bit_in):
414         # Process a received pair of bits.
415         # Emit the bits' annotations. Only interpret the data when we
416         # are in a frame (have seen the start of the frame, and don't
417         # exceed the expected number of bits in a frame).
418         if bit_out is not None:
419             self.putx(ss, es, Ann.BITS_OUT, ['{:d}'.format(bit_out)])
420         if bit_in is not None:
421             self.putx(ss, es, Ann.BITS_IN, ['{:d}'.format(bit_in)])
422         if self.frame_ss_list is None:
423             return
424         self.frame_ss_list.append(es)
425         have_len = len(self.frame_ss_list) - 1
426         if have_len > self.frame_total_bits:
427             return
428
429         # Accumulate the bits within the frame, until one slot of the
430         # frame has become available.
431         slot_idx = 0
432         if bit_out is not None:
433             self.frame_bits_out.append(bit_out)
434             slot_idx = len(self.frame_slot_data_out)
435         if bit_in is not None:
436             self.frame_bits_in.append(bit_in)
437             slot_idx = len(self.frame_slot_data_in)
438         want_len = self.frame_slot_lens[slot_idx + 1]
439         if have_len != want_len:
440             return
441         prev_len = self.frame_slot_lens[slot_idx]
442
443         # Convert bits to integer values. This shall simplify extraction
444         # of bit fields in multiple other locations.
445         slot_data_out = None
446         if bit_out is not None:
447             slot_bits = self.frame_bits_out[prev_len:]
448             slot_data = self.bits_to_int(slot_bits)
449             self.frame_slot_data_out.append(slot_data)
450             slot_data_out = slot_data
451         slot_data_in = None
452         if bit_in is not None:
453             slot_bits = self.frame_bits_in[prev_len:]
454             slot_data = self.bits_to_int(slot_bits)
455             self.frame_slot_data_in.append(slot_data)
456             slot_data_in = slot_data
457
458         # Emit simple annotations for the integer values, until upper
459         # layer decode stages will be implemented.
460         slot_len = have_len - prev_len
461         slot_ss = self.frame_ss_list[prev_len]
462         slot_es = self.frame_ss_list[have_len]
463         if slot_data_out is not None:
464             slot_text = self.int_to_nibble_text(slot_data_out, slot_len)
465             self.putx(slot_ss, slot_es, Ann.SLOT_OUT_RAW, [slot_text])
466         if slot_data_in is not None:
467             slot_text = self.int_to_nibble_text(slot_data_in, slot_len)
468             self.putx(slot_ss, slot_es, Ann.SLOT_IN_RAW, [slot_text])
469
470         self.handle_slot(slot_idx, slot_data_out, slot_data_in)
471
472     def decode(self):
473         have_sdo = self.has_channel(Pins.SDATA_OUT)
474         have_sdi = self.has_channel(Pins.SDATA_IN)
475         if not have_sdo and not have_sdi:
476             raise ChannelError('Either SDATA_OUT or SDATA_IN (or both) are required.')
477         have_reset = self.has_channel(Pins.RESET)
478
479         # Data is sampled at falling CLK edges. Annotations need to span
480         # the period between rising edges. SYNC rises one cycle _before_
481         # the start of a frame. Grab the earliest SYNC sample we can get
482         # and advance to the start of a bit time. Then keep getting the
483         # samples and the end of all subsequent bit times.
484         prev_sync = [None, None, None]
485         pins = self.wait({Pins.BIT_CLK: 'e'})
486         if pins[Pins.BIT_CLK] == 0:
487             prev_sync[-1] = pins[Pins.SYNC]
488             pins = self.wait({Pins.BIT_CLK: 'r'})
489         bit_ss = self.samplenum
490         while True:
491             pins = self.wait({Pins.BIT_CLK: 'f'})
492             prev_sync.pop(0)
493             prev_sync.append(pins[Pins.SYNC])
494             self.wait({Pins.BIT_CLK: 'r'})
495             if prev_sync[0] == 0 and prev_sync[1] == 1:
496                 self.start_frame(bit_ss)
497             self.handle_bits(bit_ss, self.samplenum,
498                     pins[Pins.SDATA_OUT] if have_sdo else None,
499                     pins[Pins.SDATA_IN] if have_sdi else None)
500             bit_ss = self.samplenum