//==========================================================================
//
//      ide.c
//
//      Generic IDE disk driver
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.      
// Copyright (C) 2004, 2005, 2006, 2007 eCosCentric Limited                 
//
// eCos 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 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    nickg (freely adapted from redboot ide.c by msalter)
// Contributors: jlarmour
// Date:         2004-01-19
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/devs_disk_ide.h>

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/drv_api.h>
#include <cyg/io/io.h>
#include <cyg/io/devtab.h>
#include <cyg/io/disk.h>

#include <cyg/hal/hal_if.h>

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

// Commands
#define IDE_CMD_ATAPI_RESET     0x08    /* ATAPI soft reset             */
#define IDE_CMD_RESTORE         0x10    /* Restore                      */
#define IDE_CMD_READ            0x20    /* Read sectors                 */
#define IDE_CMD_READLONG        0x22    /* Read long                    */
#define IDE_CMD_READ_EXT        0x24    /* Read sectors 48bit           */
#define IDE_CMD_READ_DMA_EXT    0x25    /* Read using DMA 48bit         */
#define IDE_CMD_READMUL_EXT     0x29    /* Multiple read 48bit          */
#define IDE_CMD_WRITE           0x30    /* Write sectors                */
#define IDE_CMD_WRITELONG       0x32    /* Write long                   */
#define IDE_CMD_WRITE_EXT       0x34    /* Write sectors 48bit          */
#define IDE_CMD_WRITE_DMA_EXT   0x35    /* Write using DMA 48bit        */
#define IDE_CMD_WRITEMUL_EXT    0x39    /* Multiple write 48bit         */
#define IDE_CMD_VERIFY          0x40    /* Read/Verify                  */
#define IDE_CMD_FORMAT          0x50    /* Format track                 */
#define IDE_CMD_SEEK            0x70    /* Seek                         */
#define IDE_CMD_DIAG            0x90    /* Diagnostic tests             */
#define IDE_CMD_PACKET          0xA0    /* ATAPI packet function        */
#define IDE_CMD_PACKET_ID       0xA1    /* ATAPI packet identify        */      
#define IDE_CMD_READMUL         0xc4    /* Multiple read                */
#define IDE_CMD_WRITEMUL        0xc5    /* Multiple write               */
#define IDE_CMD_SETMUL          0xc6    /* Set multiple mode            */
#define IDE_CMD_READ_DMA        0xc8    /* Read using DMA               */
#define IDE_CMD_WRITE_DMA       0xca    /* Write using DMA              */
#define IDE_CMD_STANDBY         0xe0    /* Standby                      */
#define IDE_CMD_IDLE            0xe1    /* Idle                         */
#define IDE_CMD_STANDBYTO       0xe2    /* Timeout standby              */
#define IDE_CMD_IDLETO          0xe3    /* Timeout idle                 */
#define IDE_CMD_RDBUF           0xe4    /* Read buffer                  */
#define IDE_CMD_CHKPOWER        0xe5    /* Check power mode             */
#define IDE_CMD_SLEEP           0xe6    /* Set sleep mode               */
#define IDE_CMD_WRBUF           0xe8    /* Write buffer                 */
#define IDE_CMD_GETID           0xec    /* Get drive's ID               */
#define IDE_CMD_SET_FEATURE     0xef    /* Set features                 */

// Status register bits
#define IDE_STAT_BSY            0x80
#define IDE_STAT_DRDY           0x40
#define IDE_STAT_DF             0x20
#define IDE_STAT_SERVICE        0x10
#define IDE_STAT_DRQ            0x08
#define IDE_STAT_CORR           0x04
#define IDE_STAT_ERR            0x01

// Error register bits
#define IDE_ERROR_ICRC          0x80
#define IDE_ERROR_UNC           0x40
#define IDE_ERROR_MC            0x20
#define IDE_ERROR_IDNF          0x10
#define IDE_ERROR_MCR           0x08
#define IDE_ERROR_ABRT          0x04
#define IDE_ERROR_NM            0x02
#define IDE_ERROR_OBS           0x01


#define IDE_REASON_REL          0x04
#define IDE_REASON_IO           0x02
#define IDE_REASON_COD          0x01

// SET FEATURE subcommands

#define IDE_CMD_FEATURE_TFR_MODE        0x03
#define IDE_CMD_FEATURE_RLA_DISABLE     0x55
#define IDE_CMD_FEATURE_RLA_ENABLE      0xAA

//
// Drive ID offsets of interest
//
#define IDE_DEVID_GENCONFIG               0
#define IDE_DEVID_CYLS                    2
#define IDE_DEVID_HEADS                   6
#define IDE_DEVID_SECTORS                12
#define IDE_DEVID_SERNO                  20
#define IDE_DEVID_FIRMWARE_REV           46
#define IDE_DEVID_MODEL                  54
#define IDE_DEVID_MULTIPLE               94
#define IDE_DEVID_LBA_CAPACITY          120
#define IDE_DEVID_DMA_MODE              126
#define IDE_DEVID_PIO_MODE              128
#define IDE_DEVID_UDMA_MODE             176
#define IDE_DEVID_LBA_CAPACITY48        200
#define IDE_DEVID_PHYSECT               212

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

#if (CYG_BYTEORDER == CYG_LSBFIRST)

