/************************************************************
**
**     Object name : 
**
**     Public      : 
**                   
**
**     Private     : 
**
**
**     Description
**     -- -- -- -- -- -- -- --
**     
**
**     History
**     -- -- -- -- -- -- -- --
**     2014.09.09 K.Takagi SPIレジスタ設定をHARKボード用に変更
**     2015.11.26 A.Koike  ライン入力対応
**
**     Copyright (c) 2014
**     System In Frontier,Inc.
**
**
**     printkのログレベル
**       KERN_EMESG
**         緊急用。クラッシュを引き起こすような状況。
**
**       KERN_ALERT
**         すぐに対応が必要な状況。
**
**       KERN_CRIT
**         危機的な状況。ハードウェア又はソフトウェアの深刻なエラー
**
**       KERN_ERR
**         エラー通知。ドライバがハードウェアのトラブルを通知するために使用
**
**       KERN_WARNING
**         疑わしい状況についての警告。
**
**       KERN_NOTICE
**         状況は正常だが、注意が必要。
**
**       KERN_INFO
**         情報メッセージ。
**
**       KERN_DEBUG
**         デバック用のメッセージ。
**
*/


/*=========================================================*/
/*               Special Macro Definition                  */
/*=========================================================*/
#ifndef MODULE
#define MODULE
#endif

#ifndef __KERNEL__
#define __KERNEL__
#endif


#define DEBUG_IOCTL
#define HARK_SPI
#undef  WAND_SPI

/*=========================================================*/
/*                   Include Files                         */
/*=========================================================*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/ioport.h>
#include <linux/workqueue.h>
#include <linux/delay.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include "drv.h"

/*=========================================================*/
/*                   Macro Definition                      */
/*=========================================================*/
MODULE_AUTHOR("SiF");
MODULE_DESCRIPTION("K.T");
//MODULE_LICENSE("Proprietary");
MODULE_LICENSE("GPL");// create_workqueue が GPLシンボル

#define DEVICE_NAME        "kt"
#define REGISTERED         0x1
#define NOT_REGISTERED     0x0


/* デバック用のマクロ */
#ifdef  DEBUG_IOCTL

#define DEBUG_IOCTL_PRINT      printk

#else

#define DEBUG_IOCTL_PRINT(...)

#endif


#define init_MUTEX(a)            sema_init(a,1)

#define AD_FIFO_SIZE             (1024 * 32)
#define AD_BUFFER_SIZE           (AD_FIFO_SIZE * 4)

#define DA_FIFO_SIZE             (2048 * 1)
#define DA_BUFFER_SIZE           (DA_FIFO_SIZE * 2)

#define IMX6Q_ECSPI1_BASEADDR    (0x02008000)
#define IMX6Q_ECSPI2_BASEADDR    (0x0200c000)
#define IMX6Q_ECSPI3_BASEADDR    (0x02010000)
#define IMX6Q_ECSPI4_BASEADDR    (0x02014000)
#define IMX6Q_ECSPI5_BASEADDR    (0x02018000)
#define IMX6Q_ECSPIx_REGSIZE     (0x00004000)
#define	IMX6Q_CCM_BASEADDR       (0x020c4000)
#define	IMX6Q_CCM_REGSIZE        (0x00004000)
#define	IMX6Q_IOMUXC_BASEADDR    (0x020e0000)
#define	IMX6Q_IOMUXC_REGSIZE     (0x00004000)

/*=========================================================*/
/*                   Module Parameter                      */
/*=========================================================*/
static int mic_mode = 0;	// default : tablet mic mode
module_param( mic_mode, int, S_IRUGO );
MODULE_PARM_DESC( mic_mode, "RASP-MX mic select for use := 0(tablet) | 1(cascade) | 2(tablet-F & cascade) | 3(tablet-R & cascade)" );

/*=========================================================*/
/*                 Structure Declaration                   */
/*=========================================================*/

struct spi_regs {
	ulong	rxdata;		/* 0x00 */
	ulong	txdata;		/* 0x04 */
	ulong	conreg;		/* 0x08 */
	ulong	configreg;	/* 0x0c */
	ulong	intreg;		/* 0x10 */
	ulong	dmareg;		/* 0x14 */
	ulong	statreg;	/* 0x18 */
	ulong	periodreg;	/* 0x1c */
	ulong	testreg;	/* 0x20 */
	ulong	rev[7];
	ulong	msgdatareg;	/* 0x40 */
};

