/* * \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_ */