From 98616a181201d86ac8888cdec7f3840c6541d757 Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Wed, 18 Mar 2015 17:56:50 +0100 Subject: [PATCH] i.MX53: SD-card driver and bench The driver for the Freescale eSDHCv2 doesn't support the highest available bus frequency by now and also the bus width may be set to a higher value but that needs further checks on the capabilities of the inserted card. The commits provide a benchmark as it exists for the OMAP4 SDHC driver. Fix #1458 --- .../imx53/drivers/board_base_support.h | 4 + .../src/drivers/sd_card/imx53/bench/main.cc | 166 ++++ .../src/drivers/sd_card/imx53/bench/target.mk | 5 + repos/os/src/drivers/sd_card/imx53/driver.h | 135 +++ repos/os/src/drivers/sd_card/imx53/esdhcv2.h | 830 ++++++++++++++++++ repos/os/src/drivers/sd_card/imx53/main.cc | 57 ++ repos/os/src/drivers/sd_card/imx53/target.mk | 5 + repos/os/src/drivers/sd_card/sd_card.h | 8 + 8 files changed, 1210 insertions(+) create mode 100644 repos/os/src/drivers/sd_card/imx53/bench/main.cc create mode 100644 repos/os/src/drivers/sd_card/imx53/bench/target.mk create mode 100644 repos/os/src/drivers/sd_card/imx53/driver.h create mode 100644 repos/os/src/drivers/sd_card/imx53/esdhcv2.h create mode 100644 repos/os/src/drivers/sd_card/imx53/main.cc create mode 100644 repos/os/src/drivers/sd_card/imx53/target.mk diff --git a/repos/base/include/platform/imx53/drivers/board_base_support.h b/repos/base/include/platform/imx53/drivers/board_base_support.h index 8289dee7c0..092ed8b948 100644 --- a/repos/base/include/platform/imx53/drivers/board_base_support.h +++ b/repos/base/include/platform/imx53/drivers/board_base_support.h @@ -26,6 +26,10 @@ struct Imx53::Board_base MMIO_BASE = 0x0, MMIO_SIZE = 0x70000000, + ESDHCV2_1_IRQ = 1, + ESDHCV2_1_MMIO_BASE = 0x50004000, + ESDHCV2_1_MMIO_SIZE = 0x00004000, + UART_1_IRQ = 31, UART_1_MMIO_BASE = 0x53fbc000, UART_1_MMIO_SIZE = 0x00004000, diff --git a/repos/os/src/drivers/sd_card/imx53/bench/main.cc b/repos/os/src/drivers/sd_card/imx53/bench/main.cc new file mode 100644 index 0000000000..43abb53536 --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/bench/main.cc @@ -0,0 +1,166 @@ +/* + * \brief SD-card benchmark Imx53 platform + * \author Martin Stein + * \date 2015-03-04 + */ + +/* + * Copyright (C) 2012-2013 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include + +/* local includes */ +#include + + +struct Operation +{ + virtual void operator () (Block::Driver &driver, + Genode::addr_t block_number, + Genode::size_t block_count, + Genode::addr_t buffer_phys, + char *buffer_virt) = 0; +}; + + +/* + * \param total_size total number of bytes to read + * \param request_size number of bytes per request + */ +static void run_benchmark(Block::Driver &driver, + Timer::Session &timer, + char *buffer_virt, + Genode::addr_t buffer_phys, + Genode::size_t buffer_size, + Genode::size_t request_size, + Operation &operation) +{ + using namespace Genode; + + PLOG("request_size=%zd bytes", request_size); + + size_t const time_before_ms = timer.elapsed_ms(); + + size_t num_requests = buffer_size / request_size; + + /* + * Trim number of requests if it would take to much time + */ + if (num_requests > 1280) { + buffer_size = 1280 * request_size; + num_requests = buffer_size / request_size; + } + + for (size_t i = 0; i < num_requests; i++) + { + size_t const block_count = request_size / driver.block_size(); + addr_t const block_number = i*block_count; + + operation(driver, block_number, block_count, + buffer_phys + i*request_size, + buffer_virt + i*request_size); + } + + size_t const time_after_ms = timer.elapsed_ms(); + size_t const duration_ms = time_after_ms - time_before_ms; + + /* + * Convert bytes per milliseconds to kilobytes per seconds + * + * (total_size / 1024) / (duration_ms / 1000) + */ + size_t const buffer_size_kb = buffer_size / 1024; + size_t const throughput_kb_per_sec = (1000*buffer_size_kb) / duration_ms; + + PLOG(" duration: %zd ms", duration_ms); + PLOG(" amount: %zd KiB", buffer_size_kb); + PLOG(" throughput: %zd KiB/sec", throughput_kb_per_sec); +} + + +int main(int argc, char **argv) +{ + using namespace Genode; + + printf("--- i.MX53 SD card benchmark ---\n"); + + bool const use_dma = true; + + static Block::Imx53_driver driver(use_dma); + + static Timer::Connection timer; + + long const request_sizes[] = { + 512, 1024, 2048, 4096, 8192, 16384, 32768, 64*1024, 128*1024, 0 }; + + /* total size of communication buffer */ + size_t const buffer_size = 10 * 1024 * 1024; + + /* allocate read/write buffer */ + static Attached_ram_dataspace buffer(env()->ram_session(), buffer_size, Genode::UNCACHED); + char * const buffer_virt = buffer.local_addr(); + addr_t const buffer_phys = Dataspace_client(buffer.cap()).phys_addr(); + + /* + * Benchmark reading from SD card + */ + + printf("\n-- reading from SD card --\n"); + + struct Read : Operation + { + void operator () (Block::Driver &driver, + addr_t number, size_t count, addr_t phys, char *virt) + { + Block::Packet_descriptor p; + if (driver.dma_enabled()) + driver.read_dma(number, count, phys, p); + else + driver.read(number, count, virt, p); + } + } read_operation; + + for (unsigned i = 0; request_sizes[i]; i++) { + run_benchmark(driver, timer, buffer_virt, buffer_phys, buffer_size, + request_sizes[i], read_operation); + } + + /* + * Benchmark writing to SD card + * + * We write back the content of the buffer, which we just filled during the + * read benchmark. If both read and write succeed, the SD card will retain + * its original content. + */ + + printf("\n-- writing to SD card --\n"); + + struct Write : Operation + { + void operator () (Block::Driver &driver, + addr_t number, size_t count, addr_t phys, char *virt) + { + Block::Packet_descriptor p; + if (driver.dma_enabled()) + driver.write_dma(number, count, phys, p); + else + driver.write(number, count, virt, p); + } + } write_operation; + + for (unsigned i = 0; request_sizes[i]; i++) + run_benchmark(driver, timer, buffer_virt, buffer_phys, buffer_size, + request_sizes[i], write_operation); + + printf("\n--- i.MX53 SD card benchmark finished ---\n"); + sleep_forever(); + return 0; +} diff --git a/repos/os/src/drivers/sd_card/imx53/bench/target.mk b/repos/os/src/drivers/sd_card/imx53/bench/target.mk new file mode 100644 index 0000000000..256953e6e7 --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/bench/target.mk @@ -0,0 +1,5 @@ +TARGET = sd_card_bench +REQUIRES = imx53 +SRC_CC = main.cc +LIBS = base +INC_DIR += $(PRG_DIR)/.. $(PRG_DIR)/../.. diff --git a/repos/os/src/drivers/sd_card/imx53/driver.h b/repos/os/src/drivers/sd_card/imx53/driver.h new file mode 100644 index 0000000000..edbdaf31ce --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/driver.h @@ -0,0 +1,135 @@ +/* + * \brief Imx53-specific implementation of the Block::Driver interface + * \author Martin Stein + * \date 2015-02-04 + */ + +/* + * Copyright (C) 2012-2013 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _DRIVER_H_ +#define _DRIVER_H_ + +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include + +namespace Block { + using namespace Genode; + class Imx53_driver; +} + + +class Block::Imx53_driver : public Block::Driver +{ + private: + + struct Timer_delayer : Timer::Connection, Mmio::Delayer + { + /** + * Implementation of 'Delayer' interface + */ + void usleep(unsigned us) { Timer::Connection::usleep(us); } + } _delayer; + + Attached_io_mem_dataspace _esdhcv2_1_mmio; + Esdhcv2_controller _controller; + + bool const _use_dma; + + public: + + Imx53_driver(bool use_dma) + : + _esdhcv2_1_mmio(Genode::Board_base::ESDHCV2_1_MMIO_BASE, + Genode::Board_base::ESDHCV2_1_MMIO_SIZE), + _controller((addr_t)_esdhcv2_1_mmio.local_addr(), + Genode::Board_base::ESDHCV2_1_IRQ, _delayer, use_dma), + _use_dma(use_dma) + { + Sd_card::Card_info const card_info = _controller.card_info(); + + PLOG("SD card detected"); + PLOG("capacity: %zd MiB", card_info.capacity_mb()); + } + + + /***************************** + ** Block::Driver interface ** + *****************************/ + + Genode::size_t block_size() { return 512; } + + virtual Block::sector_t block_count() + { + return _controller.card_info().capacity_mb() * 1024 * 2; + } + + Block::Session::Operations ops() + { + Block::Session::Operations o; + o.set_operation(Block::Packet_descriptor::READ); + o.set_operation(Block::Packet_descriptor::WRITE); + return o; + } + + void read(Block::sector_t block_number, + Genode::size_t block_count, + char *out_buffer, + Packet_descriptor &packet) + { + if (!_controller.read_blocks(block_number, block_count, out_buffer)) + throw Io_error(); + ack_packet(packet); + } + + void write(Block::sector_t block_number, + Genode::size_t block_count, + char const *buffer, + Packet_descriptor &packet) + { + if (!_controller.write_blocks(block_number, block_count, buffer)) + throw Io_error(); + ack_packet(packet); + } + + void read_dma(Block::sector_t block_number, + Genode::size_t block_count, + Genode::addr_t phys, + Packet_descriptor &packet) + { + if (!_controller.read_blocks_dma(block_number, block_count, phys)) + throw Io_error(); + ack_packet(packet); + } + + void write_dma(Block::sector_t block_number, + Genode::size_t block_count, + Genode::addr_t phys, + Packet_descriptor &packet) + { + if (!_controller.write_blocks_dma(block_number, block_count, phys)) + throw Io_error(); + ack_packet(packet); + } + + bool dma_enabled() { return _use_dma; } + + Genode::Ram_dataspace_capability alloc_dma_buffer(Genode::size_t size) { + return Genode::env()->ram_session()->alloc(size, UNCACHED); } + + void free_dma_buffer(Genode::Ram_dataspace_capability c) { + return Genode::env()->ram_session()->free(c); } +}; + +#endif /* _DRIVER_H_ */ diff --git a/repos/os/src/drivers/sd_card/imx53/esdhcv2.h b/repos/os/src/drivers/sd_card/imx53/esdhcv2.h new file mode 100644 index 0000000000..74229d1808 --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/esdhcv2.h @@ -0,0 +1,830 @@ +/* + * \brief ESDHCv2 host controller + * \author Martin Stein + * \date 2015-02-05 + */ + +/* + * Copyright (C) 2012-2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _ESDHCV2_H_ +#define _ESDHCV2_H_ + +/* Genode includes */ +#include +#include +#include +#include + +/* local includes */ +#include + +namespace Adma2 +{ + using namespace Genode; + + /** + * Descriptor layout + */ + struct Desc : Register<64> + { + static size_t constexpr max_size = 64 * 1024 - 4; + + struct Valid : Bitfield<0, 1> { }; + struct End : Bitfield<1, 1> { }; + struct Int : Bitfield<2, 1> { }; + struct Act1 : Bitfield<4, 1> { }; + struct Act2 : Bitfield<5, 1> { }; + struct Length : Bitfield<16, 16> { }; + struct Address : Bitfield<32, 32> { }; + }; + + /** + * Descriptor table + */ + class Table + { + static size_t constexpr _max_desc = 1024; + static size_t constexpr _ds_size = _max_desc * sizeof(Desc::access_t); + + Attached_ram_dataspace _ds; + Desc::access_t * const _base_virt; + addr_t const _base_phys; + + public: + + /** + * Constructor + */ + Table() : + _ds(env()->ram_session(), _ds_size, UNCACHED), + _base_virt(_ds.local_addr()), + _base_phys(Dataspace_client(_ds.cap()).phys_addr()) + { } + + /** + * Marshal descriptors according to block request + * + * \param size request size in bytes + * \param buffer_phys physical base of transfer buffer + * + * \return true on success + */ + bool setup_req(size_t const size, addr_t const buffer_phys) + { + /* + * Sanity check + * + * FIXME An alternative to this sanity check would be to + * expose the maximum transfer size to the driver and let + * the driver partition large requests into ones that are + * supported. + */ + static size_t constexpr max_size = _max_desc * Desc::max_size; + if (size > max_size) { + PERR("Block request too large"); + return false; + } + /* install new descriptors till they cover all requested bytes */ + addr_t consumed = 0; + for (int index = 0; consumed < size; index++) { + + /* clamp current request to maximum request size */ + size_t const remaining = size - consumed; + size_t const curr = min(Desc::max_size, remaining); + + /* assemble new descriptor */ + Desc::access_t desc = 0; + Desc::Address::set(desc, buffer_phys + consumed); + Desc::Length::set(desc, curr); + Desc::Act1::set(desc, 0); + Desc::Act2::set(desc, 1); + Desc::Valid::set(desc, 1); + + /* let last descriptor generate transfer-complete signal */ + if (consumed + curr == size) { Desc::End::set(desc, 1); } + + /* install and account descriptor */ + _base_virt[index] = desc; + consumed += curr; + } + return true; + } + + /* + * Accessors + */ + + addr_t base_phys() const { return _base_phys; } + }; + +} + +/** + * Freescale eSDHCv2 host MMIO + */ +struct Esdhcv2 : Genode::Mmio +{ + typedef Genode::size_t size_t; + typedef Genode::addr_t addr_t; + + /* + * MMIO structure + */ + + struct Blkattr : Register<0x4, 32> + { + struct Blksize : Bitfield<0, 13> { }; + struct Blkcnt : Bitfield<16, 16> { }; + }; + template + struct Cmdrsp_tpl : Register + { + struct Rsp136_8_24 : Register::template Bitfield<0, 24> { }; + struct Rsp136_0_8 : Register::template Bitfield<24, 8> { }; + }; + struct Cmdarg : Register<0x8, 32> { }; + struct Cmdrsp0 : Cmdrsp_tpl<0x10> { }; + struct Cmdrsp1 : Cmdrsp_tpl<0x14> { }; + struct Cmdrsp2 : Cmdrsp_tpl<0x18> { }; + struct Cmdrsp3 : Cmdrsp_tpl<0x1c> { }; + struct Rsp136_0 : Genode::Bitset_2 { }; + struct Rsp136_1 : Genode::Bitset_2 { }; + struct Rsp136_2 : Genode::Bitset_2 { }; + struct Rsp136_3 : Genode::Bitset_2 { }; + struct Xfertyp : Register<0xc, 32> + { + struct Dmaen : Bitfield<0, 1> { }; + struct Bcen : Bitfield<1, 1> { }; + struct Ac12en : Bitfield<2, 1> { }; + struct Dtdsel : Bitfield<4, 1> + { + enum { WRITE = 0, READ = 1, }; + }; + struct Msbsel : Bitfield<5, 1> { }; + struct Rsptyp : Bitfield<16, 2> + { + enum { + _0BIT = 0, + _136BIT = 1, + _48BIT = 2, + _48BIT_BUSY = 3, + }; + }; + struct Dpsel : Bitfield<21, 1> { }; + struct Cmdtyp : Bitfield<22, 2> + { + enum { ABORT_CMD12 = 3 }; + }; + struct Cmdinx : Bitfield<24, 6> { }; + }; + struct Datport : Register<0x20, 32> { }; + struct Prsstat_lhw : Register<0x24, 16> + { + struct Sdstb : Bitfield<3, 1> { }; + + static constexpr access_t cmd_allowed() { return Sdstb::reg_mask(); } + }; + struct Proctl : Register<0x28, 32> + { + struct Dtw : Bitfield<1, 2> + { + enum { _1BIT = 0, _4BIT = 1 }; + }; + struct Dmas : Bitfield<8, 2> { enum { ADMA2 = 2 }; }; + }; + struct Sysctl : Register<0x2c, 32> + { + struct Ipgen : Bitfield<0, 1> { }; + struct Hcken : Bitfield<1, 1> { }; + struct Peren : Bitfield<2, 1> { }; + struct Dvs : Bitfield<4, 4> + { + enum { DIV1 = 0x0, DIV4 = 0x3, DIV16 = 0xf, }; + }; + struct Sdclkfs : Bitfield<8, 8> + { + enum { DIV1 = 0x00, DIV2 = 0x01, DIV32 = 0x10, }; + }; + struct Dtocv : Bitfield<16, 4> + { + enum { SDCLK_TIMES_2_POW_27 = 0xe }; + }; + struct Rsta : Bitfield<24, 1> { }; + struct Rstc : Bitfield<25, 1> { }; + struct Rstd : Bitfield<26, 1> { }; + }; + + template + struct Irq_tpl : Register + { + struct Cc : Register::template Bitfield<0, 1> { }; + struct Tc : Register::template Bitfield<1, 1> { }; + struct Dint : Register::template Bitfield<03, 1> { }; + struct Ctoe : Register::template Bitfield<16, 1> { }; + struct Cce : Register::template Bitfield<17, 1> { }; + struct Cebe : Register::template Bitfield<18, 1> { }; + struct Cie : Register::template Bitfield<19, 1> { }; + struct Dtoe : Register::template Bitfield<20, 1> { }; + struct Dce : Register::template Bitfield<21, 1> { }; + struct Debe : Register::template Bitfield<22, 1> { }; + struct Ac12e : Register::template Bitfield<24, 1> { }; + struct Dmae : Register::template Bitfield<28, 1> { }; + }; + struct Irq : Irq_tpl<0> { }; + struct Irqstat : Irq_tpl<0x30> { }; + struct Irqstaten : Irq_tpl<0x34> { }; + struct Irqsigen : Irq_tpl<0x38> { }; + struct Maxcurrent : Register<0x48, 32> { }; + struct Adsaddr : Register<0x58, 32> { }; + struct Hostver : Register<0xfc, 32> + { + struct Svn : Bitfield<0, 8> { }; + struct Vvn : Bitfield<8, 8> { }; + }; + struct Wml : Register<0x44, 32> + { + struct Rd_wml : Bitfield<0, 8> { }; + struct Rd_brst_len : Bitfield<8, 5> { }; + struct Wr_wml : Bitfield<16, 8> { }; + struct Wr_brst_len : Bitfield<24, 5> { }; + }; + + /** + * Constructor + */ + Esdhcv2(addr_t const mmio_base) : Genode::Mmio(mmio_base) { } + + /** + * Reset command line circuit of host controller + */ + bool reset_command(Delayer & delayer) + { + write(1); + if (!wait_for(0, delayer)) { + PERR("Reset of command circuit failed"); + return false; + } + return true; + } + + /** + * Reset data circuit of host controller + */ + bool reset_data(Delayer & delayer) + { + write(1); + if (!wait_for(0, delayer)) { + PERR("Reset of data circuit failed"); + return false; + } + return true; + } + + /** + * Full host reset + */ + bool reset_all(Delayer & delayer) + { + /* start reset */ + write(1); + + /* + * The SDHC specification says that a software reset shouldn't + * have an effect on the the card detection circuit. The ESDHC + * clears Sysctl::Ipgen, Sysctl::Hcken, and Sysctl::Peren + * nonetheless which disables clocks that card detection relies + * on. + */ + Sysctl::access_t sysctl = read(); + Sysctl::Ipgen::set(sysctl, 1); + Sysctl::Hcken::set(sysctl, 1); + Sysctl::Peren::set(sysctl, 1); + write(sysctl); + + /* wait for reset completion */ + return wait_for(0, delayer); + } + + /** + * Disable all interrupts + */ + void disable_irqs() + { + write(0); + write(0); + } + + /** + * Enable desired interrupts + */ + void enable_irqs() + { + Irq::access_t irq = 0; + Irq::Cc::set(irq, 1); + Irq::Tc::set(irq, 1); + Irq::Dint::set(irq, 1); + Irq::Ctoe::set(irq, 1); + Irq::Cce::set(irq, 1); + Irq::Cebe::set(irq, 1); + Irq::Cie::set(irq, 1); + Irq::Dtoe::set(irq, 1); + Irq::Dce::set(irq, 1); + Irq::Debe::set(irq, 1); + Irq::Ac12e::set(irq, 1); + Irq::Dmae::set(irq, 1); + write(irq); + write(irq); + } + + enum Bus_width { BUS_WIDTH_1, BUS_WIDTH_4 }; + + /** + * Set bus width + */ + void bus_width(Bus_width bus_width) + { + switch (bus_width) { + case BUS_WIDTH_1: write(Proctl::Dtw::_1BIT); break; + case BUS_WIDTH_4: write(Proctl::Dtw::_4BIT); break; + } + } + + /** + * Disable host clock + */ + void disable_clock() + { + Sysctl::access_t sysctl = read(); + Sysctl::Ipgen::set(sysctl, 0); + Sysctl::Hcken::set(sysctl, 0); + Sysctl::Peren::set(sysctl, 0); + Sysctl::Dvs::set(sysctl, Sysctl::Dvs::DIV1); + Sysctl::Sdclkfs::set(sysctl, Sysctl::Sdclkfs::DIV1); + write(sysctl); + } + + enum Clock_divider { CLOCK_DIV_8, CLOCK_DIV_512 }; + + /** + * Enable host clock and configure it to fullfill a given divider + */ + void enable_clock(Clock_divider divider, Delayer &delayer) + { + Sysctl::access_t sysctl = read(); + Sysctl::Ipgen::set(sysctl, 1); + Sysctl::Hcken::set(sysctl, 1); + Sysctl::Peren::set(sysctl, 1); + switch (divider) { + case CLOCK_DIV_8: + Sysctl::Dvs::set(sysctl, Sysctl::Dvs::DIV4); + Sysctl::Sdclkfs::set(sysctl, Sysctl::Sdclkfs::DIV2); + break; + case CLOCK_DIV_512: + Sysctl::Dvs::set(sysctl, Sysctl::Dvs::DIV16); + Sysctl::Sdclkfs::set(sysctl, Sysctl::Sdclkfs::DIV32); + break; + } + write(sysctl); + delayer.usleep(1000); + } + + /** + * Set clock to fulfill a given divider + */ + void clock(enum Clock_divider divider, Delayer &delayer) + { + disable_clock(); + write(Sysctl::Dtocv::SDCLK_TIMES_2_POW_27); + enable_clock(divider, delayer); + } +}; + +struct Esdhcv2_controller : private Esdhcv2, public Sd_card::Host_controller +{ + private: + + enum { + BLOCK_SIZE = 512, + WATERMARK_WORDS = 16, + BURST_WORDS = 8, + }; + + Genode::Irq_connection _irq; + Delayer & _delayer; + Sd_card::Card_info _card_info; + bool const _use_dma; + Adma2::Table _adma2_table; + + /** + * Print 'err' and throw detection-error exception + */ + void _detect_err(char const * const err) + { + PERR("%s", err); + throw Detection_failed(); + } + + /** + * Initialize host and card and return basic card information + */ + Sd_card::Card_info _init() + { + /* configure host for initialization stage */ + using namespace Sd_card; + if (!reset_all(_delayer)) { _detect_err("Host reset failed"); } + disable_irqs(); + + /* check host version */ + Hostver::access_t const hostver = read(); + if (Hostver::Vvn::get(hostver) != 18) { + _detect_err("Unexpected Vendor Version Number"); } + if (Hostver::Svn::get(hostver) != 1) { + _detect_err("Unexpected Specification Version Number"); } + + /* + * We should check host capabilities at this point if we want to + * support other versions of the ESDHC. For the i.MX53 ESDHCv2 we + * know that the capabilities fit our requirements. + */ + + enable_irqs(); + bus_width(BUS_WIDTH_1); + _delayer.usleep(10000); + clock(CLOCK_DIV_512, _delayer); + + /* + * Initialize card + */ + + /* + * At this point we should do an SDIO card reset if we later want + * to detect the unwanted case of an SDIO card beeing inserted. + * The reset would be done via 2 differently configured + * Io_rw_direct commands. + */ + + _delayer.usleep(1000); + if (!issue_command(Go_idle_state())) { + _detect_err("Go_idle_state command failed"); } + + _delayer.usleep(2000); + if (!issue_command(Send_if_cond())) { + _detect_err("Send_if_cond command failed"); } + + if (read() != 0x1aa) { + _detect_err("Unexpected response of Send_if_cond command"); } + + /* + * At this point we could detect the unwanted case of an SDIO card + * beeing inserted by issuing 4 Io_send_op_cond commands at an + * interval of 10 ms (they should time out on SD). + */ + + if (!issue_command(Sd_send_op_cond(0, false))) { + _detect_err("Sd_send_op_cond command failed"); } + + if (read() != 0xff8000) { + _detect_err("Unexpected response of Sd_send_op_cond command"); } + + _delayer.usleep(1000); + if (!issue_command(Go_idle_state())) { + _detect_err("Go_idle_state command failed"); } + + _delayer.usleep(2000); + if (!issue_command(Send_if_cond())) { + _detect_err("Send_if_cond failed"); } + + if (read() != 0x1aa) { + _detect_err("Unexpected response of Send_if_cond command"); } + + /* + * Power on card + * + * We need to issue the same Sd_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. + */ + int i = 1000; + for (; i > 0; --i) { + if (!issue_command(Sd_send_op_cond(0x200000, true))) { + _detect_err("Sd_send_op_cond command failed"); } + + if (Ocr::Busy::get(read())) { break; } + _delayer.usleep(1000); + } + if (!i) { _detect_err("Could not power-on SD card"); } + + /* get basic information about the card */ + Card_info card_info = _detect(); + + /* + * Configure working clock of host + * + * FIXME Host and card may be driven with a higher clock rate but + * checks (maybe read SSR/SCR, read switch, try frequencies) are + * necessary for that. + */ + clock(CLOCK_DIV_8, _delayer); + + /* + * Configure card and host to use 4 data signals + * + * FIXME Host and card may be driven with a higher bus width but + * further checks (read SCR) are necessary for that. + */ + if (!issue_command( + Set_bus_width(Set_bus_width::Arg::Bus_width::FOUR_BITS), + card_info.rca())) + { _detect_err("Set_bus_width(FOUR_BITS) command failed"); } + + bus_width(BUS_WIDTH_4); + _delayer.usleep(10000); + + /* configure card to use our block size */ + if (!issue_command(Set_blocklen(BLOCK_SIZE))) { + _detect_err("Set_blocklen command failed"); } + + /* configure host buffer */ + Wml::access_t wml = read(); + Wml::Rd_wml::set(wml, WATERMARK_WORDS); + Wml::Rd_brst_len::set(wml, BURST_WORDS); + Wml::Wr_wml::set(wml, WATERMARK_WORDS); + Wml::Wr_brst_len::set(wml, BURST_WORDS); + write(wml); + + /* configure ADMA */ + write(Proctl::Dmas::ADMA2); + + /* configure interrupts for operational mode */ + disable_irqs(); + write(~0); + enable_irqs(); + return card_info; + } + + /* + * Wait till sending a new command is allowed + * + * \return true on success + */ + bool _wait_for_cmd_allowed() + { + /* + * At least after multi-block writes with our + * "Broken Auto Command 12" fix, waiting only for Prsstat::Cihb + * isn't sufficient as Prsstat::Dla and Prsstat::Cdihb may also + * be active. + */ + if (!wait_for(Prsstat_lhw::cmd_allowed(), _delayer)) { + PERR("wait till issuing a new command is allowed timed out"); + return false; + } + return true; + } + + /** + * Wait for the completion of a non-multiple-block command + * + * \return true on success + */ + bool _wait_for_cmd_complete() + { + _irq.wait_for_irq(); + if (read() != Irqstat::Cc::reg_mask()) { + PWRN("received unexpected host signal"); + reset_command(_delayer); + reset_data(_delayer); + enable_irqs(); + return false; + } + write(Irqstat::Cc::reg_mask()); + return true; + } + + /** + * Abort transmission by manually issuing stop command + * + * \return true on success + */ + bool _abort_transmission() + { + write(0); + Xfertyp::access_t xfertyp = 0; + Xfertyp::Cmdinx::set(xfertyp, Sd_card::Stop_transmission::INDEX); + Xfertyp::Cmdtyp::set(xfertyp, Xfertyp::Cmdtyp::ABORT_CMD12); + Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT); + write(xfertyp); + return _wait_for_cmd_complete(); + } + + /** + * Wait for the completion of a multiple-block command + * + * \param r wether the transfer reads from card to host + * + * \return true on success + */ + bool _wait_for_mbc_complete(bool const r) + { + /* wait for a signal */ + _irq.wait_for_irq(); + Irqstat::access_t const irq = read(); + + /* + * The ESDHC signals on multi-block transfers seem to be broken. + * Synchronizing to Irqstat::Tc before returning from transfer + * requests and to Prsstat::Cihb before sending the next command, + * as it is done with other SDHCs, isn't sufficient. Instead, both + * completion signals must be gathered while dealing with the fact + * that the interrupt triggers only on signal edges and we can + * therefore not wait for a second IRQ when we've received a + * single signal the first time. + */ + Irqstat::access_t constexpr irq_cc_tc = + Irq::Cc::reg_mask() | Irq::Tc::reg_mask(); + switch (irq) { + case Irq::Cc::reg_mask(): + case Irq::Tc::reg_mask(): + + /* poll for the missing signal */ + if (!wait_for(irq_cc_tc, _delayer)) { + PERR("completion host signal timed out"); + return false; + } + case irq_cc_tc: + + /* acknowledge both completion signals */ + write(irq_cc_tc); + + /* + * The "Auto Command 12" feature of the ESDHC seems to be + * broken for multi-block writes as it causes command- + * timeout errors sometimes. Thus, we end such transfers + * manually. + */ + return r ? true : _abort_transmission(); + default: + PERR("received unexpected host signal"); + return false; + } + } + + /** + * Prepare multi-block-transfer command with DMA + * + * \param blk_cnt number of transferred blocks block + * \param buf_phys physical base of transfer buffer + * + * \return true on success + */ + bool _prepare_dma_mbc(size_t blk_cnt, addr_t buf_phys) + { + size_t const req_size = blk_cnt * BLOCK_SIZE; + if (!_adma2_table.setup_req(req_size, buf_phys)) { + PERR("Setup ADMA2 table failed"); + return false; + } + write(_adma2_table.base_phys()); + write(BLOCK_SIZE); + write(blk_cnt); + return true; + } + + + /**************************************** + ** Sd_card::Host_controller interface ** + ****************************************/ + + 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() + { + Cmdrsp0::access_t const rsp0 = read(); + return Sd_card::Send_relative_addr::Response::Rca::get(rsp0); + } + + public: + + /** + * Constructor + * + * \param mmio_base local base address of MMIO registers + * \param irq host-interrupt ID + * \param delayer delayer timing of MMIO accesses + * \param use_dma wether to use DMA or direct IO for transfers + */ + Esdhcv2_controller(addr_t const mmio_base, unsigned const irq, + Delayer & delayer, bool const use_dma) + : + Esdhcv2(mmio_base), _irq(irq), _delayer(delayer), + _card_info(_init()), _use_dma(use_dma) + { } + + + /**************************************** + ** Sd_card::Host_controller interface ** + ****************************************/ + + bool _issue_command(Sd_card::Command_base const & command) + { + /* detect if command is a multi-block transfer and if it reads */ + using namespace Sd_card; + bool const r = command.transfer == Sd_card::TRANSFER_READ; + bool const mb = + command.index == Sd_card::Read_multiple_block::INDEX || + command.index == Sd_card::Write_multiple_block::INDEX; + + /* assemble comsendcode */ + Xfertyp::access_t cmd = 0; + Xfertyp::Cmdinx::set(cmd, command.index); + if (command.transfer != Sd_card::TRANSFER_NONE) { + Xfertyp::Dpsel::set(cmd); + Xfertyp::Bcen::set(cmd); + Xfertyp::Msbsel::set(cmd); + if (mb) { + /* + * The "Auto Command 12" feature of the ESDHC seems to be + * broken for multi-block writes as it causes command- + * timeout errors sometimes. Thus, we end such transfers + * manually. + */ + if (r) { Xfertyp::Ac12en::set(cmd); } + if (_use_dma) { Xfertyp::Dmaen::set(cmd); } + } + Xfertyp::Dtdsel::set(cmd, + r ? Xfertyp::Dtdsel::READ : Xfertyp::Dtdsel::WRITE); + } + typedef Xfertyp::Rsptyp Rsptyp; + Xfertyp::access_t rt = 0; + switch (command.rsp_type) { + case RESPONSE_NONE: rt = Rsptyp::_0BIT; break; + case RESPONSE_136_BIT: rt = Rsptyp::_136BIT; break; + case RESPONSE_48_BIT: rt = Rsptyp::_48BIT; break; + case RESPONSE_48_BIT_WITH_BUSY: rt = Rsptyp::_48BIT_BUSY; break; + } + Xfertyp::Rsptyp::set(cmd, rt); + + /* send command as soon as the host allows it */ + if (!_wait_for_cmd_allowed()) { return false; } + write(command.arg); + write(cmd); + + /* wait for completion */ + return mb ? _wait_for_mbc_complete(r) : _wait_for_cmd_complete(); + } + + Sd_card::Card_info card_info() const { return _card_info; } + + bool read_blocks(size_t const block_number, size_t const block_count, + char * out_buffer_phys) + { + PERR("block transfer without DMA not supported by now"); + return false; + } + + bool write_blocks(size_t block_number, size_t block_count, + char const *buffer_phys) + { + PERR("block transfer without DMA not supported by now"); + return false; + } + + bool read_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys) + { + if (!_prepare_dma_mbc(blk_cnt, buf_phys)) { return false; } + return issue_command(Sd_card::Read_multiple_block(blk_nr)); + } + + bool write_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys) + { + if (!_prepare_dma_mbc(blk_cnt, buf_phys)) { return false; } + return issue_command(Sd_card::Write_multiple_block(blk_nr)); + } +}; + +#endif /* _ESDHCV2_H_ */ diff --git a/repos/os/src/drivers/sd_card/imx53/main.cc b/repos/os/src/drivers/sd_card/imx53/main.cc new file mode 100644 index 0000000000..f9ed3cb501 --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/main.cc @@ -0,0 +1,57 @@ +/* + * \brief SD-card driver + * \author Martin Stein + * \date 2015-02-04 + */ + +/* + * Copyright (C) 2012-2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include + +/* local includes */ +#include + + +struct Main +{ + Server::Entrypoint &ep; + + struct Factory : Block::Driver_factory + { + Block::Driver *create() { + return new (Genode::env()->heap()) Block::Imx53_driver(true); } + + void destroy(Block::Driver *driver) { + Genode::destroy(Genode::env()->heap(), + static_cast(driver)); } + } factory; + + Block::Root root; + + Main(Server::Entrypoint &ep) + : ep(ep), root(ep, Genode::env()->heap(), factory) + { + Genode::printf("--- Imx53 SD card driver ---\n"); + + Genode::env()->parent()->announce(ep.manage(root)); + } +}; + + +/************ + ** Server ** + ************/ + +namespace Server { + char const *name() { return "sd_card_ep"; } + size_t stack_size() { return 2*1024*sizeof(long); } + void construct(Entrypoint &ep) { static Main server(ep); } +} + diff --git a/repos/os/src/drivers/sd_card/imx53/target.mk b/repos/os/src/drivers/sd_card/imx53/target.mk new file mode 100644 index 0000000000..dba3e89a4b --- /dev/null +++ b/repos/os/src/drivers/sd_card/imx53/target.mk @@ -0,0 +1,5 @@ +TARGET = sd_card_drv +REQUIRES = imx53 +SRC_CC = main.cc +LIBS = base server +INC_DIR += $(PRG_DIR) $(PRG_DIR)/.. diff --git a/repos/os/src/drivers/sd_card/sd_card.h b/repos/os/src/drivers/sd_card/sd_card.h index 92f926fc81..3ad6c5fe4a 100644 --- a/repos/os/src/drivers/sd_card/sd_card.h +++ b/repos/os/src/drivers/sd_card/sd_card.h @@ -195,6 +195,14 @@ namespace Sd_card { }; }; + struct Set_blocklen : Command<16, RESPONSE_48_BIT> + { + Set_blocklen(size_t blocklen) + { + arg = blocklen; + }; + }; + struct Read_multiple_block : Command<18, RESPONSE_48_BIT, TRANSFER_READ> { Read_multiple_block(unsigned long addr)