##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2020 Raptor Engineering, LLC <support@raptorengineering.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU Affero General Public License as
## published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
##
## You should have received a copy of the GNU Affero General Public License
## along with this program.  If not, see <https://www.gnu.org/licenses/>.
##

import sigrokdecode as srd

# ...
fields = {
    # START field (indicates the start of a transaction)
    'START': {
        0b1: 'Start of FSI cycle',
    },
}

class Decoder(srd.Decoder):
    api_version = 3
    id = 'fsi'
    name = 'FSI'
    longname = 'Flexible Service Interface'
    desc = 'Protocol for FSI devices on Raptor OpenPOWER systems.'
    license = 'agplv3'
    inputs = ['logic']
    outputs = []
    tags = ['PC']
    channels = (
        {'id': 'data',  'name': 'DATA',  'desc': 'Frame', 'idn':'dec_fsi_chan_data'},
        {'id': 'clock', 'name': 'CLOCK', 'desc': 'Clock', 'idn':'dec_fsi_chan_clock'},
    )
    annotations = (
        ('warnings', 'Warnings'),
        ('start', 'Start'),
        ('cycle-type', 'Cycle type'),
        ('direction', 'Direction'),
        ('addr', 'Address'),
        ('data', 'Data'),
        ('commands', 'Commands'),
        ('crc', 'CRC'),
        ('turn-around', 'TAR'),
    )
    annotation_rows = (
        ('data', 'Data', (1, 2, 3, 4, 5, 6, 7, 8,)),
        ('warnings', 'Warnings', (0,)),
    )

    def __init__(self):
        self.tar_cycles = 3
        self.reset()

    def reset(self):
        self.state = 'IDLE'
        self.break_start_sample_number = 0
        self.break_counter = 0
        self.samplenum = 0
        self.samplenum_prev = 0
        self.fsi_data_prev = 0
        self.fsi_data_break_prev = 0
        self.crc_internal = 0
        self.response_received = 0
        self.crc_calculating = 0
        self.busy_seq_count = 0
        self.valid_response = 0
        self.prev_address = {}
        self.prev_address_valid = {0: False, 1: False, 2: False, 3: False}
        self.ss_block = None
        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 decode(self):
        while True:
            # FSI is clocked out on the falling edge and latched in on the rising edge of the clock...according to the specification.
            # That said, the specification is not clear on what this actually means (and it doesn't follow industry convention).
            # Real IBM POWER9 hardware is verified to work in the following mode:
            # FSI data being sent to the master is latched by the master logic at the falling edge of the FSI clock
            # FSI data being from to the master is strobed by the master logic at the falling edge of the FSI clock
            # FSI data being sent to the slave is latched by the slave logic at the rising edge of the FSI clock
            # FSI data being from to the slave is strobed by the slave logic at the rising edge of the FSI clock
            #
            # The above means that we have to be able to make a reasonable guess as to who is transmitting (the master or the slave)
            # in order to know which edge to sample on

            # Wait for either clock edge
            (data, clk) = self.wait({1: 'e'})

            # FSI data is electrically inverted
            fsi_data = not data
            fsi_clk = clk
            current_sample_number = self.samplenum

            # Detect BREAK commands
            # Note these can only be sent by the master, so sample on the rising clock edge only
            if (fsi_clk):
                if (self.fsi_data_break_prev == 1):
                    self.break_counter = self.break_counter + 1
                    if (self.break_counter == 256):
                        self.ss_block = self.break_start_sample_number
                        self.es_block = current_sample_number
                        self.putb([6, ['BREAK']])
                        self.state = 'BREAK_TAR_QUEUED'
                        self.busy_seq_count = 0
                        self.valid_response = 0
                        self.prev_address = {}
                        self.prev_address_valid = {0: False, 1: False, 2: False, 3: False}
                        self.ss_block = current_sample_number
                else:
                    if (self.break_counter > 256):
                        self.es_block = current_sample_number
                        self.putb([0, ['BREAK asserted in excess of specification cycles']])
                    self.break_start_sample_number = current_sample_number
                    self.break_counter = 0
                self.fsi_data_break_prev = fsi_data

            if ((self.state == 'TAR') or (self.state == 'RX_SLAVE_ID') or (self.state == 'RESPONSE') or (self.state == 'RX_DATA')
                or (self.state == 'RX_IPOLL_INTERRUPT_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD')
                or ((self.state == 'CRC') and (self.valid_response))):
                # Slave is / should be transmitting, sample on falling clock edge only
                if (fsi_clk):
                    continue
            else:
                # Master is / should be transmitting, sample on rising clock edge only
                if (not fsi_clk):
                    continue

            # Transfer state machine
            if (self.state == 'IDLE'):
                self.crc_internal = 0
                self.response_received = 0
                if (self.fsi_data_prev == 1):
                    self.tx_slave_id = 0
                    self.data_count = 2
                    self.ss_block = self.samplenum_prev
                    self.es_block = current_sample_number
                    self.putb([1, ['START']])
                    self.ss_block = current_sample_number
                    self.crc_calculating = 1
                    self.state = 'TX_SLAVE_ID'

            elif (self.state == 'TX_SLAVE_ID'):
                self.crc_calculating = 1
                if (self.data_count > 0):
                    self.tx_slave_id = (self.tx_slave_id >> 1) | (self.fsi_data_prev << 1)
                    self.data_count = self.data_count - 1
                    if (self.data_count == 0):
                        self.es_block = current_sample_number
                        self.putb([5, ['Slave ID: 0x%01x' % self.tx_slave_id]])
                        self.ss_block = current_sample_number
                        self.command_count = 0
                        self.command_code = 0
                        self.command = None
                        self.valid_command = False
                        self.state = 'COMMAND'

            elif (self.state == 'COMMAND'):
                self.crc_calculating = 1
                self.command_code = (self.command_code << 1) | self.fsi_data_prev
                self.command_count = self.command_count + 1
                if ((self.command_count == 3) and (self.command_code == 0b100)):
                    self.command = 'ABS_ADR'
                    self.valid_command = True
                elif ((self.command_count == 3) and (self.command_code == 0b101)):
                    self.command = 'REL_ADR'
                    self.valid_command = True
                elif ((self.command_count == 2) and (self.command_code == 0b11)):
                    self.command = 'SAME_ADR'
                    self.valid_command = True
                elif ((self.command_count == 3) and (self.command_code == 0b010)):
                    self.command = 'D_POLL'
                    self.valid_command = True
                elif ((self.command_count == 3) and (self.command_code == 0b011)):
                    self.command = 'E_POLL'
                    self.valid_command = True
                elif ((self.command_count == 3) and (self.command_code == 0b001)):
                    self.command = 'I_POLL'
                    self.valid_command = True
                if ((self.command_count > 7) or (self.valid_command == True)):
                    if (self.command_count == 8):
                        self.es_block = current_sample_number
                        self.putb([6, ['Invalid command code: 0x%02x/%d' % (self.command_code, self.command_count)]])
                        self.putb([0, ['%s' % 'Invalid command code']])
                        self.ss_block = current_sample_number
                        self.state = 'IDLE'
                    else:
                        self.es_block = current_sample_number
                        self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]])
                        self.ss_block = current_sample_number
                        if (self.command == 'ABS_ADR'):
                            self.address_length = 21
                            self.address_count = 0
                            self.address = 0
                            self.state = 'DIRECTION'
                        elif (self.command == 'REL_ADR'):
                            self.address_length = 8
                            self.address_count = 0
                            self.address = 0
                            self.state = 'DIRECTION'
                        elif (self.command == 'SAME_ADR'):
                            self.address_length = 2
                            self.address_count = 0
                            self.address = 0
                            self.state = 'DIRECTION'
                        elif (self.command == 'D_POLL'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.command == 'E_POLL'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.command == 'I_POLL'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        else:
                            self.state = 'IDLE'

            elif (self.state == 'DIRECTION'):
                self.crc_calculating = 1
                self.direction = self.fsi_data_prev
                self.es_block = current_sample_number
                if (self.direction == 1):
                    self.putb([3, ['Direction: %s' % 'Read']])
                else:
                    self.putb([3, ['Direction: %s' % 'Write']])
                self.ss_block = current_sample_number
                if (self.command == 'REL_ADR'):
                    self.state = 'REL_ADDRESS_SIGN'
                else:
                    self.state = 'ADDRESS'

            elif (self.state == 'REL_ADDRESS_SIGN'):
                self.crc_calculating = 1
                self.relative_address_negative = self.fsi_data_prev
                self.es_block = current_sample_number
                if (self.relative_address_negative == 1):
                    self.putb([5, ['Relative address sign: %s' % '(-)']])
                else:
                    self.putb([5, ['Relative address sign: %s' % '(+)']])
                self.ss_block = current_sample_number
                self.state = 'ADDRESS'

            elif (self.state == 'ADDRESS'):
                self.crc_calculating = 1
                self.address = (self.address << 1) | self.fsi_data_prev
                self.address_count = self.address_count + 1
                if (self.address_count >= self.address_length):
                    self.address_raw = self.address
                    if (self.prev_address_valid[self.tx_slave_id]):
                        if (self.command == 'SAME_ADR'):
                            self.address = (self.prev_address[self.tx_slave_id] & ~0b11) | (self.address_raw & 0b11)
                        elif (self.command == 'REL_ADR'):
                            if (self.relative_address_negative):
                                self.address = self.prev_address[self.tx_slave_id] - (0x100 - self.address_raw)
                            else:
                                self.address = self.prev_address[self.tx_slave_id] + self.address_raw
                    self.es_block = current_sample_number
                    if (((self.command == 'SAME_ADR') or (self.command == 'REL_ADR'))):
                        self.putb([5, ['Address: 0x%06x (0x%03x)' % (self.address, self.address_raw)]])
                        if (not self.prev_address_valid[self.tx_slave_id]):
                            self.putb([0, ['%s' % 'Base address for relative address not captured']])
                    else:
                        self.putb([5, ['Address: 0x%06x' % self.address]])
                    self.ss_block = current_sample_number
                    self.state = 'DATA_SIZE'

            elif (self.state == 'DATA_SIZE'):
                self.crc_calculating = 1
                if (self.direction and ((self.address_raw & 3) == 3) and self.fsi_data_prev):
                    # OpenFSI suffers from an unfortunate conflict between the SAME_ADR command
                    # and the TERM command.  Both start with 2'b11 and since both are variable
                    # length it is impossible to determine if a TERM command was sent until this
                    # point in the receiver process!
                    #
                    # Set correct command code for further processing
                    self.command_code = 0b111111
                    self.command_count = 6
                    self.command = 'TERM'
                    self.es_block = current_sample_number
                    self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]])
                    self.ss_block = current_sample_number
                    self.direction = 0
                    self.busy_seq_count = 0
                    self.crc = 0
                    self.crc_count = 0
                    self.state = 'CRC'
                else:
                    if (self.fsi_data_prev == 0):
                        self.data_size = 'BYTE'
                    else:
                        if ((self.address_raw & 3) == 1):
                            self.data_size = 'WORD'
                            # Force lowest address bits to specification-mandated values if required
                            self.address = (self.address & ~3) | 1
                        elif ((self.address_raw & 1) == 0):
                            self.data_size = 'HALF_WORD'
                            # Force lowest address bits to specification-mandated values if required
                            self.address = self.address & ~1
                        else:
                            self.data_size = 'UNKNOWN'
                    self.es_block = current_sample_number
                    if (self.data_size == 'UNKNOWN'):
                        self.putb([0, ['Data Size: %s' % 'UNKNOWN']])
                        self.state = 'IDLE'
                    else:
                        self.putb([3, ['Data Size: %s' % self.data_size]])
                        if (self.direction == 1):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        else:
                            self.data = 0
                            self.data_count = 0
                            if (self.data_size == 'BYTE'):
                                self.data_length = 8
                            elif (self.data_size == 'HALF_WORD'):
                                self.data_length = 16
                            elif (self.data_size == 'WORD'):
                                self.data_length = 32
                            else:
                                self.data_size = None
                            self.state = 'TX_DATA'
                self.ss_block = current_sample_number

            elif (self.state == 'TX_DATA'):
                self.crc_calculating = 1
                self.data = (self.data << 1) | self.fsi_data_prev
                self.data_count = self.data_count + 1
                if (self.data_count >= self.data_length):
                    self.es_block = current_sample_number
                    if (self.data_size == 'BYTE'):
                        self.putb([5, ['Data: 0x%02x' % self.data]])
                    elif (self.data_size == 'HALF_WORD'):
                        self.putb([5, ['Data: 0x%04x' % self.data]])
                    else:
                        self.putb([5, ['Data: 0x%08x' % self.data]])
                    self.ss_block = current_sample_number
                    self.crc = 0
                    self.crc_count = 0
                    self.state = 'CRC'

            elif (self.state == 'CRC'):
                if (self.crc_count == 0):
                    self.computed_crc_tx_end = self.crc_internal
                self.crc_calculating = 1
                self.crc = (self.crc << 1) | self.fsi_data_prev
                self.crc_count = self.crc_count + 1
                if (self.crc_count >= 4):
                    self.es_block = current_sample_number
                    if (self.crc == self.computed_crc_tx_end):
                        self.putb([7, ['CRC: 0x%01x (GOOD)' % self.crc]])
                        if (self.response_received):
                            if (((self.command == 'ABS_ADR') or (self.command == 'REL_ADR') or (self.command == 'SAME_ADR'))
                                and ((self.response == 'ACK_D') or (self.response == 'ACK'))):
                                self.prev_address[self.tx_slave_id] = self.address
                                self.prev_address_valid[self.tx_slave_id] = True
                    else:
                        self.putb([7, ['CRC: 0x%01x (BAD)' % self.crc]])
                        self.putb([0, ['%s' % 'Bad CRC']])
                    self.ss_block = current_sample_number
                    self.tar_timer = 0
                    self.state = 'TAR'
                    self.timeout_counter = 0

            elif (self.state == 'BREAK_TAR_QUEUED'):
                # Special case, since break operates outside of the main state machine
                # This state is a safe entry point into the main state machine
                self.tar_timer = 0
                self.state = 'BREAK_TAR'

            elif (self.state == 'BREAK_TAR'):
                self.tar_timer = self.tar_timer + 1
                if (self.tar_timer > self.tar_cycles):
                    self.crc_calculating = 0
                    self.crc_internal = 0
                    self.es_block = current_sample_number
                    self.putb([8, ['%s' % 'TAR']])
                    self.ss_block = current_sample_number
                    self.state = 'IDLE'

            elif (self.state == 'TAR'):
                self.crc_calculating = 0
                self.crc_internal = 0
                self.tar_timer = self.tar_timer + 1
                if (self.tar_timer > self.tar_cycles):
                    if (self.response_received == 1):
                        self.response_received = 0
                        if (self.rx_slave_id == self.tx_slave_id):
                            if (self.response == 'BUSY'):
                                self.busy_seq_count = self.busy_seq_count + 1
                            else:
                                self.busy_seq_count = 0
                            # Sequence complete
                            self.state = 'IDLE'
                    if (self.timeout_counter == 0):
                        self.es_block = self.samplenum_prev
                        self.putb([8, ['%s' % 'TAR']])
                        self.ss_block = current_sample_number
                    if (self.fsi_data_prev == 1):
                        self.crc_calculating = 1
                        self.rx_slave_id = 0
                        self.data_count = 2
                        if (self.state == 'IDLE'):
                            # Already processed response message, was going to IDLE state
                            self.state = 'TX_SLAVE_ID'
                        else:
                            self.state = 'RX_SLAVE_ID'
                        self.ss_block = self.samplenum_prev
                        self.es_block = current_sample_number
                        self.putb([1, ['START']])
                        self.ss_block = current_sample_number
                    else:
                        self.timeout_counter = self.timeout_counter + 1
                        if (self.timeout_counter >= 256):
                            self.es_block = current_sample_number
                            self.putb([8, ['%s' % 'Response timeout']])
                            self.putb([0, ['%s' % 'Response timeout']])
                            self.state = 'IDLE'

            elif (self.state == 'RX_SLAVE_ID'):
                self.crc_calculating = 1
                self.response_received = 1
                if (self.data_count > 0):
                    self.rx_slave_id = (self.rx_slave_id >> 1) | (self.fsi_data_prev << 1)
                    self.data_count = self.data_count - 1
                    if (self.data_count == 0):
                        self.es_block = current_sample_number
                        self.putb([5, ['Slave ID: 0x%01x' % self.rx_slave_id]])
                        if (self.rx_slave_id != self.tx_slave_id):
                            self.putb([0, ['%s' % 'Slave ID does not match active transaction']])
                        self.ss_block = current_sample_number
                        self.response_count = 0
                        self.response_code = 0
                        self.response = None
                        self.valid_response = False
                        self.state = 'RESPONSE'

            elif (self.state == 'RESPONSE'):
                self.crc_calculating = 1
                self.response_code = (self.response_code << 1) | self.fsi_data_prev
                self.response_count = self.response_count + 1
                if ((self.command == 'I_POLL') and (self.rx_slave_id == self.tx_slave_id) and (self.response_count == 1) and (self.response_code == 0b0)):
                    self.response = 'I_POLL_RSP'
                    self.valid_response = True
                elif ((self.response_count == 2) and (self.response_code == 0b00)):
                    if (self.direction == 1):
                        self.response = 'ACK_D'
                    else:
                        self.response = 'ACK'
                    self.valid_response = True
                elif ((self.response_count == 2) and (self.response_code == 0b01)):
                    self.response = 'BUSY'
                    self.valid_response = True
                elif ((self.response_count == 2) and (self.response_code == 0b10)):
                    self.response = 'ERR_A'
                    self.valid_response = True
                elif ((self.response_count == 2) and (self.response_code == 0b11)):
                    self.response = 'ERR_C'
                    self.valid_response = True
                if ((self.response_count > 2) or (self.valid_response == True)):
                    if (self.response_count == 8):
                        self.es_block = current_sample_number
                        self.putb([6, ['Invalid response code: 0x%02x/%d' % (self.response_code, self.response_count)]])
                        self.putb([0, ['%s' % 'Invalid response code']])
                        self.ss_block = current_sample_number
                        self.state = 'IDLE'
                    else:
                        self.es_block = current_sample_number
                        self.putb([6, ['Response: %s (0x%02x/%d)' % (self.response, self.response_code, self.response_count)]])
                        self.ss_block = current_sample_number
                        if (self.response == 'ACK_D'):
                            self.data = 0
                            self.data_count = 0
                            if (self.data_size == 'BYTE'):
                                self.data_length = 8
                            elif (self.data_size == 'HALF_WORD'):
                                self.data_length = 16
                            elif (self.data_size == 'WORD'):
                                self.data_length = 32
                            else:
                                self.data_size = None
                            #self.state = 'DIRECTION'
                            self.state = 'RX_DATA'
                        elif (self.response == 'ACK'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.response == 'BUSY'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.response == 'ERR_A'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.response == 'ERR_C'):
                            self.crc = 0
                            self.crc_count = 0
                            self.state = 'CRC'
                        elif (self.response == 'I_POLL_RSP'):
                            self.data = 0
                            self.data_count = 0
                            self.data_length = 2
                            self.state = 'RX_IPOLL_INTERRUPT_FIELD'
                        else:
                            self.state = 'IDLE'

            elif (self.state == 'RX_DATA'):
                self.crc_calculating = 1
                self.data = (self.data << 1) | self.fsi_data_prev
                self.data_count = self.data_count + 1
                if (self.data_count >= self.data_length):
                    self.es_block = current_sample_number
                    self.putb([5, ['Data: 0x%08x' % self.data]])
                    self.ss_block = current_sample_number
                    self.crc = 0
                    self.crc_count = 0
                    self.state = 'CRC'

            elif (self.state == 'RX_IPOLL_INTERRUPT_FIELD'):
                self.crc_calculating = 1
                self.data = (self.data << 1) | self.fsi_data_prev
                self.data_count = self.data_count + 1
                if (self.data_count >= self.data_length):
                    self.es_block = current_sample_number
                    self.putb([5, ['Interrupt Field: 0x%01x' % self.data]])
                    self.ss_block = current_sample_number
                    self.data = 0
                    self.data_count = 0
                    self.data_length = 3
                    self.state = 'RX_IPOLL_DMA_CONTROL_FIELD'

            elif (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD'):
                self.crc_calculating = 1
                self.data = (self.data << 1) | self.fsi_data_prev
                self.data_count = self.data_count + 1
                if (self.data_count >= self.data_length):
                    self.es_block = current_sample_number
                    self.putb([5, ['DMA Control Field: 0x%01x' % self.data]])
                    self.ss_block = current_sample_number
                    self.crc = 0
                    self.crc_count = 0
                    self.state = 'CRC'

            # CRC calculation
            # Implement Galios-type LFSR for polynomial 0x7 (MSB first)
            crc_prev = self.crc_internal
            if (self.crc_calculating):
                crc_feedback = (((crc_prev >> 3) & 1) ^ self.fsi_data_prev) & 1
            if (self.crc_calculating):
                self.crc_internal = (self.crc_internal & ~(1 << 0)) | ((crc_feedback & 1) << 0)
                self.crc_internal = (self.crc_internal & ~(1 << 1)) | ((((crc_prev & 1) ^ crc_feedback) & 1) << 1)
                self.crc_internal = (self.crc_internal & ~(1 << 2)) | (((((crc_prev >> 1) & 1) ^ crc_feedback) & 1) << 2)
                self.crc_internal = (self.crc_internal & ~(1 << 3)) | ((((crc_prev >> 2) & 1) & 1) << 3)

            self.fsi_data_prev = fsi_data
            self.samplenum_prev = current_sample_number
