/*
 * btncpyctl.c - tool to manipulate the /dev/btncpy device on a ZyXEL nas
 *
 * Copyright (c) 2014 Mijzelf.
 *
 *  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.
 *
 */

/*
 * A tool to intercept the Copy button function. But it can do more, 
 * like dis- and enabling WOL on a 325. The tool is based on the 
 * ZyXEL GPL kernel sources, which implements 'the other end'.
 *
 * Change Log:
 * ---------------
 *
 * Version 1.1 2015-11-02
 *
 * Added:
 * - Version 
 * - An option to choose a different device node. The NAS500 uses /dev/gpio
 *    instead of /dev/btncpy
 *
 * Version 1.0 2014-11-09
 *
 * Initial version
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/io.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define PROGRAM_VERSION	"1.1 (2015-11-02)" 


#define BTNCPY_IOC_MAGIC 'g'
#define BTNCPY_IOC_SET_NUM      _IO(BTNCPY_IOC_MAGIC, 1)

#define LED_SET_CTL_IOC_NUM     _IO(BTNCPY_IOC_MAGIC, 2)
#define LED_GET_CTL_IOC_NUM     _IO(BTNCPY_IOC_MAGIC, 3)

#define BUZ_SET_CTL_IOC_NUM     _IO(BTNCPY_IOC_MAGIC, 4)

#define SPR_HD0_IOC_NUM         _IO(BTNCPY_IOC_MAGIC, 5)
#define GPOUT_BLK_QUERY_IOC_NUM _IO(BTNCPY_IOC_MAGIC, 6)
#define BTN_POWER_ENABLE        _IO(BTNCPY_IOC_MAGIC, 7)
#define BTN_RESET_ENABLE        _IO(BTNCPY_IOC_MAGIC, 8)
#define BUTTON_TEST_IN_IOC_NUM  _IO(BTNCPY_IOC_MAGIC, 9)
#define BUTTON_TEST_OUT_IOC_NUM _IO(BTNCPY_IOC_MAGIC, 10)
#define WOL_ICO_ENABLE          _IO(BTNCPY_IOC_MAGIC, 22)
#define POWER_RESUME_SET        _IO(BTNCPY_IOC_MAGIC, 32)
#define POWER_RESUME_CLR        _IO(BTNCPY_IOC_MAGIC, 33)

#define LED_COLOR_BITS  0       // 0,1
#define LED_STATE_BITS  2       // 2,3
#define LED_NUM_BITS    4       // 4,5


#define LED_STATE_MAP_ADDR(led_num, led_state, led_color)       \
((led_num & 0xf) << LED_NUM_BITS) | ((led_state & 0x3) << LED_STATE_BITS) | ((led_color & 0x3) << LED_COLOR_BITS)

#define GET_LED_INDEX(map_addr) ((map_addr >> LED_NUM_BITS) & 0xf)
#define GET_LED_COLOR(map_addr) (map_addr & 0x3)
#define GET_LED_STATE(map_addr) ((map_addr >> LED_STATE_BITS) & 0x3)


#define BZ_TIMER_PERIOD (HZ/2)

#define TIME_BITS       5
#define FREQ_BITS       4
#define STATE_BITS      2

#define TIME_MASK       (0x1F << (FREQ_BITS + STATE_BITS))
#define FREQ_MASK       (0xF << STATE_BITS)
#define STATE_MASK      0x3


#define GET_TIME(addr)  ((addr & TIME_MASK) >> (FREQ_BITS + STATE_BITS))
#define GET_FREQ(addr)  ((addr & FREQ_MASK) >> STATE_BITS)
#define GET_STATE(addr) (addr & STATE_MASK)

#define BUZ_GPIO_MAP_ADDR(time, freq, state)    \
        ((time & 0x1f) << (FREQ_BITS + STATE_BITS) | ((freq & 0xf) << STATE_BITS) | (state & 0x3))



typedef struct {
	int cmd, numargs, retvalue;
	
	const char *name, *desc;
} COMMAND;

static const COMMAND _commands[] = {
{ BTNCPY_IOC_SET_NUM, 1, 0, "BTNCPY_IOC_SET_NUM", "pid\n"
	"\t\tSet the pid of the btncpy daemon. A signal 10 is send when the copy button is pressed shortly,\n"
	"\t\ta signal 12 is send when the button is pressed long. (In both cases at release of the button." },
{ LED_SET_CTL_IOC_NUM, 3, 0, "LED_SET_CTL_IOC_NUM", "index color state\n"
	"\t\tSet led. (index < 16, color < 4, state < 4)" },
{ LED_GET_CTL_IOC_NUM, 1, 1, "LED_GET_CTL_IOC_NUM", "index\n"
	"\t\tGet led status"  },
{ BUZ_SET_CTL_IOC_NUM, 3, 0, "BUZ_SET_CTL_IOC_NUM", "time freq state\n"
	"\t\tSet buzzer state (time < 32, freq < 16, state < 4)" },
{ BUTTON_TEST_IN_IOC_NUM, 2, 0, "BUTTON_TEST_IN_IOC_NUM", "pid button\n"
	"\t\tSend signal 10 to pid when button is pressed (0 reset, 1 copy, 2 power)\n"
	"\t\tNote: this resets the 'BTNCPY_IOC_SET_NUM' pid" },
{ BUTTON_TEST_OUT_IOC_NUM, 0, 0, "BUTTON_TEST_OUT_IOC_NUM", "\n"
	"\t\tResets 'BUTTON_TEST_OUT_IOC_NUM'" },
{ SPR_HD0_IOC_NUM, 1, 0, "SPR_HD0_IOC_NUM", "arg\n\t\t?" },
{ GPOUT_BLK_QUERY_IOC_NUM, 1, 1, "GPOUT_BLK_QUERY_IOC_NUM", "arg\n\t\t?" },
{ BTN_POWER_ENABLE, 1, 0, "BTN_POWER_ENABLE", "0|1\n"
	"\t\tDis- or enable power button?" },
{ BTN_RESET_ENABLE, 1, 0, "BTN_RESET_ENABLE", "0|1\n"
	"\t\tDis- or enable reset button?" },
{ WOL_ICO_ENABLE, 1, 0, "WOL_ICO_ENABLE", "0|1\n"
	"\t\tDis- or enable wol on shutdown." },
{ POWER_RESUME_SET, 0, 0, "POWER_RESUME_SET", "\n"
	"\t\tSet in low power mode?" },
{ POWER_RESUME_CLR, 0, 0, "POWER_RESUME_CLR", "\n"
	"\t\tRelease from low power mode?" },
{ 0, } };

void help()
{
	int i;
	printf( "A tool to control the /dev/btncpy device on a ZyXEL nas.\n"
                "version " PROGRAM_VERSION "\n"
		"\n"
		"Usage: btncpyctl <options> command <args>\n"
		"Commands:\n" );

	for( i=0; _commands[ i ].cmd; i++ )
		printf( "\t%s %s\n", _commands[ i ].name, _commands[ i ].desc );

	printf(	"Options:\n"
		"\t-d <devicenode>: Alternate device node to use.\n"
			"\t\tBy default /dev/btncpy. Should be /dev/gpio on a NAS5xx\n"
		"\t-q: Quiet mode.\n"
		"\t-v: Print version and exit.\n"
		"\t-h: This page.\n"
		"\n"
		"Note: not all devices support all commands\n"
		);

	exit( 0 );
}

const COMMAND *FindCommand( const char *name )
{
	const COMMAND *pret = 0;
	int i;
	size_t len = strlen( name );
	if( 0 == len )
		return 0;
	
	for( i=0; _commands[ i ].cmd; i++ )
		if( 0 == strncasecmp( name, _commands[ i ].name, len ) )
		{
			if( 0 != pret )
			{
				printf( "Ambiguous command %s\n", name );
				return 0;
			}
			pret = &_commands[ i ];
		}
		
	if( pret )
		return pret;
		
	printf( "Unrecognized command %s\n", name );
	return 0;
}

int main( int argc, char **argv )
{
	int i, quiet=0, args[ 4 ] = { 0, }, numargs=0;
	const COMMAND *pcommand=0;
	char *devicenode=strdup("/dev/btncpy");
	
	for( i=1; i<argc; i++ )
	{
		if( '-' == argv[ i ][ 0 ] )
		{
			switch( argv[ i ][ 1 ] )
			{
				case 'd':
					free(devicenode);
					if( i + 1 >= argc )
					{
						printf( "-d needs an argument\n" );
						help();
						break;
					}
					devicenode=strdup( argv[ i + 1 ] );
					i++;
					break;
					
				case 'h':
				case '?':
					help();
					break;
				case 'v':
					printf( PROGRAM_VERSION "\n" );
					exit( 0 );
				case 'q':
					quiet=1;
					break;
				default: 
					printf( "Unknown option %c\n", argv[ i ][ 1 ] );
					help();
					break;
			}
		}
		else if( 0 == pcommand )
		{
			pcommand = FindCommand( argv[ i ] );
			if( 0 == pcommand )
			{
				help();
			}
		}
		else
		{
			if( numargs >= pcommand->numargs )
				printf( "%s doesn't take %d args\n", pcommand->name, numargs + 1 );
			args[ numargs++ ] = atoi( argv[ i ] );
		}
	}
	
	if( 0 == pcommand )
	{
		help();
	}
	else
	{
		if( numargs != pcommand->numargs )
		{
			printf( "%s needs %d arguments\n", pcommand->name, pcommand->numargs );
			help();
		}
		
		int fdes = open( devicenode, O_RDWR ), ret;
		if( 0 > fdes )
		{
			if( !quiet )
				printf( "Cannot open %s. Error %d\n", devicenode, fdes );
			return fdes;
		}

		switch( pcommand->cmd )
		{
			default:
				ret = ioctl( fdes, pcommand->cmd, args[ 0 ] );
				break;
			case LED_SET_CTL_IOC_NUM:
				ret = ioctl( fdes, pcommand->cmd, LED_STATE_MAP_ADDR( args[ 0 ], args[ 1 ], args[ 2 ] ) );
				break;
			case BUTTON_TEST_IN_IOC_NUM:
				ret = ioctl( fdes, pcommand->cmd, (args[ 0 ] << 3) | (args[ 1 ] & 0x7) );
				break;
			case BUZ_SET_CTL_IOC_NUM:
				ret = ioctl( fdes, pcommand->cmd, BUZ_GPIO_MAP_ADDR( args[ 0 ], args[ 1 ], args[ 2 ] ) );
		}
		
		close( fdes );
		
		if( pcommand->retvalue )
		{
			if( !quiet )
				printf( "Return value is %d\n", ret );
			return ret;
		}
		else
		{
			if( 0 == ret )
			{
				if( !quiet )
					printf( "Succeeded\n" );
				return 0;
			}
		
			if( !quiet )
				printf( "Failed. Error %d\n", ret );
			return ret;
		}
	}

	return 0;
}