struct ccm_regs {
	ulong	ccr;		/* 0x00 */
	ulong	ccdr;		/* 0x04 */
	ulong	csr;		/* 0x08 */
	ulong	ccsr;		/* 0x0c */
	ulong	cacrr;		/* 0x10 */
	ulong	cbcdr;		/* 0x14 */
	ulong	cbcmr;		/* 0x18 */
	ulong	cbcmr1;		/* 0x1c */
	ulong	cbcmr2;		/* 0x20 */
	ulong	cscdr1;		/* 0x24 */
	ulong	cs1cdr;		/* 0x28 */
	ulong	cs2cdr;		/* 0x2c */
	ulong	cdcdr;		/* 0x30 */
	ulong	chsccdr;	/* 0x34 */
	ulong	cscdr2;		/* 0x38 */
	ulong	cscdr3;		/* 0x3c */
	ulong	rev1[2];
	ulong	cdhipr;		/* 0x48 */
	ulong	rev2[2];
	ulong	clpcr;		/* 0x54 */
	ulong	cisr;		/* 0x58 */
	ulong	cimr;		/* 0x5c */
	ulong	ccosr;		/* 0x60 */
	ulong	cgpr;		/* 0x64 */
	ulong	ccgr0;		/* 0x68 */
	ulong	ccgr1;		/* 0x6c */
	ulong	ccgr2;		/* 0x70 */
	ulong	ccgr3;		/* 0x74 */
	ulong	ccgr4;		/* 0x78 */
	ulong	ccgr5;		/* 0x7c */
	ulong	ccgr6;		/* 0x80 */
	ulong	rev3;
	ulong	cmeor;		/* 0x88 */
};

/*=========================================================*/
/*                    Private Valiable                     */
/*=========================================================*/

/* 動的割り当てを使用 */
static unsigned int drv_major = 0;

static int module_registered = NOT_REGISTERED;

/* A/DとD/Aの待ち行列 */
static wait_queue_head_t read_wait;
static wait_queue_head_t write_wait;

/* A/D関係のバッファ操作用変数 */
static char *ad_buffer, *ad_buffer_cut;
static int ad_readable_size;


/* D/A関係のバッファ操作用変数 */
static char *da_buffer;
static int da_writeable_size;

/* レジスタマッピング変数 */
static volatile struct spi_regs *spi_base[2];
static volatile struct ccm_regs *ccm_base;
//static volatile ulong *iomuxc_base;

/* SPI排他制御用セマフォ */
static struct semaphore sem_spi;

/* タイマーISR用ワークキュー */
static struct workqueue_struct *timer_wq;
static struct work_struct timer_work;
static void get_fifo_cnt( struct work_struct *work );

/* 最大マイク数 */
static int max_mic_num;

static unsigned int rasp24_stack_board_num = 1;
static int capture_mic_num;
static int spi_num;

/*=========================================================*/
/*                    Public Valiable                      */
/*=========================================================*/

/*=========================================================*/
/*                   Private Functions                     */
/*=========================================================*/

static void spi_set_burst_length( int bytes )
{
    ulong rdat, wdat;

    for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
    {
        wdat = (((ulong)( 32 * bytes - 1 )) << 20 ) & 0xfff00000;
        rdat = readl( &spi_base[spi_num]->conreg );
        rdat = ( rdat & ~0xfff00000 ) | wdat;	/* BURST_LENGTH = (32bit * bytes) */
        writel( rdat, &spi_base[spi_num]->conreg );
    }
}

static void spi_start( void )
{
    ulong rdat;

    for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
    {
        rdat = readl( &spi_base[spi_num]->conreg );
        rdat |= 0x00000004;
        writel( rdat, &spi_base[spi_num]->conreg );
    }
}

static void spi_poll_end( void )
{
    ulong rdat[2];

    while( 1 )
    {
        for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
        {
            rdat[spi_num] = readl( &spi_base[spi_num]->conreg );
        }
        if((( rdat[0] & 0x00000004 ) == 0 ) && (( rdat[1] & 0x00000004 ) == 0 )) break;
    }
}

static void spi_poll_fifo_cnt( ulong rxth )
{
    ulong rdat[2], rxfifo[2];

    while( 1 )
    {
        for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
        {
            rdat[spi_num] = readl( &spi_base[spi_num]->testreg );
            rxfifo[spi_num] = ((rdat[spi_num] & 0x00007f00) >> 8);
        }
        if(( rxth < rxfifo[0] ) && ( rxth < rxfifo[1] )) break;
        for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
        {
            rdat[spi_num] = readl( &spi_base[spi_num]->conreg );
        }
        if((( rdat[0] & 0x00000004 ) == 0 ) && (( rdat[1] & 0x00000004 ) == 0 )) break;
    }
}

