# Global variables ------------------------------------------------------------
set irl_riscv_list {
    "IRL1206FI"
}
set irl_target_arch_list {
    "riscv"
}
set irl_reset_mode_list {
    "auto"
    "auto_open_drain"
    "manual"
}
set irl_flash_mem_irl1206_list {
    "main"
    "info"
    "all"
}

set irl_targets_list  [list {*}$irl_riscv_list]
set irl_target        IRL1206FI
set irl_target_arch   riscv
set irl_reset_mode    auto
set irl_flash_mem     main
set irl_adapter_speed 400
set CHIPNAME          riscv

echo "-----------------------------------------"
echo "IRlabs recovery helper"
echo "Erases incorrect firmware in flash memory"
echo "Use \"-c help\" to get help"
echo "-----------------------------------------"

# Exported procedures ------------------------------------------------------------
proc help { } {
    echo "Usage:
openocd -f interface/<adapter>.cfg -f tools/irl_recovery.tcl -c \"<commands>*\"

Supported commands:
help                     - get this help, then exit
get_target_list          - print list supported targets with supported flash memories to erase, then exit
set_target <target>      - select target, default \"IRL1206FI\"
set_reset_mode <mode>    - select reset mode, default \"auto\"
                           - auto (slow halt, stable):    automatically resets the target, uses SRST push-pull mode if supported by debugger, otherwise uses open-drain mode
                           - auto_open_drain (slow halt): automatically resets the target, uses SRST open-drain mode
                           - manual (fast halt):          requires manually reset the target within 5 seconds after run script
set_adapter_speed <freq> - set JTAG clock frequency in kHz, default 400 kHz
erase_flash <flash_mem>  - erase selected flash memory. List of supported flash memories is displayed by \"get_target_list\" command

Note:    In the list of commands the \"erase_flash\" should be the last one
Warning: When using \"reset_mode = auto\", check that the board will not have a driver conflict on the SRST line, for example, a supervisor,
         otherwise use \"reset_mode = auto_open_drain\"

For example, to erase main flash with auto reset, run:
$ openocd -f interface/jlink.cfg -f tools/irl_recovery.tcl -c \"set_target IRL1206FI; erase_flash main\"\n"
    shutdown
}

proc get_target_list { } {
    echo "List of supported targets with supported flash memories to erase:"
    foreach target_name $::irl_targets_list {
        echo "- $target_name"
        if { $target_name eq "IRL1206FI"} {
            foreach flash_mem $::irl_flash_mem_irl1206_list { echo "  - $flash_mem" }
        }
    }
    echo ""
    shutdown
}

proc set_target { ::irl_target } { }

proc set_reset_mode { ::irl_reset_mode } { }

proc set_adapter_speed { ::irl_adapter_speed } { }

proc erase_flash { ::irl_flash_mem } {
    # Check and print parameters
    check_parameters
    print_parameters
    set_target_arch

    # Redirect log output to file for faster performance (${work_dir}/openocd_irl_recovery.log)
    log_output openocd_irl_recovery.log

    # Include target configuaration file
    set ::CHIPNAME $::irl_target_arch
    set USERESETHALT 0
    source [find target/$::irl_target.cfg]

    # Erase selected flash memory
    switch $::irl_flash_mem {
        main {
            puts stdout "Erase main flash: all sectors"
            flash erase_sector 0 0 last
        }
        info {
            puts stdout "Erase info flash: sector 0"
            flash erase_sector 1 0 0
        }
        all {
            puts stdout "Erase main flash: all sectors"
            flash erase_sector 0 0 last
            puts stdout "Erase info flash: sector 0"
            flash erase_sector 1 0 0
        }
        default {
            error "Incorrect flash mem: \"$::irl_flash_mem\""
        }
    }
    exit
}

# Private procedures for script --------------------------------------------------
proc set_target_arch { } {
    if { [lsearch $::irl_riscv_list $::irl_target] != -1} {
        set ::irl_target_arch riscv
    }
}