#define IDE_READMEM16(  _reg_, _val_ ) ((_val_) = *((volatile CYG_WORD16 *)(_reg_)))
#define IDE_READMEM32(  _reg_, _val_ ) ((_val_) = *((volatile CYG_WORD32 *)(_reg_)))

#else

#define IDE_READMEM16( _data_, _var_ )                  \
    (_var_ = *( ((cyg_uint8 *)(_data_)) + 0 ) |         \
             *( ((cyg_uint8 *)(_data_)) + 1 ) << 8 )

#define IDE_READMEM32(_data_, _var_)                    \
    (_var_ = *( ((cyg_uint8 *)(_data_)) + 0 )        |  \
             *( ((cyg_uint8 *)(_data_)) + 1 ) << 8   |  \
             *( ((cyg_uint8 *)(_data_)) + 2 ) << 16  |  \
             *( ((cyg_uint8 *)(_data_)) + 3 ) << 24)

#endif

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

#ifdef CYGPKG_REDBOOT
static void db_printf( char *fmt, ... )
{
    extern int start_console(void);
    extern void end_console(int);
    va_list a;
    int old_console;
    va_start( a, fmt );
    old_console = start_console();  
    diag_vprintf( fmt, a );
    end_console(old_console);
    va_end( a );
}
#else
#define db_printf diag_printf
#endif

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

#define DEBUG 0

// (DEBUG & 1) == minor debug
// (DEBUG & 2) == ide_diag
// (DEBUG & 4) == IDE_EVENT logging
// (DEBUG & 8) == dump whole buffer read/written by PIO


#if (DEBUG & 2)
#define ide_diag( __fmt, ... ) db_printf("IDE: %30s[%4d]: " __fmt, __FUNCTION__, __LINE__, __VA_ARGS__ );
#define ide_dump_buf( __buf, __size )  diag_dump_buf( __buf, __size )
#else
#define ide_diag( __fmt, ... ) 
#define ide_dump_buf( __buf, __size )
#endif

// ----------------------------------------------------------------------------
// Set this to prevent driver writing to disk. Mainly a debugging feature.

#define IDE_READONLY    0

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

#if (DEBUG & 4)

#define IDE_EVENT_LEN 256
struct ide_event
{
    cyg_uint32          code;
    cyg_int32           interval;
    cyg_uint32          arg1;
    cyg_uint32          arg2;
} ide_events[IDE_EVENT_LEN];
cyg_uint32 ide_event_pos = 0;
cyg_uint32 ide_event_last = 0;
cyg_uint32 ide_event_len = IDE_EVENT_LEN;
#define IDE_EVENT( __code, __arg1, __arg2 )                             \
{                                                                       \
    CYG_INTERRUPT_STATE old;                                            \
    cyg_uint32 now;                                                     \
    struct ide_event *e;                                                \
    HAL_DISABLE_INTERRUPTS( old );                                      \
    e = &ide_events[ide_event_pos++ & (IDE_EVENT_LEN-1)];               \
    e->code = __code;                                                   \
    e->arg1 = (cyg_uint32)(__arg1);                                     \
    e->arg2 = (cyg_uint32)(__arg2);                                     \
    HAL_CLOCK_READ( &now );                                             \
    e->interval = now-ide_event_last;                                   \
    if( e->interval < 0 ) e->interval += CYGNUM_HAL_RTC_PERIOD;         \
    ide_event_last = now;                                               \
    ide_event_len = IDE_EVENT_LEN;                                      \
    ide_events[ide_event_pos & (IDE_EVENT_LEN-1)].code = 0xFFFFFFFF;    \
    HAL_RESTORE_INTERRUPTS( old );                                      \
}

#define IDE_READ_STATUS_FOR_EVENT(__ctlr, __status) HAL_IDE_READ_UINT8(__ctlr, IDE_REG_STATUS, __status);
#else
#define IDE_EVENT( __code, __arg1, __arg2 )
#define IDE_READ_STATUS_FOR_EVENT(__ctlr, __status)
#endif

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

typedef struct {
    int                 ctlr;           // Controller number

    // Current transfer parameters
    int                 op;             // operation: read or write      
    cyg_uint8           *buf;           // next sector to transfer
    cyg_uint32          len;            // length of transfer in bytes
    disk_channel        *chan;          // disk channel tfr is for
    int                 blocksize;      // Size of block for multiple transfers
    cyg_uint16          error_status;   // Error+status registers or zero
    
#ifndef CYGVAR_DEVS_DISK_IDE_POLLED    
    int                 vector;         // Interrupt vector
    cyg_handle_t        interrupt;      // Interrupt handle
    cyg_interrupt       interrupt_obj;  // Interrupt object
#endif
#if CYGINT_DEVS_DISK_IDE_DMA
    CYG_ADDRWORD                dma;    // Pointer to DMA control info
#endif
    
} ide_controller_info_t;

typedef struct {
    int                         num;    // disk number
    ide_controller_info_t       *ctlr;  // pointer to controller
    int                         dev;    // disk device within controller
    int                         flags;  // flag bits, see below
    cyg_disk_identify_t         ident;  // disk ident data
    int                         blocksize;  // Size of blocks for multiple tranfers
    int                         max_mdma_mode;
    int                         max_udma_mode;
} ide_disk_info_t;

