/*********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris SPMI Driver Interface                   File: astris_spmi.h
 *
 *********************************************************************
 *
 *  Copyright (C) 2018-2018 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.
 ********************************************************************* */

#pragma once

#include <stdio.h>

#include "astris_api.h"

// Convert the register into a full address
#define SPMI_REG(r) (m_ctrl_base + (r))

// SpmiDriver_t
class SpmiDriver_t {
public:

    SpmiDriver_t(AXchip_t *chip, AXmemaddr_t ctrl_base);
    SpmiDriver_t(AXchip_t *chip, AXap_t *ap, AXmemaddr_t ctrl_base);

    virtual ~SpmiDriver_t() {}

    // There are many different types of SPMI comands. I'm choosing to only
    // implement a select few for now. It would require a considerable
    // amount of effort to implement all of the commands, and we don't
    // currently have a use for them.
    //       - Andrew

    /*!
     @function     erwl
     @discussion   Perform an 'Extended register write long' SMPI command.
     @param devaddr Device address of device to write
     @param regaddr Address of register to write
     @param val         Data to write to the slave
     @param cnt         Count of octets in val to write to regaddr, max of 8
     @result       astris error code
     @note         regaddr should be aligned to 4-bytes (regaddr &0x3 == 0)
     */
    int erwl(astris_spmi_dev_addr_t devaddr, astris_spmi_reg_addr_t regaddr, uint64_t val, uint8_t cnt);

    /*!
     @function         errl
     @discussion       Perform an 'Extended register read long' SPMI command.
     @param devaddr        Device address of device to read
     @param regaddr        Address of register to read
     @param reg_conts   Pointer to the contents of the register read
     @param cnt                Count of octets read from regaddr, max of 8
     @result          astris error code
     @note            regaddr should be aligned to 4-bytes (regaddr &0x3 == 0)
     */
    int errl(astris_spmi_dev_addr_t devaddr, astris_spmi_reg_addr_t regaddr, uint64_t &reg_conts, uint8_t cnt);

    const uint8_t MAX_SPMI_READ_SIZE  = 8;
    const uint8_t MAX_SPMI_WRITE_SIZE = 8;

    /*
    Field Name      Range  Len Val //Description = Config
    RSVD3           31:24  8   X   //Reserved
    GPIO_DS3_DS2    23:22  2   11  //GPIO Output Drive Strength = 12mA
    LOCK_BIT        21     1   0   //Security Lock-bit = not locked
    RSVD2           20:19  2   X   //Reserved
    INTR_GROUP_SEL  18:16  3   111 //Interrupt Group Select Index = Disabled
    RSVD1           15     1   X   //Reserved
    GPIO_ST         14     1   1   //Input Receiver mode = Schmitt
    GPIO_SL         13     1   1   //Slew Rate control = weak
    RSVD0           12     1   X   //Reserved
    GPIO_DS1_DS0    11:10  2   00  //GPIO Output Drive Strength Select (combine with bits 23:22)
    GPIO_IE         9      1   1   //GPIO Input Enable = Enabled
    GPIO_PS_PE      8:7    2   01  //Pull-Up/Down = Pull Down
    GPIO_FUNCSEL    6:4    3   010 //Function = Main Func
    GPIO_CFG        3:1    3   000 //Config = GP Input
    GPIO_DAT        0      1   0   //Data (in/out)
    */
    static constexpr uint32_t SPMI_GPIO_CONFIG_VALUE = 0xc762a0;

    // Manual PS [3:0] = run_max: 0xf Power on, clock running
    static constexpr uint8_t  SPMI_PS_PWRON_CLKRUN = 0x0000000f;

protected:

    // Initialize the driver
    virtual int _init();

    // Is there a response pending in the fault queue?
    int _is_fault_pending(bool &pending);

    // Is there a response pending in
    int _is_response_pending(bool &pending);

    // Checks for any faults that need to be addressed
    int _handle_fault();

    // Checks for a response to the command
    int _handle_response_errl(bool &success, uint64_t &reg_conts);

    // Checks for a response to the command
    int _handle_response_erwl(bool &success);

    const uint8_t MAX_ATTEMPTS = 10;

    // Various SPMI registers
    AXmemaddr_t RO_QUEUE_STATUS_FAULT;
    AXmemaddr_t RO_QUEUE_POP_FAULT;
    AXmemaddr_t RO_QUEUE_STATUS_SW;
    AXmemaddr_t WO_QUEUE_PUSH_SW;
    AXmemaddr_t RO_QUEUE_POP_SW;

    static const AXmemaddr_t QUEUE_SW_CONFIG_0_OFFSET = 0x80a0;

    const AXmemaddr_t RW_SW_QUEUE_ARB_LEVEL = 0x000;

    const AXmemaddr_t RW_SCLK_PAUSE_SLAVE_0 = 0x050;

    const AXmemaddr_t RO_QUEUE_STATUS_FAULT_DEFAULT    = 0xd00;
    const uint32_t M_RO_QUEUE_STATUS_FAULT_rsp_entries = 0x70000;
    const uint32_t S_RO_QUEUE_STATUS_FAULT_rsp_entries = 16;

    const AXmemaddr_t RO_QUEUE_POP_FAULT_DEFAULT   = 0xd04;
    const uint32_t M_RO_QUEUE_POP_FAULT_fault_type = 0xc0000000;
    const uint32_t S_RO_QUEUE_POP_FAULT_fault_type = 30;

