#ifdef CONFIG_XENO_DRIVERS_ANALOGY_LOOP
#ifdef CONFIG_XENO_DRIVERS_ANALOGY_FAKE
#ifdef CONFIG_XENO_DRIVERS_ANALOGY
#include <linux/module.h>
#include <analogy/analogy_driver.h>

#define LOOP_TASK_PERIOD 1000000
#define LOOP_NB_BITS 16

#define LOOP_INPUT_SUBD 0 
#define LOOP_OUTPUT_SUBD 1

/* Channels descriptor */
static a4l_chdesc_t loop_chandesc = {
	.mode = A4L_CHAN_GLOBAL_CHANDESC,
	.length = 8,
	.chans = { 
		{A4L_CHAN_AREF_GROUND, LOOP_NB_BITS},
	},
};

/* Ranges tab */
static a4l_rngtab_t loop_rngtab = {
	.length =  2,
	.rngs = {
		RANGE_V(-5,5),
		RANGE_V(-10,10),
	},
};
/* Ranges descriptor */
a4l_rngdesc_t loop_rngdesc = RNG_GLOBAL(loop_rngtab);

/* Command options mask */
static a4l_cmd_t loop_cmd_mask = {
	.idx_subd = 0,
	.start_src = TRIG_NOW,
	.scan_begin_src = TRIG_TIMER,
	.convert_src = TRIG_NOW|TRIG_TIMER,
	.scan_end_src = TRIG_COUNT,
	.stop_src = TRIG_COUNT|TRIG_NONE,
};

/* Private data organization */
struct loop_priv {

	/* Task descriptor */
	a4l_task_t loop_task;

	/* Misc fields */
	volatile int loop_running:1;
	uint16_t loop_insn_value;
};
typedef struct loop_priv lpprv_t;

/* Attach arguments contents */
struct loop_attach_arg {
	unsigned long period;
};
typedef struct loop_attach_arg lpattr_t;

static void loop_task_proc(void *arg);

/* --- Task part --- */

/* Timer task routine  */
static void loop_task_proc(void *arg)
{
	a4l_dev_t *dev = (a4l_dev_t*)arg;
	a4l_subd_t *input_subd, *output_subd;
	lpprv_t *priv = (lpprv_t *)dev->priv;
    
	while (!a4l_check_dev(dev))
		a4l_task_sleep(LOOP_TASK_PERIOD);

	input_subd = a4l_get_subd(dev, LOOP_INPUT_SUBD);
	output_subd = a4l_get_subd(dev, LOOP_OUTPUT_SUBD);

	if (input_subd == NULL || output_subd == NULL) {
		a4l_err(dev, "loop_task_proc: subdevices unavailable\n");
		return;
	}

	while (1) {
	
		if (priv->loop_running) {
			uint16_t value;
			int ret=0;
	    
			while (ret==0) {
		
				ret = a4l_buf_get(output_subd, 
						  &value, sizeof(uint16_t));

				if (ret == 0) {

					a4l_info(dev, 
						 "loop_task_proc: "
						 "data available\n");

					a4l_buf_evt(output_subd, 0);
		    
					ret = a4l_buf_put(input_subd, 
							  &value, 
							  sizeof(uint16_t));

					if (ret==0)
						a4l_buf_evt(input_subd, 0);
				}
			}
		}

		a4l_task_sleep(LOOP_TASK_PERIOD);
	}
}

/* --- Analogy Callbacks --- */

/* Command callback */
int loop_cmd(a4l_subd_t *subd, a4l_cmd_t *cmd)
{
	lpprv_t *priv = (lpprv_t *)subd->dev->priv;

	a4l_info(subd->dev, "loop_cmd: (subd=%d)\n", subd->idx);

	priv->loop_running = 1;
  
	return 0;
  
}

/* Cancel callback */
int loop_cancel(a4l_subd_t *subd)
{
	lpprv_t *priv = (lpprv_t *)subd->dev->priv;

	a4l_info(subd->dev, "loop_cancel: (subd=%d)\n", subd->idx);

	priv->loop_running = 0;

	return 0;
}