/* flag values */
#define IDE_DEV_PRESENT         (1<<0)  // Device is present
#define IDE_DEV_PACKET          (1<<1)  // Supports packet interface
#define IDE_DEV_ADDR48          (1<<2)  // Supports 48bit addressing
#define IDE_DISK_PRESENT        (1<<3)  // Removable disk device present
#define IDE_DEV_DMA             (1<<4)  // Use DMA for this disk
#define IDE_DEV_MULTIPLE        (1<<5)  // Multi-sector tfrs supported

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

static cyg_bool ide_disk_init(struct cyg_devtab_entry *tab);

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED
static Cyg_ErrNo ide_disk_read_poll(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num);
#endif

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_read_pio(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num);
#endif

#if CYGINT_DEVS_DISK_IDE_DMA
static Cyg_ErrNo ide_disk_read_dma(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num);
#endif

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED
static Cyg_ErrNo ide_disk_write_poll(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num);
#endif

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_write_pio(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num);
#endif

#if CYGINT_DEVS_DISK_IDE_DMA
static Cyg_ErrNo ide_disk_write_dma(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num);
#endif

static Cyg_ErrNo ide_disk_get_config(disk_channel *chan, 
                                     cyg_uint32    key,
                                     const void   *xbuf, 
                                     cyg_uint32   *len);

static Cyg_ErrNo ide_disk_set_config(disk_channel *chan, 
                                     cyg_uint32    key,
                                     const void   *xbuf, 
                                     cyg_uint32   *len);

static Cyg_ErrNo ide_disk_lookup(struct cyg_devtab_entry  **tab,
                                 struct cyg_devtab_entry   *sub_tab,
                                 const char                *name);

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED                 

DISK_FUNS(ide_disk_funs,
          ide_disk_read_poll, 
          ide_disk_write_poll,
          ide_disk_get_config,
          ide_disk_set_config
);

#else

DISK_FUNS(ide_disk_funs,
          ide_disk_read_pio, 
          ide_disk_write_pio,
          ide_disk_get_config,
          ide_disk_set_config
);

#endif

#if CYGINT_DEVS_DISK_IDE_DMA

DISK_FUNS(ide_disk_funs_dma,
          ide_disk_read_dma, 
          ide_disk_write_dma,
          ide_disk_get_config,
          ide_disk_set_config
);

#endif

// ----------------------------------------------------------------------------
// DMA modes

#define IDE_DMA_MODE_MDMA       1
#define IDE_DMA_MODE_UDMA       2

// ----------------------------------------------------------------------------
// Command map
//
// The IDE data transfer commands do not have a nice numerical
// relationship to each other. This table provides that relationship.
//
// Transfer commands start at IDE_OP_READ or IDE_OP_WRITE. If DMA is
// supported, then we add IDE_OP_DMA to move to the DMA set. If the
// device supports 48 bit LBA addressing then we additionally add
// IDE_OP_EXT to select the 48 bit version of the command. For PIO
// multiple sector ops, IDE_OP_MUL is added.

cyg_uint8 opmap[] =
{
    0,                  0,                      0,                      0,
    IDE_CMD_READ,       IDE_CMD_READ_EXT,       IDE_CMD_READ_DMA,       IDE_CMD_READ_DMA_EXT,
    IDE_CMD_READMUL,    IDE_CMD_READMUL_EXT,    0,                      0,
    IDE_CMD_WRITE,      IDE_CMD_WRITE_EXT,      IDE_CMD_WRITE_DMA,      IDE_CMD_WRITE_DMA_EXT,
    IDE_CMD_WRITEMUL,   IDE_CMD_WRITEMUL_EXT,   0,                      0,
};

#define IDE_OP_READ             4       // Read ops start at 4
#define IDE_OP_WRITE           12       // Write ops start at 12
#define IDE_OP_EXT              1       // LBA48 version is base op+1
#define IDE_OP_DMA              2       // DMA op is op+2

#define IDE_OP_MUL              4       // PIO multiple op is op+4

// ----------------------------------------------------------------------------
// Include platform-specific config file. 

#include CYGDAT_DEVS_DISK_IDE_INL

// ----------------------------------------------------------------------------
// If DMA is not present, or has been disabled by the target package,
// then supply some empty macros to keep the compiler happy.

#if !CYGINT_DEVS_DISK_IDE_DMA

#define ide_dma_init( __ctlr, __mode, __speed ) (-1)

#define ide_dma_setup( __ctlr, __op, __buf, __len )

#define ide_dma_shutdown( __ctlr, __op )

#endif

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

static void
ide_strncpy(char *dest, cyg_uint16 *src, cyg_uint16 size)
{
    int i;
    cyg_uint8 *s = (cyg_uint8 *)src;

    for (i = 0; i < size; i+=2)
    {
        *dest++ = s[i+1];
        *dest++ = s[i+0];
    }
    *dest = '\0';
}

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

static void ide_udelay(cyg_uint32 usec)
{
#ifdef CYGACC_CALL_IF_DELAY_US
    CYGACC_CALL_IF_DELAY_US(usec);
#else
    HAL_DELAY_US(usec);
#endif
}

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

