[linux-cirrus] sorry no binary format allowed

/*
 *  FILE:                       ssp.c
 *
 *  DESCRIPTION:        SSP Interface Driver Module implementation
 *
 *  Copyright Cirrus Logic Corporation, 2001-2003.  All rights reserved
 *
 * 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 should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
/*
 *  This driver provides a way to read and write devices on the SSP 
 *  interface.
 *
 *  For Tx devices, EGPIO7 is used as an address pin:
 *  I2S Codec CS4228        = EGPIO7 == 1
 *  Serial Flash AT25F1024  = EGPIO7 == 0
 */
#include <linux/delay.h>
#include <asm/irq.h>

#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/arch/hardware.h>
#include <asm/arch/ssp.h>

#undef DEBUG
// #define DEBUG 1
#ifdef DEBUG
#define DPRINTK( x... )  printk( ##x )
#else
#define DPRINTK( x... )
#endif


#define EP93XX_KEY_TIMER_PERIOD_MSEC 20

static int SSP_Open(SSPDeviceType Device, SSPDataCallback Callback);
static int SSP_Close(int Handle);
static int SSP_Read(int Handle, unsigned int Addr, unsigned int *pValue);
static int SSP_Write(int Handle, unsigned int Addr, unsigned int Value);
static int CheckHandle(int Handle);

static void  SetSSPtoPS2(void);
static void  SetSSPtoI2S(void);
static int  ReadIntoBuffer(void);

static int SSP_Write_I2SCodec(int Handle, unsigned int RegAddr,unsigned int 
RegValue);

/*
 * Key buffer...
 */
#define KEYBUF_SIZE 256
static unsigned int uiKeyBuffer[KEYBUF_SIZE];
                                                          
                                                          
static spinlock_t ssp_spinlock = SPIN_LOCK_UNLOCKED;


typedef enum{
        SSP_MODE_UNKNOWN = 0,
        SSP_MODE_PS2,
        SSP_MODE_I2S,
        SSP_MODE_FLASH,
} SSPmodes_t;

static struct timer_list g_KbdTimer;
static SSPmodes_t gSSPmode = SSP_MODE_UNKNOWN;
static SSPDataCallback gKeyCallback = NULL;
static int gHookedInterrupt = 0;

/*
 * Keep the last valid handle for SSP for kbd, i2s, and flash
 */
static int iLastValidHandle = -1;
static int KeyboardHandle = 0;
static int I2SHandle = 0;
static int FlashHandle = 0;

#define SSP_DEVICE_MASK    0xf0000000
#define SSP_DEVICE_SHIFT         28

SSP_DRIVER_API SSPinstance = 
{
    SSP_Open,
        SSP_Read,
        SSP_Write,
        SSP_Close,
};

/*
 * The only instance of this driver.
 */
SSP_DRIVER_API *SSPDriver = &SSPinstance;

//=============================================================================
// SSPIrqHandler
//=============================================================================
// This routine will get all of the keys out of the SPI FIFO.
//=============================================================================
irqreturn_t SSPIrqHandler( int irq, void *dev_id, struct pt_regs *regs)
{
        //
        // Get key codes from SSP and send them to the keyboard callback.
        //
        ReadIntoBuffer();
        
    //
    // Clear the interrupt.
    //
    outl( 0, SSPIIR );
    return IRQ_HANDLED;
}

//=============================================================================
// TimerRoutine
//=============================================================================
// This function is called periodically to make sure that no keys are stuck in 
// the SPI FIFO.  This is necessary because the SPI only interrupts on half
// full FIFO which can leave up to one keyboard event in the FIFO until 
another
// key is pressed.
//=============================================================================
static void TimerRoutine(unsigned long Data)
{
        int keycount;
    
    //
        // Get key codes from SSP and send them to the keyboard callback.
        //
        keycount = ReadIntoBuffer();
        
        //
        // If no keys were received, call the Data callback anyway so it can
    // check for stuck keys. 
        //
        if( (keycount==0) && gKeyCallback )
        {
                gKeyCallback(-1);
        }
        
        //
        // Reschedule our timer in another 20 mSec.
        //
        g_KbdTimer.expires = jiffies + 
MSECS_TO_JIFFIES( EP93XX_KEY_TIMER_PERIOD_MSEC );
        add_timer(&g_KbdTimer); 
}

