846 lines
23 KiB
C++
846 lines
23 KiB
C++
/*
|
|
* \brief ESDHCv2 host controller
|
|
* \author Martin Stein
|
|
* \date 2015-02-05
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2012-2015 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 _DRIVERS__SD_CARD__SPEC__IMX53__ESDHCV2_H_
|
|
#define _DRIVERS__SD_CARD__SPEC__IMX53__ESDHCV2_H_
|
|
|
|
/* Genode includes */
|
|
#include <util/mmio.h>
|
|
#include <os/attached_ram_dataspace.h>
|
|
#include <irq_session/connection.h>
|
|
#include <base/sleep.h>
|
|
|
|
/* local includes */
|
|
#include <sd_card.h>
|
|
|
|
namespace Adma2
|
|
{
|
|
using namespace Genode;
|
|
|
|
/**
|
|
* Descriptor layout
|
|
*/
|
|
struct Desc : Register<64>
|
|
{
|
|
static size_t constexpr max_size = 64 * 1024 - 4;
|
|
|
|
struct Valid : Bitfield<0, 1> { };
|
|
struct End : Bitfield<1, 1> { };
|
|
struct Int : Bitfield<2, 1> { };
|
|
struct Act1 : Bitfield<4, 1> { };
|
|
struct Act2 : Bitfield<5, 1> { };
|
|
struct Length : Bitfield<16, 16> { };
|
|
struct Address : Bitfield<32, 32> { };
|
|
};
|
|
|
|
/**
|
|
* Descriptor table
|
|
*/
|
|
class Table
|
|
{
|
|
static size_t constexpr _max_desc = 1024;
|
|
static size_t constexpr _ds_size = _max_desc * sizeof(Desc::access_t);
|
|
|
|
Attached_ram_dataspace _ds;
|
|
Desc::access_t * const _base_virt;
|
|
addr_t const _base_phys;
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
Table() :
|
|
_ds(env()->ram_session(), _ds_size, UNCACHED),
|
|
_base_virt(_ds.local_addr<Desc::access_t>()),
|
|
_base_phys(Dataspace_client(_ds.cap()).phys_addr())
|
|
{ }
|
|
|
|
/**
|
|
* Marshal descriptors according to block request
|
|
*
|
|
* \param size request size in bytes
|
|
* \param buffer_phys physical base of transfer buffer
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool setup_req(size_t const size, addr_t const buffer_phys)
|
|
{
|
|
/*
|
|
* Sanity check
|
|
*
|
|
* FIXME An alternative to this sanity check would be to
|
|
* expose the maximum transfer size to the driver and let
|
|
* the driver partition large requests into ones that are
|
|
* supported.
|
|
*/
|
|
static size_t constexpr max_size = _max_desc * Desc::max_size;
|
|
if (size > max_size) {
|
|
PERR("Block request too large");
|
|
return false;
|
|
}
|
|
/* install new descriptors till they cover all requested bytes */
|
|
addr_t consumed = 0;
|
|
for (int index = 0; consumed < size; index++) {
|
|
|
|
/* clamp current request to maximum request size */
|
|
size_t const remaining = size - consumed;
|
|
size_t const curr = min(Desc::max_size, remaining);
|
|
|
|
/* assemble new descriptor */
|
|
Desc::access_t desc = 0;
|
|
Desc::Address::set(desc, buffer_phys + consumed);
|
|
Desc::Length::set(desc, curr);
|
|
Desc::Act1::set(desc, 0);
|
|
Desc::Act2::set(desc, 1);
|
|
Desc::Valid::set(desc, 1);
|
|
|
|
/* let last descriptor generate transfer-complete signal */
|
|
if (consumed + curr == size) { Desc::End::set(desc, 1); }
|
|
|
|
/* install and account descriptor */
|
|
_base_virt[index] = desc;
|
|
consumed += curr;
|
|
}
|
|
/* ensure that all descriptor writes were actually executed */
|
|
asm volatile ("dsb");
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Accessors
|
|
*/
|
|
|
|
addr_t base_phys() const { return _base_phys; }
|
|
};
|
|
|
|
}
|
|
|
|
/**
|
|
* Freescale eSDHCv2 host MMIO
|
|
*/
|
|
struct Esdhcv2 : Genode::Mmio
|
|
{
|
|
typedef Genode::size_t size_t;
|
|
typedef Genode::addr_t addr_t;
|
|
|
|
/*
|
|
* MMIO structure
|
|
*/
|
|
|
|
struct Blkattr : Register<0x4, 32>
|
|
{
|
|
struct Blksize : Bitfield<0, 13> { };
|
|
struct Blkcnt : Bitfield<16, 16> { };
|
|
};
|
|
template <Genode::off_t OFFSET>
|
|
struct Cmdrsp_tpl : Register<OFFSET, 32>
|
|
{
|
|
struct Rsp136_8_24 : Register<OFFSET, 32>::template Bitfield<0, 24> { };
|
|
struct Rsp136_0_8 : Register<OFFSET, 32>::template Bitfield<24, 8> { };
|
|
};
|
|
struct Cmdarg : Register<0x8, 32> { };
|
|
struct Cmdrsp0 : Cmdrsp_tpl<0x10> { };
|
|
struct Cmdrsp1 : Cmdrsp_tpl<0x14> { };
|
|
struct Cmdrsp2 : Cmdrsp_tpl<0x18> { };
|
|
struct Cmdrsp3 : Cmdrsp_tpl<0x1c> { };
|
|
struct Rsp136_0 : Genode::Bitset_2<Cmdrsp3::Rsp136_0_8, Cmdrsp0::Rsp136_8_24> { };
|
|
struct Rsp136_1 : Genode::Bitset_2<Cmdrsp0::Rsp136_0_8, Cmdrsp1::Rsp136_8_24> { };
|
|
struct Rsp136_2 : Genode::Bitset_2<Cmdrsp1::Rsp136_0_8, Cmdrsp2::Rsp136_8_24> { };
|
|
struct Rsp136_3 : Genode::Bitset_2<Cmdrsp2::Rsp136_0_8, Cmdrsp3::Rsp136_8_24> { };
|
|
struct Xfertyp : Register<0xc, 32>
|
|
{
|
|
struct Dmaen : Bitfield<0, 1> { };
|
|
struct Bcen : Bitfield<1, 1> { };
|
|
struct Ac12en : Bitfield<2, 1> { };
|
|
struct Dtdsel : Bitfield<4, 1>
|
|
{
|
|
enum { WRITE = 0, READ = 1, };
|
|
};
|
|
struct Msbsel : Bitfield<5, 1> { };
|
|
struct Rsptyp : Bitfield<16, 2>
|
|
{
|
|
enum {
|
|
_0BIT = 0,
|
|
_136BIT = 1,
|
|
_48BIT = 2,
|
|
_48BIT_BUSY = 3,
|
|
};
|
|
};
|
|
struct Dpsel : Bitfield<21, 1> { };
|
|
struct Cmdtyp : Bitfield<22, 2>
|
|
{
|
|
enum { ABORT_CMD12 = 3 };
|
|
};
|
|
struct Cmdinx : Bitfield<24, 6> { };
|
|
};
|
|
struct Datport : Register<0x20, 32> { };
|
|
struct Prsstat_lhw : Register<0x24, 16>
|
|
{
|
|
struct Sdstb : Bitfield<3, 1> { };
|
|
|
|
static constexpr access_t cmd_allowed() { return Sdstb::reg_mask(); }
|
|
};
|
|
struct Proctl : Register<0x28, 32>
|
|
{
|
|
struct Dtw : Bitfield<1, 2>
|
|
{
|
|
enum { _1BIT = 0, _4BIT = 1 };
|
|
};
|
|
struct Dmas : Bitfield<8, 2> { enum { ADMA2 = 2 }; };
|
|
};
|
|
struct Sysctl : Register<0x2c, 32>
|
|
{
|
|
struct Ipgen : Bitfield<0, 1> { };
|
|
struct Hcken : Bitfield<1, 1> { };
|
|
struct Peren : Bitfield<2, 1> { };
|
|
struct Dvs : Bitfield<4, 4>
|
|
{
|
|
enum { DIV1 = 0x0, DIV4 = 0x3, DIV16 = 0xf, };
|
|
};
|
|
struct Sdclkfs : Bitfield<8, 8>
|
|
{
|
|
enum { DIV1 = 0x00, DIV2 = 0x01, DIV32 = 0x10, };
|
|
};
|
|
struct Dtocv : Bitfield<16, 4>
|
|
{
|
|
enum { SDCLK_TIMES_2_POW_27 = 0xe };
|
|
};
|
|
struct Rsta : Bitfield<24, 1> { };
|
|
struct Rstc : Bitfield<25, 1> { };
|
|
struct Rstd : Bitfield<26, 1> { };
|
|
};
|
|
|
|
template <Genode::off_t OFFSET>
|
|
struct Irq_tpl : Register<OFFSET, 32>
|
|
{
|
|
struct Cc : Register<OFFSET, 32>::template Bitfield<0, 1> { };
|
|
struct Tc : Register<OFFSET, 32>::template Bitfield<1, 1> { };
|
|
struct Dint : Register<OFFSET, 32>::template Bitfield<03, 1> { };
|
|
struct Ctoe : Register<OFFSET, 32>::template Bitfield<16, 1> { };
|
|
struct Cce : Register<OFFSET, 32>::template Bitfield<17, 1> { };
|
|
struct Cebe : Register<OFFSET, 32>::template Bitfield<18, 1> { };
|
|
struct Cie : Register<OFFSET, 32>::template Bitfield<19, 1> { };
|
|
struct Dtoe : Register<OFFSET, 32>::template Bitfield<20, 1> { };
|
|
struct Dce : Register<OFFSET, 32>::template Bitfield<21, 1> { };
|
|
struct Debe : Register<OFFSET, 32>::template Bitfield<22, 1> { };
|
|
struct Ac12e : Register<OFFSET, 32>::template Bitfield<24, 1> { };
|
|
struct Dmae : Register<OFFSET, 32>::template Bitfield<28, 1> { };
|
|
};
|
|
struct Irq : Irq_tpl<0> { };
|
|
struct Irqstat : Irq_tpl<0x30> { };
|
|
struct Irqstaten : Irq_tpl<0x34> { };
|
|
struct Irqsigen : Irq_tpl<0x38> { };
|
|
struct Maxcurrent : Register<0x48, 32> { };
|
|
struct Adsaddr : Register<0x58, 32> { };
|
|
struct Hostver : Register<0xfc, 32>
|
|
{
|
|
struct Svn : Bitfield<0, 8> { };
|
|
struct Vvn : Bitfield<8, 8> { };
|
|
};
|
|
struct Wml : Register<0x44, 32>
|
|
{
|
|
struct Rd_wml : Bitfield<0, 8> { };
|
|
struct Rd_brst_len : Bitfield<8, 5> { };
|
|
struct Wr_wml : Bitfield<16, 8> { };
|
|
struct Wr_brst_len : Bitfield<24, 5> { };
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
Esdhcv2(addr_t const mmio_base) : Genode::Mmio(mmio_base) { }
|
|
|
|
/**
|
|
* Reset command line circuit of host controller
|
|
*/
|
|
bool reset_command(Delayer & delayer)
|
|
{
|
|
write<Sysctl::Rstc>(1);
|
|
if (!wait_for<Sysctl::Rstc>(0, delayer)) {
|
|
PERR("Reset of command circuit failed");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reset data circuit of host controller
|
|
*/
|
|
bool reset_data(Delayer & delayer)
|
|
{
|
|
write<Sysctl::Rstd>(1);
|
|
if (!wait_for<Sysctl::Rstd>(0, delayer)) {
|
|
PERR("Reset of data circuit failed");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Full host reset
|
|
*/
|
|
bool reset_all(Delayer & delayer)
|
|
{
|
|
/* start reset */
|
|
write<Sysctl::Rsta>(1);
|
|
|
|
/*
|
|
* The SDHC specification says that a software reset shouldn't
|
|
* have an effect on the the card detection circuit. The ESDHC
|
|
* clears Sysctl::Ipgen, Sysctl::Hcken, and Sysctl::Peren
|
|
* nonetheless which disables clocks that card detection relies
|
|
* on.
|
|
*/
|
|
Sysctl::access_t sysctl = read<Sysctl>();
|
|
Sysctl::Ipgen::set(sysctl, 1);
|
|
Sysctl::Hcken::set(sysctl, 1);
|
|
Sysctl::Peren::set(sysctl, 1);
|
|
write<Sysctl>(sysctl);
|
|
|
|
/* wait for reset completion */
|
|
return wait_for<Sysctl::Rsta>(0, delayer);
|
|
}
|
|
|
|
/**
|
|
* Disable all interrupts
|
|
*/
|
|
void disable_irqs()
|
|
{
|
|
write<Irqstaten>(0);
|
|
write<Irqsigen>(0);
|
|
}
|
|
|
|
/**
|
|
* Enable desired interrupts
|
|
*/
|
|
void 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);
|
|
write<Irqstaten>(irq);
|
|
write<Irqsigen>(irq);
|
|
}
|
|
|
|
enum Bus_width { BUS_WIDTH_1, BUS_WIDTH_4 };
|
|
|
|
/**
|
|
* Set bus width
|
|
*/
|
|
void bus_width(Bus_width bus_width)
|
|
{
|
|
switch (bus_width) {
|
|
case BUS_WIDTH_1: write<Proctl::Dtw>(Proctl::Dtw::_1BIT); break;
|
|
case BUS_WIDTH_4: write<Proctl::Dtw>(Proctl::Dtw::_4BIT); break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable host clock
|
|
*/
|
|
void disable_clock()
|
|
{
|
|
Sysctl::access_t sysctl = read<Sysctl>();
|
|
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);
|
|
write<Sysctl>(sysctl);
|
|
}
|
|
|
|
enum Clock_divider { CLOCK_DIV_8, CLOCK_DIV_512 };
|
|
|
|
/**
|
|
* Enable host clock and configure it to fullfill a given divider
|
|
*/
|
|
void enable_clock(Clock_divider divider, Delayer &delayer)
|
|
{
|
|
Sysctl::access_t sysctl = read<Sysctl>();
|
|
Sysctl::Ipgen::set(sysctl, 1);
|
|
Sysctl::Hcken::set(sysctl, 1);
|
|
Sysctl::Peren::set(sysctl, 1);
|
|
switch (divider) {
|
|
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;
|
|
}
|
|
write<Sysctl>(sysctl);
|
|
delayer.usleep(1000);
|
|
}
|
|
|
|
/**
|
|
* Set clock to fulfill a given divider
|
|
*/
|
|
void clock(enum Clock_divider divider, Delayer &delayer)
|
|
{
|
|
disable_clock();
|
|
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_27);
|
|
enable_clock(divider, delayer);
|
|
}
|
|
};
|
|
|
|
struct Esdhcv2_controller : private Esdhcv2, public Sd_card::Host_controller
|
|
{
|
|
private:
|
|
|
|
enum {
|
|
BLOCK_SIZE = 512,
|
|
WATERMARK_WORDS = 16,
|
|
BURST_WORDS = 8,
|
|
};
|
|
|
|
Genode::Irq_connection _irq;
|
|
Genode::Signal_receiver _irq_rec;
|
|
Genode::Signal_context _irq_ctx;
|
|
Delayer & _delayer;
|
|
Sd_card::Card_info _card_info;
|
|
bool const _use_dma;
|
|
Adma2::Table _adma2_table;
|
|
|
|
/**
|
|
* Print 'err' and throw detection-error exception
|
|
*/
|
|
void _detect_err(char const * const err)
|
|
{
|
|
PERR("%s", err);
|
|
throw Detection_failed();
|
|
}
|
|
|
|
/**
|
|
* Initialize host and card and return basic card information
|
|
*/
|
|
Sd_card::Card_info _init()
|
|
{
|
|
/* install IRQ signal */
|
|
_irq.sigh(_irq_rec.manage(&_irq_ctx));
|
|
|
|
/* configure host for initialization stage */
|
|
using namespace Sd_card;
|
|
if (!reset_all(_delayer)) { _detect_err("Host reset failed"); }
|
|
disable_irqs();
|
|
|
|
/* check host version */
|
|
Hostver::access_t const hostver = read<Hostver>();
|
|
if (Hostver::Vvn::get(hostver) != 18) {
|
|
_detect_err("Unexpected Vendor Version Number"); }
|
|
if (Hostver::Svn::get(hostver) != 1) {
|
|
_detect_err("Unexpected Specification Version Number"); }
|
|
|
|
/*
|
|
* We should check host capabilities at this point if we want to
|
|
* support other versions of the ESDHC. For the i.MX53 ESDHCv2 we
|
|
* know that the capabilities fit our requirements.
|
|
*/
|
|
|
|
enable_irqs();
|
|
bus_width(BUS_WIDTH_1);
|
|
_delayer.usleep(10000);
|
|
clock(CLOCK_DIV_512, _delayer);
|
|
|
|
/*
|
|
* 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 (read<Cmdrsp0>() != 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 (read<Cmdrsp0>() != 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(read<Cmdrsp0>())) { 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
|
|
*
|
|
* FIXME 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_DIV_8, _delayer);
|
|
|
|
/*
|
|
* Configure card and host to use 4 data signals
|
|
*
|
|
* FIXME 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 our block size */
|
|
if (!issue_command(Set_blocklen(BLOCK_SIZE))) {
|
|
_detect_err("Set_blocklen command failed"); }
|
|
|
|
/* configure host buffer */
|
|
Wml::access_t wml = read<Wml>();
|
|
Wml::Rd_wml::set(wml, WATERMARK_WORDS);
|
|
Wml::Rd_brst_len::set(wml, BURST_WORDS);
|
|
Wml::Wr_wml::set(wml, WATERMARK_WORDS);
|
|
Wml::Wr_brst_len::set(wml, BURST_WORDS);
|
|
write<Wml>(wml);
|
|
|
|
/* configure ADMA */
|
|
write<Proctl::Dmas>(Proctl::Dmas::ADMA2);
|
|
|
|
/* configure interrupts for operational mode */
|
|
disable_irqs();
|
|
write<Irqstat>(~0);
|
|
enable_irqs();
|
|
return card_info;
|
|
}
|
|
|
|
/**
|
|
* Wait until we received the IRQ signal
|
|
*/
|
|
void _wait_for_irq()
|
|
{
|
|
/*
|
|
* Acknowledge the IRQ first to implicitly activate
|
|
* receiving of further IRQ signals on the first usage
|
|
* of this method.
|
|
*/
|
|
_irq.ack_irq();
|
|
_irq_rec.wait_for_signal();
|
|
}
|
|
|
|
/*
|
|
* Wait till sending a new command is allowed
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool _wait_for_cmd_allowed()
|
|
{
|
|
/*
|
|
* At least after multi-block writes with our
|
|
* "Broken Auto Command 12" fix, waiting only for Prsstat::Cihb
|
|
* isn't sufficient as Prsstat::Dla and Prsstat::Cdihb may also
|
|
* be active.
|
|
*/
|
|
if (!wait_for<Prsstat_lhw>(Prsstat_lhw::cmd_allowed(), _delayer)) {
|
|
PERR("wait till issuing a new command is allowed timed out");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Wait for the completion of a non-multiple-block command
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool _wait_for_cmd_complete()
|
|
{
|
|
_wait_for_irq();
|
|
if (read<Irqstat>() != Irqstat::Cc::reg_mask()) {
|
|
PWRN("received unexpected host signal");
|
|
reset_command(_delayer);
|
|
reset_data(_delayer);
|
|
enable_irqs();
|
|
return false;
|
|
}
|
|
write<Irqstat>(Irqstat::Cc::reg_mask());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Abort transmission by manually issuing stop command
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool _abort_transmission()
|
|
{
|
|
write<Cmdarg>(0);
|
|
Xfertyp::access_t xfertyp = 0;
|
|
Xfertyp::Cmdinx::set(xfertyp, Sd_card::Stop_transmission::INDEX);
|
|
Xfertyp::Cmdtyp::set(xfertyp, Xfertyp::Cmdtyp::ABORT_CMD12);
|
|
Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT);
|
|
write<Xfertyp>(xfertyp);
|
|
return _wait_for_cmd_complete();
|
|
}
|
|
|
|
/**
|
|
* Wait for the completion of a multiple-block command
|
|
*
|
|
* \param r wether the transfer reads from card to host
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool _wait_for_mbc_complete(bool const r)
|
|
{
|
|
/* wait for a signal */
|
|
_wait_for_irq();
|
|
Irqstat::access_t const irq = read<Irqstat>();
|
|
|
|
/*
|
|
* The ESDHC signals on multi-block transfers seem to be broken.
|
|
* Synchronizing to Irqstat::Tc before returning from transfer
|
|
* requests and to Prsstat::Cihb before sending the next command,
|
|
* as it is done with other SDHCs, isn't sufficient. Instead, both
|
|
* completion signals must be gathered while dealing with the fact
|
|
* that the interrupt triggers only on signal edges and we can
|
|
* therefore not wait for a second IRQ when we've received a
|
|
* single signal the first time.
|
|
*/
|
|
Irqstat::access_t constexpr irq_goal =
|
|
Irq::Cc::reg_mask() | Irq::Tc::reg_mask();
|
|
|
|
/* poll for the missing signal */
|
|
if (irq != irq_goal) {
|
|
if (!wait_for<Irqstat>(irq_goal, _delayer)) {
|
|
PERR("completion host signal timed out");
|
|
return false;
|
|
}
|
|
}
|
|
/* acknowledge both completion signals */
|
|
write<Irqstat>(irq_goal);
|
|
if (!r) {
|
|
|
|
/*
|
|
* The "Auto Command 12" feature of the ESDHC seems to be
|
|
* broken for multi-block writes as it causes command-
|
|
* timeout errors sometimes. Thus, we end such transfers
|
|
* manually.
|
|
*/
|
|
if (!_abort_transmission()) { return false; }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Prepare multi-block-transfer command with DMA
|
|
*
|
|
* \param blk_cnt number of transferred blocks block
|
|
* \param buf_phys physical base of transfer buffer
|
|
*
|
|
* \return true on success
|
|
*/
|
|
bool _prepare_dma_mbc(size_t blk_cnt, addr_t buf_phys)
|
|
{
|
|
size_t const req_size = blk_cnt * BLOCK_SIZE;
|
|
if (!_adma2_table.setup_req(req_size, buf_phys)) {
|
|
PERR("Setup ADMA2 table failed");
|
|
return false;
|
|
}
|
|
write<Adsaddr>(_adma2_table.base_phys());
|
|
write<Blkattr::Blksize>(BLOCK_SIZE);
|
|
write<Blkattr::Blkcnt>(blk_cnt);
|
|
return true;
|
|
}
|
|
|
|
|
|
/****************************************
|
|
** Sd_card::Host_controller interface **
|
|
****************************************/
|
|
|
|
Sd_card::Cid _read_cid()
|
|
{
|
|
Sd_card::Cid cid;
|
|
cid.raw_0 = read<Rsp136_0>();
|
|
cid.raw_1 = read<Rsp136_1>();
|
|
cid.raw_2 = read<Rsp136_2>();
|
|
cid.raw_3 = read<Rsp136_3>();
|
|
return cid;
|
|
}
|
|
|
|
Sd_card::Csd _read_csd()
|
|
{
|
|
Sd_card::Csd csd;
|
|
csd.csd0 = read<Rsp136_0>();
|
|
csd.csd1 = read<Rsp136_1>();
|
|
csd.csd2 = read<Rsp136_2>();
|
|
csd.csd3 = read<Rsp136_3>();
|
|
return csd;
|
|
}
|
|
|
|
unsigned _read_rca()
|
|
{
|
|
Cmdrsp0::access_t const rsp0 = read<Cmdrsp0>();
|
|
return Sd_card::Send_relative_addr::Response::Rca::get(rsp0);
|
|
}
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* \param mmio_base local base address of MMIO registers
|
|
* \param irq host-interrupt ID
|
|
* \param delayer delayer timing of MMIO accesses
|
|
* \param use_dma wether to use DMA or direct IO for transfers
|
|
*/
|
|
Esdhcv2_controller(addr_t const mmio_base, unsigned const irq,
|
|
Delayer & delayer, bool const use_dma)
|
|
:
|
|
Esdhcv2(mmio_base), _irq(irq),
|
|
_delayer(delayer), _card_info(_init()), _use_dma(use_dma) { }
|
|
|
|
~Esdhcv2_controller() { _irq_rec.dissolve(&_irq_ctx); }
|
|
|
|
|
|
/****************************************
|
|
** Sd_card::Host_controller interface **
|
|
****************************************/
|
|
|
|
bool _issue_command(Sd_card::Command_base const & command)
|
|
{
|
|
/* detect if command is a multi-block transfer and if it reads */
|
|
using namespace Sd_card;
|
|
bool const r = command.transfer == Sd_card::TRANSFER_READ;
|
|
bool const mb =
|
|
command.index == Sd_card::Read_multiple_block::INDEX ||
|
|
command.index == Sd_card::Write_multiple_block::INDEX;
|
|
|
|
/* assemble comsendcode */
|
|
Xfertyp::access_t cmd = 0;
|
|
Xfertyp::Cmdinx::set(cmd, command.index);
|
|
if (command.transfer != Sd_card::TRANSFER_NONE) {
|
|
Xfertyp::Dpsel::set(cmd);
|
|
Xfertyp::Bcen::set(cmd);
|
|
Xfertyp::Msbsel::set(cmd);
|
|
if (mb) {
|
|
/*
|
|
* The "Auto Command 12" feature of the ESDHC seems to be
|
|
* broken for multi-block writes as it causes command-
|
|
* timeout errors sometimes. Thus, we end such transfers
|
|
* manually.
|
|
*/
|
|
if (r) { Xfertyp::Ac12en::set(cmd); }
|
|
if (_use_dma) { Xfertyp::Dmaen::set(cmd); }
|
|
}
|
|
Xfertyp::Dtdsel::set(cmd,
|
|
r ? Xfertyp::Dtdsel::READ : Xfertyp::Dtdsel::WRITE);
|
|
}
|
|
typedef Xfertyp::Rsptyp Rsptyp;
|
|
Xfertyp::access_t rt = 0;
|
|
switch (command.rsp_type) {
|
|
case RESPONSE_NONE: rt = Rsptyp::_0BIT; break;
|
|
case RESPONSE_136_BIT: rt = Rsptyp::_136BIT; break;
|
|
case RESPONSE_48_BIT: rt = Rsptyp::_48BIT; break;
|
|
case RESPONSE_48_BIT_WITH_BUSY: rt = Rsptyp::_48BIT_BUSY; break;
|
|
}
|
|
Xfertyp::Rsptyp::set(cmd, rt);
|
|
|
|
/* send command as soon as the host allows it */
|
|
if (!_wait_for_cmd_allowed()) { return false; }
|
|
write<Cmdarg>(command.arg);
|
|
write<Xfertyp>(cmd);
|
|
|
|
/* wait for completion */
|
|
return mb ? _wait_for_mbc_complete(r) : _wait_for_cmd_complete();
|
|
}
|
|
|
|
Sd_card::Card_info card_info() const { return _card_info; }
|
|
|
|
bool read_blocks(size_t const block_number, size_t const block_count,
|
|
char * out_buffer_phys)
|
|
{
|
|
PERR("block transfer without DMA not supported by now");
|
|
return false;
|
|
}
|
|
|
|
bool write_blocks(size_t block_number, size_t block_count,
|
|
char const *buffer_phys)
|
|
{
|
|
PERR("block transfer without DMA not supported by now");
|
|
return false;
|
|
}
|
|
|
|
bool read_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys)
|
|
{
|
|
if (!_prepare_dma_mbc(blk_cnt, buf_phys)) { return false; }
|
|
return issue_command(Sd_card::Read_multiple_block(blk_nr));
|
|
}
|
|
|
|
bool write_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys)
|
|
{
|
|
if (!_prepare_dma_mbc(blk_cnt, buf_phys)) { return false; }
|
|
return issue_command(Sd_card::Write_multiple_block(blk_nr));
|
|
}
|
|
};
|
|
|
|
#endif /* _DRIVERS__SD_CARD__SPEC__IMX53__ESDHCV2_H_ */
|