#include #include #include #include #include "exceptions.hh" #include "sleep.hh" #include "globals.hh" #include "drv_omap35x_gpt.hh" #include "omap35x_intc.hh" #include "omap35x_prcm.hh" #include "drv_omap35x_sdmmc.hh" class commanderror : public ex::error { public: commanderror(uint32_t stat) : ex::error(EIO), stat(stat) { } uint32_t stat; }; class dataerror : public ex::error { public: dataerror(uint32_t stat) : ex::error(EIO), stat(stat) { } uint32_t stat; }; #define ACMD(x) ((((x)) | 0x100)) typedef union { uint32_t data[4]; struct { unsigned :8; unsigned mdt_m:4; unsigned mdt_y:8; unsigned :4; unsigned psn:32; unsigned prv:8; // BCD coded char pnm[5]; char oid[2]; unsigned mid:8; } __attribute__((packed)) bits; } CID_reg; typedef union { uint32_t data[4]; struct { unsigned :10; unsigned file_format:2; unsigned tmp_write_protect:1; unsigned perm_write_protect:1; unsigned copy:1; unsigned file_format_grp:1; unsigned :5; unsigned write_bl_partial:1; unsigned write_bl_len:4; unsigned r2w_factor:3; unsigned :2; unsigned wp_grp_enable:1; unsigned wp_grp_size:7; unsigned sector_size:7; unsigned erase_blk_en:1; unsigned c_size_mult:3; unsigned vdd_w_curr_max:3; unsigned vdd_w_curr_min:3; unsigned vdd_r_curr_max:3; unsigned vdd_r_curr_min:3; unsigned c_size:12; unsigned :2; unsigned dsr_imp:1; unsigned read_blk_misalign:1; unsigned write_blk_misalign:1; unsigned read_bl_partial:1; unsigned read_bl_len:4; unsigned ccc:12; unsigned tran_speed:8; unsigned nsac:8; unsigned taac:8; unsigned :6; unsigned csd_structure:2; } __attribute__((packed)) v1; struct { unsigned :12; unsigned tmp_write_protect:1; unsigned perm_write_protect:1; unsigned copy:1; unsigned :32; unsigned :1; unsigned c_size:22; unsigned :6; unsigned dsr_imp:1; unsigned :7; unsigned ccc:12; unsigned :30; unsigned csd_structure:2; } __attribute__((packed)) v2; } CSD_reg; #define BCDDEC(x) (((((x))&0xf) + (((((x))>>4)&0xf)*10))) OMAP35x_SDMMC::OMAP35x_SDMMC(uintptr_t base, unsigned irq, unsigned drq_rx, unsigned drq_tx) : base_{base}, irq_(irq), drq_rx_(drq_rx), drq_tx_(drq_tx), rca_(0), intSem_(0) { printf("OMAP35x SD/MMC controller\n"); // Reset the controller r_sysconfig() |= (1<<1); while(!(r_sysstatus() & (1<<0))); r_sysctl() |= (1<<24); // Software reset all while(r_sysctl() & (1<<24)); // Set capabilities uint32_t capa = (1<<26); if(base_.get_phys() == 0x4809c000) { // MMC 1 printf("MMCHS1, activating 3.0V support\n"); capa |= (1<<25); } r_capa() |= capa; // Install interrupt handler global::intc->register_handler(irq_, std::bind(&OMAP35x_SDMMC::int_handler, this), 0); global::intc->enable_int(irq_); // Configure r_hctl() = (r_hctl() & ~(0x7<<9)) | (0x6<<9); // 3.0V mode r_hctl() |= (1<<8); // power on while(!(r_hctl() & (1<<8))); // TODO: padconf changeClock(0x3ff); r_ie() = (1<<28) | (1<<22) | (1<<21) | (1<<20) |(1<<19) | (1<<18) | (1<<17) | (1<<16) | (1<<5) | (1<<1) | (1<<0); // Card detect r_con() |= (1<<1); // Send initialization stream r_cmd() = 0x0; // Dummy command usleep(1000); r_stat() |= (1<<0); r_con() &= ~(1<<1); // End initialization stream r_stat() = 0xffffffff; // Set clock to ~400 kHz changeClock(240); cmd_reset(); r_ise() = (1<<28) | (1<<22) | (1<<21) // | (1<<20) |(1<<19) | (1<<18) | (1<<17) | (1<<16) // | (1<<5) | (1<<1) | (1<<0); printf("Probing SD/MMC card...\n"); try { // Send CMD0 command(0, 0); // Here we could test for SDIO (send CMD5), but we don't support SDIO (yet) bool sdv2 = false; uint32_t resp; try { resp = command_r1(8, 0x000001a5); if((resp&0xfff) != 0x1a5) { printf("CMD8 unexpected response: %.8lx\n", resp); throw ex::error{EIO}; } else sdv2 = true; } catch(commanderror &ex) { printf("CMD8 failed: %.8lx\n", ex.stat); } catch(ex::timeout &ex) { printf("CMD8 timed out\n"); cmd_reset(); } bool sd = false; uint32_t startTicks = global::ticktimer->getTicks(), endTicks; uint32_t arg = (sdv2?0x50000000:0); while((endTicks = global::ticktimer->getTicks()) < (startTicks+1000/TICKTIMER_MS)) { // Try for 1s try { resp = command_r1(ACMD(41), arg); arg |= (0x1ff<<15); // Set OCR bits after first ACMD41 } catch (ex::timeout &ex) { break; // On command timeout we don't have to continue trying } if(resp & (1<<31)) { printf("ACMD41 resp: %.8lx after %lu ms\n", resp, (endTicks-startTicks)*TICKTIMER_MS); sd = true; break; } } if(!sd) { // Try MMC init assert(!sdv2); arg = 0; r_con() |= (1<<0); // Open drain startTicks = global::ticktimer->getTicks(); while((endTicks = global::ticktimer->getTicks()) < startTicks+1000/TICKTIMER_MS) { try { resp = command_r1(1, 0); arg |= (0x1ff<<15); // Set OCR bits after first CMD1 } catch (ex::timeout &ex) { // On command timeout we don't have to continue trying // Unknown/Unsupported card type throw ex::error{EIO}; } if(resp & (1<<31)) { printf("CMD1 resp: %.8lx after %lu ms\n", resp, (endTicks-startTicks)*TICKTIMER_MS); break; } } } std::array resp4 = command_r2(2, 0); CID_reg cid; for(int i = 0;i <4;++i) cid.data[i] = resp4[i]; printf(" %c%c %c%c%c%c%c Rev %hhu Serial# %.8x Date %u/%u\n", cid.bits.oid[1], cid.bits.oid[0], cid.bits.pnm[4], cid.bits.pnm[3], cid.bits.pnm[2], cid.bits.pnm[1], cid.bits.pnm[0], cid.bits.prv, cid.bits.psn, cid.bits.mdt_m, 2000+cid.bits.mdt_y); resp = command_r1(3, 0); resp &= 0xffff0000; rca_ = resp>>16; r_con() &= ~(1<<0); // No more open drain resp4 = command_r2(9, rca_<<16); printf("CSD: %.8lx %.8lx %.8lx %.8lx\n", resp4[3], resp4[2], resp4[1], resp4[0]); CSD_reg csd; for(int i = 0;i <4;++i) csd.data[i] = resp4[i]; if((csd.v1.csd_structure == 1) && sdv2) { blockSize_ = 512; sectorSize_ = 64*1024; cardBlocks_ = (csd.v2.c_size+1)*1024; } else { assert(csd.v1.csd_structure == 0); blockSize_ = _pow2(csd.v1.read_bl_len); sectorSize_ = blockSize_*(csd.v1.sector_size+1); cardBlocks_ = (csd.v1.c_size+1)*_pow2(csd.v1.c_size_mult+2); } if(!sd) { changeClock(5); // 19.2 MHz writeTimeout_ = 11; // ~500 ms readTimeout_ = 8; // ~100 ms } else { // TODO: Check card for High-Speed (50 MHz) support changeClock(4); // 24 MHz writeTimeout_ = 11; // ~ 500 ms readTimeout_ = 9; // ~ 100 ms } if(sd) { if(sdv2) { if(csd.v1.csd_structure == 1) if(cardBlocks_ >= 67108864u) // 32GiB cardType_ = CardType::sdxc; else cardType_ = CardType::sdhc; else cardType_ = CardType::sdv2; } else cardType_ = CardType::sdv1; } else cardType_ = CardType::mmc; printf("Detected %s card, %u KiB (%u byte Blocks, %u byte Sectors)\n", cardTypeToString(), cardBlocks_*blockSize_/1024, blockSize_, sectorSize_); resp = command_r1b(7, rca_<<16); printf("%.8lx\n", resp); if(cardType_ != CardType::mmc) { resp = command_r1(ACMD(6), 0x2); // Set 4 byte bus on card r_hctl() |= (1<<1); // Set 4 byte bus on controller } resp = command_r1(16, 512); printf("%.8lx\n", resp); try { test_data(0); } catch(dataerror &ex) { printf("Data error: %.8lx\n", ex.stat); } } catch(commanderror &ex) { printf("Unexpected command error: %.8lx\n", ex.stat); throw; } } OMAP35x_SDMMC::~OMAP35x_SDMMC() { global::intc->disable_int(irq_); } unsigned OMAP35x_SDMMC::getBlockSize() const { return blockSize_; } void OMAP35x_SDMMC::write(unsigned pos, char const* data, unsigned count) { throw ex::error{ENOTSUP}; } void OMAP35x_SDMMC::read(unsigned pos, char *data, unsigned count) { throw ex::error{ENOTSUP}; } void OMAP35x_SDMMC::command(unsigned cmd, uint32_t arg) { assert(intSem_.getCount() == 0); if(cmd&0x100) { acmd(); cmd &= 0xff; } assert(cmd <= 63); while(r_pstate() & (1<<0)); // Wait for command line free r_con() &= ~(1<<6) & ~(1<<3); r_csre() = 0x0; r_blk() &= 0x0000f800; // No data transfer r_stat() = 0xffffffff; r_arg() = arg; uint32_t creg = (cmd<<24); if(cmd == 12) creg |= (0x3<<22); creg |= // (1<<19) | (0<<16); r_cmd() = creg; intSem_.acquire(); if(intStat_ & (1<<16)) { cmd_reset(); throw ex::timeout{}; } else if(intStat_ & (1<<15)) { throw commanderror{intStat_}; } assert(intStat_ & (1<<0)); } uint32_t OMAP35x_SDMMC::command_r1(unsigned cmd, uint32_t arg) { assert(intSem_.getCount() == 0); if(cmd&0x100) { acmd(); cmd &= 0xff; } assert(cmd <= 63); while(r_pstate() & (1<<0)); // Wait for command line free r_con() &= ~(1<<6) & ~(1<<3); r_csre() = 0x0; r_blk() &= 0x0000f800; // No data transfer r_stat() = 0xffffffff; r_arg() = arg; uint32_t creg = (cmd<<24); if(cmd == 12) creg |= (0x3<<22); if(!(cmd == 41)) // These commands do not respond with CRC, otherwise creg |= (1<<19); // CRC check enable if(!(cmd == 41)) // These commands do not respond with command index, otherwise creg |= (1<<20); // Index check enable creg |= (2<<16); r_cmd() = creg; intSem_.acquire(); if(intStat_ & (1<<16)) { cmd_reset(); throw ex::timeout{}; } else if(intStat_ & (1<<15)) { throw commanderror{intStat_}; } assert(intStat_ & (1<<0)); return r_rsp10(); } uint32_t OMAP35x_SDMMC::command_r1b(unsigned cmd, uint32_t arg) { assert(intSem_.getCount() == 0); if(cmd&0x100) { acmd(); cmd &= 0xff; } assert(cmd <= 63); while(r_pstate() & (1<<0)); // Wait for command line free r_sysctl() = (r_sysctl() & ~(0xf<<16)) | (readTimeout_<<16); // Use read timeout r_con() &= ~(1<<6) & ~(1<<3); r_csre() = 0x0; r_blk() &= 0x0000f800; // No data transfer r_stat() = 0xffffffff; r_arg() = arg; uint32_t creg = (cmd<<24); if(cmd == 12) creg |= (0x3<<22); creg |= (1<<19) | (1<<20); // CRC check, index check enable creg |= // (1<<19) | (3<<16); r_cmd() = creg; intSem_.acquire(); if(intStat_ & (1<<16)) { cmd_reset(); throw ex::timeout{}; } else if(intStat_ & (1<<15)) { throw commanderror{intStat_}; } assert(intStat_ & (1<<0)); return r_rsp10(); } std::array OMAP35x_SDMMC::command_r2(unsigned cmd, uint32_t arg) { assert(intSem_.getCount() == 0); if(cmd&0x100) { acmd(); cmd &= 0xff; } assert(cmd <= 63); while(r_pstate() & (1<<0)); // Wait for command line free r_con() &= ~(1<<6) & ~(1<<3); r_csre() = 0x0; r_blk() &= 0x0000f800; // No data transfer r_stat() = 0xffffffff; r_arg() = arg; uint32_t creg = (cmd<<24); if(cmd == 12) creg |= (0x3<<22); creg |= (1<<19); // CRC check enable creg |= (1<<16); r_cmd() = creg; intSem_.acquire(); if(intStat_ & (1<<16)) { cmd_reset(); throw ex::timeout{}; } else if(intStat_ & (1<<15)) { throw commanderror{intStat_}; } assert(intStat_ & (1<<0)); return std::array{r_rsp10(), r_rsp32(), r_rsp54(), r_rsp76()}; } void OMAP35x_SDMMC::int_handler() { intStat_ = r_stat(); r_stat() = intStat_; // Read and clear status bits intSem_.release(); global::prcm->clear_wake_core(24); } void OMAP35x_SDMMC::acmd() { uint32_t resp = command_r1(55, rca_<<16); if(!(resp & (1<<5))) { // Not in ACMD mode // WTF? printf("Error: CMD55 response without APP_CMD set (%.8lx)\n", resp); throw ex::error{EIO}; } } void OMAP35x_SDMMC::cmd_reset() { r_sysctl() |= (1<<25); while(r_sysctl() & (1<<25)); } void OMAP35x_SDMMC::dat_reset() { r_sysctl() |= (1<<26); while(r_sysctl() & (1<<26)); } void OMAP35x_SDMMC::changeClock(unsigned cdiv) { assert(cdiv <= 0x3ff); r_sysctl() &= ~(1<<2) & ~(1<<0); // Clock disable r_sysctl() &= ~(0x3ff<<6); // clear clock divider r_sysctl() |= (cdiv<<6) | (1<<0); // Set new divider and start internal clock while(!(r_sysctl() & (1<<1))); // Wait until clock stable r_sysctl() |= (1<<2); // Clock reenable } char const* OMAP35x_SDMMC::cardTypeToString() const { switch(cardType_) { case CardType::sdxc: return "SDxc"; case CardType::sdhc: return "SDhc"; case CardType::sdv2: return "SDsc v2"; case CardType::sdv1: return "SDsc v1"; case CardType::mmc: return "MMC"; default: return "Unknown"; } } void OMAP35x_SDMMC::test_data(unsigned block) { assert(intSem_.getCount() == 0); std::vector buffer(128, 0); while(r_pstate() & (1<<0)); // Wait for command line free while(r_pstate() & (1<<1)); // Wait for data line free r_sysctl() = (r_sysctl() & ~(0xf<<16)) | (readTimeout_<<16); // Use read timeout r_con() &= ~(1<<6) & ~(1<<3); r_csre() = 0x0; r_blk() &= 0x0000f800; r_blk() |= 0x00010000 | blockSize_; r_stat() = 0xffffffff; if((cardType_ == CardType::sdxc) || (cardType_ == CardType::sdhc)) r_arg() = block; else r_arg() = block*blockSize_; uint32_t creg = (17<<24); // CMD17 read single block creg |= (1<<21) | (1<<20) | (1<<19) | (2<<16) | (1<<4); r_cmd() = creg; intSem_.acquire(); if(intStat_ & (1<<16)) { cmd_reset(); throw ex::timeout{}; } else if(intStat_ & (1<<15)) { throw commanderror{intStat_}; } assert(intStat_ & (1<<0)); // TODO: DMA for data transfers unsigned pos = 0; while(true) { uint32_t stat = r_stat() | intStat_; // Make sure no status bits get 'lost' intStat_ = 0; if(stat & (1<<15)) { dat_reset(); throw dataerror{stat}; } else if(stat & (1<<1)) { break; } else if(stat & (1<<5)) { assert(pos < 128); buffer[pos++] = r_data(); } } for(int i = 0;i < 32;++i) { for(int j = 0;j < 4;++j) { printf("%.2lx %.2lx %.2lx %.2lx ", buffer[i*4+j]&0xff, (buffer[i*4+j]>>8)&0xff, (buffer[i*4+j]>>16)&0xff, (buffer[i*4+j]>>24)&0xff); } printf("\n"); } }