# Ref: ARC® 700 External Interfaces Reference
# Document Number: 5117-014
# Accession: G00004
# Ref: Microchip MEC1618/MEC1618i Low Power 32-bit Microcontroller with Embedded Flash
# Document Number: DS00002339A
# Accession: G00005
# Ref: Microchip MEC1609 Mixed Signal Mobile Embedded Flash ARC EC BC-Link/VLPC Base Component
# Document Number: DS00002485A
# Accession: G00006

# The ARC JTAG TAP core has an important quirk: all transactions are initiated by a TCK pulse
# in Run-Test/Idle state:
#
#   The Run-Test/Idle state always precedes the Test-Logic-Reset, Update-DR and Update-IR states
#   on the rising edge of TCK when TMS is low. This state is employed to initiate a read/write
#   access or place the JTAG module in the idle state. The read/write access defined by the
#   address, data and command registers only occurs once on entry to Run-Test/Idle.

import logging

from glasgow.arch.jtag import DR_IDCODE
from glasgow.arch.arc import *
from glasgow.database.arc import devices
from glasgow.applet import GlasgowAppletError
from glasgow.applet.interface.jtag_probe import JTAGProbeApplet


__all__ = ["ARCDebugError", "ARCDebugInterface", "DebugARCApplet"]


class ARCDebugError(GlasgowAppletError):
    pass


class ARCDebugInterface:
    def __init__(self, interface, logger):
        self.lower   = interface
        self._logger = logger
        self._level  = logging.DEBUG if self._logger.name == __name__ else logging.TRACE

    def _log(self, message, *args):
        self._logger.log(self._level, "ARC: " + message, *args)

    async def identify(self):
        await self.lower.test_reset()
        idcode_bits = await self.lower.read_dr(32)
        idcode = DR_IDCODE.from_bits(idcode_bits)
        self._log("read IDCODE mfg_id=%03x arc_type=%02x arc_number=%03x",
                  idcode.mfg_id, idcode.part_id & 0b111111, idcode.part_id >> 6)
        device = devices[idcode.mfg_id, idcode.part_id & 0b111111]
        return idcode, device

    async def _wait_txn(self):
        await self.lower.write_ir(IR_STATUS)
        status = DR_STATUS()
        while not status.RD:
            status_bits = await self.lower.read_dr(4)
            status = DR_STATUS.from_bits(status_bits)
            self._log("status %s", status.bits_repr())
            if status.FL:
                raise ARCDebugError(f"transaction failed: {status.bits_repr()}")

    async def read(self, address, space):
        if space == "memory":
            dr_txn_command = DR_TXN_COMMAND_READ_MEMORY
        elif space == "core":
            dr_txn_command = DR_TXN_COMMAND_READ_CORE
        elif space == "aux":
            dr_txn_command = DR_TXN_COMMAND_READ_AUX
        else:
            assert False

        self._log("read %s address=%08x", space, address)
        dr_address = DR_ADDRESS(Address=address)
        await self.lower.write_ir(IR_ADDRESS)
        await self.lower.write_dr(dr_address.to_bits())
        await self.lower.write_ir(IR_TXN_COMMAND)
        await self.lower.write_dr(dr_txn_command)
        await self.lower.run_test_idle(1)
        await self._wait_txn()
        await self.lower.write_ir(IR_DATA)
        dr_data_bits = await self.lower.read_dr(32)
        dr_data = DR_DATA.from_bits(dr_data_bits)
        self._log("read data=%08x", dr_data.Data)
        return dr_data.Data

    async def write(self, address, data, space):
        if space == "memory":
            dr_txn_command = DR_TXN_COMMAND_WRITE_MEMORY
        elif space == "core":
            dr_txn_command = DR_TXN_COMMAND_WRITE_CORE
        elif space == "aux":
            dr_txn_command = DR_TXN_COMMAND_WRITE_AUX
        else:
            assert False

        self._log("write %s address=%08x data=%08x", space, address, data)
        dr_address = DR_ADDRESS(Address=address)
        await self.lower.write_ir(IR_ADDRESS)
        await self.lower.write_dr(dr_address.to_bits())
        await self.lower.write_ir(IR_DATA)
        dr_data = DR_DATA(Data=data)
        await self.lower.write_dr(dr_data.to_bits())
        await self.lower.write_ir(IR_TXN_COMMAND)
        await self.lower.write_dr(dr_txn_command)
        await self.lower.run_test_idle(1)
        await self._wait_txn()

    async def is_halted(self):
        status32 = AUX_STATUS32.from_int(await self.read(AUX_STATUS32_addr, space="aux"))
        return status32.H

    async def force_halt(self, read_modify_write=True):
        debug = AUX_DEBUG.from_int(
            (await self.read(AUX_DEBUG_addr, space="aux")) if read_modify_write else 0)
        debug.FH = 1
        await self.write(AUX_DEBUG_addr, debug.to_int(), space="aux")
        debug.FH = 0
        await self.write(AUX_DEBUG_addr, debug.to_int(), space="aux")

    async def set_halted(self, halted):
        current_halted = await self.is_halted()
        if bool(current_halted) == bool(halted):
            self._log(f"The ARC was requested to set halted={halted}, but it was already like that")
            return
        if halted:
            # According to the ARCompact Programmers Reference, using the FH(Force Halt) bit
            # to stop the processor is the correct way.
            await self.force_halt()
            if await self.is_halted():
                self._log(f"The ARC was halted successfully")
            else:
                raise ARCDebugError("Halting the ARC failed!")
        else:
            status32 = AUX_STATUS32.from_int(await self.read(AUX_STATUS32_addr, space="aux"))
            status32.H = 0
            await self.write(AUX_STATUS32_addr, status32.to_int(), space="aux")
            if not await self.is_halted():
                self._log(f"The ARC was unhalted successfully")
            else:
                raise ARCDebugError("Un halting the ARC failed!")


class DebugARCApplet(JTAGProbeApplet):
    logger = logging.getLogger(__name__)
    help = "debug ARC processors via JTAG"
    description = """
    Debug ARC processors via the JTAG interface.

    The list of supported devices is:

{devices}

    There is currently no debug server implemented. This applet only allows manipulating Memory,
    Core and Aux spaces via a Python REPL.
    """.format(
        devices="\n".join(f"    * {x.name}" for x in devices.values())
    )
    requires_tap = True

    async def setup(self, args):
        await super().setup(args)
        self.arc_iface = ARCDebugInterface(self.tap_iface, self.logger)

    async def run(self, args):
        idcode, device = await self.arc_iface.identify()
        if device is None:
            raise GlasgowAppletError(
                f"cannot operate on unknown device with IDCODE={idcode.to_int():08x}")
        self.logger.info("IDCODE=%08x device=%s rev=%d",
                         idcode.to_int(), device.name, idcode.version)
