1
0
mirror of git://projects.qi-hardware.com/iris.git synced 2024-06-28 23:15:26 +03:00
iris/source/sdmmc.ccp
2013-05-12 09:46:11 -04:00

591 lines
19 KiB
COBOL

#pypp 0
// Iris: micro-kernel for a capability-based operating system.
// source/sd+mmc.ccp: sd+mmc driver.
// Copyright 2009 Bas Wijnen <wijnen@debian.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "devices.hh"
#define ARCH
#include "arch.hh"
class Mmc:
public:
enum Response_type:
NONE = MSC_CMDAT_RESPONSE_NONE
RD_DATA = MSC_CMDAT_RESPONSE_R1 | MSC_CMDAT_DATA_EN | MSC_CMDAT_READ
WR_DATA = MSC_CMDAT_RESPONSE_R1 | MSC_CMDAT_DATA_EN | MSC_CMDAT_WRITE
R1 = MSC_CMDAT_RESPONSE_R1
R1B = MSC_CMDAT_RESPONSE_R1 | MSC_CMDAT_BUSY
R2 = MSC_CMDAT_RESPONSE_R2
R3 = MSC_CMDAT_RESPONSE_R3
R4 = MSC_CMDAT_RESPONSE_R4
R5 = MSC_CMDAT_RESPONSE_R5
R6 = MSC_CMDAT_RESPONSE_R6
R7 = MSC_CMDAT_RESPONSE_R7
static unsigned const POWER_PORT = 3
static unsigned const POWER_PIN = 2
struct CID:
unsigned mid
char oid[2]
char pnm[5]
unsigned prv
unsigned psn
unsigned year
unsigned month
struct CSD:
unsigned c_size
unsigned c_size_mult
unsigned read_bl_len, write_bl_len
bool copy
bool perm_write_protect
bool tmp_write_protect
bool send (unsigned cmd, unsigned arg, Response_type response_type, unsigned *response = NULL)
void check_sd ()
void check_sdmem ()
void check_mmc ()
public:
void reset ()
void detect ()
void release ()
void interrupt ()
CID const &get_cid ():
return cid
unsigned get_num_blocks ():
return num_blocks
unsigned get_read_block_size ():
return read_block_size
unsigned get_block_bits ():
return hc ? 9 : csd.read_bl_len > csd.write_bl_len ? csd.read_bl_len : csd.write_bl_len
void write_page (Iris::Page page, Iris::Num address, unsigned size, unsigned offset)
void read_page (Iris::Page page, Iris::Num address, unsigned size, unsigned offset)
void wait_write ()
void add_cb (Iris::Listitem item):
cb_list.add_item (item)
private:
void set_block (unsigned block)
unsigned current_block_num
bool dirty
unsigned *current_block
unsigned rca
bool have_sdmem, have_io
bool hc
CID cid
CSD csd
unsigned num_blocks, read_block_size
Iris::Page buffer_page
Iris::List cb_list
static unsigned const buffer = 0x15000
bool Mmc::send (unsigned cmd, unsigned arg, Response_type response_type, unsigned *response):
MSC_CMD = cmd
MSC_ARG = arg
MSC_CMDAT = response_type
MSC_IMASK = ~MSC_IMASK_END_CMD_RES
Iris::register_interrupt (IRQ_MSC)
msc_start_op ()
Iris::wait_for_interrupt ()
MSC_IMASK = ~0
//kdebug ("cmd: ")
//kdebug_num (cmd)
unsigned stat = MSC_STAT
//kdebug (", stat: ")
//kdebug_num (stat)
//kdebug ("\n")
if stat & MSC_STAT_CRC_RES_ERR:
Iris::panic (0, "crc error in mmc response")
return false
if stat & MSC_STAT_TIME_OUT_RES:
kdebug ("time out waiting for mmc response\n")
MSC_IREG = MSC_IREG_END_CMD_RES
return false
if response_type == R2:
unsigned d = MSC_RES
if d >> 8 != 0x3f:
Iris::panic (d, "invalid r2 response")
if cmd == 3:
// Read out result.
cid.mid = d & 0xff
d = MSC_RES
cid.oid[0] = d >> 8
cid.oid[1] = d & 0xff
d = MSC_RES
cid.pnm[0] = d >> 8
cid.pnm[1] = d & 0xff
d = MSC_RES
cid.pnm[2] = d >> 8
cid.pnm[3] = d & 0xff
d = MSC_RES
cid.pnm[4] = d >> 8
cid.prv = d & 0xff
d = MSC_RES
cid.psn = d << 16
d = MSC_RES
cid.psn |= d
d = MSC_RES
cid.year = 2000 + (d >> 4 & 0xff)
cid.month = d & 0xf
#if 1
Iris::debug ("CID: mid=%x, oid=%x %x, pnm=%x %x %x %x %x, prv=%x, psn=%x, year=%x, month=%x\n", cid.mid, cid.oid[0], cid.oid[1], cid.pnm[0], cid.pnm[1], cid.pnm[2], cid.pnm[3], cid.pnm[4], cid.prv, cid.psn, cid.year, cid.month)
#endif
else:
// Header (8) 1.0 1.0
// Read out csd.
// Ignore csd_structure. 2 (+ 6) 1.0 2.0 ***
d = MSC_RES
// Ignore taac and nsac. 8 + 8 2.0 4.0 ***
d = MSC_RES
// Ignore tran_speed, ccc. 8 + 8/12 2.0 6.0 ***
d = MSC_RES
// Ignore rest of ccc. 4/12 0.4 6.4
// 4 0.4 7.0
csd.read_bl_len = (d >> 8) & 0xf
// Ignore read_bl_partial, write_blk_misalign, read_blk_misalign, dsr_imp. 1 + 1 + 1 + 1 (+ 2) 0.6 7.6
// 2/12 0.2 8.0 ***
csd.c_size = (d & 0x0003) << 10
d = MSC_RES
// 10/12 1.2 9.2
csd.c_size |= d >> 6
// Ignore vdd_r_cur_min, vdd_r_cur_max. 3 + 3 0.6 10.0 ***
d = MSC_RES
// Ignore vdd_w_cur_min, vdd_w_cur_max. 3 + 3 0.6 10.6
// 3 0.3 11.1
csd.c_size_mult = (d >> 7) & 0x7
// Ignore erase_blk_enable, sector_size. 1 + 6/7 0.7 12.0 ***
d = MSC_RES
// Ignore rest of sector_size, wp_grp_size, wp_grp_enable, r2w_factor. 1/7 + 7 + 1 (+ 2) + 3 1.6 13.6
// 2/4 0.4 14.0 ***
csd.write_bl_len = (d << 2) & 0xc
d = MSC_RES
// 2/4 0.2 14.2
csd.write_bl_len |= (d >> 14) & 0x3
// Ignore write_bl_partial, file_format_grp. 1 (+ 5) + 1 0.7 15.1
// 1 0.1 15.2
csd.copy = d & 0x40
// 1 0.1 15.3
csd.perm_write_protect = d & 0x20
// 1 0.1 15.4
csd.tmp_write_protect = d & 0x10
// Ignore file_format. 2 (+ 2) 0.4 16.0 ***
read_block_size = hc ? 512 : 1 << csd.read_bl_len
num_blocks = (csd.c_size + 1) << (csd.c_size_mult + 2)
if hc:
if csd.read_bl_len < 9:
num_blocks >>= 9 - csd.read_bl_len
else:
num_blocks <<= csd.read_bl_len - 9
#if 1
Iris::debug ("CSD: size=%x<<%x, r/w len=%x/%x, %s, %s, %s\n", csd.c_size, csd.c_size_mult, csd.read_bl_len, csd.write_bl_len, csd.copy ? "copy" : "no copy", csd.perm_write_protect ? "fixed write protect" : "no fixed write protect", csd.tmp_write_protect ? "write protect" : "no write protect")
#endif
unsigned c_size
unsigned c_size_mult
unsigned read_bl_len, write_bl_len
bool copy
bool perm_write_protect
bool tmp_write_protect
else if response_type != NONE:
unsigned r = MSC_RES
if response_type == R3:
if r >> 8 != 0x3f:
Iris::panic (r, "r3 response was not 3f")
else if r >> 8 != cmd:
kdebug ("stat: ")
kdebug_num (MSC_STAT)
kdebug ("; response: ")
kdebug_num (r)
kdebug ("; cmd: ")
kdebug_num (cmd)
Iris::panic (r, "response doesn't match command")
r <<= 24
r |= MSC_RES << 8
r |= MSC_RES & 0xff
if response:
*response = r
else:
//kdebug ("extra response fifo read: ")
//for unsigned i = 0; i < 9; ++i:
//kdebug (" ")
//kdebug_num (MSC_RES, 4)
//kdebug ("\n")
MSC_IREG = MSC_IREG_END_CMD_RES
return true
void Mmc::reset ():
current_block_num = ~0
dirty = false
cb_list = Iris::my_memory.create_list ()
current_block = (unsigned *)(buffer + PAGE_SIZE)
// Create a buffer to use for data transfer.
buffer_page = Iris::my_memory.create_page ()
Iris::my_memory.map (buffer_page, buffer)
// Reset all state, by faking a release event.
release ()
// Enable 25 MHz clock to msc.
CPM_MSCCDR = 13
cpm_start_msc ()
// Enable msc pins.
gpio_as_msc ()
// Disable power to card.
gpio_as_gpio (POWER_PORT, 1 << POWER_PIN)
gpio_as_output (POWER_PORT, 1 << POWER_PIN)
gpio_disable_pull (POWER_PORT, 1 << POWER_PIN)
gpio_set (POWER_PORT, 1 << POWER_PIN)
// Stop the clock.
MSC_STRPCL = MSC_STRPCL_CLOCK_CONTROL_STOP
while MSC_STAT & MSC_STAT_CLK_EN:
//kdebug (",")
Iris::sleep (1)
// Reset controller and inserted devices.
MSC_STRPCL = MSC_STRPCL_RESET
while MSC_STAT & MSC_STAT_IS_RESETTING:
//kdebug (":")
Iris::sleep (1)
// Initialize registers.
MSC_CLKRT = MSC_CLKRT_CLK_RATE_DIV_1
MSC_RESTO = 64
MSC_RDTO = ~0
MSC_BLKLEN = 0x200
MSC_NOB = 0
MSC_IREG = ~0
MSC_IMASK = ~(MSC_IMASK_END_CMD_RES | MSC_IMASK_RXFIFO_RD_REQ)
MSC_ARG = 0
// Start the clock.
MSC_STRPCL = MSC_STRPCL_CLOCK_CONTROL_START
// Set cards, if any, to idle.
send (0, 0, NONE)
// Reset SDIO device, if any. Don't do this, because it breaks for some reason.
//send (52, 0x88000c08, R5)
void Mmc::check_mmc ():
//kdebug ("checking mmc\n")
// 1. SEND CMD1 (SEND_OP_CMD) TO VALIDATE VOLTAGE (THE GENERAL OCR VALUE IS 0X00FF88000).
// 2. IF THE RESPONSE IS CORRECT, THEN CONTINUE, ELSE GOTO 9.
// 3. IF THE INITIALIZATION HAS FINISHED, GO TO 5. (THE RESPONSE IS THE OCR REGISTER AND IT INCLUDES A STATUS INFORMATION BIT (BIT [31]). THIS STATUS BIT IS SET IF THE CARD POWER UP PROCEDURE HAS BEEN FINISHED. AS LONG AS THE CARD IS BUSY, THE CORRESPONDING BIT[31] IS SET TO LOW.)
// 4. Send CMD1 (SEND_OP_CMD) to validate voltage, and then go to 3.
// 5. Send CMD2 (ALL_SEND_CID) to get the card CID.
// 6. If the response timeout occurs, goto 9.
// 7. Send CMD3 (SET_RELATIVE_ADDR) to assign the card a RCA.
void Mmc::check_sdmem ():
kdebug ("checking sdmem\n")
// 2. Send CMD55. Here the default RCA 0x0000 is used for CMD55.
// 3. If the response is correct (CMD55 has response), then continue, else go to check MMC.
unsigned code
hc = false
if send (8, 0x1aa, R7, &code) && (code & 0xff) == 0xaa:
kdebug ("hc\n")
hc = true
if !send (55, 0, R1, &code):
check_mmc ()
return
// 4. Send ACMD41 (SD_SEND_OP_CMD) to validate voltage (the general OCR value is 0x00FF8000).
if !send (41, hc ? 0x40800000 : 0x00800000, R3, &code):
check_mmc ()
return
// 5. If the initialization has finished, go to 7. (The response is the OCR register and it includes a status information bit (bit [31]). This status bit is set if the card power up procedure has been finished. As long as the card is busy, the corresponding bit[31] is set to LOW.)
// 6. Send CMD55 and ACMD41 to validate voltage, and then go to 5.
unsigned retries = 100
while !(code & (1 << 31)) && --retries:
if !send (55, 0, R1, &code):
return
if !send (41, hc ? 0x40800000 : 0x00800000, R3, &code):
return
Iris::sleep (1)
if !(code & (1 << 31)):
Iris::panic (code, "card fails to finish setting up")
// 7. Send CMD2 (ALL_SEND_CID) to get the card CID.
if !send (2, 0, R2):
Iris::panic (0, "card failed to send CID")
// 8. Send CMD3 (SET_RELATIVE_ADDR) to let card publish a RCA. The RCA is returned from the response.
// 9. If do not accept the new RCA, go to 8, else record the new RCA.
rca = 0
while !rca:
if !send (3, 0, R6, &rca):
Iris::panic (0, "card failed to provide rca")
rca &= 0xffff0000
kdebug ("received rca ")
kdebug_num (rca >> 16, 4)
kdebug ("\n")
have_sdmem = true
void Mmc::check_sd ():
//kdebug ("checking sdio\n")
if !send (0, 0, NONE):
Iris::panic (0, "unable to reset cards?")
// 2. Send CMD5 (IO_SEND_OP_CMD) to validate voltage.
// 3. If the response is correct and the number of IO functions > 0, then continue, else go to check SDMEM.
unsigned code
if !send (5, 1 << 20, R4, &code) || !(code & (7 << 28)):
check_sdmem ()
return
// 4. If C-bit in the response is ready (the initialization has finished), go to 6.
// 5. Send CMD5 (IO_SEND_OP_CMD) to validate voltage, then go to 4.
while !(code & (1 << 31)):
if !send (5, 1 << 20, R4, &code):
Iris::panic (0, "invalid response to cmd 5")
// 6. If memory-present-bit in the response is true, then it is a combo card (SDIO + Memory), else it is only a SDIO card.
// 7. If it is a combo card, go to check SDMEM to initialize the memory part.
have_io = true
if code & (1 << 27):
check_sdmem ()
return
// 8. Send CMD3 (SET_RELATIVE_ADDR) to let the card publish a RCA. The RCA is returned from the response.
// 9. If do not accept the new RCA, go to 8, else record the new RCA.
rca = 0
while rca == 0:
if !send (3, 0, R6, &rca):
Iris::panic (0, "unable to set rca")
rca &= 0xffff0000
check_mmc ()
void Mmc::detect ():
kdebug ("mmc detect\n")
gpio_clear (POWER_PORT, 1 << POWER_PIN)
check_sd ()
check_mmc ()
if have_sdmem:
if !send (9, rca, R2):
Iris::panic (0, "unable to request csd")
if !send (7, rca, R1B):
Iris::panic (0, "unable to select sdmem")
kdebug ("found device; size = ")
kdebug_num (num_blocks)
kdebug (" * ")
kdebug_num (read_block_size)
kdebug (" = ")
kdebug_num (num_blocks * read_block_size)
kdebug ("\n")
// Set up buffer memory.
for unsigned i = 0; i < 1 << csd.write_bl_len; i += PAGE_SIZE:
Iris::Page p = Iris::my_memory.create_page ()
p.set_flags (Iris::Page::PAYING | Iris::Page::FRAME)
Iris::my_memory.map (p, (unsigned)current_block + i)
Iris::free_cap (p)
Iris::Listitem item = cb_list.get_next ()
while item.code != Iris::Cap ().code:
Iris::Cap c = cb_list.get_cap (item)
c.invoke (0, ~0)
Iris::free_cap (c)
Iris::Listitem nextitem = cb_list.get_next (item);
Iris::free_cap (item)
item = nextitem
void Mmc::release ():
kdebug ("mmc release\n")
gpio_set (POWER_PORT, 1 << POWER_PIN)
have_sdmem = false
have_io = false
read_block_size = 0
if num_blocks != 0:
for unsigned i = 0; i < 1 << csd.write_bl_len; i += PAGE_SIZE:
Iris::Page p = Iris::my_memory.mapping ((void *)((unsigned)current_block + i))
Iris::my_memory.destroy (p)
Iris::free_cap (p)
if dirty:
Iris::debug ("Warning: sd/mmc card removed before data was written to it")
current_block_num = ~0
dirty = false
num_blocks = 0
Iris::Listitem item = cb_list.get_next ()
while item.code != Iris::Cap ().code:
Iris::Cap c = cb_list.get_cap (item)
c.invoke (0, ~0)
Iris::free_cap (c)
Iris::Listitem nextitem = cb_list.get_next (item);
Iris::free_cap (item)
item = nextitem
void Mmc::interrupt ():
kdebug ("mmc interrupt\n")
void Mmc::set_block (unsigned block):
if current_block_num == block:
return
if dirty && current_block_num != ~0:
MSC_NOB = 1
MSC_BLKLEN = 1 << csd.write_bl_len
if !send (24, (current_block_num << csd.write_bl_len), WR_DATA):
Iris::panic (0, "unable to send data")
MSC_IMASK = ~MSC_IMASK_TXFIFO_WR_REQ
for unsigned a = 0; a < 1 << csd.write_bl_len; a += 4:
while MSC_STAT & MSC_STAT_DATA_FIFO_FULL:
Iris::register_interrupt (IRQ_MSC)
Iris::wait_for_interrupt ()
MSC_TXFIFO = current_block[a >> 2]
MSC_IMASK = ~0
MSC_IREG = MSC_IREG_DATA_TRAN_DONE
//kdebug ("done writing page\n")
current_block_num = block
dirty = false
MSC_NOB = 1
MSC_BLKLEN = 1 << 9
for unsigned a = 0; a < 1 << csd.write_bl_len; a += 1 << 9:
if !send (17, (block << csd.write_bl_len) + a, RD_DATA):
Iris::panic (0, "unable to request data")
MSC_IMASK = ~MSC_IMASK_RXFIFO_RD_REQ
for unsigned aa = 0; aa < 1 << 9; aa += 4:
while MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY:
Iris::register_interrupt (IRQ_MSC)
Iris::wait_for_interrupt ()
current_block[(a + aa) >> 2] = MSC_RXFIFO
MSC_IMASK = ~0
MSC_IREG = MSC_IREG_DATA_TRAN_DONE
//kdebug ("done filling page\n")
void Mmc::read_page (Iris::Page page, Iris::Num address, unsigned size, unsigned offset):
if address.value () >> (csd.write_bl_len + 32):
Iris::panic (address.h, "page too high: not supported")
unsigned block = address.value () >> csd.write_bl_len
unsigned start_pos = address.l & (1 << csd.write_bl_len) - 1
set_block (block)
unsigned blockmask = ~((1 << 9) - 1)
size &= blockmask
offset &= ~PAGE_MASK & ~3
if size + offset > PAGE_SIZE:
size = PAGE_SIZE - offset
page.set_flags (Iris::Page::PAYING | Iris::Page::FRAME)
page.share (buffer_page)
buffer_page.set_flags (Iris::Page::PAYING | Iris::Page::FRAME)
page.set_flags (0, Iris::Page::PAYING)
for unsigned i = 0; i < size; i += 4:
((unsigned *)buffer)[(offset + i) >> 2] = current_block[(start_pos + i) >> 2]
void Mmc::write_page (Iris::Page page, Iris::Num address, unsigned size, unsigned offset):
if address.value () >> (csd.write_bl_len + 32):
Iris::panic (address.h, "page too high: not supported")
unsigned block = address.value () >> csd.write_bl_len
unsigned start_pos = address.l & (1 << csd.write_bl_len) - 1
set_block (block)
unsigned blockmask = ~((1 << 9) - 1)
size &= blockmask
offset &= ~PAGE_MASK & ~3
if size + offset > PAGE_SIZE:
size = PAGE_SIZE - offset
page.set_flags (Iris::Page::PAYING | Iris::Page::FRAME)
page.share (buffer_page)
buffer_page.set_flags (Iris::Page::PAYING | Iris::Page::FRAME)
page.set_flags (0, Iris::Page::PAYING)
for unsigned i = 0; i < size; i += 4:
current_block[(start_pos + i) >> 2] = ((unsigned *)buffer)[(offset + i) >> 2]
dirty = true
void Mmc::wait_write ():
MSC_IMASK = ~MSC_IMASK_PRG_DONE
while !MSC_STAT & MSC_STAT_PRG_DONE:
Iris::register_interrupt (IRQ_MSC)
Iris::wait_for_interrupt ()
MSC_IREG = MSC_IREG_PRG_DONE
static Mmc mmc
enum types:
DETECT = 1
REQUEST
Iris::Num start ():
map_msc ()
map_gpio ()
map_cpm ()
mmc.reset ()
Iris::Event detect = Iris::my_parent.get_capability <Iris::Event> ()
Iris::Cap cap = Iris::my_receiver.create_capability (DETECT)
detect.set_cb (cap.copy ())
cap.invoke (~0)
Iris::free_cap (cap)
// Get a message from the queue. This is either the "there is no card" message, or the message we just sent.
Iris::wait ()
if Iris::recv.data[0].l != ~0:
// If it was "there is no card", the message we sent is still in the queue.
Iris::wait ()
else:
// Otherwise, there is a card.
mmc.detect ()
cap = Iris::my_receiver.create_capability (REQUEST)
Iris::my_parent.provide_capability <Iris::WBlock> (cap.copy ())
Iris::free_cap (cap)
Iris::my_parent.init_done ()
while true:
Iris::wait ()
switch Iris::recv.protected_data.l:
case 0:
mmc.interrupt ()
break
case DETECT:
if Iris::recv.data[0].l:
mmc.detect ()
else:
mmc.release ()
break
case REQUEST:
//kdebug ("sd+mmc request ")
//kdebug_num (Iris::recv.data[0].l)
//kdebug ("\n")
switch Iris::recv.data[0].l:
case Iris::Block::GET_SIZE:
Iris::debug ("get size\n")
unsigned long long size = mmc.get_num_blocks () * mmc.get_read_block_size ()
Iris::recv.reply.invoke (size)
break
case Iris::Block::GET_ALIGN_BITS:
Iris::debug ("get align bits\n")
Iris::recv.reply.invoke (9)
break
case Iris::Block::GET_BLOCK:
//Iris::debug ("get block\n")
Iris::Cap reply = Iris::get_reply ()
Iris::Page page = Iris::get_arg ()
mmc.read_page (page, Iris::recv.data[1], Iris::recv.data[0].h >> 16, Iris::recv.data[0].h & 0xffff)
reply.invoke ()
Iris::free_cap (page)
Iris::free_cap (reply)
break
case Iris::WBlock::SET_BLOCK:
Iris::debug ("set block\n")
Iris::Cap reply = Iris::get_reply ()
Iris::Page page = Iris::get_arg ()
mmc.write_page (page, Iris::recv.data[1], Iris::recv.data[0].h >> 16, Iris::recv.data[0].h & 0xffff)
reply.invoke ()
Iris::free_cap (page)
Iris::free_cap (reply)
mmc.wait_write ()
break
case Iris::Block::SET_CHANGE_CB:
Iris::debug ("set change cb\n")
Iris::Listitem item = Iris::get_arg ()
Iris::Cap reply = Iris::get_reply ()
mmc.add_cb (item)
reply.invoke ()
Iris::free_cap (item)
Iris::free_cap (reply)
break
case Iris::WBlock::TRUNCATE:
Iris::debug ("truncate\n")
// Fall through: don't support resizing.
default:
Iris::panic (0, "unexpected event for sd+mmc")
break
default:
Iris::panic (0, "unexpected request source for sd+mmc")