static cyg_bool
ide_wait_for_drq(ide_controller_info_t *info)
{
    cyg_uint8 status;
    cyg_bool result = false;
#if (DEBUG & 1)
    int count = 0;
    cyg_uint8 old_status = 0;
#endif
    
    do {
	HAL_IDE_READ_UINT8(info->ctlr, IDE_REG_STATUS, status);
#if (DEBUG & 1)
        if( status != old_status )
        {
            old_status = status;
        }
        count++;
#endif
	if (status & IDE_STAT_DRQ)
            result = true;
    } while (status & IDE_STAT_BSY);

    if( ! result )
        ide_diag("failed status %02x\n", status);
    
    return result;
}

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

void ide_wait_rdy( int ctlr )
{
    cyg_uint8 status;
#if (DEBUG & 1)
    cyg_uint8 old_status = 0;
    int count = 0;
#endif

    // Wait for the controller to get into ready state where we can
    // send it a command.
    
    do {
	HAL_IDE_READ_UINT8(ctlr, IDE_REG_STATUS, status);
#if (DEBUG & 1)
        if( status != old_status )
        {
            old_status = status;
        }
        count++;
#endif        
    } while((status & (IDE_STAT_BSY|IDE_STAT_DRDY|IDE_STAT_SERVICE|IDE_STAT_DRQ))
            != (IDE_STAT_DRDY|IDE_STAT_SERVICE));
}

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

static void ide_tfr_cmd( ide_disk_info_t *info, int op, cyg_uint32 block_num, cyg_uint32 count )
{
    int ctlr = info->ctlr->ctlr;
    ide_diag("dev %d[%d] cmd %02x block %d[%d]\n", ctlr, info->dev, op, block_num, count );
    
    ide_wait_rdy( ctlr );

    if( info->flags & IDE_DEV_ADDR48 )
    {
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, count>>8);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, count);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBALOW, (block_num >> 24) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAMID, 0);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAHI,  0);

        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBALOW, (block_num >>  0) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAMID, (block_num >>  8) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAHI,  (block_num >> 16) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE, (info->dev << 4) | 0xe0);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, opmap[op+IDE_OP_EXT] );
    }
    else
    {
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, count);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBALOW, block_num & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAMID, (block_num >>  8) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAHI,  (block_num >> 16) & 0xff);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_DEVICE,
                            ((block_num >> 24) & 0xf) | (info->dev << 4) | 0xe0);
        HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, opmap[op]);
    }
}

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

static __inline__ void ide_read_sector( int ctlr, cyg_uint16 *p )
{
    int j;
    for (j = 0; j < (SECTOR_SIZE / sizeof(cyg_uint16)); j++, p++)
        HAL_IDE_READ_UINT16(ctlr, IDE_REG_DATA, *p);
}

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

static __inline__ void ide_write_sector( int ctlr, const cyg_uint16 *p )
{
    int j;
    for (j = 0; j < (SECTOR_SIZE / sizeof(cyg_uint16)); j++, p++)
        HAL_IDE_WRITE_UINT16(ctlr, IDE_REG_DATA, *p);
    //    ide_wait_rdy(ctlr);
    ide_udelay(50);
#if 0
    db_printf("%02x%02x%02x%02x\n",
                ((cyg_uint8*)p)[-4],
                ((cyg_uint8*)p)[-3],
                ((cyg_uint8*)p)[-2],
                ((cyg_uint8*)p)[-1]);
#endif
}

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

#if CYGINT_DEVS_DISK_IDE_DMA
static void ide_set_feature( ide_disk_info_t *info, int code, int arg1, int arg2, int arg3, int arg4  )
{
    int ctlr = info->ctlr->ctlr;
    
    ide_diag("dev %d[%d] code %02x args %02x %02x %02x %02x\n", ctlr, info->dev, code, arg1, arg2, arg3, arg4);

    ide_wait_rdy( ctlr );
    
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_FEATURES, code);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, arg1);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBALOW, arg2);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAMID, arg3 );
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_LBAHI,  arg4 );

    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, IDE_CMD_SET_FEATURE);    
    
    ide_wait_rdy( ctlr );
}
#endif

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

static void ide_set_multiple( ide_disk_info_t *info, int spb )
{
    int ctlr = info->ctlr->ctlr;
    
    ide_diag("dev %d[%d] spb %02x\n", ctlr, info->dev, spb );

    ide_wait_rdy( ctlr );
    
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COUNT, spb);
    HAL_IDE_WRITE_UINT8(ctlr, IDE_REG_COMMAND, IDE_CMD_SETMUL);    
    
    ide_wait_rdy( ctlr );
}

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

static cyg_bool
ide_reset(ide_controller_info_t *info)
{
    cyg_uint8 status;
    int delay;

    ide_diag("\n", 0 );

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED
    HAL_IDE_WRITE_CONTROL(info->ctlr, 6);	// polled mode, reset asserted
    ide_udelay(5000);
    HAL_IDE_WRITE_CONTROL(info->ctlr, 2);	// polled mode, reset cleared
#else
    HAL_IDE_WRITE_CONTROL(info->ctlr, 4);	// interrupt mode, reset asserted    
    ide_udelay(5000);
    HAL_IDE_WRITE_CONTROL(info->ctlr, 0);	// interrupt mode, reset cleared

#endif
    ide_udelay(50000);

    // wait 30 seconds max for not busy
    for (delay = 0; delay < 300; ++delay) {
	ide_udelay(100000);
	HAL_IDE_READ_UINT8(info->ctlr, IDE_REG_STATUS, status);
	// bail out early on bogus status
	if ((status & (IDE_STAT_BSY|IDE_STAT_DRDY)) == (IDE_STAT_BSY|IDE_STAT_DRDY))
	    break;
	if (!(status & IDE_STAT_BSY))
	    return true;
    }
    ide_diag("ide_reset failed\n", 0);
    return false;
}

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

