/*      $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);
}