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