static cyg_bool
ide_ident(ide_disk_info_t *info, int is_packet_dev, cyg_uint16 *buf)
{
    ide_diag("\n", 0 );

    cyg_drv_isr_lock();
    
    HAL_IDE_WRITE_UINT8(info->ctlr->ctlr, IDE_REG_DEVICE, 0xA0 | info->dev << 4);
    HAL_IDE_WRITE_UINT8(info->ctlr->ctlr, IDE_REG_COMMAND, is_packet_dev ? 0xA1 : 0xEC);
    ide_udelay(50000);

    if (!ide_wait_for_drq(info->ctlr))
    {
        cyg_drv_isr_unlock();
	return false;
    }

    ide_read_sector( info->ctlr->ctlr, buf );

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED    
    cyg_drv_interrupt_acknowledge( info->ctlr->vector );
#endif
    
    cyg_drv_isr_unlock();
    
#if DEBUG
    ide_diag("Ident block:\n", 0);
    diag_dump_buf_with_offset( (cyg_uint8*)buf, 512, (cyg_uint8*)buf );
#endif
    
    return true;
}

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

static cyg_int32 ide_get_ident(ide_disk_info_t   *info)
{
    ide_controller_info_t *ctlr = info->ctlr;    
    cyg_uint8 buf[SECTOR_SIZE];
    cyg_uint16 u16;
    
    if (!ide_ident(info, 0, (cyg_uint16 *)buf)) {
        if (!ide_ident(info, 1, (cyg_uint16 *)buf)) {
            info->flags &= ~IDE_DISK_PRESENT;
            return ENOENT;  // can't identify device
        } else {
            IDE_READMEM16(buf + IDE_DEVID_GENCONFIG, u16);
            if (((u16 >> 8) & 0x1f) != 5) {
                db_printf("Non-CDROM ATAPI device #%d/%d - skipped\n", ctlr->ctlr,info->dev);
            info->flags &= ~IDE_DISK_PRESENT;                
                return ENOENT;
            }
            info->flags |= IDE_DEV_PACKET;
            
        }
    }

    info->ident.sector_size = SECTOR_SIZE;
    
    IDE_READMEM16( buf + IDE_DEVID_CYLS, info->ident.cylinders_num );
    IDE_READMEM16( buf + IDE_DEVID_HEADS, info->ident.heads_num );
    IDE_READMEM16( buf + IDE_DEVID_SECTORS, info->ident.sectors_num );
    IDE_READMEM32(buf + IDE_DEVID_LBA_CAPACITY, info->ident.lba_sectors_num );

    // Look for 48 bit addressing
    
    if( info->ident.lba_sectors_num == 0x0FFFFFFF )
    {
        cyg_uint64 lbalo, lbahi;
        IDE_READMEM32(buf + IDE_DEVID_LBA_CAPACITY48, lbalo );
        IDE_READMEM32(buf + IDE_DEVID_LBA_CAPACITY48 + 4, lbahi );
        info->ident.lba_sectors_num = (lbahi << 32) | lbalo;

        info->flags |= IDE_DEV_ADDR48;
    }
    
    // Report physical block size

    IDE_READMEM16 ( buf + IDE_DEVID_PHYSECT, u16 );

    if( (u16 & 0xF000) == 0x06000 )
        info->ident.phys_block_size = 1 << (u16 & 0x000F);
    else 
        info->ident.phys_block_size = 1;

    // Set maximum transfer size depending on addressing mode
    
    if( info->flags & IDE_DEV_ADDR48 )
        info->ident.max_transfer = 2048;
    else
        info->ident.max_transfer = 256;


    // See whether multiple sector transfers are supported

    IDE_READMEM16 ( buf + IDE_DEVID_MULTIPLE, u16 );

    if( ((u16 & 0x0100) != 0) && ((u16 & 0x00FF) > 1) )
    {
        info->flags |= IDE_DEV_MULTIPLE;
        info->blocksize = u16 & 0x00FF;

        ide_set_multiple( info, info->blocksize );
    }
    else
        info->blocksize = 1;


#if CYGINT_DEVS_DISK_IDE_DMA

    // Determine supported MDMA and UDMA modes.
    
    IDE_READMEM16( buf + IDE_DEVID_DMA_MODE, u16 );
    if( (u16 & 0x0007) != 0 )
    {
        info->flags |= IDE_DEV_DMA;
        if( u16 & (1<<2) ) info->max_mdma_mode = 2;
        else if( u16 & (1<<1) ) info->max_mdma_mode = 1;
        else if( u16 & (1<<0) ) info->max_mdma_mode = 0;
    }
    else
        info->max_mdma_mode = -1;
    ide_diag("MDMA mode word: %04x max_mdma_mode %d\n", u16, info->max_mdma_mode );    
        
    IDE_READMEM16( buf + IDE_DEVID_UDMA_MODE, u16 );
    if( (u16 & 0x007F) != 0 )
    {
        info->flags |= IDE_DEV_DMA;
        if( u16 & (1<<6) ) info->max_udma_mode = 6;
        else if( u16 & (1<<5) ) info->max_udma_mode = 5;
        else if( u16 & (1<<4) ) info->max_udma_mode = 4;
        else if( u16 & (1<<3) ) info->max_udma_mode = 3;
        else if( u16 & (1<<2) ) info->max_udma_mode = 2;        
        else if( u16 & (1<<1) ) info->max_udma_mode = 1;
        else if( u16 & (1<<0) ) info->max_udma_mode = 0;
    }
    else
        info->max_udma_mode = -1;
    ide_diag("UDMA mode word: %04x max_udma_mode %d\n", u16, info->max_udma_mode );    
#endif

    // Set transfer mode, default to PIO mode 3 initially. We do the
    // switch to DMA mode when we replace the function table later.
    
    IDE_READMEM16 ( buf + IDE_DEVID_PIO_MODE, u16 );
    ide_diag("PIO mode word: %04x\n", u16 );
//    ide_set_feature( info, IDE_CMD_FEATURE_TFR_MODE, 0x08|3, 0, 0, 0 );
    
    ide_strncpy( info->ident.serial, (cyg_uint16 *)(buf + IDE_DEVID_SERNO), 20);
    ide_strncpy( info->ident.firmware_rev, (cyg_uint16 *)(buf + IDE_DEVID_FIRMWARE_REV), 8);
    ide_strncpy( info->ident.model_num, (cyg_uint16 *)(buf + IDE_DEVID_MODEL), 40);

    ide_diag("Ident        : C/H/S %d/%d/%d LBA %u\n",
             info->ident.cylinders_num,
             info->ident.heads_num,
             info->ident.sectors_num,
             info->ident.lba_sectors_num);
    ide_diag("     Serial  : >%s<\n", info->ident.serial );
    ide_diag("     Firmware: >%s<\n", info->ident.firmware_rev );
    ide_diag("     Model   : >%s<\n", info->ident.model_num );
    
    return 0;
}

