#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/map.h>
#include <sys/debug.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "mdsp.h"

static int mdsp_identify (dev_info_t *dip);
static int mdsp_getinfo (dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
                         void **result);
static int mdsp_attach (dev_info_t *dip, ddi_attach_cmd_t cmd);
static int mdsp_detach (dev_info_t *dip, ddi_detach_cmd_t cmd);
static int mdsp_open (dev_t *devp, int flag, int otyp, cred_t *credp);
static int mdsp_close (dev_t dev, int openflags, int otyp, cred_t *credp);
static int mdsp_write (dev_t dev, struct uio *uiop, cred_t *credp);
static int mdsp_read (dev_t dev, struct uio *uiop, cred_t *credp);
static int mdsp_strategy (struct buf *bp);
static int mdsp_ioctl (dev_t dev, int cmd, int arg, int flag, cred_t *credp,
                       int *rvalp);
static u_int mdsp_intr (caddr_t instance);

static void *state_head;	/* opaque handle top of state structs */
static char revision[] = "Motorola ADS device driver {1.1-alpha}, 6-2-1996";

static struct cb_ops mdsp_cb_ops =
{
    mdsp_open,
    mdsp_close,
    mdsp_strategy,
    nodev,			/* no print routine */
    nodev,			/* no dump routine */
    mdsp_read,
    mdsp_write,
    mdsp_ioctl,
    nodev,			/* no devmap routine */
    nulldev,			/* no mmap routine */
    nulldev,			/* no segmap routine */
    nochpoll,                   /* no poll routine */
    ddi_prop_op,
    0,				/* not a STREAMS driver */
    (D_NEW | D_MP)		/* safe for multi-thread/multi-processor */
};

static struct dev_ops mdsp_ops =
{
    DEVO_REV,			/* DEVO_REV indicated by manual */
    0,				/* device reference count       */
    mdsp_getinfo,
    mdsp_identify,
    nulldev,			/* device probe for non-self-id */
    mdsp_attach,
    mdsp_detach,		/* device detach */
    nodev,			/* device reset routine         */
    &mdsp_cb_ops,
    (struct bus_ops *) 0,	/* bus operations               */
};

extern struct mod_ops mod_driverops;

static struct modldrv modldrv =
{
    &mod_driverops,		/* Identified as a loadable driver */
    "Motorola ADS driver",
    &mdsp_ops,
};

static struct modlinkage modlinkage =
{
    MODREV_1,			/* Reguired */
    {(void *) &modldrv, NULL}
};


/* Loadable driver support */
int
_init (void)
{
    register int error;

    /* Prepare for per-instance allocations */
    if ((error = ddi_soft_state_init (&state_head,
                                      sizeof (struct mdsp_state), 1)) != 0)
    {
        return (error);
    }

    if ((error = mod_install (&modlinkage)) != 0)
    {
	ddi_soft_state_fini (&state_head);
    }
    
    return (error);
}


int
_info (struct modinfo *modinfop)
{
    return (mod_info (&modlinkage, modinfop));
}


int
_fini (void)
{
    int status;

    if ((status = mod_remove (&modlinkage)) != 0)
    {
	return (status);
    }
    
    ddi_soft_state_fini (&state_head);

    return (status);
}


static int
mdsp_identify (dev_info_t *dip)
{
    char *dev_name;


    dev_name = ddi_get_name (dip);

    if (strcmp (dev_name, "MOTO,modsp") == 0)
    {
        return (DDI_IDENTIFIED);
    }
    else
    {
	return (DDI_NOT_IDENTIFIED);
    }
}


static int
mdsp_getinfo (dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
              void **result)
{
    dev_t dev;
    int error;
    int instance;
    struct mdsp_state *mdspsp;


    switch (infocmd)
    {
    case DDI_INFO_DEVT2INSTANCE:
        dev = (dev_t) arg;
	*result = (void *) getminor (dev);
	error = DDI_SUCCESS;
	break;
    case DDI_INFO_DEVT2DEVINFO:
        dev = (dev_t) arg;
        instance = getminor (dev);
        
	mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head,
                                                           instance);
        
	if (mdspsp == NULL)
	{
	    error = DDI_FAILURE;
	}
	else
	{
	    *result = (void *) mdspsp->dip;
	    error = DDI_SUCCESS;
	}
	break;
    default:
	error = DDI_FAILURE;
    }

    return (error);
}