proc check_parameters { } {
    # Check target
    if { [lsearch $::irl_targets_list $::irl_target] == -1} {
        error "Incorrect target: \"$::irl_target\""
    }

    # Check reset_mode
    if { [lsearch $::irl_reset_mode_list $::irl_reset_mode] == -1} {
        error "Incorrect reset mode: \"$::irl_reset_mode\""
    }

    # Check flash_mem
    if { $::irl_target eq "IRL1206FI" } {
        if { [lsearch $::irl_flash_mem_irl1206_list $::irl_flash_mem] == -1} {
            error "Incorrect flash mem: \"$::irl_flash_mem\""
        }
    }
}

proc print_parameters { } {
    puts stdout "Target:        $::irl_target"
    puts stdout "Reset mode:    $::irl_reset_mode"
    puts stdout "Adapter speed: $::irl_adapter_speed kHz"
    puts stdout "Flash memory:  $::irl_flash_mem\n"
}

# Private procedures for target --------------------------------------------------
proc jtag_init { } {
    # Adapter configuration
    reset_config srst_gates_jtag srst_open_drain
    adapter srst delay 0
    adapter speed $::irl_adapter_speed

    # Allow JTAG chain enumeration
    jtag arp_init

    # Halt target according to selected reset_mode
    switch $::irl_reset_mode {
        auto {
            reset_config srst_gates_jtag srst_push_pull
            if { $::irl_target_arch eq "riscv" } {
                riscv_halt_auto_reset target_halt_status
            }
            reset_config srst_gates_jtag srst_open_drain
        }
        auto_open_drain {
            if { $::irl_target_arch eq "riscv" } {
                riscv_halt_auto_reset target_halt_status
            }
        }
        manual {
            if { $::irl_target_arch eq "riscv" } {
                riscv_halt_manual_reset target_halt_status
            }
        }
        default {
            error "Incorrect reset_mode: \"$::irl_reset_mode\""
        }
    }

    if { $target_halt_status } {
        puts stdout "Target is halted"
    } else {
        puts stdout "Target is not halted"
        shutdown
    }
}

proc riscv_halt_auto_reset { riscv_halt_status } {
    upvar $riscv_halt_status halt_status

    # Get path to SVF
    set svf_path [find tools/irl_riscv_halt.svf]

    for {set srst_delay_ms 0} {$srst_delay_ms < 10} {incr srst_delay_ms} {
        adapter srst delay $srst_delay_ms
        for {set halt_number 0} {$halt_number < 20} {incr halt_number} {
            # Assert and deassert the SRST (nRESET) line to reset the target
            adapter assert srst
            adapter deassert srst

            # Halt the target with SVF: sending one halt command
            svf $svf_path quiet

            # Get target halt status
            set halt_status [get_riscv_halt_status]
            if { $halt_status } {
                return
            }
        }
    }
}

proc riscv_halt_manual_reset { riscv_halt_status } {
    upvar $riscv_halt_status halt_status

    # Get path to SVF
    set svf_path [find tools/irl_riscv_halt_continuous.svf]

    # Halt the target with SVF: sending halt commands within 5 seconds (for 400 kHz)
    puts stdout "Reset the target! Assert and deassert the nRESET line, for example, short-press the RESET button"
    puts stdout "Sending halt commands within 5 seconds (for adapter speed 400 kHz)\n"
    svf $svf_path quiet

    # Get target halt status
    set halt_status [get_riscv_halt_status]
}

proc get_riscv_halt_status { } {
    # Select the dmi (0x11) JTAG DTM register
    irscan $::CHIPNAME.cpu 0x11

    # Read the dmstatus (0x11) DM register
    drscan $::CHIPNAME.cpu 41 0x4400000001
    runtest 7
    set dmstatus [drscan $::CHIPNAME.cpu 41 0x4400000000]

    # Check dmi.op == 0 (previous operation completed successfully)
    # and dmstatus.allhalted == 1 (all selected harts are halted)
    # and dmstatus.allunavail == 0 (all selected harts are available)
    if { [expr { ("0x$dmstatus" & 0x3) == 0 }] && [expr { ("0x$dmstatus" & 0x2200) == 0x200 }] } {
        return 1
    } else {
        return 0
    }
}

puts ""
