| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2006 Daniel Ankers |
| * |
| * 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. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #include "fat.h" |
| #include "hotswap.h" |
| #include "ata-sd-target.h" |
| #include "ata_idle_notify.h" |
| #include "system.h" |
| #include <string.h> |
| #include "thread.h" |
| #include "led.h" |
| #include "disk.h" |
| #include "cpu.h" |
| #include "panic.h" |
| #include "usb.h" |
| |
| #define BLOCK_SIZE 512 |
| #define SECTOR_SIZE 512 |
| #define BLOCKS_PER_BANK 0x7a7800 |
| |
| #define STATUS_REG (*(volatile unsigned int *)(0x70008204)) |
| #define REG_1 (*(volatile unsigned int *)(0x70008208)) |
| #define UNKNOWN (*(volatile unsigned int *)(0x70008210)) |
| #define BLOCK_SIZE_REG (*(volatile unsigned int *)(0x7000821c)) |
| #define BLOCK_COUNT_REG (*(volatile unsigned int *)(0x70008220)) |
| #define REG_5 (*(volatile unsigned int *)(0x70008224)) |
| #define CMD_REG0 (*(volatile unsigned int *)(0x70008228)) |
| #define CMD_REG1 (*(volatile unsigned int *)(0x7000822c)) |
| #define CMD_REG2 (*(volatile unsigned int *)(0x70008230)) |
| #define RESPONSE_REG (*(volatile unsigned int *)(0x70008234)) |
| #define SD_STATE_REG (*(volatile unsigned int *)(0x70008238)) |
| #define REG_11 (*(volatile unsigned int *)(0x70008240)) |
| #define REG_12 (*(volatile unsigned int *)(0x70008244)) |
| #define DATA_REG (*(volatile unsigned int *)(0x70008280)) |
| |
| /* STATUS_REG bits */ |
| #define DATA_DONE (1 << 12) |
| #define CMD_DONE (1 << 13) |
| #define ERROR_BITS (0x3f) |
| #define READY_FOR_DATA (1 << 8) |
| #define FIFO_FULL (1 << 7) |
| #define FIFO_EMPTY (1 << 6) |
| |
| #define CMD_OK 0x0 /* Command was successful */ |
| #define CMD_ERROR_2 0x2 /* SD did not respond to command (either it doesn't |
| understand the command or is not inserted) */ |
| |
| /* SD States */ |
| #define IDLE 0 |
| #define READY 1 |
| #define IDENT 2 |
| #define STBY 3 |
| #define TRAN 4 |
| #define DATA 5 |
| #define RCV 6 |
| #define PRG 7 |
| #define DIS 8 |
| |
| #define FIFO_LEN 16 /* FIFO is 16 words deep */ |
| |
| /* SD Commands */ |
| #define GO_IDLE_STATE 0 |
| #define ALL_SEND_CID 2 |
| #define SEND_RELATIVE_ADDR 3 |
| #define SET_DSR 4 |
| #define SWITCH_FUNC 6 |
| #define SELECT_CARD 7 |
| #define DESELECT_CARD 7 |
| #define SEND_IF_COND 8 |
| #define SEND_CSD 9 |
| #define SEND_CID 10 |
| #define STOP_TRANSMISSION 12 |
| #define SEND_STATUS 13 |
| #define GO_INACTIVE_STATE 15 |
| #define SET_BLOCKLEN 16 |
| #define READ_SINGLE_BLOCK 17 |
| #define READ_MULTIPLE_BLOCK 18 |
| #define SEND_NUM_WR_BLOCKS 22 |
| #define WRITE_BLOCK 24 |
| #define WRITE_MULTIPLE_BLOCK 25 |
| #define ERASE_WR_BLK_START 32 |
| #define ERASE_WR_BLK_END 33 |
| #define ERASE 38 |
| #define APP_CMD 55 |
| |
| #define EC_OK 0 |
| #define EC_FAILED 1 |
| #define EC_NOCARD 2 |
| #define EC_WAIT_STATE_FAILED 3 |
| #define EC_CHECK_TIMEOUT_FAILED 4 |
| #define EC_POWER_UP 5 |
| #define EC_READ_TIMEOUT 6 |
| #define EC_WRITE_TIMEOUT 7 |
| #define EC_TRAN_SEL_BANK 8 |
| #define EC_TRAN_READ_ENTRY 9 |
| #define EC_TRAN_READ_EXIT 10 |
| #define EC_TRAN_WRITE_ENTRY 11 |
| #define EC_TRAN_WRITE_EXIT 12 |
| #define EC_FIFO_SEL_BANK_EMPTY 13 |
| #define EC_FIFO_SEL_BANK_DONE 14 |
| #define EC_FIFO_ENA_BANK_EMPTY 15 |
| #define EC_FIFO_READ_FULL 16 |
| #define EC_FIFO_WR_EMPTY 17 |
| #define EC_FIFO_WR_DONE 18 |
| #define EC_COMMAND 19 |
| #define NUM_EC 20 |
| |
| /* Application Specific commands */ |
| #define SET_BUS_WIDTH 6 |
| #define SD_APP_OP_COND 41 |
| |
| /** global, exported variables **/ |
| #ifdef HAVE_HOTSWAP |
| #define NUM_VOLUMES 2 |
| #else |
| #define NUM_VOLUMES 1 |
| #endif |
| |
| /* for compatibility */ |
| int ata_spinup_time = 0; |
| |
| long last_disk_activity = -1; |
| |
| /** static, private data **/ |
| static bool initialized = false; |
| |
| static long next_yield = 0; |
| #define MIN_YIELD_PERIOD 1000 |
| |
| static tSDCardInfo card_info[2]; |
| static tSDCardInfo *currcard = NULL; /* current active card */ |
| |
| struct sd_card_status |
| { |
| int retry; |
| int retry_max; |
| }; |
| |
| static struct sd_card_status sd_status[NUM_VOLUMES] = |
| { |
| { 0, 1 }, |
| #ifdef HAVE_HOTSWAP |
| { 0, 10 } |
| #endif |
| }; |
| |
| /* Shoot for around 75% usage */ |
| static long sd_stack [(DEFAULT_STACK_SIZE*2 + 0x1c0)/sizeof(long)]; |
| static const char sd_thread_name[] = "ata/sd"; |
| static struct mutex sd_mtx SHAREDBSS_ATTR; |
| static struct event_queue sd_queue; |
| |
| /* Posted when card plugged status has changed */ |
| #define SD_HOTSWAP 1 |
| /* Actions taken by sd_thread when card status has changed */ |
| enum sd_thread_actions |
| { |
| SDA_NONE = 0x0, |
| SDA_UNMOUNTED = 0x1, |
| SDA_MOUNTED = 0x2 |
| }; |
| |
| /* Private Functions */ |
| |
| static unsigned int check_time[NUM_EC]; |
| |
| static inline bool sd_check_timeout(long timeout, int id) |
| { |
| return !TIME_AFTER(USEC_TIMER, check_time[id] + timeout); |
| } |
| |
| static bool sd_poll_status(unsigned int trigger, long timeout) |
| { |
| long t = USEC_TIMER; |
| |
| while ((STATUS_REG & trigger) == 0) |
| { |
| long time = USEC_TIMER; |
| |
| if (TIME_AFTER(time, next_yield)) |
| { |
| long ty = USEC_TIMER; |
| yield(); |
| timeout += USEC_TIMER - ty; |
| next_yield = ty + MIN_YIELD_PERIOD; |
| } |
| |
| if (TIME_AFTER(time, t + timeout)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int sd_command(unsigned int cmd, unsigned long arg1, |
| unsigned int *response, unsigned int type) |
| { |
| int i, words; /* Number of 16 bit words to read from RESPONSE_REG */ |
| unsigned int data[9]; |
| |
| CMD_REG0 = cmd; |
| CMD_REG1 = (unsigned int)((arg1 & 0xffff0000) >> 16); |
| CMD_REG2 = (unsigned int)((arg1 & 0xffff)); |
| UNKNOWN = type; |
| |
| if (!sd_poll_status(CMD_DONE, 100000)) |
| return -EC_COMMAND; |
| |
| if ((STATUS_REG & ERROR_BITS) != CMD_OK) |
| /* Error sending command */ |
| return -EC_COMMAND - (STATUS_REG & ERROR_BITS)*100; |
| |
| if (cmd == GO_IDLE_STATE) |
| return 0; /* no response here */ |
| |
| words = (type == 2) ? 9 : 3; |
| |
| for (i = 0; i < words; i++) /* RESPONSE_REG is read MSB first */ |
| data[i] = RESPONSE_REG; /* Read most significant 16-bit word */ |
| |
| if (response == NULL) |
| { |
| /* response discarded */ |
| } |
| else if (type == 2) |
| { |
| /* Response type 2 has the following structure: |
| * [135:135] Start Bit - '0' |
| * [134:134] Transmission bit - '0' |
| * [133:128] Reserved - '111111' |
| * [127:001] CID or CSD register including internal CRC7 |
| * [000:000] End Bit - '1' |
| */ |
| response[3] = (data[0]<<24) + (data[1]<<8) + (data[2]>>8); |
| response[2] = (data[2]<<24) + (data[3]<<8) + (data[4]>>8); |
| response[1] = (data[4]<<24) + (data[5]<<8) + (data[6]>>8); |
| response[0] = (data[6]<<24) + (data[7]<<8) + (data[8]>>8); |
| } |
| else |
| { |
| /* Response types 1, 1b, 3, 6, 7 have the following structure: |
| * Types 4 and 5 are not supported. |
| * |
| * [47] Start bit - '0' |
| * [46] Transmission bit - '0' |
| * [45:40] R1, R1b, R6, R7: Command index |
| * R3: Reserved - '111111' |
| * [39:8] R1, R1b: Card Status |
| * R3: OCR Register |
| * R6: [31:16] RCA |
| * [15: 0] Card Status Bits 23, 22, 19, 12:0 |
| * [23] COM_CRC_ERROR |
| * [22] ILLEGAL_COMMAND |
| * [19] ERROR |
| * [12:9] CURRENT_STATE |
| * [8] READY_FOR_DATA |
| * [7:6] |
| * [5] APP_CMD |
| * [4] |
| * [3] AKE_SEQ_ERROR |
| * [2] Reserved |
| * [1:0] Reserved for test mode |
| * R7: [19:16] Voltage accepted |
| * [15:8] echo-back of check pattern |
| * [7:1] R1, R1b: CRC7 |
| * R3: Reserved - '1111111' |
| * [0] End Bit - '1' |
| */ |
| response[0] = (data[0]<<24) + (data[1]<<8) + (data[2]>>8); |
| } |
| |
| return 0; |
| } |
| |
| static int sd_wait_for_state(unsigned int state, int id) |
| { |
| unsigned int response = 0; |
| unsigned int timeout = 0x80000; |
| |
| check_time[id] = USEC_TIMER; |
| |
| while (1) |
| { |
| int ret = sd_command(SEND_STATUS, currcard->rca, &response, 1); |
| long us; |
| |
| if (ret < 0) |
| return ret*100 - id; |
| |
| if (((response >> 9) & 0xf) == state) |
| { |
| SD_STATE_REG = state; |
| return 0; |
| } |
| |
| if (!sd_check_timeout(timeout, id)) |
| return -EC_WAIT_STATE_FAILED*100 - id; |
| |
| us = USEC_TIMER; |
| if (TIME_AFTER(us, next_yield)) |
| { |
| yield(); |
| timeout += USEC_TIMER - us; |
| next_yield = us + MIN_YIELD_PERIOD; |
| } |
| } |
| } |
| |
| static inline void copy_read_sectors_fast(unsigned char **buf) |
| { |
| /* Copy one chunk of 16 words using best method for start alignment */ |
| switch ( (intptr_t)*buf & 3 ) |
| { |
| case 0: |
| asm volatile ( |
| "ldmia %[data], { r2-r9 } \r\n" |
| "orr r2, r2, r3, lsl #16 \r\n" |
| "orr r4, r4, r5, lsl #16 \r\n" |
| "orr r6, r6, r7, lsl #16 \r\n" |
| "orr r8, r8, r9, lsl #16 \r\n" |
| "stmia %[buf]!, { r2, r4, r6, r8 } \r\n" |
| "ldmia %[data], { r2-r9 } \r\n" |
| "orr r2, r2, r3, lsl #16 \r\n" |
| "orr r4, r4, r5, lsl #16 \r\n" |
| "orr r6, r6, r7, lsl #16 \r\n" |
| "orr r8, r8, r9, lsl #16 \r\n" |
| "stmia %[buf]!, { r2, r4, r6, r8 } \r\n" |
| : [buf]"+&r"(*buf) |
| : [data]"r"(&DATA_REG) |
| : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9" |
| ); |
| break; |
| case 1: |
| asm volatile ( |
| "ldmia %[data], { r2-r9 } \r\n" |
| "orr r3, r2, r3, lsl #16 \r\n" |
| "strb r3, [%[buf]], #1 \r\n" |
| "mov r3, r3, lsr #8 \r\n" |
| "strh r3, [%[buf]], #2 \r\n" |
| "mov r3, r3, lsr #16 \r\n" |
| "orr r3, r3, r4, lsl #8 \r\n" |
| "orr r3, r3, r5, lsl #24 \r\n" |
| "mov r5, r5, lsr #8 \r\n" |
| "orr r5, r5, r6, lsl #8 \r\n" |
| "orr r5, r5, r7, lsl #24 \r\n" |
| "mov r7, r7, lsr #8 \r\n" |
| "orr r7, r7, r8, lsl #8 \r\n" |
| "orr r7, r7, r9, lsl #24 \r\n" |
| "mov r2, r9, lsr #8 \r\n" |
| "stmia %[buf]!, { r3, r5, r7 } \r\n" |
| "ldmia %[data], { r3-r10 } \r\n" |
| "orr r2, r2, r3, lsl #8 \r\n" |
| "orr r2, r2, r4, lsl #24 \r\n" |
| "mov r4, r4, lsr #8 \r\n" |
| "orr r4, r4, r5, lsl #8 \r\n" |
| "orr r4, r4, r6, lsl #24 \r\n" |
| "mov r6, r6, lsr #8 \r\n" |
| "orr r6, r6, r7, lsl #8 \r\n" |
| "orr r6, r6, r8, lsl #24 \r\n" |
| "mov r8, r8, lsr #8 \r\n" |
| "orr r8, r8, r9, lsl #8 \r\n" |
| "orr r8, r8, r10, lsl #24 \r\n" |
| "mov r10, r10, lsr #8 \r\n" |
| "stmia %[buf]!, { r2, r4, r6, r8 } \r\n" |
| "strb r10, [%[buf]], #1 \r\n" |
| : [buf]"+&r"(*buf) |
| : [data]"r"(&DATA_REG) |
| : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10" |
| ); |
| break; |
| case 2: |
| asm volatile ( |
| "ldmia %[data], { r2-r9 } \r\n" |
| "strh r2, [%[buf]], #2 \r\n" |
| "orr r3, r3, r4, lsl #16 \r\n" |
| "orr r5, r5, r6, lsl #16 \r\n" |
| "orr r7, r7, r8, lsl #16 \r\n" |
| "stmia %[buf]!, { r3, r5, r7 } \r\n" |
| "ldmia %[data], { r2-r8, r10 } \r\n" |
| "orr r2, r9, r2, lsl #16 \r\n" |
| "orr r3, r3, r4, lsl #16 \r\n" |
| "orr r5, r5, r6, lsl #16 \r\n" |
| "orr r7, r7, r8, lsl #16 \r\n" |
| "stmia %[buf]!, { r2, r3, r5, r7 } \r\n" |
| "strh r10, [%[buf]], #2 \r\n" |
| : [buf]"+&r"(*buf) |
| : [data]"r"(&DATA_REG) |
| : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10" |
| ); |
| break; |
| case 3: |
| asm volatile ( |
| "ldmia %[data], { r2-r9 } \r\n" |
| "orr r3, r2, r3, lsl #16 \r\n" |
| "strb r3, [%[buf]], #1 \r\n" |
| "mov r3, r3, lsr #8 \r\n" |
| "orr r3, r3, r4, lsl #24 \r\n" |
| "mov r4, r4, lsr #8 \r\n" |
| "orr r5, r4, r5, lsl #8 \r\n" |
| "orr r5, r5, r6, lsl #24 \r\n" |
| "mov r6, r6, lsr #8 \r\n" |
| "orr r7, r6, r7, lsl #8 \r\n" |
| "orr r7, r7, r8, lsl #24 \r\n" |
| "mov r8, r8, lsr #8 \r\n" |
| "orr r2, r8, r9, lsl #8 \r\n" |
| "stmia %[buf]!, { r3, r5, r7 } \r\n" |
| "ldmia %[data], { r3-r10 } \r\n" |
| "orr r2, r2, r3, lsl #24 \r\n" |
| "mov r3, r3, lsr #8 \r\n" |
| "orr r4, r3, r4, lsl #8 \r\n" |
| "orr r4, r4, r5, lsl #24 \r\n" |
| "mov r5, r5, lsr #8 \r\n" |
| "orr r6, r5, r6, lsl #8 \r\n" |
| "orr r6, r6, r7, lsl #24 \r\n" |
| "mov r7, r7, lsr #8 \r\n" |
| "orr r8, r7, r8, lsl #8 \r\n" |
| "orr r8, r8, r9, lsl #24 \r\n" |
| "mov r9, r9, lsr #8 \r\n" |
| "orr r10, r9, r10, lsl #8 \r\n" |
| "stmia %[buf]!, { r2, r4, r6, r8 } \r\n" |
| "strh r10, [%[buf]], #2 \r\n" |
| "mov r10, r10, lsr #16 \r\n" |
| "strb r10, [%[buf]], #1 \r\n" |
| : [buf]"+&r"(*buf) |
| : [data]"r"(&DATA_REG) |
| : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10" |
| ); |
| break; |
| } |
| } |
| |
| static inline void copy_read_sectors_slow(unsigned char** buf) |
| { |
| int cnt = FIFO_LEN; |
| int t; |
| |
| /* Copy one chunk of 16 words */ |
| asm volatile ( |
| "1: \r\n" |
| "ldrh %[t], [%[data]] \r\n" |
| "strb %[t], [%[buf]], #1 \r\n" |
| "mov %[t], %[t], lsr #8 \r\n" |
| "strb %[t], [%[buf]], #1 \r\n" |
| "subs %[cnt], %[cnt], #1 \r\n" |
| "bgt 1b \r\n" |
| : [cnt]"+&r"(cnt), [buf]"+&r"(*buf), |
| [t]"=&r"(t) |
| : [data]"r"(&DATA_REG) |
| ); |
| } |
| |
| /* Writes have to be kept slow for now */ |
| static inline void copy_write_sectors(const unsigned char** buf) |
| { |
| int cnt = FIFO_LEN; |
| unsigned t; |
| |
| do |
| { |
| t = *(*buf)++; |
| t |= *(*buf)++ << 8; |
| DATA_REG = t; |
| } while (--cnt > 0); /* tail loop is faster */ |
| } |
| |
| static int sd_select_bank(unsigned char bank) |
| { |
| unsigned char card_data[512]; |
| const unsigned char* write_buf; |
| int i, ret; |
| |
| memset(card_data, 0, 512); |
| |
| ret = sd_wait_for_state(TRAN, EC_TRAN_SEL_BANK); |
| if (ret < 0) |
| return ret; |
| |
| BLOCK_SIZE_REG = 512; |
| BLOCK_COUNT_REG = 1; |
| |
| ret = sd_command(35, 0, NULL, 0x1c0d); /* CMD35 is vendor specific */ |
| if (ret < 0) |
| return ret; |
| |
| SD_STATE_REG = PRG; |
| |
| card_data[0] = bank; |
| |
| /* Write the card data */ |
| write_buf = card_data; |
| for (i = 0; i < BLOCK_SIZE/2; i += FIFO_LEN) |
| { |
| /* Wait for the FIFO to empty */ |
| if (sd_poll_status(FIFO_EMPTY, 10000)) |
| { |
| copy_write_sectors(&write_buf); /* Copy one chunk of 16 words */ |
| continue; |
| } |
| |
| return -EC_FIFO_SEL_BANK_EMPTY; |
| } |
| |
| if (!sd_poll_status(DATA_DONE, 10000)) |
| return -EC_FIFO_SEL_BANK_DONE; |
| |
| currcard->current_bank = bank; |
| |
| return 0; |
| } |
| |
| static void sd_card_mux(int card_no) |
| { |
| /* Set the current card mux */ |
| #if defined(SANSA_E200) || defined(PHILIPS_SA9200) |
| if (card_no == 0) |
| { |
| GPO32_VAL |= 0x4; |
| |
| GPIO_CLEAR_BITWISE(GPIOA_ENABLE, 0x7a); |
| GPIO_CLEAR_BITWISE(GPIOA_OUTPUT_EN, 0x7a); |
| GPIO_SET_BITWISE(GPIOD_ENABLE, 0x1f); |
| GPIO_SET_BITWISE(GPIOD_OUTPUT_VAL, 0x1f); |
| GPIO_SET_BITWISE(GPIOD_OUTPUT_EN, 0x1f); |
| |
| outl((inl(0x70000014) & ~(0x3ffff)) | 0x255aa, 0x70000014); |
| } |
| else |
| { |
| GPO32_VAL &= ~0x4; |
| |
| GPIO_CLEAR_BITWISE(GPIOD_ENABLE, 0x1f); |
| GPIO_CLEAR_BITWISE(GPIOD_OUTPUT_EN, 0x1f); |
| GPIO_SET_BITWISE(GPIOA_ENABLE, 0x7a); |
| GPIO_SET_BITWISE(GPIOA_OUTPUT_VAL, 0x7a); |
| GPIO_SET_BITWISE( GPIOA_OUTPUT_EN, 0x7a); |
| |
| outl(inl(0x70000014) & ~(0x3ffff), 0x70000014); |
| } |
| #else /* SANSA_C200 */ |
| if (card_no == 0) |
| { |
| GPO32_VAL |= 0x4; |
| |
| GPIO_CLEAR_BITWISE(GPIOD_ENABLE, 0x1f); |
| GPIO_CLEAR_BITWISE(GPIOD_OUTPUT_EN, 0x1f); |
| GPIO_SET_BITWISE(GPIOA_ENABLE, 0x7a); |
| GPIO_SET_BITWISE(GPIOA_OUTPUT_VAL, 0x7a); |
| GPIO_SET_BITWISE( GPIOA_OUTPUT_EN, 0x7a); |
| |
| outl(inl(0x70000014) & ~(0x3ffff), 0x70000014); |
| } |
| else |
| { |
| GPO32_VAL &= ~0x4; |
| |
| GPIO_CLEAR_BITWISE(GPIOA_ENABLE, 0x7a); |
| GPIO_CLEAR_BITWISE(GPIOA_OUTPUT_EN, 0x7a); |
| GPIO_SET_BITWISE(GPIOD_ENABLE, 0x1f); |
| GPIO_SET_BITWISE(GPIOD_OUTPUT_VAL, 0x1f); |
| GPIO_SET_BITWISE(GPIOD_OUTPUT_EN, 0x1f); |
| |
| outl((inl(0x70000014) & ~(0x3ffff)) | 0x255aa, 0x70000014); |
| } |
| #endif |
| } |
| |
| static void sd_init_device(int card_no) |
| { |
| /* SD Protocol registers */ |
| #ifdef HAVE_HOTSWAP |
| unsigned int response = 0; |
| #endif |
| unsigned int i; |
| unsigned int c_size; |
| unsigned long c_mult; |
| unsigned char carddata[512]; |
| unsigned char *dataptr; |
| int ret; |
| |
| /* Enable and initialise controller */ |
| REG_1 = 6; |
| |
| /* Initialise card data as blank */ |
| memset(currcard, 0, sizeof(*currcard)); |
| |
| /* Switch card mux to card to initialize */ |
| sd_card_mux(card_no); |
| |
| /* Init NAND */ |
| REG_11 |= (1 << 15); |
| REG_12 |= (1 << 15); |
| REG_12 &= ~(3 << 12); |
| REG_12 |= (1 << 13); |
| REG_11 &= ~(3 << 12); |
| REG_11 |= (1 << 13); |
| |
| DEV_EN |= DEV_ATA; /* Enable controller */ |
| DEV_RS |= DEV_ATA; /* Reset controller */ |
| DEV_RS &=~DEV_ATA; /* Clear Reset */ |
| |
| SD_STATE_REG = TRAN; |
| |
| REG_5 = 0xf; |
| |
| ret = sd_command(GO_IDLE_STATE, 0, NULL, 256); |
| if (ret < 0) |
| goto card_init_error; |
| |
| check_time[EC_POWER_UP] = USEC_TIMER; |
| |
| #ifdef HAVE_HOTSWAP |
| /* Check for SDHC: |
| - non-SDHC cards simply ignore SEND_IF_COND (CMD8) and we get error -219, |
| which we can just ignore and assume we're dealing with standard SD. |
| - SDHC cards echo back the argument into the response. This is how we |
| tell if the card is SDHC. |
| */ |
| ret = sd_command(SEND_IF_COND,0x1aa, &response,7); |
| if ( (ret < 0) && (ret!=-219) ) |
| goto card_init_error; |
| #endif |
| |
| while ((currcard->ocr & (1 << 31)) == 0) /* until card is powered up */ |
| { |
| ret = sd_command(APP_CMD, currcard->rca, NULL, 1); |
| if (ret < 0) |
| goto card_init_error; |
| |
| #ifdef HAVE_HOTSWAP |
| if(response == 0x1aa) |
| { |
| /* SDHC */ |
| ret = sd_command(SD_APP_OP_COND, (1<<30)|0x100000, |
| &currcard->ocr, 3); |
| } |
| else |
| #endif /* HAVE_HOTSWAP */ |
| { |
| /* SD Standard */ |
| ret = sd_command(SD_APP_OP_COND, 0x100000, &currcard->ocr, 3); |
| } |
| |
| if (ret < 0) |
| goto card_init_error; |
| |
| if (!sd_check_timeout(5000000, EC_POWER_UP)) |
| { |
| ret = -EC_POWER_UP; |
| goto card_init_error; |
| } |
| } |
| |
| ret = sd_command(ALL_SEND_CID, 0, currcard->cid, 2); |
| if (ret < 0) |
| goto card_init_error; |
| |
| ret = sd_command(SEND_RELATIVE_ADDR, 0, &currcard->rca, 1); |
| if (ret < 0) |
| goto card_init_error; |
| |
| ret = sd_command(SEND_CSD, currcard->rca, currcard->csd, 2); |
| if (ret < 0) |
| goto card_init_error; |
| |
| /* These calculations come from the Sandisk SD card product manual */ |
| if( (currcard->csd[3]>>30) == 0) |
| { |
| /* CSD version 1.0 */ |
| c_size = ((currcard->csd[2] & 0x3ff) << 2) + (currcard->csd[1]>>30) + 1; |
| c_mult = 4 << ((currcard->csd[1] >> 15) & 7); |
| currcard->max_read_bl_len = 1 << ((currcard->csd[2] >> 16) & 15); |
| currcard->block_size = BLOCK_SIZE; /* Always use 512 byte blocks */ |
| currcard->numblocks = c_size * c_mult * (currcard->max_read_bl_len/512); |
| currcard->capacity = currcard->numblocks * currcard->block_size; |
| } |
| #ifdef HAVE_HOTSWAP |
| else if( (currcard->csd[3]>>30) == 1) |
| { |
| /* CSD version 2.0 */ |
| c_size = ((currcard->csd[2] & 0x3f) << 16) + (currcard->csd[1]>>16) + 1; |
| currcard->max_read_bl_len = 1 << ((currcard->csd[2] >> 16) & 0xf); |
| currcard->block_size = BLOCK_SIZE; /* Always use 512 byte blocks */ |
| currcard->numblocks = c_size << 10; |
| currcard->capacity = currcard->numblocks * currcard->block_size; |
| } |
| #endif /* HAVE_HOTSWAP */ |
| |
| REG_1 = 0; |
| |
| ret = sd_command(SELECT_CARD, currcard->rca, NULL, 129); |
| if (ret < 0) |
| goto card_init_error; |
| |
| ret = sd_command(APP_CMD, currcard->rca, NULL, 1); |
| if (ret < 0) |
| goto card_init_error; |
| |
| ret = sd_command(SET_BUS_WIDTH, currcard->rca | 2, NULL, 1); /* 4 bit */ |
| if (ret < 0) |
| goto card_init_error; |
| |
| ret = sd_command(SET_BLOCKLEN, currcard->block_size, NULL, 1); |
| if (ret < 0) |
| goto card_init_error; |
| |
| BLOCK_SIZE_REG = currcard->block_size; |
| |
| /* If this card is >4GB & not SDHC, then we need to enable bank switching */ |
| if( (currcard->numblocks >= BLOCKS_PER_BANK) && |
| ((currcard->ocr & (1<<30)) == 0) ) |
| { |
| SD_STATE_REG = TRAN; |
| BLOCK_COUNT_REG = 1; |
| |
| ret = sd_command(SWITCH_FUNC, 0x80ffffef, NULL, 0x1c05); |
| if (ret < 0) |
| goto card_init_error; |
| |
| /* Read 512 bytes from the card. |
| The first 512 bits contain the status information |
| TODO: Do something useful with this! */ |
| dataptr = carddata; |
| for (i = 0; i < BLOCK_SIZE/2; i += FIFO_LEN) |
| { |
| /* Wait for the FIFO to be full */ |
| if (sd_poll_status(FIFO_FULL, 100000)) |
| { |
| copy_read_sectors_slow(&dataptr); |
| continue; |
| } |
| |
| ret = -EC_FIFO_ENA_BANK_EMPTY; |
| goto card_init_error; |
| } |
| } |
| |
| currcard->initialized = 1; |
| return; |
| |
| /* Card failed to initialize so disable it */ |
| card_init_error: |
| currcard->initialized = ret; |
| } |
| |
| /* lock must already be aquired */ |
| static void sd_select_device(int card_no) |
| { |
| currcard = &card_info[card_no]; |
| |
| if (card_no == 0) |
| { |
| /* Main card always gets a chance */ |
| sd_status[0].retry = 0; |
| } |
| |
| if (currcard->initialized > 0) |
| { |
| /* This card is already initialized - switch to it */ |
| sd_card_mux(card_no); |
| return; |
| } |
| |
| if (currcard->initialized == 0) |
| { |
| /* Card needs (re)init */ |
| sd_init_device(card_no); |
| } |
| } |
| |
| /* API Functions */ |
| |
| static void ata_led(bool onoff) |
| { |
| led(onoff); |
| } |
| |
| int ata_read_sectors(IF_MV2(int drive,) unsigned long start, int incount, |
| void* inbuf) |
| { |
| #ifndef HAVE_HOTSWAP |
| const int drive = 0; |
| #endif |
| int ret; |
| unsigned char *buf, *buf_end; |
| int bank; |
| |
| /* TODO: Add DMA support. */ |
| |
| mutex_lock(&sd_mtx); |
| |
| ata_led(true); |
| |
| ata_read_retry: |
| if (drive != 0 && !card_detect_target()) |
| { |
| /* no external sd-card inserted */ |
| ret = -EC_NOCARD; |
| goto ata_read_error; |
| } |
| |
| sd_select_device(drive); |
| |
| if (currcard->initialized < 0) |
| { |
| ret = currcard->initialized; |
| goto ata_read_error; |
| } |
| |
| last_disk_activity = current_tick; |
| |
| /* Only switch banks with non-SDHC cards */ |
| if((currcard->ocr & (1<<30))==0) |
| { |
| bank = start / BLOCKS_PER_BANK; |
| |
| if (currcard->current_bank != bank) |
| { |
| ret = sd_select_bank(bank); |
| if (ret < 0) |
| goto ata_read_error; |
| } |
| |
| start -= bank * BLOCKS_PER_BANK; |
| } |
| |
| ret = sd_wait_for_state(TRAN, EC_TRAN_READ_ENTRY); |
| if (ret < 0) |
| goto ata_read_error; |
| |
| BLOCK_COUNT_REG = incount; |
| |
| #ifdef HAVE_HOTSWAP |
| if(currcard->ocr & (1<<30) ) |
| { |
| /* SDHC */ |
| ret = sd_command(READ_MULTIPLE_BLOCK, start, NULL, 0x1c25); |
| } |
| else |
| #endif |
| { |
| ret = sd_command(READ_MULTIPLE_BLOCK, start * BLOCK_SIZE, NULL, 0x1c25); |
| } |
| if (ret < 0) |
| goto ata_read_error; |
| |
| /* TODO: Don't assume BLOCK_SIZE == SECTOR_SIZE */ |
| |
| buf_end = (unsigned char *)inbuf + incount * currcard->block_size; |
| for (buf = inbuf; buf < buf_end;) |
| { |
| /* Wait for the FIFO to be full */ |
| if (sd_poll_status(FIFO_FULL, 0x80000)) |
| { |
| copy_read_sectors_fast(&buf); /* Copy one chunk of 16 words */ |
| /* TODO: Switch bank if necessary */ |
| continue; |
| } |
| |
| ret = -EC_FIFO_READ_FULL; |
| goto ata_read_error; |
| } |
| |
| last_disk_activity = current_tick; |
| |
| ret = sd_command(STOP_TRANSMISSION, 0, NULL, 1); |
| if (ret < 0) |
| goto ata_read_error; |
| |
| ret = sd_wait_for_state(TRAN, EC_TRAN_READ_EXIT); |
| if (ret < 0) |
| goto ata_read_error; |
| |
| while (1) |
| { |
| ata_led(false); |
| mutex_unlock(&sd_mtx); |
| |
| return ret; |
| |
| ata_read_error: |
| if (sd_status[drive].retry < sd_status[drive].retry_max |
| && ret != -EC_NOCARD) |
| { |
| sd_status[drive].retry++; |
| currcard->initialized = 0; |
| goto ata_read_retry; |
| } |
| } |
| } |
| |
| int ata_write_sectors(IF_MV2(int drive,) unsigned long start, int count, |
| const void* outbuf) |
| { |
| /* Write support is not finished yet */ |
| /* TODO: The standard suggests using ACMD23 prior to writing multiple blocks |
| to improve performance */ |
| #ifndef HAVE_HOTSWAP |
| const int drive = 0; |
| #endif |
| int ret; |
| const unsigned char *buf, *buf_end; |
| int bank; |
| |
| mutex_lock(&sd_mtx); |
| |
| ata_led(true); |
| |
| ata_write_retry: |
| if (drive != 0 && !card_detect_target()) |
| { |
| /* no external sd-card inserted */ |
| ret = -EC_NOCARD; |
| goto ata_write_error; |
| } |
| |
| sd_select_device(drive); |
| |
| if (currcard->initialized < 0) |
| { |
| ret = currcard->initialized; |
| goto ata_write_error; |
| } |
| |
| /* Only switch banks with non-SDHC cards */ |
| if((currcard->ocr & (1<<30))==0) |
| { |
| bank = start / BLOCKS_PER_BANK; |
| |
| if (currcard->current_bank != bank) |
| { |
| ret = sd_select_bank(bank); |
| if (ret < 0) |
| goto ata_write_error; |
| } |
| |
| start -= bank * BLOCKS_PER_BANK; |
| } |
| |
| check_time[EC_WRITE_TIMEOUT] = USEC_TIMER; |
| |
| ret = sd_wait_for_state(TRAN, EC_TRAN_WRITE_ENTRY); |
| if (ret < 0) |
| goto ata_write_error; |
| |
| BLOCK_COUNT_REG = count; |
| |
| #ifdef HAVE_HOTSWAP |
| if(currcard->ocr & (1<<30) ) |
| { |
| /* SDHC */ |
| ret = sd_command(WRITE_MULTIPLE_BLOCK, start, NULL, 0x1c2d); |
| } |
| else |
| #endif |
| { |
| ret = sd_command(WRITE_MULTIPLE_BLOCK, start*BLOCK_SIZE, NULL, 0x1c2d); |
| } |
| if (ret < 0) |
| goto ata_write_error; |
| |
| buf_end = outbuf + count * currcard->block_size - 2*FIFO_LEN; |
| |
| for (buf = outbuf; buf <= buf_end;) |
| { |
| if (buf == buf_end) |
| { |
| /* Set SD_STATE_REG to PRG for the last buffer fill */ |
| SD_STATE_REG = PRG; |
| } |
| |
| udelay(2); /* needed here (loop is too fast :-) */ |
| |
| /* Wait for the FIFO to empty */ |
| if (sd_poll_status(FIFO_EMPTY, 0x80000)) |
| { |
| copy_write_sectors(&buf); /* Copy one chunk of 16 words */ |
| /* TODO: Switch bank if necessary */ |
| continue; |
| } |
| |
| ret = -EC_FIFO_WR_EMPTY; |
| goto ata_write_error; |
| } |
| |
| last_disk_activity = current_tick; |
| |
| if (!sd_poll_status(DATA_DONE, 0x80000)) |
| { |
| ret = -EC_FIFO_WR_DONE; |
| goto ata_write_error; |
| } |
| |
| ret = sd_command(STOP_TRANSMISSION, 0, NULL, 1); |
| if (ret < 0) |
| goto ata_write_error; |
| |
| ret = sd_wait_for_state(TRAN, EC_TRAN_WRITE_EXIT); |
| if (ret < 0) |
| goto ata_write_error; |
| |
| while (1) |
| { |
| ata_led(false); |
| mutex_unlock(&sd_mtx); |
| |
| return ret; |
| |
| ata_write_error: |
| if (sd_status[drive].retry < sd_status[drive].retry_max |
| && ret != -EC_NOCARD) |
| { |
| sd_status[drive].retry++; |
| currcard->initialized = 0; |
| goto ata_write_retry; |
| } |
| } |
| } |
| |
| static void sd_thread(void) __attribute__((noreturn)); |
| static void sd_thread(void) |
| { |
| struct queue_event ev; |
| bool idle_notified = false; |
| |
| while (1) |
| { |
| queue_wait_w_tmo(&sd_queue, &ev, HZ); |
| |
| switch ( ev.id ) |
| { |
| #ifdef HAVE_HOTSWAP |
| case SYS_HOTSWAP_INSERTED: |
| case SYS_HOTSWAP_EXTRACTED: |
| fat_lock(); /* lock-out FAT activity first - |
| prevent deadlocking via disk_mount that |
| would cause a reverse-order attempt with |
| another thread */ |
| mutex_lock(&sd_mtx); /* lock-out card activity - direct calls |
| into driver that bypass the fat cache */ |
| |
| /* We now have exclusive control of fat cache and ata */ |
| |
| disk_unmount(1); /* release "by force", ensure file |
| descriptors aren't leaked and any busy |
| ones are invalid if mounting */ |
| |
| /* Force card init for new card, re-init for re-inserted one or |
| * clear if the last attempt to init failed with an error. */ |
| card_info[1].initialized = 0; |
| sd_status[1].retry = 0; |
| |
| if (ev.id == SYS_HOTSWAP_INSERTED) |
| disk_mount(1); |
| |
| queue_broadcast(SYS_FS_CHANGED, 0); |
| |
| /* Access is now safe */ |
| mutex_unlock(&sd_mtx); |
| fat_unlock(); |
| break; |
| #endif |
| case SYS_TIMEOUT: |
| if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) |
| { |
| idle_notified = false; |
| } |
| else |
| { |
| /* never let a timer wrap confuse us */ |
| next_yield = USEC_TIMER; |
| |
| if (!idle_notified) |
| { |
| call_ata_idle_notifys(false); |
| idle_notified = true; |
| } |
| } |
| break; |
| case SYS_USB_CONNECTED: |
| usb_acknowledge(SYS_USB_CONNECTED_ACK); |
| /* Wait until the USB cable is extracted again */ |
| usb_wait_for_disconnect(&sd_queue); |
| |
| break; |
| case SYS_USB_DISCONNECTED: |
| usb_acknowledge(SYS_USB_DISCONNECTED_ACK); |
| break; |
| } |
| } |
| } |
| |
| |
| void ata_spindown(int seconds) |
| { |
| (void)seconds; |
| } |
| |
| bool ata_disk_is_active(void) |
| { |
| return 0; |
| } |
| |
| void ata_sleep(void) |
| { |
| } |
| |
| void ata_spin(void) |
| { |
| } |
| |
| /* Hardware reset protocol as specified in chapter 9.1, ATA spec draft v5 */ |
| int ata_hard_reset(void) |
| { |
| return 0; |
| } |
| |
| int ata_soft_reset(void) |
| { |
| return 0; |
| } |
| |
| void ata_enable(bool on) |
| { |
| if(on) |
| { |
| DEV_EN |= DEV_ATA; /* Enable controller */ |
| } |
| else |
| { |
| DEV_EN &= ~DEV_ATA; /* Disable controller */ |
| } |
| } |
| |
| #ifdef HAVE_HOTSWAP |
| void card_enable_monitoring_target(bool on) |
| { |
| if (on) |
| { |
| #ifdef SANSA_E200 |
| GPIO_SET_BITWISE(GPIOA_INT_EN, 0x80); |
| #elif defined(SANSA_C200) |
| GPIO_SET_BITWISE(GPIOL_INT_EN, 0x08); |
| #endif |
| } |
| else |
| { |
| #ifdef SANSA_E200 |
| GPIO_CLEAR_BITWISE(GPIOA_INT_EN, 0x80); |
| #elif defined(SANSA_C200) |
| GPIO_CLEAR_BITWISE(GPIOL_INT_EN, 0x08); |
| #endif |
| } |
| } |
| #endif |
| |
| int ata_init(void) |
| { |
| int ret = 0; |
| |
| if (!initialized) |
| mutex_init(&sd_mtx); |
| |
| mutex_lock(&sd_mtx); |
| |
| ata_led(false); |
| |
| if (!initialized) |
| { |
| initialized = true; |
| |
| /* init controller */ |
| outl(inl(0x70000088) & ~(0x4), 0x70000088); |
| outl(inl(0x7000008c) & ~(0x4), 0x7000008c); |
| GPO32_ENABLE |= 0x4; |
| |
| GPIO_SET_BITWISE(GPIOG_ENABLE, (0x3 << 5)); |
| GPIO_SET_BITWISE(GPIOG_OUTPUT_EN, (0x3 << 5)); |
| GPIO_SET_BITWISE(GPIOG_OUTPUT_VAL, (0x3 << 5)); |
| |
| #ifdef HAVE_HOTSWAP |
| /* enable card detection port - mask interrupt first */ |
| #ifdef SANSA_E200 |
| GPIO_CLEAR_BITWISE(GPIOA_INT_EN, 0x80); |
| |
| GPIO_CLEAR_BITWISE(GPIOA_OUTPUT_EN, 0x80); |
| GPIO_SET_BITWISE(GPIOA_ENABLE, 0x80); |
| #elif defined SANSA_C200 |
| GPIO_CLEAR_BITWISE(GPIOL_INT_EN, 0x08); |
| |
| GPIO_CLEAR_BITWISE(GPIOL_OUTPUT_EN, 0x08); |
| GPIO_SET_BITWISE(GPIOL_ENABLE, 0x08); |
| #endif |
| #endif |
| sd_select_device(0); |
| |
| if (currcard->initialized < 0) |
| ret = currcard->initialized; |
| |
| queue_init(&sd_queue, true); |
| create_thread(sd_thread, sd_stack, sizeof(sd_stack), 0, |
| sd_thread_name IF_PRIO(, PRIORITY_USER_INTERFACE) |
| IF_COP(, CPU)); |
| |
| /* enable interupt for the mSD card */ |
| sleep(HZ/10); |
| #ifdef HAVE_HOTSWAP |
| #ifdef SANSA_E200 |
| CPU_INT_EN = HI_MASK; |
| CPU_HI_INT_EN = GPIO0_MASK; |
| |
| GPIOA_INT_LEV = (0x80 << 8) | (~GPIOA_INPUT_VAL & 0x80); |
| |
| GPIOA_INT_CLR = 0x80; |
| #elif defined SANSA_C200 |
| CPU_INT_EN = HI_MASK; |
| CPU_HI_INT_EN = GPIO2_MASK; |
| |
| GPIOL_INT_LEV = (0x08 << 8) | (~GPIOL_INPUT_VAL & 0x08); |
| |
| GPIOL_INT_CLR = 0x08; |
| #endif |
| #endif |
| } |
| |
| mutex_unlock(&sd_mtx); |
| |
| return ret; |
| } |
| |
| /* move the sd-card info to mmc struct */ |
| tCardInfo *card_get_info_target(int card_no) |
| { |
| int i, temp; |
| static tCardInfo card; |
| static const char mantissa[] = { /* *10 */ |
| 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; |
| static const int exponent[] = { /* use varies */ |
| 1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000 }; |
| |
| card.initialized = card_info[card_no].initialized; |
| card.ocr = card_info[card_no].ocr; |
| for(i=0; i<4; i++) card.csd[i] = card_info[card_no].csd[3-i]; |
| for(i=0; i<4; i++) card.cid[i] = card_info[card_no].cid[3-i]; |
| card.numblocks = card_info[card_no].numblocks; |
| card.blocksize = card_info[card_no].block_size; |
| card.size = card_info[card_no].capacity < 0xffffffff ? |
| card_info[card_no].capacity : 0xffffffff; |
| card.block_exp = card_info[card_no].block_exp; |
| temp = card_extract_bits(card.csd, 29, 3); |
| card.speed = mantissa[card_extract_bits(card.csd, 25, 4)] |
| * exponent[temp > 2 ? 7 : temp + 4]; |
| card.nsac = 100 * card_extract_bits(card.csd, 16, 8); |
| temp = card_extract_bits(card.csd, 13, 3); |
| card.tsac = mantissa[card_extract_bits(card.csd, 9, 4)] |
| * exponent[temp] / 10; |
| card.cid[0] = htobe32(card.cid[0]); /* ascii chars here */ |
| card.cid[1] = htobe32(card.cid[1]); /* ascii chars here */ |
| temp = *((char*)card.cid+13); /* adjust year<=>month, 1997 <=> 2000 */ |
| *((char*)card.cid+13) = (unsigned char)((temp >> 4) | (temp << 4)) + 3; |
| |
| return &card; |
| } |
| |
| bool card_detect_target(void) |
| { |
| #ifdef HAVE_HOTSWAP |
| #ifdef SANSA_E200 |
| return (GPIOA_INPUT_VAL & 0x80) == 0; /* low active */ |
| #elif defined SANSA_C200 |
| return (GPIOL_INPUT_VAL & 0x08) != 0; /* high active */ |
| #endif |
| #else |
| return false; |
| #endif |
| } |
| |
| #ifdef HAVE_HOTSWAP |
| static bool sd1_oneshot_callback(struct timeout *tmo) |
| { |
| (void)tmo; |
| |
| /* This is called only if the state was stable for 300ms - check state |
| * and post appropriate event. */ |
| if (card_detect_target()) |
| queue_broadcast(SYS_HOTSWAP_INSERTED, 0); |
| else |
| queue_broadcast(SYS_HOTSWAP_EXTRACTED, 0); |
| |
| return false; |
| } |
| |
| /* called on insertion/removal interrupt */ |
| void microsd_int(void) |
| { |
| static struct timeout sd1_oneshot; |
| |
| #ifdef SANSA_E200 |
| GPIO_CLEAR_BITWISE(GPIOA_INT_EN, 0x80); |
| GPIOA_INT_LEV = (0x80 << 8) | (~GPIOA_INPUT_VAL & 0x80); |
| GPIOA_INT_CLR = 0x80; |
| GPIO_SET_BITWISE(GPIOA_INT_EN, 0x80); |
| |
| #elif defined SANSA_C200 |
| GPIO_CLEAR_BITWISE(GPIOL_INT_EN, 0x08); |
| GPIOL_INT_LEV = (0x08 << 8) | (~GPIOL_INPUT_VAL & 0x08); |
| GPIOL_INT_CLR = 0x08; |
| GPIO_SET_BITWISE(GPIOL_INT_EN, 0x08); |
| #endif |
| timeout_register(&sd1_oneshot, sd1_oneshot_callback, (3*HZ/10), 0); |
| |
| } |
| #endif /* HAVE_HOTSWAP */ |