/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbBootDsp_Common.c
    @brief  Common part of phStbBootDsp component.

@b  Component:  phStbBootDsp.

This file should not need changing when porting to a different OS.

*//*

Rev Date       Author      Comments
--------------------------------------------------------------------------------
  1 20070709   batelaan    Initial
  2 20070919   batelaan    Moved ASM bootcode to DSP kernel image.
  3 20070921   batelaan    Remove redundant code.
  4 20080118   hutchinson  Merge in EB's 20080117 BSP
4.1.1 20080422 batelaan    Update file header
  5 20080327   laird       Change types and move #define as needed elsewhere.
  6 20080404   batelaan    Bugfix MIPS2 starting. Use cached addresses.
  7 20080425   batelaan    Merge 4.1.1
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include <phStbBootDsp.h>
#include <phStbBootDsp_Internal.h>
#include <tmml.h>

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

/** Command line (default) for the kernel.
    Includes #PH_STBBOOTDSP_DFLT_CMDLINE_DEBUG. */
#define PH_STBBOOTDSP_DFLT_CMDLINE "console=ttyS1,115200" PH_STBBOOTDSP_DFLT_CMDLINE_DEBUG

/** Offset from base address (below which kernel should not access memory)
    to kernel argv array. */
#define PH_STBBOOTDSP_BASE_TO_ARGV_OFFSET 0x0A00UL

/** Offset from base address (below which kernel should not access memory)
    to top of stack. */
#define PH_STBBOOTDSP_BASE_TO_SP_OFFSET PH_STBBOOTDSP_BASE_TO_ARGV_OFFSET

#define PH_STBBOOTDSP_RESET_VECTOR 0xB7E0002CUL

#define MMIO(_x) (*((volatile UInt32 *)(_x)))

/** PI_CONTROL block, PIBC_CTRL register */
#define PH_STBBOOTDSP_MMIO_PIBC_CTRL 0xB7E00000UL

#define PH_STBBOOTDSP_MMIO_RESET_BLK 0xB7E08000UL
#define PH_STBBOOTDSP_MMIO_RESET_CTL (PH_STBBOOTDSP_MMIO_RESET_BLK + 0x0004UL)
#define PH_STBBOOTDSP_RESET_CPU_FLAG (1LU << 27U)

#define PH_STBBOOTDSP_MMIO_RESET_CONFIG (PH_STBBOOTDSP_MMIO_RESET_BLK + 0x0008UL)
#define PH_STBBOOTDSP_DSP_ENDIAN_MASK 0x10UL
#define PH_STBBOOTDSP_DSP_ENDIAN_LITTLE 0U
#define PH_STBBOOTDSP_DSP_ENDIAN_BIG PH_STBBOOTDSP_DSP_ENDIAN_MASK

/******************************************************************
* FUNCTION PROTOTYPES                    [p]ph<Module>_<Word>+    *
*******************************************************************/

static void StbBootDsp_StartDsp(ulong entry_uncached);

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                   [p]ph<Module>_<Word>+_t  *
*******************************************************************/

/*******************************************************************
* EXPORTED DATA                 g[k|p|kp|pk|kpk]ph<Module>_<Word>+ *
********************************************************************/

image_header_t phStbBootDsp_UImageHeader;

uint32_t phStbBootDsp_ImageLen;

pphStbBootDsp_Img_t phStbBootDsp_ImageTmpLoad;

#if PH_STBBOOTDSP_VERBOSE
int gphStbBootDsp_Verbose = 1;
#endif

