From a3afb3dae46480d55e78be888a0427c146e40ba8 Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Fri, 22 Mar 2013 15:19:09 +0100 Subject: [PATCH] sd_card: Exynos5/MMC support Added MMC specific parts to Sd-card implemntation. Read/Write takes advantage of DMA. Currently we leave card in one bit mode. --- base/mk/spec-platform_arndale.mk | 2 +- os/src/drivers/sd_card/exynos5/driver.h | 123 +++++ os/src/drivers/sd_card/exynos5/dwmmc.h | 660 +++++++++++++++++++++++ os/src/drivers/sd_card/exynos5/main.cc | 50 ++ os/src/drivers/sd_card/exynos5/target.mk | 5 + os/src/drivers/sd_card/sd_card.h | 93 +++- 6 files changed, 931 insertions(+), 2 deletions(-) create mode 100644 os/src/drivers/sd_card/exynos5/driver.h create mode 100644 os/src/drivers/sd_card/exynos5/dwmmc.h create mode 100644 os/src/drivers/sd_card/exynos5/main.cc create mode 100644 os/src/drivers/sd_card/exynos5/target.mk diff --git a/base/mk/spec-platform_arndale.mk b/base/mk/spec-platform_arndale.mk index d773556bd4..853c81314f 100644 --- a/base/mk/spec-platform_arndale.mk +++ b/base/mk/spec-platform_arndale.mk @@ -5,7 +5,7 @@ # # denote specs that are fullfilled by this spec -SPECS += cortex_a15 +SPECS += exynos5 cortex_a15 # add repository relative paths REP_INC_DIR += include/platform/arndale \ diff --git a/os/src/drivers/sd_card/exynos5/driver.h b/os/src/drivers/sd_card/exynos5/driver.h new file mode 100644 index 0000000000..6bb6ac3b60 --- /dev/null +++ b/os/src/drivers/sd_card/exynos5/driver.h @@ -0,0 +1,123 @@ +/* + * \brief Exynos5-specific implementation of the Block::Driver interface + * \author Sebastian Sumpf + * \date 2013-03-22 + */ + +#ifndef _DRIVER_H_ +#define _DRIVER_H_ + +#include +#include +#include +#include +#include + +/* local includes */ +#include + +namespace Block { + using namespace Genode; + class Exynos5_driver; +} + + +class Block::Exynos5_driver : public Block::Driver +{ + private: + + struct Timer_delayer : Timer::Connection, Mmio::Delayer + { + /** + * Implementation of 'Delayer' interface + */ + void usleep(unsigned us) + { + /* polling */ + if (us == 0) + return; + + Timer::Connection::usleep(us); + } + } _delayer; + + enum { + MSH_BASE = 0x12200000, /* host controller base for eMMC */ + MSH_SIZE = 0x10000, + }; + + + /* display sub system registers */ + Attached_io_mem_dataspace _mmio; + + /* mobile storage host controller instance */ + Exynos5_msh_controller _controller; + + bool const _use_dma; + + public: + + Exynos5_driver(bool use_dma) + : + _mmio(MSH_BASE, MSH_SIZE), + _controller((addr_t)_mmio.local_addr(), + _delayer, use_dma), + _use_dma(use_dma) + { + Sd_card::Card_info const card_info = _controller.card_info(); + + PLOG("SD/MMC card detected"); + PLOG("capacity: %zd MiB", card_info.capacity_mb()); + } + + + /***************************** + ** Block::Driver interface ** + *****************************/ + + Genode::size_t block_size() { return 512; } + + virtual Genode::size_t block_count() + { + return _controller.card_info().capacity_mb() * 1024 * 2; + } + + void read(Genode::size_t block_number, + Genode::size_t block_count, + char *out_buffer) + { + if (!_controller.read_blocks(block_number, block_count, out_buffer)) + throw Io_error(); + } + + void write(Genode::size_t block_number, + Genode::size_t block_count, + char const *buffer) + { + if (!_controller.write_blocks(block_number, block_count, buffer)) + throw Io_error(); + } + + void read_dma(Genode::size_t block_number, + Genode::size_t block_count, + Genode::addr_t phys) + { + if (!_controller.read_blocks_dma(block_number, block_count, phys)) + throw Io_error(); + } + + void write_dma(Genode::size_t block_number, + Genode::size_t block_count, + Genode::addr_t phys) + { + if (!_controller.write_blocks_dma(block_number, block_count, phys)) + throw Io_error(); + } + + bool dma_enabled() { return _use_dma; } + + Ram_dataspace_capability alloc_dma_buffer(size_t size) { + return Genode::env()->ram_session()->alloc(size, false); } +}; + +#endif /* _DRIVER_H_ */ diff --git a/os/src/drivers/sd_card/exynos5/dwmmc.h b/os/src/drivers/sd_card/exynos5/dwmmc.h new file mode 100644 index 0000000000..9756032d98 --- /dev/null +++ b/os/src/drivers/sd_card/exynos5/dwmmc.h @@ -0,0 +1,660 @@ +/* + * \brief DesignWare Multimedia Card interface + * \author Sebastian Sumpf + * \date 2013-03-06 + */ + +#ifndef _DWMMC_H_ +#define _DWMMC_H_ + +#include +#include +#include + +#include + +struct Dwmmc : Genode::Mmio +{ + enum { verbose = false }; + + /* + * These apply to card controller 0 and 1 only + */ + enum { + HOST_FREQ = 52000000, /* Hz */ + CLK_FREQ = 400000000, /* Hz */ + + /* CLK_FREQ / (2 * CLK_DIV <= HOST_FREQ) */ + CLK_DIV_52Mhz = 4, + CLK_DIV_400Khz = 0xff, + }; + + Dwmmc(Genode::addr_t base) : Genode::Mmio(base) { } + + template + struct Register : Genode::Mmio::Register { }; + + /** + * Control register + */ + struct Ctrl : Register<0x0> + { + /* Controller/FIFO/DMA reset */ + struct Reset : Bitfield<0, 3> { }; + struct Global_interrupt : Bitfield<4, 1> { }; + struct Dma_enable : Bitfield<5, 1> { }; + struct Use_internal_dmac : Bitfield<25, 1> { }; + }; + + /** + * Power-enable register + */ + struct Pwren : Register<0x4> { }; + + /** + * Clock-devider register + */ + struct Clkdiv : Register<0x8> { }; + + /** + * Clock-enable register + */ + + struct Clkena : Register<0x10> { }; + + /** + * Timeout register + */ + struct Tmout : Register<0x14> { }; + + /** + * Card-type register + */ + struct Ctype : Register<0x18, true> { }; + + /** + * Block-size register + */ + struct Blksize : Register<0x1c> { }; + + /** + * Byte-count register + */ + struct Bytcnt : Register<0x20> { }; + /** + * Interrupt-mask register + */ + struct Intmask : Register<0x24> { }; + + /** + * Command-argument register + */ + struct Cmdarg : Register<0x28> { }; + + /** + * Command register + */ + struct Cmd : Register<0x2c> + { + struct Index : Bitfield<0, 6> { }; + struct Rsp_type : Bitfield<6, 3> + { + enum Response { RESPONSE_NONE = 0, + RESPONSE_48_BIT = 1, + RESPONSE_48_BIT_WITH_BUSY = 5, + RESPONSE_136_BIT = 7, + }; + }; + struct Data_expected : Bitfield<9, 1> { }; + struct Write : Bitfield<10, 1> { }; + struct Wait_prvdata_complete : Bitfield<13, 1> { }; + struct Init_sequence : Bitfield<15, 1> { }; + struct Update_clock_registers_only : Bitfield<21, 1> { }; + struct Use_hold_reg : Bitfield<29, 1> { }; + struct Start_cmd : Bitfield<31, 1> { }; + }; + + /** + * Response bits 0..127 + */ + struct Rsp0 : Register<0x30> { }; + struct Rsp1 : Register<0x34> { }; + struct Rsp2 : Register<0x38> { }; + struct Rsp3 : Register<0x3c> { }; + + /** + * Interrupt-status register + */ + struct Mintsts : Register<0x40> { }; + struct Rintsts : Register<0x44, true> + { + struct Response_error : Bitfield<1, 1> { }; + struct Data_transfer_over : Bitfield<3, 1> { }; + struct Command_done : Bitfield<2, 1> { }; + struct Data_crc_error : Bitfield<7, 1> { }; + struct Response_timeout : Bitfield<8, 1> { }; + struct Data_read_timeout : Bitfield<9, 1> { }; + }; + + /** + * Status register + */ + struct Status : Register<0x48> + { + struct Data_busy : Bitfield<9, 1> { }; + }; + + /** + * Fifo-threshold register + */ + struct Fifoth : Register<0x4c> { }; + + /** + * Bus-mode register + */ + struct Bmod : Register<0x80, true> + { + struct Fixed_burst : Bitfield<1, 1> { }; + struct Idmac_enable : Bitfield<7, 1> { }; + }; + struct Idsts : Register<0x8c> { }; + struct Idinten : Register<0x90, true> { }; + + /** + * Descriptor list base-address register + */ + struct Dbaddr : Register<0x88> { }; + + /** + * Clock selector + */ + struct Clksel : Register<0x9c> { }; + + struct Emmc_ddr_req : Register<0x10c, true> + { + }; + typedef Genode::size_t size_t; + + void powerup() + { + write(1); + } + + bool reset(Delayer &delayer) + { + /* set all three bits */ + write(0x7); + + if (!wait_for(0, delayer, 100, 1000)) { + PERR("Could not reset host contoller"); + return false; + } + + return true; + } + + void reset_fifo(Delayer &delayer) + { + write(0x2); + + if (!wait_for(0, delayer, 100, 1000)) + PERR("Could not reset fifo"); + } + + void disable_irq() + { + write(~0U); + write(0); + } + + enum Bus_width { + BUS_WIDTH_1 = 0, + BUS_WIDTH_4 = 1, + BUS_WIDTH_8 = 1 << 16, + }; + + void bus_width(Bus_width bus_width) + { + write(bus_width); + } + + bool update_clock_registers(Delayer &delayer) + { + Cmd::access_t cmd = 0; + Cmd::Wait_prvdata_complete::set(cmd, 1); + Cmd::Update_clock_registers_only::set(cmd, 1); + Cmd::Start_cmd::set(cmd, 1); + + write(cmd); + + if (!wait_for(0, delayer)) { + PERR("Update clock registers failed"); + return false; + } + + return true; + } + + bool setup_bus(unsigned clock_div, Delayer &delayer) + { + /* set host clock divider */ + write(clock_div); + + if (!update_clock_registers(delayer)) + return false; + + /* enable clock for card 1 */ + write(0x1); + + if (!update_clock_registers(delayer)) + return false; + + delayer.usleep(10 * 1000); + return true; + } +}; + + +struct Exynos5_msh_controller : private Dwmmc, Sd_card::Host_controller +{ + private: + + enum { BLOCK_SIZE = 512 }; + + /** + * DMA descriptpor + */ + struct Idmac_desc + { + enum Flags { + NONE = 0, + LD = 1 << 2, + FS = 1 << 3, + CH = 1 << 4, + OWN = 1 << 31, + }; + + unsigned flags; + unsigned bytes; + unsigned addr; + unsigned next; + + size_t set(size_t block_count, Genode::addr_t phys_addr, Flags flag) + { + flags = OWN | CH | flag | (block_count <= 8 ? LD : 0); + bytes = ((block_count < 8) ? block_count : 8) * BLOCK_SIZE; + addr = phys_addr; + next = (unsigned)&next + sizeof(unsigned); + + return block_count < 8 ? 0 : block_count - 8; + } + }; + + /* + * DMA descriptors + */ + enum { IDMAC_DESC_MAX_ENTRIES = 1024 /* can be up to 65536 */}; + Genode::Attached_ram_dataspace _idmac_desc_ds; + Idmac_desc * const _idmac_desc; + Genode::addr_t const _idmac_desc_phys; + + Delayer &_delayer; + Sd_card::Card_info _card_info; + + Genode::Irq_connection _irq; + + Sd_card::Card_info _init() + { + using namespace Sd_card; + + powerup(); + + if (!reset(_delayer)) + throw Detection_failed(); + + write(0x1); + + disable_irq(); + + write(~0U); + write(0); + write(1); + write(0); + + write(0x203f0040); + + /* set to one bit transfer Bit */ + if (!setup_bus(CLK_DIV_400Khz, _delayer)) + throw Detection_failed(); + + bus_width(BUS_WIDTH_1); + + if (!issue_command(Go_idle_state())) { + PWRN("Go_idle_state command failed"); + throw Detection_failed(); + } + + _delayer.usleep(2000); + + if (!issue_command(Send_if_cond())) { + PWRN("Send_if_cond command failed"); + throw Detection_failed(); + } + + /* if this succeeds it is an SD card */ + if ((read() & 0xff) == 0xaa) + PINF("Found SD card"); + + /* + * We need to issue the same Mmc_send_op_cond command multiple + * times. The first time, we receive the status information. On + * subsequent attempts, the response tells us that the card is + * busy. Usually, the command is issued twice. We give up if the + * card is not reaching busy state after one second. + */ + unsigned i = 1000; + unsigned voltages = 0x300080; + unsigned arg = 0; + for (; i > 0; --i) { + if (!issue_command(Mmc_send_op_cond(arg, true))) { + PWRN("Sd_send_op_cond command failed"); + throw Detection_failed(); + } + + arg = read(); + arg = (voltages & (arg & 0x007FFF80)) | (arg & 0x60000000); + + _delayer.usleep(1000); + + if (Ocr::Busy::get(read())) + break; + } + + if (i == 0) { + PERR("Send_op_cond timed out, could no power-on SD/MMC card"); + throw Detection_failed(); + } + + Card_info card_info = _detect_mmc(); +#if 0 + /* set to eight bit transfer Bit */ + if (!setup_bus(CLK_DIV_52Mhz, _delayer)) + throw Detection_failed(); + + bus_width(BUS_WIDTH_8); + /* + * TODO SD card: set bus width (on card) - 4 bit + */ +#endif + + /* + * Enable Interrupts data read timeout | data transfer done | response + * error + */ + write(0x28a); + write(1); + return card_info; + } + + bool _setup_idmac_descriptor_table(size_t block_count, + Genode::addr_t phys_addr) + { + size_t const max_idmac_block_count = IDMAC_DESC_MAX_ENTRIES * 8; + + if (block_count > max_idmac_block_count) { + PERR("Block request too large"); + return false; + } + + reset_fifo(_delayer); + + Idmac_desc::Flags flags = Idmac_desc::FS; + size_t b = block_count; + for (int index = 0; b; + index++, phys_addr += 0x1000, flags = Idmac_desc::NONE) + b = _idmac_desc[index].set(b, phys_addr, flags); + + write(_idmac_desc_phys); + + write(1); + write(1); + + write(1); + write(1); + + write(BLOCK_SIZE); + write(BLOCK_SIZE * block_count); + + return true; + } + + bool _wait_for_transfer_complete() + { + while (1) { + _irq.wait_for_irq(); + + if (read()) { + write(~0U); + return true; + } + + if (read()) { + PERR("Response error"); + return false; + } + + if (read()) { + PERR("Data read timeout"); + return false; + } + + if (read()) { + PERR("CRC error"); + return false; + } + } + } + + public: + + enum { IRQ_NUMBER = 107 }; + + Exynos5_msh_controller(Genode::addr_t const mmio_base, Delayer &delayer, + bool use_dma) + : Dwmmc(mmio_base), + _idmac_desc_ds(Genode::env()->ram_session(), + IDMAC_DESC_MAX_ENTRIES*sizeof(Idmac_desc), + false), + _idmac_desc(_idmac_desc_ds.local_addr()), + _idmac_desc_phys(Genode::Dataspace_client(_idmac_desc_ds.cap()).phys_addr()), + _delayer(delayer), _card_info(_init()), _irq(IRQ_NUMBER) + { + } + + + bool _issue_command(Sd_card::Command_base const &command) + { + if (verbose) + PLOG("-> index=0x%08x, arg=0x%08x, rsp_type=%d", + command.index, command.arg, command.rsp_type); + + if (!wait_for(0, _delayer, 10000, 100)) { + PERR("wait for State::Data_busy timed out %x", read()); + return false; + } + + write(~0UL); + + /* write command argument */ + write(command.arg); + + Cmd::access_t cmd = 0; + Cmd::Index::set(cmd, command.index); + + if (command.transfer != Sd_card::TRANSFER_NONE) { + /* set data-direction bit depending on the command */ + bool const write = command.transfer == Sd_card::TRANSFER_WRITE; + Cmd::Data_expected::set(cmd, 1); + Cmd::Write::set(cmd, write ? 1 : 0); + } + + Cmd::access_t rsp_type = 0; + switch (command.rsp_type) { + case Sd_card::RESPONSE_NONE: rsp_type = Cmd::Rsp_type::RESPONSE_NONE; break; + case Sd_card::RESPONSE_136_BIT: rsp_type = Cmd::Rsp_type::RESPONSE_136_BIT; break; + case Sd_card::RESPONSE_48_BIT: rsp_type = Cmd::Rsp_type::RESPONSE_48_BIT; break; + case Sd_card::RESPONSE_48_BIT_WITH_BUSY: rsp_type = Cmd::Rsp_type::RESPONSE_48_BIT_WITH_BUSY; break; + } + + Cmd::Rsp_type::set(cmd, rsp_type); + Cmd::Start_cmd::set(cmd, 1); + Cmd::Use_hold_reg::set(cmd ,1); + Cmd::Wait_prvdata_complete::set(cmd, 1); + + if (command.index == 0) + Cmd::Init_sequence::set(cmd, 1); + + /* issue command */ + write(cmd); + + if (!wait_for(1, _delayer, 10000, 100)) { + PERR("Command failed Rintst: %x Mintst: %x Status: %x", read(), read(), read()); + + if (read()) + PWRN("timeout"); + + if (read()) + PWRN("Repsonse error"); + + return false; + } + + if (verbose) + PDBG("IRQ: Rintsts %x transfer %s", read(), + command.transfer == Sd_card::TRANSFER_NONE ? "none" : "data"); + + /* acknowledge interrupt */ + write(1); + + _delayer.usleep(100); + + return true; + } + + Sd_card::Cid _read_cid() + { + Sd_card::Cid cid; + cid.raw_0 = read(); + cid.raw_1 = read(); + cid.raw_2 = read(); + cid.raw_3 = read(); + return cid; + } + + Sd_card::Csd _read_csd() + { + Sd_card::Csd csd; + csd.csd0 = read(); + csd.csd1 = read(); + csd.csd2 = read(); + csd.csd3 = read(); + return csd; + } + + unsigned _read_rca() { return 0; } + + size_t _read_ext_csd() + { + using namespace Genode; + Attached_ram_dataspace ds(env()->ram_session(), 0x1000, false); + + addr_t phys = Genode::Dataspace_client(ds.cap()).phys_addr(); + _setup_idmac_descriptor_table(1, phys); + + if (!issue_command(Sd_card::Mmc_send_ext_csd())) + throw Detection_failed(); + + if (!wait_for(1, _delayer)) { + PERR("Error retrieving extented CSD"); + throw Detection_failed(); + } + /* clear IRQ */ + write(1); + + /* contruct extented CSD */ + Sd_card::Ext_csd csd((addr_t)ds.local_addr()); + + /* read revision */ + if (csd.read() < 2) { + PERR("Extented CSD revision is < 2"); + throw Detection_failed(); + } + + /* return sector count */ + uint64_t capacity = csd.read() * BLOCK_SIZE; + + /* to MB */ + return capacity / (1024 * 1024); + } + + Sd_card::Card_info card_info() const + { + return _card_info; + } + + bool read_blocks(size_t block_number, size_t block_count, char *out_buffer) + { + PWRN("'read_blocks' not implemented"); + return true; + } + + bool write_blocks(size_t block_number, size_t block_count, char const *buffer) + { + PWRN("'write_blocks' not implemented"); + return true; + } + + bool read_blocks_dma(size_t block_number, size_t block_count, + Genode::addr_t buffer_phys) + { + if (!_setup_idmac_descriptor_table(block_count, buffer_phys)) + return false; + + if (!_issue_command(Sd_card::Read_multiple_block(block_number))) { + PERR("Read_multiple_block failed, Status: 0x%08x", read()); + return false; + } + + bool complete = _wait_for_transfer_complete(); + + if (!_issue_command(Sd_card::Stop_transmission())) { + PERR("Unable to stop transmission"); + return false; + } + + return complete; + } + + bool write_blocks_dma(size_t block_number, size_t block_count, + Genode::addr_t buffer_phys) + { + if (!_setup_idmac_descriptor_table(block_count, buffer_phys)) + return false; + + if (!_issue_command(Sd_card::Write_multiple_block(block_number))) { + PERR("Read_multiple_block failed, Status: 0x%08x", read()); + return false; + } + + bool complete = _wait_for_transfer_complete(); + + if (!_issue_command(Sd_card::Stop_transmission())) { + PERR("Unable to stop transmission"); + return false; + } + + return complete; + } +}; + +#endif /* _DWMMC_H_ */ diff --git a/os/src/drivers/sd_card/exynos5/main.cc b/os/src/drivers/sd_card/exynos5/main.cc new file mode 100644 index 0000000000..f32d5af2b3 --- /dev/null +++ b/os/src/drivers/sd_card/exynos5/main.cc @@ -0,0 +1,50 @@ +/* + * \brief eMMC driver for Arndale/Exynos5 platform + * \author Sebastian Sumpf + * \date 2013-03-06 + */ + +/* Genode includes */ +#include +#include +#include + +/* local includes */ +#include + + +int main(int argc, char **argv) +{ + using namespace Genode; + + printf("--- Arndale eMMC card driver ---\n"); + + /** + * Factory used by 'Block::Root' at session creation/destruction time + */ + struct Driver_factory : Block::Driver_factory + { + Block::Driver *create() + { + bool use_dma = true; + return new (env()->heap()) Block::Exynos5_driver(use_dma); + } + + void destroy(Block::Driver *driver) + { + Genode::destroy(env()->heap(), + static_cast(driver)); + } + + } driver_factory; + + enum { STACK_SIZE = 8192 }; + static Cap_connection cap; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "block_ep"); + + static Block::Root block_root(&ep, env()->heap(), driver_factory); + env()->parent()->announce(ep.manage(&block_root)); + + sleep_forever(); + return 0; +} diff --git a/os/src/drivers/sd_card/exynos5/target.mk b/os/src/drivers/sd_card/exynos5/target.mk new file mode 100644 index 0000000000..a5de263b51 --- /dev/null +++ b/os/src/drivers/sd_card/exynos5/target.mk @@ -0,0 +1,5 @@ +TARGET = sd_card_drv +REQUIRES = exynos5 +SRC_CC = main.cc +LIBS = base +INC_DIR += $(PRG_DIR) $(PRG_DIR)/.. diff --git a/os/src/drivers/sd_card/sd_card.h b/os/src/drivers/sd_card/sd_card.h index 161e8cc511..ba81a3c3ee 100644 --- a/os/src/drivers/sd_card/sd_card.h +++ b/os/src/drivers/sd_card/sd_card.h @@ -1,6 +1,7 @@ /* * \brief SD card protocol definitions * \author Norman Feske + * \author Sebastian Sumpf * \date 2012-07-06 */ @@ -53,8 +54,10 @@ namespace Sd_card { struct Version : Bitfield<126 - BIT_BASE, 2> { - enum { HIGH_CAPACITY = 1 }; + enum { HIGH_CAPACITY = 1, EXT_CSD = 3 }; }; + + struct Mmc_spec_vers : Bitfield<122 - BIT_BASE, 4> { }; }; struct Csd @@ -65,6 +68,15 @@ namespace Sd_card { Csd3::access_t csd3; }; + + struct Ext_csd : Mmio + { + Ext_csd(addr_t base) : Mmio(base) { } + + struct Revision : Register<0xc0, 8> { }; + struct Sector_count : Register<0xd4, 32> { }; + }; + struct Arg : Register<32> { }; enum Response { RESPONSE_NONE, @@ -117,6 +129,11 @@ namespace Sd_card { { struct Rca : Bitfield<16, 16> { }; }; + + Send_relative_addr(unsigned rca = 0) + { + Response::Rca::set(arg, rca); + } }; struct Select_card : Command<7, RESPONSE_48_BIT> @@ -160,6 +177,9 @@ namespace Sd_card { } }; + struct Mmc_send_ext_csd : Command<8, RESPONSE_48_BIT_WITH_BUSY, TRANSFER_READ> + { }; + struct Set_block_count : Command<23, RESPONSE_48_BIT> { Set_block_count(size_t count) @@ -222,6 +242,30 @@ namespace Sd_card { } }; + struct Mmc_send_op_cond : Command<1, RESPONSE_48_BIT> + { + struct Arg : Sd_card::Arg + { + /** + * Operating condition register + */ + struct Ocr : Bitfield<0, 24> { }; + + /** + * Host capacity support + */ + struct Hcs : Bitfield<30, 1> { }; + }; + + Mmc_send_op_cond(unsigned ocr, bool hcs) + { + Arg::Ocr::set(arg, ocr); + Arg::Hcs::set(arg, hcs); + } + }; + + struct Stop_transmission : Command<12, RESPONSE_48_BIT> { }; + struct Acmd_prefix : Command<55, RESPONSE_48_BIT> { struct Arg : Sd_card::Arg @@ -282,6 +326,8 @@ namespace Sd_card { virtual unsigned _read_rca() = 0; + virtual size_t _read_ext_csd() { return 0; } + public: virtual Card_info card_info() const = 0; @@ -362,6 +408,51 @@ namespace Sd_card { return Card_info(rca, device_size / 2); } + + Card_info _detect_mmc() + { + if (!issue_command(All_send_cid())) { + PWRN("All_send_cid command failed"); + throw Detection_failed(); + } + + unsigned const rca = 1; + + if (!issue_command(Send_relative_addr(rca))) { + PERR("Send_relative_addr timed out"); + throw Detection_failed(); + } + + if (!issue_command(Send_csd(rca))) { + PERR("Send_csd failed"); + throw Detection_failed(); + } + + Csd const csd = _read_csd(); + + if (Csd3::Version::get(csd.csd3) != Csd3::Version::EXT_CSD) { + PERR("Csd version is not extented CSD"); + throw Detection_failed(); + } + + if (Csd3::Mmc_spec_vers::get(csd.csd3) < 4) { + PERR("Csd specific version is less than 4"); + throw Detection_failed(); + } + + if (!issue_command(Select_card(rca))) { + PERR("Select_card failed"); + throw Detection_failed(); + } + + size_t device_size; + if(!(device_size = _read_ext_csd())) { + PERR("Could not read extented CSD"); + throw Detection_failed(); + } + + return Card_info(rca, device_size); + } }; }