sd_card: i.MX6 support

The i.MX6 driver shares most of its code with the i.MX53 driver.

Ref #2206
This commit is contained in:
Martin Stein 2016-12-15 15:37:18 +01:00 committed by Norman Feske
parent 4ae4f7f605
commit 613f4171f3
16 changed files with 631 additions and 389 deletions

View File

@ -33,7 +33,7 @@ bool secure_irq(unsigned const i)
if (i == Board::EPIT_2_IRQ) return true;
if (i == Board::I2C_2_IRQ) return SECURE_I2C;
if (i == Board::I2C_3_IRQ) return SECURE_I2C;
if (i == Board::ESDHCV2_1_IRQ) return SECURE_ESDHC;
if (i == Board::SDHC_IRQ) return SECURE_ESDHC;
if (i >= Board::GPIO1_IRQL && i <= Board::GPIO4_IRQH) return SECURE_GPIO;
if (i >= Board::GPIO5_IRQL && i <= Board::GPIO7_IRQH) return SECURE_GPIO;
return false;

View File

@ -26,9 +26,9 @@ struct Imx53::Board_base
MMIO_BASE = 0x0,
MMIO_SIZE = 0x70000000,
ESDHCV2_1_IRQ = 1,
ESDHCV2_1_MMIO_BASE = 0x50004000,
ESDHCV2_1_MMIO_SIZE = 0x00004000,
SDHC_IRQ = 1,
SDHC_MMIO_BASE = 0x50004000,
SDHC_MMIO_SIZE = 0x00004000,
UART_1_IRQ = 31,
UART_1_MMIO_BASE = 0x53fbc000,

View File

@ -40,6 +40,11 @@ struct Genode::Board_base
UART_1_MMIO_BASE = 0x02020000,
UART_1_MMIO_SIZE = 0x00004000,
/* SD host controller */
SDHC_IRQ = 54,
SDHC_MMIO_BASE = 0x02190000,
SDHC_MMIO_SIZE = 0x00004000,
/* timer */
EPIT_2_IRQ = 89,
EPIT_2_MMIO_BASE = 0x020d4000,

View File

@ -1,4 +1,5 @@
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx53
SRC_CC += spec/imx53/adma2.cc
SRC_CC += spec/imx53/esdhcv2.cc
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx
SRC_CC += spec/imx/adma2.cc
SRC_CC += spec/imx/sdhc.cc
SRC_CC += spec/imx53/sdhc.cc
include $(REP_DIR)/lib/mk/sd_card_bench.inc

View File

@ -1,5 +1,6 @@
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx53
SRC_CC += spec/imx53/adma2.cc
SRC_CC += spec/imx53/esdhcv2.cc
SRC_CC += spec/imx53/main.cc
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx
SRC_CC += spec/imx/adma2.cc
SRC_CC += spec/imx/sdhc.cc
SRC_CC += spec/imx/main.cc
SRC_CC += spec/imx53/sdhc.cc
include $(REP_DIR)/lib/mk/sd_card.inc

View File

@ -0,0 +1,5 @@
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx
SRC_CC += spec/imx/adma2.cc
SRC_CC += spec/imx/sdhc.cc
SRC_CC += spec/imx6/sdhc.cc
include $(REP_DIR)/lib/mk/sd_card_bench.inc

View File

@ -0,0 +1,6 @@
INC_DIR += $(REP_DIR)/src/drivers/sd_card/spec/imx
SRC_CC += spec/imx/adma2.cc
SRC_CC += spec/imx/sdhc.cc
SRC_CC += spec/imx/main.cc
SRC_CC += spec/imx6/sdhc.cc
include $(REP_DIR)/lib/mk/sd_card.inc

View File

