/* * \brief Secured Digital Host Controller * \author Martin Stein * \date 2015-02-05 */ /* * Copyright (C) 2015-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ /* local includes */ #include using namespace Sd_card; using namespace Genode; int Driver::_wait_for_card_ready_mbw() { /* * Poll card status * * The maximum number of attempts and the delay between two attempts are * freely chosen. */ unsigned attempts = 5; uint64_t constexpr attempts_delay_us = 100000; while (1) { if (!attempts) { error("Reading card status after multiblock write failed"); return -1; } /* assemble argument register value */ Send_status::Arg::access_t cmdarg = 0; Send_status::Arg::Rca::set(cmdarg, _card_info.rca()); /* assemble command register value */ Xfertyp::access_t xfertyp = 0; Xfertyp::Cmdinx::set(xfertyp, Send_status::INDEX); Xfertyp::Cicen::set(xfertyp, 1); Xfertyp::Cccen::set(xfertyp, 1); Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT); Xfertyp::Msbsel::set(xfertyp, 1); Xfertyp::Bcen::set(xfertyp, 1); Xfertyp::Dmaen::set(xfertyp, 1); /* send command as soon as the host allows it */ if (_wait_for_cmd_allowed()) { return -1; } Mmio::write(cmdarg); Mmio::write(xfertyp); /* wait for command completion */ if (_wait_for_cmd_complete()) { return -1; } /* check for errors */ R1_response_0::access_t const resp = Mmio::read(); if (R1_response_0::Error::get(resp)) { error("Reading card status after multiblock write failed"); return -1; } /* if card is in a ready state, return success, retry otherwise */ if (R1_response_0::card_ready(resp)) { break; } _delayer.usleep(attempts_delay_us); } return 0; } int Driver::_stop_transmission() { /* write argument register */ Mmio::write(0); /* write command register */ Xfertyp::access_t xfertyp = 0; Xfertyp::Cmdinx::set(xfertyp, Stop_transmission::INDEX); Xfertyp::Cmdtyp::set(xfertyp, Xfertyp::Cmdtyp::ABORT_CMD12); Xfertyp::Cccen::set(xfertyp, 1); Xfertyp::Cicen::set(xfertyp, 1); Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT_BUSY); _stop_transmission_finish_xfertyp(xfertyp); Mmio::write(xfertyp); /* wait for command completion */ if (_wait_for_cmd_complete()) { return -1; } return 0; } void Driver::_handle_irq() { _irq.ack_irq(); /* the handler is only for block transfers, on other commands we poll */ if (!_block_transfer.pending) { return; } /* * The host signals on multi-block transfers seem to be broken. * Synchronizing to "Transfer Complete" before returning from transfers * and to "Command Inhibit" before sending further commands - as it is * done with other controllers - isn't sufficient. Instead, both "Transfer * Complete" and "Command Complete" must be gathered. */ try { wait_for(Attempts(1000), Microseconds(1000), _delayer, Irqstat::Cc::Equal(1), Irqstat::Tc::Equal(1)); } catch (Polling_timeout) { error("Completion host signal timed out"); throw -1; } /* acknowledge completion signals */ Irqstat::access_t irqstat = 0; Irqstat::Cc::set(irqstat, 1); Irqstat::Tc::set(irqstat, 1); Mmio::write(irqstat); if (_wait_for_cmd_complete_mb_finish(_block_transfer.read)) { throw -1; } _block_transfer.pending = false; ack_packet(_block_transfer.packet, true); } int Driver::_wait_for_cmd_complete() { try { wait_for(Attempts(200), Microseconds(5000), _delayer, Irqstat::Cc::Equal(1)); } catch (Polling_timeout) { error("command timed out"); return -1; } Mmio::write(Irqstat::Cc::reg_mask()); return 0; } bool Driver::_issue_command(Command_base const & command) { /* get command characteristics */ bool const transfer = command.transfer != TRANSFER_NONE; bool const reading = command.transfer == TRANSFER_READ; bool const multiblock = command.index == Read_multiple_block::INDEX || command.index == Write_multiple_block::INDEX; /* set command index */ Xfertyp::access_t xfertyp = 0; Xfertyp::Cmdinx::set(xfertyp, command.index); /* select response type */ typedef Xfertyp::Rsptyp Rsptyp; Xfertyp::access_t rsptyp = 0; switch (command.rsp_type) { case RESPONSE_NONE: rsptyp = Rsptyp::_0BIT; break; case RESPONSE_136_BIT: rsptyp = Rsptyp::_136BIT; break; case RESPONSE_48_BIT: rsptyp = Rsptyp::_48BIT; break; case RESPONSE_48_BIT_WITH_BUSY: rsptyp = Rsptyp::_48BIT_BUSY; break; } Xfertyp::Rsptyp::set(xfertyp, rsptyp); /* generic transfer settings */ if (command.transfer != TRANSFER_NONE) { Xfertyp::Dpsel::set(xfertyp); if (multiblock) { Xfertyp::Cicen::set(xfertyp, 1); Xfertyp::Cccen::set(xfertyp, 1); } } /* version-dependent transfer settings and issue command */ _issue_cmd_finish_xfertyp(xfertyp, transfer, multiblock, reading); Mmio::write(command.arg); Mmio::write(xfertyp); /* for block transfers there's a signal handler, on other commands poll */ return transfer ? true : !_wait_for_cmd_complete(); } Cid Driver::_read_cid() { Cid cid; cid.raw_0 = Mmio::read(); cid.raw_1 = Mmio::read(); cid.raw_2 = Mmio::read(); cid.raw_3 = Mmio::read(); return cid; } Csd Driver::_read_csd() { Csd csd; csd.csd0 = Mmio::read(); csd.csd1 = Mmio::read(); csd.csd2 = Mmio::read(); csd.csd3 = Mmio::read(); return csd; } unsigned Driver::_read_rca() { Cmdrsp0::access_t const rsp0 = Mmio::read(); return Send_relative_addr::Response::Rca::get(rsp0); } void Driver::read_dma(Block::sector_t blk_nr, size_t blk_cnt, addr_t buf_phys, Block::Packet_descriptor &packet) { if (_prepare_dma_mb(packet, true, blk_cnt, buf_phys)) { throw Io_error(); } if (!issue_command(Read_multiple_block(blk_nr))) { throw Io_error(); } } void Driver::write_dma(Block::sector_t blk_nr, size_t blk_cnt, addr_t buf_phys, Block::Packet_descriptor &packet) { if (_prepare_dma_mb(packet, false, blk_cnt, buf_phys)) { throw Io_error(); } if (!issue_command(Write_multiple_block(blk_nr))) { throw Io_error(); } } int Driver::_prepare_dma_mb(Block::Packet_descriptor packet, bool reading, size_t blk_cnt, addr_t buf_phys) { if (_block_transfer.pending) { throw Request_congestion(); } /* write ADMA2 table to DMA */ size_t const req_size = blk_cnt * _block_size(); if (_adma2_table.setup_request(req_size, buf_phys)) { return -1; } /* configure DMA at host */ Mmio::write(_adma2_table.base_phys()); Mmio::write(_block_size()); Mmio::write(blk_cnt); _block_transfer.read = reading; _block_transfer.packet = packet; _block_transfer.pending = true; return 0; } int Driver::_wait_for_cmd_allowed() { /* * At least after multi-block writes on i.MX53 with the fix for the broken * "Auto Command 12", waiting only for "Command Inhibit" isn't sufficient * as "Data Line Active" and "Data Inhibit" may also be active. */ try { wait_for(_delayer, Prsstat::Dla::Equal(0), Prsstat::Sdstb::Equal(1), Prsstat::Cihb::Equal(0), Prsstat::Cdihb::Equal(0)); } catch (Polling_timeout) { error("wait till issuing a new command is allowed timed out"); return -1; } return 0; } Card_info Driver::_init() { /* install IRQ signal */ _irq.sigh(_irq_handler); _irq.ack_irq(); /* configure host for initialization stage */ if (_reset()) { _detect_err("Host reset failed"); } _disable_irqs(); if (!_supported_host_version(Mmio::read())) { error("host version not supported"); throw Detection_failed(); } /* * We should check host capabilities at this point if we want to * support other versions of the SDHC. For the already supported * versions we know that the capabilities fit our requirements. */ /* configure IRQs, bus width, and clock for initialization */ _enable_irqs(); _bus_width(BUS_WIDTH_1); _delayer.usleep(10000); _clock(CLOCK_INITIAL); /* * 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 (Mmio::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"); } _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 (Mmio::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(Mmio::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 * * 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_OPERATIONAL); /* * Configure card and host to use 4 data signals * * 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 given block size */ if (!issue_command(Set_blocklen(_block_size()))) { _detect_err("Set_blocklen command failed"); } /* configure host buffer */ Wml::access_t wml = Mmio::read(); _watermark_level(wml); Mmio::write(wml); /* configure ADMA */ Mmio::write(Proctl::Dmas::ADMA2); /* configure interrupts for operational mode */ _disable_irqs(); Mmio::write(~0); _enable_irqs(); return card_info; } void Driver::_detect_err(char const * const err) { error(err); throw Detection_failed(); } int Driver::_reset() { /* start reset */ Mmio::write(1); _reset_amendments(); /* wait for reset completion */ try { wait_for(_delayer, Sysctl::Rsta::Equal(0)); } catch (Polling_timeout) { error("Reset timed out"); return -1; } return 0; } void Driver::_disable_irqs() { Mmio::write(0); Mmio::write(0); } void Driver::_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); Mmio::write(irq); Mmio::write(irq); } void Driver::_bus_width(Bus_width bus_width) { switch (bus_width) { case BUS_WIDTH_1: Mmio::write(Proctl::Dtw::_1BIT); break; case BUS_WIDTH_4: Mmio::write(Proctl::Dtw::_4BIT); break; } } void Driver::_disable_clock() { _disable_clock_preparation(); Sysctl::access_t sysctl = Mmio::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); Mmio::write(sysctl); } void Driver::_enable_clock(Clock_divider divider) { Sysctl::access_t sysctl = Mmio::read(); Sysctl::Ipgen::set(sysctl, 1); Sysctl::Hcken::set(sysctl, 1); Sysctl::Peren::set(sysctl, 1); switch (divider) { case CLOCK_DIV_4: Sysctl::Dvs::set(sysctl, Sysctl::Dvs::DIV4); Sysctl::Sdclkfs::set(sysctl, Sysctl::Sdclkfs::DIV1); break; 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; } Mmio::write(sysctl); _enable_clock_finish(); _delayer.usleep(1000); } void Driver::_clock(Clock clock) { wait_for(_delayer, Prsstat::Sdstb::Equal(1)); _disable_clock(); _clock_finish(clock); }