// ----------------------------------------------------------------------------
// Include ISR and DSR functions

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED
#include "ide_intr.inl"
#endif // CYGVAR_DEVS_DISK_IDE_POLLED

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

static cyg_bool ide_disk_init(struct cyg_devtab_entry *tab)
{
    disk_channel      *chan     = (disk_channel *) tab->priv;
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;
    cyg_uint8 u8;    
    int num_controllers;

    ide_diag("\n", 0 );

    if (chan->init) 
        return true;

    // set up controller if not already done.
    if( !chan->controller->init )
    {
        num_controllers = HAL_IDE_INIT();

        ide_udelay(5);

        if( ctlr->ctlr > num_controllers )
            return false;
    
        // Soft reset the devices on this controller
        if (!ide_reset(ctlr))
            return false;

        // Check whether the IDE controller is present
        //
        // This is reminiscent of a memory test. We write a value to a
        // certain location (device register), then write a different
        // value somewhere else so that the first value is not hanging on
        // the bus, then we read back the first value to see if the write
        // was succesful.

#define DEV_INIT_VAL ((info->dev << 4) | 0xA0)
    
        HAL_IDE_WRITE_UINT8(ctlr->ctlr, IDE_REG_DEVICE, DEV_INIT_VAL);
        HAL_IDE_WRITE_UINT8(ctlr->ctlr, IDE_REG_FEATURES, 0);
        ide_udelay(50000);
        HAL_IDE_READ_UINT8(ctlr->ctlr, IDE_REG_DEVICE, u8);
        if (u8 != DEV_INIT_VAL)
        {
            db_printf("IDE failed to identify unit %d - wrote: %x, read: %x\n", 
                        ctlr->ctlr, DEV_INIT_VAL, u8);
            return false;
        }
        
#ifndef CYGVAR_DEVS_DISK_IDE_POLLED
        
        // Set up the interrupt
        cyg_drv_interrupt_create( ctlr->vector,
                                  3,
                                  (cyg_addrword_t)ctlr,
                                  ide_isr,
                                  ide_dsr,
                                  &ctlr->interrupt,
                                  &ctlr->interrupt_obj);
        cyg_drv_interrupt_attach( ctlr->interrupt );
        cyg_drv_interrupt_unmask( ctlr->vector );
        
#endif // CYGVAR_DEVS_DISK_IDE_POLLED

        // chan->controller->init will be set by the call to
        // disk_init() below.
    }
    
    info->flags |= IDE_DEV_PRESENT;

    if (!(chan->callbacks->disk_init)(tab))
        return false;

    return true;
}

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

