/*  ide.cpp
    Low-Level ATA driver

    27.08.2001: tg, IDE_identify() modified to display
                    model, revision and serialnumber.
    25.08.2001: tk, sourcecode taken from Beck IDE100 example.

    tk: thomas.kindler@gmx.de
    tg: thorsten.godau@tz-mikroelektronik.de

    Copyright (c)2001 by Thomas Kindler, Thorsten Godau

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files
//
#include <dos.h>
#include <mem.h>
#include <stdio.h>

#include "ide.h"
#include "rtxapi.h"
#include "rtos.h"
#include "hwapi.h"
#include "amd186.h"

// IO Port adresses
//
#define IDE_PORT0        (opt_iobase+0x00)
#define IDE_PORT1        (opt_iobase+0x20)
#define IDE_WRITEPORT    (opt_iobase+0x10)

#define IDE_DATA         (IDE_PORT0 + 0)  /* Data (16 Bit wide)  r/w */
#define IDE_FEATURE      (IDE_PORT0 + 2)  /* Features            w   */
#define IDE_ERROR        (IDE_PORT0 + 2)  /* Error               r   */
#define IDE_COUNT        (IDE_PORT0 + 4)  /* Sector Count        r/w */
#define IDE_SECTOR       (IDE_PORT0 + 6)  /* Sector Number       r/w */
#define IDE_CYL_LO       (IDE_PORT0 + 8)  /* Cylinder low        r/w */
#define IDE_CYL_HI       (IDE_PORT0 +10)  /* Cylinder high       r/w */
#define IDE_HEAD         (IDE_PORT0 +12)  /* Drive/Head          r/w */
#define IDE_COMMAND      (IDE_PORT0 +14)  /* Command             w   */
#define IDE_STATUS       (IDE_PORT0 +14)  /* Status              r   */

#define IDE_CONTROL      (IDE_PORT1 +12)  /* Device Control      w   */
#define IDE_ALT_STS      (IDE_PORT1 +12)  /* Alternate Status    r   */
#define IDE_ADDRESS      (IDE_PORT1 +14)  /* Drive Address       r   */

extern  int       opt_timeout;
extern  unsigned  opt_iobase;

unsigned char   err,  sts;


////////////////////////////////////////////////////////
// Read status register
////////////////////////////////////////////////////////
static unsigned char IDE_Status()
{
  sts = inp(IDE_STATUS);
	return sts;
}


////////////////////////////////////////////////////////
// test DRQ-Bit
////////////////////////////////////////////////////////
static unsigned char IDE_DataRequest()
{
  return IDE_Status() & IDE_STS_DRQ;
}


////////////////////////////////////////////////////////
// test error-bit
////////////////////////////////////////////////////////
static unsigned char IDE_Error()
{
  sts = IDE_Status();
  if(sts & IDE_STS_ERR)
    err = inp(IDE_ERROR);
  else
    err = 0;
  return sts & (IDE_STS_ERR | IDE_STS_DWF);
}


////////////////////////////////////////////////////////
// wait until BUSY-Bit is cleared
// ret 0 on error
////////////////////////////////////////////////////////
static unsigned char IDE_WaitIRQ()
{
  unsigned long t0, t1;
  RTX_Get_System_Ticks(&t0);

  while( (IDE_Status() & (IDE_STS_BSY | IDE_STS_DRDY | IDE_STS_DSC))
         != (IDE_STS_DRDY | IDE_STS_DSC))
  {
    RTX_Get_System_Ticks(&t1);
    if((t1-t0) > opt_timeout*1000UL)  {
      outp(IDE_FEATURE, IDE_ERR_TMOUT);
      printf("EXTIDE: drive IRQ not ready\n");
      return 0;
    }
  }
  return 1;
}


////////////////////////////////////////////////////////
// wait for DRQ Bit: Ready for data
// ret 0 on error
////////////////////////////////////////////////////////
static unsigned char IDE_WaitDRQ()
{
  if (IDE_WaitIRQ())
    return IDE_DataRequest();
  else
    return 0;
}


////////////////////////////////////////////////////////
// send a CHS command to disk
////////////////////////////////////////////////////////
static unsigned char IDE_Command( unsigned char feat,
                       unsigned c, unsigned char h, unsigned char s,
                       unsigned char cnt,  unsigned char cmd )
{
  IDE_WaitIRQ();
  outp(IDE_CONTROL, 8);
  outp(IDE_FEATURE, feat);
  outp(IDE_COUNT,   cnt);
  outp(IDE_SECTOR,  s);
  outp(IDE_CYL_LO,  c & 0xff);
  outp(IDE_CYL_HI,  c >> 8);
  outp(IDE_HEAD,    0xa0 | (h & 0x0f));
  outp(IDE_COMMAND, cmd);
  return cnt;
}


////////////////////////////////////////////////////////
// read buffer
////////////////////////////////////////////////////////
static void IDE_ReadBuf(unsigned cnt, char *buf)
{
  asm {
    cld
    mov  cx, cnt
    shr  cx, 1
    mov  dx, opt_iobase
    mov  es, word ptr [buf+2]
    mov  di, word ptr [buf]
    rep  insw
  }
}


////////////////////////////////////////////////////////
// write buffer
////////////////////////////////////////////////////////
static void IDE_WriteBuf(unsigned cnt, char *buf)
{
  asm {
    cld
    push  ds
    mov   cx, cnt
    shr   cx, 3
    mov   dx, opt_iobase
    add   dx, 0x10
    mov   ds, word ptr[buf+2]
    mov   si, word ptr[buf]
  l1:
    lodsw
    xchg  al, ah
    out   dx, ax
    lodsw
    xchg  al, ah
    out   dx, ax
    lodsw
    xchg  al, ah
    out   dx, ax
    lodsw
    xchg  al, ah
    out   dx, ax
    loop l1
    pop   ds
  }
}


