#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/spi/spidev.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define LETIL_MODEL_ID_AK130        0x01
#define LETIL_MODEL_ID_AK130_ES2    0x02
#define LETIL_MODEL_ID LETIL_MODEL_ID_AK130_ES2
#if( LETIL_MODEL_ID == LETIL_MODEL_ID_AK130 )
#define LETIL_MODEL_NAME "AK130"
#else
#define LETIL_MODEL_NAME "AK-130 ES2"
#endif


int spi_read( int fd, uint32_t reg, int num, char* rx_buf );
int spi_write( int fd, uint32_t reg, uint16_t value );

void read_help( void )
{
    printf("Usage: mmd-read [PHY] [MMD] [REGISTER]\r\n");
    printf("Read "LETIL_MODEL_NAME" KSZ9896 MMD register v0.1.\r\n\r\n");
    printf("       --help   display this help and exit.\r\n");
}

void write_help( void )
{
    printf("Usage: mmd-write [PHY] [MMD] [REGISTER] [VALUE]\r\n");
    printf("Write "LETIL_MODEL_NAME" KSZ9896 MMD register v0.1.\r\n\r\n");
    printf("       --help   display this help and exit.\r\n");
}

int main( int argc, char** argv )
{
	struct timeval delay;
    char port_status=0;
    uint32_t spi_mode=0;
    int fd,ret=0;
    uint16_t reg = 0;
	uint16_t value = 0;
	uint8_t phy=1;
	uint8_t mmd=1;
	uint32_t spi_reg=0, spi_value=0;

	delay.tv_sec = 0;
	delay.tv_usec = 20*1000; //20ms

	fd = open( "/dev/spidev0.0", O_RDWR|O_SYNC);

    if( fd == -1 )
    {
        printf( "Open SPI failed..\r\n");
        return -1;
    }
    ioctl( fd, SPI_IOC_RD_MODE32, &spi_mode );


	if( spi_mode != 3 )
	{
        printf( "spi mode: %d\r\n", spi_mode);
		spi_mode = 3;
		ret = ioctl( fd, SPI_IOC_WR_MODE, &spi_mode);
		if( ret == -1 )
		{
			printf("Can't change SPI mode!!\r\n");
			close(fd);
			return -1;
		}
		ioctl( fd, SPI_IOC_RD_MODE32, &spi_mode );
        printf( "spi mode change to mode %d.\r\n", spi_mode);
	}

	if( strstr( argv[0], "mmd-write")!=0 )
	{
		if( argc == 5 )
		{
			phy = (uint8_t)strtol( argv[1], 0, 0 );
			mmd = (uint8_t)strtol( argv[2], 0, 0 );
			reg = (uint16_t)strtol( argv[3], 0, 0 );
			value = (uint16_t)strtol( argv[4], 0, 0 );
			// MMD Setup register - set register address
			spi_reg =  0x011A | (uint16_t)phy<<12;
			spi_value = mmd;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Data register - select register
			spi_reg =  0x011C | (uint16_t)phy<<12;
			spi_value = reg;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Setup register - set register data
			spi_reg =  0x011A | (uint16_t)phy<<12;
			spi_value = 0x4000|mmd;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Data register - write data
			spi_reg =  0x011C | (uint16_t)phy<<12;
			spi_value = value;
			ret = spi_write( fd, spi_reg, spi_value);
		}
		else
			write_help();
	}
	else
	{
		if( argc == 4 )
		{
			phy = (uint8_t)strtol( argv[1], 0, 0 );
			mmd = (uint8_t)strtol( argv[2], 0, 0 );
			reg = (uint16_t)strtol( argv[3], 0, 0 );
			// MMD Setup register - set register address
			spi_reg =  0x011A | (uint16_t)phy<<12;
			spi_value = mmd;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Data register - select register
			spi_reg =  0x011C | (uint16_t)phy<<12;
			spi_value = reg;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Setup register - set register data
			spi_reg =  0x011A | (uint16_t)phy<<12;
			spi_value = 0x4000|mmd;
			ret = spi_write( fd, spi_reg, spi_value);
			select( 0, NULL, NULL, NULL, &delay);
			// MMD Data register - Read data
			spi_reg =  0x011C | (uint16_t)phy<<12;
			ret = spi_read( fd, spi_reg,2,  (char*)&spi_value);
			uint8_t* tmp = (uint8_t*)&spi_value;
			if( ret == 0 )
				printf("phy %d, mmd 0x%02X, register 0x%04X, value 0x%02X%02X\n",phy, mmd, reg, *tmp, *(tmp+1));
		}
		else
			read_help();
	}
	if( ret < 0 )
		printf("spi process failed!!\r\n");

	close(fd);
    return 0;
}

int spi_read( int fd, uint32_t reg, int num, char* rx_buf )
{
    char tx_buf[5] = {0};
    int ret;
    uint32_t reg_shift = 0;
    struct spi_ioc_transfer xfer[2];
    memset( xfer, 0x00, sizeof(xfer));

    reg_shift = reg<< 5;
    reg_shift |= 0x60000000;

    for(int i=3 ; i >= 0 ; i-- )
    {
        tx_buf[3-i] = (reg_shift >>(8*i))&0xFF;
    }


	xfer[0].tx_buf = (unsigned long)tx_buf;
	xfer[0].len = 4;
	xfer[1].rx_buf = (unsigned long)rx_buf;
	xfer[1].len = num;

    ret = ioctl( fd, SPI_IOC_MESSAGE(2), xfer);
    if( ret == -1 )
    {
		printf( "ioctl ret: %d, %s\r\n", ret, strerror(errno) );
        return -1;
    }
#if 1
    printf( "Read REG[0x%04X](len:%d):", reg, num);
    for( int i = 0 ; i < num ; i++ )
    {
            printf( " %02X", rx_buf[i]);
    }
    printf("\r\n");
	printf("ret: 0x%X\r\n", ret);
#endif
    return 0;
}

int spi_write( int fd, uint32_t reg, uint16_t value )
{
    char tx_buf[6] = {0};
    int ret;
    uint32_t reg_shift = 0;
    struct spi_ioc_transfer xfer;
	memset( &xfer, 0x00, sizeof(xfer));

    reg_shift = reg<< 5;
    reg_shift |= 0x40000000;

    for( int i = 3 ; i >= 0 ; i-- )
    {
        tx_buf[3-i] = (reg_shift >>(8*i))&0xFF;
    }

    tx_buf[4] = value>>8;
	tx_buf[5] = value&0xFF;

	xfer.tx_buf = (unsigned long)tx_buf;
	xfer.len = 6;

    ret = ioctl( fd, SPI_IOC_MESSAGE(1), &xfer);
    if( ret == -1 )
    {
		printf( "ioctl ret: %d, %s\r\n", ret, strerror(errno) );
        return -1;
    }

	#if 1
	    printf( "Write REG[0x%04X]:", reg);
	    for( int i = 0 ; i < 6 ; i++ )
	    {
	            printf( " %02X", tx_buf[i]);
	    }
	    printf("\r\n");
		printf("ret: 0x%X\r\n", ret);
	#endif
    return 0;
}
