#include <sys/cdefs.h>

//#include "opt_gpio.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
//#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/ctype.h>
#include <sys/errno.h>
#include <sys/sysctl.h>
#include <sys/file.h>
#include <sys/uio.h>
//#include <sys/socketvar.h>
//#include <sys/socket.h>
//#include <sys/protosw.h>
//#include <sys/proc.h>
#include <sys/ioccom.h>
#include <sys/queue.h>
//#include <sys/kthread.h>
//#include <sys/mbuf.h>
//#include <sys/syslog.h>
//#include <vm/uma.h>

#include <sys/lock.h>
#include <sys/sx.h>

#include <dev/gpio/gpio.h>

static struct unrhdr *gpio_unit;
#if 0
static struct callout gpio_ch;
#endif
static d_open_t gpio_open;
static d_close_t gpio_close;
static d_ioctl_t gpio_ioctl;
static d_read_t gpio_read;
static d_read_t gpio_write;

static struct cdevsw gpio_cdevsw = {
     .d_version = D_VERSION,
     .d_open	= gpio_open,
     .d_close	= gpio_close,
     .d_ioctl	= gpio_ioctl,
     .d_read	= gpio_read,
     .d_write	= gpio_write,
     .d_name	= "gpio",
};

static struct mtx gpio_mtx;
static struct sx gpio_sx;

struct gpiosc {
     LIST_ENTRY(gpiosc)	list;
     void		*private;
     int		unit;
     gpio_t		*func;
     struct cdev	*dev;
};

static LIST_HEAD(, gpiosc) gpio_list = LIST_HEAD_INITIALIZER(&gpio_list);
static MALLOC_DEFINE(M_GPIO, "GPIO", "GPIO driver");

static int
gpio_open(struct cdev *dev, int flags, int otype, struct thread *td)
{
     int error = 0;

     return error;
}

static int
gpio_close(struct cdev *dev, int flag, int otyp, struct thread *td)
{
     int error = 0;

     return error;
}

static int
gpio_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode, struct thread *td)
{
     int error = 0;

     return error;

}

static int
gpio_read(struct cdev *dev, struct uio *uio, int ioflag)
{
     int error = ENXIO;
     struct 	gpiosc *sc;
     char	c;

     mtx_lock(&gpio_mtx);
     sc = dev->si_drv1;
     if(sc != NULL) {
	  if(sc->func(sc->private, 2) == 0) // XXX: ask Dimi
	       c = '1';
	  else
	       c = '0';
	  error = uiomove(&c, 1, uio);
     }
     mtx_unlock(&gpio_mtx);

     return error;
}

static int
gpio_write(struct cdev *dev, struct uio *uio, int ioflag)
{
     int	error = 0;
     char	s;
     struct 	gpiosc *sc;

     s = '0';
     if(uio->uio_resid > 0)
	  error = uiomove(&s, 1, uio);
     if(error == 0) {
	  if(s == '0' || s == '1')
	       s -= '0';
	  else
	       return EINVAL;
	  mtx_lock(&gpio_mtx);
	  sc = dev->si_drv1;
	  if(sc != NULL)
	       sc->func(sc->private, s);
	  mtx_unlock(&gpio_mtx);
     }
     return error;
}

struct cdev *
gpio_create(gpio_t *func, void *priv, char const *name, int state)
{
     struct gpiosc	*sc;

     sc = malloc(sizeof *sc, M_GPIO, M_WAITOK | M_ZERO);

     sx_xlock(&gpio_sx);
     sc->unit = alloc_unr(gpio_unit);
     sc->private = priv;
     sc->func = func;
     sc->dev = make_dev(&gpio_cdevsw, unit2minor(sc->unit),
			UID_ROOT, GID_WHEEL, 0644, "gpio/%s", name);
     sx_xunlock(&gpio_sx);

     mtx_lock(&gpio_mtx);
     sc->dev->si_drv1 = sc;
#if 0
     if(LIST_EMPTY(&gpio_list))
	  callout_reset(&gpio_ch, hz / 10, gpio_timeout, NULL);
#endif
     LIST_INSERT_HEAD(&gpio_list, sc, list);
     sc->func(sc->private, state);
     mtx_unlock(&gpio_mtx);

     return (sc->dev);
     
}

static void
gpio_drvinit(void)
{
     printf("gpio_drvinit\n");
     gpio_unit = new_unrhdr(0, minor2unit(MAXMINOR), NULL);
     mtx_init(&gpio_mtx, "GPIOmtx", NULL, MTX_DEF);
     sx_init(&gpio_sx, "GPIO sx");
#if 0
     callout_init(&gpio_ch, CALLOUT_MPSAFE);
#endif
}

#if 0
void
gpio_destroy(struct cdev *dev)
{
	struct gpiosc *sc;

	mtx_lock(&gpio_mtx);
	sc = dev->si_drv1;
	dev->si_drv1 = NULL;

	LIST_REMOVE(sc, list);
	if (LIST_EMPTY(&gpio_list))
		callout_stop(&gpio_ch);
	mtx_unlock(&gpio_mtx);

	sx_xlock(&gpio_sx);
	free_unr(gpio_unit, sc->unit);
	destroy_dev(dev);
	if (sc->spec != NULL)
		sbuf_delete(sc->spec);
	free(sc, M_GPIO);
	sx_xunlock(&gpio_sx);
}
static void
gpio_drvstop(void)
{
}
#endif


SYSINIT(gpiodev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, gpio_drvinit, NULL);