static int rasp_mx_init( void )
{
    ulong rdat;

    for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
    {
        // STEP-1 : Clear the EN bit in ESCPI_CONREG to reset the block.
        rdat = readl( &spi_base[spi_num]->conreg );
        writel( rdat & ~0x00000001, &spi_base[spi_num]->conreg );

        // STEP-2 : Enable the clocks for ECSPI.
        rdat = readl( &ccm_base->ccgr1 );
        writel( rdat | 0x000000cc, &ccm_base->ccgr1 );    // ECSPI2 & ECSPI4

        // STEP-3 : Set the EN bit in ESCPI_CONREG to put ECSPI out of reset.
        rdat = readl( &spi_base[spi_num]->conreg );
        writel( rdat | 0x00000001, &spi_base[spi_num]->conreg );

        // STEP-4 : Configure corresponding IOMUX for ECSPI external signals.
        // Do in DeviceTree

        // STEP-5 : Configure registers of ECSPI properly according to the specifications of the external SPI device.
        rdat = readl( &spi_base[spi_num]->conreg );
        rdat = ( rdat & ~0xfff00000 ) | 0x01f00000;	/* BURST_LENGTH = 32bit */
        if( spi_num )
        {
            /* CHANNEL_SELECT = SS0 for ECSPI-4 & ECSPI-5 */
            rdat = ( rdat & ~0x000c0000 ) | 0x00000000;
        }
        else
        {
            /* CHANNEL_SELECT = SS1 for ECSPI-1 & ECSPI-2 & ECSPI-3 */
            rdat = ( rdat & ~0x000c0000 ) | 0x00040000;
        }
        rdat = ( rdat & ~0x00030000 ) | 0x00000000;	/* DRCTL = SPI_RDY Don't Care */

        if( spi_num )
        {
            // ECSPI-1 & ECSPI-4 & ECSPI-5 (Fast Group)
            rdat = ( rdat & ~0x0000f000 ) | 0x00002000;	/* PRE_DIVIDER = Divide by 3 (20MHz)*/
            rdat = ( rdat & ~0x00000f00 ) | 0x00000000;	/* POST_DIVIDER = Divide by 1 (60MHz)*/
        }
        else
        {
            // ECSPI-2 & ECSPI-3 (Slow Group)
            rdat = ( rdat & ~0x0000f000 ) | 0x00001000;	/* PRE_DIVIDER = Divide by 2 (15MHz)*/
            rdat = ( rdat & ~0x00000f00 ) | 0x00000100;	/* POST_DIVIDER = Divide by 2 (30MHz)*/
        }
        rdat = ( rdat & ~0x000000f0 ) | 0x000000f0;	/* CHANNEL_MODE = All master mode */
        rdat = ( rdat & ~0x00000008 ) | 0x00000000;	/* SMC = SPI Exchange Bit(XCH) controls */
        rdat = ( rdat & ~0x00000004 ) | 0x00000000;	/* XCH = Idle */
        rdat = ( rdat & ~0x00000002 ) | 0x00000000;	/* HT = Disable HT mode */
        rdat = ( rdat & ~0x00000001 ) | 0x00000001;	/* EN = Enable the block */
        writel( rdat, &spi_base[spi_num]->conreg );

        rdat = readl( &spi_base[spi_num]->configreg );
        rdat = ( rdat & ~0x00f00000 ) | 0x00000000;	/* SCLK_CTL = All stay low */
        rdat = ( rdat & ~0x000f0000 ) | 0x00000000;	/* DATA_CTL = All stay high */
        rdat = ( rdat & ~0x0000f000 ) | 0x00000000;	/* SS_POL = All active low */
//      rdat = ( rdat & ~0x00000f00 ) | 0x00000f00;	/* SS_CTL = All negate SS between SPI bursts */
        rdat = ( rdat & ~0x00000f00 ) | 0x00000000;	/* SS_CTL = only one SPI burst will be transmitted */
        rdat = ( rdat & ~0x000000f0 ) | 0x00000000;	/* SCLK_POL = All active high polarity */
        rdat = ( rdat & ~0x0000000f ) | 0x00000000;	/* SCLK_PHA = All phase 0 operation */
        writel( rdat, &spi_base[spi_num]->configreg );
    }

    return 0;
}

int rasp_mx_exit( void )
{
    ulong rdat;

    for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
    {
        // STEP-1 : Clear the EN bit in ESCPI_CONREG to reset the block.
        rdat = readl( &spi_base[spi_num]->conreg );
        writel( rdat & ~0x00000001, &spi_base[spi_num]->conreg );

        // STEP-2 : Disable the clocks for ECSPI.
        rdat = readl( &ccm_base->ccgr1 );
        writel( rdat & ~0x000000cc, &ccm_base->ccgr1 );
    }

    return 0;
}

static int rasp_mx_start( void )
{
    ulong rdat;

    if( down_interruptible(&sem_spi) )
    {
        printk("down_interruptible error\n" );
        return -ERESTARTSYS;
    }

    writel( 0x08000000, &spi_base[0]->txdata );	// 同期スタート
    writel( 0x00000000, &spi_base[1]->txdata ); // dummy

    spi_start();
    spi_poll_end();

    rdat = readl( &spi_base[0]->rxdata );
    rdat = readl( &spi_base[1]->rxdata );

    up(&sem_spi);

    return 0;
}

int rasp_mx_check( ulong *rfifo, ulong *wfifo, ulong *version, ulong *mic_num, int spino )
{
    ulong rdat[2];

    if( down_interruptible(&sem_spi) )
    {
        printk("down_interruptible error\n" );
        return -ERESTARTSYS;
    }

    writel( 0x00000000, &spi_base[0]->txdata );
    writel( 0x00000000, &spi_base[1]->txdata );

    spi_start();
    spi_poll_end();

    rdat[0] = readl( &spi_base[0]->rxdata );
    rdat[1] = readl( &spi_base[1]->rxdata );

    if( version != NULL ) *version = ( rdat[spino] & 0xf0000000 ) >> 28;
    if( wfifo   != NULL ) *wfifo   = ( rdat[spino] & 0x07ff0000 ) >> 16;
    if( mic_num != NULL ) *mic_num = ( rdat[spino] & 0x0000fc00 ) >> 10;
    if( rfifo   != NULL ) *rfifo   = ( rdat[spino] & 0x000003ff ) >>  0;

    up(&sem_spi);

    return 0;
}