static Cyg_ErrNo ide_disk_lookup(struct cyg_devtab_entry  **tab,
                                 struct cyg_devtab_entry   *sub_tab,
                                 const char                *name)
{
    Cyg_ErrNo res = ENOERR;
    disk_channel    *chan = (disk_channel *) (*tab)->priv;
    ide_disk_info_t *info = (ide_disk_info_t *) chan->dev_priv;
    
    ide_diag("\n", 0 );

    // If the disk is not connected, then ident it here since the physical
    // device may have been changed since we disconnected.
    if( !chan->info->connected )
        res = ide_get_ident( info );

#if CYGINT_DEVS_DISK_IDE_DMA    
    chan->funs = &ide_disk_funs;
    if( (res == ENOERR) && (info->flags & IDE_DEV_DMA) )
    {
        int speed;

        ide_diag("fl %08x dma %d\n", info->flags, (info->flags & IDE_DEV_DMA) );

        // Negotiate the DMA mode and speed with the platform DMA hardware.
        
        speed = ide_dma_init( info->ctlr, IDE_DMA_MODE_UDMA, info->max_udma_mode );
        if( info->max_udma_mode >= 0 && speed > 0 )
        {
            ide_set_feature( info, IDE_CMD_FEATURE_TFR_MODE, 0x40|speed, 0, 0, 0 );
            chan->funs = &ide_disk_funs_dma;
        }
        else
        {
            speed = ide_dma_init( info->ctlr, IDE_DMA_MODE_MDMA, info->max_mdma_mode );
            if( info->max_mdma_mode >= 0 && speed > 0 )
            {
                ide_set_feature( info, IDE_CMD_FEATURE_TFR_MODE, 0x20|speed, 0, 0, 0 );
                chan->funs = &ide_disk_funs_dma;
            }
        }
    }
#endif
    
    if( res == ENOERR )
        res = (chan->callbacks->disk_connected)(*tab, &info->ident);

    if( res == ENOERR )
        res = (chan->callbacks->disk_lookup)(tab, sub_tab, name);

    return res;
}

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

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_read_poll(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;
    int i;
    int nsect = len;
    cyg_uint16 *p = buf;
#if (DEBUG & 4)
    cyg_uint8 status;
#endif

    ide_diag("%d[%d] -> %08x\n", block_num, nsect, buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xAAAA1111, block_num, (status<<24)|len );

    ide_tfr_cmd( info, IDE_OP_READ, block_num, nsect );

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xAAAA2222, block_num, status );

    // We now wait for the controller to tell us it has data and then
    // read it for each sector.

    for(i = 0; i < nsect; i++)
    {
        if (!ide_wait_for_drq(ctlr)) {
            ide_diag("NO DRQ for ide%d, device %d.\n",
                     ctlr->ctlr, info->dev);
            return -EIO;
        }

        IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);        
        IDE_EVENT( 0xAAAA3333, i, status );
        
        ide_read_sector( ctlr->ctlr, p );

        IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);        
        IDE_EVENT( 0xAAAA4444, i, status );
        
        p += SECTOR_SIZE / sizeof(cyg_uint16);
    }

    IDE_EVENT( 0xAAAAFFFF, 0, 0 );
    
#if (DEBUG & 8)
    db_printf("read block\n");
    diag_dump_buf( buf, 64 );
#endif

    return ENOERR;    
}
#endif

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

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_read_pio(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;
#if (DEBUG & 4)
    cyg_uint8 status;
#endif
    
    ide_diag("%d[%d] -> %08x\n", block_num, len, buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xAAAA1111, block_num, (status<<24)|len );
    
    // Set up controller fields to record this transfer.
    
    ctlr->buf = buf;
    ctlr->len = len;
    ctlr->chan = chan;
    ctlr->op = IDE_OP_READ;

    if( info->flags & IDE_DEV_MULTIPLE )
        ctlr->op += IDE_OP_MUL;

    ctlr->blocksize = info->blocksize;
    
    ide_tfr_cmd( info, ctlr->op, block_num, len );

    IDE_EVENT( 0xAAAAFFFF, block_num, 0 );
    
    // We return -EWOULDBLOCK and wait for the interrupt to tell us
    // the data is ready.
    
    return -EWOULDBLOCK;
}
#endif

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

#if CYGINT_DEVS_DISK_IDE_DMA
static Cyg_ErrNo ide_disk_read_dma(disk_channel *chan, 
                               void         *buf,
                               cyg_uint32    len,
                               cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;
#if (DEBUG & 4)
    cyg_uint8 status;
#endif
    
    ide_diag("%d[%d] -> %08x\n", block_num, len, buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xAAAA1111, block_num, status );    
    
    ctlr->chan = chan;
    ctlr->op = IDE_OP_READ+IDE_OP_DMA;

    ide_dma_setup( ctlr, ctlr->op, buf, len*SECTOR_SIZE );

    IDE_EVENT( 0xAAAA2222, buf, len );
    
    ide_tfr_cmd( info, ctlr->op, block_num, len );

    IDE_EVENT( 0xAAAAFFFF, block_num, 0 );

    // We return -EWOULDBLOCK and wait for the interrupt to tell us
    // the DMA has completed.
    
    return -EWOULDBLOCK;
}
#endif

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

#ifdef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_write_poll(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;    
    int i;
    int nsect = len;
    const cyg_uint16 *p = buf;
#if (DEBUG & 4)
    cyg_uint8 status;
#endif

    ide_diag("%d[%d] <- %08x\n",block_num, nsect ,buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xBBBB1111, block_num, (status<<24)|len );

#if IDE_READONLY
    return ENOERR;
#endif
    
    ide_tfr_cmd( info, IDE_OP_WRITE, block_num, nsect );

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xBBBB2222, block_num, status );    
    
    // For each sector, we wait for the controller to tell us it is
    // ready and send it the data.

