/*
 * Copyright (c) 2010 embedded brains GmbH.
 *
 * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
 * Authors: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#include <assert.h>
#include <string.h>

#include <libchip/ide_ctrl_io.h>
#include <libchip/ide_ctrl_cfg.h>
#include <libchip/ata_internal.h>

/* ata_process_request_on_init_phase --
 *     Process the ATA request during system initialization. Request
 *     processing is syncronous and doesn't use multiprocessing enviroment.
 *
 * PARAMETERS:
 *     ctrl_minor - controller identifier
 *     areq       - ATA request
 *
 * RETURNS:
 *     NONE
 */
void
ata_process_request_on_init_phase(rtems_device_minor_number  ctrl_minor,
                                  ata_req_t                 *areq)
{
    uint16_t           byte;/* emphasize that only 8 low bits is meaningful */
    uint8_t            i;
#if 0
    uint8_t            dev;
#endif
    uint16_t           val, val1;
    volatile unsigned  retries;

    assert(areq);

#if 0
    dev =  areq->regs.regs[IDE_REGISTER_DEVICE_HEAD] &
           IDE_REGISTER_DEVICE_HEAD_DEV;
#endif

    ide_controller_write_register(ctrl_minor, IDE_REGISTER_DEVICE_HEAD,
                                  areq->regs.regs[IDE_REGISTER_DEVICE_HEAD]);

	retries = 0;
    do {
        ide_controller_read_register(ctrl_minor, IDE_REGISTER_STATUS, &byte);
        /* If device (on INIT, i.e. it should be idle) is neither
         * busy nor ready something's fishy, i.e., there is probably
         * no device present.
         * I'd like to do a proper timeout but don't know of a portable
         * timeout routine (w/o using multitasking / rtems_task_wake_after())
         */
        if ( ! (byte & (IDE_REGISTER_STATUS_BSY | IDE_REGISTER_STATUS_DRDY))) {
            retries++;
            if ( 10000 == retries ) {
              /* probably no drive connected */
              areq->breq->status = RTEMS_UNSATISFIED;
              return;
            }
        }
    } while ((byte & IDE_REGISTER_STATUS_BSY) ||
             (!(byte & IDE_REGISTER_STATUS_DRDY)));

    for (i=0; i< ATA_MAX_CMD_REG_OFFSET; i++)
    {
        uint32_t   reg = (1 << i);
        if (areq->regs.to_write & reg)
            ide_controller_write_register(ctrl_minor, i,
                                          areq->regs.regs[i]);
    }

    do {
        ide_controller_read_register(ctrl_minor, IDE_REGISTER_STATUS, &byte);
    } while (byte & IDE_REGISTER_STATUS_BSY);

    ide_controller_read_register(ctrl_minor, IDE_REGISTER_STATUS, &val);
    ide_controller_read_register(ctrl_minor, IDE_REGISTER_ERROR, &val1);

    if (val & IDE_REGISTER_STATUS_ERR)
    {
        areq->breq->status = RTEMS_IO_ERROR;
        return;
    }

    switch(areq->type)
    {
        case ATA_COMMAND_TYPE_PIO_IN:
            if (areq->cnt)
            {
              int ccbuf = areq->cbuf;
              ide_controller_read_data_block(ctrl_minor,
                                             areq->breq->bufs[0].length * areq->cnt,
                                             areq->breq->bufs, &areq->cbuf,
                                             &areq->pos);
              ccbuf = areq->cbuf - ccbuf;
              areq->cnt -= ccbuf;
            }
            if (areq->cnt == 0)
            {
                areq->breq->status = RTEMS_SUCCESSFUL;
            }
            else
            {
                /*
                 * this shouldn't happend on the initialization
                 * phase!
                 */
                rtems_fatal_error_occurred(RTEMS_INTERNAL_ERROR);
            }
            break;

        case ATA_COMMAND_TYPE_NON_DATA:
            areq->breq->status = RTEMS_SUCCESSFUL;
            areq->info = val1;
            break;

        default:
            areq->breq->status = RTEMS_IO_ERROR;
            break;
    }
}