static int
mdsp_attach (dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    int instance;
    register struct mdsp_state *mdspsp;


    if (cmd != DDI_ATTACH)
    {
	return (DDI_FAILURE);
    }
    else
    {
	instance = ddi_get_instance (dip);

	/* Allocate per instance structure */
	if (ddi_soft_state_zalloc (state_head, instance) != DDI_SUCCESS)
        {
	    return (DDI_FAILURE);
        }
        
	mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head,
                                                           instance);
	mdspsp->adm_type[0] = adm_type_default;
	mdspsp->adm_active = adm_active_default;
	mdspsp->adm_active_type = adm_type_default;

        if (ddi_map_regs (dip, 0,
                          (caddr_t *) &mdspsp->regsp, 0,
                          sizeof (struct mdsp_regs)) != DDI_SUCCESS)
	{
	    return (DDI_FAILURE);
	}

        if (ddi_create_minor_node (dip, ddi_get_name (dip), S_IFCHR,
				   instance, "MOTO:modsp", 0) != 0)
	{
	    ddi_unmap_regs (dip, 0, (caddr_t *) &mdspsp->regsp,
			    0, sizeof (struct mdsp_regs));

	    ddi_soft_state_free (state_head, instance);
	    return (DDI_FAILURE);
	}

        if (ddi_add_intr (dip, 0, mdspsp->iblock_cookie,
			  (ddi_idevice_cookie_t *) NULL, mdsp_intr,
			  (caddr_t) instance) != DDI_SUCCESS)
	{
	    ddi_unmap_regs (dip, 0, (caddr_t *) &mdspsp->regsp,
			    0, sizeof (struct mdsp_regs));

	    ddi_soft_state_free (state_head, instance);
	    return (DDI_FAILURE);
	}

        return (DDI_SUCCESS);
    }
}


static int
mdsp_detach (dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    int instance;
    struct mdsp_state *mdspsp;


    if (cmd != DDI_DETACH)
    {
	return (DDI_FAILURE);
    }
    
    instance = ddi_get_instance (dip);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);

    ddi_remove_intr (dip, 0, mdspsp->iblock_cookie);
    ddi_remove_minor_node (dip, NULL);
    ddi_unmap_regs (dip, 0, (caddr_t *) &mdspsp->regsp, 0,
                    sizeof (struct mdsp_regs));

    ddi_soft_state_free (state_head, instance);

    return (DDI_SUCCESS);
}


/* Driver Entry Points */
static int
mdsp_open (dev_t *devp, int flag, int otyp, cred_t *credp)
{
    int instance;
    struct mdsp_state *mdspsp;


    instance = getminor (*devp);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);

    /* Verify instance structure */
    if (mdspsp == NULL)
    {
	return (ENXIO);
    }

    /* Verify we are being opened as a character device */
    if (otyp != OTYP_CHR)
    {
	return (EINVAL);
    }

    /* Check that read and write is permissable */
    if (!(flag & FWRITE) || !(flag & FREAD))
    {
	return (ENODEV);
    }

    return (0);
}


static int
mdsp_close (dev_t dev, int openflags, int otyp, cred_t *credp)
{
    return (0);
}


static u_int
mdsp_intr (caddr_t instance)
{
    struct mdsp_state *mdspsp;
    struct mdsp_regs *regs;


    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head,
                                                       (u_int) instance);

    regs = mdspsp->regsp;

    if (!(regs->adm_control & HOST_BRK) && (regs->adm_interrupt & INT_EN))
    {
	/* Disable Interrupt */
	regs->adm_interrupt &= ~INT_EN;
	return (DDI_INTR_CLAIMED);
    }

    return (DDI_INTR_UNCLAIMED);
}


