/*	$NetBSD: ace.c,v 1.5 2021/07/24 21:31:32 andvar Exp $	*/

/*-
 * Copyright (c) 2010 The NetBSD Foundation, Inc.
 * Copyright (c) 1999 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code was written by Alessandro Forin and Neil Pittman
 * at Microsoft Research and contributed to The NetBSD Foundation
 * by Microsoft Corporation.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/* --------------------------------------------------------------------------
 *
 * Module:
 *
 *    ace.c
 *
 * Purpose:
 *
 *    Driver for the Xilinx System ACE CompactFlash Solution
 *
 * Author:
 *    A. Forin (sandrof)
 *
 * References:
 *    "System ACE CompactFlash Solution", Advance Product Specification
 *    Document DS080 Version 1.5 April 5, 2002.  Xilinx Corp.
 *    Available at http://www.xilinx.com
 *
 *    "CF+ and CompactFlash Specification", Revision 4.1, 02/16/2007.
 *    CompactFlash Association.
 *    Available at http://www.compactflash.org
 * --------------------------------------------------------------------------
 */

#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include <machine/emipsreg.h>

#include <sys/param.h>
#include <sys/disklabel.h>
#include <sys/endian.h>

#include "common.h"
#include "ace.h"
#include "start.h"

#define NSAC 2
#define SAC0 ((struct _Sac  *)IDE_DEFAULT_ADDRESS)
#define SAC1 ((struct _Sac  *)(IDE_DEFAULT_ADDRESS+256))

#define CF_SECBITS     9
#define CF_SECTOR_SIZE (1 << CF_SECBITS)


/* Error codes 
 */
#define FAILED(x)           (x < 0)
#define S_OK                (0)
#define E_INVALID_PARAMETER (-1)
#define E_DISK_RESET_FAILED (-2)
#define E_NO_MEDIA_IN_DRIVE (-3)
#define E_TIMED_OUT         (-4)

/* Utilities
 */
#if defined(DEBUG)
int acedebug = 2;
#define DBGME(lev,x) \
	do \
		if (lev >= acedebug) { \
			x; \
		} \
	while (/*CONSTCOND*/0)
#else
#define DBGME(lev,x) 
#endif

#if defined(DEBUG)
typedef char *NAME;
typedef struct _REGDESC {
    NAME RegisterName;
    NAME BitNames[32];
} REGDESC, *PREGDESC;

static void SysacePrintRegister(const REGDESC *Desc, uint32_t Value);

static void SysacePrintRegister(const REGDESC *Desc, uint32_t Value)
{
    int i;
    printf("\t%s %x =", Desc->RegisterName, Value);
    for (i = 31; i >= 0; i--) {
        if (Value & (1 << i))
            printf(" %s", 
                     (Desc->BitNames[i]) ? Desc->BitNames[i] : "?");
    }
    printf("\n");            
}