int rasp_mx_fifo_rd( ulong sample_per_ch, ulong *wavedata )
{
    ulong rdat, wav[3], wav_tmp[2][32];
    int	i, j, k, burst;

    if( down_interruptible(&sem_spi) )
    {
        printk("down_interruptible error\n" );
        return -ERESTARTSYS;
    }

    burst = max_mic_num / 4 * 3;

    spi_set_burst_length( burst + 1 );

    for( i = 0 ; i < sample_per_ch ; i++ ){
        writel( 0x20000000, &spi_base[0]->txdata );
        writel( 0x20000000, &spi_base[1]->txdata );
        spi_start();
        for( j = 0 ; j < burst ; j++ )
        {
            writel( 0x00000000, &spi_base[0]->txdata );
            writel( 0x00000000, &spi_base[1]->txdata );
        }

        // SPI 通信中にデータのコピーを実施する
        if( i )
        {
            k = ( i - 1 ) * max_mic_num;
            if( mic_mode == 0 ) { // tablet mode
                for( j = 0 ; j < max_mic_num ; j++ )
                    wavedata[k + j] = wav_tmp[0][j];
            } else if( mic_mode == 1 ) { // cascade mode
                for( j = 0 ; j < max_mic_num ; j++ )
                    wavedata[k + j] = wav_tmp[1][j];
            } else if( mic_mode == 2 ) { // tablet-F & cascade mode
                for( j = 0 ; j < 8 ; j++ )
                    wavedata[k + j] = wav_tmp[0][j];
                for( j = 8 ; j < max_mic_num ; j++ )
                    wavedata[k + j] = wav_tmp[1][j - 8];
            } else { // tablet-R & cascade mode
                for( j = 0 ; j < 8 ; j++ )
                    wavedata[k + j] = wav_tmp[0][j + 8];
                for( j = 8 ; j < max_mic_num ; j++ )
                    wavedata[k + j] = wav_tmp[1][j - 8];
            }
        }

        spi_poll_fifo_cnt( 0 );
        rdat = readl( &spi_base[0]->rxdata );	/* skip read data */
        rdat = readl( &spi_base[1]->rxdata );	/* skip read data */
        for( j = 0 ; j < (burst / 3) ; j++ ){
            spi_poll_fifo_cnt( 2 );
            for( spi_num = 0 ; spi_num < 2 ; spi_num++ )
            {
                wav[0] = readl( &spi_base[spi_num]->rxdata );
                wav[1] = readl( &spi_base[spi_num]->rxdata );
                wav[2] = readl( &spi_base[spi_num]->rxdata );
                // 24bit -> 32bit convert
                wav_tmp[spi_num][j * 4 + 0] = ((wav[0] & 0xffffff00) >>  8);
                wav_tmp[spi_num][j * 4 + 1] = ((wav[0] & 0x000000ff) << 16) | ((wav[1] & 0xffff0000) >> 16);
                wav_tmp[spi_num][j * 4 + 2] = ((wav[1] & 0x0000ffff) <<  8) | ((wav[2] & 0xff000000) >> 24);
                wav_tmp[spi_num][j * 4 + 3] = ((wav[2] & 0x00ffffff) >>  0);
                // code extension
                for( k = 0 ; k < 4 ; k++ ){
                    if( wav_tmp[spi_num][j * 4 + k] & 0x00800000 )
                        wav_tmp[spi_num][j * 4 + k] |= 0xff000000;
                }
            }
        }
        spi_poll_end();
    }

    // 最後の1回分
    k = ( i - 1 ) * max_mic_num;
    if( mic_mode == 0 ) { // tablet mode
        for( j = 0 ; j < max_mic_num ; j++ )
            wavedata[k + j] = wav_tmp[0][j];
    } else if( mic_mode == 1 ) { // cascade mode
        for( j = 0 ; j < max_mic_num ; j++ )
            wavedata[k + j] = wav_tmp[1][j];
    } else if( mic_mode == 2 ) { // tablet-F & cascade mode
        for( j = 0 ; j < 8 ; j++ )
            wavedata[k + j] = wav_tmp[0][j];
        for( j = 8 ; j < max_mic_num ; j++ )
            wavedata[k + j] = wav_tmp[1][j - 8];
    } else { // tablet-R & cascade mode
        for( j = 0 ; j < 8 ; j++ )
            wavedata[k + j] = wav_tmp[0][j + 8];
        for( j = 8 ; j < max_mic_num ; j++ )
            wavedata[k + j] = wav_tmp[1][j - 8];
    }

    spi_set_burst_length( 1 );

    up(&sem_spi);

    return 0;
}

int rasp_mx_fifo_wr( ulong sample, short *wavedata )
{
    ulong rdat, wdat, burst;
    int i, j;

    if( down_interruptible(&sem_spi) )
    {
        printk("down_interruptible error\n" );
        return -ERESTARTSYS;
    }

    if( sample > 63 )
    {
        burst = 64;
        spi_set_burst_length( burst );
    }

    i = 0;
    while( sample )
    {
        if( sample < 64 )
        {
            burst = sample;
            spi_set_burst_length( sample );
        }
        for( j = 0 ; j < burst ; j++ )
        {
            wdat = (int)wavedata[i++];
            writel(( 0x10000000 | ( wdat & 0xffff )), &spi_base[0]->txdata );
            writel( 0x00000000, &spi_base[1]->txdata );	// dummy
            if( j == 0 ) spi_start();
        }

        for( j = 0 ; j < burst ; j++ ){
            spi_poll_fifo_cnt( 0 );
            rdat = readl( &spi_base[0]->rxdata );
            rdat = readl( &spi_base[1]->rxdata );
        }
        spi_poll_end();

        sample -= burst;
    }

    spi_set_burst_length( 1 );

    writel( 0x00000000, &spi_base[0]->txdata );
    writel( 0x00000000, &spi_base[1]->txdata );
    spi_start();
    spi_poll_end();
    rdat = readl( &spi_base[0]->rxdata );
    rdat = readl( &spi_base[1]->rxdata );

    up(&sem_spi);

    return 0;
}