static int
mdsp_ioctl (dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp)
{
    int instance;
    int count;
    register int i;
    char cmdcnt;
    char user_data[10];
    u_char get_cmd;
    u_char set_cmd;
    u_char set_adm;
    u_char set_data;
    u_char user_char[2];
    struct mdsp_state *mdspsp;
    register struct mdsp_regs *regsp;


    instance = getminor (dev);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);
    regsp = mdspsp->regsp;

    switch (cmd)
    {
    case MDSPSETADM:
	if (ddi_copyin ((caddr_t) arg, (caddr_t) &user_char,
                        sizeof (user_char), flag) != 0)
	{
	    return (EFAULT);
	}

	set_data = user_char[0]; /* extract data argument */
	set_cmd = user_char[1] & 0x0F; /* extract set command type */
	set_adm = (u_char) (user_char[1] & 0xF0) >> 4; /* extract adm number */

        switch (set_cmd)
	{
	case set_adm_type:
	    mdspsp->adm_type[set_adm] = set_data;
	    break;
	case set_adm_active:
	    mdspsp->adm_active = set_data;
	    break;
	case set_active_type:
	    mdspsp->adm_active_type = set_data;
	    break;
	}
	break;

    case MDSPVERSION:
	if (ddi_copyout (revision, (caddr_t) arg,
                         sizeof (revision) / sizeof (char), flag) != 0)
	{
	    return (EFAULT);
	}
	break;

    case MDSPSELECT:
	/* select a group member */
	if (ddi_copyin ((caddr_t) arg, (caddr_t) user_char, sizeof (u_char),
                        flag) != 0)
	{
	    return (EFAULT);
	}

	regsp->adm_control = user_char[0];
	regsp->adm_group = ADM_GROUP;
	regsp->adm_group = 0;

        break;

    case MDSPDESELECT:
	/* deselect a group of ADMs */
	regsp->adm_control = ADM_ALL | HOST_ACK;
	regsp->adm_control = 0;
	regsp->adm_group = 0;

        break;

    case MDSPRESET:
	/* assert ADM_RESET line, capacitor time
	   constant is done in main program */
	regsp->adm_control = mdspsp->control | ADM_RESET;

	break;

    case MDSPCLEAR:
	/* deassert ADM_RESET line */
	regsp->adm_control = mdspsp->control;

	break;

    case MDSPCOMMAND:
	/* preset command count for adm */
	if (ddi_copyin ((caddr_t) arg++, (caddr_t) &cmdcnt,
                        sizeof (char), flag) != 0)
	{
	    return (EFAULT);
	}

        if (ddi_copyin ((caddr_t) arg, (caddr_t) user_data,
                        sizeof (char) * cmdcnt, flag) != 0)
	{
	    return (EFAULT);
	}

        /* Move Pointer to start of data */
	for (count = 0; count < cmdcnt; count++)
	{
	    /* set up command */
	    regsp->adm_data = user_data[count];

	    /* set ADM_REQ high */
	    regsp->adm_control = mdspsp->control | ADM_REQ;

	    /* wait for ADM_ACK to go high */
	    for (i = 0; i < TIMEOUT_COUNT; i++)
	    {
		if (regsp->adm_control & HOST_ACK)
                {
		    break;
                }
                
		drv_usecwait (DEL);
	    }

	    /* deassert ADM_REQ */
	    regsp->adm_control = mdspsp->control;
	    regsp->adm_data = 0;

	    /* if ADM_ACK didn't assert return error */
	    if (i >= TIMEOUT_COUNT - 1)
	    {
		return (EIO);
	    }

            /* wait for ADM_ACK to deassert */
	    for (i = 0; i < TIMEOUT_COUNT; i++)
	    {
		if (!(regsp->adm_control & ADM_ACK))
                {
		    break;
                }
                
		drv_usecwait (DEL);
	    }

	    /* if ADM_ACK didn't deassert return error */
	    if (i >= TIMEOUT_COUNT - 1)
	    {
		return (EIO);
	    }
	}
	break;

    case MDSPBREAK:
	/* assert/deassert ADM_BRK signal */
	regsp->adm_control = mdspsp->control | ADM_BRK;
	regsp->adm_control = mdspsp->control;

	/* wait for ADM to assert ADM_INT */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (regsp->adm_control & ADM_INT)
            {
		break;
            }
            
	    drv_usecwait (2 * DEL);
	}

	/* if ADM didn't assert ADM_INT return error */
	if (i >= TIMEOUT_COUNT - 1)
        {
	    return (EIO);
        }
        
	/* assert/deassert INT_ACK signal */
	regsp->adm_group = INT_ACK;

        /* wait for ADM to de-assert ADM_INT */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (!(regsp->adm_control & ADM_INT))
            {
		break;
            }
            
	    drv_usecwait (2 * DEL);
	}

	regsp->adm_group = 0;

        /* if ADM did not de-assert ADM_INT return error */
	if (i >= TIMEOUT_COUNT - 1)
        {
	    return (EIO);
        }
        
	return (0);

    case MDSPIOW:
	/* write to control or data address */
	if (ddi_copyin ((caddr_t) arg, (caddr_t) user_char,
                        sizeof (user_char), flag) != 0)
	{
	    return (EFAULT);
	}

	set_cmd = user_char[0]; /* first value is address to write to# */

        if (!set_cmd)
	{
	    regsp->adm_control = mdspsp->control | user_char[1];
	}
	else if (set_cmd == 1)
	{
	    regsp->adm_data = user_char[1];
	}
	else
	{
	    regsp->adm_group = user_char[1];
	}
	break;

    case MDSPWBYTE:
	/* present data on bus */
	if (ddi_copyin ((caddr_t) arg, (caddr_t) user_char, sizeof (u_char),
                        flag) != 0)
	{
	    return (EFAULT);
	}

	regsp->adm_data = user_char[0];

	/* assert HOST_REQ */
	regsp->adm_control = mdspsp->control | HOST_REQ;

	/* wait for ADM_ACK to assert */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (regsp->adm_control & ADM_ACK)
            {
		break;
            }
            
	    drv_usecwait (DEL);
	}

	/* deassert HOST_REQ */
	regsp->adm_control = mdspsp->control;

	/* check if ADM_ACK was asserted */
	if (i >= TIMEOUT_COUNT - 1)
	{
	    return (EIO);
	}

        /* wait for ADM_ACK to be deasserted */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (!(regsp->adm_control & ADM_ACK))
            {
		break;
            }
            
	    drv_usecwait (DEL);
	}

	if (i >= TIMEOUT_COUNT - 1)
	{
	    return (EIO);
	}
	break;

    case MDSPRBYTE:
	/* check if ADM_REQ is asserted */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (regsp->adm_control & ADM_REQ)
            {
		break;
            }
            
	    drv_usecwait (DEL);
	}

        if (i >= TIMEOUT_COUNT - 1)
	{
	    return (EIO);
	}

	/* assert HOST_ACK */
	regsp->adm_control = mdspsp->control | HOST_ACK;

        /* read data */
	user_char[0] = regsp->adm_data;
	if (ddi_copyout ((caddr_t) user_char, (caddr_t) arg, sizeof (u_char),
			 flag) != 0)
	{
	    return (EFAULT);
	}

        /* wait for ADM_REQ to be deasserted */
	for (i = 0; i < TIMEOUT_COUNT; i++)
	{
	    if (!(regsp->adm_control & ADM_REQ))
            {
		break;
            }
            
	    drv_usecwait (DEL);
	}

        /* deassert HOST_ACK line */
	regsp->adm_control = mdspsp->control;

	if (i >= TIMEOUT_COUNT - 1)
	{
	    return (EIO);
	}

	break;

    case MDSPIOR:
	if (ddi_copyin ((caddr_t) arg, (caddr_t) user_char, sizeof (u_char),
                        flag) != 0)
	{
	    return (EFAULT);
	}

	drv_usecwait (DEL);

        get_cmd = user_char[0]; /* first value is address to read from # */

	if (!get_cmd)
	{
	    user_char[0] = regsp->adm_control;
	}
	else if (get_cmd == 1)
	{
	    user_char[0] = regsp->adm_data;
	}
	else
	{
	    user_char[0] = regsp->adm_group;
	}

	if (ddi_copyout ((caddr_t) user_char, (caddr_t) arg, sizeof (u_char),
			 flag) != 0)
	{
	    return (EFAULT);
	}
	break;

    case MDSPDEVICE:
	if (ddi_copyin ((caddr_t) arg, (caddr_t) user_char, sizeof (u_char),
                        flag) != 0)
	{
	    return (EFAULT);
	}

	mdspsp->control = (u_char) (user_char[0] << 4) & 0xf0;
	regsp->adm_control = mdspsp->control;

	break;

    default:
	return (ENOTTY);
    }

    return (0);
}