@ -1,19 +1,20 @@
/*
* \brief Imx53-specific implementation of the Block::Driver interface
* \brief Implementation of the Block::Driver interface
* \author Martin Stein
* \date 2015-02-04
*/
/*
* Copyright (C) 2012-2015 Genode Labs GmbH
* Copyright (C) 2012-2016 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__DRIVER_H_
#define _DRIVERS__SD_CARD__SPEC__IMX53__DRIVER_H_
#ifndef _DRIVER_H_
#define _DRIVER_H_
/* Genode includes */
#include <util/mmio.h>
#include <os/attached_io_mem_dataspace.h>
#include <base/log.h>
@ -23,7 +24,7 @@
#include <os/server.h>
/* local includes */
#include <esdhcv2.h>
#include <sdhc.h>
namespace Block {
using namespace Genode;
@ -43,8 +44,8 @@ class Block::Sdhci_driver : public Block::Driver
void usleep(unsigned us) { Timer::Connection::usleep(us); }
} _delayer;
Attached_io_mem_dataspace _esdhcv2_1_mmio;
Esdhcv2_controller _controller;
Attached_io_mem_dataspace _sdhc_mmio;
Sdhc _controller;
bool const _use_dma;
@ -52,10 +53,9 @@ class Block::Sdhci_driver : public Block::Driver
Sdhci_driver(Entrypoint &, bool use_dma)
:
_esdhcv2_1_mmio(Genode::Board_base::ESDHCV2_1_MMIO_BASE,
Genode::Board_base::ESDHCV2_1_MMIO_SIZE),
_controller((addr_t)_esdhcv2_1_mmio.local_addr<void>(),
Genode::Board_base::ESDHCV2_1_IRQ, _delayer, use_dma),
_sdhc_mmio(Board_base::SDHC_MMIO_BASE, Board_base::SDHC_MMIO_SIZE),
_controller((addr_t)_sdhc_mmio.local_addr<void>(),
Board_base::SDHC_IRQ, _delayer, use_dma),
_use_dma(use_dma)
{
Sd_card::Card_info const card_info = _controller.card_info();
@ -133,4 +133,4 @@ class Block::Sdhci_driver : public Block::Driver
return Genode::env()->ram_session()->free(c); }
};
#endif /* _DRIVERS__SD_CARD__SPEC__IMX53__DRIVER_H_ */
#endif /* _DRIVER_H_ */

View File

