#pypp 0 // Iris: micro-kernel for a capability-based operating system. // source/sd+mmc.ccp: sd+mmc driver. // Copyright 2009 Bas Wijnen // // 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 . #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::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 (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")