/*
 * HookInterrupt
 *
 * Requests SSP interrupt, sets up interrupt handler, sets up keyboard polling
 * timer.
 */
static int HookInterrupt(void)
{
        if (gHookedInterrupt)
        {
                printk( KERN_ERR "SSP driver interrupt already hooked\n");
                return(-1);
        }


    if (request_irq(IRQ_SSPRX, SSPIrqHandler, SA_INTERRUPT, "ep93xxsspd", 
NULL))
    {
        printk( KERN_ERR "SSP driver failed to get IRQ handler\n");
                return(-1);
    }
        
        gHookedInterrupt = 1;

    //
        // Initialize the timer that we will use to poll the SPI.
        //
        init_timer(&g_KbdTimer);
    g_KbdTimer.function = TimerRoutine;
    g_KbdTimer.data = 1;
    g_KbdTimer.expires = jiffies + 
MSECS_TO_JIFFIES( EP93XX_KEY_TIMER_PERIOD_MSEC );

    add_timer(&g_KbdTimer);
        
        return(0);
}

static int SSP_Open(SSPDeviceType Device, SSPDataCallback Callback)
{
        int Handle;
        
    /*
     * Generate a handle and pass it back.
     *
     * Increment the last valid handle.
     * Check for wraparound (unlikely, but we like to be complete).
     */
    iLastValidHandle++;
        
    if((iLastValidHandle & ~SSP_DEVICE_MASK) == 0)
    {
        /*
         * If we wrapped around start over.  Unlikely.
         */
        iLastValidHandle = 1;
    }
        
        Handle = iLastValidHandle | (Device << SSP_DEVICE_SHIFT);

        switch (Device)
        {
                case PS2_KEYBOARD:
                {
                        DPRINTK("SSP_Open - PS2_KEYBOARD\n");
                        if (KeyboardHandle)
                        {
                                return(-1);
                        }
                        else 
                        {
                            DPRINTK("Handle:%08x  Callback:%08x  -- Success\n",
                    Handle, (unsigned int)Callback);
                    
                                KeyboardHandle = Handle;
                                //
                                // Hook the interrupt if we have not yet. 
                                //
                                HookInterrupt();
                                SetSSPtoPS2();
                                gKeyCallback = Callback;
                        }
                        break;
                }
                case I2S_CODEC:
                {
                        DPRINTK("SSP_Open - I2S_CODEC\n");
                        if (I2SHandle)
                        {
                                return(-1);
                        }
                        else
                        {
                            DPRINTK("Handle:%08x  Callback:%08x  -- Success\n",
                    Handle, (unsigned int)Callback);

                                I2SHandle = Handle;
                        }
                        break;
                }
                case SERIAL_FLASH:
                {
                        DPRINTK("SSP_Open - SERIAL_FLASH\n");
                        if (FlashHandle)
                        {
                                return(-1);
                        }
                        else
                        {
                            DPRINTK("Handle:%08x  Callback:%08x  -- Success\n",
                    Handle, (unsigned int)Callback);
                                FlashHandle = Handle;
                        }
                        break;
                }
                default:
                {
                        return(-1);
                }
        }
        
    /*
     * Return the handle.
     */
        return(Handle );
}

/*
 * Release that Handle!
 */
