/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  SWD Bypass Declaration
 *
 *  File: astris_swdbypass.cpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2019 Apple Inc. All rights reserved.
 *
 *  This document is the property of Apple Inc.
 *  It is considered confidential and proprietary.
 *
 *  This document may not be reproduced or transmitted in any form,
 *  in whole or in part, without the express written permission of
 *  Apple Inc.
 ********************************************************************* */

#ifndef astris_swdbypass_h
#define astris_swdbypass_h

#include "astris_api.h"
#include "bitfields.h"

#include <map>
#include <tuple>
#include <string>

class GatewaySetup_t;

class SWDBypass {
public:
    SWDBypass(AXchip_t *chip, AXap_t *ap);
    SWDBypass(AXconn_t *conn);

    virtual ~SWDBypass();

    /*!
     @function setup_bypass
     @discussion Sets up the PS, TCK and TMS pins.
     @param bypass_sel This is either the TMS line you need us to configure
     or you can give -1 as a special argument to enable all the TMS lines.
     @result An ASTRIS_ERR code
     */
    virtual int setup_bypass(int bypass_sel);

    /*!
     @function setup_no_force_pins
     @discussion Tell KIS-STM whether to set up the PS, TCK and TMS pins or not.
     @param no_force_pins True means skip setting up pins; false means set up the pins.
     @result An ASTRIS_ERR code
     */
    virtual int setup_no_force_pins(bool no_force_pins) {m_no_force_pins = no_force_pins; return ASTRIS_ERR_OK;}

    /*!
     @function enable_swd_bypass
     @discussion This function will do the first steps if setting up PS,TCK, TMS
     for a given bypass_sel just like setup_bypass but then it will also do the
     procedure of breaking the connection and bypassing to the given line
     @param bypass_sel The TMS line you want us to bypass to.
     @param force_pins If you want us to configure PS,TMS & TCK for you.
     @result An ASTRIS_ERR code
     */
    int enable_swd_bypass(uint32_t bypass_sel, bool force_pins = true);

    /*!
     @function set_breakseq
     @discussion Sets up the break sequence to use to break bypass;
     @param new_seq a 32bit integer that is the sequence we should write when bypassing.
     */
    void set_breakseq(uint32_t new_seq) {m_current_breq_seq = new_seq;}

    /*!
     @function get_bypass_device_on_select
     @discussion This is for future planing, where given boardid and bypass sel, we should
     be able to give a string name of the SoC we bypassing to. This could lead to a better
     user experience where user asks to bypass to an SoC rather than bypass_sel
     @param boardid Board ID of the SoC you are starting the bypass from
     @param bypass_sel The TMS line you want to bypass to
     @result string that is the SoC that lives on that line
     */
    virtual std::string get_bypass_device_on_select(uint32_t boardid, uint32_t bypass_sel) {return "Unknown";}

    /*!
     @function get_stm_bypass_setup
     @discussion Get a SWDBypass object for the particular SoC PID
     @param conn AXconn_t object
     @param pid PID for the main SoC
     @result SWDBypass object for the particular SoC, or NULL if no matching
     */
    static SWDBypass *get_stm_bypass_setup(AXconn_t *conn, uint32_t pid, uint32_t bid);

    /*!
     @function release_bypass_kis
     @discussion Cleans up and release KIS SWD control. This function should be called
     after setup_bypass is called on a KIS connection
     @param bypass_sel given by setup_bypass
     @result astris error code.
     */
    int release_bypass_kis(int bypass_sel);

    bool bypass_triggered() {return m_bypass_triggered;}

    void set_board_id(uint32_t bid);

    /*!
     @function setup_bypass_kis
     @discussion Configure KIS to acccess Bypass SWD. Sets up the PS, TCK and TMS pins.
     @param bypass_sel This is either the TMS line you need us to configure
     or you can give -1 as a special arugment to enable all the TMS lines.
     @param force_setup To force setup and skip hygiene checks like whether probe is KIS-STM
     @result An ASTRIS_ERR code
     */
    virtual int setup_bypass_kis(int bypass_sel, bool force_setup);

protected:
    int take_bypass_kis(int bypass_sel);
    int dump_swd_bypass_pin_config(uint32_t bypass_sel);

    virtual int set_bypass_channel_kis(int bypass_sel);

    /* This enables the GPIO blocks, aka powers them on using PS registers  */
    int enable_gpio_blocks(uint32_t bypass_sel);

    /* This sets each GPIO we need to act as TMS and TCK */
    int config_swd_bypass_pins(uint32_t bypass_sel);
    int config_all_pins();

    /* This will power up GPIO blocks using PS registers, configure GPIOs to act as TMS and TCK */
    int setup_swd_bypass_pins(uint32_t bypass_sel);

    virtual int enter_bypass(uint32_t bypass_sel);

    virtual uint32_t get_tgt_cfg(uint32_t tgt_sel);

    virtual bool check_fw_version();

    virtual AXmemaddr_t get_tgt_cfg_reg() = 0;
    virtual AXmemaddr_t get_tgt_en_reg()  = 0;