@ -1,24 +1,24 @@
/*
* \brief Freescale Enhanced Secured Digital Host Controller Version 2
* \brief Secured Digital Host Controller
* \author Martin Stein
* \date 2015-02-05
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
* Copyright (C) 2015-2016 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.
*/
/* local includes */
#include <esdhcv2.h>
#include <sdhc.h>
using namespace Sd_card;
using namespace Genode;
int Esdhcv2_controller::_wait_for_card_ready_mbw()
int Sdhc::_wait_for_card_ready_mbw()
{
/*
* Poll card status
@ -69,7 +69,7 @@ int Esdhcv2_controller::_wait_for_card_ready_mbw()
}
int Esdhcv2_controller::_stop_transmission_mbw()
int Sdhc::_stop_transmission()
{
/* write argument register */
write<Cmdarg>(0);
@ -81,9 +81,7 @@ int Esdhcv2_controller::_stop_transmission_mbw()
Xfertyp::Cccen::set(xfertyp, 1);
Xfertyp::Cicen::set(xfertyp, 1);
Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT_BUSY);
Xfertyp::Msbsel::set(xfertyp, 1);
Xfertyp::Bcen::set(xfertyp, 1);
Xfertyp::Dmaen::set(xfertyp, 1);
_stop_transmission_finish_xfertyp(xfertyp);
write<Xfertyp>(xfertyp);
/* wait for command completion */
@ -92,10 +90,10 @@ int Esdhcv2_controller::_stop_transmission_mbw()
}
int Esdhcv2_controller::_wait_for_cmd_complete_mb(bool const r)
int Sdhc::_wait_for_cmd_complete_mb(bool const r)
{
/*
* The ESDHC signals on multi-block transfers seem to be broken.
* 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
@ -120,29 +118,11 @@ int Esdhcv2_controller::_wait_for_cmd_complete_mb(bool const r)
}
/* acknowledge 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 stop such transfers
* manually.
*/
if (_stop_transmission_mbw()) { return -1; }
/*
* The manual termination of multi-block writes seems to leave
* the card in a busy state sometimes. This causes
* errors on subsequent commands. Thus, we have to synchronize
* manually with the card-internal state.
*/
if (_wait_for_card_ready_mbw()) { return -1; }
}
return 0;
return _wait_for_cmd_complete_mb_finish(r);
}
int Esdhcv2_controller::_wait_for_cmd_complete()
int Sdhc::_wait_for_cmd_complete()
{
/* wait for "Command Completion" signal and acknowledge it */
_wait_for_irq();
@ -155,54 +135,50 @@ int Esdhcv2_controller::_wait_for_cmd_complete()
}
bool Esdhcv2_controller::_issue_command(Command_base const & command)
bool Sdhc::_issue_command(Command_base const & command)
{
/* detect if command is a multi-block transfer and whether it reads */
bool const r = command.transfer == TRANSFER_READ;
bool const mb =
/* 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;
/* assemble command register value */
Xfertyp::access_t cmd = 0;
Xfertyp::Cmdinx::set(cmd, command.index);
if (command.transfer != 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.
*/
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);
/* set command index */
Xfertyp::access_t xfertyp = 0;
Xfertyp::Cmdinx::set(xfertyp, command.index);
/* send command as soon as the host allows it */
if (_wait_for_cmd_allowed()) { return false; }
/* 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);
write<Cmdarg>(command.arg);
write<Xfertyp>(cmd);
write<Xfertyp>(xfertyp);
/* wait for completion */
return mb ? !_wait_for_cmd_complete_mb(r) : !_wait_for_cmd_complete();
return multiblock ? !_wait_for_cmd_complete_mb(reading) :
!_wait_for_cmd_complete();
}
Cid Esdhcv2_controller::_read_cid()
Cid Sdhc::_read_cid()
{
Cid cid;
cid.raw_0 = read<Rsp136_0>();
@ -213,7 +189,7 @@ Cid Esdhcv2_controller::_read_cid()
}
Csd Esdhcv2_controller::_read_csd()
Csd Sdhc::_read_csd()
{
Csd csd;
csd.csd0 = read<Rsp136_0>();
@ -224,36 +200,35 @@ Csd Esdhcv2_controller::_read_csd()
}
unsigned Esdhcv2_controller::_read_rca()
unsigned Sdhc::_read_rca()
{
Cmdrsp0::access_t const rsp0 = read<Cmdrsp0>();
return Send_relative_addr::Response::Rca::get(rsp0);
}
bool Esdhcv2_controller::read_blocks(size_t, size_t, char *)
bool Sdhc::read_blocks(size_t, size_t, char *)
{
error("block transfer without DMA not supported by now");
return false;
}
bool Esdhcv2_controller::write_blocks(size_t, size_t, char const *)
bool Sdhc::write_blocks(size_t, size_t, char const *)
{
error("block transfer without DMA not supported by now");
return false;
}
bool Esdhcv2_controller::read_blocks_dma(size_t blk_nr, size_t blk_cnt,
addr_t buf_phys)
bool Sdhc::read_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys)
{
if (_prepare_dma_mb(blk_cnt, buf_phys)) { return false; }
return issue_command(Read_multiple_block(blk_nr));
}
bool Esdhcv2_controller::write_blocks_dma(size_t blk_nr, size_t blk_cnt,
bool Sdhc::write_blocks_dma(size_t blk_nr, size_t blk_cnt,
addr_t buf_phys)
{
if (_prepare_dma_mb(blk_cnt, buf_phys)) { return false; }
@ -261,15 +236,17 @@ bool Esdhcv2_controller::write_blocks_dma(size_t blk_nr, size_t blk_cnt,
}
Esdhcv2_controller::Esdhcv2_controller(addr_t const base, unsigned const irq,
Delayer & delayer, bool const use_dma)
Sdhc::Sdhc(addr_t const base,
unsigned const irq,
Delayer &delayer,
bool const use_dma)
:
Esdhcv2(base), _irq(irq), _delayer(delayer), _card_info(_init()),
Mmio(base), _irq(irq), _delayer(delayer), _card_info(_init()),
_use_dma(use_dma)
{ }
int Esdhcv2_controller::_prepare_dma_mb(size_t blk_cnt, addr_t buf_phys)
int Sdhc::_prepare_dma_mb(size_t blk_cnt, addr_t buf_phys)
{
/* write ADMA2 table to DMA */
size_t const req_size = blk_cnt * BLOCK_SIZE;
@ -283,14 +260,18 @@ int Esdhcv2_controller::_prepare_dma_mb(size_t blk_cnt, addr_t buf_phys)
}
int Esdhcv2_controller::_wait_for_cmd_allowed()
int Sdhc::_wait_for_cmd_allowed()
{
/*
* At least after multi-block writes 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.
* 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.
*/
if (!wait_for<Prsstat_lhw>(Prsstat_lhw::cmd_allowed(), _delayer)) {
if (!wait_for<Prsstat::Dla>(0, _delayer) ||
!wait_for<Prsstat::Sdstb>(1, _delayer) ||
!wait_for<Prsstat::Cihb>(0, _delayer) ||
!wait_for<Prsstat::Cdihb>(0, _delayer))
{
error("wait till issuing a new command is allowed timed out");
return -1;
}
@ -298,7 +279,7 @@ int Esdhcv2_controller::_wait_for_cmd_allowed()
}
void Esdhcv2_controller::_wait_for_irq()
void Sdhc::_wait_for_irq()
{
/* acknowledge IRQ first, to activate IRQ propagation initially */
_irq.ack_irq();
@ -306,7 +287,7 @@ void Esdhcv2_controller::_wait_for_irq()
}
Card_info Esdhcv2_controller::_init()
Card_info Sdhc::_init()
{
/* install IRQ signal */
_irq.sigh(_irq_rec.manage(&_irq_ctx));
@ -315,24 +296,22 @@ Card_info Esdhcv2_controller::_init()
if (_reset(_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"); }
if (!_supported_host_version(read<Hostver>())) {
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 ESDHC. For the i.MX53 ESDHCv2 we
* know that the capabilities fit our requirements.
* 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_DIV_512, _delayer);
_clock(CLOCK_INITIAL);
/*
* Initialize card
@ -405,7 +384,7 @@ Card_info Esdhcv2_controller::_init()
* checks (maybe read SSR/SCR, read switch, try frequencies) are
* necessary for that.
*/
_clock(CLOCK_DIV_8, _delayer);
_clock(CLOCK_OPERATIONAL);
/*
* Configure card and host to use 4 data signals
@ -427,10 +406,7 @@ Card_info Esdhcv2_controller::_init()
/* 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);
_watermark_level(wml);
write<Wml>(wml);
/* configure ADMA */
@ -444,30 +420,18 @@ Card_info Esdhcv2_controller::_init()
}
void Esdhcv2_controller::_detect_err(char const * const err)
void Sdhc::_detect_err(char const * const err)
{
error(err);
throw Detection_failed();
}
int Esdhcv2_controller::_reset(Delayer & delayer)
int Sdhc::_reset(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);
_reset_amendments();
/* wait for reset completion */
if (!wait_for<Sysctl::Rsta>(0, delayer)) {
@ -478,14 +442,14 @@ int Esdhcv2_controller::_reset(Delayer & delayer)
}
void Esdhcv2_controller::_disable_irqs()
void Sdhc::_disable_irqs()
{
write<Irqstaten>(0);
write<Irqsigen>(0);
}
void Esdhcv2_controller::_enable_irqs()
void Sdhc::_enable_irqs()
{
Irq::access_t irq = 0;
Irq::Cc::set(irq, 1);
@ -505,7 +469,7 @@ void Esdhcv2_controller::_enable_irqs()
}
void Esdhcv2_controller::_bus_width(Bus_width bus_width)
void Sdhc::_bus_width(Bus_width bus_width)
{
switch (bus_width) {
case BUS_WIDTH_1: write<Proctl::Dtw>(Proctl::Dtw::_1BIT); break;
@ -514,8 +478,9 @@ void Esdhcv2_controller::_bus_width(Bus_width bus_width)
}
void Esdhcv2_controller::_disable_clock()
void Sdhc::_disable_clock()
{
_disable_clock_preparation();
Sysctl::access_t sysctl = read<Sysctl>();
Sysctl::Ipgen::set(sysctl, 0);
Sysctl::Hcken::set(sysctl, 0);
@ -526,13 +491,17 @@ void Esdhcv2_controller::_disable_clock()
}
void Esdhcv2_controller::_enable_clock(Clock_divider divider, Delayer &delayer)
void Sdhc::_enable_clock(Clock_divider divider)
{
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_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);
@ -543,13 +512,14 @@ void Esdhcv2_controller::_enable_clock(Clock_divider divider, Delayer &delayer)
break;
}
write<Sysctl>(sysctl);
delayer.usleep(1000);
_enable_clock_finish();
_delayer.usleep(1000);
}
void Esdhcv2_controller::_clock(enum Clock_divider divider, Delayer &delayer)
void Sdhc::_clock(Clock clock)
{
wait_for<Prsstat::Sdstb>(1, _delayer);
_disable_clock();
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_27);
_enable_clock(divider, delayer);
_clock_finish(clock);
}

View File

@ -0,0 +1,261 @@
/*
* \brief Secured Digital Host Controller
* \author Martin Stein
* \date 2015-02-05
*/
/*
* Copyright (C) 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 _SDHC_H_
#define _SDHC_H_
/* Genode includes */
#include <util/mmio.h>
#include <irq_session/connection.h>
#include <base/sleep.h>
/* local includes */
#include <sd_card.h>
#include <adma2.h>
namespace Genode { class Sdhc; }
class Genode::Sdhc : public Sd_card::Host_controller, public Mmio
{
private:
struct Blkattr : Register<0x4, 32>
{
struct Blksize : Bitfield<0, 13> { };
struct Blkcnt : Bitfield<16, 16> { };
};
template <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 : Bitset_2<Cmdrsp3::Rsp136_0_8, Cmdrsp0::Rsp136_8_24> { };
struct Rsp136_1 : Bitset_2<Cmdrsp0::Rsp136_0_8, Cmdrsp1::Rsp136_8_24> { };
struct Rsp136_2 : Bitset_2<Cmdrsp1::Rsp136_0_8, Cmdrsp2::Rsp136_8_24> { };
struct Rsp136_3 : Bitset_2<Cmdrsp2::Rsp136_0_8, Cmdrsp3::Rsp136_8_24> { };
template <off_t OFFSET>
struct Xfertyp_base : Register<OFFSET, 32>
{
struct Dmaen : Register<OFFSET, 32>::template Bitfield<0, 1> { };
struct Bcen : Register<OFFSET, 32>::template Bitfield<1, 1> { };
struct Ac12en : Register<OFFSET, 32>::template Bitfield<2, 1> { };
struct Dtdsel : Register<OFFSET, 32>::template Bitfield<4, 1>
{
enum { WRITE = 0, READ = 1, };
};
struct Msbsel : Register<OFFSET, 32>::template Bitfield<5, 1> { };
};
struct Mixctrl : Xfertyp_base<0x48>
{
struct Ddren : Bitfield<3, 1> { };
struct Nibblepos : Bitfield<6, 1> { };
struct Ac23en : Bitfield<7, 1> { };
struct Always_ones : Bitfield<31, 1> { };
};
struct Xfertyp : Xfertyp_base<0xc>
{
struct Rsptyp : Bitfield<16, 2>
{
enum {
_0BIT = 0,
_136BIT = 1,
_48BIT = 2,
_48BIT_BUSY = 3,
};
};
struct Cccen : Bitfield<19, 1> { };
struct Cicen : Bitfield<20, 1> { };
struct Dpsel : Bitfield<21, 1> { };
struct Cmdtyp : Bitfield<22, 2>
{
enum { ABORT_CMD12 = 3 };
};
struct Cmdinx : Bitfield<24, 6> { };
};
struct Prsstat : Register<0x24, 32>
{
struct Cihb : Bitfield<0, 1> { };
struct Cdihb : Bitfield<1, 1> { };
struct Dla : Bitfield<2, 1> { };
struct Sdstb : Bitfield<3, 1> { };
};
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_28 = 0xf,
SDCLK_TIMES_2_POW_27 = 0xe,
SDCLK_TIMES_2_POW_13 = 0x0,
};
};
struct Ipp_rst_n : Bitfield<23, 1> { };
struct Rsta : Bitfield<24, 1> { };
struct Rstc : Bitfield<25, 1> { };
struct Rstd : Bitfield<26, 1> { };
};
template <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<3, 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> { };
};
struct Vendspec : Register<0xc0, 32>
{
struct Frc_sdclk_on : Bitfield<8, 1> { };
};
enum { BLOCK_SIZE = 512 };
enum Bus_width { BUS_WIDTH_1, BUS_WIDTH_4 };
enum Clock { CLOCK_INITIAL, CLOCK_OPERATIONAL };
enum Clock_divider { CLOCK_DIV_4, CLOCK_DIV_8, CLOCK_DIV_512 };
Irq_connection _irq;
Signal_receiver _irq_rec;
Signal_context _irq_ctx;
Delayer & _delayer;
Sd_card::Card_info _card_info;
bool const _use_dma;
Adma2::Table _adma2_table;
static bool _supported_host_version(Hostver::access_t hostver);
static void _watermark_level(Wml::access_t &wml);
void _detect_err(char const * const err);
void _disable_irqs();
void _enable_irqs();
void _bus_width(Bus_width bus_width);
void _disable_clock();
void _disable_clock_preparation();
void _enable_clock(Clock_divider divider);
void _enable_clock_finish();
void _clock(Clock clock);
void _clock_finish(Clock clock);
void _wait_for_irq();
int _reset(Delayer & delayer);
void _reset_amendments();
int _wait_for_cmd_allowed();
int _wait_for_cmd_complete();
int _wait_for_card_ready_mbw();
int _stop_transmission();
void _stop_transmission_finish_xfertyp(Xfertyp::access_t &xfertyp);
int _wait_for_cmd_complete_mb(bool const r);
int _wait_for_cmd_complete_mb_finish(bool const reading);
int _prepare_dma_mb(size_t blk_cnt, addr_t buf_phys);
bool _issue_cmd_finish_xfertyp(Xfertyp::access_t &xfertyp,
bool const transfer,
bool const multiblock,
bool const reading);
Sd_card::Card_info _init();
/****************************************
** Sd_card::Host_controller interface **
****************************************/
Sd_card::Cid _read_cid();
Sd_card::Csd _read_csd();
unsigned _read_rca();
bool _issue_command(Sd_card::Command_base const & command);
public:
/**
* Constructor
*
* \param 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
*/
Sdhc(addr_t const base,
unsigned const irq,
Delayer &delayer,
bool const use_dma);
~Sdhc() { _irq_rec.dissolve(&_irq_ctx); }
/****************************************
** Sd_card::Host_controller interface **
****************************************/
bool read_blocks(size_t, size_t, char *);
bool write_blocks(size_t, size_t, char const *);
bool read_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys);
bool write_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys);
Sd_card::Card_info card_info() const { return _card_info; }
};
#endif /* _SDHC_H_ */