static int SSP_Close(int Handle)
{
        //
        // Find out which device this API was called for.
        // 
        switch( CheckHandle(Handle) )
        {
                case PS2_KEYBOARD:
                {
                        DPRINTK("SSP_Open - PS2_KEYBOARD\n");
                del_timer(&g_KbdTimer); 
            free_irq(IRQ_SSPRX, NULL);
                        gKeyCallback = NULL;
                        KeyboardHandle = 0;
            gHookedInterrupt = 0;
                        break;
                }
                case I2S_CODEC:
                {
                        DPRINTK("SSP_Open - I2S_CODEC\n");
                        I2SHandle = 0;
            break;
                }
                case SERIAL_FLASH:
                {
                        DPRINTK("SSP_Open - SERIAL_FLASH\n");
                        FlashHandle = 0;
            break;
                }
                default:
                {
                        return(-1);
                }
        }
        return 0;
}

static int SSP_Read(int Handle, unsigned int Addr, unsigned int *pValue)
{
        DPRINTK("SSP_Read\n");
        return(0);
}

static int SSP_Write(int Handle, unsigned int Addr, unsigned int Value)
{
        int iRet = 0;
    // DPRINTK("SSP_Write - Handle:0x%08x  Addr:0x%08x  Value:0x%08x\n",
    //    Handle, Addr, Value );
        
        //
        // Find out which device this API was called for.
        // 
        switch( CheckHandle(Handle) )
        {
                case PS2_KEYBOARD:
                {
                        break;
                }
                case I2S_CODEC:
                {
            iRet = SSP_Write_I2SCodec( Handle, Addr, Value );
                        break;
                }
                case SERIAL_FLASH:
                {
                        break;
                }
                default:
                {
                        return(-1);
                }
        }

        return iRet;
}

static void SetSSPtoPS2(void)
{
        unsigned int uiRegTemp;
        
        if( gSSPmode == SSP_MODE_PS2 )
        {
                return;
        }
        
    /*
         * Disable the SSP, disable interrupts
     */
    outl( 0, SSPCR1 );

        /*
         * It takes almost a millisecond for a key to come in so
         * make sure we have completed all transactions.
         */
        mdelay(1);

        //
        // Set EGPIO7 to disable EEPROM device on EDB9312.
        //
    uiRegTemp = inl(GPIO_PADDR);
    outl( uiRegTemp | 0x80, GPIO_PADDR );
    
    uiRegTemp = inl(GPIO_PADR);
    outl( uiRegTemp | 0x80, GPIO_PADR );

        /*
         * Still haven't enabled the keyboard.  So anything in
         * the rx fifo is garbage.  Time to take out the trash.
         */
        while( inl(SSPSR) & SSPSR_RNE )
    {
        uiRegTemp = inl(SSPDR);
    }
        
    /*
     * SPICR0_SPO - SCLKOUT Polarity
     * SPICR0_SPH - SCLKOUT Phase
     * Motorola format, 11 bit, one start, 8 data, one bit for 
         * parity, one stop bit.
     */
    outl( (SSPCR0_FRF_MOTOROLA | SSPCR0_SPH | SSPCR0_SPO | SSPCR0_DSS_11BIT),
         SSPCR0 );
    /*
     * Configure the device as a slave, Clear FIFO overrun interrupts, 
         * enable interrupts and reset the device.
     */
    outl( (SSPC1_MS | SSPC1_RIE | SSPC1_SOD), SSPCR1 );
        outl( 0, SSPIIR );
    outl( (SSPC1_MS | SSPC1_RIE | SSPC1_SOD | SSPC1_SSE), SSPCR1 );

    /*
     * Configure EGPIO pins 12 and 14 as outputs because they are used 
     * as buffer enables for the SPI interface to the ps2 keyboard.  
     * Clear EGPIO pins 12 and 14, this will enable the SPI keyboard.
     */
    uiRegTemp = inl(GPIO_PBDDR);
    outl( uiRegTemp | 0x50, GPIO_PBDDR );
    
    uiRegTemp = inl(GPIO_PBDR);
    outl( uiRegTemp & ~0x50, GPIO_PBDR );
    
        gSSPmode = SSP_MODE_PS2;
}

