/*      $NetBSD: octeon_powvar.h,v 1.7 2020/06/23 05:15:33 simonb Exp $ */

/*
* Copyright (c) 2007 Internet Initiative Japan, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#ifndef _OCTEON_POWVAR_H_
#define _OCTEON_POWVAR_H_

#include <sys/cpu.h>

#include <mips/cavium/octeonreg.h>

#define POW_TAG_TYPE_ORDERED    0
#define POW_TAG_TYPE_ATOMIC     1
#define POW_TAG_TYPE_NULL       2
#define POW_TAG_TYPE_NULL_NULL  3

#define POW_TAG_OP_SWTAG                0
#define POW_TAG_OP_SWTAG_FULL           1
#define POW_TAG_OP_SWTAG_DESCHED        2
#define POW_TAG_OP_DESCHED              3
#define POW_TAG_OP_ADDWQ                4
#define POW_TAG_OP_UPD_WQP_GRP          5
#define POW_TAG_OP_CLR_NSCHED           7
#define POW_TAG_OP_NOP                  15

#define POW_WAIT        1
#define POW_NO_WAIT     0

#define POW_WORKQ_IRQ(group)            (group)

/* XXX */
struct octpow_softc {
       device_t                sc_dev;
       bus_space_tag_t         sc_regt;
       bus_space_handle_t      sc_regh;
       int                     sc_port;
       int                     sc_int_pc_base;
};

/* XXX */
struct octpow_attach_args {
       int                     aa_port;
       bus_space_tag_t         aa_regt;
};

void            octpow_config(struct octpow_softc *, int);
void            octpow_error_int_enable(void *, int);
uint64_t        octpow_error_int_summary(void *);
int             octpow_ring_reduce(void *);
int             octpow_ring_grow(void *);
int             octpow_ring_size(void);
int             octpow_ring_intr(void);

#define _POW_RD8(sc, off) \
       bus_space_read_8((sc)->sc_regt, (sc)->sc_regh, (off))
#define _POW_WR8(sc, off, v) \
       bus_space_write_8((sc)->sc_regt, (sc)->sc_regh, (off), (v))
#define _POW_GROUP_RD8(sc, pi, off) \
       bus_space_read_8((sc)->sc_regt, (sc)->sc_regh, \
           (off) + sizeof(uint64_t) * (pi)->pi_group)
#define _POW_GROUP_WR8(sc, pi, off, v) \
       bus_space_write_8((sc)->sc_regt, (sc)->sc_regh, \
           (off) + sizeof(uint64_t) * (pi)->pi_group, (v))

extern struct octpow_softc      octpow_softc;

/* -------------------------------------------------------------------------- */

/* Load Operations */

/* GET_WORK Loads */

static __inline uint64_t
octpow_ops_get_work_load(
       int wait)                       /* 0-1 */
{
       uint64_t ptr =
           OCTEON_ADDR_IO_DID(POW_MAJOR_DID, POW_OP_SUBDID_GET_WORK) |
           (wait ? POW_GET_WORK_LOAD_WAIT : 0);

       return octeon_xkphys_read_8(ptr);
}

/* IOBDMA Operations */

/* ``subdid'' values are inverted between ``get_work_addr'' and ``null_read_id'' */

/* The ``scraddr'' part is index in 8 byte words, not address. */

/* GET_WORK IOBDMAs */

static __inline void
octpow_ops_get_work_iobdma(
       int scraddr,                    /* 0-2047 */
       int wait)                       /* 0-1 */
{
       /* ``scraddr'' part is index in 64-bit words, not address */
       const int scrindex = scraddr / sizeof(uint64_t);

       uint64_t value = IOBDMA_CREATE(POW_MAJOR_DID,
           POW_IOBDMA_SUBDID_GET_WORK, scrindex, POW_IOBDMA_LEN,
           wait ? POW_IOBDMA_GET_WORK_WAIT : 0);

       octeon_iobdma_write_8(value);
}

/* NULL_RD IOBDMAs */

static __inline void
octpow_ops_null_rd_iobdma(
       int scraddr)                    /* 0-2047 */
{
       /* ``scraddr'' part is index in 64-bit words, not address */
       const int scrindex = scraddr / sizeof(uint64_t);

       uint64_t value = IOBDMA_CREATE(POW_MAJOR_DID,
           POW_IOBDMA_SUBDID_NULL_RD, scrindex, POW_IOBDMA_LEN, 0);

       octeon_iobdma_write_8(value);
}

