/** @file sample_apcie1564.c
*
* @author Sylvain Nahas
*
* This software 
* - scans the /proc/sys/apcie1564/ directory
* - run a serie of test on each detected boards
*/

/** @par LICENCE
* @verbatim
 Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code and the documentation.
        
        ADDI-DATA GmbH
        Dieselstrasse 3 
        D-77833 Ottersweier
        Tel: +19(0)7223/9493-0
        Fax: +49(0)7223/9493-92
        http://www.addi-data-com
        info@addi-data.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details. 
        
    You shoud find the complete GPL in the COPYING file accompanying 
    this source code. 
* @endverbatim
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/sysmacros.h> /* major() minor() */

#include <apcie1564.h>
#include <apcie1564-events.h>

#define ASSERT( __EXP ) \
{\
	if ( ! (__EXP) ) \
	{ \
		printf("\n! %s:%s():%d:assertion \"%s\" failed\n",__FILE__,__FUNCTION__,__LINE__,#__EXP); \
		perror("error"); \
		exit(1); \
	}\
}

//--------------------------------------------------------------------------------
static void usage(void)
{
	printf("testapcie1564 COMMAND1 [COMMAND2] ... [COMMANDN] \n" \
	"FILE is the path of a special device file\n" \
	);
	
}

//------------------------------------------------------------------------------
/** Filter the "." and ".." entries in the struct dirent ** area set up by the scandir() function (utility function).*/
int
get_real_dir_entry (const struct dirent * ent)
{
        if (! strcmp(ent->d_name,"."))
                return 0;
        if (! strcmp(ent->d_name,".."))
                return 0;
                                
        return 1;
}

//--------------------------------------------------------------------------------
/** this function look up for cards in the /proc/sys/apcie1564/ directory
* @param cards A pointer to the card area to fill
* @return The number of cards detected (may be zero if nor cards found).
*/
int apcie1564_find_cards(int ** cards)
{
	
	struct dirent **eps;
	int cnt = 0; // number of card found
	char APCI_SPECIAL_FILE_DIR[255]={0}; 
	char proc_path_template[255]={0};

	/* Test which node is availabe */
	{
		struct stat file_stat;
		const char PROC_PATH[]="/proc/sys/apcie1564";
		const char DEV_PATH[]="/dev/apcie1564/";

		if (stat(PROC_PATH,&file_stat) == 0)
		{
				/* Kernel 2.4 */
				strncpy(APCI_SPECIAL_FILE_DIR, PROC_PATH, strlen(PROC_PATH));
				strcpy(proc_path_template, "%s%s/ctrl");
		}
		else if (stat(DEV_PATH,&file_stat) == 0)
		{
				/* Kernel 2.6 */
				strncpy(APCI_SPECIAL_FILE_DIR, DEV_PATH, strlen(DEV_PATH));
				strcpy(proc_path_template, "%s%s");
		}
		else
		{
				perror("fstat");
				exit(1);
		}
	}
	
	/* scan directory for card entry */
	if ( (cnt = scandir (APCI_SPECIAL_FILE_DIR, &eps, get_real_dir_entry , alphasort)) == -1 )
	{
		perror("scandir");
		return 0;
	}	
	
	if (cnt == 0) return 0;
	
	/* allocate cards area */
	if ( ! ( (*cards) = malloc( cnt * sizeof(int) ) ) )
	{
		perror("malloc");
		free(eps);
		return 0;
	}
	
	/* for each card entry, open it and store file descriptor in cards[] */
	{
		int i;
		for (i=0; i< cnt; i++)
		{
			int fd;
			char name[100];
			
			snprintf(name,sizeof(name),proc_path_template,APCI_SPECIAL_FILE_DIR,eps[i]->d_name);

			if ( ( fd = open(name, O_RDWR)) == -1 )
			{
				perror("open");
				exit(1);
			}			
									
			{
				struct stat file_stat;
				if ( fstat(fd,&file_stat) )
				{
					perror("fstat");
					exit(1);
				}			
				if (!S_ISCHR(file_stat.st_mode)) 
				{
					/* should never happen !*/
					printf("file %d is not a character device\n",i);
					exit(1);
				}
				printf("%s, major:%d minor:%d \n",name, major(file_stat.st_rdev),minor(file_stat.st_rdev));
			
				(*cards)[minor(file_stat.st_rdev)] = fd;
			} 
			/* index device file descriptor by the minor number */
			
		}	
		
		
		
	}
	
	
	
	return cnt;
	
	
}

//--------------------------------------------------------------------------------

int do_chenillard(int fd, int CMD)
{
    uint32_t outputs;

	int i = 1;
	while (i < 0x80000000 )
	{
		if ( ioctl (fd,CMD,i) == -1)
		{
			perror("ioctl");
			return 1;
		}		
		
        if ( ioctl(fd, CMD_APCIE1564_Read32DigitalOutput, &outputs) == -1)
        {
			perror("ioctl(CMD_APCIE1564_Read32DigitalOutput)" );
			return 1;
        }
        printf ( "Digital Outputs read: 0x%08X\n", outputs);

        usleep(200000);
		i= i << 1;
	}	
	return 0;
}

//------------------------------------------------------------------------------

void apcie1564_sample_output(int fd)
{
	printf("=> CMD_APCIE1564_SetOutputMemoryOff\n");
	{
		
		if ( ioctl (fd,CMD_APCIE1564_SetOutputMemoryOff,NULL) == -1)
		{
			perror("ioctl");
			exit(1);
		}

	}		
	
	printf("=> CMD_APCIE1564_Set32DigitalOutputOn with output memory OFF\n");
	{
		
		if ( do_chenillard(fd,CMD_APCIE1564_Set32DigitalOutputOn) )
			exit(1);
			
		printf("\n");
	}	

	printf("=> CMD_APCIE1564_SetOutputMemoryOn\n");
	{
		
		if ( ioctl (fd,CMD_APCIE1564_SetOutputMemoryOn,NULL) == -1)
		{
			perror("ioctl");
			exit(1);
		}	
	}		
	
	printf("=> CMD_APCIE1564_Set32DigitalOutputOn with output memory ON\n");
	{
		
		if ( do_chenillard(fd,CMD_APCIE1564_Set32DigitalOutputOn) )
			exit(1);
			
		printf("\n");
	}	
	
	printf("=> CMD_APCIE1564_Set32DigitalOutputOff with output memory ON\n");
	{
		
		if ( do_chenillard(fd,CMD_APCIE1564_Set32DigitalOutputOff) )
			exit(1);
			
		printf("\n");
	}		
}

//------------------------------------------------------------------------------

void apcie1564_sample_input_withoutinterrupt(int fd)
{
	printf("=> CMD_APCIE1564_Read32DigitalInput\n");
	{
		unsigned int port;

		if ( ioctl (fd,CMD_APCIE1564_Read32DigitalInput,&port) == -1)
		{
			perror("ioctl");
			exit(1);
		}
		printf ("%u\n",port);
	}					
	
}

void apcie1564_run_test(int fd, char* command);
//------------------------------------------------------------------------------
/* this function illustrate the use of digital input event (how to get interrupts in user-space)*/
void apcie1564_sample_input_event(int fd)
{
	printf("* testing digital input interrupt\n");
	
	printf("=> CMD_APCIE1564_ClearAndStopDigitalInputInterrupt\n");
	{   
		if ( ioctl (fd,CMD_APCIE1564_ClearAndStopDigitalInputInterrupt,NULL) == -1)
		{   
			perror("ioctl");
		}   
      
		printf("\n");   
	}
	printf("=> CMD_APCIE1564_InitAndStartDigitalInputInterrupt\n");
	{   
		struct 
		{ 
			int InterruptLogic;
 			uint32_t InterruptMaskMode1;
			uint32_t InterruptMaskMode2;
		} params;
		
		/* configure all inputs for logic OR, both edges */
		params.InterruptLogic = 0;
		params.InterruptMaskMode1 = 0x000FFFF0;
		params.InterruptMaskMode2 = 0x000FFFF0;
		
		ASSERT( ioctl (fd,CMD_APCIE1564_InitAndStartDigitalInputInterrupt,&params) == 0 );
		printf("\n");
	}
	printf("=> CMD_APCIE1564_GetDigitalInputInterruptConfig\n");
	{   
		struct 
		{
			int Enabled; 
			int InterruptLogic;
 			uint32_t InterruptMaskMode1;
			uint32_t InterruptMaskMode2;
		} params;
		ASSERT ( ioctl (fd,CMD_APCIE1564_GetDigitalInputInterruptConfig,&params) == 0 );
   
      	printf("Enabled: %X\n",params.Enabled);
      	ASSERT( params.Enabled == 1 );
      	printf("InterruptLogic: %X\n",params.InterruptLogic);
      	ASSERT( params.InterruptLogic == 0 );
      	printf("InterruptMaskMode1: %X\n",params.InterruptMaskMode1);
      	ASSERT( params.InterruptMaskMode1 == 0x000FFFF0 );
      	printf("InterruptMaskMode2: %X\n",params.InterruptMaskMode2);
      	ASSERT( params.InterruptMaskMode2 == 0x000FFFF0 );
		printf("\n");
	}
	/* Empty the event buffer */
	printf("=> CMD_APCIE1564_EventEmpty\n");
	{   
		ASSERT ( ioctl (fd,CMD_APCIE1564_EventEmpty,NULL) == 0 );
		printf("\n");
	}
	printf("=> looping on CMD_APCIE1564_EventWait\n");
	printf("\tplease generate interrupts\n");
	printf("\tto stop type CTRL+C \n");
	while(1)
	{   
		apcie1564_event_t evt;
		ASSERT ( ioctl (fd,CMD_APCIE1564_EventWait, &evt) == 0 );
		printf("source: %d\n",evt.source);
		printf("param1: 0x%lX\n",evt.param1);
		printf("\n");
	}
}
//--------------------------------------------------------------------------------
/* this function illustrate the use of a counter (counter 0) and events
 * 
 * the counter is initalised with the following parameters:
 * - reload value is 3
 * - direction is up
 * - level is 1
 */
void apcie1564_sample_counter(int fd)
{
	int index = 0;
	printf("\tcounter %d with events\n",index);
	printf("=> CMD_APCIE1564_CounterReset [counter%d]\n",index);
	{
		ASSERT( ioctl (fd,CMD_APCIE1564_CounterReset,index) == 0 );
	}
	printf("\tcounter %d with reload value 3\n",index);
	{
		struct
		{
			unsigned long CounterIndex; 
			uint32_t ReloadValue;
			unsigned long DirectionSelection;
			unsigned long LevelSelection;
		} param;
		{
			param.CounterIndex = index;
			param.ReloadValue = 3;
			param.DirectionSelection = 0; 
			param.LevelSelection = 1;
			ASSERT( 0 == ioctl (fd,CMD_APCIE1564_CounterInitAndStart,&param) );
		}
		{
			uint32_t Value = index;
			ASSERT( 0 == ioctl (fd,CMD_APCIE1564_ReadCounterValue,&Value) );
			ASSERT( Value == 3 );
		}
		
		printf("=> please generates events on counter %d\n",index);
		
		{
			printf("=> CMD_APCIE1564_EventEmpty\n");
			{   
				ASSERT ( ioctl (fd,CMD_APCIE1564_EventEmpty,NULL) == 0 );
			}
			while(1)
			{
				/* read a character from input */
				printf("\nPRESS A KEY TO GO ON\n");
				printf("pressing 'q' leaves the loop\n");	
				if ('q' == getchar())
					break;
				printf("=> CMD_APCIE1564_EventWait\n");
				{   
					apcie1564_event_t evt;
					ASSERT ( ioctl (fd,CMD_APCIE1564_EventWait, &evt) == 0 );
					printf("source: %d\n",evt.source);
					printf("param1: 0x%lX\n",evt.param1);
					printf("\n");
				}
			}
		}
	}
}
//--------------------------------------------------------------------------------
void apcie1564_sample_all(int fd)
{
	apcie1564_run_test(fd, "output");
	apcie1564_run_test(fd, "input");
}


//--------------------------------------------------------------------------------
void apcie1564_sample_help(int fd);
//--------------------------------------------------------------------------------

static struct {char* name; char * desc; void (*fc) (int) ;} commandtab[] =
{
	{
		"output",
		"test output",
		apcie1564_sample_output
	},
	{
		"input",
		"input without event",
		apcie1564_sample_input_withoutinterrupt
	},
	{
		"inputevt",
		"input with event",
		apcie1564_sample_input_event
	},
	{
		"counterevt",
		"counter with event",
		apcie1564_sample_counter
	},
	{
		"help",
		"show this help",
		apcie1564_sample_help
	},
	{
		"all",
		"output input",
		apcie1564_sample_all
	},
	{NULL,NULL,NULL}
};

//--------------------------------------------------------------------------------
void apcie1564_sample_help(int fd)
{
	int i = 0;
	printf("\navailable commands:\n");
	while (commandtab[i].name != NULL)
	{
		
		printf("%s\t%s\n",commandtab[i].name,commandtab[i].desc);
		i++;
	}
	printf("\n");
}
//------------------------------------------------------------------------------

void apcie1564_run_test(int fd, char* command)
{
	int i = 0;
	while (commandtab[i].name != NULL)
	{
		if (strcmp(command, commandtab[i].name) == 0 )
		{
			commandtab[i].fc (fd);
			return;
		}
		i++;
	}
	printf("%s: unknown command\n",command);
	apcie1564_run_test(fd, "help");
	exit(1);	

}
//--------------------------------------------------------------------------------
int main(int argc, char** argv)
{

	int * apcie1564_card_fd;
	unsigned int apcie1564_card_number = 0;
		
	if (argc < 2 )
	{
		printf("not enough arguments\n");
		usage();
		apcie1564_sample_help(0);
		exit(1);
		
	}

	{
		apcie1564_card_number = apcie1564_find_cards(&apcie1564_card_fd);
		
		printf("total: %d cards\n",apcie1564_card_number);
	}	
	
	{
		int i;
		for (i=0;i< apcie1564_card_number; i++)
		{
			printf("\nrunning test on device of minor number %d\n",i);
			{
				int j = 0;
				for ( j=1; j< argc; j++)
					apcie1564_run_test(apcie1564_card_fd[i], argv[j]);
			}
		}
	}
	return 0;
}
