233 lines
5.4 KiB
C++
233 lines
5.4 KiB
C++
/*
|
|
* \brief PL180-specific implementation of the Block::Driver interface
|
|
* \author Christian Helmuth
|
|
* \author Martin Stein
|
|
* \date 2011-05-19
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2011-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;
|
|
|
|
|
|
void Driver::_write_command(unsigned cmd_index, bool resp)
|
|
{
|
|
enum Bits {
|
|
CmdIndex = 0x3f,
|
|
Response = 1 << 6,
|
|
Enable = 1 << 10
|
|
};
|
|
cmd_index = (cmd_index & CmdIndex) | Enable;
|
|
cmd_index |= (resp ? Response : 0);
|
|
|
|
_write_reg(Command, cmd_index);
|
|
|
|
while (!(_read_reg(Status) & (CmdRespEnd | CmdSent))) ;
|
|
}
|
|
|
|
|
|
void Driver::_request(unsigned char cmd,
|
|
unsigned *out_resp)
|
|
{
|
|
_write_reg(Argument, 0);
|
|
_write_command(cmd, (out_resp != 0));
|
|
if (out_resp)
|
|
*out_resp = _read_reg(Response0);
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
void Driver::_request(unsigned char cmd,
|
|
unsigned arg,
|
|
unsigned *out_resp)
|
|
{
|
|
_write_reg(Argument, arg);
|
|
_write_command(cmd, (out_resp != 0));
|
|
if (out_resp)
|
|
*out_resp = _read_reg(Response0);
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
void Driver::_read_request(unsigned char cmd,
|
|
unsigned arg,
|
|
unsigned length,
|
|
unsigned *out_resp)
|
|
{
|
|
/*
|
|
* FIXME on real hardware the blocksize must be written into
|
|
* DataCtrl:BlockSize.
|
|
*/
|
|
enum { CTRL_ENABLE = 0x01, CTRL_READ = 0x02 };
|
|
|
|
_write_reg(DataLength, length);
|
|
_write_reg(DataCtrl, CTRL_ENABLE | CTRL_READ);
|
|
|
|
_write_reg(Argument, arg);
|
|
_write_command(cmd, (out_resp != 0));
|
|
if (out_resp)
|
|
*out_resp = _read_reg(Response0);
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
void Driver::_write_request(unsigned char cmd,
|
|
unsigned arg,
|
|
unsigned length,
|
|
unsigned *out_resp)
|
|
{
|
|
/*
|
|
* FIXME on real hardware the blocksize must be written into
|
|
* DataCtrl:BlockSize.
|
|
*/
|
|
enum { CTRL_ENABLE = 0x01 };
|
|
|
|
_write_reg(DataLength, length);
|
|
_write_reg(DataCtrl, CTRL_ENABLE);
|
|
|
|
_write_reg(Argument, arg);
|
|
_write_command(cmd, (out_resp != 0));
|
|
if (out_resp)
|
|
*out_resp = _read_reg(Response0);
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
void Driver::_read_data(unsigned length,
|
|
char *out_buffer)
|
|
{
|
|
unsigned *buf = reinterpret_cast<unsigned *>(out_buffer);
|
|
for (unsigned count = 0; count < length / 4; ) {
|
|
/*
|
|
* The FIFO contains at least 'remainder - FifoCnt' words.
|
|
*/
|
|
int chunk = length / 4 - count - _read_reg(FifoCnt);
|
|
count += chunk;
|
|
for ( ; chunk > 0; --chunk)
|
|
buf[count - chunk] = _read_reg(FIFO);
|
|
}
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
void Driver::_write_data(unsigned length,
|
|
char const *buffer)
|
|
{
|
|
enum { FIFO_SIZE = 16 };
|
|
|
|
unsigned const *buf = reinterpret_cast<unsigned const *>(buffer);
|
|
for (unsigned count = 0; count < length / 4; ) {
|
|
uint32_t status;
|
|
while (!((status = _read_reg(Status)) & TxFifoHalfEmpty)) ;
|
|
|
|
int chunk = (status & TxFifoEmpty) ? FIFO_SIZE : FIFO_SIZE / 2;
|
|
count += chunk;
|
|
for ( ; chunk > 0; --chunk)
|
|
_write_reg(FIFO, buf[count - chunk]);
|
|
}
|
|
_clear_status();
|
|
}
|
|
|
|
|
|
Driver::Driver(Env &env)
|
|
: Block::Driver(env.ram()), Attached_mmio(env, PL180_PHYS, PL180_SIZE), _timer(env)
|
|
{
|
|
enum { POWER_UP = 2, POWER_ON = 3 };
|
|
|
|
_write_reg(Power, POWER_UP);
|
|
_timer.msleep(10);
|
|
_write_reg(Power, POWER_ON);
|
|
_timer.msleep(10);
|
|
_clear_status();
|
|
|
|
/* CMD0: go idle state */
|
|
_request(0, 0);
|
|
|
|
/*
|
|
* CMD8: send interface condition
|
|
*
|
|
* XXX only one hard-coded value currently.
|
|
*/
|
|
unsigned resp;
|
|
_request(8, 0x1aa, &resp);
|
|
|
|
/*
|
|
* ACMD41: card send operating condition
|
|
*
|
|
* This is an application-specific command and, therefore, consists
|
|
* of prefix command CMD55 + CMD41.
|
|
*/
|
|
_request(55, 0, &resp);
|
|
_request(41, 0x4000, &resp);
|
|
|
|
/* CMD2: all send card identification (CID) */
|
|
_request(2, &resp);
|
|
|
|
/* CMD3: send relative card address (RCA) */
|
|
_request(3, &resp);
|
|
unsigned short rca = resp >> 16;
|
|
|
|
/*
|
|
* Now, the card is in transfer mode...
|
|
*/
|
|
|
|
/* CMD7: select card */
|
|
_request(7, rca << 16, &resp);
|
|
}
|
|
|
|
|
|
void Driver::read(Block::sector_t block_number,
|
|
size_t block_count,
|
|
char *buffer,
|
|
Block::Packet_descriptor &packet)
|
|
{
|
|
unsigned resp;
|
|
unsigned length = _block_size;
|
|
|
|
for (size_t i = 0; i < block_count; ++i) {
|
|
/*
|
|
* CMD17: read single block
|
|
*
|
|
* SDSC cards use a byte address as argument while SDHC/SDSC uses a
|
|
* block address here.
|
|
*/
|
|
_read_request(17, (block_number + i) * _block_size,
|
|
length, &resp);
|
|
_read_data(length, buffer + (i * _block_size));
|
|
}
|
|
ack_packet(packet);
|
|
}
|
|
|
|
|
|
void Driver::write(Block::sector_t block_number,
|
|
size_t block_count,
|
|
char const *buffer,
|
|
Block::Packet_descriptor &packet)
|
|
{
|
|
unsigned resp;
|
|
unsigned length = _block_size;
|
|
|
|
for (size_t i = 0; i < block_count; ++i) {
|
|
/*
|
|
* CMD24: write single block
|
|
*
|
|
* SDSC cards use a byte address as argument while SDHC/SDSC uses a
|
|
* block address here.
|
|
*/
|
|
_write_request(24, (block_number + i) * _block_size,
|
|
length, &resp);
|
|
_write_data(length, buffer + (i * _block_size));
|
|
}
|
|
ack_packet(packet);
|
|
}
|
|
|