/* Store Operations */

static __inline void
octpow_store(
       int subdid,                     /* 0, 1, 3 */
       uint64_t addr,                  /* 0-0x0000.000f.ffff.ffff */
       int no_sched,                   /* 0, 1 */
       int index,                      /* 0-8191 */
       int op,                         /* 0-15 */
       int qos,                        /* 0-7 */
       int grp,                        /* 0-7 */
       int type,                       /* 0-7 */
       uint32_t tag)                   /* 0-0xffff.ffff */
{
       /* Physical Address to Store to POW */
       uint64_t ptr = OCTEON_ADDR_IO_DID(POW_MAJOR_DID, subdid) |
           __SHIFTIN(addr, POW_PHY_ADDR_STORE_ADDR);

       /* Store Data on Store to POW */
       uint64_t args =
           __SHIFTIN(no_sched, POW_STORE_DATA_NO_SCHED) |
           __SHIFTIN(index, POW_STORE_DATA_INDEX) |
           __SHIFTIN(op, POW_STORE_DATA_OP) |
           __SHIFTIN(qos, POW_STORE_DATA_QOS) |
           __SHIFTIN(grp, POW_STORE_DATA_GRP) |
           __SHIFTIN(type, POW_STORE_DATA_TYPE) |
           __SHIFTIN(tag, POW_STORE_DATA_TAG);

       octeon_xkphys_write_8(ptr, args);
}

/* SWTAG */

static __inline void
octpow_ops_swtag(int type, uint32_t tag)
{

       octpow_store(
               POW_STORE_SUBDID_OTHER,
               0,                      /* addr (not used for SWTAG) */
               0,                      /* no_sched (not used for SWTAG) */
               0,                      /* index (not used for SWTAG) */
               POW_TAG_OP_SWTAG,       /* op == SWTAG */
               0,                      /* qos (not used for SWTAG) */
               0,                      /* grp (not used for SWTAG) */
               type,
               tag);
       /* switch to NULL completes immediately */
}

/* SWTAG_FULL */

static __inline void
octpow_ops_swtag_full(paddr_t addr, int grp, int type, uint32_t tag)
{

       octpow_store(
               POW_STORE_SUBDID_SWTAG_FULL,
               addr,
               0,                      /* no_sched (not used for SWTAG_FULL) */
               0,                      /* index (not used for SWTAG_FULL) */
               POW_TAG_OP_SWTAG_FULL,  /* op == SWTAG_FULL */
               0,                      /* qos (not used for SWTAG_FULL) */
               grp,
               type,
               tag);
}

/* SWTAG_DESCHED */

static __inline void
octpow_ops_swtag_desched(int no_sched, int grp, int type, uint32_t tag)
{

       octpow_store(
               POW_STORE_SUBDID_DESCHED,
               0,                      /* addr (not used for SWTAG_DESCHED) */
               no_sched,
               0,                      /* index (not used for SWTAG_DESCHED) */
               POW_TAG_OP_SWTAG_DESCHED, /* op == SWTAG_DESCHED */
               0,                      /* qos (not used for SWTAG_DESCHED) */
               grp,
               type,
               tag);
}

/* DESCHED */

static __inline void
octpow_ops_desched(int no_sched)
{

       octpow_store(
               POW_STORE_SUBDID_DESCHED,
               0,                      /* addr (not used for DESCHED) */
               no_sched,
               0,                      /* index (not used for DESCHED) */
               POW_TAG_OP_DESCHED,     /* op == DESCHED */
               0,                      /* qos (not used for DESCHED) */
               0,                      /* grp (not used for DESCHED) */
               0,                      /* type (not used for DESCHED) */
               0);                     /* tag (not used for DESCHED) */
}

/* ADDWQ */

static __inline void
octpow_ops_addwq(paddr_t addr, int qos, int grp, int type, uint32_t tag)
{

       octpow_store(
               POW_STORE_SUBDID_OTHER,
               addr,
               0,                      /* no_sched (not used for ADDWQ) */
               0,                      /* index (not used for ADDWQ) */
               POW_TAG_OP_ADDWQ,       /* op == ADDWQ */
               qos,
               grp,
               type,
               tag);
}

/* UPD_WQP_GRP */