//    ide_udelay(100000);
    
    for(p = buf, i = 0; i < nsect; i++)
    {
        if (!ide_wait_for_drq(ctlr)) {
            ide_diag("NO DRQ for ide%d, device %d.\n",
                     ctlr->ctlr, info->dev);
            return -EIO;
        }

        IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);        
        IDE_EVENT( 0xBBBB3333, i, status );
        
        ide_write_sector( ctlr->ctlr, p );

        IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);        
        IDE_EVENT( 0xBBBB4444, i, status );
        
        p += SECTOR_SIZE / sizeof(cyg_uint16);        
    }

    IDE_EVENT( 0xBBBBFFFF, block_num, 0 );

#if (DEBUG & 8)
    db_printf("wrote block\n");
    diag_dump_buf( buf, 64 );
#endif
    
    return ENOERR;    
}
#endif

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

#ifndef CYGVAR_DEVS_DISK_IDE_POLLED                 
static Cyg_ErrNo ide_disk_write_pio(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;    
    const cyg_uint16 *p;
    int sectors = 1;
#if (DEBUG & 4)
    cyg_uint8 status;
#endif
    
    ide_diag("%d[%d] <- %08x\n", block_num, len, buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xBBBB1111, block_num, (status<<24)|len );

#if IDE_READONLY
    return ENOERR;
#endif
    
    // Fill in the parameters of this transfer for the ISR. The buf
    // and len are set for the next sector/block since we are going to
    // send the first one here.
    
    ctlr->chan = chan;
    ctlr->op = IDE_OP_WRITE;

    if( info->flags & IDE_DEV_MULTIPLE )
        ctlr->op += IDE_OP_MUL;

    sectors = ctlr->blocksize = info->blocksize;

    ctlr->buf = (cyg_uint8 *)buf + sectors*SECTOR_SIZE;
    ctlr->len = len - sectors;

    ide_tfr_cmd( info, ctlr->op, block_num, len );

    IDE_EVENT( 0xBBBB2222, block_num, len );    
    
    // We send the first sector/block immediately and wait for the
    // interrupt to tell us it has gone. The ISR then sends the
    // following sectors.
    
    p = buf;

    if (!ide_wait_for_drq(ctlr))
    {
        db_printf("%s: NO DRQ for ide%d, device %d.\n",
                    __FUNCTION__, ctlr->ctlr, info->dev);
        return -EIO;
    }

    while( sectors-- > 0 )
    {
        IDE_EVENT( 0xBBBB3333, sectors, 0 );        

        ide_write_sector( ctlr->ctlr, p );
        p += SECTOR_SIZE/sizeof(*p);
    }

    IDE_EVENT( 0xBBBBFFFF, block_num, 0 );    

    return -EWOULDBLOCK;
}
#endif

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

#if CYGINT_DEVS_DISK_IDE_DMA
static Cyg_ErrNo ide_disk_write_dma(disk_channel *chan, 
                                const void   *buf,
                                cyg_uint32    len,
                                cyg_uint32    block_num)
{
    ide_disk_info_t   *info     = (ide_disk_info_t *) chan->dev_priv;
    ide_controller_info_t *ctlr = (ide_controller_info_t *)chan->controller->priv;    
#if (DEBUG & 4)
    cyg_uint8 status;
#endif
    
    ide_diag("%d[%d] <- %08x\n",block_num,len,buf);

    IDE_READ_STATUS_FOR_EVENT(info->ctlr, status);
    IDE_EVENT( 0xBBBB1111, block_num, (status<<24)|len );

#if IDE_READONLY
    return ENOERR;
#endif
    
    ctlr->chan = chan;
    ctlr->op = IDE_OP_WRITE+IDE_OP_DMA;

    ide_dma_setup( ctlr, ctlr->op, buf, len*SECTOR_SIZE );        

    IDE_EVENT( 0xBBBB2222, buf, len );
    
    ide_tfr_cmd( info, ctlr->op, block_num, len );

    IDE_EVENT( 0xBBBBFFFF, block_num, 0 );
    
    return -EWOULDBLOCK;
}
#endif

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

static Cyg_ErrNo ide_disk_get_config(disk_channel *chan, 
                                     cyg_uint32    key,
                                     const void   *xbuf, 
                                     cyg_uint32   *len)
{
    ide_diag("\n", 0 );

    return -EINVAL;
}

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

static Cyg_ErrNo ide_disk_set_config(disk_channel *chan, 
                                     cyg_uint32    key,
                                     const void   *xbuf,
                                     cyg_uint32   *len)
{
    Cyg_ErrNo res = -EINVAL;

    ide_diag("\n", 0 );

    switch ( key )
    {
    case CYG_IO_SET_CONFIG_DISK_MOUNT:
        res = ENOERR;
        break;
            
    case CYG_IO_SET_CONFIG_DISK_UMOUNT:
        if( chan->info->mounts == 0 )
        {
            // If this is the last unmount of this disk, then disconnect it from
            // the driver system so the user can swap it out if he wants.
            res = (chan->callbacks->disk_disconnected)(chan);
        }
        else
            res = ENOERR;
        break;
            
    default:
        break;
    }
    
    return res;
}


// ----------------------------------------------------------------------------
// EOF ide.c