static void SetSSPtoI2S(void)
{
        unsigned int uiRegTemp;
    
    if( gSSPmode == SSP_MODE_I2S )
        {
                return;
        }
        
    /*
         * Disable recieve interrupts.
     */
    outl( (SSPC1_MS | SSPC1_SSE), SSPCR1 );  

    /*
     * Set GPIO pins 12 and 14, this will bring the clock line low
         * which signals to the keyboard to buffer keystrokes.
         * Note that EGPIO 14 is the clock line and EGPIO 12 is data line.
     */
    uiRegTemp = inl(GPIO_PBDR);
    outl( 0x50 | uiRegTemp, GPIO_PBDR );

        /*
         * It takes almost a millisecond for an partial keystrokes to come in.
     * Delay to make sure we have completed all transactions.
         */
        mdelay(1);

        /*
         * Anything we just recieved is garbage.  Time to take out the trash.
         */
        while( inl(SSPSR) & SSPSR_RNE )
    {
        uiRegTemp = inl(SSPDR);
    }
        
    /*
         * Disable the SSP and disable interrupts
     */
    outl( 0, SSPCR1 );

        /*
         * Clock will be 14.7 MHz divided by 4.
         */
        outl( 2, SSPCPSR );

        /*
         * Configure EGPIO7 as an output and set it.  This selects
         * I2S codec as the device on the SSP output instead of 
         * the serial flash.
         */
    uiRegTemp = inl(GPIO_PADDR);
    outl( uiRegTemp | 0x80, GPIO_PADDR );
    
    uiRegTemp = inl(GPIO_PADR);
    outl( uiRegTemp | 0x80, GPIO_PADR );

    /*
     * Motorola format, 8 bit.
     */
    outl( (SSPCR0_SPO | SSPCR0_SPH | SSPCR0_FRF_MOTOROLA | SSPCR0_DSS_8BIT),
        SSPCR0 );
        
    /*
     * Configure the device as master, reenable the device.
     */
    outl( SSPC1_SSE, SSPCR1 );  

        gSSPmode = SSP_MODE_I2S;

        udelay(10);
}


/*
 *  CheckHandle
 *
 *  If Handle is valid, returns 0.  Otherwise it returns -1.
 */
static int CheckHandle(int Handle)
{
        int iRet;
    
    if ((Handle != KeyboardHandle) && 
        (Handle != I2SHandle) && 
                (Handle != FlashHandle))
        {
                DPRINTK("OOPS! Invalid SSP Handle!\n");
                return(-1);
        }
    
    /*
     * Get the SSP driver instance number from the handle.
     */
    iRet = (((int)Handle & SSP_DEVICE_MASK) >> SSP_DEVICE_SHIFT);
    
#if 0
    switch(iRet)
    {
                case PS2_KEYBOARD:
            DPRINTK("CheckHandle - valid - Keyboard\n");
            break;

                case I2S_CODEC:
            DPRINTK("CheckHandle - valid - I2S\n");
            break;

                case SERIAL_FLASH:
            DPRINTK("CheckHandle - valid - Keyboard\n");
            break;
    }
#endif

    return iRet;
}

/*
 * ReadIntoBuffer
 *
 * Drains the SSP rx fifo into a buffer here.  If we overflow this buffer
 * then something's wrong. 
 */
