summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libbsp/powerpc/gen5200/ide/ata.c
blob: 5230f5d3e17b6fccd9750c33778aa92b6907728b (plain) (tree)


















                                                                      
                      

                        

                                   
















































                                                                                    
                                                    
                                  
                     
                         
                        




















                                             
                       


                           
                          
                                                        





















                                                                                   
                               
                                             
   




                                                            
                               
                                              
   


























                                                                                                           
                                                   








































































                                                                                                         
/*
 * Copyright (c) 2010-2013 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Dornierstr. 4
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.com/license/LICENSE.
 */

#define NDEBUG

#include <bsp/ata.h>

#include <bsp.h>
#include <bsp/fatal.h>
#include <bsp/mpc5200.h>

#include <libcpu/powerpc-utility.h>

bool ata_execute_io_command(uint8_t command, uint32_t lba, uint32_t sector_count_32)
{
  assert(sector_count_32 >= 1);
  assert(sector_count_32 <= 256);

  assert(ata_is_drive_ready_for_selection());

  ATA->write.head = (uint8_t) (0xe0 | ((lba >> 24) & 0x0f));

  ata_wait_400_nano_seconds();
  ata_wait_for_drive_ready();

  /*
   * It seems that the write to the ATA device registers is sometimes not
   * successful.  The write is retried until the read back values are all right
   * or a timeout is reached.
   */
  bool ok = false;
  uint8_t sector = (uint8_t) lba;
  uint8_t cylinder_low = (uint8_t) (lba >> 8);
  uint8_t cylinder_high = (uint8_t) (lba >> 16);
  uint8_t sector_count = (uint8_t) sector_count_32;
  int i;
  for (i = 0; !ok && i < 100; ++i) {
    ATA->write.sector = sector;
    ATA->write.cylinder_low = cylinder_low;
    ATA->write.cylinder_high = cylinder_high;
    ATA->write.sector_count = sector_count;

    uint8_t actual_sector = ATA->read.sector;
    uint8_t actual_cylinder_low = ATA->read.cylinder_low;
    uint8_t actual_cylinder_high = ATA->read.cylinder_high;
    uint8_t actual_sector_count = ATA->read.sector_count;

    ok = actual_cylinder_high == cylinder_high
      && actual_cylinder_low == cylinder_low
      && actual_sector == sector
      && actual_sector_count == sector_count;
  }

  if (ok) {
    ATA->write.command = command;
  }

  return ok;
}

void ata_reset_device(void)
{
  /* ATA/ATAPI-7 V2, 11.2 Software reset protocol */
  ATA->write.control = DCTRL_SRST;
  rtems_bsp_delay(5);
  ATA->write.control = 0;
  rtems_bsp_delay(2000);
  ata_wait_for_not_busy();
}

bool ata_set_transfer_mode(uint8_t mode)
{
  assert(ata_is_drive_ready_for_selection());

  ATA->write.head = 0xe0;

  ata_wait_400_nano_seconds();
  ata_wait_for_drive_ready();

  ATA->write.feature = 0x3;
  ATA->write.sector_count = mode;
  ATA->write.command = 0xef;

  ata_wait_for_not_busy();

  return ata_check_status();
}

static bool probe(void)
{
  bool card_present = true;

#ifdef MPC5200_BOARD_BRS5L
  volatile struct mpc5200_gpt *gpt = &mpc5200.gpt[GPT2];

  /* Enable card detection on GPT2 */
  gpt->emsel = (GPT_EMSEL_GPIO_IN | GPT_EMSEL_TIMER_MS_GPIO);

  /* Check for card detection (-CD0) */
  if ((gpt->status & GPT_STATUS_PIN) != 0) {
    card_present = false;
  }
#endif

  return card_present;
}

static void create_lock(ata_driver *self)
{
  rtems_status_code sc = rtems_semaphore_create(
    rtems_build_name('A', 'T', 'A', ' '),
    1,
    RTEMS_LOCAL | RTEMS_BINARY_SEMAPHORE | RTEMS_PRIORITY | RTEMS_INHERIT_PRIORITY,
    0,
    &self->lock
  );
  if (sc != RTEMS_SUCCESSFUL) {
    bsp_fatal(MPC5200_FATAL_ATA_LOCK_CREATE);
  }
}

static void destroy_lock(const ata_driver *self)
{
  rtems_status_code sc = rtems_semaphore_delete(self->lock);
  if (sc != RTEMS_SUCCESSFUL) {
    bsp_fatal(MPC5200_FATAL_ATA_LOCK_DESTROY);
  }
}

void ata_driver_create(ata_driver *self, const char *device_file_path, rtems_block_device_ioctl io_control)
{
  self->card_present = probe();

  if (ata_driver_is_card_present(self)) {
    create_lock(self);
    ata_reset_device();

    uint16_t sector_buffer[256];
    ata_dev_t ata_device;
    rtems_status_code sc = ata_identify_device(0, 0, sector_buffer, &ata_device);

    if (sc == RTEMS_SUCCESSFUL && ata_device.lba_avaible) {
      sc = ide_controller_config_io_speed(0, ata_device.modes_available);

      if (sc == RTEMS_SUCCESSFUL) {
        sc = rtems_blkdev_create(
          device_file_path,
          ATA_SECTOR_SIZE,
          ata_device.lba_sectors,
          io_control,
          self
        );

        if (sc != RTEMS_SUCCESSFUL) {
          bsp_fatal(MPC5200_FATAL_ATA_DISK_CREATE);
        }
      }
    }
  }
}

void ata_driver_destroy(ata_driver *self)
{
  destroy_lock(self);
  /* TODO */
  assert(0);
}

static bool transfer_pio_polled(ata_driver *self, bool read, rtems_blkdev_sg_buffer *sg, size_t sg_count)
{
  bool ok = true;

  ata_sg_context sg_context;
  ata_sg_create(&sg_context, sg, sg_count);
  rtems_blkdev_bnum start_sector = ata_sg_get_start_sector(&sg_context);
  rtems_blkdev_bnum sector_count = ata_sg_get_sector_count(&sg_context);
  rtems_blkdev_bnum relative_sector = 0;

  uint8_t command = ata_read_or_write_sectors_command(read);

  while (ok && relative_sector < sector_count) {
    rtems_blkdev_bnum remaining_sectors = sector_count - relative_sector;
    rtems_blkdev_bnum transfer_count = ata_max_transfer_count(remaining_sectors);
    rtems_blkdev_bnum transfer_end = relative_sector + transfer_count;

    ok = ata_execute_io_command(command, start_sector + relative_sector, transfer_count);

    rtems_blkdev_bnum transfer;
    for (transfer = relative_sector; ok && transfer < transfer_end; ++transfer) {
      uint16_t *current = ata_sg_get_sector_data_begin(&sg_context, transfer);
      uint16_t *end = ata_sg_get_sector_data_end(&sg_context, current);

      ok = ata_wait_for_data_request();

      if (ok) {
        if (read) {
          while (current != end) {
            *current = ATA->read.data;
            ++current;
          }
        } else {
          while (current != end) {
            ATA->write.data = *current;
            ++current;
          }
        }
      }
    }

    if (ok) {
      ata_wait_for_not_busy();
      ok = ata_check_status();
    }

    relative_sector += ATA_PER_TRANSFER_SECTOR_COUNT_MAX;
  }

  return ok;
}

int ata_driver_io_control_pio_polled(
  rtems_disk_device *dd,
  uint32_t cmd,
  void *arg
)
{
  return ata_driver_io_control(dd, cmd, arg, transfer_pio_polled);
}