From 91e0a5d5dd92889cb6f53054f9c4861a498e8b23 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Sat, 4 Apr 2015 18:14:46 +0200 Subject: [PATCH] SD-card driver for the Raspberry Pi The driver operates in PIO mode only. Depending on the block size (512 bytes versus 128 KiB), it has a troughput of 2 MiB/sec - 10 MiB/sec for reading and 173 KiB/sec - 8 MiB/sec for writing. Fixes #1475 --- repos/os/src/drivers/sd_card/rpi/driver.h | 143 ++++++ repos/os/src/drivers/sd_card/rpi/main.cc | 63 +++ repos/os/src/drivers/sd_card/rpi/sdhci.h | 491 +++++++++++++++++++++ repos/os/src/drivers/sd_card/rpi/target.mk | 5 + 4 files changed, 702 insertions(+) create mode 100644 repos/os/src/drivers/sd_card/rpi/driver.h create mode 100644 repos/os/src/drivers/sd_card/rpi/main.cc create mode 100644 repos/os/src/drivers/sd_card/rpi/sdhci.h create mode 100644 repos/os/src/drivers/sd_card/rpi/target.mk diff --git a/repos/os/src/drivers/sd_card/rpi/driver.h b/repos/os/src/drivers/sd_card/rpi/driver.h new file mode 100644 index 000000000..d0b30bb4d --- /dev/null +++ b/repos/os/src/drivers/sd_card/rpi/driver.h @@ -0,0 +1,143 @@ +/* + * \brief Raspberry Pi implementation of the Block::Driver interface + * \author Norman Feske + * \date 2014-09-21 + */ + +/* + * Copyright (C) 2014 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 + +/* local includes */ +#include + +namespace Block { + using namespace Genode; + class Sdhci_driver; +} + + +class Block::Sdhci_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; + + /* memory map */ + enum { + SDHCI_BASE = 0x20300000, + SDHCI_SIZE = 0x100, + SDHCI_IRQ = 62, + }; + + /* display sub system registers */ + Attached_io_mem_dataspace _sdhci_mmio { SDHCI_BASE, SDHCI_SIZE }; + + /* host-controller instance */ + Sdhci_controller _controller; + + bool const _use_dma; + + public: + + Sdhci_driver(bool use_dma) + : + _controller((addr_t)_sdhci_mmio.local_addr(), + _delayer, SDHCI_IRQ, 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/rpi/main.cc b/repos/os/src/drivers/sd_card/rpi/main.cc new file mode 100644 index 000000000..e58dc866d --- /dev/null +++ b/repos/os/src/drivers/sd_card/rpi/main.cc @@ -0,0 +1,63 @@ +/* + * \brief SD-card driver for Raspberry Pi + * \author Norman Feske + * \date 2014-09-21 + */ + +/* + * Copyright (C) 2014 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 + +/* local includes */ +#include + + +struct Main +{ + Server::Entrypoint &ep; + + Platform::Connection platform; + + struct Factory : Block::Driver_factory + { + Block::Driver *create() { + return new (Genode::env()->heap()) Block::Sdhci_driver(false); } + + 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("--- SD card driver ---\n"); + + while (platform.power_state(Platform::Session::POWER_SDHCI) == 0) + platform.power_state(Platform::Session::POWER_SDHCI, true); + + 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/rpi/sdhci.h b/repos/os/src/drivers/sd_card/rpi/sdhci.h new file mode 100644 index 000000000..801327ede --- /dev/null +++ b/repos/os/src/drivers/sd_card/rpi/sdhci.h @@ -0,0 +1,491 @@ +/* + * \brief SDHCI controller driver + * \author Norman Feske + * \author Christian Helmuth + * \date 2014-09-21 + */ + +/* + * Copyright (C) 2014 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 _SDHCI_H_ +#define _SDHCI_H_ + +/* Genode includes */ +#include +#include +#include +#include + +/* local includes */ +#include + + +struct Sdhci : Genode::Mmio +{ + enum { verbose = false }; + + typedef Genode::size_t size_t; + + struct Blksizecnt : Register<0x4, 32> + { + struct Blkcnt : Bitfield<16, 16> { }; + struct Blksize : Bitfield<0, 10> { }; + }; + + struct Resp0 : Register<0x10, 32> { }; + struct Resp1 : Register<0x14, 32> { }; + struct Resp2 : Register<0x18, 32> { }; + struct Resp3 : Register<0x1c, 32> { }; + + struct Data : Register<0x20, 32> { }; + + struct Control0 : Register<0x28, 32> + { + struct Hctl_dwidth : Bitfield<1, 1> { }; + struct Hctl_hs_en : Bitfield<2, 1> { }; + }; + + struct Control1 : Register<0x2c, 32> + { + struct Clk_internal_en : Bitfield<0, 1> { }; + struct Clk_internal_stable : Bitfield<1, 1> { }; + struct Clk_en : Bitfield<2, 1> { }; + + struct Clk_freq8 : Bitfield<8, 8> { }; + struct Clk_freq_ms2 : Bitfield<6, 2> { }; + struct Data_tounit : Bitfield<16, 4> { }; + struct Srst_hc : Bitfield<24, 1> { }; + struct Srst_cmd : Bitfield<25, 1> { }; + struct Srst_data : Bitfield<26, 1> { }; + }; + + struct Status : Register<0x24, 32> + { + struct Inhibit : Bitfield<0, 2> { }; + struct Bwe : Bitfield<10, 1> { }; + struct Bre : Bitfield<11, 1> { }; + }; + + struct Arg1 : Register<0x8, 32> { }; + + struct Cmdtm : Register<0xc, 32> + { + struct Index : Bitfield<24, 6> { }; + struct Isdata : Bitfield<21, 1> { }; + struct Tm_blkcnt_en : Bitfield<1, 1> { }; + struct Tm_multi_block : Bitfield<5, 1> { }; + struct Tm_auto_cmd_en : Bitfield<2, 2> + { + enum { CMD12 = 1 }; + }; + struct Tm_dat_dir : Bitfield<4, 1> + { + enum { WRITE = 0, READ = 1 }; + }; + struct Rsp_type : Bitfield<16, 2> + { + enum Response { RESPONSE_NONE = 0, + RESPONSE_136_BIT = 1, + RESPONSE_48_BIT = 2, + RESPONSE_48_BIT_WITH_BUSY = 3 }; + }; + }; + + struct Interrupt : Register<0x30, 32> + { + struct Cmd_done : Bitfield<0, 1> { }; + struct Data_done : Bitfield<1, 1> { }; + }; + + struct Irpt_mask : Register<0x34, 32> { }; + struct Irpt_en : Register<0x38, 32> { }; + + Sdhci(Genode::addr_t const mmio_base) : Genode::Mmio(mmio_base) { } +}; + + +struct Sdhci_controller : private Sdhci, public Sd_card::Host_controller +{ + private: + + Delayer &_delayer; + Sd_card::Card_info _card_info; + + Genode::Irq_connection _irq; + + void _set_and_enable_clock(unsigned divider) + { + Control1::access_t v = read(); + Control1::Clk_freq8::set(v, divider); + Control1::Clk_freq_ms2::set(v, 0); + Control1::Clk_internal_en::set(v, 1); + + write(v); + + if (!wait_for(1, _delayer)) { + PERR("could not set internal clock"); + throw Detection_failed(); + } + + write(1); + + _delayer.usleep(10*1000); + + /* data timeout unit exponent */ + write(0xe); + } + + Sd_card::Card_info _init() + { + using namespace Sd_card; + + /* reset host controller */ + { + Control1::access_t v = read(); + Control1::Srst_hc::set(v); + Control1::Srst_data::set(v); + write(v); + } + + if (!wait_for(0, _delayer)) { + PERR("host-controller soft reset timed out"); + throw Detection_failed(); + } + + /* enable interrupt status reporting */ + write(~0UL); + write(~0UL); + + /* + * We don't read the capability register as the BCM2835 always + * returns all bits set to zero. + */ + + _set_and_enable_clock(240); + + 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 (read() != 0x1aa) { + PERR("unexpected response of Send_if_cond command"); + throw Detection_failed(); + } + + /* + * 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(0x18000, true))) { + PWRN("Sd_send_op_cond command failed"); + throw Detection_failed(); + } + + if (Sd_card::Ocr::Busy::get(read())) + break; + + _delayer.usleep(1000); + } + + if (i == 0) { + PERR("Sd_send_op_cond timed out, could no power-on SD card"); + throw Detection_failed(); + } + + Card_info card_info = _detect(); + + /* + * Switch card to use 4 data signals + */ + if (!issue_command(Set_bus_width(Set_bus_width::Arg::Bus_width::FOUR_BITS), + card_info.rca())) { + PWRN("Set_bus_width(FOUR_BITS) command failed"); + throw Detection_failed(); + } + + /* switch host controller to use 4 data signals */ + { + Control0::access_t v = read(); + Control0::Hctl_dwidth::set(v); + Control0::Hctl_hs_en::set(v); + write(v); + } + + _delayer.usleep(10*1000); + + /* + * Accelerate clock, the divider is hard-coded for now. + * + * The Raspberry Pi report as clock of 250 MHz. According to the + * SDHCI specification, it is possible to driver SD cards with + * 50 MHz in high-speed mode (Hctl_hs_en). + */ + _set_and_enable_clock(5); + + return card_info; + } + + /** + * Define the block count for the next data transfer + */ + void _set_block_count(size_t block_count) + { + /* + * The 'Blksizecnt' register must be written in one step. If we + * used subsequent writes for the 'Blkcnt' and 'Blksize' bitfields, + * the host controller of the BCM2835 would fail to recognize any + * but the first write operation. + */ + Blksizecnt::access_t v = read(); + Blksizecnt::Blkcnt::set(v, block_count); + Blksizecnt::Blksize::set(v, 0x200); + write(v); + } + + template + bool _poll_and_wait_for(unsigned value) + { + /* poll for a while */ + if (wait_for(value, _delayer, 5000, 0)) + return true; + + /* if the value were not reached while polling, start sleeping */ + return wait_for(value, _delayer); + } + + public: + + /** + * Constructor + * + * \param mmio_base local base address of MMIO registers + */ + Sdhci_controller(Genode::addr_t const mmio_base, Delayer &delayer, + unsigned irq, bool use_dma) + : + Sdhci(mmio_base), _delayer(delayer), _card_info(_init()), _irq(irq) + { } + + + /**************************************** + ** Sd_card::Host_controller interface ** + ****************************************/ + + 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 (!_poll_and_wait_for(0)) { + PERR("controller inhibits issueing commands"); + return false; + } + + /* write command argument */ + write(command.arg); + + /* assemble command register */ + Cmdtm::access_t cmd = 0; + Cmdtm::Index::set(cmd, command.index); + if (command.transfer != Sd_card::TRANSFER_NONE) { + + Cmdtm::Isdata::set(cmd); + Cmdtm::Tm_blkcnt_en::set(cmd); + Cmdtm::Tm_multi_block::set(cmd); + + if (command.index == Sd_card::Read_multiple_block::INDEX + || command.index == Sd_card::Write_multiple_block::INDEX) { + Cmdtm::Tm_auto_cmd_en::set(cmd, Cmdtm::Tm_auto_cmd_en::CMD12); + } + + /* set data-direction bit depending on the command */ + bool const read = command.transfer == Sd_card::TRANSFER_READ; + Cmdtm::Tm_dat_dir::set(cmd, read ? Cmdtm::Tm_dat_dir::READ + : Cmdtm::Tm_dat_dir::WRITE); + } + + Cmdtm::access_t rsp_type = 0; + switch (command.rsp_type) { + case Sd_card::RESPONSE_NONE: rsp_type = Cmdtm::Rsp_type::RESPONSE_NONE; break; + case Sd_card::RESPONSE_136_BIT: rsp_type = Cmdtm::Rsp_type::RESPONSE_136_BIT; break; + case Sd_card::RESPONSE_48_BIT: rsp_type = Cmdtm::Rsp_type::RESPONSE_48_BIT; break; + case Sd_card::RESPONSE_48_BIT_WITH_BUSY: rsp_type = Cmdtm::Rsp_type::RESPONSE_48_BIT_WITH_BUSY; break; + } + Cmdtm::Rsp_type::set(cmd, rsp_type); + + /* write command */ + write(cmd); + + if (!_poll_and_wait_for(1)) { + PERR("command timed out"); + return false; + } + + /* clear interrupt state */ + write(1); + + return true; + } + + Sd_card::Card_info card_info() const + { + return _card_info; + } + + 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 Sd_card::Send_relative_addr::Response::Rca::get(read()); + } + + /** + * Read data blocks from SD card + * + * \return true on success + */ + bool read_blocks(size_t block_number, size_t block_count, char *out_buffer) + { + using namespace Sd_card; + + _set_block_count(block_count); + + if (!issue_command(Read_multiple_block(block_number))) { + PERR("Read_multiple_block failed, Status: 0x%08x", read()); + return false; + } + + Data::access_t *dst = (Data::access_t *)(out_buffer); + for (size_t i = 0; i < block_count; i++) { + + /* + * Check for buffer-read enable bit for each block + * + * According to the BCM2835 documentation, this bit is + * reserved but it actually corresponds to the bre status + * bit as described in the SDHCI specification. + */ + if (!_poll_and_wait_for(1)) + return false; + + /* read data from sdhci buffer */ + for (size_t j = 0; j < 512/sizeof(Data::access_t); j++) + *dst++ = read(); + } + + if (!_poll_and_wait_for(1)) { + PERR("completion of read request failed (interrupt status %08x)", + read()); + return false; + } + + /* clear interrupt state */ + write(1); + + return true; + } + + /** + * Write data blocks to SD card + * + * \return true on success + */ + bool write_blocks(size_t block_number, size_t block_count, char const *buffer) + { + using namespace Sd_card; + + _set_block_count(block_count); + + if (!issue_command(Write_multiple_block(block_number))) { + PERR("Write_multiple_block failed, Status: 0x%08x", read()); + return false; + } + + Data::access_t const *src = (Data::access_t const *)(buffer); + for (size_t i = 0; i < block_count; i++) { + + /* check for buffer-write enable bit for each block */ + if (!_poll_and_wait_for(1)) + return false; + + /* write data into sdhci buffer */ + for (size_t j = 0; j < 512/sizeof(Data::access_t); j++) + write(*src++); + } + + if (!_poll_and_wait_for(1)) { + PERR("completion of write request failed (interrupt status %08x)", + read()); + return false; + } + + /* clear interrupt state */ + write(1); + + return true; + } + + /** + * Read data blocks from SD card via master DMA + * + * \return true on success + */ + bool read_blocks_dma(size_t block_number, size_t block_count, + Genode::addr_t out_buffer_phys) + { + return false; + } + + /** + * Write data blocks to SD card via master DMA + * + * \return true on success + */ + bool write_blocks_dma(size_t block_number, size_t block_count, + Genode::addr_t buffer_phys) + { + using namespace Sd_card; + + return false; + } +}; + +#endif /* _SDHCI_H_ */ diff --git a/repos/os/src/drivers/sd_card/rpi/target.mk b/repos/os/src/drivers/sd_card/rpi/target.mk new file mode 100644 index 000000000..985cfdc2a --- /dev/null +++ b/repos/os/src/drivers/sd_card/rpi/target.mk @@ -0,0 +1,5 @@ +TARGET = sd_card_drv +REQUIRES = platform_rpi +SRC_CC = main.cc +LIBS = base server +INC_DIR += $(PRG_DIR) $(PRG_DIR)/..