static void SysaceDumpRegisters(struct _Sac *Interface)
{
	const REGDESC Control_Names =
        { "Control",
          {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          "RST", //			0x00010000
          "BUS8", //		0x00020000
          "BUS16", //		0x00040000
          "BUS32", //		0x00080000
          "IRQ", //			0x00100000
          "BRDY", //		0x00200000
          "IMSK0", //		0x00400000
          "IMSK1", //		0x00800000
          "TD0", //	    	0x0f000000
          "TD1", //		    0x0f000000
          "TD2", //	    	0x0f000000
          "TD3", //	    	0x0f000000
          "BUFW8", //		0x10000000
          "BUFW16", //		0x20000000
          "BUFW32", //		0x40000000
          "DEBUG"} //		0x80000000
        };

	const REGDESC STATUS_Names =
        { "STATUS",
          {"CFGLOCK", //	0x00000001
          "MPULOCK", //		0x00000002
          "CFGERROR", //	0x00000004
          "CFCERROR", //	0x00000008
          "CFDETECT", //	0x00000010
          "DATABUFRDY", //	0x00000020
          "DATABUFWRITE", //0x00000040
          "CFGDONE", //		0x00000080
          "RDYFORCFCMD", //	0x00000100
          "CFGMODEPIN", //	0x00000200
          0,0,0,
          "CFGADDRPIN0", //	0x0000e000
          "CFGADDRPIN1", //	0x0000e000
          "CFGADDRPIN2", //	0x0000e000
          0,
          "CFBSY", //		0x00020000
          "CFRDY", //		0x00040000
          "CFDWF", //		0x00080000
          "CFDSC", //		0x00100000
          "CFDRQ", //		0x00200000
          "CFCORR", //		0x00400000
          "CFERR", //		0x00800000
          0,}
        };

	const REGDESC ERRORREG_Names =
        { "ERRORREG",
          {"CARDRESETERR", //			0x00000001
          "CARDRDYERR", //				0x00000002
          "CARDREADERR", //				0x00000004
          "CARDWRITEERR", //			0x00000008
          "SECTORRDYERR", //			0x00000010
          "CFGADDRERR", //				0x00000020
          "CFGFAILED", //				0x00000040
          "CFGREADERR", //				0x00000080
          "CFGINSTRERR", //				0x00000100
          "CFGINITERR", //				0x00000200
          0,
          "CFBBK", //					0x00000800
          "CFUNC", //					0x00001000
          "CFIDNF", //					0x00002000
          "CFABORT", //					0x00004000
          "CFAMNF", //					0x00008000
           0,}
        };

    const NAME CommandNames[8] =
        { "0", //     				0x0000
          "RESETMEMCARD", //   		0x0100
          "IDENTIFYMEMCARD", //		0x0200
          "READMEMCARDDATA", //		0x0300
          "WRITEMEMCARDDATA", //  	0x0400
          "5", //     				0x0500
          "ABORT", //			    0x0600
          "7" //     			    0x0700
        };

	const REGDESC CONTROLREG_Names =
        { "CONTROLREG",
          {"FORCELOCKREQ", //			0x00000001
          "LOCKREQ", //					0x00000002
          "FORCECFGADDR", //			0x00000004
          "FORCECFGMODE", //			0x00000008
          "CFGMODE", //					0x00000010
          "CFGSTART", //				0x00000020
          "CFGSEL_MPU", //  			0x00000040
          "CFGRESET", //				0x00000080
          "DATABUFRDYIRQ", //			0x00000100
          "ERRORIRQ", //				0x00000200
          "CFGDONEIRQ", //				0x00000400
          "RESETIRQ", //				0x00000800
          "CFGPROG", //					0x00001000
          "CFGADDR_B0", //				0x00002000
          "CFGADDR_B1", //				0x00004000
          "CFGADDR_B2", //				0x00008000
          0,}
        };

	const REGDESC FATSTATREG_Names =
        { "FATSTATREG",
          {"MBRVALID", //				0x00000001
          "PBRVALID", //				0x00000002
          "MBRFAT12", //				0x00000004
          "PBRFAT12", //				0x00000008
          "MBRFAT16", //				0x00000010
          "PBRFAT16", //				0x00000020
          "CALCFAT12", //				0x00000040
          "CALCFAT16", //				0x00000080
          0, }
        };

    printf("Sysace@%p:\n", Interface);
    printf("\tTag %x\n", Interface->Tag);
    SysacePrintRegister(&Control_Names, Interface->Control);
    printf("\tBUSMODEREG %x\n", Interface->BUSMODEREG);
    SysacePrintRegister(&STATUS_Names, Interface->STATUS);
    SysacePrintRegister(&ERRORREG_Names, Interface->ERRORREG);
    printf("\tCFGLBAREG %x\n", Interface->CFGLBAREG);
    printf("\tMPULBAREG %x\n", Interface->MPULBAREG);
    printf("\tVERSIONREG %x\n", Interface->VERSIONREG);
    printf("\tSECCNTCMDREG %x = %s cnt=%d\n", Interface->SECCNTCMDREG,
             CommandNames[(Interface->SECCNTCMDREG >> 8)&7],
             Interface->SECCNTCMDREG & SAC_SECCCNT);
    SysacePrintRegister(&CONTROLREG_Names, Interface->CONTROLREG);
    SysacePrintRegister(&FATSTATREG_Names, Interface->FATSTATREG);
}

#else
#define SysaceDumpRegisters(_c_)
#endif

/* Reset the device and the interface
 */