    bool use_stm();

    /*!
     @function read_memory
     @discussion Read a given address either via m_ap or if connected
     via STM, would be through m_sam.
     @param addr address to read
     @param reg_val reference to the value that will be filled out.
     @result astris error code.
     */
    int read_memory(AXmemaddr_t addr, uint32_t *reg_val);

    /*!
     @function write_memory
     @discussion Write a given value to the the address. Will use m_ap or if
     connected via STM, would be through m_sam.
     @param addr address to write to
     @param reg_val value to write
     @result astris error code.
     */
    int write_memory(AXmemaddr_t addr, uint32_t reg_val);

    /*!
     @function read_memory_dbgwrap
     @discussion Read a given address either via m_dbgap or if connected
     via STM, would be through m_lam.
     @param addr address to read, this is the system address. When using m_dbgap
     this value will be convered to APB address.
     @param reg_val reference to the value that will be filled out.
     @result astris error code.
     */
    int read_memory_dbgwrap(AXmemaddr_t addr, uint32_t *reg_val);

    /*!
     @function write_memory_dbgwrap
     @discussion Write a given value to the the address. Will use m_dbgap or if
     connected via STM, would be through m_lam.
     @param addr address to write to, this is the system address. When using m_dbgap
     this value will be convered to APB address.
     @param reg_val value to write
     @result astris error code.
     */
    int write_memory_dbgwrap(AXmemaddr_t addr, uint32_t reg_val);

    /* Some devices connected to SWD require that they are configured before talking to them */
    virtual int config_swd_bypass_device(uint32_t bypass_sel)  {return ASTRIS_ERR_OK;}
    virtual int cleanup_swd_bypass_device(uint32_t bypass_sel) {return ASTRIS_ERR_OK;}

    /*
        # GPIO configuration value
        #   GPIO_ST = 1'b1 (Schmitt triggering)
        #   DS[3:0] = 4'b0100
        #   IE = 1'b1
        #   FUNCSEL = 1'b1 (SWD alternate function)
     */
    const uint32_t K_GPIO_CONFIG_VAL_DEFAULT = 0x404220;
    const uint32_t K_GPIO_PS_VAL_DEFAULT     = 0x30F;

    /*
    Map:
        Key: Name of PS, should match what is used in get_gpios_tms
        Value: std::tuple
            0) Address of PS register
            1) Value of PS register
    */
    virtual std::map <std::string, std::tuple <AXmemaddr_t, uint32_t> > const &get_gpio_ps() = 0;

    /*
    Map:
        Key: Int that represents the tms
        Value: std::tuple
            0) Address of TMS
            1) Config value of TMS
            2) Name of TMS
            3) Name of PS register to hit for GPIO
            4) Name of TCK needed to set up for TMS
    */
    virtual std::map <int, std::tuple <AXmemaddr_t, uint32_t, std::string, std::string, std::string> > const &get_gpios_tms() = 0;

    /*
    Map:
        Key: Name of TCK, should match what is used in get_gpios_tms
        Value: std::tuple
            0) Address of TCK
            1) Config value of TCK
            2) Name of PS register to enable GPIO block for TCK (PS register will be empty string if both TCK and TMS belongs to same GPIO block
                                                                 because anyway we will enable GPIO block for TMS pin so it is redundant to do it again)
    */
    virtual std::map <std::string, std::tuple <AXmemaddr_t, uint32_t, std::string> > const &get_gpios_tck() = 0;

    AXchip_t *m_chip;
    AXconn_t *m_conn;
    AXap_t *m_ap;
    AXap_t *m_dbgap;
    uint32_t m_current_breq_seq;
    AXcsversion_t m_version;
    AXportal_t *m_sam;
    AXportal_t *m_lam;
    GatewaySetup_t *m_gateway_setup;
    bool m_bypass_triggered;
    uint32_t m_board_id;
    bool m_no_force_pins;

    const char *_log_key = "SWDBypass";

    static const uint32_t
        S_DBG_SWD_GPIO_TGT_CFG_BREAKTOKENSEQ   = 16,
        S_DBG_SWD_GPIO_TGT_CFG_DRIVEPARKBIT_EN = 4,
        S_DBG_SWD_GPIO_TGT_CFG_TARGETSEL       = 0;

    static const uint32_t
        M_DBG_SWD_GPIO_TGT_CFG_BREAKTOKENSEQ   = _MM_MAKEMASK(16, S_DBG_SWD_GPIO_TGT_CFG_BREAKTOKENSEQ),
        M_DBG_SWD_GPIO_TGT_CFG_DRIVEPARKBIT_EN = _MM_MAKEMASK(1, S_DBG_SWD_GPIO_TGT_CFG_DRIVEPARKBIT_EN),
        M_DBG_SWD_GPIO_TGT_CFG_TARGETSEL       = _MM_MAKEMASK(3, S_DBG_SWD_GPIO_TGT_CFG_TARGETSEL);
};

#endif /* astris_swdbypass_h */