/*******************************************************************
* LOCAL DATA                                                       *
********************************************************************/

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          tm[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

void phStbBootDsp_DspRun(Bool run)
{
    UInt32 flags;
    flags = MMIO(PH_STBBOOTDSP_MMIO_RESET_CTL);
    DBGPRINT(("phStbBootDsp: reset control at 0x%p was: 0x%08x\n", &MMIO(PH_STBBOOTDSP_MMIO_RESET_CTL), flags));
    flags &= 0x7FFFFFFF; // bit 31 is reserved, write 0.
    if (run == TM_FALSE)
    {
        flags &= ~PH_STBBOOTDSP_RESET_CPU_FLAG;
    }
    else
    {
        flags |= PH_STBBOOTDSP_RESET_CPU_FLAG;
    }
    MMIO(PH_STBBOOTDSP_MMIO_RESET_CTL) = flags;
    DBGPRINT(("phStbBootDsp: %s done\n", (run == TM_FALSE) ? "reset" : "start"));
}

/******************************************************************************/
/** Ensure the DSP's endianness is correct.
    It is set to the same endianness as the current CPU!. */
static void StbBootDsp_DspEndianSetup(void)
{
    UInt32 flags;

    flags = MMIO(PH_STBBOOTDSP_MMIO_RESET_CONFIG);
    //DBGPRINT(("phStbBootDsp: reset config at 0x%p was: 0x%08lx\n", &MMIO(PH_STBBOOTDSP_MMIO_RESET_CONFIG), flags));

    flags &= ~PH_STBBOOTDSP_DSP_ENDIAN_MASK;
#if TMFL_ENDIAN == TMFL_ENDIAN_BIG
    flags |= PH_STBBOOTDSP_DSP_ENDIAN_BIG;
#else
    flags |= PH_STBBOOTDSP_DSP_ENDIAN_LITTLE;
#endif

    MMIO(PH_STBBOOTDSP_MMIO_RESET_CONFIG) = flags;
}

/******************************************************************************/
Int phStbBootDsp_ProcessImageHeader(void)
{
    Int result = -1;

#if TMFL_ENDIAN != TMFL_ENDIAN_BIG
    /* The data in a uImage header is in network byte order (aka natural aka bigendian).
       Convert it if necessary. */
    phStbBootDsp_UImageHeader.ih_magic = ntohl(phStbBootDsp_UImageHeader.ih_magic);
    phStbBootDsp_UImageHeader.ih_hcrc  = ntohl(phStbBootDsp_UImageHeader.ih_hcrc);
    phStbBootDsp_UImageHeader.ih_time  = ntohl(phStbBootDsp_UImageHeader.ih_time);
    phStbBootDsp_UImageHeader.ih_size  = ntohl(phStbBootDsp_UImageHeader.ih_size);
    phStbBootDsp_UImageHeader.ih_load  = ntohl(phStbBootDsp_UImageHeader.ih_load);
    phStbBootDsp_UImageHeader.ih_ep    = ntohl(phStbBootDsp_UImageHeader.ih_ep);
    phStbBootDsp_UImageHeader.ih_dcrc  = ntohl(phStbBootDsp_UImageHeader.ih_dcrc);
#endif

#if PH_STBBOOTDSP_VERBOSE
    if (gphStbBootDsp_Verbose)
    {
        phStbBootDsp_UImageHeaderDump();
    }
#endif

    if (IH_MAGIC != phStbBootDsp_UImageHeader.ih_magic)
    {
        ERRPRINT((ERRPREFIX "phStbBootDsp: uImage magic incorrect\n"));
        goto DEPARTURES;
    }
    phStbBootDsp_ImageTmpLoad = (pphStbBootDsp_Img_t)ALIGN(phStbBootDsp_UImageHeader.ih_load + PH_STBBOOTDSP_TMP_LOAD_OFFSET,
                                                           PAGE_SIZE);
    DBGPRINT(("phStbBootDsp: tmp load addr = 0x%08lx\n", (ulong) phStbBootDsp_ImageTmpLoad));

    result = 0;

  DEPARTURES:
    return result;
}

/******************************************************************************/
Int StbBootDsp_UncompressImage(void)
{
    ulong uncompressed_len_max = PH_STBBOOTDSP_TMP_LOAD_OFFSET;
    ulong uncompressed_len = phStbBootDsp_ImageLen;

    switch (phStbBootDsp_UImageHeader.ih_comp) {
        case IH_COMP_GZIP:
            DBGPRINT(("phStbBootDsp: gunzip image...\n"));
            if (phStbBootDsp_Gunzip((void *)phStbBootDsp_UImageHeader.ih_load,
                                    uncompressed_len_max,
                                    (pphStbBootDsp_Img_t)phStbBootDsp_ImageTmpLoad, &uncompressed_len) != 0)
            {
                ERRPRINT((ERRPREFIX "StbBootDsp_UncompressImage: GUNZIP ERROR\n"));
                return -1;
            }
            tmmlCacheFlush(0, ~0);
            DBGPRINT(("phStbBootDsp: gunzip image to 0x%08lx (%lu) bytes DONE...\n",
                      uncompressed_len, uncompressed_len));
            break;

        default:
            ERRPRINT((ERRPREFIX "StbBootDsp: unsupported uImage compression format %d\n",
                      phStbBootDsp_UImageHeader.ih_comp));
            return -1;
    }
    return 0;
}

/******************************************************************************/
/** Uncompress the kernel image, set up boot prologue code, argc/argv
    etc, MIPS reset vector, etc, and release the DSP from reset.
    @param @b IN cmdline Command line to pass to the kernel image.
    See phStbBootDsp_BootDspFile().
    @return Negative errno on error, 0 on success.
 */
static Int StbBootDsp_BootDspLinuxImage(const char *cmdline)
{
    ulong entry_uncached;
    phStbBootDsp_BootKernelInfo_t *bootinfo = (phStbBootDsp_BootKernelInfo_t*)PH_STBBOOTDSP_DSP_INFO_ADDR;
    Char **argv;
    size_t cmdlineLen;

    bootinfo->baseUncached = UNCAC_ADDR(phStbBootDsp_UImageHeader.ih_load
        - PH_STBBOOTDSP_BASE_TO_LOAD_OFFSET);

    entry_uncached = UNCAC_ADDR((ulong)phStbBootDsp_UImageHeader.ih_ep);
    DBGPRINT(("phStbBootDsp: entry_uncached = 0x%lx\n", entry_uncached));

    argv = (void*)(bootinfo->baseUncached + PH_STBBOOTDSP_BASE_TO_ARGV_OFFSET);

    /* Setup argc and argv arguments for kernel entry routine: */
    bootinfo->argc = 2;
    bootinfo->argv = argv;
    argv[0] = NULL;
    /* Put first real argument after last NULL entry in argv array: */
    argv[1] = (Char*)&argv[3];
    strcpy(argv[1], cmdline);
    cmdlineLen = strlen(argv[1]);
    if (strstr(argv[1], "phStbNetDev.pool=") == NULL)
    {
        sprintf(argv[1] + cmdlineLen, " phStbNetDev.pool=0x%x", phStbNetDev_GetPoolPhysical());
        cmdlineLen = strlen(argv[1]);
    }
    DBGPRINT(("phStbBootDsp: argv[1] (kernel cmdline) = '%s'\n", argv[1]));
    argv[2] = NULL; /* last entry */

    /* Setup env argument for kernel entry routine: */
    bootinfo->env = (Char**)(argv[1] + cmdlineLen + 1);
    bootinfo->env = (Char**)ALIGN((ulong)bootinfo->env, sizeof(Char*));
    bootinfo->env[0] = NULL; /* last entry */

    bootinfo->tmp = UNCAC_ADDR(ALIGN(phStbBootDsp_UImageHeader.ih_load + PH_STBBOOTDSP_TMP_LOAD_OFFSET,
                                     PAGE_SIZE));

    bootinfo->booting = 0;

    bootinfo->sp = bootinfo->baseUncached + PH_STBBOOTDSP_BASE_TO_SP_OFFSET;
    DBGPRINT(("phStbBootDsp: setting kernel sp to 0x%08lx\n", bootinfo->sp));

    if (StbBootDsp_UncompressImage() != 0)
    {
        return -EIO;
    }

    StbBootDsp_DspEndianSetup();

    StbBootDsp_StartDsp(entry_uncached);
    return 0;
}

/******************************************************************************/

/** Starts the dsp.
    Sets up the reset vector, and releases it from reset.
    @param entry @b IN Execution entry point for the dsp we are starting.
*/
static void StbBootDsp_StartDsp(ulong entry_uncached)
{
    UInt32 flags;

    /* Enable ability to set start instructions: */
    flags = MMIO(PH_STBBOOTDSP_MMIO_PIBC_CTRL);
    flags |= 1UL << 5;
    MMIO(PH_STBBOOTDSP_MMIO_PIBC_CTRL) = flags;

    /* Setup reset vector: */
    DBGPRINT(("phStbBootDsp: reset vector set up (entry address = 0x%08lx) ...\n", entry_uncached));
    /* li $at, XXXXXXXX */
    MMIO(PH_STBBOOTDSP_RESET_VECTOR)     = 0x3c010000UL | (entry_uncached >> 16U);
    MMIO(PH_STBBOOTDSP_RESET_VECTOR + 4) = 0x34210000UL | (entry_uncached & 0x0000FFFFU);
    /* jr $at */
    MMIO(PH_STBBOOTDSP_RESET_VECTOR + 8) = 0x00200008UL;
    /* nop */
    MMIO(PH_STBBOOTDSP_RESET_VECTOR + 16) = 0U;

    DBGPRINT(("phStbBootDsp: reset vector set up done\n"));

    /* Release from reset. */
    phStbBootDsp_DspRun(TM_TRUE);
}

/******************************************************************************/
Int phStbBootDsp_BootDspFile(const char *file, const char *cmdline)
{
    Int result;

    /* Ensure DSP is reset first. We don't want to start writing into
     * an area that the DSP is currently running in! */
    phStbBootDsp_DspRun(TM_FALSE);

    result = phStbBootDsp_ReadImage(file);
    if (result != 0)
    {
        goto ERROR_RETURN;
    }
    result = StbBootDsp_BootDspLinuxImage(cmdline);
    if (result != 0)
    {
        goto ERROR_RETURN;
    }
    return 0;

  ERROR_RETURN:
    ERRPRINT((ERRPREFIX "Cannot boot DSP image \"%s\"\n", file));
    return result;
}

/******************************************************************************/

#if PH_STBBOOTDSP_VERBOSE
void phStbBootDsp_UImageHeaderDump(void)
{
    DBGPRINT(("%30s: %s\n", "Image Name", phStbBootDsp_UImageHeader.ih_name));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Header Magic Number", phStbBootDsp_UImageHeader.ih_magic, phStbBootDsp_UImageHeader.ih_magic));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Header CRC Checksum", phStbBootDsp_UImageHeader.ih_hcrc, phStbBootDsp_UImageHeader.ih_hcrc));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Creation Timestamp", phStbBootDsp_UImageHeader.ih_time, phStbBootDsp_UImageHeader.ih_time));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Data Size", phStbBootDsp_UImageHeader.ih_size, phStbBootDsp_UImageHeader.ih_size));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Data Load Address", phStbBootDsp_UImageHeader.ih_load, phStbBootDsp_UImageHeader.ih_load));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Entry Point Address", phStbBootDsp_UImageHeader.ih_ep, phStbBootDsp_UImageHeader.ih_ep));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Data CRC Checksum", phStbBootDsp_UImageHeader.ih_dcrc, phStbBootDsp_UImageHeader.ih_dcrc));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Operating System", phStbBootDsp_UImageHeader.ih_os, phStbBootDsp_UImageHeader.ih_os));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "CPU architecture", phStbBootDsp_UImageHeader.ih_arch, phStbBootDsp_UImageHeader.ih_arch));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Image Type", phStbBootDsp_UImageHeader.ih_type, phStbBootDsp_UImageHeader.ih_type));
    DBGPRINT(("%30s: 0x%08x (%u)\n", "Compression Type", phStbBootDsp_UImageHeader.ih_comp, phStbBootDsp_UImageHeader.ih_comp));
}
#endif

/******************************************************************************/