int rasp_mx_stop( void )
{
    ulong rdat;

    if( down_interruptible(&sem_spi) )
    {
        printk("down_interruptible error\n" );
        return -ERESTARTSYS;
    }

    writel( 0x04000000, &spi_base[0]->txdata );	// 同期ストップ
    writel( 0x00000000, &spi_base[1]->txdata );	// dummy

    spi_start();
    spi_poll_end();

    rdat = readl( &spi_base[0]->rxdata );
    rdat = readl( &spi_base[1]->rxdata );

    up(&sem_spi);

    return 0;
}

/************************************************************
**
**  Private : ioctl
**
**  History : 
**
**  Description
**  -----------
**  ioctlメソッド
**
*/

static int  capture_info_g[4];

//static int drv_ioctl( struct inode *inode,
static long drv_ioctl(
                        struct file  *file,
                        unsigned int  cmd,
                        unsigned long arg )
{
    int ret_val;
    int stat;
    int i, j;

    CAPTURE_CHANNELS_t  capture_info;

    /* マジックナンバーのチェック */
    if( _IOC_TYPE( cmd )!= DRV_IOCTL_MAGIC )
    {
        return -ENOTTY;
    }
    
    stat = 0;
    ret_val = 0;
    switch( cmd )
    {
        case FIO_AD_START:
        {
            DEBUG_IOCTL_PRINT("CMD = FIO_AD_START\n");

            capture_mic_num = 0;
            for(i = 0 ; i < 4 ; i++)
            {
                for(j = 0 ; j < 8 ; j++)
                {
                    if(capture_info_g[i] & (0x01 << j))
                        capture_mic_num++;
                }
            }

            /* A/D開始 */
            rasp_mx_start();
        }
        break;

        case FIO_AD_STOP:
        {
            DEBUG_IOCTL_PRINT("CMD = FIO_AD_STOP\n");

            /* A/D停止 */
            rasp_mx_stop();
        }
        break;

        /* A/D取り込みチャネル設定 */
        case FIO_SET_CAPTURE_CHANNELS:
        {
            DEBUG_IOCTL_PRINT("CMD = FIO_SET_CAPTURE_CHANNELS\n");

            if( !access_ok( VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd) ) )
            {
                ret_val = -EFAULT;
                break;
            }

            if( copy_from_user( &capture_info, (void __user *)arg, sizeof(CAPTURE_CHANNELS_t) ) )
            {
                ret_val = -EFAULT;
                break;
            }

            stat = 0;
            capture_info_g[capture_info.board_no] = capture_info.channels;
            if(stat != 0)
            {
                ret_val = -EINVAL;
            }

            DEBUG_IOCTL_PRINT("Board No = %d, Capture Channels = %x, Status = %d\n",
                              capture_info.board_no, capture_info.channels, stat);
        }
        break;

        /* A/D取り込みチャネル取得 */
        case FIO_GET_CAPTURE_CHANNELS:
        {
            DEBUG_IOCTL_PRINT("CMD = FIO_GET_CAPTURE_CHANNELS\n");

            if( !access_ok( VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd) ) )
            {
                ret_val = -EFAULT;
                break;
            }

            if( copy_from_user( &capture_info, (void __user *)arg, sizeof(CAPTURE_CHANNELS_t) ) )
            {
                ret_val = -EFAULT;
                break;
            }

            stat = 0;
            capture_info.channels = capture_info_g[capture_info.board_no];
            if(stat != 0)
            {
                ret_val = -EINVAL;
            }

            DEBUG_IOCTL_PRINT("Board No = %d, Capture Channels = %x, Status = %d\n",
                              capture_info.board_no, capture_info.channels, stat);

            if( copy_to_user( (void __user *)arg, &capture_info, sizeof(CAPTURE_CHANNELS_t) ) )
            {
                ret_val = -EFAULT;
            }
        }
        break;

        /* スタックボード枚数の取得 */
        case FIO_GET_BOARD_NUM:
        {
            DEBUG_IOCTL_PRINT("CMD = FIO_GET_BOARD_NUM\n");

            if( !access_ok( VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd) ) )
            {
                ret_val = -EFAULT;
                break;
            }
            DEBUG_IOCTL_PRINT("RASP24 Stack Board Num = %d\n", rasp24_stack_board_num);

            __put_user(rasp24_stack_board_num, (unsigned int *)arg);
        }
        break;

        default:
        {
            DEBUG_IOCTL_PRINT("%s CMD = 0x%08x\n", __func__, cmd);
            ret_val = -ENOTTY;
        }
        break;
    }
    
    return ret_val;
}

