/*
 * ASYN Octet driver for reading FPGA programming information
 */
#include <epicsStdio.h>
#include <epicsString.h>
#include <epicsExport.h>
#include <epicsInterrupt.h>
#include <cantProceed.h>
#include <ctype.h>

#include <asynDriver.h>
#include <asynOctet.h>
#include <devLib.h>

#include "FPGA.h"

/*
 * Driver private storage
 */
typedef struct dpvt {
    const char    *portName;
    volatile char *romBase;
    size_t         romSize;
    asynInterface  common;
    asynInterface  asynOctet;
    asynOctet      asynOctetMethods;
} dpvt;

/*
 * asynCommon methods
 */
static void
report(void *pvt, FILE *fp, int details)
{
}

static asynStatus
connect(void *pvt, asynUser *pasynUser)
{
    pasynManager->exceptionConnect(pasynUser);
    return asynSuccess;
}

static asynStatus
disconnect(void *pvt, asynUser *pasynUser)
{
    pasynManager->exceptionDisconnect(pasynUser);
    return asynSuccess;
}
static asynCommon common = { report, connect, disconnect };

/*
 * asynOctet methods
 */
static asynStatus
octetRead(void *pvt, asynUser *pasynUser, char *data, size_t maxchars, size_t *nTransferred, int *eomReason)
{
    dpvt *pdpvt = (dpvt *)pvt;
    int i;
    int eom = 0;
    asynStatus status;
    int channel;

    if ((status = pasynManager->getAddr(pasynUser, &channel)) != asynSuccess)
        return status;
    if ((channel < 0) || (channel > 0))
        return asynError;

    /*
     * The address manipulation works around the fact that SOPC Builder
     * and the ColdFire hardware disagree on byte lanes.
     */
    for (i = 0 ; (i < pdpvt->romSize) && (i < maxchars) ; i++)
        *data++ = *(pdpvt->romBase + (i ^ 1));
    if (i >= maxchars)
        eom |= ASYN_EOM_CNT;
    if (i >= pdpvt->romSize)
        eom |= ASYN_EOM_END;
    *nTransferred = i;
    *eomReason = eom;
    return asynSuccess;
}

/*
 * Register ourself with ASYN
 */
static void
devFpgaInfoConfigure(const char *portName, int a16, int size)
{
    dpvt *pdpvt;
    asynStatus status;
    volatile void *rom;
    char dummy;

    portName = epicsStrDup(portName);
    pdpvt = callocMustSucceed(sizeof(dpvt), 1, portName);
    pdpvt->portName = portName;
    if (size == 0)
        size = 64;
    pdpvt->romSize = size;
    if (devRegisterAddress(pdpvt->portName, atVMEA16, a16, size, &rom) != 0) {
        printf("Can't register VME A16 address %#x (%#x bytes) for %s\n",
                                                    a16, size, pdpvt->portName);
        return;
    }
    pdpvt->romBase = rom;
    if (devReadProbe(sizeof(dummy), rom, &dummy) != 0) {
        printf("%s: memory probe failed\n", pdpvt->portName);
        return;
    }
    if (!isprint(dummy)) {
        printf("%s: Information at location %p (%2.2x %2.2x %2.2x %2.2x) is garbled.\n",
                                            pdpvt->portName,
                                            rom,
                                            (unsigned char)pdpvt->romBase[1],
                                            (unsigned char)pdpvt->romBase[0],
                                            (unsigned char)pdpvt->romBase[3],
                                            (unsigned char)pdpvt->romBase[2]);
        return;
    }

    status = pasynManager->registerPort(pdpvt->portName, ASYN_MULTIDEVICE, 1, 0, 0);
    if(status != asynSuccess) {
        printf("fpgaProgrammingInfo registerDriver failed\n");
        return;
    }

    pdpvt->common.interfaceType = asynCommonType;
    pdpvt->common.pinterface  = (void *)&common;
    pdpvt->common.drvPvt = pdpvt;
    status = pasynManager->registerInterface(pdpvt->portName, &pdpvt->common);
    if (status != asynSuccess) {
        printf("fpgaProgrammingInfo registerInterface failed\n");
        return;
    }

    pdpvt->asynOctetMethods.read = octetRead;
    pdpvt->asynOctet.interfaceType = asynOctetType;
    pdpvt->asynOctet.pinterface  = &pdpvt->asynOctetMethods;
    pdpvt->asynOctet.drvPvt = pdpvt;
    status = pasynOctetBase->initialize(pdpvt->portName, &pdpvt->asynOctet, 0, 0, 0);
    if (status != asynSuccess) {
        printf("fpgaProgrammingInfo pasynOctetBase->initialize failed\n");
        return;
    }
}

/*
 * IOC shell command registration
 */
#include <iocsh.h>
static const iocshArg devFpgaInfoConfigureArg0 = { "name",iocshArgString};
static const iocshArg devFpgaInfoConfigureArg1 = { "VME A16/D16 address",iocshArgInt};
static const iocshArg devFpgaInfoConfigureArg2 = { "[size]",iocshArgInt};
static const iocshArg *devFpgaInfoConfigureArgs[] = {
    &devFpgaInfoConfigureArg0, &devFpgaInfoConfigureArg1,
    &devFpgaInfoConfigureArg2};
static const iocshFuncDef devFpgaInfoConfigureFuncDef =
              {"devFpgaInfoConfigure",3,devFpgaInfoConfigureArgs};
static void devFpgaInfoConfigureCallFunc(const iocshArgBuf *args)
{
    devFpgaInfoConfigure(args[0].sval, args[1].ival, args[2].ival);
}
static void
devFpgaInfoRegisterCommands(void)
{
    iocshRegister(&devFpgaInfoConfigureFuncDef,devFpgaInfoConfigureCallFunc);
}
epicsExportRegistrar(devFpgaInfoRegisterCommands);