/* Read instruction callback */
int loop_insn_read(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	lpprv_t *priv = (lpprv_t*)subd->dev->priv;
	uint16_t *data = (uint16_t *)insn->data;

	/* Checks the buffer size */
	if (insn->data_size != sizeof(uint16_t))
		return -EINVAL;

	/* Sets the memorized value */
	data[0] = priv->loop_insn_value;
    
	return 0;
}

/* Write instruction callback */
int loop_insn_write(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	lpprv_t *priv = (lpprv_t*)subd->dev->priv;
	uint16_t *data = (uint16_t *)insn->data;	

	/* Checks the buffer size */
	if (insn->data_size != sizeof(uint16_t))
		return -EINVAL;

	/* Retrieves the value to memorize */
	priv->loop_insn_value = data[0];
    
	return 0;
}

void setup_input_subd(a4l_subd_t *subd)
{
	memset(subd, 0, sizeof(a4l_subd_t));

	subd->flags |= A4L_SUBD_AI;
	subd->flags |= A4L_SUBD_CMD;
	subd->flags |= A4L_SUBD_MMAP;
	subd->rng_desc = &loop_rngdesc;
	subd->chan_desc = &loop_chandesc;
	subd->do_cmd = loop_cmd;
	subd->do_cmdtest = NULL;
	subd->cancel = loop_cancel;
	subd->cmd_mask = &loop_cmd_mask;
	subd->insn_read = loop_insn_read;
	subd->insn_write = loop_insn_write;
}

void setup_output_subd(a4l_subd_t *subd)
{
	memset(subd, 0, sizeof(a4l_subd_t));

	subd->flags = A4L_SUBD_AO;
	subd->flags |= A4L_SUBD_CMD;
	subd->flags |= A4L_SUBD_MMAP;
	subd->rng_desc = &loop_rngdesc;
	subd->chan_desc = &loop_chandesc;
	subd->insn_read = loop_insn_read;
	subd->insn_write = loop_insn_write;
}

/* Attach callback */
int loop_attach(a4l_dev_t *dev,
		a4l_lnkdesc_t *arg)
{
	int ret = 0;
	a4l_subd_t *subd;
	lpprv_t *priv = (lpprv_t *)dev->priv;

	/* Add the fake input subdevice */
	subd = a4l_alloc_subd(0, setup_input_subd); 
	if (subd == NULL)
		return -ENOMEM;  

	ret = a4l_add_subd(dev, subd);
	if (ret != LOOP_INPUT_SUBD)
		/* Let Analogy free the lately allocated subdevice */
		return (ret < 0) ? ret : -EINVAL;

	/* Add the fake output subdevice */
	subd = a4l_alloc_subd(0, setup_output_subd); 
	if (subd == NULL)
		/* Let Analogy free the lately allocated subdevice */
		return -ENOMEM;  

	ret = a4l_add_subd(dev, subd);
	if (ret != LOOP_OUTPUT_SUBD)
		/* Let Analogy free the lately allocated subdevices */
		return (ret < 0) ? ret : -EINVAL;

	priv->loop_running = 0;
	priv->loop_insn_value = 0;

	ret = a4l_task_init(&priv->loop_task, 
			    "a4l_loop task", 
			    loop_task_proc,
			    dev, A4L_TASK_HIGHEST_PRIORITY);

	return ret;
}

/* Detach callback */
int loop_detach(a4l_dev_t *dev)
{
	lpprv_t *priv = (lpprv_t *)dev->priv;

	a4l_task_destroy(&priv->loop_task);

	return 0;
}

/* --- Module part --- */

static a4l_drv_t loop_drv = {
	.owner = THIS_MODULE,
	.board_name = "analogy_loop",
	.attach = loop_attach,
	.detach = loop_detach,
	.privdata_size = sizeof(lpprv_t),
};

static int __init a4l_loop_init(void)
{
	return a4l_register_drv(&loop_drv);
}

static void __exit a4l_loop_cleanup(void)
{
	a4l_unregister_drv(&loop_drv);
}

MODULE_DESCRIPTION("Analogy loop driver");
MODULE_LICENSE("GPL");

module_init(a4l_loop_init);
module_exit(a4l_loop_cleanup);
#endif
#endif
#endif