static int ReadIntoBuffer(void)
{
        unsigned int count, index, saved_count, uiRegTemp;
        
        count = 0;
        index = 0;


        if( gSSPmode != SSP_MODE_PS2 )
        {
                return 0;
        }
        
    /*
     * This spinlock will prevent I2S from grabbing the SSP to do a 
     * write while we are using the SSP for PS2.
     *
     * There is a slight chance that we are in the beginning phase
     * of doing an I2S write but the mode flag hadn't yet switched
     * to I2S.  If that happens we will end up waiting on I2S to 
     * finish a write.  Not great.
     */
        spin_lock(&ssp_spinlock);

        while( inl(SSPSR) & SSPSR_RNE)
        {
        /*
         * Read in the value from the SPI controller into
                 * the partial key buffer.
         */
                uiKeyBuffer[count] = inl(SSPDR);
                if (((uiKeyBuffer[count] & 0x3fc) != 0x3e0) && 
                        ((uiKeyBuffer[count] & 0x3fc) != 0x3c0))
                {
            /*
             * Set GPIO pins 12 and 14, this will bring the clock line low
                 * which signals to the keyboard to buffer keystrokes.
                 * Note that EGPIO 14 is the clock line and EGPIO 12 is data 
line.
             */    
            uiRegTemp = inl(GPIO_PBDR);
            outl( 0x50 | uiRegTemp, GPIO_PBDR );

            outl( 0, SSPCR1 );
            outl( (SSPC1_MS | SSPC1_RIE | SSPC1_SSE), SSPCR1 );

            /*
             * Clear EGPIO pins 12 and 14, this will enable the SPI keyboard.
             */
            uiRegTemp = inl(GPIO_PBDR);
            outl( uiRegTemp & ~0x50, GPIO_PBDR );
            
                        count++;
                        break;
                }
                count++;
        }

    saved_count = count;
        index = 0;
        while (count)
        {
                //
                // No callback, dump data.
                //
                if (gKeyCallback)
                {
                        gKeyCallback(uiKeyBuffer[index++]);
                }       
                count--;
        }

        spin_unlock(&ssp_spinlock);
    
    return saved_count;
}

/*
 * SSP_Write_I2SCodec 
 *
 */
static int SSP_Write_I2SCodec
(
        int Handle,
    unsigned int RegAddr,
    unsigned int RegValue
)
{
        SSPmodes_t saved_mode;
    
    DPRINTK("SSP_Write_I2SCodec\n");
    
        spin_lock(&ssp_spinlock);

    /*
     * Save the SSP mode.  Switch to I2S mode if we're not
     * already in I2S mode.
     */
    saved_mode = gSSPmode;
    SetSSPtoI2S();

    /*
         * Let TX fifo clear out.  Poll the Transmit Fifo Empty bit.
         */
        while( !( inl(SSPSR) & SSPSR_TFE ) );
        
        /*
         * Write the data out to the tx fifo.
         */
        outl( 0x20, SSPDR ); /* chip address for CS4228 */
        outl( (RegAddr & 0xff), SSPDR );
        outl( (RegValue & 0xff), SSPDR );

    /*
         * Let TX fifo clear out.  Poll the Transmit Fifo Empty bit.
         */
        while( !( inl(SSPSR) & SSPSR_TFE ) );
        
        /*
         * Delay to let stuff make it out of the SR before doing
         * anthing else to the SSP.  It takes 6.8 uSec to do a
         * I2S codec register write.
         */
        udelay(10);

        /*
     * If we were in PS2 mode, switch back to PS2 mode.
     * If we weren't in PS2 mode, that means we didn't compile in
     * the PS2 keyboard support, so no need to switch to PS2 mode.
     */
    if( saved_mode == SSP_MODE_PS2 )
        {
            SetSSPtoPS2();
        }

        spin_unlock(&ssp_spinlock);

        /*
         * Return success.
         */
        return 0;
}

-- 
mfg
 Manfred Gruber
==========================================
Contec Steuerungstechnik & Automation GmbH
Manfred Gruber
Waldeck 1
A-6330 Kufstein
AUSTRIA
------------------------------------------
HOMEPAGE: <http://www.contec.at/>
E-MAIL:manfred.gruber@xxxxxxxxx

Phone: 0043 / (0)5372 64121 25
Fax: 0043 / (0)5372 64121 20
==========================================

Other related posts: