/* * \brief Exynos5-specific implementation of the Block::Driver interface * \author Sebastian Sumpf * \author Martin Stein * \date 2013-03-22 */ /* * 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 Genode; using namespace Sd_card; Driver::Driver(Env &env) : Driver_base(env.ram()), Attached_mmio(env, MSH_BASE, MSH_SIZE), _env(env) { _irq.sigh(_irq_handler); _irq.ack_irq(); log("SD/MMC card detected"); log("capacity: ", _card_info.capacity_mb(), " MiB"); } void Driver::read_dma(Block::sector_t block_number, size_t block_count, addr_t buf_phys, Block::Packet_descriptor &pkt) { if (_block_transfer.pending) { throw Request_congestion(); } if (!_setup_idmac_descriptor_table(block_count, buf_phys)) throw Io_error(); _block_transfer.packet = pkt; _block_transfer.pending = true; if (!_issue_command(Read_multiple_block(block_number))) { error("Read_multiple_block failed"); throw Io_error(); } } void Driver::write_dma(Block::sector_t block_number, size_t block_count, addr_t buf_phys, Block::Packet_descriptor &pkt) { if (_block_transfer.pending) { throw Request_congestion(); } if (!_setup_idmac_descriptor_table(block_count, buf_phys)) throw Io_error(); _block_transfer.packet = pkt; _block_transfer.pending = true; if (!_issue_command(Write_multiple_block(block_number))) { error("Read_multiple_block failed"); throw Io_error(); } } bool Driver::_reset() { Mmio::write(0x7); try { wait_for(Attempts(100), Microseconds(1000), _delayer, Ctrl::Reset::Equal(0)); } catch (Polling_timeout) { error("Could not reset host contoller"); return false; } return true; } void Driver::_reset_fifo() { Mmio::write(0x2); try { wait_for(Attempts(100), Microseconds(1000), _delayer, Ctrl::Reset::Equal(0)); } catch (Polling_timeout) { error("Could not reset fifo"); } } void Driver::_disable_irq() { Mmio::write(~0U); Mmio::write(0); } bool Driver::_update_clock_registers() { 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); Mmio::write(cmd); try { wait_for(_delayer, Cmd::Start_cmd::Equal(0)); } catch (Polling_timeout) { error("Update clock registers failed"); return false; } return true; } bool Driver::_setup_bus(unsigned clock_div) { /* set host clock divider */ Mmio::write(clock_div); if (!_update_clock_registers()) return false; /* enable clock for card 1 */ Mmio::write(0x1); if (!_update_clock_registers()) return false; _delayer.usleep(10 * 1000); return true; } Card_info Driver::_init() { Mmio::write(1); if (!_reset()) throw Detection_failed(); Mmio::write(0x1); _disable_irq(); Mmio::write(~0U); Mmio::write(0); Mmio::write(1); Mmio::write(0); Mmio::write(0x203f0040); /* set to one bit transfer Bit */ if (!_setup_bus(CLK_DIV_400Khz)) throw Detection_failed(); Mmio::write(BUS_WIDTH_1); if (!issue_command(Go_idle_state())) { warning("Go_idle_state command failed"); throw Detection_failed(); } _delayer.usleep(2000); if (!issue_command(Send_if_cond())) { warning("Send_if_cond command failed"); throw Detection_failed(); } /* if this succeeds it is an SD card */ if ((Mmio::read() & 0xff) == 0xaa) log("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))) { warning("Sd_send_op_cond command failed"); throw Detection_failed(); } arg = Mmio::read(); arg = (voltages & (arg & 0x007FFF80)) | (arg & 0x60000000); _delayer.usleep(1000); if (Ocr::Busy::get(Mmio::read())) break; } if (i == 0) { error("Send_op_cond timed out, could no power-on SD/MMC card"); throw Detection_failed(); } Card_info card_info = _detect_mmc(); /* switch frequency to high speed */ enum { EXT_CSD_HS_TIMING = 185 }; if (!issue_command(Mmc_switch(EXT_CSD_HS_TIMING, 1))) { error("Error setting high speed frequency"); throw Detection_failed(); } enum { EXT_CSD_BUS_WIDTH = 183 }; /* set card to 8 bit */ if (!issue_command(Mmc_switch(EXT_CSD_BUS_WIDTH, 2))) { error("Error setting card bus width"); throw Detection_failed(); } Mmio::write(BUS_WIDTH_8); /* set to eight bit transfer Bit */ if (!_setup_bus(CLK_DIV_52Mhz)) { error("Error setting bus to high speed"); throw Detection_failed(); } /* Enable IRQs data read timeout, data transfer done, resp error */ Mmio::write(0x28a); Mmio::write(1); return card_info; } bool Driver::_setup_idmac_descriptor_table(size_t block_count, addr_t phys_addr) { size_t const max_idmac_block_count = IDMAC_DESC_MAX_ENTRIES * 8; if (block_count > max_idmac_block_count) { error("Block request too large"); return false; } _reset_fifo(); Idmac_desc::Flags flags = Idmac_desc::FS; size_t b = block_count; int index = 0; for (index = 0; b; index++, phys_addr += 0x1000, flags = Idmac_desc::NONE) { b = _idmac_desc[index].set(b, _block_size(), phys_addr, flags); _idmac_desc[index].next = _idmac_desc_phys + ((index + 1) * sizeof(Idmac_desc)); } _idmac_desc[index].next = (unsigned)_idmac_desc; _idmac_desc[index].flags |= Idmac_desc::ER; Mmio::write(_idmac_desc_phys); Mmio::write(1); Mmio::write(1); Mmio::write(1); Mmio::write(1); Mmio::write(_block_size()); Mmio::write(_block_size() * block_count); Mmio::write(1); return true; } void Driver::_handle_irq() { _irq.ack_irq(); if (!_block_transfer.pending) { return; } bool success = false; if (Mmio::read()) { error("Response error"); } if (Mmio::read()) { error("Data read timeout"); } if (Mmio::read()) { error("CRC error"); } if (Mmio::read()) { Mmio::write(~0U); if (!_issue_command(Stop_transmission())) { error("unable to stop transmission"); } else { success = true; } } _block_transfer.pending = false; ack_packet(_block_transfer.packet, success); } bool Driver::_issue_command(Command_base const &command) { try { wait_for(Attempts(10000), Microseconds(100), _delayer, Status::Data_busy::Equal(0)); } catch (Polling_timeout) { error("wait for State::Data_busy timed out ", Hex(Mmio::read())); return false; } Mmio::write(~0UL); /* write command argument */ Mmio::write(command.arg); Cmd::access_t cmd = 0; Cmd::Index::set(cmd, command.index); if (command.transfer != TRANSFER_NONE) { /* set data-direction bit depending on the command */ bool const write = command.transfer == 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 RESPONSE_NONE: rsp_type = Cmd::Rsp_type::RESPONSE_NONE; break; case RESPONSE_136_BIT: rsp_type = Cmd::Rsp_type::RESPONSE_136_BIT; break; case RESPONSE_48_BIT: rsp_type = Cmd::Rsp_type::RESPONSE_48_BIT; break; case 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 */ Mmio::write(cmd); try { wait_for(Attempts(10000), Microseconds(100), _delayer, Rintsts::Command_done::Equal(1)); } catch (Polling_timeout) { error("command failed " "Rintst: ", Mmio::read(), " " "Mintst: ", Mmio::read(), " " "Status: ", Mmio::read()); if (Mmio::read()) warning("timeout"); if (Mmio::read()) warning("repsonse error"); return false; } /* acknowledge interrupt */ Mmio::write(1); _delayer.usleep(100); return true; } 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; } size_t Driver::_read_ext_csd() { Attached_ram_dataspace ds(_env.ram(), _env.rm(), 0x1000, UNCACHED); addr_t phys = Dataspace_client(ds.cap()).phys_addr(); _setup_idmac_descriptor_table(1, phys); if (!issue_command(Mmc_send_ext_csd())) throw Detection_failed(); try { wait_for(_delayer, Rintsts::Data_transfer_over::Equal(1)); } catch (Polling_timeout) { error("cannot retrieve extented CSD"); throw Detection_failed(); } /* clear IRQ */ Mmio::write(1); /* contruct extented CSD */ Ext_csd csd((addr_t)ds.local_addr()); /* read revision */ if (csd.Mmio::read() < 2) { error("extented CSD revision is < 2"); throw Detection_failed(); } /* return sector count */ uint64_t capacity = csd.Mmio::read() * _block_size(); /* to MB */ return capacity / (1024 * 1024); } size_t Driver::Idmac_desc::set(size_t block_count, size_t block_size, addr_t phys_addr, Flags flag) { constexpr size_t MAX_BLOCKS = 8; flags = OWN | flag | (block_count <= MAX_BLOCKS ? LD : (CH | DIC)); bytes = ((block_count < MAX_BLOCKS) ? block_count : MAX_BLOCKS) * block_size; addr = phys_addr; return block_count < MAX_BLOCKS ? 0 : block_count - MAX_BLOCKS; }