From bc8cab4c24a0891182ac9711c67165a9e3373b1c Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Thu, 12 Mar 2009 02:01:25 +0000 Subject: Commit the common portion of FS#9708: ATA (IDE) DMA by Boris Gjenero with a couple cosmetic tweaks and without the inclusion of 'FS#9721: No error check after writes in ata.c'changes (which can be done separately). No code is changed for targets without HAVE_ATA_DMA defined other than to not display DMA modes in the View Disk Info debug screen if not using DMA (Gigabeat F/X/S were). No target uses the code yet but Gigabeat S use will follow shortly. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20298 a1c6a512-1295-4272-9138-f99709370657 --- apps/debug_menu.c | 27 ++++-- firmware/drivers/ata.c | 245 +++++++++++++++++++++++++++++++++++++++---------- firmware/export/ata.h | 14 ++- 3 files changed, 228 insertions(+), 58 deletions(-) diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 561be7c947..2912129a1a 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -2029,7 +2029,7 @@ static int disk_callback(int btn, struct gui_synclist *lists) simplelist_addline(SIMPLELIST_ADD_LINE, "No timing info"); } -#if defined (TOSHIBA_GIGABEAT_F) || defined (TOSHIBA_GIGABEAT_S) +#ifdef HAVE_ATA_DMA if (identify_info[63] & (1<<0)) { char mdma0[2], mdma1[2], mdma2[2]; mdma0[1] = mdma1[1] = mdma2[1] = 0; @@ -2047,24 +2047,25 @@ static int disk_callback(int btn, struct gui_synclist *lists) simplelist_addline(SIMPLELIST_ADD_LINE, "No MDMA mode info"); } - if (identify_info[88] & (1<<0)) { - char udma0[2], udma1[2], udma2[2], udma3[2], udma4[2], udma5[2]; - udma0[1] = udma1[1] = udma2[1] = udma3[1] = udma4[1] = udma5[1] = 0; + if (identify_info[53] & (1<<2)) { + char udma0[2], udma1[2], udma2[2], udma3[2], udma4[2], udma5[2], udma6[2]; + udma0[1] = udma1[1] = udma2[1] = udma3[1] = udma4[1] = udma5[1] = udma6[1] = 0; udma0[0] = (identify_info[88] & (1<<0)) ? '0' : 0; udma1[0] = (identify_info[88] & (1<<1)) ? '1' : 0; udma2[0] = (identify_info[88] & (1<<2)) ? '2' : 0; udma3[0] = (identify_info[88] & (1<<3)) ? '3' : 0; udma4[0] = (identify_info[88] & (1<<4)) ? '4' : 0; udma5[0] = (identify_info[88] & (1<<5)) ? '5' : 0; + udma6[0] = (identify_info[88] & (1<<6)) ? '6' : 0; simplelist_addline(SIMPLELIST_ADD_LINE, - "UDMA modes: %s %s %s %s %s %s", udma0, udma1, udma2, - udma3, udma4, udma5); + "UDMA modes: %s %s %s %s %s %s %s", udma0, udma1, udma2, + udma3, udma4, udma5, udma6); } else { simplelist_addline(SIMPLELIST_ADD_LINE, "No UDMA mode info"); } -#endif /* defined (TOSHIBA_GIGABEAT_F) || defined (TOSHIBA_GIGABEAT_S) */ +#endif /* HAVE_ATA_DMA */ timing_info_present = identify_info[53] & (1<<1); if(timing_info_present) { i = identify_info[49] & (1<<11); @@ -2079,6 +2080,18 @@ static int disk_callback(int btn, struct gui_synclist *lists) } simplelist_addline(SIMPLELIST_ADD_LINE, "Cluster size: %d bytes", fat_get_cluster_size(IF_MV(0))); +#ifdef HAVE_ATA_DMA + i = ata_get_dma_mode(); + if (i == 0) { + simplelist_addline(SIMPLELIST_ADD_LINE, + "DMA not enabled"); + } else { + simplelist_addline(SIMPLELIST_ADD_LINE, + "DMA mode: %s %c", + (i & 0x40) ? "UDMA" : "MDMA", + '0' + (i & 7)); + } +#endif /* HAVE_ATA_DMA */ return btn; } #else /* No SD, MMC or ATA */ diff --git a/firmware/drivers/ata.c b/firmware/drivers/ata.c index 23f65b35c5..88633f0ec9 100644 --- a/firmware/drivers/ata.c +++ b/firmware/drivers/ata.c @@ -60,6 +60,12 @@ #define CMD_SLEEP 0xE6 #define CMD_SET_FEATURES 0xEF #define CMD_SECURITY_FREEZE_LOCK 0xF5 +#ifdef HAVE_ATA_DMA +#define CMD_READ_DMA 0xC8 +#define CMD_READ_DMA_EXT 0x25 +#define CMD_WRITE_DMA 0xCA +#define CMD_WRITE_DMA_EXT 0x35 +#endif /* Should all be < 0x100 (which are reserved for control messages) */ #define Q_SLEEP 0 @@ -188,6 +194,10 @@ static struct sector_cache_entry sector_cache; static int phys_sector_mult = 1; #endif +#ifdef HAVE_ATA_DMA +static int dma_mode = 0; +#endif + static int ata_power_on(void); static int perform_soft_reset(void); static int set_multiple_mode(int sectors); @@ -308,6 +318,9 @@ int ata_read_sectors(IF_MV2(int drive,) int count; void* buf; long spinup_start; +#ifdef HAVE_ATA_DMA + bool usedma = false; +#endif #ifndef MAX_PHYS_SECTOR_SIZE #ifdef HAVE_MULTIVOLUME @@ -358,6 +371,12 @@ int ata_read_sectors(IF_MV2(int drive,) ret = 0; last_disk_activity = current_tick; +#ifdef HAVE_ATA_DMA + /* If DMA is supported and parameters are ok for DMA, use it */ + if (dma_mode && ata_dma_setup(inbuf, incount * SECTOR_SIZE, false)) + usedma = true; +#endif + #ifdef HAVE_LBA48 if (lba48) { @@ -370,7 +389,11 @@ int ata_read_sectors(IF_MV2(int drive,) SET_REG(ATA_HCYL, 0); /* 47:40 */ SET_REG(ATA_HCYL, (start >> 16) & 0xff); /* 23:16 */ SET_REG(ATA_SELECT, SELECT_LBA | ata_device); +#ifdef HAVE_ATA_DMA + SET_REG(ATA_COMMAND, usedma ? CMD_READ_DMA_EXT : CMD_READ_MULTIPLE_EXT); +#else SET_REG(ATA_COMMAND, CMD_READ_MULTIPLE_EXT); +#endif } else #endif @@ -380,7 +403,11 @@ int ata_read_sectors(IF_MV2(int drive,) SET_REG(ATA_LCYL, (start >> 8) & 0xff); SET_REG(ATA_HCYL, (start >> 16) & 0xff); SET_REG(ATA_SELECT, ((start >> 24) & 0xf) | SELECT_LBA | ata_device); +#ifdef HAVE_ATA_DMA + SET_REG(ATA_COMMAND, usedma ? CMD_READ_DMA : CMD_READ_MULTIPLE); +#else SET_REG(ATA_COMMAND, CMD_READ_MULTIPLE); +#endif } /* wait at least 400ns between writing command and reading status */ @@ -390,22 +417,13 @@ int ata_read_sectors(IF_MV2(int drive,) __asm__ volatile ("nop"); __asm__ volatile ("nop"); - while (count) { - int sectors; - int wordcount; - int status; +#ifdef HAVE_ATA_DMA + if (usedma) { + if (!ata_dma_finish()) + ret = -7; - if (!wait_for_start_of_transfer()) { - /* We have timed out waiting for RDY and/or DRQ, possibly - because the hard drive is shaking and has problems reading - the data. We have two options: - 1) Wait some more - 2) Perform a soft reset and try again. - - We choose alternative 2. - */ + if (ret != 0) { perform_soft_reset(); - ret = -5; goto retry; } @@ -415,36 +433,67 @@ int ata_read_sectors(IF_MV2(int drive,) sleeping = false; poweroff = false; } + } + else +#endif /* HAVE_ATA_DMA */ + { + while (count) { + int sectors; + int wordcount; + int status; + + if (!wait_for_start_of_transfer()) { + /* We have timed out waiting for RDY and/or DRQ, possibly + because the hard drive is shaking and has problems + reading the data. We have two options: + 1) Wait some more + 2) Perform a soft reset and try again. + + We choose alternative 2. + */ + perform_soft_reset(); + ret = -5; + goto retry; + } - /* read the status register exactly once per loop */ - status = ATA_STATUS; + if (spinup) { + spinup_time = current_tick - spinup_start; + spinup = false; + sleeping = false; + poweroff = false; + } - if (count >= multisectors ) - sectors = multisectors; - else - sectors = count; + /* read the status register exactly once per loop */ + status = ATA_STATUS; - wordcount = sectors * SECTOR_SIZE / 2; + if (count >= multisectors ) + sectors = multisectors; + else + sectors = count; - copy_read_sectors(buf, wordcount); + wordcount = sectors * SECTOR_SIZE / 2; - /* - "Device errors encountered during READ MULTIPLE commands are - posted at the beginning of the block or partial block transfer, - but the DRQ bit is still set to one and the data transfer shall - take place, including transfer of corrupted data, if any." - -- ATA specification - */ - if ( status & (STATUS_BSY | STATUS_ERR | STATUS_DF) ) { - perform_soft_reset(); - ret = -6; - goto retry; - } + copy_read_sectors(buf, wordcount); - buf += sectors * SECTOR_SIZE; /* Advance one chunk of sectors */ - count -= sectors; + /* + "Device errors encountered during READ MULTIPLE commands + are posted at the beginning of the block or partial block + transfer, but the DRQ bit is still set to one and the data + transfer shall take place, including transfer of corrupted + data, if any." + -- ATA specification + */ + if ( status & (STATUS_BSY | STATUS_ERR | STATUS_DF) ) { + perform_soft_reset(); + ret = -6; + goto retry; + } - last_disk_activity = current_tick; + buf += sectors * SECTOR_SIZE; /* Advance one chunk of sectors */ + count -= sectors; + + last_disk_activity = current_tick; + } } if(!ret && !wait_for_end_of_transfer()) { @@ -515,6 +564,9 @@ int ata_write_sectors(IF_MV2(int drive,) int i; int ret = 0; long spinup_start; +#ifdef HAVE_ATA_DMA + bool usedma = false; +#endif #ifndef MAX_PHYS_SECTOR_SIZE #ifdef HAVE_MULTIVOLUME @@ -554,6 +606,12 @@ int ata_write_sectors(IF_MV2(int drive,) goto error; } +#ifdef HAVE_ATA_DMA + /* If DMA is supported and parameters are ok for DMA, use it */ + if (dma_mode && ata_dma_setup((void *)buf, count * SECTOR_SIZE, true)) + usedma = true; +#endif + #ifdef HAVE_LBA48 if (lba48) { @@ -566,7 +624,11 @@ int ata_write_sectors(IF_MV2(int drive,) SET_REG(ATA_HCYL, 0); /* 47:40 */ SET_REG(ATA_HCYL, (start >> 16) & 0xff); /* 23:16 */ SET_REG(ATA_SELECT, SELECT_LBA | ata_device); +#ifdef HAVE_ATA_DMA + SET_REG(ATA_COMMAND, usedma ? CMD_WRITE_DMA_EXT : CMD_WRITE_SECTORS_EXT); +#else SET_REG(ATA_COMMAND, CMD_WRITE_SECTORS_EXT); +#endif } else #endif @@ -576,32 +638,51 @@ int ata_write_sectors(IF_MV2(int drive,) SET_REG(ATA_LCYL, (start >> 8) & 0xff); SET_REG(ATA_HCYL, (start >> 16) & 0xff); SET_REG(ATA_SELECT, ((start >> 24) & 0xf) | SELECT_LBA | ata_device); +#ifdef HAVE_ATA_DMA + SET_REG(ATA_COMMAND, usedma ? CMD_WRITE_DMA : CMD_WRITE_SECTORS); +#else SET_REG(ATA_COMMAND, CMD_WRITE_SECTORS); +#endif } - for (i=0; i= 5us */ +#ifdef HAVE_ATA_DMA + /* DMA requires INTRQ be enabled */ + SET_REG(ATA_CONTROL, 0); +#else SET_REG(ATA_CONTROL, CONTROL_nIEN); +#endif sleep(1); /* >2ms */ /* This little sucker can take up to 30 seconds */ @@ -1179,6 +1265,22 @@ static int set_multiple_mode(int sectors) return 0; } +#ifdef HAVE_ATA_DMA +static int get_best_mode(unsigned short identword, int max, int modetype) +{ + unsigned short testbit = 1u << max; + + while (1) { + if (identword & testbit) + return max | modetype; + testbit >>= 1; + if (!testbit) + return 0; + max--; + } +} +#endif + static int set_features(void) { static struct { @@ -1191,6 +1293,9 @@ static int set_features(void) { 83, 3, 0x05, 0x80 }, /* adv. power management: lowest w/o standby */ { 83, 9, 0x42, 0x80 }, /* acoustic management: lowest noise */ { 82, 6, 0xaa, 0 }, /* enable read look-ahead */ +#ifdef HAVE_ATA_DMA + { 0, 0, 0x03, 0 }, /* DMA mode */ +#endif }; int i; int pio_mode = 2; @@ -1204,6 +1309,23 @@ static int set_features(void) /* Update the table: set highest supported pio mode that we also support */ features[0].parameter = 8 + pio_mode; + +#ifdef HAVE_ATA_DMA + if (identify_info[53] & (1<<2)) + /* Ultra DMA mode info present, find a mode */ + dma_mode = get_best_mode(identify_info[88], ATA_MAX_UDMA, 0x40); + + if (!dma_mode) { + /* No UDMA mode found, try to find a multi-word DMA mode */ + dma_mode = get_best_mode(identify_info[63], ATA_MAX_MWDMA, 0x20); + features[4].id_word = 63; + } + else + features[4].id_word = 88; + + features[4].id_bit = dma_mode & 7; + features[4].parameter = dma_mode; +#endif /* HAVE_ATA_DMA */ SET_REG(ATA_SELECT, ata_device); @@ -1237,6 +1359,10 @@ static int set_features(void) ata_set_pio_timings(pio_mode); #endif +#ifdef HAVE_ATA_DMA + ata_dma_set_mode(dma_mode); +#endif + return 0; } @@ -1305,6 +1431,11 @@ int ata_init(void) sleep(HZ/4); /* allow voltage to build up */ } +#ifdef HAVE_ATA_DMA + /* DMA requires INTRQ be enabled */ + SET_REG(ATA_CONTROL, 0); +#endif + /* first try, hard reset at cold start only */ rc = init_and_check(coldstart); @@ -1450,3 +1581,17 @@ void ata_get_info(struct storage_info *info) info->revision=revision; } #endif + +#ifdef HAVE_ATA_DMA +/* Returns last DMA mode as set by set_features() */ +int ata_get_dma_mode(void) +{ + return dma_mode; +} + +/* Needed to allow updating while waiting for DMA to complete */ +void ata_keep_active(void) +{ + last_disk_activity = current_tick; +} +#endif diff --git a/firmware/export/ata.h b/firmware/export/ata.h index f491e525e6..b5e39de3aa 100644 --- a/firmware/export/ata.h +++ b/firmware/export/ata.h @@ -60,5 +60,17 @@ bool ata_present(IF_MV_NONVOID(int drive)); long ata_last_disk_activity(void); int ata_spinup_time(void); /* ticks */ +#ifdef HAVE_ATA_DMA +/* Needed to allow updating while waiting for DMA to complete */ +void ata_keep_active(void); +/* Returns current DMA mode */ +int ata_get_dma_mode(void); +/* Set DMA mode for ATA interface */ +void ata_dma_set_mode(unsigned char mode); +/* Sets up DMA transfer */ +bool ata_dma_setup(void *addr, unsigned long bytes, bool write); +/* Waits for DMA transfer completion */ +bool ata_dma_finish(void); +#endif /* HAVE_ATA_DMA */ -#endif +#endif /* __ATA_H__ */ -- cgit v1.2.3