View File

@ -1,243 +0,0 @@
/*
* \brief Freescale Enhanced Secured Digital Host Controller Version 2
* \author Martin Stein
* \date 2015-02-05
*/
/*
* Copyright (C) 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 _ESDHCV2_H_
#define _ESDHCV2_H_
/* Genode includes */
#include <util/mmio.h>
#include <irq_session/connection.h>
#include <base/sleep.h>
/* local includes */
#include <sd_card.h>
#include <adma2.h>
namespace Genode
{
struct Esdhcv2;
class Esdhcv2_controller;
}
/**
* MMIO structure of a Freescale ESDHCv2
*/
struct Genode::Esdhcv2 : Mmio
{
struct Blkattr : Register<0x4, 32>
{
struct Blksize : Bitfield<0, 13> { };
struct Blkcnt : Bitfield<16, 16> { };
};
template <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 : Bitset_2<Cmdrsp3::Rsp136_0_8, Cmdrsp0::Rsp136_8_24> { };
struct Rsp136_1 : Bitset_2<Cmdrsp0::Rsp136_0_8, Cmdrsp1::Rsp136_8_24> { };
struct Rsp136_2 : Bitset_2<Cmdrsp1::Rsp136_0_8, Cmdrsp2::Rsp136_8_24> { };
struct Rsp136_3 : 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 Cccen : Bitfield<19, 1> { };
struct Cicen : Bitfield<20, 1> { };
struct Dpsel : Bitfield<21, 1> { };
struct Cmdtyp : Bitfield<22, 2>
{
enum { ABORT_CMD12 = 3 };
};
struct Cmdinx : Bitfield<24, 6> { };
};
struct Prsstat : Register<0x24, 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 <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<3, 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> { };
};
Esdhcv2(addr_t const mmio_base) : Mmio(mmio_base) { }
};
/**
* Implementation of the SD host-controller interface for the ESDHCv2
*/
struct Genode::Esdhcv2_controller
:
private Esdhcv2, public Sd_card::Host_controller
{
private:
enum {
BLOCK_SIZE = 512,
WATERMARK_WORDS = 16,
BURST_WORDS = 8,
};
enum Bus_width { BUS_WIDTH_1, BUS_WIDTH_4 };
enum Clock_divider { CLOCK_DIV_8, CLOCK_DIV_512 };
Irq_connection _irq;
Signal_receiver _irq_rec;
Signal_context _irq_ctx;
Delayer & _delayer;
Sd_card::Card_info _card_info;
bool const _use_dma;
Adma2::Table _adma2_table;
void _detect_err(char const * const err);
void _disable_irqs();
void _enable_irqs();
void _bus_width(Bus_width bus_width);
void _disable_clock();
void _enable_clock(Clock_divider divider, Delayer &delayer);
void _clock(enum Clock_divider divider, Delayer &delayer);
void _wait_for_irq();
int _reset(Delayer & delayer);
int _wait_for_cmd_allowed();
int _wait_for_cmd_complete();
int _wait_for_card_ready_mbw();
int _stop_transmission_mbw();
int _wait_for_cmd_complete_mb(bool const r);
int _prepare_dma_mb(size_t blk_cnt, addr_t buf_phys);
Sd_card::Card_info _init();
/****************************************
** Sd_card::Host_controller interface **
****************************************/
Sd_card::Cid _read_cid();
Sd_card::Csd _read_csd();
unsigned _read_rca();
bool _issue_command(Sd_card::Command_base const & command);
public:
/**
* Constructor
*
* \param 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 base, unsigned const irq,
Delayer & delayer, bool const use_dma);
~Esdhcv2_controller() { _irq_rec.dissolve(&_irq_ctx); }
/****************************************
** Sd_card::Host_controller interface **
****************************************/
bool read_blocks(size_t, size_t, char *);
bool write_blocks(size_t, size_t, char const *);
bool read_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys);
bool write_blocks_dma(size_t blk_nr, size_t blk_cnt, addr_t buf_phys);
Sd_card::Card_info card_info() const { return _card_info; }
};
#endif /* _ESDHCV2_H_ */