static int SysaceInitialize(struct _Sac *Interface)
{
    /* 16bit mode etc etc */
	uint32_t BusMode, Control;

    /* reset our interface */
    Interface->Control = SAC_RST;
    Delay(200);

    /* repeat on both byte lanes */
	Interface->BUSMODEREG = SAC_MODE16 | (SAC_MODE16 << 8);
    Delay(1);

    /* check what our interface does and what the SysACE expects */
	Control = Interface->Control;
	BusMode = Interface->BUSMODEREG;

    /* get them to agree */
	if (BusMode & SAC_MODE16)
	{
		Interface->Control = Control | SAC_BUS16;
		Interface->Control = Interface->Control & ~SAC_BUS8;
	}
	else
	{
		Interface->Control = Control | SAC_BUS8;
		Interface->Control = Interface->Control & ~SAC_BUS16;
	}

    /* check that it worked */
	BusMode = Interface->BUSMODEREG;
	Control = Interface->Control;

	if (((BusMode & SAC_MODE16) == 0) && ((Control & SAC_BUS8) == 0)) return E_DISK_RESET_FAILED;
	if (((BusMode & SAC_MODE16) > 0) && ((Control & SAC_BUS16) == 0)) return E_DISK_RESET_FAILED;

    /* interrupts off for now */
    Interface->Control &= ~SAC_INTMASK;
#define SAC_INTERRUPTS (SAC_DATABUFRDYIRQ |	SAC_ERRORIRQ )// | SAC_CFGDONEIRQ)
    Control = Interface->CONTROLREG;
    Control = (Control & ~SAC_INTERRUPTS) | SAC_RESETIRQ | SAC_FORCECFGMODE;
    Interface->CONTROLREG = Control;
    Interface->CONTROLREG = Control & ~SAC_RESETIRQ;

    /* no command */
    Interface->MPULBAREG = 0;

    return S_OK;
}

/* Take control of the ACE datapath
 */
static int SysaceLock(struct _Sac *Interface)
{
    uint32_t Status;
    int i;

    /* Locked already?
     */
    Status = Interface->STATUS;
    if (Status & SAC_MPULOCK)
        return TRUE;

    /* Request lock
     */
    Interface->CONTROLREG |= SAC_LOCKREQ;

    /* Spin a bit until we get it
     */
    for (i = 0; i < 200; i++) {
        Status = Interface->STATUS;
        if (Status & SAC_MPULOCK)
            return TRUE;
        Delay(100);
        DBGME(0,printf("Sysace::Lock loops.. (st=%x)\n",Status));
    }

    /* oopsie!
     */
    DBGME(3,printf("Sysace::Lock timeout (st=%x)\n",Status));
    SysaceDumpRegisters(Interface);
    return FALSE;
}

/* Release control of the ACE datapath
 */
static int SysaceUnlock(struct _Sac *Interface)
{
    uint32_t Status;
    int i;

    /* Clear reset 
     */
    Interface->CONTROLREG &= ~SAC_CFGRESET;

    /* Unlocked already?
     */
    Status = Interface->STATUS;
    if (0 == (Status & SAC_MPULOCK))
        return TRUE;

    /* Request unlock
     */
    Interface->CONTROLREG &= ~SAC_LOCKREQ;

    /* Spin a bit until we get it
     */
    for (i = 0; i < 200; i++) {
        Status = Interface->STATUS;
        if (0 == (Status & SAC_MPULOCK))
            return TRUE;
        Delay(100);
        DBGME(0,printf("Sysace::Unlock loops.. (st=%x)\n",Status));
    }

    /* oopsie!
     */
    DBGME(3,printf("Sysace::Unlock timeout (st=%x)\n",Status));
    SysaceDumpRegisters(Interface);
    return FALSE;
}

/* Check if the ACE is waiting for a comamnd
 */
#define SysaceReadyForCommand(_i_) ((_i_)->STATUS & SAC_RDYFORCFCMD)

/* Check if the ACE is executing a comamnd
 */
#define SysaceBusyWithCommand(_i_) ((_i_)->STATUS & SAC_CFBSY)

/* Turn on interrupts from the ACE
 */
#define SysaceInton(_i_) { \
          (_i_)->CONTROLREG |= SAC_INTERRUPTS; \
          (_i_)->Control |= SAC_INTMASK; \
}

/* Turn off interrupts from the ACE
 */
#define SysaceIntoff(_i_) { \
          (_i_)->CONTROLREG &= ~SAC_INTERRUPTS; \
          (_i_)->Control &= ~SAC_INTMASK; \
}

/* Start a command on the ACE, such as read or identify.
 */
static int SysaceStartCommand(struct _Sac *Interface,
                              uint32_t Command,
                              uint32_t Lba,
                              uint32_t nSectors)
{
    int sc = E_DISK_RESET_FAILED;

    /* Lock it if not already
     */
    if (!SysaceLock(Interface)) {
        /* printed already */
        return sc;
    }

    /* Is there a CF inserted
     */
    if (! (Interface->STATUS & SAC_CFDETECT)) {
        /* NB: Not a failure state */
        DBGME(2,printf("Sysace:: no media (st=%x)\n",Interface->STATUS));
        return E_NO_MEDIA_IN_DRIVE;
    }

    /* Is it ready for a command
     */
    if (!SysaceReadyForCommand(Interface)) {
        DBGME(3,printf("Sysace:: not ready (st=%x)\n",Interface->STATUS));
        SysaceDumpRegisters(Interface);
        return sc;
    }

    /* sector number and command
     */
    Interface->MPULBAREG = Lba;
    Interface->SECCNTCMDREG = (uint16_t)(Command | (nSectors & SAC_SECCCNT));

    /* re-route the chip
     * NB: The word "RESET" is actually not much of a misnomer.
     * The chip was designed for a one-shot execution only, at reset time,
     * namely loading the configuration data into the FPGA. So..
     */
    Interface->CONTROLREG |= SAC_CFGRESET;
    return S_OK;
}


/* "Interrupt service routine"
 */
static void SysAce_isr ( struct _Sac *Interface )
{
    uint32_t Control;

    /* Turn off interrupts and ACK them
     */
    SysaceIntoff(Interface);

    Control = Interface->CONTROLREG & (~(SAC_RESETIRQ|SAC_INTERRUPTS));
    Interface->CONTROLREG = Control | SAC_RESETIRQ;
    Interface->CONTROLREG = Control;
}

static int SysAce_wait(struct _Sac *Interface)
{
    int i;
    for (i = 0; i < 30000; i++) {
        if (Interface->STATUS & SAC_DATABUFRDY) {
            SysAce_isr(Interface);
            return S_OK;
        }
        Delay(100);
    }
    return E_TIMED_OUT;
}


/* Read NBLOCKS blocks of 512 bytes each,
 * starting at block number STARTSECTOR,
 * into the buffer BUFFER.
 * Return 0 if ok, -1 otherwise.
 */
static int SysAce_read(struct _Sac * Interface, char *Buffer, uint32_t StartSector, uint32_t nBlocks)
{
    int sc = S_OK;
    uint32_t Size, SizeThisTime;
    uint32_t Status = 0, SizeRead = 0;
    unsigned int i, j;

    Size = nBlocks << CF_SECBITS;

    DBGME(1,printf("Sysace Read(%p %x %x)\n",
                     Buffer, StartSector, nBlocks));

    /* Repeat until we are done or error
     */
    while (sc == S_OK) {

        /* .. one sector at a time
         * BUGBUG Supposedly we can do up to 256 sectors?
         */
        SizeThisTime = Size;
        if (SizeThisTime > CF_SECTOR_SIZE)
            SizeThisTime = CF_SECTOR_SIZE;

        /*  Start a new sector read
         */
        SysaceInton(Interface);
        sc = SysaceStartCommand(Interface,
                                SAC_CMD_READMEMCARDDATA,
                                StartSector,
                                1);
        /* And wait until done, if ok
         */
        if (!FAILED(sc)) {
            sc = SysAce_wait(Interface);
        }

        /* Are we doing ok
         */
        if (!FAILED(sc)) {

            /* Get the data out of the ACE
             */
            for (i = 0; i < SizeThisTime; i += 4) {

                /* Make sure the FIFO is ready
                 */
                for (j = 0; j < 100; j++) {
                    Status = Interface->STATUS;
                    if (Status & SAC_DATABUFRDY)
                        break;
                    Delay(10);
                }

                /* Got it?
                 */
                if (Status & SAC_DATABUFRDY) {
                    uint32_t Data32;
                    
                    Data32 = Interface->DATABUFREG[0];
                    Data32 = le32toh(Data32);
		    DBGME(0,printf(" %x", Data32));
		    if (0 == (0xf & (i+4))) DBGME(0,printf("\n"));
                    memcpy(Buffer+i, &Data32, 4);
                }
                else
                {
                    /* Ooops, get out of here
                     */
                    DBGME(3,printf("Sysace::READ timeout\n"));
                    SysaceDumpRegisters(Interface);
                    sc = E_TIMED_OUT;
                    break;
                }
            }

            /* Still doing ok?
             */
            if (!FAILED(sc)) {
                StartSector        += 1;
                Buffer             += SizeThisTime;
                SizeRead           += SizeThisTime;
                Size               -= SizeThisTime;
            }
        }

        /* Free the ACE for the JTAG, just in case */
        SysaceUnlock(Interface);

        /* Are we done yet?
         */
        if (Size == 0)
            break;
    }

    return sc;
}

/* Exported interface
 */
struct	ace_softc {
    struct _Sac *sc_dp;         /* I/O regs */
	int	sc_part;		        /* disk partition number */
	struct	disklabel sc_label;	/* disk label for this disk */
};