/************************************************************
**
**  Private : 
**
**  History : 
**
**  Description
**  -----------
**  SPI経由でFPGAのバッファの状態をチェック
**
*/

static void check_buffer( void )
{
    ulong rdcnt, wrcnt, spino;

    if( mic_mode == 1 ) spino = 1;
    else spino = 0;

    if( rasp_mx_check( &rdcnt, &wrcnt, NULL, NULL, spino ) == 0 )
    {
//        ad_readable_size = rdcnt * sizeof(ulong) * max_mic_num;
        ad_readable_size = rdcnt * sizeof(ulong) * capture_mic_num;
        da_writeable_size = ( 2000 - wrcnt ) * sizeof(short);
    }
}

/************************************************************
**
**  Private : drv_read
**
**  History : 
**
**  Description
**  -----------
**  Readメソッド
**
*/

static int drv_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
    int ret_val;
    int copy_count;
    int i, smpl;
    int *ad_buf_p, *ad_buf_cut_p;
    
    /* 読み出し可能か確認 */
    check_buffer();    
    
    if(ad_readable_size == 0)
    {
        /* 非ブロックモードの場合はエラーを返す */
        if(file->f_flags & O_NONBLOCK)
        {
            return -EAGAIN;
        }
        
        /* 読み出し可能になるまで待機 */
        do
        {
            ret_val = wait_event_interruptible_timeout(read_wait, 
                                                       ( ad_readable_size > 0), 
                                                       1 * HZ);
        }while(ret_val == 0);

        if(ret_val == -ERESTARTSYS)
        {
            return ret_val;
        }
    }
    
    /* コピーするサイズを算出 */
    if(AD_BUFFER_SIZE < count)
    {
        count = AD_BUFFER_SIZE;
    }
    if(ad_readable_size < count)
    {
        count = ad_readable_size;
    }
    
    /* A/Dバッファにデータを読み込む */
//    rasp_mx_fifo_rd( count / sizeof(ulong) / max_mic_num, (ulong *)ad_buffer );
    rasp_mx_fifo_rd( count / sizeof(ulong) / capture_mic_num, (ulong *)ad_buffer );

    /* 必要なデータの取り出し */
    ad_buf_p = (int *)ad_buffer;
    ad_buf_cut_p = (int *)ad_buffer_cut;
//    smpl = count / sizeof(ulong) / max_mic_num;
    smpl = count / sizeof(ulong) / capture_mic_num;
    count = 0;

    while(smpl--)
    {
        for(i = 0 ; i < max_mic_num ; i++ )
        {
            if(capture_info_g[i / 8] & (0x01 << (i % 8)))
            {
                *ad_buf_cut_p++ = *ad_buf_p;
                count += 4;
            }
            ad_buf_p++;
        }
    }

    /* ユーザ空間にコピー */
    copy_count = copy_to_user(buf, &ad_buffer_cut[0], count);
    if(copy_count != 0)
    {
        goto Err_Read;
    }
    
    return count;
    
Err_Read:
    return -EFAULT;
}

/************************************************************
**
**  Private : drv_write
**
**  History : 
**
**  Description
**  -----------
**  Writeメソッド
**
*/

static int drv_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
    int ret_val;
    int copy_count;
    
    /* 書き込み可能か確認 */
    check_buffer();    
    
    if(da_writeable_size == 0)
    {
        /* 非ブロックモードの場合はエラーを返す */
        if(file->f_flags & O_NONBLOCK)
        {
            return -EAGAIN;
        }
        
        /* 書き込み可能になるまで待機 */
        do
        {
            ret_val = wait_event_interruptible_timeout(write_wait, 
                                                       (da_writeable_size > 0), 
                                                       1 * HZ);
        }while(ret_val == 0);

        if(ret_val == -ERESTARTSYS)
        {
            return ret_val;
        }
    }
    
    /* コピーするサイズを算出 */
    if(DA_BUFFER_SIZE < count)
    {
        count = DA_BUFFER_SIZE;
    }
    if(da_writeable_size < count)
    {
        count = da_writeable_size;
    }
    
    /* ユーザ空間からデータをコピー */
    copy_count = copy_from_user(&da_buffer[0], buf, count);
    if(copy_count != 0)
    {
        goto Err_Write;
    }
    
    /* D/Aバッファにデータを書き込む */
    rasp_mx_fifo_wr( count / sizeof(short) , (short *)da_buffer );
    
    return count;
    
Err_Write:
    return -EFAULT;
}

/************************************************************
**
**  Private : drv_poll
**
**  History : 
**
**  Description
**  -----------
**  Select(Poll)メソッド
**
*/

static unsigned int drv_poll(struct file *file, poll_table *wait)
{
    unsigned int mask;

    mask = 0;
    
    /* 待ち行列に登録 */
    poll_wait(file, &read_wait,  wait);
    poll_wait(file, &write_wait, wait);
    
    /* 読込み可能かを判定 */
    if(ad_readable_size > 0)
    {
        mask |= (POLLIN | POLLRDNORM);
    }
    
    /* 書き込み可能かを判定 */
    if(da_writeable_size > 0)
    {
        mask |= (POLLOUT | POLLWRNORM);
    }
    
    return mask;

}