View File

@ -0,0 +1,120 @@
/*
* \brief Secured Digital Host Controller
* \author Martin Stein
* \date 2016-12-13
*/
/*
* Copyright (C) 2016 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.
*/
/* local includes */
#include <sdhc.h>
using namespace Sd_card;
using namespace Genode;
void Sdhc::_stop_transmission_finish_xfertyp(Xfertyp::access_t &xfertyp)
{
Xfertyp::Msbsel::set(xfertyp, 1);
Xfertyp::Bcen::set(xfertyp, 1);
Xfertyp::Dmaen::set(xfertyp, 1);
}
int Sdhc::_wait_for_cmd_complete_mb_finish(bool const reading)
{
if (reading) { return 0; }
/*
* 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 stop such transfers
* manually.
*/
if (_stop_transmission()) { return -1; }
/*
* The manual termination of multi-block writes seems to leave
* the card in a busy state sometimes. This causes
* errors on subsequent commands. Thus, we have to synchronize
* manually with the card-internal state.
*/
return _wait_for_card_ready_mbw() ? -1 : 0;
}
bool Sdhc::_issue_cmd_finish_xfertyp(Xfertyp::access_t &xfertyp,
bool const transfer,
bool const multiblock,
bool const reading)
{
if (transfer) {
Xfertyp::Bcen::set(xfertyp, 1);
Xfertyp::Msbsel::set(xfertyp, 1);
if (multiblock) {
/*
* The "Auto Command 12" feature of the ESDHC seems to be
* broken for multi-block writes as it causes command-
* timeout errors sometimes.
*/
if (reading) {
Xfertyp::Ac12en::set(xfertyp, 1); }
if (_use_dma) {
Xfertyp::Dmaen::set(xfertyp, 1); }
}
Xfertyp::Dtdsel::set(xfertyp,
reading ? Xfertyp::Dtdsel::READ : Xfertyp::Dtdsel::WRITE);
}
return _wait_for_cmd_allowed() ? false : true;
}
bool Sdhc::_supported_host_version(Hostver::access_t hostver)
{
return Hostver::Vvn::get(hostver) == 18 &&
Hostver::Svn::get(hostver) == 1;
}
void Sdhc::_watermark_level(Wml::access_t &wml)
{
Wml::Wr_wml::set(wml, 16);
Wml::Wr_brst_len::set(wml, 8);
}
void Sdhc::_reset_amendments()
{
/*
* 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);
}
void Sdhc::_clock_finish(Clock clock)
{
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_27);
switch (clock) {
case CLOCK_INITIAL: _enable_clock(CLOCK_DIV_512); break;
case CLOCK_OPERATIONAL: _enable_clock(CLOCK_DIV_8); break; }
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_27);
}
void Sdhc::_disable_clock_preparation() { }
void Sdhc::_enable_clock_finish() { }

View File

@ -0,0 +1,116 @@
/*
* \brief Secured Digital Host Controller
* \author Martin Stein
* \date 2016-12-13
*/
/*
* Copyright (C) 2016 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.
*/
/* local includes */
#include <sdhc.h>
using namespace Sd_card;
using namespace Genode;
void Sdhc::_stop_transmission_finish_xfertyp(Xfertyp::access_t &xfertyp)
{
Mixctrl::access_t mixctrl = read<Mixctrl>();
Mixctrl::Dmaen::set(mixctrl, 1);
Mixctrl::Bcen::set(mixctrl, 1);
Mixctrl::Ac12en::set(mixctrl, 0);
Mixctrl::Ddren::set(mixctrl, 0);
Mixctrl::Dtdsel::set(mixctrl, Mixctrl::Dtdsel::READ);
Mixctrl::Msbsel::set(mixctrl, 1);
Mixctrl::Nibblepos::set(mixctrl, 0);
Mixctrl::Ac23en::set(mixctrl, 0);
write<Mixctrl>(mixctrl);
}
int Sdhc::_wait_for_cmd_complete_mb_finish(bool const reading)
{
/* we can't use the "Auto Command 12" feature as it does not work */
return _stop_transmission() ? -1 : 0;
}
bool Sdhc::_issue_cmd_finish_xfertyp(Xfertyp::access_t &,
bool const transfer,
bool const multiblock,
bool const reading)
{
Mixctrl::access_t mixctrl = read<Mixctrl>();
Mixctrl::Dmaen ::set(mixctrl, transfer && multiblock && _use_dma);
Mixctrl::Bcen ::set(mixctrl, transfer);
Mixctrl::Ac12en ::set(mixctrl, 0);
Mixctrl::Msbsel ::set(mixctrl, transfer);
Mixctrl::Ddren ::set(mixctrl, 0);
Mixctrl::Nibblepos::set(mixctrl, 0);
Mixctrl::Ac23en ::set(mixctrl, 0);
Mixctrl::Dtdsel ::set(mixctrl, reading ? Mixctrl::Dtdsel::READ :
Mixctrl::Dtdsel::WRITE);
if (_wait_for_cmd_allowed()) {
return false; }
write<Mixctrl>(mixctrl);
return true;
}
bool Sdhc::_supported_host_version(Hostver::access_t hostver)
{
return Hostver::Vvn::get(hostver) == 0 &&
Hostver::Svn::get(hostver) == 3;
}
void Sdhc::_watermark_level(Wml::access_t &wml)
{
Wml::Wr_wml::set(wml, 64);
Wml::Wr_brst_len::set(wml, 16);
}
void Sdhc::_reset_amendments()
{
/* the USDHC doesn't reset the Mixer Control register automatically */
Mixctrl::access_t mixctrl = read<Mixctrl>();
Mixctrl::Dmaen::set(mixctrl, 0);
Mixctrl::Bcen::set(mixctrl, 0);
Mixctrl::Ac12en::set(mixctrl, 0);
Mixctrl::Ddren::set(mixctrl, 0);
Mixctrl::Dtdsel::set(mixctrl, 0);
Mixctrl::Msbsel::set(mixctrl, 0);
Mixctrl::Nibblepos::set(mixctrl, 0);
Mixctrl::Ac23en::set(mixctrl, 0);
Mixctrl::Always_ones::set(mixctrl, 1);
write<Mixctrl>(mixctrl);
}
void Sdhc::_clock_finish(Clock clock)
{
switch (clock) {
case CLOCK_INITIAL:
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_13);
_enable_clock(CLOCK_DIV_512);
break;
case CLOCK_OPERATIONAL:
write<Sysctl::Dtocv>(Sysctl::Dtocv::SDCLK_TIMES_2_POW_28);
write<Sysctl::Ipp_rst_n>(0);
_enable_clock(CLOCK_DIV_4);
break;
}
}
void Sdhc::_disable_clock_preparation() { write<Vendspec::Frc_sdclk_on>(0); }
void Sdhc::_enable_clock_finish() { write<Vendspec::Frc_sdclk_on>(0); }