paul@258 | 1 | /* |
paul@258 | 2 | * MSC (MMC/SD controller) peripheral support. |
paul@258 | 3 | * |
paul@258 | 4 | * Copyright (C) 2024 Paul Boddie <paul@boddie.org.uk> |
paul@258 | 5 | * |
paul@258 | 6 | * This program is free software; you can redistribute it and/or |
paul@258 | 7 | * modify it under the terms of the GNU General Public License as |
paul@258 | 8 | * published by the Free Software Foundation; either version 2 of |
paul@258 | 9 | * the License, or (at your option) any later version. |
paul@258 | 10 | * |
paul@258 | 11 | * This program is distributed in the hope that it will be useful, |
paul@258 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@258 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@258 | 14 | * GNU General Public License for more details. |
paul@258 | 15 | * |
paul@258 | 16 | * You should have received a copy of the GNU General Public License |
paul@258 | 17 | * along with this program; if not, write to the Free Software |
paul@258 | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
paul@258 | 19 | * Boston, MA 02110-1301, USA |
paul@258 | 20 | */ |
paul@258 | 21 | |
paul@258 | 22 | #include <l4/devices/hw_mmio_register_block.h> |
paul@258 | 23 | #include <l4/sys/irq.h> |
paul@268 | 24 | #include <l4/sys/rcv_endpoint.h> |
paul@258 | 25 | #include <l4/util/util.h> |
paul@258 | 26 | |
paul@271 | 27 | #include <systypes/thread.h> |
paul@268 | 28 | |
paul@264 | 29 | #include <math.h> |
paul@258 | 30 | #include <stdio.h> |
paul@258 | 31 | |
paul@258 | 32 | #include "msc-common.h" |
paul@264 | 33 | #include "msc-defs.h" |
paul@258 | 34 | |
paul@258 | 35 | |
paul@258 | 36 | |
paul@258 | 37 | // Command frame: |
paul@258 | 38 | // byte: start (1), direction (1), command (6) |
paul@258 | 39 | // 4 bytes: argument |
paul@258 | 40 | // byte: CRC (7), end (1) |
paul@258 | 41 | |
paul@258 | 42 | // IO_RW_DIRECT argument: |
paul@258 | 43 | // Argument MSB to LSB: R/W (1), function number (3), read after write flag (1), |
paul@258 | 44 | // stuff (1), register address (17), stuff (1), |
paul@258 | 45 | // write data or stuff (8) |
paul@258 | 46 | // 0x88000c08: W, function = 0, read after write, register address = 6 (CCCR), |
paul@258 | 47 | // data = 8 (reset) |
paul@258 | 48 | |
paul@258 | 49 | const uint32_t Io_rw_direct_reset = 0x88000c08; |
paul@258 | 50 | |
paul@258 | 51 | // (IO_)SEND_OP_COND argument and default voltage range expected in R3, R4: |
paul@258 | 52 | // Argument MSB to LSB: stuff (8), voltage range (16), reserved (8) |
paul@258 | 53 | // 0x00ff8000: voltage range 2.7 - 3.6V |
paul@258 | 54 | |
paul@258 | 55 | const uint32_t Ocr_default_voltage_range = 0x00ff8000; |
paul@258 | 56 | |
paul@258 | 57 | // SEND_IF_COND argument and default voltage range expected in R7: |
paul@258 | 58 | // Argument MSB to LSB: stuff (20), voltage supplied (4), check (8) |
paul@258 | 59 | // 0x000001aa: voltage range 2.7 - 3.6V, check = 0b10101010 |
paul@258 | 60 | |
paul@258 | 61 | const uint32_t If_cond_default_voltage_range = 0x000001aa; |
paul@258 | 62 | |
paul@258 | 63 | |
paul@258 | 64 | |
paul@259 | 65 | // Utilities. |
paul@259 | 66 | |
paul@259 | 67 | static enum Command_data_control_bits |
paul@259 | 68 | encode_bus_width(uint8_t width) |
paul@259 | 69 | { |
paul@259 | 70 | switch (width) |
paul@259 | 71 | { |
paul@266 | 72 | case 4: return Cdc_bus_width_4bit; |
paul@266 | 73 | case 1: return Cdc_bus_width_1bit; |
paul@266 | 74 | default: return Cdc_bus_width_1bit; |
paul@259 | 75 | } |
paul@259 | 76 | } |
paul@259 | 77 | |
paul@259 | 78 | |
paul@259 | 79 | |
paul@258 | 80 | // Channel abstraction. |
paul@258 | 81 | |
paul@264 | 82 | Msc_channel::Msc_channel(l4_addr_t msc_start, l4_addr_t addr, l4_cap_idx_t irq, |
paul@264 | 83 | Cpm_chip *cpm, enum Clock_identifiers clock) |
paul@264 | 84 | : _msc_start(msc_start), _irq(irq), _cpm(cpm), _clock(clock) |
paul@258 | 85 | { |
paul@258 | 86 | _regs = new Hw::Mmio_register_block<32>(addr); |
paul@258 | 87 | } |
paul@258 | 88 | |
paul@258 | 89 | Msc_channel::~Msc_channel() |
paul@258 | 90 | { |
paul@258 | 91 | } |
paul@258 | 92 | |
paul@258 | 93 | // Utility methods. |
paul@258 | 94 | // NOTE: Also defined in the CPM abstraction, should be consolidated. |
paul@258 | 95 | |
paul@258 | 96 | uint32_t |
paul@258 | 97 | Msc_channel::get_field(uint32_t reg, uint32_t mask, uint8_t shift) |
paul@258 | 98 | { |
paul@258 | 99 | return (_regs[reg] & (mask << shift)) >> shift; |
paul@258 | 100 | } |
paul@258 | 101 | |
paul@258 | 102 | void |
paul@258 | 103 | Msc_channel::set_field(uint32_t reg, uint32_t mask, uint8_t shift, uint32_t value) |
paul@258 | 104 | { |
paul@258 | 105 | _regs[reg] = (_regs[reg] & (~(mask << shift))) | ((mask & value) << shift); |
paul@258 | 106 | } |
paul@258 | 107 | |
paul@258 | 108 | bool |
paul@264 | 109 | Msc_channel::set_clock_frequency(uint64_t frequency) |
paul@264 | 110 | { |
paul@264 | 111 | uint64_t division = _cpm->get_frequency(_clock) / frequency; |
paul@264 | 112 | double divider = ceil(log2(division)); |
paul@264 | 113 | |
paul@264 | 114 | if ((divider < 0) || (divider > Clock_rate_field_mask)) |
paul@264 | 115 | return false; |
paul@264 | 116 | |
paul@264 | 117 | set_field(Msc_clock_rate, Clock_rate_field_mask, Clock_rate_field_shift, |
paul@264 | 118 | (uint32_t) divider); |
paul@264 | 119 | |
paul@264 | 120 | return true; |
paul@264 | 121 | } |
paul@264 | 122 | |
paul@264 | 123 | void |
paul@264 | 124 | Msc_channel::reset() |
paul@264 | 125 | { |
paul@264 | 126 | _regs[Msc_control] = _regs[Msc_control] | Control_reset; |
paul@264 | 127 | |
paul@264 | 128 | while (_regs[Msc_status] & Status_resetting); |
paul@264 | 129 | } |
paul@264 | 130 | |
paul@264 | 131 | void |
paul@264 | 132 | Msc_channel::start_clock() |
paul@264 | 133 | { |
paul@264 | 134 | set_field(Msc_control, Control_clock_control_field_mask, |
paul@264 | 135 | Control_clock_control_field_shift, Control_clock_control_start); |
paul@264 | 136 | |
paul@264 | 137 | while (!(_regs[Msc_status] & Status_clock_enabled)); |
paul@264 | 138 | } |
paul@264 | 139 | |
paul@264 | 140 | void |
paul@264 | 141 | Msc_channel::stop_clock() |
paul@264 | 142 | { |
paul@264 | 143 | set_field(Msc_control, Control_clock_control_field_mask, |
paul@264 | 144 | Control_clock_control_field_shift, Control_clock_control_stop); |
paul@264 | 145 | |
paul@264 | 146 | while (_regs[Msc_status] & Status_clock_enabled); |
paul@264 | 147 | } |
paul@264 | 148 | |
paul@264 | 149 | void |
paul@264 | 150 | Msc_channel::ack_irq(uint32_t flags) |
paul@264 | 151 | { |
paul@264 | 152 | // Clear the flags by setting them. |
paul@264 | 153 | |
paul@264 | 154 | _regs[Msc_interrupt_flag] = _regs[Msc_interrupt_flag] | flags; |
paul@264 | 155 | } |
paul@264 | 156 | |
paul@264 | 157 | void |
paul@264 | 158 | Msc_channel::unmask_irq(uint32_t flags) |
paul@264 | 159 | { |
paul@264 | 160 | ack_irq(flags); |
paul@264 | 161 | |
paul@264 | 162 | if (_regs[Msc_interrupt_mask] & flags) |
paul@264 | 163 | _regs[Msc_interrupt_mask] = _regs[Msc_interrupt_mask] & ~flags; |
paul@264 | 164 | } |
paul@264 | 165 | |
paul@264 | 166 | bool |
paul@266 | 167 | Msc_channel::have_dma_enable_in_command() |
paul@266 | 168 | { |
paul@266 | 169 | return false; |
paul@266 | 170 | } |
paul@266 | 171 | |
paul@266 | 172 | bool |
paul@266 | 173 | Msc_channel::have_dma_selection() |
paul@266 | 174 | { |
paul@266 | 175 | return true; |
paul@266 | 176 | } |
paul@266 | 177 | |
paul@266 | 178 | bool |
paul@258 | 179 | Msc_channel::command_will_write(uint8_t index) |
paul@258 | 180 | { |
paul@258 | 181 | // NOTE: Probably incomplete coverage. |
paul@258 | 182 | |
paul@258 | 183 | switch (index) |
paul@258 | 184 | { |
paul@258 | 185 | case Command_write_dat_until_stop: return true; |
paul@258 | 186 | case Command_write_block: return true; |
paul@258 | 187 | case Command_write_multiple_block: return true; |
paul@258 | 188 | case Command_program_cid: return true; |
paul@258 | 189 | case Command_program_csd: return true; |
paul@258 | 190 | case Command_lock_unlock: return true; |
paul@258 | 191 | default: return false; |
paul@258 | 192 | } |
paul@258 | 193 | } |
paul@258 | 194 | |
paul@258 | 195 | bool |
paul@259 | 196 | Msc_channel::app_command_will_write(uint8_t index) |
paul@259 | 197 | { |
paul@259 | 198 | // NOTE: Probably incomplete coverage. |
paul@259 | 199 | |
paul@259 | 200 | (void) index; |
paul@259 | 201 | |
paul@259 | 202 | return false; |
paul@259 | 203 | } |
paul@259 | 204 | |
paul@259 | 205 | bool |
paul@258 | 206 | Msc_channel::command_with_data(uint8_t index) |
paul@258 | 207 | { |
paul@258 | 208 | // NOTE: Probably incomplete coverage. |
paul@258 | 209 | |
paul@258 | 210 | switch (index) |
paul@258 | 211 | { |
paul@258 | 212 | case Command_read_dat_until_stop: return true; |
paul@258 | 213 | case Command_read_single_block: return true; |
paul@258 | 214 | case Command_read_multiple_block: return true; |
paul@258 | 215 | case Command_write_dat_until_stop: return true; |
paul@258 | 216 | case Command_write_block: return true; |
paul@258 | 217 | case Command_write_multiple_block: return true; |
paul@258 | 218 | case Command_program_cid: return true; |
paul@258 | 219 | case Command_program_csd: return true; |
paul@258 | 220 | case Command_lock_unlock: return true; |
paul@258 | 221 | default: return false; |
paul@258 | 222 | } |
paul@258 | 223 | } |
paul@258 | 224 | |
paul@258 | 225 | bool |
paul@259 | 226 | Msc_channel::app_command_with_data(uint8_t index) |
paul@259 | 227 | { |
paul@259 | 228 | // NOTE: Probably incomplete coverage. |
paul@259 | 229 | |
paul@259 | 230 | switch (index) |
paul@259 | 231 | { |
paul@259 | 232 | case App_command_sd_status: return true; |
paul@259 | 233 | case App_command_send_num_wr_blocks: return true; |
paul@259 | 234 | case App_command_send_scr: return true; |
paul@259 | 235 | default: return false; |
paul@259 | 236 | } |
paul@259 | 237 | } |
paul@259 | 238 | |
paul@259 | 239 | bool |
paul@258 | 240 | Msc_channel::command_uses_busy(uint8_t index) |
paul@258 | 241 | { |
paul@258 | 242 | // NOTE: Probably incomplete coverage. |
paul@258 | 243 | |
paul@258 | 244 | switch (index) |
paul@258 | 245 | { |
paul@258 | 246 | case Command_select_deselect_card: return true; |
paul@258 | 247 | case Command_stop_transmission: return true; |
paul@258 | 248 | default: return false; |
paul@258 | 249 | } |
paul@258 | 250 | } |
paul@258 | 251 | |
paul@259 | 252 | bool |
paul@259 | 253 | Msc_channel::app_command_uses_busy(uint8_t index) |
paul@259 | 254 | { |
paul@259 | 255 | // NOTE: Probably incomplete coverage. |
paul@259 | 256 | |
paul@259 | 257 | (void) index; |
paul@259 | 258 | |
paul@259 | 259 | return false; |
paul@259 | 260 | } |
paul@259 | 261 | |
paul@258 | 262 | uint8_t |
paul@258 | 263 | Msc_channel::get_response_format(uint8_t index) |
paul@258 | 264 | { |
paul@258 | 265 | // NOTE: Probably incomplete coverage. |
paul@258 | 266 | |
paul@258 | 267 | switch (index) |
paul@258 | 268 | { |
paul@258 | 269 | // Common commands without response. |
paul@258 | 270 | |
paul@258 | 271 | case Command_go_idle_state: return 0; |
paul@258 | 272 | case Command_set_dsr: return 0; |
paul@258 | 273 | case Command_go_inactive_state: return 0; |
paul@258 | 274 | |
paul@258 | 275 | // Common commands with response. |
paul@258 | 276 | |
paul@258 | 277 | case Command_send_op_cond: return 3; |
paul@258 | 278 | case Command_all_send_cid: return 2; |
paul@258 | 279 | case Command_send_csd: return 2; |
paul@258 | 280 | case Command_send_cid: return 2; |
paul@258 | 281 | |
paul@258 | 282 | // SDIO only. |
paul@258 | 283 | |
paul@258 | 284 | case Command_io_send_op_cond: return 4; |
paul@258 | 285 | case Command_io_rw_direct: return 5; |
paul@258 | 286 | |
paul@258 | 287 | // SDMEM only. |
paul@258 | 288 | |
paul@258 | 289 | case Command_send_relative_addr: return 6; |
paul@258 | 290 | case Command_send_if_cond: return 7; |
paul@258 | 291 | |
paul@258 | 292 | // All other commands. |
paul@258 | 293 | |
paul@258 | 294 | default: return 1; |
paul@258 | 295 | } |
paul@258 | 296 | } |
paul@258 | 297 | |
paul@258 | 298 | uint8_t |
paul@258 | 299 | Msc_channel::get_app_response_format(uint8_t index) |
paul@258 | 300 | { |
paul@258 | 301 | // NOTE: Probably incomplete coverage. |
paul@258 | 302 | |
paul@258 | 303 | switch (index) |
paul@258 | 304 | { |
paul@258 | 305 | // SDMEM only. |
paul@258 | 306 | |
paul@258 | 307 | case App_command_sd_send_op_cond: return 3; |
paul@258 | 308 | |
paul@258 | 309 | // All other commands. |
paul@258 | 310 | |
paul@258 | 311 | default: return 1; |
paul@258 | 312 | } |
paul@258 | 313 | } |
paul@258 | 314 | |
paul@258 | 315 | // Read a response directly from the FIFO. |
paul@258 | 316 | |
paul@258 | 317 | void |
paul@258 | 318 | Msc_channel::read_response(uint16_t *buffer, uint8_t units) |
paul@258 | 319 | { |
paul@258 | 320 | uint8_t unit = units; |
paul@258 | 321 | |
paul@258 | 322 | while (unit > 0) |
paul@258 | 323 | { |
paul@258 | 324 | uint32_t data = _regs[Msc_response_fifo]; |
paul@258 | 325 | |
paul@258 | 326 | // Ignore the upper byte of the last unit in small transfers since it is the |
paul@258 | 327 | // lower byte from the previous unit not shifted out of the register. |
paul@258 | 328 | |
paul@258 | 329 | unit--; |
paul@258 | 330 | |
paul@258 | 331 | if ((unit == 0) && (units == 3)) |
paul@258 | 332 | buffer[unit] = (data & 0xff) << 8; |
paul@258 | 333 | else |
paul@258 | 334 | buffer[unit] = data; |
paul@258 | 335 | } |
paul@258 | 336 | } |
paul@258 | 337 | |
paul@258 | 338 | // Send an application-specific command. |
paul@258 | 339 | |
paul@258 | 340 | bool |
paul@258 | 341 | Msc_channel::send_app_command(uint8_t index, uint32_t arg) |
paul@258 | 342 | { |
paul@262 | 343 | if (!send_command(Command_app_cmd, _cards[_card].rca << 16)) |
paul@258 | 344 | return false; |
paul@258 | 345 | |
paul@259 | 346 | return send_command(index, arg, get_app_response_format(index), |
paul@259 | 347 | app_command_with_data(index), |
paul@259 | 348 | app_command_will_write(index), |
paul@259 | 349 | app_command_uses_busy(index)); |
paul@258 | 350 | } |
paul@258 | 351 | |
paul@258 | 352 | // Send a common MMC/SD command. |
paul@258 | 353 | |
paul@258 | 354 | bool |
paul@258 | 355 | Msc_channel::send_command(uint8_t index, uint32_t arg) |
paul@258 | 356 | { |
paul@258 | 357 | return send_command(index, arg, get_response_format(index), |
paul@259 | 358 | command_with_data(index), |
paul@259 | 359 | command_will_write(index), |
paul@258 | 360 | command_uses_busy(index)); |
paul@258 | 361 | } |
paul@258 | 362 | |
paul@258 | 363 | // Initiate a command having the given index and using the given argument, |
paul@258 | 364 | // employing the specified response format and involving a data transfer if |
paul@258 | 365 | // indicated. |
paul@258 | 366 | |
paul@258 | 367 | bool |
paul@258 | 368 | Msc_channel::send_command(uint8_t index, uint32_t arg, uint8_t response_format, |
paul@258 | 369 | bool data, bool write, bool busy) |
paul@258 | 370 | { |
paul@258 | 371 | stop_clock(); |
paul@258 | 372 | |
paul@266 | 373 | // Enable DMA for data transfers for JZ4780, X1600 and others that have a |
paul@266 | 374 | // channel-level control selecting special or conventional DMA. |
paul@258 | 375 | |
paul@266 | 376 | if (have_dma_selection()) |
paul@266 | 377 | _regs[Msc_dma_control] = (data ? Dma_select_common_dma | Dma_enable : Dma_disable); |
paul@258 | 378 | |
paul@258 | 379 | // Set the command index and argument. |
paul@258 | 380 | |
paul@258 | 381 | _regs[Msc_command_index] = index; |
paul@258 | 382 | _regs[Msc_command_argument] = arg; |
paul@258 | 383 | |
paul@258 | 384 | // Configure the response format and data bus width. |
paul@258 | 385 | |
paul@258 | 386 | set_field(Msc_command_data_control, Cdc_response_format_field_mask, |
paul@258 | 387 | Cdc_response_format_field_shift, response_format); |
paul@258 | 388 | |
paul@258 | 389 | // NOTE: May need to set the SD bus width. |
paul@258 | 390 | |
paul@258 | 391 | set_field(Msc_command_data_control, Cdc_bus_width_field_mask, |
paul@266 | 392 | Cdc_bus_width_field_shift, |
paul@266 | 393 | encode_bus_width(_cards[_card].bus_width)); |
paul@258 | 394 | |
paul@258 | 395 | set_field(Msc_command_data_control, Cdc_recv_fifo_level_field_mask, |
paul@258 | 396 | Cdc_recv_fifo_level_field_shift, Cdc_fifo_level_16); |
paul@258 | 397 | |
paul@258 | 398 | set_field(Msc_command_data_control, Cdc_trans_fifo_level_field_mask, |
paul@258 | 399 | Cdc_trans_fifo_level_field_shift, Cdc_fifo_level_16); |
paul@258 | 400 | |
paul@258 | 401 | // Set and clear control bits appropriate to the command. |
paul@258 | 402 | |
paul@258 | 403 | _regs[Msc_command_data_control] = _regs[Msc_command_data_control] | |
paul@258 | 404 | (busy ? Cdc_expect_busy : Cdc_do_not_expect_busy) | |
paul@258 | 405 | (data ? Cdc_data_with_command : Cdc_no_data_with_command) | |
paul@258 | 406 | (write ? Cdc_write_operation : Cdc_read_operation); |
paul@258 | 407 | |
paul@258 | 408 | _regs[Msc_command_data_control] = _regs[Msc_command_data_control] & |
paul@266 | 409 | ~((busy ? Cdc_do_not_expect_busy : Cdc_expect_busy) | |
paul@258 | 410 | (data ? Cdc_no_data_with_command : Cdc_data_with_command) | |
paul@258 | 411 | (write ? Cdc_read_operation : Cdc_write_operation) | |
paul@258 | 412 | Cdc_stream_block | Cdc_init_sequence); |
paul@258 | 413 | |
paul@266 | 414 | // Pre-JZ4780 SoCs enable DMA in the command/data register. |
paul@266 | 415 | |
paul@266 | 416 | if (have_dma_enable_in_command()) |
paul@266 | 417 | set_field(Msc_command_data_control, Cdc_dma_field_mask, Cdc_dma_field_shift, |
paul@266 | 418 | data ? Cdc_dma_enable : Cdc_dma_disable); |
paul@266 | 419 | |
paul@258 | 420 | // Unmask interrupts, start the clock, then initiate the command. |
paul@258 | 421 | |
paul@258 | 422 | uint32_t flags = Int_end_command_response | Int_response_timeout; |
paul@258 | 423 | |
paul@258 | 424 | unmask_irq(flags); |
paul@258 | 425 | start_clock(); |
paul@258 | 426 | |
paul@258 | 427 | _regs[Msc_control] = _regs[Msc_control] | Control_start_operation; |
paul@258 | 428 | |
paul@258 | 429 | // Wait for command completion. |
paul@258 | 430 | |
paul@258 | 431 | if (!wait_for_irq(flags)) |
paul@258 | 432 | return false; |
paul@258 | 433 | |
paul@258 | 434 | // Determine whether a timeout occurred. |
paul@258 | 435 | |
paul@261 | 436 | bool have_response = !((_regs[Msc_interrupt_flag] & Int_response_timeout) || |
paul@261 | 437 | (_regs[Msc_status] & Status_response_crc_error) || |
paul@261 | 438 | (_regs[Msc_status] & Status_timeout_response)); |
paul@258 | 439 | |
paul@261 | 440 | // Acknowledge the response interrupts and return the status. |
paul@258 | 441 | |
paul@258 | 442 | ack_irq(flags); |
paul@258 | 443 | return have_response; |
paul@258 | 444 | } |
paul@258 | 445 | |
paul@262 | 446 | // Wait indefinitely for an interrupt request, returning true if one was delivered. |
paul@262 | 447 | |
paul@262 | 448 | bool |
paul@262 | 449 | Msc_channel::wait_for_irq(uint32_t flags) |
paul@262 | 450 | { |
paul@271 | 451 | if (l4_error(l4_rcv_ep_bind_thread(_irq, get_current_thread(), 0))) |
paul@268 | 452 | return false; |
paul@268 | 453 | |
paul@262 | 454 | return !l4_error(l4_irq_receive(_irq, L4_IPC_NEVER)) && |
paul@262 | 455 | (_regs[Msc_interrupt_flag] & flags); |
paul@262 | 456 | } |
paul@262 | 457 | |
paul@262 | 458 | // Wait up to the given timeout (in microseconds) for an interrupt request, |
paul@262 | 459 | // returning true if one was delivered. |
paul@262 | 460 | |
paul@262 | 461 | bool |
paul@262 | 462 | Msc_channel::wait_for_irq(uint32_t flags, unsigned int timeout) |
paul@262 | 463 | { |
paul@271 | 464 | if (l4_error(l4_rcv_ep_bind_thread(_irq, get_current_thread(), 0))) |
paul@268 | 465 | return false; |
paul@268 | 466 | |
paul@262 | 467 | return !l4_error(l4_irq_receive(_irq, l4_timeout(L4_IPC_TIMEOUT_NEVER, |
paul@262 | 468 | l4util_micros2l4to(timeout)))) && |
paul@262 | 469 | (_regs[Msc_interrupt_flag] & flags); |
paul@262 | 470 | } |
paul@262 | 471 | |
paul@262 | 472 | |
paul@262 | 473 | |
paul@258 | 474 | // Check the voltage range of the SD card, potentially establishing that it is |
paul@258 | 475 | // a high capacity card. Return false if the voltage range is incompatible. |
paul@258 | 476 | |
paul@258 | 477 | bool |
paul@258 | 478 | Msc_channel::check_sd() |
paul@258 | 479 | { |
paul@262 | 480 | struct R7 r7; |
paul@258 | 481 | |
paul@258 | 482 | // Send an interface condition command. |
paul@258 | 483 | // A card may not respond to this command. |
paul@258 | 484 | |
paul@258 | 485 | if (!send_command(Command_send_if_cond, If_cond_default_voltage_range)) |
paul@258 | 486 | return true; |
paul@258 | 487 | |
paul@262 | 488 | read_response((uint16_t *) &r7, Response_size_R7); |
paul@258 | 489 | |
paul@258 | 490 | // Reject any card not supporting the default voltage range. |
paul@258 | 491 | |
paul@262 | 492 | if (r7.check_voltage.raw != If_cond_default_voltage_range) |
paul@258 | 493 | return false; |
paul@258 | 494 | |
paul@258 | 495 | return true; |
paul@258 | 496 | } |
paul@258 | 497 | |
paul@258 | 498 | // Check the voltage range of the SDIO card, inactivating it if incompatible. |
paul@258 | 499 | |
paul@258 | 500 | void |
paul@258 | 501 | Msc_channel::init_sdio() |
paul@258 | 502 | { |
paul@262 | 503 | struct R4 r4; |
paul@258 | 504 | uint32_t ocr = 0; |
paul@258 | 505 | |
paul@258 | 506 | // Reset any SDIO card or IO unit in a combined memory/IO card. |
paul@258 | 507 | // A non-SDIO card may not respond to this command. |
paul@258 | 508 | |
paul@258 | 509 | if (!send_command(Command_io_rw_direct, Io_rw_direct_reset)) |
paul@258 | 510 | return; |
paul@258 | 511 | |
paul@258 | 512 | // Attempt to assert the operating conditions. |
paul@258 | 513 | |
paul@258 | 514 | do |
paul@258 | 515 | { |
paul@258 | 516 | // Obtain OCR (operating conditions register) values for any IO card. |
paul@258 | 517 | // Without a response, the card may have inactivated itself due to voltage |
paul@258 | 518 | // range incompatibility reasons. |
paul@258 | 519 | |
paul@258 | 520 | if (!send_command(Command_io_send_op_cond, ocr)) |
paul@258 | 521 | return; |
paul@258 | 522 | |
paul@262 | 523 | read_response((uint16_t *) &r4, Response_size_R4); |
paul@258 | 524 | |
paul@258 | 525 | // Finish if no IO functions provided. |
paul@258 | 526 | // NOTE: Should only need to check this the first time. |
paul@258 | 527 | |
paul@262 | 528 | if (r4.number_io_functions == 0) |
paul@258 | 529 | return; |
paul@258 | 530 | |
paul@262 | 531 | if (r4.ocr != Ocr_default_voltage_range) |
paul@258 | 532 | { |
paul@258 | 533 | ocr = Ocr_default_voltage_range; |
paul@258 | 534 | continue; |
paul@258 | 535 | } |
paul@258 | 536 | } |
paul@262 | 537 | while (!r4.ready); |
paul@258 | 538 | } |
paul@258 | 539 | |
paul@258 | 540 | void |
paul@258 | 541 | Msc_channel::init_sdmem() |
paul@258 | 542 | { |
paul@262 | 543 | struct R3 r3; |
paul@258 | 544 | |
paul@258 | 545 | // Incorporate the HCS bit into the OCR for SDMEM. |
paul@258 | 546 | |
paul@258 | 547 | uint32_t ocr = Ocr_high_capacity_storage; |
paul@258 | 548 | |
paul@258 | 549 | do |
paul@258 | 550 | { |
paul@258 | 551 | if (!send_app_command(App_command_sd_send_op_cond, ocr)) |
paul@258 | 552 | return; |
paul@258 | 553 | |
paul@262 | 554 | read_response((uint16_t *) &r3, Response_size_R3); |
paul@258 | 555 | |
paul@262 | 556 | if (r3.ocr != Ocr_default_voltage_range) |
paul@258 | 557 | { |
paul@258 | 558 | ocr = Ocr_default_voltage_range | Ocr_high_capacity_storage; |
paul@258 | 559 | continue; |
paul@258 | 560 | } |
paul@258 | 561 | } |
paul@262 | 562 | while (!(r3.ocr & Ocr_card_powered_up)); |
paul@258 | 563 | } |
paul@258 | 564 | |
paul@258 | 565 | void |
paul@258 | 566 | Msc_channel::init_mmc() |
paul@258 | 567 | { |
paul@258 | 568 | // Obtain OCR (operating conditions register) values for each card using |
paul@258 | 569 | // send_op_cond command variants without argument, or assert operating |
paul@258 | 570 | // conditions with argument to avoid handling card responses. Where responses |
paul@258 | 571 | // are solicited, the host must determine a suitable argument and reissue the |
paul@258 | 572 | // command. |
paul@258 | 573 | |
paul@262 | 574 | struct R3 r3; |
paul@258 | 575 | uint32_t ocr = 0; |
paul@258 | 576 | |
paul@258 | 577 | do |
paul@258 | 578 | { |
paul@258 | 579 | if (!send_command(Command_send_op_cond, ocr)) |
paul@258 | 580 | return; |
paul@258 | 581 | |
paul@262 | 582 | read_response((uint16_t *) &r3, Response_size_R3); |
paul@258 | 583 | |
paul@262 | 584 | if (r3.ocr != Ocr_default_voltage_range) |
paul@258 | 585 | { |
paul@258 | 586 | ocr = Ocr_default_voltage_range; |
paul@258 | 587 | continue; |
paul@258 | 588 | } |
paul@258 | 589 | } |
paul@262 | 590 | while (!(r3.ocr & Ocr_card_powered_up)); |
paul@258 | 591 | } |
paul@258 | 592 | |
paul@258 | 593 | void |
paul@258 | 594 | Msc_channel::identify_cards() |
paul@258 | 595 | { |
paul@262 | 596 | struct R2 r2; |
paul@262 | 597 | struct R6 r6; |
paul@258 | 598 | |
paul@262 | 599 | _num_cards = 0; |
paul@258 | 600 | |
paul@258 | 601 | while (send_command(Command_all_send_cid, 0)) |
paul@258 | 602 | { |
paul@262 | 603 | read_response((uint16_t *) &r2, Response_size_R2); |
paul@258 | 604 | |
paul@262 | 605 | _cards[_num_cards].cid = r2.payload.cid; |
paul@258 | 606 | |
paul@262 | 607 | printf("card: %d\n", _num_cards); |
paul@262 | 608 | printf("date: %d %d\n", r2.payload.cid.month, r2.payload.cid.year); |
paul@262 | 609 | printf("serial: %d\n", r2.payload.cid.serial); |
paul@262 | 610 | printf("revision: %d\n", r2.payload.cid.revision); |
paul@262 | 611 | printf("name: %c%c%c%c%c\n", r2.payload.cid.name[4], r2.payload.cid.name[3], |
paul@262 | 612 | r2.payload.cid.name[2], r2.payload.cid.name[1], |
paul@262 | 613 | r2.payload.cid.name[0]); |
paul@262 | 614 | printf("oem: %d\n", r2.payload.cid.oem); |
paul@262 | 615 | printf("manufacturer: %d\n", r2.payload.cid.manufacturer); |
paul@258 | 616 | |
paul@258 | 617 | // Try and obtain a card-issued address. |
paul@258 | 618 | |
paul@258 | 619 | if (send_command(Command_send_relative_addr, 0)) |
paul@258 | 620 | { |
paul@262 | 621 | read_response((uint16_t *) &r6, Response_size_R6); |
paul@262 | 622 | _cards[_num_cards].rca = r6.rca; |
paul@258 | 623 | } |
paul@258 | 624 | |
paul@258 | 625 | // Try and assign an address. |
paul@258 | 626 | // Employ 1-based relative addressing. |
paul@258 | 627 | |
paul@262 | 628 | else if (send_command(Command_set_relative_addr, _num_cards + 1)) |
paul@262 | 629 | _cards[_num_cards].rca = _num_cards + 1; |
paul@258 | 630 | |
paul@258 | 631 | // Otherwise, stop identification. |
paul@258 | 632 | |
paul@258 | 633 | else |
paul@258 | 634 | return; |
paul@258 | 635 | |
paul@262 | 636 | // Set the default bus width to be determined. |
paul@259 | 637 | |
paul@262 | 638 | _cards[_num_cards].bus_width = 0; |
paul@259 | 639 | |
paul@262 | 640 | _num_cards++; |
paul@258 | 641 | } |
paul@258 | 642 | } |
paul@258 | 643 | |
paul@258 | 644 | void |
paul@258 | 645 | Msc_channel::query_cards() |
paul@258 | 646 | { |
paul@262 | 647 | struct R2 r2; |
paul@262 | 648 | struct R3 r3; |
paul@258 | 649 | uint8_t card; |
paul@258 | 650 | |
paul@262 | 651 | for (card = 0; card < _num_cards; card++) |
paul@258 | 652 | { |
paul@258 | 653 | // Employ 1-based relative addressing. |
paul@258 | 654 | |
paul@262 | 655 | if (!send_command(Command_send_csd, _cards[card].rca << 16)) |
paul@258 | 656 | return; |
paul@258 | 657 | |
paul@262 | 658 | read_response((uint16_t *) &r2, Response_size_R2); |
paul@258 | 659 | |
paul@262 | 660 | _cards[card].csd = r2.payload.csd; |
paul@258 | 661 | |
paul@263 | 662 | struct CSD *csd = &_cards[card].csd; |
paul@263 | 663 | |
paul@258 | 664 | printf("card: %d\n", card); |
paul@263 | 665 | printf("csd: %d\n", csd->csd); |
paul@263 | 666 | printf("copy: %s\n", csd->copy ? "copied" : "original"); |
paul@263 | 667 | printf("card command classes: %03x\n", csd->card_command_classes); |
paul@263 | 668 | printf("device (size multiplier): %d %d\n", csd->device_size + 1, |
paul@263 | 669 | 1 << (csd->device_size_multiplier + 2)); |
paul@263 | 670 | printf("device size: %d\n", (1 << csd->read_blocklen) * |
paul@263 | 671 | (csd->device_size + 1) * |
paul@263 | 672 | (1 << (csd->device_size_multiplier + 2))); |
paul@263 | 673 | printf("transfer speed: %d MHz\n", csd->tran_speed == 0x32 ? 25 : 50); |
paul@263 | 674 | printf("format group: %d %d\n", csd->format, r2.payload.csd.format_group); |
paul@263 | 675 | printf("write time factor: %d\n", 1 << csd->write_time_factor); |
paul@263 | 676 | printf("write protect (temp perm): %s %s\n", csd->temp_write_prot ? "yes" : "no", |
paul@263 | 677 | csd->perm_write_prot ? "yes" : "no"); |
paul@263 | 678 | printf("write protect group (enable size): %s %d\n", csd->write_prot_group_enable ? "yes" : "no", |
paul@263 | 679 | csd->write_prot_group_size + 1); |
paul@263 | 680 | printf("write block (partial length): %s %d\n", csd->write_block_partial ? "yes" : "no", |
paul@263 | 681 | 1 << csd->write_blocklen); |
paul@263 | 682 | printf("read block (partial length): %s %d\n", csd->read_block_partial ? "yes" : "no", |
paul@263 | 683 | 1 << csd->read_blocklen); |
paul@263 | 684 | printf("erase: sector single: %d %s\n", csd->erase_sector_size + 1, |
paul@263 | 685 | csd->erase_single_block_enable ? "yes" : "no"); |
paul@263 | 686 | printf("misalign: read write: %s %s\n", csd->read_block_misalign ? "yes" : "no", |
paul@263 | 687 | csd->write_block_misalign ? "yes" : "no"); |
paul@263 | 688 | printf("max read current (min max): %d %d\n", csd->max_read_current_min, |
paul@263 | 689 | csd->max_read_current_max); |
paul@263 | 690 | printf("max write current (min max): %d %d\n", csd->max_write_current_min, |
paul@263 | 691 | csd->max_write_current_max); |
paul@263 | 692 | printf("read access time (1 2): %d %d\n", csd->data_read_access_time_1, |
paul@263 | 693 | csd->data_read_access_time_2); |
paul@263 | 694 | printf("DSR: %s\n", csd->dsr_implemented ? "yes" : "no"); |
paul@262 | 695 | |
paul@262 | 696 | // Query the OCR again now that we can associate it with a specific card. |
paul@262 | 697 | |
paul@262 | 698 | if (!send_app_command(App_command_read_ocr, 0)) |
paul@262 | 699 | return; |
paul@262 | 700 | |
paul@262 | 701 | read_response((uint16_t *) &r3, Response_size_R3); |
paul@262 | 702 | |
paul@262 | 703 | _cards[card].ocr = r3.ocr; |
paul@258 | 704 | } |
paul@258 | 705 | } |
paul@258 | 706 | |
paul@262 | 707 | |
paul@262 | 708 | |
paul@263 | 709 | // Enable the controller and identify cards. |
paul@263 | 710 | |
paul@263 | 711 | void |
paul@263 | 712 | Msc_channel::enable() |
paul@263 | 713 | { |
paul@263 | 714 | // NOTE: X1600 and other recent SoCs only. |
paul@263 | 715 | |
paul@263 | 716 | _regs[Msc_low_power_mode] = _regs[Msc_low_power_mode] & ~Low_power_mode_enable; |
paul@263 | 717 | |
paul@263 | 718 | stop_clock(); |
paul@263 | 719 | reset(); |
paul@263 | 720 | |
paul@263 | 721 | // Slow the clock for initialisation. |
paul@264 | 722 | // NOTE: Should produce an error. |
paul@263 | 723 | |
paul@264 | 724 | if (!set_clock_frequency(400000)) |
paul@264 | 725 | return; |
paul@263 | 726 | |
paul@263 | 727 | send_command(Command_go_idle_state, 0); |
paul@263 | 728 | |
paul@263 | 729 | if (check_sd()) |
paul@263 | 730 | { |
paul@263 | 731 | init_sdio(); |
paul@263 | 732 | init_sdmem(); |
paul@263 | 733 | } |
paul@263 | 734 | |
paul@263 | 735 | init_mmc(); |
paul@263 | 736 | identify_cards(); |
paul@263 | 737 | query_cards(); |
paul@263 | 738 | |
paul@263 | 739 | // Initially, no card is selected. |
paul@263 | 740 | |
paul@263 | 741 | _card = -1; |
paul@263 | 742 | } |
paul@263 | 743 | |
paul@263 | 744 | // Obtain the card details. |
paul@263 | 745 | |
paul@263 | 746 | struct msc_card * |
paul@263 | 747 | Msc_channel::get_cards() |
paul@263 | 748 | { |
paul@263 | 749 | return _cards; |
paul@263 | 750 | } |
paul@263 | 751 | |
paul@263 | 752 | // Return the number of active cards. |
paul@263 | 753 | |
paul@263 | 754 | uint8_t |
paul@263 | 755 | Msc_channel::num_cards() |
paul@263 | 756 | { |
paul@263 | 757 | return _num_cards; |
paul@263 | 758 | } |
paul@263 | 759 | |
paul@262 | 760 | // Receive data from the selected card. |
paul@262 | 761 | |
paul@258 | 762 | uint32_t |
paul@264 | 763 | Msc_channel::recv_data(struct dma_region *region, uint32_t count) |
paul@258 | 764 | { |
paul@264 | 765 | if (count > region->size) |
paul@264 | 766 | return 0; |
paul@264 | 767 | |
paul@261 | 768 | uint32_t flags = Int_data_transfer_done; |
paul@261 | 769 | |
paul@261 | 770 | unmask_irq(flags); |
paul@261 | 771 | |
paul@264 | 772 | uint32_t to_transfer = transfer(_msc_start + Msc_recv_data_fifo, region->paddr, true, count); |
paul@261 | 773 | |
paul@261 | 774 | wait_for_irq(flags); |
paul@261 | 775 | ack_irq(flags); |
paul@261 | 776 | |
paul@261 | 777 | if (!to_transfer || |
paul@261 | 778 | (_regs[Msc_status] & Status_read_crc_error) || |
paul@261 | 779 | (_regs[Msc_status] & Status_timeout_read)) |
paul@261 | 780 | return 0; |
paul@261 | 781 | |
paul@261 | 782 | return to_transfer; |
paul@258 | 783 | } |
paul@258 | 784 | |
paul@262 | 785 | // Send data to the selected card. |
paul@262 | 786 | |
paul@258 | 787 | uint32_t |
paul@264 | 788 | Msc_channel::send_data(struct dma_region *region, uint32_t count) |
paul@258 | 789 | { |
paul@264 | 790 | if (count > region->size) |
paul@264 | 791 | return 0; |
paul@264 | 792 | |
paul@261 | 793 | uint32_t flags = Int_data_transfer_done; |
paul@261 | 794 | |
paul@261 | 795 | unmask_irq(flags); |
paul@261 | 796 | |
paul@264 | 797 | uint32_t to_transfer = transfer(region->paddr, _msc_start + Msc_trans_data_fifo, false, count); |
paul@261 | 798 | |
paul@261 | 799 | wait_for_irq(flags); |
paul@261 | 800 | ack_irq(flags); |
paul@261 | 801 | |
paul@261 | 802 | if (!to_transfer || |
paul@261 | 803 | (_regs[Msc_status] & Status_write_crc_error_data) || |
paul@261 | 804 | (_regs[Msc_status] & Status_write_crc_error_no_status)) |
paul@261 | 805 | return 0; |
paul@261 | 806 | |
paul@261 | 807 | return to_transfer; |
paul@258 | 808 | } |
paul@258 | 809 | |
paul@268 | 810 | // Read data from the indicated card into a memory region. |
paul@268 | 811 | |
paul@268 | 812 | uint32_t |
paul@268 | 813 | Msc_channel::read(uint8_t card, struct dma_region *region, |
paul@268 | 814 | uint32_t address, uint32_t count) |
paul@268 | 815 | { |
paul@268 | 816 | uint32_t block_size = 1 << _cards[card].csd.read_blocklen; |
paul@268 | 817 | |
paul@268 | 818 | // Obtain the block address from the byte address and the number of blocks |
paul@268 | 819 | // from the byte count. |
paul@268 | 820 | |
paul@268 | 821 | // NOTE: Rejecting addresses and counts that are not multiples of the block |
paul@268 | 822 | // NOTE: size. Should produce an error. |
paul@268 | 823 | |
paul@268 | 824 | if ((address % block_size) || (count % block_size)) |
paul@268 | 825 | return 0; |
paul@268 | 826 | |
paul@268 | 827 | return read_blocks(card, region, address / block_size, count / block_size); |
paul@268 | 828 | } |
paul@268 | 829 | |
paul@262 | 830 | // Read blocks from the indicated card into a memory region. |
paul@262 | 831 | |
paul@258 | 832 | uint32_t |
paul@264 | 833 | Msc_channel::read_blocks(uint8_t card, struct dma_region *region, |
paul@261 | 834 | uint32_t block_address, uint32_t block_count) |
paul@258 | 835 | { |
paul@262 | 836 | uint32_t block_size = 1 << _cards[card].csd.read_blocklen; |
paul@262 | 837 | struct R1 r1; |
paul@258 | 838 | |
paul@258 | 839 | // Select the requested card. |
paul@258 | 840 | |
paul@262 | 841 | if (_card != card) |
paul@258 | 842 | { |
paul@264 | 843 | // Choose an appropriate clock frequency for the card. |
paul@264 | 844 | // NOTE: Should produce an error. |
paul@264 | 845 | |
paul@264 | 846 | if (!set_clock_frequency(_cards[card].csd.tran_speed == 0x32 ? |
paul@264 | 847 | 25000000 : 50000000)) |
paul@264 | 848 | return 0; |
paul@264 | 849 | |
paul@262 | 850 | if (!send_command(Command_select_deselect_card, _cards[card].rca << 16)) |
paul@258 | 851 | return 0; |
paul@258 | 852 | |
paul@262 | 853 | read_response((uint16_t *) &r1, Response_size_R1); |
paul@258 | 854 | |
paul@262 | 855 | if (r1.status & R1_status_error_mask) |
paul@258 | 856 | return 0; |
paul@258 | 857 | |
paul@262 | 858 | _card = card; |
paul@258 | 859 | } |
paul@258 | 860 | |
paul@259 | 861 | // NOTE: SMEM cards should allow bus width setting with the SCR register |
paul@259 | 862 | // NOTE: describing the permitted values. |
paul@258 | 863 | // NOTE: SDIO cards have their bus width set in CCCR via CMD52. |
paul@258 | 864 | |
paul@262 | 865 | if (!_cards[card].bus_width) |
paul@259 | 866 | { |
paul@259 | 867 | if (send_app_command(App_command_set_bus_width, Bus_width_4bit)) |
paul@262 | 868 | _cards[card].bus_width = 4; |
paul@259 | 869 | else |
paul@262 | 870 | _cards[card].bus_width = 1; |
paul@259 | 871 | } |
paul@259 | 872 | |
paul@258 | 873 | if (!send_command(Command_set_blocklen, block_size)) |
paul@258 | 874 | return 0; |
paul@258 | 875 | |
paul@262 | 876 | read_response((uint16_t *) &r1, Response_size_R1); |
paul@258 | 877 | |
paul@262 | 878 | if (r1.status & R1_status_error_mask) |
paul@258 | 879 | return 0; |
paul@258 | 880 | |
paul@261 | 881 | // NOTE: Consider issuing a predefined block count command to any multiple |
paul@261 | 882 | // NOTE: block operation, at least for cards that support it. |
paul@261 | 883 | |
paul@258 | 884 | // Apply block count and size properties to the issued command. |
paul@258 | 885 | |
paul@261 | 886 | _regs[Msc_block_count] = block_count; |
paul@258 | 887 | _regs[Msc_block_length] = block_size; |
paul@258 | 888 | |
paul@263 | 889 | // Where CCS = 0, byte addressing is used. Otherwise, block addressing is used. |
paul@263 | 890 | |
paul@263 | 891 | uint32_t address; |
paul@263 | 892 | |
paul@263 | 893 | if (_cards[card].ocr & Ocr_high_capacity_storage) |
paul@263 | 894 | address = block_address; |
paul@263 | 895 | else |
paul@263 | 896 | address = block_address * block_size; |
paul@258 | 897 | |
paul@261 | 898 | if (!send_command(block_count == 1 ? Command_read_single_block |
paul@263 | 899 | : Command_read_multiple_block, address)) |
paul@258 | 900 | return 0; |
paul@258 | 901 | |
paul@262 | 902 | read_response((uint16_t *) &r1, Response_size_R1); |
paul@258 | 903 | |
paul@262 | 904 | if (r1.status & R1_status_error_mask) |
paul@258 | 905 | return 0; |
paul@258 | 906 | |
paul@261 | 907 | // NOTE: Use Msc_block_success_count instead. |
paul@261 | 908 | |
paul@264 | 909 | uint32_t transferred = recv_data(region, block_size * block_count); |
paul@261 | 910 | |
paul@261 | 911 | if (block_count > 1) |
paul@261 | 912 | send_command(Command_stop_transmission, 0); |
paul@261 | 913 | |
paul@261 | 914 | return transferred; |
paul@258 | 915 | } |
paul@258 | 916 | |
paul@258 | 917 | |
paul@258 | 918 | |
paul@258 | 919 | // Peripheral abstraction. |
paul@258 | 920 | |
paul@264 | 921 | Msc_chip::Msc_chip(l4_addr_t msc_start, l4_addr_t start, l4_addr_t end, |
paul@264 | 922 | Cpm_chip *cpm) |
paul@264 | 923 | : _msc_start(msc_start), _start(start), _end(end), _cpm(cpm) |
paul@258 | 924 | { |
paul@258 | 925 | } |