459 lines
10 KiB
C++
459 lines
10 KiB
C++
/*
|
|
* \brief OMAP4-specific implementation of the Block::Driver interface
|
|
* \author Norman Feske
|
|
* \date 2012-07-19
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2012-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 <driver.h>
|
|
|
|
using namespace Genode;
|
|
using namespace Sd_card;
|
|
|
|
|
|
Card_info Driver::_init()
|
|
{
|
|
Mmio::write<Sysconfig>(0x2015);
|
|
Mmio::write<Hctl>(0x0);
|
|
|
|
_set_bus_power(VOLTAGE_3_0);
|
|
|
|
if (!_sd_bus_power_on()) {
|
|
error("sd_bus_power failed");
|
|
}
|
|
_disable_irq();
|
|
|
|
_bus_width(BUS_WIDTH_1);
|
|
|
|
_delayer.usleep(10*1000);
|
|
|
|
_stop_clock();
|
|
if (!_set_and_enable_clock(CLOCK_DIV_240)) {
|
|
error("set_clock failed");
|
|
throw Detection_failed();
|
|
}
|
|
if (!_init_stream()) {
|
|
error("sending the initialization stream failed");
|
|
throw Detection_failed();
|
|
}
|
|
Mmio::write<Blk>(0);
|
|
_delayer.usleep(1000);
|
|
|
|
if (!issue_command(Go_idle_state())) {
|
|
error("Go_idle_state command failed");
|
|
throw Detection_failed();
|
|
}
|
|
_delayer.usleep(2000);
|
|
|
|
if (!issue_command(Send_if_cond())) {
|
|
error("Send_if_cond command failed");
|
|
throw Detection_failed();
|
|
}
|
|
if (Mmio::read<Rsp10>() != 0x1aa) {
|
|
error("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))) {
|
|
warning("Sd_send_op_cond command failed");
|
|
throw Detection_failed();
|
|
}
|
|
|
|
if (Ocr::Busy::get(Mmio::read<Rsp10>()))
|
|
break;
|
|
|
|
_delayer.usleep(1000);
|
|
}
|
|
if (i == 0) {
|
|
error("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())) {
|
|
warning("Set_bus_width(FOUR_BITS) command failed");
|
|
throw Detection_failed();
|
|
}
|
|
_bus_width(BUS_WIDTH_4);
|
|
_delayer.usleep(10*1000);
|
|
|
|
_stop_clock();
|
|
if (!_set_and_enable_clock(CLOCK_DIV_0)) {
|
|
error("set_clock failed");
|
|
throw Detection_failed();
|
|
}
|
|
|
|
/* enable IRQs */
|
|
Mmio::write<Ie::Tc_enable>(1);
|
|
Mmio::write<Ie::Cto_enable>(1);
|
|
Mmio::write<Ise::Tc_sigen>(1);
|
|
Mmio::write<Ise::Cto_sigen>(1);
|
|
|
|
return card_info;
|
|
}
|
|
|
|
|
|
bool Driver::_wait_for_bre()
|
|
{
|
|
try { wait_for(Attempts(1000000), Microseconds(0), _delayer,
|
|
Pstate::Bre::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
try { wait_for(_delayer, Pstate::Bre::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("Pstate::Bre timed out");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Driver::_wait_for_bwe()
|
|
{
|
|
try { wait_for(Attempts(1000000), Microseconds(0), _delayer,
|
|
Pstate::Bwe::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
try { wait_for(_delayer, Pstate::Bwe::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("Pstate::Bwe timed out");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void Driver::_handle_irq()
|
|
{
|
|
_irq.ack_irq();
|
|
|
|
if (!_block_transfer.pending) {
|
|
return; }
|
|
|
|
if (Mmio::read<Stat::Tc>() != 1) {
|
|
warning("unexpected interrupt, Stat: ", Hex(Mmio::read<Stat>()));
|
|
return;
|
|
}
|
|
|
|
Mmio::write<Stat::Tc>(1);
|
|
|
|
if (Mmio::read<Stat>() != 0) {
|
|
warning("unexpected state ("
|
|
"Stat: ", Hex(Mmio::read<Stat>()), " "
|
|
"Blen: ", Hex(Mmio::read<Blk::Blen>()), " "
|
|
"Nblk: ", Mmio::read<Blk::Nblk>());
|
|
return;
|
|
}
|
|
_block_transfer.pending = false;
|
|
ack_packet(_block_transfer.packet, true);
|
|
}
|
|
|
|
|
|
bool Driver::_reset_cmd_line()
|
|
{
|
|
Mmio::write<Sysctl::Src>(1);
|
|
|
|
/*
|
|
* We must poll quickly. If we waited too long until checking the
|
|
* bit, the polling would be infinite. Apparently the hardware
|
|
* depends on the timing here.
|
|
*/
|
|
try { wait_for(Attempts(1000), Microseconds(0), _delayer,
|
|
Sysctl::Src::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("reset of cmd line timed out (src != 1)");
|
|
return false;
|
|
}
|
|
try { wait_for(Attempts(1000), Microseconds(0), _delayer,
|
|
Sysctl::Src::Equal(0)); }
|
|
catch (Polling_timeout) {
|
|
error("reset of cmd line timed out (src != 0)");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void Driver::_disable_irq()
|
|
{
|
|
Mmio::write<Ise>(0);
|
|
Mmio::write<Ie>(0);
|
|
Mmio::write<Stat>(~0);
|
|
}
|
|
|
|
|
|
void Driver::_bus_width(Bus_width bus_width)
|
|
{
|
|
switch (bus_width) {
|
|
case BUS_WIDTH_1:
|
|
Mmio::write<Con::Dw8>(0);
|
|
Mmio::write<Hctl::Dtw>(Hctl::Dtw::ONE_BIT);
|
|
break;
|
|
|
|
case BUS_WIDTH_4:
|
|
Mmio::write<Con::Dw8>(0);
|
|
Mmio::write<Hctl::Dtw>(Hctl::Dtw::FOUR_BITS);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool Driver::_sd_bus_power_on()
|
|
{
|
|
Mmio::write<Hctl::Sdbp>(Hctl::Sdbp::POWER_ON);
|
|
|
|
try { wait_for(_delayer, Hctl::Sdbp::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("setting Hctl::Sdbp timed out");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Driver::_set_and_enable_clock(enum Clock_divider divider)
|
|
{
|
|
Mmio::write<Sysctl::Dto>(Sysctl::Dto::TCF_2_POW_27);
|
|
|
|
switch (divider) {
|
|
case CLOCK_DIV_0: Mmio::write<Sysctl::Clkd>(0); break;
|
|
case CLOCK_DIV_240: Mmio::write<Sysctl::Clkd>(240); break;
|
|
}
|
|
|
|
Mmio::write<Sysctl::Ice>(1);
|
|
|
|
/* wait for clock to become stable */
|
|
try { wait_for(_delayer, Sysctl::Ics::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("clock enable timed out");
|
|
return false;
|
|
}
|
|
/* enable clock */
|
|
Mmio::write<Sysctl::Ce>(1);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void Driver::_set_bus_power(Voltage voltage)
|
|
{
|
|
switch (voltage) {
|
|
case VOLTAGE_3_0:
|
|
Mmio::write<Hctl::Sdvs>(Hctl::Sdvs::VOLTAGE_3_0);
|
|
break;
|
|
case VOLTAGE_1_8:
|
|
Mmio::write<Hctl::Sdvs>(Hctl::Sdvs::VOLTAGE_1_8);
|
|
break;
|
|
}
|
|
Mmio::write<Capa::Vs18>(1);
|
|
|
|
if (voltage == VOLTAGE_3_0)
|
|
Mmio::write<Capa::Vs30>(1);
|
|
}
|
|
|
|
|
|
bool Driver::_init_stream()
|
|
{
|
|
Mmio::write<Ie>(0x307f0033);
|
|
|
|
/* start initialization sequence */
|
|
Mmio::write<Con::Init>(1);
|
|
Mmio::write<Cmd>(0);
|
|
|
|
try { wait_for(Attempts(1000000), Microseconds(0), _delayer,
|
|
Stat::Cc::Equal(1)); }
|
|
catch (Polling_timeout) {
|
|
error("init stream timed out");
|
|
return false;
|
|
}
|
|
/* stop initialization sequence */
|
|
Mmio::write<Con::Init>(0);
|
|
Mmio::write<Stat>(~0);
|
|
Mmio::read<Stat>();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Driver::_issue_command(Command_base const &command)
|
|
{
|
|
try { wait_for(_delayer, Pstate::Cmdi::Equal(0)); }
|
|
catch (Polling_timeout) {
|
|
error("wait for Pstate::Cmdi timed out");
|
|
return false;
|
|
}
|
|
/* write command argument */
|
|
Mmio::write<Arg>(command.arg);
|
|
|
|
/* assemble command register */
|
|
Cmd::access_t cmd = 0;
|
|
Cmd::Index::set(cmd, command.index);
|
|
if (command.transfer != TRANSFER_NONE) {
|
|
|
|
Cmd::Dp::set(cmd);
|
|
Cmd::Bce::set(cmd);
|
|
Cmd::Msbs::set(cmd);
|
|
|
|
if (command.index == Read_multiple_block::INDEX ||
|
|
command.index == Write_multiple_block::INDEX)
|
|
{
|
|
Cmd::Acen::set(cmd);
|
|
}
|
|
/* set data-direction bit depending on the command */
|
|
bool const read = command.transfer == TRANSFER_READ;
|
|
Cmd::Ddir::set(cmd, read ? Cmd::Ddir::READ : Cmd::Ddir::WRITE);
|
|
}
|
|
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);
|
|
|
|
/* write command */
|
|
Mmio::write<Cmd>(cmd);
|
|
bool result = false;
|
|
|
|
/* wait until command is completed, return false on timeout */
|
|
for (unsigned long i = 0; i < 1000*1000; i++) {
|
|
Stat::access_t const stat = Mmio::read<Stat>();
|
|
if (Stat::Erri::get(stat)) {
|
|
warning("SD command error");
|
|
if (Stat::Cto::get(stat))
|
|
warning("timeout");
|
|
|
|
_reset_cmd_line();
|
|
Mmio::write<Stat::Cc>(~0);
|
|
Mmio::read<Stat>();
|
|
result = false;
|
|
break;
|
|
}
|
|
if (Stat::Cc::get(stat) == 1) {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
/* clear status of command-completed bit */
|
|
Mmio::write<Stat::Cc>(1);
|
|
Mmio::read<Stat>();
|
|
return result;
|
|
}
|
|
|
|
|
|
Cid Driver::_read_cid()
|
|
{
|
|
Cid cid;
|
|
cid.raw_0 = Mmio::read<Rsp10>();
|
|
cid.raw_1 = Mmio::read<Rsp32>();
|
|
cid.raw_2 = Mmio::read<Rsp54>();
|
|
cid.raw_3 = Mmio::read<Rsp76>();
|
|
return cid;
|
|
}
|
|
|
|
|
|
Csd Driver::_read_csd()
|
|
{
|
|
Csd csd;
|
|
csd.csd0 = Mmio::read<Rsp10>();
|
|
csd.csd1 = Mmio::read<Rsp32>();
|
|
csd.csd2 = Mmio::read<Rsp54>();
|
|
csd.csd3 = Mmio::read<Rsp76>();
|
|
return csd;
|
|
}
|
|
|
|
|
|
Driver::Driver(Env &env)
|
|
:
|
|
Driver_base(env.ram()),
|
|
Attached_mmio(env, MMCHS1_MMIO_BASE, MMCHS1_MMIO_SIZE), _env(env)
|
|
{
|
|
_irq.sigh(_irq_handler);
|
|
_irq.ack_irq();
|
|
log("SD card detected");
|
|
log("capacity: ", _card_info.capacity_mb(), " MiB");
|
|
}
|
|
|
|
|
|
void Driver::read(Block::sector_t block_number,
|
|
size_t block_count,
|
|
char *buffer,
|
|
Block::Packet_descriptor &pkt)
|
|
{
|
|
if (_block_transfer.pending) {
|
|
throw Request_congestion(); }
|
|
|
|
Mmio::write<Blk::Blen>(_block_size());
|
|
Mmio::write<Blk::Nblk>(block_count);
|
|
|
|
_block_transfer.packet = pkt;
|
|
_block_transfer.pending = true;
|
|
|
|
if (!issue_command(Read_multiple_block(block_number))) {
|
|
error("Read_multiple_block failed");
|
|
throw Io_error();
|
|
}
|
|
size_t const num_accesses = block_count * _block_size() /
|
|
sizeof(Data::access_t);
|
|
Data::access_t *dst = (Data::access_t *)(buffer);
|
|
|
|
for (size_t i = 0; i < num_accesses; i++) {
|
|
if (!_wait_for_bre())
|
|
throw Io_error();
|
|
|
|
*dst++ = Mmio::read<Data>();
|
|
}
|
|
}
|
|
|
|
|
|
void Driver::write(Block::sector_t block_number,
|
|
size_t block_count,
|
|
char const *buffer,
|
|
Block::Packet_descriptor &pkt)
|
|
{
|
|
if (_block_transfer.pending) {
|
|
throw Request_congestion(); }
|
|
|
|
Mmio::write<Blk::Blen>(_block_size());
|
|
Mmio::write<Blk::Nblk>(block_count);
|
|
|
|
_block_transfer.packet = pkt;
|
|
_block_transfer.pending = true;
|
|
|
|
if (!issue_command(Write_multiple_block(block_number))) {
|
|
error("Write_multiple_block failed");
|
|
throw Io_error();
|
|
}
|
|
size_t const num_accesses = block_count * _block_size() /
|
|
sizeof(Data::access_t);
|
|
Data::access_t const *src = (Data::access_t const *)(buffer);
|
|
|
|
for (size_t i = 0; i < num_accesses; i++) {
|
|
if (!_wait_for_bwe()) {
|
|
throw Io_error(); }
|
|
|
|
Mmio::write<Data>(*src++);
|
|
}
|
|
}
|