    const AXmemaddr_t RO_QUEUE_STATUS_SW_0          = 0x400;
    const uint32_t M_RO_QUEUE_STATUS_SW_rsp_entries = 0x7f0000;
    const uint32_t S_RO_QUEUE_STATUS_SW_rsp_entries = 16;

    const AXmemaddr_t WO_QUEUE_PUSH_SW_0 = 0x404;

    const AXmemaddr_t RO_QUEUE_POP_SW_0 = 0x408;

    // SPMI command IDs
    const uint8_t CMD_EXTENDED_REGISTER_WRITE_LONG = 0x30;
    const uint8_t CMD_EXTENDED_REGISTER_READ_LONG  = 0x38;

    // SPMI Extended Register Write Long command fields
    const uint32_t S_CMD_ERWL_BYTE_COUNT = 0;
    const uint32_t M_CMD_ERWL_BYTE_COUNT = (0x7 << S_CMD_ERWL_BYTE_COUNT);
    //    const uint32_t S_CMD_ERWL_COMMAND    = 3;
    //    const uint32_t M_CMD_ERWL_COMMAND    = (0x6 << S_CMD_ERWL_COMMAND);
    const uint32_t S_CMD_ERWL_SLAVE_ID  = 8;
    const uint32_t M_CMD_ERWL_SLAVE_ID  = (0x7 << S_CMD_ERWL_SLAVE_ID);
    const uint32_t S_CMD_ERWL_SLAVE_ACK = 15;
    const uint32_t M_CMD_ERWL_SLAVE_ACK = (0x7 << S_CMD_ERWL_SLAVE_ACK);

    // SPMI Extended Register Read Long command fields
    const uint32_t S_CMD_ERRL_BYTE_COUNT = 0;
    const uint32_t M_CMD_ERRL_BYTE_COUNT = (0x7 << S_CMD_ERRL_BYTE_COUNT);
    //    const uint32_t S_CMD_ERRL_COMMAND    = 3;
    //    const uint32_t M_CMD_ERRL_COMMAND    = (0x7 << S_CMD_ERRL_COMMAND);
    const uint32_t S_CMD_ERRL_SLAVE_ID = 8;
    const uint32_t M_CMD_ERRL_SLAVE_ID = (0x7 << S_CMD_ERRL_SLAVE_ID);
    const uint32_t S_CMD_ERRL_PARITY   = 16;
    const uint32_t M_CMD_ERRL_PARITY   = (0xff << S_CMD_ERRL_PARITY);

    // SPMI fault types
    const uint8_t FAULT_EVENT_EXTERNAL_DEVICE_REQUEST = 0x00;
    const uint8_t FAULT_EVENT_SOC_REQUEST             = 0x01;
    const uint8_t FAULT_EVENT_ARBITRATION             = 0x02;
    const uint8_t FAULT_EVENT_ILLEGAL_ACCESS_QUEUE    = 0x03;

    // Log key for the class
    const char *_log_key = AXLOG_M_SPMI;

    // Chip handle to access other APs, if necessary
    AXchip_t *m_chip;
    // Primary AP to use to access the SPMI block
    AXap_t *m_ap;

    // SPMI block base address
    AXmemaddr_t m_ctrl_base;

    // Indicates whether the driver has been initialized
    bool is_initted = false;
};

/*
 @class SpmiDriverH14_t
 @discussion This driver is focused to provide SPMI support for H15+ SoCs that need access
 to NUB SPMI (bus0) software queues
 */
class SpmiDriverH14_t:
    public SpmiDriver_t {
public:

    SpmiDriverH14_t(AXchip_t *chip, AXmemaddr_t ctrl_base);
    SpmiDriverH14_t(AXchip_t *chip, AXap_t *ap, AXmemaddr_t ctrl_base);

    virtual ~SpmiDriverH14_t() {}

    static constexpr uint8_t NUM_SW_Q = 4;

    int set_sw_queue(uint8_t sw_q);
    int init_spmi(AXmemaddr_t spmi_ps, AXmemaddr_t gpio_ps, AXmemaddr_t spmi_sclk_gpio, AXmemaddr_t spmi_sdata_gpio);

protected:

    int _init();

    // NUB SPMI
    const AXmemaddr_t RO_QUEUE_STATUS_FAULT_VAL = 0x4000;
    const AXmemaddr_t RO_QUEUE_POP_FAULT_VAL    = 0x4008;

    static constexpr AXmemaddr_t RO_QUEUE_STATUS_SW_Q[NUM_SW_Q] = {
        0x08000,
        0x0c000,
        0x10000,
        0x14000,
    };
    static constexpr AXmemaddr_t WO_QUEUE_PUSH_SW_Q[NUM_SW_Q] = {
        0x08004,
        0x0c004,
        0x10004,
        0x14004,
    };
    static constexpr AXmemaddr_t RO_QUEUE_POP_SW_Q[NUM_SW_Q] = {
        0x08008,
        0x0c008,
        0x10008,
        0x14008,
    };

    AXmemaddr_t QUEUE_SW_CONFIG_0 = 0;

    // taken from ellis
    const uint32_t sw_queue_priority[4] = {6, 5, 4, 7};
};