static void
mdsp_minphys (struct buf *bp)
{
    if (bp->b_bcount > MDSP_MINPHYS_SIZE)
    {
	bp->b_bcount = MDSP_MINPHYS_SIZE;
    }

    minphys (bp);
}

static int
mdsp_strategy (struct buf *bp)
{
    int i;
    int instance;
    int num_bytes;
    int byte_count;
    int xfer_count = 0;
    struct mdsp_state *mdspsp;
    register struct mdsp_regs *regsp;
    register caddr_t buf_addr;


    instance = getminor (bp->b_edev);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);

    regsp = mdspsp->regsp;

    if (mdspsp->adm_active_type & dsp16bit)
    {
	num_bytes = 2;
    }
    else if (mdspsp->adm_active_type & dsp24bit)
    {
	num_bytes = 3;
    }
    else if (mdspsp->adm_active_type & dsp32bit)
    {
	num_bytes = 4;
    }
    else
    {
        bp->b_resid = bp->b_bcount;
        bp->b_error = EIO;
        bp->b_flags |= B_ERROR;
        biodone (bp);
	return (0);
    }

    bp_mapin (bp);
    buf_addr = bp->b_un.b_addr;

    /* check if read */
    if (bp->b_flags & B_READ)
    {
        while (xfer_count < bp->b_bcount)
	{
            /* Zero out extra bytes */
	    for (i = 0; i < (sizeof (long) - num_bytes); i++)
	    {
		xfer_count++;
		*buf_addr++ = 0;
	    }

	    /* Get each byte */
            for (byte_count = 0; byte_count < num_bytes; byte_count++)
	    {
		/* check if ADM_REQ is asserted */
		for (i = 0; i < TIMEOUT_COUNT; i++)
		{
		    if (regsp->adm_control & ADM_REQ)
                    {
			break;
                    }
                    
		    drv_usecwait (DEL);
		}

		if (i >= TIMEOUT_COUNT - 1)
		{
		    bp->b_resid = bp->b_bcount - xfer_count;
		    bp->b_error = EIO;
		    bp->b_flags |= B_ERROR;
                    bp_mapout (bp);
		    biodone (bp);
		    return (0);
		}

                /* assert HOST_ACK */
		regsp->adm_control = mdspsp->control | HOST_ACK;

                /* read data */
		xfer_count++;
		*buf_addr++ = regsp->adm_data;

		/* wait for ADM_REQ to be deaserted */
		for (i = 0; i < TIMEOUT_COUNT; i++)
		{
		    if (!(regsp->adm_control & ADM_REQ))
                    {
			break;
                    }

		    drv_usecwait (DEL);
		}

		/* deassert HOST_ACK line */
		regsp->adm_control = mdspsp->control;

		if (i >= TIMEOUT_COUNT - 1)
		{
		    bp->b_resid = bp->b_bcount - xfer_count;
		    bp->b_error = EIO;
		    bp->b_flags |= B_ERROR;
                    bp_mapout (bp);
		    biodone (bp);
		    return (0);
		}
	    }			/* end of byte_count loop */
	}			/* end of xfer_count loop */
    }				/* end of read */
    else
    {
        /* must be write */
        while (xfer_count < bp->b_bcount)
	{
	    /* ignore first bytes of long if needed based on type */
	    for (i = 0; i < (sizeof (long) - num_bytes); i++)
	    {
		buf_addr++;
		xfer_count++;
	    }

            /* Send each byte */
	    for (byte_count = 0; byte_count < num_bytes; byte_count++)
	    {
		/* present data on bus */
		xfer_count++;
		regsp->adm_data = *buf_addr++;

		/* assert HOST_REQ */
		regsp->adm_control = mdspsp->control | HOST_REQ;

                /* wait for ADM_ACK to assert */
		for (i = 0; i < TIMEOUT_COUNT; i++)
		{
		    if (regsp->adm_control & ADM_ACK)
                    {
			break;
                    }
                    
		    drv_usecwait (DEL);
		}

		/* deassert HOST_REQ */
		regsp->adm_control = mdspsp->control;

		/* check if ADM_ACK was asserted */
		if (i >= TIMEOUT_COUNT - 1)
		{
		    bp->b_resid = bp->b_bcount - xfer_count;
		    bp->b_error = EIO;
		    bp->b_flags |= B_ERROR;
                    bp_mapout (bp);
		    biodone (bp);
		    return (0);
		}

                /* wait for ADM_ACK to be deasserted */
		for (i = 0; i < TIMEOUT_COUNT; i++)
		{
		    if (!(regsp->adm_control & ADM_ACK))
                    {
			break;
                    }
                    
		    drv_usecwait (DEL);
		}

		/* check if ADM_ACK deasserted */
		if (i >= TIMEOUT_COUNT - 1)
		{
		    bp->b_resid = bp->b_bcount - xfer_count;
		    bp->b_error = EIO;
		    bp->b_flags |= B_ERROR;
                    bp_mapout (bp);
		    biodone (bp);
		    return (0);
		}
	    }			/* end of byte_count loop */
	}			/* end of xfer_count loop */
    }				/* end of write */

    bp_mapout (bp);
    biodone (bp);

    return (0);
}


static int
mdsp_write (dev_t dev, struct uio *uiop, cred_t *credp)
{
    int instance;
    struct mdsp_state *mdspsp;


    instance = getminor (dev);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);

    if (mdspsp == NULL)
    {
	return (ENXIO);
    }

    return (physio (mdsp_strategy, NULL, dev, B_WRITE, mdsp_minphys, uiop));
}


static int
mdsp_read (dev_t dev, struct uio *uiop, cred_t *credp)
{
    int instance;
    struct mdsp_state *mdspsp;


    instance = getminor (dev);
    mdspsp = (struct mdsp_state *) ddi_get_soft_state (state_head, instance);

    if (mdspsp == NULL)
    {
	return (ENXIO);
    }

    return (physio (mdsp_strategy, NULL, dev, B_READ, mdsp_minphys, uiop));
}