void ata_breq_init(blkdev_request1 *breq, uint16_t *sector_buffer)
{
  memset(breq, 0, sizeof(*breq));

  breq->req.done_arg = breq;
  breq->req.bufnum = 1;
  breq->req.bufs [0].length = ATA_SECTOR_SIZE;
  breq->req.bufs [0].buffer = sector_buffer;
}

rtems_status_code ata_identify_device(
  rtems_device_minor_number ctrl_minor,
  int dev,
  uint16_t *sector_buffer,
  ata_dev_t *device_entry
)
{
  ata_req_t areq;
  blkdev_request1 breq;

  ata_breq_init(&breq, sector_buffer);

  /*
   * Issue DEVICE IDENTIFY ATA command and get device
   * configuration
   */
  memset(&areq, 0, sizeof(ata_req_t));
  areq.type = ATA_COMMAND_TYPE_PIO_IN;
  areq.regs.to_write = ATA_REGISTERS_VALUE(IDE_REGISTER_COMMAND);
  areq.regs.regs [IDE_REGISTER_COMMAND] = ATA_COMMAND_IDENTIFY_DEVICE;
  areq.regs.to_read = ATA_REGISTERS_VALUE(IDE_REGISTER_STATUS);
  areq.breq = (rtems_blkdev_request *)&breq;
  areq.cnt = breq.req.bufnum;
  areq.regs.regs [IDE_REGISTER_DEVICE_HEAD] |=
    dev << IDE_REGISTER_DEVICE_HEAD_DEV_POS;

  /*
   * Process the request. Special processing of requests on
   * initialization phase is needed because at this moment there
   * is no multitasking enviroment
   */
  ata_process_request_on_init_phase(ctrl_minor, &areq);

  /* check status of I/O operation */
  if (breq.req.status != RTEMS_SUCCESSFUL) {
    return RTEMS_IO_ERROR;
  }

  /*
   * Parse returned device configuration and fill in ATA internal
   * device info structure
   */
  device_entry->cylinders =
      CF_LE_W(sector_buffer[ATA_IDENT_WORD_NUM_OF_CURR_LOG_CLNDS]);
  device_entry->heads =
      CF_LE_W(sector_buffer[ATA_IDENT_WORD_NUM_OF_CURR_LOG_HEADS]);
  device_entry->sectors =
      CF_LE_W(sector_buffer[ATA_IDENT_WORD_NUM_OF_CURR_LOG_SECS]);
  device_entry->lba_sectors =
      CF_LE_W(sector_buffer[ATA_IDENT_WORD_NUM_OF_USR_SECS1]);
  device_entry->lba_sectors <<= 16;
  device_entry->lba_sectors += CF_LE_W(sector_buffer[ATA_IDENT_WORD_NUM_OF_USR_SECS0]);
  device_entry->lba_avaible =
      (CF_LE_W(sector_buffer[ATA_IDENT_WORD_CAPABILITIES]) >> 9) & 0x1;

  if ((CF_LE_W(sector_buffer[ATA_IDENT_WORD_FIELD_VALIDITY]) &
       ATA_IDENT_BIT_VALID) == 0) {
    /* no "supported modes" info -> use default */
    device_entry->mode_active = ATA_MODES_PIO3;
  } else {
    device_entry->modes_available =
      ((CF_LE_W(sector_buffer[64]) & 0x1) ? ATA_MODES_PIO3 : 0) |
      ((CF_LE_W(sector_buffer[64]) & 0x2) ? ATA_MODES_PIO4 : 0) |
      ((CF_LE_W(sector_buffer[63]) & 0x1) ? ATA_MODES_DMA0 : 0) |
      ((CF_LE_W(sector_buffer[63]) & 0x2) ?
       ATA_MODES_DMA0 | ATA_MODES_DMA1 : 0) |
      ((CF_LE_W(sector_buffer[63]) & 0x4) ?
       ATA_MODES_DMA0 | ATA_MODES_DMA1 | ATA_MODES_DMA2 : 0);
    if (device_entry->modes_available == 0) {
      return RTEMS_IO_ERROR;
    }
  }

  return RTEMS_SUCCESSFUL;
}