/************************************************************
**
**  Private : 
**
**  History : 
**
**  Description
**  -----------
**  タイマ割り込み
**
*/

static void get_fifo_cnt( struct work_struct *work )
{
    /* A/D & D/Aバッファのチェック */
    check_buffer();

    if(ad_readable_size > 0)
    {
        wake_up_interruptible(&read_wait);
    }
    
    if(da_writeable_size > 0)
    {
        wake_up_interruptible(&write_wait);
    }
}

static void timer_set( void );

static struct timer_list drv_timer;
static int stop_timer = 0;

static void timer_isr(unsigned long data)
{
    queue_work( timer_wq, &timer_work );
    
    /* 再登録 */
    if(!stop_timer)
    {
        timer_set();
    }
}

static void timer_set( void )
{
    init_timer( &drv_timer );

//    drv_timer.expires  = jiffies + HZ;
    drv_timer.expires  = jiffies + HZ / 1000;	// 1ms
    drv_timer.data     = 0;
    drv_timer.function = timer_isr;
   
    add_timer(&drv_timer);
}

static void timer_del( void )
{
    stop_timer = 1;
    del_timer_sync(&drv_timer);
}


/************************************************************
**
**  Private : drv_open
**
**  History : 
**
**  Description
**  -----------
**  openメソッド
**
*/

static int drv_open( struct inode *inode, struct file *file )
{
    return 0;
}


/************************************************************
**
**  Private : drv_release
**
**  History : 
**
**  Description
**  -----------
**  closeメソッド
**
*/

static int drv_release( struct inode *inode, struct file *file )
{
    return 0;
}



static struct file_operations drv_fops = 
{
    open:    drv_open,
    release: drv_release,
//    ioctl:   drv_ioctl,
    unlocked_ioctl:	drv_ioctl,
    read:    drv_read,
    write:   drv_write,
    poll:    drv_poll,
};


/************************************************************
**
**  Private : drv_exit
**
**  History : 
**
**  Description
**  -----------
**  ドライバの削除関数
**
*/

void drv_exit( void )
{
    printk("%s\n", __func__);
    printk("<1>" DEVICE_NAME " uninstall\n");

    // ボトムハーフの類をクリーンアップ
    if (timer_wq)
    {
        flush_workqueue( timer_wq );
        cancel_work_sync( &timer_work );
        destroy_workqueue( timer_wq );
    }

    /* メモリの開放 */
    {
        if(ad_buffer != NULL)
        {
            kfree(ad_buffer);
        }
        if(ad_buffer_cut != NULL)
        {
            kfree(ad_buffer_cut);
        }
        
        if(da_buffer != NULL)
        {
            kfree(da_buffer);
        }
    }
    
    /* タイマー削除 */
    timer_del();
    
    /* ドライバの削除 */
    if(module_registered == REGISTERED)
    {
        unregister_chrdev( drv_major, DEVICE_NAME );
    }

    rasp_mx_exit();

    /* IOMUXCレジスタ空間の開放 */
//    iounmap(iomuxc_base);
//    release_mem_region(IMX6Q_IOMUXC_BASEADDR, IMX6Q_IOMUXC_REGSIZE);

    /* CCMレジスタ空間の開放 */
    iounmap(ccm_base);
    release_mem_region(IMX6Q_CCM_BASEADDR, IMX6Q_CCM_REGSIZE);

    /* SPIレジスタ空間の開放 */
    iounmap(spi_base[0]);
    iounmap(spi_base[1]);
//    release_mem_region(IMX6Q_ECSPI1_BASEADDR, IMX6Q_ECSPIx_REGSIZE);
    
    module_registered = NOT_REGISTERED;

}


/************************************************************
**
**  Public  : drv_init
**
**  History : 
**
**  Description
**  -----------
**  ドライバの初期化関数
**
*/