////////////////////////////////////////////////////////
// Reset IDE drive
////////////////////////////////////////////////////////
void IDE_Reset()
{
  int tries = 3;
  int error;

  while (tries--) {
    outp(IDE_CONTROL,0x0E);   // activate software reset
    RTX_Sleep_Time(1);
    outp(IDE_CONTROL,0x0A);   // remove reset, no IRQ

    error = IDE_Status();
    if(error < IDE_STS_BSY) {
      printf("EXTIDE: drive not found, %02Xh\n", error);
      return;
    }
    RTX_Sleep_Time(100);

    if(!IDE_WaitIRQ())
      printf("EXTIDE: reset failed\n");
    else
      return;
  }
}

////////////////////////////////////////////////////////
// Read n sectors
////////////////////////////////////////////////////////
int IDE_Read(unsigned c, unsigned h, unsigned s, int  cnt, char *buf)
{
  IDE_Command(0, c, h, s, cnt, IDE_READ_SECTOR);

  for (int i=0; i<cnt; i++) {
    if (IDE_WaitDRQ() && (IDE_Error()==0)) {
      IDE_ReadBuf(512, buf);
      if (IDE_Error())
        return 0;
    } else {
      printf("EXTIDE: read error %02X at C%u H%u S%u\n", err, c, h, s);
      return 0;
    }
    buf += 512;
  }
  return cnt;
}


////////////////////////////////////////////////////////
// Write n sectors
////////////////////////////////////////////////////////
int IDE_Write(unsigned c, unsigned h, unsigned s, int  cnt, char *buf)
{
  IDE_Command(0, c, h, s, cnt, IDE_WRITE_SECTOR);

  for (int i=0; i<cnt; i++) {
    if (IDE_WaitDRQ() && !IDE_Error()) {
      IDE_WriteBuf(512, buf);
      if (IDE_Error()) {
        printf("EXTIDE: write error %02X at C%u H%u S%u\n", err, c, h, s);
        return 0;
      }
      if (!IDE_WaitIRQ()) {
        printf("EXTIDE: write timeout\n");
        return 0;
      }
    } else {
      printf("EXTIDE: write error\n");
      return 0;
    }
    buf += 512;
  }

  return 1;
}


////////////////////////////////////////////////////////
// Read Verify command (test readable sectors)
////////////////////////////////////////////////////////
int IDE_ReadVerify(unsigned c, unsigned h, unsigned s, int cnt)
{
  IDE_Command(0, c, h, s, cnt, IDE_VERIFY_SECTOR);
  IDE_WaitIRQ();

  return IDE_Error() ? 0 : 1;
}


void IDE_Standby()
{
  IDE_Command(0, 0, 0, 0, 0, IDE_STANDBYIMMEDIATE);
}


int IDE_SetFeatures( unsigned char feat,
      unsigned c, unsigned char h, unsigned char s,
      unsigned char cnt)
{
  IDE_Command(feat, c, h, s, cnt, IDE_SET_FEATURES);
  IDE_WaitIRQ();
  return IDE_Error() ? 0 : 1;
}


/**
 * Strip trailing spaces from a padded string field.
 * \note  If the string contains the full length of
 *        characters (i.e. there's no space at the end)
 *        there's no '\0' added to the string!
 * \param str      pointer to string
 * \param length   length of padded string field
 */
static void trim(char *str, int length)
{
  for (int i = length-1; i>=0; i--) {
    if (str[i] == ' ' || str[i] == '\0')
      str[i] = '\0';
    else
      break;
  }
}

inline unsigned swapShort( const unsigned &i )  {
  return ((i << 8) & 0xff00) |
         ((i >> 8) & 0x00ff);
}

////////////////////////////////////////////////////////
// Identify disk
// ret 1 if o.k.
////////////////////////////////////////////////////////
int IDE_Identify(DeviceInfo *info)
{
  unsigned idbuf[256] = {0};
  memset(info, 0, sizeof(DeviceInfo));

  IDE_Reset();

  // set drive 0 and CHS mode
  //
  IDE_Command(0,0,0,0,0, IDE_IDENTIFY);

  // wait for data transfer
  //
  if(IDE_WaitDRQ() == 0)
    return 0;
  if(!IDE_DataRequest())
    return 0;
  if(IDE_Error())
    return 0;

  IDE_ReadBuf(sizeof(idbuf), (char *)idbuf);
  info->id           = idbuf[0];
  info->cylinders    = idbuf[1];
  info->heads        = idbuf[3];
  info->sectors      = idbuf[6];
  info->sector_size  = 512;
  info->totalSectors = (unsigned long)idbuf[1]*(unsigned long)idbuf[3]*
                       (unsigned long)idbuf[6];
  info->totalKBytes  = info->totalSectors >> 1;

  for (int i=10; i<=19; i++)  idbuf[i]=swapShort(idbuf[i]);
  for (int i=23; i<=26; i++)  idbuf[i]=swapShort(idbuf[i]);
  for (int i=27; i<=46; i++)  idbuf[i]=swapShort(idbuf[i]);
  memcpy(info->serial,   &idbuf[10], 20);
  memcpy(info->firmware, &idbuf[23],  8);
  memcpy(info->model,    &idbuf[27], 40);
  trim(info->serial,   20);
  trim(info->firmware,  8);
  trim(info->model,    40);

  return 1;
}