#define	RF_PROTECTED_SECTORS	64	/* XXX refer to <.../rf_optnames.h> */

int aceprobe(int unit)
{
    struct _Sac *Sac;

    if (unit == 0)
        Sac = SAC0;
    else if (unit == 1)
        Sac = SAC1;
    else 
        return E_INVALID_PARAMETER;

    /* Check the tag to see if its there
     */
    if ((Sac->Tag & SAC_TAG) != PMTTAG_SYSTEM_ACE) {
        DBGME(3,printf("init_ace: bad tag (%x != %x) @x%p\n",
                       Sac->Tag, PMTTAG_SYSTEM_ACE, Sac));
        return E_INVALID_PARAMETER;
    }

    return S_OK;
}

/* aceopen("", ctlr, unit, part);
 */
int
aceopen(struct open_file *f, ...)
{
	int ctlr, unit, part;
    struct _Sac *Sac;

	struct ace_softc *sc;
	struct disklabel *lp;
	int i;
	char *msg;
	char buf[DEV_BSIZE];
	size_t cnt;
	va_list ap;

	va_start(ap, f);

	ctlr = va_arg(ap, int);
	unit = va_arg(ap, int);
	part = va_arg(ap, int);
	va_end(ap);

	if (ctlr != 0 || unit >= NSAC || part >= 8)
		return (ENXIO);

    /* Is it there, does it work.
     */
    Sac = (unit == 0) ? SAC0 : SAC1;
    i = aceprobe(unit);
	if (i < 0)
        goto Bad;

    if (SysaceInitialize(Sac) < 0) {
        DBGME(3,printf("ace%d: no reset @x%p\n", unit, Sac));
    Bad:
		printf("open failed\n");
		return (ENXIO);
    }

    /* Yep, go ahead.
     */
	sc = alloc(sizeof(struct ace_softc));
	memset(sc, 0, sizeof(struct ace_softc));
	f->f_devdata = (void *)sc;

	sc->sc_dp = Sac;
	sc->sc_part = part;

	/* try to read disk label and partition table information */
	lp = &sc->sc_label;
	lp->d_secsize = DEV_BSIZE;
	lp->d_secpercyl = 1;
	lp->d_npartitions = MAXPARTITIONS;
	lp->d_partitions[part].p_offset = 0;
	lp->d_partitions[part].p_size = 0x7fffffff;

	i = acestrategy(sc, F_READ, (daddr_t)LABELSECTOR, DEV_BSIZE, buf, &cnt);
	if (i || cnt != DEV_BSIZE) {
		DBGME(3,printf("ace%d: error reading disk label\n", unit));
		goto bad;
	}
	msg = getdisklabel(buf, lp);
	if (msg) {
		/* If no label, just assume 0 and return */
		return (0);
	}

	if (part >= lp->d_npartitions || lp->d_partitions[part].p_size == 0) {
	bad:
		dealloc(sc, sizeof(struct ace_softc));
		DBGME(3,printf("ace%d: bad part %d\n", unit, part));
		return (ENXIO);
	}
	return (0);
}

#ifndef LIBSA_NO_DEV_CLOSE
int
aceclose(struct open_file *f)
{
	dealloc(f->f_devdata, sizeof(struct ace_softc));
	f->f_devdata = (void *)0;
	return (0);
}
#endif

int
acestrategy(
	void *devdata,
	int rw,
	daddr_t bn,
	size_t reqcnt,
	void *addr,
	size_t *cnt)	/* out: number of bytes transferred */
{
	struct ace_softc *sc = (struct ace_softc *)devdata;
	int part = sc->sc_part;
	struct partition *pp = &sc->sc_label.d_partitions[part];
	int s;
	uint32_t sector;

#if 0 //useless?
    if (rw != F_READ)
        return (EINVAL);
#endif

	/*
	 * Partial-block transfers not handled.
	 */
	if (reqcnt & (DEV_BSIZE - 1)) {
		*cnt = 0;
		return (EINVAL);
	}

    /*
     * Compute starting sector
     */
	sector = bn;
	sector += pp->p_offset;
	if (pp->p_fstype == FS_RAID)
		sector += RF_PROTECTED_SECTORS;

    /* read */
    s = SysAce_read(sc->sc_dp,addr,sector,reqcnt >> CF_SECBITS);

	if (s < 0)
		return (EIO);

    /* BUGBUG there's no validation we don't fall off the deep end */
	*cnt = reqcnt;
	return (0);
}