int drv_init( void )
{
    int  tmp;
    ulong ver, mic_num, mn[2];
    
    /* ドライバ登録済み */
    if(module_registered == REGISTERED)
    {
        return -1;
    }

    //printk(KERN_INFO DEVICE_NAME " Compile time %s at %s\n", __TIME__, __DATE__ );
    printk(KERN_INFO DEVICE_NAME "\n");

    timer_wq = create_workqueue( "timer_wq" );
    if (!timer_wq)
    {
        return -1;
    }
    INIT_WORK( &timer_work, get_fifo_cnt );

    /* Read/Write用の待ち行列を初期化 */
    init_waitqueue_head(&read_wait);
    init_waitqueue_head(&write_wait);
 
    /* 制御レジスタ設定(基本設定はブートローダで実施済み) */
    /* SPIレジスタ空間のマップ */
#if 0
    if(!request_mem_region(IMX6Q_ECSPI1_BASEADDR, IMX6Q_ECSPIx_REGSIZE, "iMX6q_spi"))
    {
        printk(KERN_ERR DEVICE_NAME " request_mem_region: spi region Error\n");
        return -EBUSY;
    }
#endif
    
    spi_base[0] = (volatile struct spi_regs *)ioremap_nocache(IMX6Q_ECSPI2_BASEADDR, IMX6Q_ECSPIx_REGSIZE);
    spi_base[1] = (volatile struct spi_regs *)ioremap_nocache(IMX6Q_ECSPI4_BASEADDR, IMX6Q_ECSPIx_REGSIZE);

    if(( spi_base[0] == NULL ) || ( spi_base[1] == NULL ))
    {
        printk(KERN_ERR DEVICE_NAME " ioremap_nocache: spi region Error\n");
        return -ENOMEM;
    }

    /* CCMレジスタ空間のマップ */
    if(!request_mem_region(IMX6Q_CCM_BASEADDR, IMX6Q_CCM_REGSIZE, "iMX6q_ccm"))
    {
        printk(KERN_ERR DEVICE_NAME " request_mem_region: ccm region Error\n");
	goto Err_End0;
    }

    ccm_base = (volatile struct ccm_regs *)ioremap_nocache(IMX6Q_CCM_BASEADDR, IMX6Q_CCM_REGSIZE);
    if(ccm_base == NULL)
    {
        printk(KERN_ERR DEVICE_NAME " ioremap_nocache: ccm region Error\n");
        release_mem_region(IMX6Q_CCM_BASEADDR, IMX6Q_CCM_REGSIZE);
        goto Err_End0;
    }

    /* キャラクタデバイス登録 */
    drv_major = 0;
    tmp = register_chrdev( drv_major, DEVICE_NAME, &drv_fops );
    if( tmp < 0 )
    {
        printk(KERN_ERR DEVICE_NAME " install: Error major = %d\n", drv_major );
        
        goto Err_End;
    }

    if( drv_major == 0 )
    {
        drv_major = tmp;
    }
    printk(KERN_INFO DEVICE_NAME " install: success !! major = %d\n", drv_major );

    module_registered = REGISTERED;
    
    
    /* A/D & D/A 用のローカルバッファを確保 */
    {
        ad_buffer = NULL;
        ad_buffer_cut = NULL;
        da_buffer = NULL;
        
        ad_buffer = kmalloc(AD_BUFFER_SIZE, GFP_ATOMIC);
        ad_buffer_cut = kmalloc(AD_BUFFER_SIZE, GFP_ATOMIC);
        da_buffer = kmalloc(DA_BUFFER_SIZE, GFP_ATOMIC);
        
        if((ad_buffer == NULL) || (ad_buffer_cut == NULL) || (da_buffer == NULL))
        {
            goto Err_End;
        }
    }

    /* セマフォ初期化 */
    init_MUTEX(&sem_spi);
    
    /* タイマ割り込みを登録 */
    {
        timer_set();
    }
    
    /* 各種初期化 */
    {
        rasp_mx_init();
        rasp_mx_check(NULL, NULL, &ver, &mn[0] , 0);
        rasp_mx_check(NULL, NULL, &ver, &mn[1] , 1);
        printk(KERN_INFO DEVICE_NAME " FPGA Version = %d\n", (int)ver );
        switch( mic_mode )
        {
            case 0:
                max_mic_num = 16;
                mic_num = mn[0];
                printk(KERN_INFO DEVICE_NAME " Mic = Tablet mic\n");
                break;
            case 1:
                max_mic_num = 32;
                mic_num = mn[1];
                printk(KERN_INFO DEVICE_NAME " Mic = Cascade mic\n");
                break;
            case 2:
                max_mic_num = 16;
                mic_num = mn[0] / 2 + mn[1];
                printk(KERN_INFO DEVICE_NAME " Mic = Tablet-Front & Cascade mic\n");
                break;
            case 3:
                max_mic_num = 16;
                mic_num = mn[0] / 2 + mn[1];
                printk(KERN_INFO DEVICE_NAME " Mic = Tablet-Rear & Cascade mic\n");
                break;
            default:
                max_mic_num = 16;
                mic_num = 0;
                printk(KERN_INFO DEVICE_NAME " Invalid Mic Mode!\n");
		break;
        }
        printk(KERN_INFO DEVICE_NAME " Mic num = %d\n", (int)mic_num );

        capture_info_g[0] = 0xff;
        capture_info_g[1] = 0xff;
        capture_info_g[2] = 0x00;
        capture_info_g[3] = 0x00;
        capture_mic_num = 16;

        if( mic_num ) rasp24_stack_board_num = ( mic_num - 1 ) / 8 + 1; /* ボードあたり8ch */
    }
    
    return 0;
    
Err_End:

    if(ad_buffer != NULL)
    {
        kfree(ad_buffer);
    }
    if(ad_buffer_cut != NULL)
    {
        kfree(ad_buffer_cut);
    }
    if(da_buffer != NULL)
    {
        kfree(da_buffer);
    }

    /* CCMレジスタ空間の開放 */
    iounmap(ccm_base);
    release_mem_region(IMX6Q_CCM_BASEADDR, IMX6Q_CCM_REGSIZE);

Err_End0:
    /* SPIレジスタ空間の開放 */
    iounmap(spi_base[0]);
    iounmap(spi_base[1]);
//    release_mem_region(IMX6Q_ECSPI1_BASEADDR, IMX6Q_ECSPIx_REGSIZE);

    /* エラー時の終了処理 */
    module_registered = NOT_REGISTERED;
    
    return -1;
}

module_init(drv_init);
module_exit(drv_exit);

/* EOF */