static __inline void
octpow_ops_upd_wqp_grp(paddr_t addr, int grp)
{

       octpow_store(
               POW_STORE_SUBDID_OTHER,
               addr,
               0,                      /* no_sched (not used for UPD_WQP_GRP) */
               0,                      /* index (not used for UPD_WQP_GRP) */
               POW_TAG_OP_UPD_WQP_GRP, /* op == UPD_WQP_GRP */
               0,                      /* qos (not used for UPD_WQP_GRP) */
               grp,
               0,                      /* type (not used for UPD_WQP_GRP) */
               0);                     /* tag (not used for UPD_WQP_GRP) */
}

/* CLR_NSCHED */

static __inline void
octpow_ops_clr_nsched(paddr_t addr, int index)
{

       octpow_store(
               POW_STORE_SUBDID_OTHER,
               addr,
               0,                      /* no_sched (not used for CLR_NSCHED) */
               index,
               POW_TAG_OP_CLR_NSCHED,  /* op == CLR_NSCHED */
               0,                      /* qos (not used for CLR_NSCHED) */
               0,                      /* grp (not used for CLR_NSCHED) */
               0,                      /* type (not used for CLR_NSCHED) */
               0);                     /* tag (not used for CLR_NSCHED) */
}

/* NOP */

static __inline void
octpow_ops_nop(void)
{

       octpow_store(
               POW_STORE_SUBDID_OTHER,
               0,                      /* addr (not used for NOP) */
               0,                      /* no_sched (not used for NOP) */
               0,                      /* index (not used for NOP) */
               POW_TAG_OP_NOP,         /* op == NOP */
               0,                      /* qos (not used for NOP) */
               0,                      /* grp (not used for NOP) */
               0,                      /* type (not used for NOP) */
               0);                     /* tag (not used for NOP) */
}

/*
* Check if there is a pending POW tag switch.
*/
static __inline int
octpow_tag_sw_pending(void)
{
       int result;

       /*
        * "RDHWR rt, $30" returns:
        *      0 => pending bit is set
        *      1 => pending bit is clear
        */

       __asm volatile (
               "       .set    push\n"
               "       .set    noreorder\n"
               "       .set    arch=mips64r2\n"
               "       rdhwr   %0, $30\n"
               "       .set    pop\n"
               : "=r" (result));
       return result == 0;
}

/*
* Wait until there is no pending POW tag switch.
*/
static inline void
octpow_tag_sw_wait(void)
{
       while (octpow_tag_sw_pending())
               continue;
}

/* -------------------------------------------------------------------------- */

/*
* global functions
*/
static __inline void
octpow_work_request_async(uint64_t scraddr, uint64_t wait)
{

       octpow_ops_get_work_iobdma(scraddr, wait);
}

static __inline uint64_t *
octpow_work_response_async(uint64_t scraddr)
{
       uint64_t result;

       OCTEON_SYNCIOBDMA;
       result = octeon_cvmseg_read_8(scraddr);

       paddr_t addr = result & POW_IOBDMA_GET_WORK_RESULT_ADDR;

       if (result & POW_IOBDMA_GET_WORK_RESULT_NO_WORK)
           return NULL;
#ifdef __mips_n32
       KASSERT(addr < MIPS_PHYS_MASK);
       return (uint64_t *)MIPS_PHYS_TO_KSEG0(addr);
#else
       return (uint64_t *)MIPS_PHYS_TO_XKPHYS_CACHED(addr);
#endif
}

static __inline void
octpow_config_int_pc(struct octpow_softc *sc, int unit)
{
       uint64_t wq_int_pc;
       uint64_t pc_thr;
       static uint64_t cpu_clock_hz;

       if (cpu_clock_hz == 0)
               cpu_clock_hz  = curcpu()->ci_cpu_freq;

       /* from SDK */
       pc_thr = (cpu_clock_hz) / (unit * 16 * 256);

       wq_int_pc = __SHIFTIN(pc_thr, POW_WQ_INT_PC_PC_THR);
       _POW_WR8(sc, POW_WQ_INT_PC_OFFSET, wq_int_pc);
}

static __inline void
octpow_config_int_pc_rate(struct octpow_softc *sc, int rate)
{

       octpow_config_int_pc(sc, sc->sc_int_pc_base / rate);
}

#endif /* !_OCTEON_POWVAR_H_ */