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@258 | 26 | #include <stdio.h> |
paul@258 | 27 | #include <string.h> |
paul@258 | 28 | |
paul@258 | 29 | #include "msc-common.h" |
paul@258 | 30 | |
paul@258 | 31 | |
paul@258 | 32 | |
paul@258 | 33 | // Register locations for each channel. |
paul@258 | 34 | |
paul@258 | 35 | enum Regs : unsigned |
paul@258 | 36 | { |
paul@258 | 37 | Msc_control = 0x000, // MSC_CTRL |
paul@258 | 38 | Msc_status = 0x004, // MSC_STAT |
paul@258 | 39 | Msc_clock_rate = 0x008, // MSC_CLKRT |
paul@258 | 40 | Msc_command_data_control = 0x00c, // MSC_CMDAT |
paul@258 | 41 | Msc_response_timeout = 0x010, // MSC_RESTO |
paul@258 | 42 | Msc_read_timeout = 0x014, // MSC_RDTO |
paul@258 | 43 | Msc_block_length = 0x018, // MSC_BLKLEN |
paul@258 | 44 | Msc_block_count = 0x01c, // MSC_NOB |
paul@258 | 45 | Msc_block_success_count = 0x020, // MSC_SNOB |
paul@258 | 46 | Msc_interrupt_mask = 0x024, // MSC_IMASK |
paul@258 | 47 | Msc_interrupt_flag = 0x028, // MSC_IFLG/MSC_IREG |
paul@258 | 48 | Msc_command_index = 0x02c, // MSC_CMD |
paul@258 | 49 | Msc_command_argument = 0x030, // MSC_ARG |
paul@258 | 50 | Msc_response_fifo = 0x034, // MSC_RES |
paul@258 | 51 | Msc_recv_data_fifo = 0x038, // MSC_RXFIFO |
paul@258 | 52 | Msc_trans_data_fifo = 0x03c, // MSC_TXFIFO |
paul@258 | 53 | |
paul@258 | 54 | // JZ4780/X1600 only. |
paul@258 | 55 | |
paul@258 | 56 | Msc_low_power_mode = 0x040, // MSC_LPM |
paul@258 | 57 | Msc_dma_control = 0x044, // MSC_DMAC |
paul@258 | 58 | Msc_dma_descriptor_address = 0x048, // MSC_DMANDA |
paul@258 | 59 | Msc_dma_data_address = 0x04c, // MSC_DMADA |
paul@258 | 60 | Msc_dma_data_length = 0x050, // MSC_DMALEN |
paul@258 | 61 | Msc_dma_command = 0x054, // MSC_DMACMD |
paul@258 | 62 | Msc_control2 = 0x058, // MSC_CTRL2 |
paul@258 | 63 | Msc_rtfifo_data_counter = 0x05c, // MSC_RTCNT |
paul@258 | 64 | |
paul@258 | 65 | // Channel block size/offset. |
paul@258 | 66 | |
paul@258 | 67 | Msc_channel_offset = 0x10000, |
paul@258 | 68 | }; |
paul@258 | 69 | |
paul@258 | 70 | // Field definitions. |
paul@258 | 71 | |
paul@258 | 72 | enum Control_bits : unsigned |
paul@258 | 73 | { |
paul@258 | 74 | // JZ4780/X1600 only. |
paul@258 | 75 | |
paul@258 | 76 | Control_send_ccsd = 0x8000, // SEND_CCSD |
paul@258 | 77 | Control_send_ccsd_automatically = 0x4000, // SEND_CCSD |
paul@258 | 78 | |
paul@258 | 79 | // Common. |
paul@258 | 80 | |
paul@258 | 81 | Control_exit_multiple = 0x0080, // EXIT_MULTIPLE |
paul@258 | 82 | Control_exit_transfer = 0x0040, // EXIT_TRANSFER |
paul@258 | 83 | Control_start_read_wait = 0x0020, // START_READ_WAIT |
paul@258 | 84 | Control_stop_read_wait = 0x0010, // STOP_READ_WAIT |
paul@258 | 85 | Control_reset = 0x0008, // RESET |
paul@258 | 86 | Control_start_operation = 0x0004, // START_OP |
paul@258 | 87 | |
paul@258 | 88 | Control_clock_control_field_mask = 0x3, // CLOCK_CTRL |
paul@258 | 89 | Control_clock_control_start = 2, |
paul@258 | 90 | Control_clock_control_stop = 1, |
paul@258 | 91 | Control_clock_control_field_shift = 0, |
paul@258 | 92 | }; |
paul@258 | 93 | |
paul@258 | 94 | enum Control2_bits : unsigned |
paul@258 | 95 | { |
paul@258 | 96 | // JZ4780/X1600 only. |
paul@258 | 97 | |
paul@258 | 98 | Control2_pin_level_polarity_field_mask = 0x1f, // PIP |
paul@258 | 99 | Control2_pin_level_polarity_field_shift = 24, |
paul@258 | 100 | |
paul@258 | 101 | // JZ4780 only. |
paul@258 | 102 | |
paul@258 | 103 | Control2_reset_enable = 0x00800000, // RST_EN |
paul@258 | 104 | |
paul@258 | 105 | // JZ4780/X1600 only. |
paul@258 | 106 | |
paul@258 | 107 | Control2_stop_read_operation_mode = 0x00000010, // STPRM |
paul@258 | 108 | |
paul@258 | 109 | // JZ4780 only. |
paul@258 | 110 | |
paul@258 | 111 | Control2_signal_voltage_change = 0x00000008, // SVC |
paul@258 | 112 | |
paul@258 | 113 | // JZ4780/X1600 only. |
paul@258 | 114 | |
paul@258 | 115 | Control2_speed_mode_field_mask = 0x7, // SMS |
paul@258 | 116 | Control2_speed_mode_default = 0, // = 0 |
paul@258 | 117 | Control2_speed_mode_high = 1, // = 1 |
paul@258 | 118 | Control2_speed_mode_sdr12 = 2, // = 2 |
paul@258 | 119 | Control2_speed_mode_sdr25 = 3, // = 3 |
paul@258 | 120 | Control2_speed_mode_sdr50 = 4, // = 4 |
paul@258 | 121 | Control2_speed_mode_field_shift = 0, |
paul@258 | 122 | }; |
paul@258 | 123 | |
paul@258 | 124 | enum Status_bits : unsigned |
paul@258 | 125 | { |
paul@258 | 126 | // JZ4780/X1600 only. |
paul@258 | 127 | |
paul@258 | 128 | Status_auto_cmd12_done = 0x80000000, // AUTO_CMD12_DONE |
paul@258 | 129 | |
paul@258 | 130 | // JZ4780 only. |
paul@258 | 131 | |
paul@258 | 132 | Status_auto_cmd23_done = 0x40000000, // AUTO_CMD23_DONE |
paul@258 | 133 | Status_signal_voltage_change = 0x20000000, // SVS |
paul@258 | 134 | |
paul@258 | 135 | // JZ4780/X1600 only. |
paul@258 | 136 | |
paul@258 | 137 | Status_pin_level_field_mask = 0x1f, // PIN_LEVEL |
paul@258 | 138 | Status_pin_level_field_shift = 24, |
paul@258 | 139 | |
paul@258 | 140 | Status_boot_crc_error = 0x00100000, // BCE |
paul@258 | 141 | Status_boot_data_end = 0x00080000, // BDE |
paul@258 | 142 | Status_boot_ack_error = 0x00040000, // BAE |
paul@258 | 143 | Status_boot_ack_received = 0x00020000, // BAR |
paul@258 | 144 | Status_dma_end = 0x00010000, // DMAEND |
paul@258 | 145 | |
paul@258 | 146 | // Common. |
paul@258 | 147 | |
paul@258 | 148 | Status_resetting = 0x8000, // IS_RESETTING |
paul@258 | 149 | Status_sdio_interrupt_active = 0x4000, // SDIO_INT_ACTIVE |
paul@258 | 150 | Status_programming_done = 0x2000, // PRG_DONE |
paul@258 | 151 | Status_data_transfer_done = 0x1000, // DATA_TRAN_DONE |
paul@258 | 152 | Status_end_command_response = 0x0800, // END_CMD_RES |
paul@258 | 153 | Status_data_fifo_almost_full = 0x0400, // DATA_FIFO_AFULL |
paul@258 | 154 | Status_read_wait = 0x0200, // IS_READWAIT |
paul@258 | 155 | Status_clock_enabled = 0x0100, // CLK_EN |
paul@258 | 156 | Status_data_fifo_full = 0x0080, // DATA_FIFO_FULL |
paul@258 | 157 | Status_data_fifo_empty = 0x0040, // DATA_FIFO_EMPTY |
paul@258 | 158 | Status_response_crc_error = 0x0020, // CRC_RES_ERR |
paul@258 | 159 | Status_read_crc_error = 0x0010, // CRC_READ_ERROR |
paul@258 | 160 | Status_write_crc_error_no_status = 0x0008, // CRC_WRITE_ERROR (2) |
paul@258 | 161 | Status_write_crc_error_data = 0x0004, // CRC_WRITE_ERROR (1) |
paul@258 | 162 | Status_timeout_response = 0x0002, // TIME_OUT_RES |
paul@258 | 163 | Status_timeout_read = 0x0001, // TIME_OUT_READ |
paul@258 | 164 | }; |
paul@258 | 165 | |
paul@258 | 166 | enum Clock_rate_bits : unsigned |
paul@258 | 167 | { |
paul@258 | 168 | Clock_rate_field_mask = 0x7, // CLK_RATE |
paul@258 | 169 | Clock_rate_field_shift = 0, |
paul@258 | 170 | }; |
paul@258 | 171 | |
paul@258 | 172 | enum Command_data_control_bits : unsigned |
paul@258 | 173 | { |
paul@258 | 174 | // JZ4780/X1600 only. |
paul@258 | 175 | |
paul@258 | 176 | Cdc_ccs_expected = 0x80000000, // CCS_EXPECTED |
paul@258 | 177 | Cdc_read_ce_ata = 0x40000000, // READ_CEATA |
paul@258 | 178 | Cdc_disable_boot = 0x08000000, // DIS_BOOT |
paul@258 | 179 | Cdc_expect_boot_ack = 0x02000000, // EXP_BOOT_ACK |
paul@258 | 180 | Cdc_alternative_boot_mode = 0x01000000, // BOOT_MODE |
paul@258 | 181 | |
paul@258 | 182 | // JZ4780 only. |
paul@258 | 183 | |
paul@258 | 184 | Cdc_auto_cmd23 = 0x00040000, // AUTO_CMD23 |
paul@258 | 185 | |
paul@258 | 186 | // JZ4780/X1600 only. |
paul@258 | 187 | |
paul@258 | 188 | Cdc_sdio_interrupt_2cycle = 0x00020000, // SDIO_PRDT |
paul@258 | 189 | Cdc_auto_cmd12 = 0x00010000, // AUTO_CMD12 |
paul@258 | 190 | |
paul@258 | 191 | Cdc_recv_fifo_level_field_mask = 0x3, // RTRG |
paul@258 | 192 | Cdc_fifo_level_16 = 0, |
paul@258 | 193 | Cdc_fifo_level_32 = 1, |
paul@258 | 194 | Cdc_fifo_level_64 = 2, |
paul@258 | 195 | Cdc_fifo_level_96 = 3, |
paul@258 | 196 | Cdc_recv_fifo_level_field_shift = 14, |
paul@258 | 197 | |
paul@258 | 198 | Cdc_trans_fifo_level_field_mask = 0x3, // TTRG |
paul@258 | 199 | Cdc_trans_fifo_level_field_shift = 12, |
paul@258 | 200 | |
paul@258 | 201 | // Common. |
paul@258 | 202 | |
paul@258 | 203 | Cdc_io_abort = 0x0800, // IO_ABORT |
paul@258 | 204 | |
paul@258 | 205 | Cdc_bus_width_field_mask = 0x3, // BUS_WIDTH |
paul@258 | 206 | Cdc_bus_width_field_1bit = 0, // = 0 |
paul@258 | 207 | Cdc_bus_width_field_4bit = 2, // = 2 |
paul@258 | 208 | Cdc_bus_width_field_shift = 9, |
paul@258 | 209 | |
paul@258 | 210 | // JZ4740 only. |
paul@258 | 211 | |
paul@258 | 212 | Cdc_dma_enable = 0x0100, // DMA_EN |
paul@258 | 213 | Cdc_dma_disable = 0x0000, |
paul@258 | 214 | |
paul@258 | 215 | // Common. |
paul@258 | 216 | |
paul@258 | 217 | Cdc_init_sequence = 0x0080, // INIT |
paul@258 | 218 | |
paul@258 | 219 | Cdc_expect_busy = 0x0040, // BUSY |
paul@258 | 220 | Cdc_do_not_expect_busy = 0x0000, |
paul@258 | 221 | |
paul@258 | 222 | Cdc_stream_block = 0x0020, // STREAM_BLOCK |
paul@258 | 223 | Cdc_not_stream_block = 0x0000, |
paul@258 | 224 | |
paul@258 | 225 | Cdc_write_operation = 0x0010, // WRITE_READ |
paul@258 | 226 | Cdc_read_operation = 0x0000, |
paul@258 | 227 | |
paul@258 | 228 | Cdc_data_with_command = 0x0008, // DATA_EN |
paul@258 | 229 | Cdc_no_data_with_command = 0x0000, |
paul@258 | 230 | |
paul@258 | 231 | Cdc_response_format_field_mask = 0x7, // RESPONSE_FORMAT |
paul@258 | 232 | Cdc_response_format_field_shift = 0, |
paul@258 | 233 | }; |
paul@258 | 234 | |
paul@258 | 235 | enum Response_timeout_bits : unsigned |
paul@258 | 236 | { |
paul@258 | 237 | // NOTE: 16-bit value in the JZ4780. |
paul@258 | 238 | // NOTE: 32-bit value in the X1600. |
paul@258 | 239 | |
paul@258 | 240 | Response_timeout_mask = 0x000000ff, // RES_TO |
paul@258 | 241 | }; |
paul@258 | 242 | |
paul@258 | 243 | enum Read_timeout_bits : unsigned |
paul@258 | 244 | { |
paul@258 | 245 | // NOTE: 16-bit value prior to the JZ4780/X1600. |
paul@258 | 246 | |
paul@258 | 247 | Read_timeout_mask = 0xffffffff, // READ_TO |
paul@258 | 248 | }; |
paul@258 | 249 | |
paul@258 | 250 | enum Block_length_bits : unsigned |
paul@258 | 251 | { |
paul@258 | 252 | // NOTE: 16-bit value in the JZ4780/X1600. |
paul@258 | 253 | |
paul@258 | 254 | Block_length_mask = 0x00000fff, // BLK_LEN |
paul@258 | 255 | }; |
paul@258 | 256 | |
paul@258 | 257 | enum Block_count_bits : unsigned |
paul@258 | 258 | { |
paul@258 | 259 | Block_count_mask = 0x0000ffff, // NOB/SNOB |
paul@258 | 260 | }; |
paul@258 | 261 | |
paul@258 | 262 | // Interrupt mask/flag bits. |
paul@258 | 263 | |
paul@258 | 264 | enum Interrupt_bits : unsigned |
paul@258 | 265 | { |
paul@258 | 266 | // X1600 only. |
paul@258 | 267 | |
paul@258 | 268 | Int_dma_data_done = 0x80000000, // DMA_DATA_DONE |
paul@258 | 269 | |
paul@258 | 270 | // JZ4780 only. |
paul@258 | 271 | |
paul@258 | 272 | Int_auto_cmd23_done = 0x40000000, // AUTO_CMD23_DONE |
paul@258 | 273 | Int_signal_voltage_change = 0x20000000, // SVS |
paul@258 | 274 | |
paul@258 | 275 | // JZ4780/X1600 only. |
paul@258 | 276 | |
paul@258 | 277 | Int_pin_level_field_mask = 0x1f, // PIN_LEVEL |
paul@258 | 278 | Int_pin_level_field_shift = 24, |
paul@258 | 279 | |
paul@258 | 280 | // X1600 only. |
paul@258 | 281 | |
paul@258 | 282 | Int_write_request_all_done = 0x00800000, // WR_ALL_DONE |
paul@258 | 283 | |
paul@258 | 284 | // JZ4780/X1600 only. |
paul@258 | 285 | |
paul@258 | 286 | Int_boot_crc_error = 0x00100000, // BCE |
paul@258 | 287 | Int_boot_data_end = 0x00080000, // BDE |
paul@258 | 288 | Int_boot_ack_error = 0x00040000, // BAE |
paul@258 | 289 | Int_boot_ack_received = 0x00020000, // BAR |
paul@258 | 290 | Int_dma_end = 0x00010000, // DMAEND |
paul@258 | 291 | Int_auto_cmd12_done = 0x00008000, // AUTO_CMD12_DONE |
paul@258 | 292 | Int_data_fifo_full = 0x00004000, // DATA_FIFO_FULL |
paul@258 | 293 | Int_data_fifo_empty = 0x00002000, // DATA_FIFO_EMP |
paul@258 | 294 | Int_crc_response_error = 0x00001000, // CRC_RES_ERR |
paul@258 | 295 | Int_crc_read_error = 0x00000800, // CRC_READ_ERR |
paul@258 | 296 | Int_crc_write_error = 0x00000400, // CRC_WRITE_ERR |
paul@258 | 297 | Int_response_timeout = 0x00000200, // TIME_OUT_RES |
paul@258 | 298 | Int_read_timeout = 0x00000100, // TIME_OUT_READ |
paul@258 | 299 | |
paul@258 | 300 | // Common. |
paul@258 | 301 | |
paul@258 | 302 | Int_sdio = 0x80, // SDIO |
paul@258 | 303 | Int_trans_fifo_write_request = 0x40, // TXFIFO_WR_REQ |
paul@258 | 304 | Int_recv_fifo_read_request = 0x20, // RXFIFO_RD_REQ |
paul@258 | 305 | Int_end_command_response = 0x04, // END_CMD_RES |
paul@258 | 306 | Int_programming_done = 0x02, // PRG_DONE |
paul@258 | 307 | Int_data_transfer_done = 0x01, // DATA_TRAN_DONE |
paul@258 | 308 | }; |
paul@258 | 309 | |
paul@258 | 310 | enum Command_index_bits : unsigned |
paul@258 | 311 | { |
paul@258 | 312 | Command_index_mask = 0x0000003f, // CMD_INDEX |
paul@258 | 313 | }; |
paul@258 | 314 | |
paul@258 | 315 | enum Command_argument_bits : unsigned |
paul@258 | 316 | { |
paul@258 | 317 | Command_argument_mask = 0xffffffff, // ARG |
paul@258 | 318 | }; |
paul@258 | 319 | |
paul@258 | 320 | enum Response_fifo_bits : unsigned |
paul@258 | 321 | { |
paul@258 | 322 | Response_fifo_mask = 0x0000ffff, // DATA |
paul@258 | 323 | }; |
paul@258 | 324 | |
paul@258 | 325 | enum Recv_data_fifo_bits : unsigned |
paul@258 | 326 | { |
paul@258 | 327 | Recv_data_fifo_mask = 0xffffffff, // DATA |
paul@258 | 328 | }; |
paul@258 | 329 | |
paul@258 | 330 | enum Trans_data_fifo_bits : unsigned |
paul@258 | 331 | { |
paul@258 | 332 | Trans_data_fifo_mask = 0xffffffff, // DATA |
paul@258 | 333 | }; |
paul@258 | 334 | |
paul@258 | 335 | enum Low_power_mode_bits : unsigned |
paul@258 | 336 | { |
paul@258 | 337 | Low_power_mode_enable = 0x00000001, // LPM |
paul@258 | 338 | }; |
paul@258 | 339 | |
paul@258 | 340 | enum Dma_control_bits : unsigned |
paul@258 | 341 | { |
paul@258 | 342 | Dma_mode_specify_transfer_length = 0x80, // MODE_SEL |
paul@258 | 343 | |
paul@258 | 344 | Dma_address_offset_field_mask = 0x3, // AOFST |
paul@258 | 345 | Dma_address_offset_field_shift = 5, |
paul@258 | 346 | |
paul@258 | 347 | Dma_align_enable = 0x10, // ALIGNEN |
paul@258 | 348 | |
paul@258 | 349 | Dma_burst_type_field_mask = 0x3, // INCR |
paul@258 | 350 | Dma_burst_type_incr16 = 0, |
paul@258 | 351 | Dma_burst_type_incr32 = 1, |
paul@258 | 352 | Dma_burst_type_incr64 = 2, |
paul@258 | 353 | Dma_burst_type_field_shift = 2, |
paul@258 | 354 | |
paul@258 | 355 | Dma_select_common_dma = 0x02, // DMASEL |
paul@258 | 356 | Dma_select_special_dma = 0x00, |
paul@258 | 357 | |
paul@258 | 358 | Dma_enable = 0x01, // DMAEN |
paul@258 | 359 | Dma_disable = 0x00, |
paul@258 | 360 | }; |
paul@258 | 361 | |
paul@258 | 362 | |
paul@258 | 363 | |
paul@258 | 364 | // Command indexes. |
paul@258 | 365 | |
paul@258 | 366 | enum Command_index : unsigned |
paul@258 | 367 | { |
paul@258 | 368 | Command_go_idle_state = 0, |
paul@258 | 369 | Command_send_op_cond = 1, |
paul@258 | 370 | Command_all_send_cid = 2, |
paul@258 | 371 | Command_send_relative_addr = 3, // SD |
paul@258 | 372 | Command_set_relative_addr = 3, // MMC |
paul@258 | 373 | Command_set_dsr = 4, |
paul@258 | 374 | Command_io_send_op_cond = 5, // SDIO |
paul@258 | 375 | Command_select_deselect_card = 7, |
paul@258 | 376 | Command_send_if_cond = 8, |
paul@258 | 377 | Command_send_csd = 9, |
paul@258 | 378 | Command_send_cid = 10, |
paul@258 | 379 | Command_read_dat_until_stop = 11, |
paul@258 | 380 | Command_stop_transmission = 12, |
paul@258 | 381 | Command_send_status = 13, |
paul@258 | 382 | Command_go_inactive_state = 15, |
paul@258 | 383 | Command_set_blocklen = 16, |
paul@258 | 384 | Command_read_single_block = 17, |
paul@258 | 385 | Command_read_multiple_block = 18, |
paul@258 | 386 | Command_write_dat_until_stop = 20, |
paul@258 | 387 | Command_set_block_count = 23, |
paul@258 | 388 | Command_write_block = 24, |
paul@258 | 389 | Command_write_multiple_block = 25, |
paul@258 | 390 | Command_program_cid = 26, |
paul@258 | 391 | Command_program_csd = 27, |
paul@258 | 392 | Command_set_write_prot = 28, |
paul@258 | 393 | Command_clr_write_prot = 29, |
paul@258 | 394 | Command_send_write_prot = 30, |
paul@258 | 395 | Command_tag_sector_start = 32, |
paul@258 | 396 | Command_tag_sector_end = 33, |
paul@258 | 397 | Command_untag_sector = 34, |
paul@258 | 398 | Command_tag_erase_group_start = 35, |
paul@258 | 399 | Command_tag_erase_group_end = 36, |
paul@258 | 400 | Command_untag_erase_group = 37, |
paul@258 | 401 | Command_erase = 38, |
paul@258 | 402 | Command_fast_io = 39, |
paul@258 | 403 | Command_go_irq_state = 40, |
paul@258 | 404 | Command_lock_unlock = 42, |
paul@258 | 405 | Command_io_rw_direct = 52, // SDIO |
paul@258 | 406 | Command_app_cmd = 55, |
paul@258 | 407 | Command_gen_cmd = 56, |
paul@258 | 408 | }; |
paul@258 | 409 | |
paul@258 | 410 | // Application-specific command indexes, used by first issuing Command_app_cmd. |
paul@258 | 411 | |
paul@258 | 412 | enum App_command_index : unsigned |
paul@258 | 413 | { |
paul@258 | 414 | App_command_set_bus_width = 6, |
paul@258 | 415 | App_command_sd_status = 13, |
paul@258 | 416 | App_command_send_num_wr_blocks = 22, |
paul@258 | 417 | App_command_set_wr_block_erase_count = 23, |
paul@258 | 418 | App_command_sd_send_op_cond = 41, |
paul@258 | 419 | App_command_set_clr_card_detect = 42, |
paul@258 | 420 | App_command_send_scr = 51, |
paul@258 | 421 | App_command_read_ocr = 58, |
paul@258 | 422 | }; |
paul@258 | 423 | |
paul@258 | 424 | enum Bus_width_bits : unsigned |
paul@258 | 425 | { |
paul@258 | 426 | Bus_width_1bit = 0, |
paul@258 | 427 | Bus_width_4bit = 2, |
paul@258 | 428 | }; |
paul@258 | 429 | |
paul@258 | 430 | // Command response sizes in 16-bit units. |
paul@258 | 431 | |
paul@258 | 432 | enum Response_sizes : unsigned |
paul@258 | 433 | { |
paul@258 | 434 | Response_size_R1 = 3, |
paul@258 | 435 | Response_size_R2 = 8, // omits the CRC and end bit |
paul@258 | 436 | Response_size_R3 = 3, |
paul@258 | 437 | Response_size_R4 = 3, |
paul@258 | 438 | Response_size_R5 = 3, |
paul@258 | 439 | Response_size_R6 = 3, |
paul@258 | 440 | Response_size_R7 = 3, |
paul@258 | 441 | }; |
paul@258 | 442 | |
paul@258 | 443 | // SD_SEND_OP_COND argument flags. |
paul@258 | 444 | |
paul@258 | 445 | enum Ocr_argument_flags : unsigned |
paul@258 | 446 | { |
paul@258 | 447 | Ocr_high_capacity_storage = 0x40000000, |
paul@258 | 448 | }; |
paul@258 | 449 | |
paul@258 | 450 | // SD_SEND_OP_COND response flags (R3). |
paul@258 | 451 | |
paul@258 | 452 | enum Ocr_response_flags : unsigned |
paul@258 | 453 | { |
paul@258 | 454 | Ocr_card_powered_up = 0x80000000, |
paul@258 | 455 | }; |
paul@258 | 456 | |
paul@258 | 457 | // R1 status flags. |
paul@258 | 458 | |
paul@258 | 459 | enum R1_status_flags : unsigned |
paul@258 | 460 | { |
paul@258 | 461 | R1_status_error_mask = 0xffff0000, |
paul@258 | 462 | }; |
paul@258 | 463 | |
paul@258 | 464 | |
paul@258 | 465 | |
paul@258 | 466 | // MMC response structures. |
paul@258 | 467 | |
paul@258 | 468 | struct R1 |
paul@258 | 469 | { |
paul@258 | 470 | uint8_t end_crc; |
paul@258 | 471 | uint32_t status; |
paul@258 | 472 | uint8_t index:6, trans_start:2; |
paul@258 | 473 | } __attribute__((packed)); |
paul@258 | 474 | |
paul@258 | 475 | struct R2 |
paul@258 | 476 | { |
paul@258 | 477 | // uint8_t end_crc; (not retrieved) |
paul@258 | 478 | |
paul@258 | 479 | union |
paul@258 | 480 | { |
paul@258 | 481 | uint8_t raw[15]; |
paul@258 | 482 | struct CID cid; |
paul@258 | 483 | struct CSD csd; |
paul@258 | 484 | } payload; |
paul@258 | 485 | |
paul@258 | 486 | uint8_t reserved_trans_start; |
paul@258 | 487 | } __attribute__((packed)); |
paul@258 | 488 | |
paul@258 | 489 | struct R3 |
paul@258 | 490 | { |
paul@258 | 491 | uint8_t end_reserved; |
paul@258 | 492 | uint32_t ocr; |
paul@258 | 493 | uint8_t reserved_trans_start; |
paul@258 | 494 | } __attribute__((packed)); |
paul@258 | 495 | |
paul@258 | 496 | // SDIO response structures. |
paul@258 | 497 | |
paul@258 | 498 | struct R4 |
paul@258 | 499 | { |
paul@258 | 500 | uint8_t end_reserved; |
paul@258 | 501 | uint32_t ocr:24, stuff:3, memory_present:1, number_io_functions:3, ready:1; |
paul@258 | 502 | uint8_t reserved_trans_start; |
paul@258 | 503 | } __attribute__((packed)); |
paul@258 | 504 | |
paul@258 | 505 | struct R5 |
paul@258 | 506 | { |
paul@258 | 507 | uint8_t end_crc; |
paul@258 | 508 | uint8_t data; |
paul@258 | 509 | uint8_t out_of_range:1, invalid_function_number:1, reserved:1, error:1, |
paul@258 | 510 | io_current_state:2, illegal_command:1, crc_error:1; |
paul@258 | 511 | uint16_t stuff; |
paul@258 | 512 | uint8_t index:6, trans_start:2; |
paul@258 | 513 | } __attribute__((packed)); |
paul@258 | 514 | |
paul@258 | 515 | struct R6 |
paul@258 | 516 | { |
paul@258 | 517 | uint8_t end_crc; |
paul@258 | 518 | uint16_t status; |
paul@258 | 519 | uint16_t rca; |
paul@258 | 520 | uint8_t index:6, trans_start:2; |
paul@258 | 521 | } __attribute__((packed)); |
paul@258 | 522 | |
paul@258 | 523 | struct R7 |
paul@258 | 524 | { |
paul@258 | 525 | uint8_t end_crc; |
paul@258 | 526 | |
paul@258 | 527 | union |
paul@258 | 528 | { |
paul@258 | 529 | uint32_t check:8, voltage:4, reserved:20; |
paul@258 | 530 | uint32_t raw; |
paul@258 | 531 | } check_voltage; |
paul@258 | 532 | |
paul@258 | 533 | uint8_t index:6, trans_start:2; |
paul@258 | 534 | } __attribute__((packed)); |
paul@258 | 535 | |
paul@258 | 536 | |
paul@258 | 537 | |
paul@258 | 538 | // Command frame: |
paul@258 | 539 | // byte: start (1), direction (1), command (6) |
paul@258 | 540 | // 4 bytes: argument |
paul@258 | 541 | // byte: CRC (7), end (1) |
paul@258 | 542 | |
paul@258 | 543 | // IO_RW_DIRECT argument: |
paul@258 | 544 | // Argument MSB to LSB: R/W (1), function number (3), read after write flag (1), |
paul@258 | 545 | // stuff (1), register address (17), stuff (1), |
paul@258 | 546 | // write data or stuff (8) |
paul@258 | 547 | // 0x88000c08: W, function = 0, read after write, register address = 6 (CCCR), |
paul@258 | 548 | // data = 8 (reset) |
paul@258 | 549 | |
paul@258 | 550 | const uint32_t Io_rw_direct_reset = 0x88000c08; |
paul@258 | 551 | |
paul@258 | 552 | // (IO_)SEND_OP_COND argument and default voltage range expected in R3, R4: |
paul@258 | 553 | // Argument MSB to LSB: stuff (8), voltage range (16), reserved (8) |
paul@258 | 554 | // 0x00ff8000: voltage range 2.7 - 3.6V |
paul@258 | 555 | |
paul@258 | 556 | const uint32_t Ocr_default_voltage_range = 0x00ff8000; |
paul@258 | 557 | |
paul@258 | 558 | // SEND_IF_COND argument and default voltage range expected in R7: |
paul@258 | 559 | // Argument MSB to LSB: stuff (20), voltage supplied (4), check (8) |
paul@258 | 560 | // 0x000001aa: voltage range 2.7 - 3.6V, check = 0b10101010 |
paul@258 | 561 | |
paul@258 | 562 | const uint32_t If_cond_default_voltage_range = 0x000001aa; |
paul@258 | 563 | |
paul@258 | 564 | |
paul@258 | 565 | |
paul@258 | 566 | // Channel abstraction. |
paul@258 | 567 | |
paul@258 | 568 | Msc_channel::Msc_channel(l4_addr_t msc_start, l4_addr_t addr, l4_cap_idx_t irq) |
paul@258 | 569 | : _msc_start(msc_start), _irq(irq) |
paul@258 | 570 | { |
paul@258 | 571 | _regs = new Hw::Mmio_register_block<32>(addr); |
paul@258 | 572 | } |
paul@258 | 573 | |
paul@258 | 574 | Msc_channel::~Msc_channel() |
paul@258 | 575 | { |
paul@258 | 576 | } |
paul@258 | 577 | |
paul@258 | 578 | // Utility methods. |
paul@258 | 579 | // NOTE: Also defined in the CPM abstraction, should be consolidated. |
paul@258 | 580 | |
paul@258 | 581 | uint32_t |
paul@258 | 582 | Msc_channel::get_field(uint32_t reg, uint32_t mask, uint8_t shift) |
paul@258 | 583 | { |
paul@258 | 584 | return (_regs[reg] & (mask << shift)) >> shift; |
paul@258 | 585 | } |
paul@258 | 586 | |
paul@258 | 587 | void |
paul@258 | 588 | Msc_channel::set_field(uint32_t reg, uint32_t mask, uint8_t shift, uint32_t value) |
paul@258 | 589 | { |
paul@258 | 590 | _regs[reg] = (_regs[reg] & (~(mask << shift))) | ((mask & value) << shift); |
paul@258 | 591 | } |
paul@258 | 592 | |
paul@258 | 593 | bool |
paul@258 | 594 | Msc_channel::command_will_write(uint8_t index) |
paul@258 | 595 | { |
paul@258 | 596 | // NOTE: Probably incomplete coverage. |
paul@258 | 597 | |
paul@258 | 598 | switch (index) |
paul@258 | 599 | { |
paul@258 | 600 | case Command_write_dat_until_stop: return true; |
paul@258 | 601 | case Command_write_block: return true; |
paul@258 | 602 | case Command_write_multiple_block: return true; |
paul@258 | 603 | case Command_program_cid: return true; |
paul@258 | 604 | case Command_program_csd: return true; |
paul@258 | 605 | case Command_lock_unlock: return true; |
paul@258 | 606 | default: return false; |
paul@258 | 607 | } |
paul@258 | 608 | } |
paul@258 | 609 | |
paul@258 | 610 | bool |
paul@258 | 611 | Msc_channel::command_with_data(uint8_t index) |
paul@258 | 612 | { |
paul@258 | 613 | // NOTE: Probably incomplete coverage. |
paul@258 | 614 | |
paul@258 | 615 | switch (index) |
paul@258 | 616 | { |
paul@258 | 617 | case Command_read_dat_until_stop: return true; |
paul@258 | 618 | case Command_read_single_block: return true; |
paul@258 | 619 | case Command_read_multiple_block: return true; |
paul@258 | 620 | case Command_write_dat_until_stop: return true; |
paul@258 | 621 | case Command_write_block: return true; |
paul@258 | 622 | case Command_write_multiple_block: return true; |
paul@258 | 623 | case Command_program_cid: return true; |
paul@258 | 624 | case Command_program_csd: return true; |
paul@258 | 625 | case Command_lock_unlock: return true; |
paul@258 | 626 | default: return false; |
paul@258 | 627 | } |
paul@258 | 628 | } |
paul@258 | 629 | |
paul@258 | 630 | bool |
paul@258 | 631 | Msc_channel::command_uses_busy(uint8_t index) |
paul@258 | 632 | { |
paul@258 | 633 | // NOTE: Probably incomplete coverage. |
paul@258 | 634 | |
paul@258 | 635 | switch (index) |
paul@258 | 636 | { |
paul@258 | 637 | case Command_select_deselect_card: return true; |
paul@258 | 638 | case Command_stop_transmission: return true; |
paul@258 | 639 | default: return false; |
paul@258 | 640 | } |
paul@258 | 641 | } |
paul@258 | 642 | |
paul@258 | 643 | uint8_t |
paul@258 | 644 | Msc_channel::get_response_format(uint8_t index) |
paul@258 | 645 | { |
paul@258 | 646 | // NOTE: Probably incomplete coverage. |
paul@258 | 647 | |
paul@258 | 648 | switch (index) |
paul@258 | 649 | { |
paul@258 | 650 | // Common commands without response. |
paul@258 | 651 | |
paul@258 | 652 | case Command_go_idle_state: return 0; |
paul@258 | 653 | case Command_set_dsr: return 0; |
paul@258 | 654 | case Command_go_inactive_state: return 0; |
paul@258 | 655 | |
paul@258 | 656 | // Common commands with response. |
paul@258 | 657 | |
paul@258 | 658 | case Command_send_op_cond: return 3; |
paul@258 | 659 | case Command_all_send_cid: return 2; |
paul@258 | 660 | case Command_send_csd: return 2; |
paul@258 | 661 | case Command_send_cid: return 2; |
paul@258 | 662 | |
paul@258 | 663 | // SDIO only. |
paul@258 | 664 | |
paul@258 | 665 | case Command_io_send_op_cond: return 4; |
paul@258 | 666 | case Command_io_rw_direct: return 5; |
paul@258 | 667 | |
paul@258 | 668 | // SDMEM only. |
paul@258 | 669 | |
paul@258 | 670 | case Command_send_relative_addr: return 6; |
paul@258 | 671 | case Command_send_if_cond: return 7; |
paul@258 | 672 | |
paul@258 | 673 | // All other commands. |
paul@258 | 674 | |
paul@258 | 675 | default: return 1; |
paul@258 | 676 | } |
paul@258 | 677 | } |
paul@258 | 678 | |
paul@258 | 679 | uint8_t |
paul@258 | 680 | Msc_channel::get_app_response_format(uint8_t index) |
paul@258 | 681 | { |
paul@258 | 682 | // NOTE: Probably incomplete coverage. |
paul@258 | 683 | |
paul@258 | 684 | switch (index) |
paul@258 | 685 | { |
paul@258 | 686 | // SDMEM only. |
paul@258 | 687 | |
paul@258 | 688 | case App_command_sd_send_op_cond: return 3; |
paul@258 | 689 | |
paul@258 | 690 | // All other commands. |
paul@258 | 691 | |
paul@258 | 692 | default: return 1; |
paul@258 | 693 | } |
paul@258 | 694 | } |
paul@258 | 695 | |
paul@258 | 696 | // Read a response directly from the FIFO. |
paul@258 | 697 | |
paul@258 | 698 | void |
paul@258 | 699 | Msc_channel::read_response(uint16_t *buffer, uint8_t units) |
paul@258 | 700 | { |
paul@258 | 701 | uint8_t unit = units; |
paul@258 | 702 | |
paul@258 | 703 | while (unit > 0) |
paul@258 | 704 | { |
paul@258 | 705 | uint32_t data = _regs[Msc_response_fifo]; |
paul@258 | 706 | |
paul@258 | 707 | // Ignore the upper byte of the last unit in small transfers since it is the |
paul@258 | 708 | // lower byte from the previous unit not shifted out of the register. |
paul@258 | 709 | |
paul@258 | 710 | unit--; |
paul@258 | 711 | |
paul@258 | 712 | if ((unit == 0) && (units == 3)) |
paul@258 | 713 | buffer[unit] = (data & 0xff) << 8; |
paul@258 | 714 | else |
paul@258 | 715 | buffer[unit] = data; |
paul@258 | 716 | } |
paul@258 | 717 | } |
paul@258 | 718 | |
paul@258 | 719 | void |
paul@258 | 720 | Msc_channel::ack_irq(uint32_t flags) |
paul@258 | 721 | { |
paul@258 | 722 | // Clear the flags by setting them. |
paul@258 | 723 | |
paul@258 | 724 | _regs[Msc_interrupt_flag] = _regs[Msc_interrupt_flag] | flags; |
paul@258 | 725 | } |
paul@258 | 726 | |
paul@258 | 727 | void |
paul@258 | 728 | Msc_channel::unmask_irq(uint32_t flags) |
paul@258 | 729 | { |
paul@258 | 730 | ack_irq(flags); |
paul@258 | 731 | |
paul@258 | 732 | if (_regs[Msc_interrupt_mask] & flags) |
paul@258 | 733 | _regs[Msc_interrupt_mask] = _regs[Msc_interrupt_mask] & ~flags; |
paul@258 | 734 | } |
paul@258 | 735 | |
paul@258 | 736 | void |
paul@258 | 737 | Msc_channel::reset() |
paul@258 | 738 | { |
paul@258 | 739 | _regs[Msc_control] = _regs[Msc_control] | Control_reset; |
paul@258 | 740 | |
paul@258 | 741 | // NOTE: X1600 and other recent SoCs only. |
paul@258 | 742 | |
paul@258 | 743 | _regs[Msc_control] = _regs[Msc_control] & ~Control_reset; |
paul@258 | 744 | |
paul@258 | 745 | // Sufficient for other SoCs... |
paul@258 | 746 | |
paul@258 | 747 | while (_regs[Msc_status] & Status_resetting); |
paul@258 | 748 | } |
paul@258 | 749 | |
paul@258 | 750 | void |
paul@258 | 751 | Msc_channel::start_clock() |
paul@258 | 752 | { |
paul@258 | 753 | set_field(Msc_control, Control_clock_control_field_mask, |
paul@258 | 754 | Control_clock_control_field_shift, Control_clock_control_start); |
paul@258 | 755 | |
paul@258 | 756 | while (!(_regs[Msc_status] & Status_clock_enabled)); |
paul@258 | 757 | } |
paul@258 | 758 | |
paul@258 | 759 | void |
paul@258 | 760 | Msc_channel::stop_clock() |
paul@258 | 761 | { |
paul@258 | 762 | set_field(Msc_control, Control_clock_control_field_mask, |
paul@258 | 763 | Control_clock_control_field_shift, Control_clock_control_stop); |
paul@258 | 764 | |
paul@258 | 765 | while (_regs[Msc_status] & Status_clock_enabled); |
paul@258 | 766 | } |
paul@258 | 767 | |
paul@258 | 768 | uint32_t |
paul@258 | 769 | Msc_channel::get_status() |
paul@258 | 770 | { |
paul@258 | 771 | return _regs[Msc_status]; |
paul@258 | 772 | } |
paul@258 | 773 | |
paul@258 | 774 | // Send an application-specific command. |
paul@258 | 775 | |
paul@258 | 776 | bool |
paul@258 | 777 | Msc_channel::send_app_command(uint8_t index, uint32_t arg) |
paul@258 | 778 | { |
paul@258 | 779 | if (!send_command(Command_app_cmd, 0, get_app_response_format(index), |
paul@258 | 780 | false, false, false)) |
paul@258 | 781 | return false; |
paul@258 | 782 | |
paul@258 | 783 | return send_command(index, arg); |
paul@258 | 784 | } |
paul@258 | 785 | |
paul@258 | 786 | // Send a common MMC/SD command. |
paul@258 | 787 | |
paul@258 | 788 | bool |
paul@258 | 789 | Msc_channel::send_command(uint8_t index, uint32_t arg) |
paul@258 | 790 | { |
paul@258 | 791 | return send_command(index, arg, get_response_format(index), |
paul@258 | 792 | command_with_data(index), command_will_write(index), |
paul@258 | 793 | command_uses_busy(index)); |
paul@258 | 794 | } |
paul@258 | 795 | |
paul@258 | 796 | // Initiate a command having the given index and using the given argument, |
paul@258 | 797 | // employing the specified response format and involving a data transfer if |
paul@258 | 798 | // indicated. |
paul@258 | 799 | |
paul@258 | 800 | bool |
paul@258 | 801 | Msc_channel::send_command(uint8_t index, uint32_t arg, uint8_t response_format, |
paul@258 | 802 | bool data, bool write, bool busy) |
paul@258 | 803 | { |
paul@258 | 804 | stop_clock(); |
paul@258 | 805 | |
paul@258 | 806 | // Enable DMA for data transfers. |
paul@258 | 807 | // NOTE: Needed for JZ4780 and later SoCs. |
paul@258 | 808 | |
paul@258 | 809 | _regs[Msc_dma_control] = (data ? Dma_select_common_dma | Dma_enable : Dma_disable); |
paul@258 | 810 | |
paul@258 | 811 | // Set the command index and argument. |
paul@258 | 812 | |
paul@258 | 813 | _regs[Msc_command_index] = index; |
paul@258 | 814 | _regs[Msc_command_argument] = arg; |
paul@258 | 815 | |
paul@258 | 816 | // Configure the response format and data bus width. |
paul@258 | 817 | |
paul@258 | 818 | set_field(Msc_command_data_control, Cdc_response_format_field_mask, |
paul@258 | 819 | Cdc_response_format_field_shift, response_format); |
paul@258 | 820 | |
paul@258 | 821 | // NOTE: May need to set the SD bus width. |
paul@258 | 822 | |
paul@258 | 823 | set_field(Msc_command_data_control, Cdc_bus_width_field_mask, |
paul@258 | 824 | Cdc_bus_width_field_shift, Cdc_bus_width_field_1bit); |
paul@258 | 825 | |
paul@258 | 826 | set_field(Msc_command_data_control, Cdc_recv_fifo_level_field_mask, |
paul@258 | 827 | Cdc_recv_fifo_level_field_shift, Cdc_fifo_level_16); |
paul@258 | 828 | |
paul@258 | 829 | set_field(Msc_command_data_control, Cdc_trans_fifo_level_field_mask, |
paul@258 | 830 | Cdc_trans_fifo_level_field_shift, Cdc_fifo_level_16); |
paul@258 | 831 | |
paul@258 | 832 | // Set and clear control bits appropriate to the command. |
paul@258 | 833 | // NOTE: Pre-JZ4780 SoCs enable DMA in this register. |
paul@258 | 834 | |
paul@258 | 835 | _regs[Msc_command_data_control] = _regs[Msc_command_data_control] | |
paul@258 | 836 | // (data ? Cdc_dma_enable : Cdc_dma_disable) | |
paul@258 | 837 | (busy ? Cdc_expect_busy : Cdc_do_not_expect_busy) | |
paul@258 | 838 | (data ? Cdc_data_with_command : Cdc_no_data_with_command) | |
paul@258 | 839 | (write ? Cdc_write_operation : Cdc_read_operation); |
paul@258 | 840 | |
paul@258 | 841 | _regs[Msc_command_data_control] = _regs[Msc_command_data_control] & |
paul@258 | 842 | ~( |
paul@258 | 843 | // (data ? Cdc_dma_disable : Cdc_dma_enable) | |
paul@258 | 844 | (busy ? Cdc_do_not_expect_busy : Cdc_expect_busy) | |
paul@258 | 845 | (data ? Cdc_no_data_with_command : Cdc_data_with_command) | |
paul@258 | 846 | (write ? Cdc_read_operation : Cdc_write_operation) | |
paul@258 | 847 | Cdc_stream_block | Cdc_init_sequence); |
paul@258 | 848 | |
paul@258 | 849 | // Unmask interrupts, start the clock, then initiate the command. |
paul@258 | 850 | |
paul@258 | 851 | uint32_t flags = Int_end_command_response | Int_response_timeout; |
paul@258 | 852 | |
paul@258 | 853 | unmask_irq(flags); |
paul@258 | 854 | start_clock(); |
paul@258 | 855 | |
paul@258 | 856 | _regs[Msc_control] = _regs[Msc_control] | Control_start_operation; |
paul@258 | 857 | |
paul@258 | 858 | // Wait for command completion. |
paul@258 | 859 | |
paul@258 | 860 | if (!wait_for_irq(flags)) |
paul@258 | 861 | return false; |
paul@258 | 862 | |
paul@258 | 863 | // Determine whether a timeout occurred. |
paul@258 | 864 | |
paul@258 | 865 | bool have_response = !(_regs[Msc_interrupt_flag] & Int_response_timeout); |
paul@258 | 866 | |
paul@258 | 867 | // Acknowledge the interrupts and return the status. |
paul@258 | 868 | |
paul@258 | 869 | ack_irq(flags); |
paul@258 | 870 | return have_response; |
paul@258 | 871 | } |
paul@258 | 872 | |
paul@258 | 873 | void |
paul@258 | 874 | Msc_channel::enable() |
paul@258 | 875 | { |
paul@258 | 876 | // NOTE: X1600 and other recent SoCs only. |
paul@258 | 877 | |
paul@258 | 878 | _regs[Msc_low_power_mode] = _regs[Msc_low_power_mode] & ~Low_power_mode_enable; |
paul@258 | 879 | |
paul@258 | 880 | stop_clock(); |
paul@258 | 881 | reset(); |
paul@258 | 882 | |
paul@258 | 883 | // Slow the clock for initialisation. |
paul@258 | 884 | // NOTE: Should use the CPM module to deduce the appropriate divider value. |
paul@258 | 885 | |
paul@258 | 886 | set_field(Msc_clock_rate, Clock_rate_field_mask, Clock_rate_field_shift, 7); |
paul@258 | 887 | |
paul@258 | 888 | send_command(Command_go_idle_state, 0); |
paul@258 | 889 | |
paul@258 | 890 | if (check_sd()) |
paul@258 | 891 | { |
paul@258 | 892 | init_sdio(); |
paul@258 | 893 | init_sdmem(); |
paul@258 | 894 | } |
paul@258 | 895 | |
paul@258 | 896 | init_mmc(); |
paul@258 | 897 | identify_cards(); |
paul@258 | 898 | query_cards(); |
paul@258 | 899 | |
paul@258 | 900 | // Restore the clock. |
paul@258 | 901 | // NOTE: Should use the CPM module to deduce the appropriate divider value. |
paul@258 | 902 | |
paul@258 | 903 | set_field(Msc_clock_rate, Clock_rate_field_mask, Clock_rate_field_shift, 1); |
paul@258 | 904 | |
paul@258 | 905 | _current_rca = 0; |
paul@258 | 906 | } |
paul@258 | 907 | |
paul@258 | 908 | // Check the voltage range of the SD card, potentially establishing that it is |
paul@258 | 909 | // a high capacity card. Return false if the voltage range is incompatible. |
paul@258 | 910 | |
paul@258 | 911 | bool |
paul@258 | 912 | Msc_channel::check_sd() |
paul@258 | 913 | { |
paul@258 | 914 | uint16_t buffer[Response_size_R7]; |
paul@258 | 915 | struct R7 *r = (struct R7 *) buffer; |
paul@258 | 916 | |
paul@258 | 917 | // Send an interface condition command. |
paul@258 | 918 | // A card may not respond to this command. |
paul@258 | 919 | |
paul@258 | 920 | if (!send_command(Command_send_if_cond, If_cond_default_voltage_range)) |
paul@258 | 921 | return true; |
paul@258 | 922 | |
paul@258 | 923 | read_response(buffer, Response_size_R7); |
paul@258 | 924 | |
paul@258 | 925 | // Reject any card not supporting the default voltage range. |
paul@258 | 926 | |
paul@258 | 927 | if (r->check_voltage.raw != If_cond_default_voltage_range) |
paul@258 | 928 | return false; |
paul@258 | 929 | |
paul@258 | 930 | return true; |
paul@258 | 931 | } |
paul@258 | 932 | |
paul@258 | 933 | // Check the voltage range of the SDIO card, inactivating it if incompatible. |
paul@258 | 934 | |
paul@258 | 935 | void |
paul@258 | 936 | Msc_channel::init_sdio() |
paul@258 | 937 | { |
paul@258 | 938 | uint16_t buffer[Response_size_R4]; |
paul@258 | 939 | struct R4 *r = (struct R4 *) buffer; |
paul@258 | 940 | uint32_t ocr = 0; |
paul@258 | 941 | |
paul@258 | 942 | // Reset any SDIO card or IO unit in a combined memory/IO card. |
paul@258 | 943 | // A non-SDIO card may not respond to this command. |
paul@258 | 944 | |
paul@258 | 945 | if (!send_command(Command_io_rw_direct, Io_rw_direct_reset)) |
paul@258 | 946 | return; |
paul@258 | 947 | |
paul@258 | 948 | // Attempt to assert the operating conditions. |
paul@258 | 949 | |
paul@258 | 950 | do |
paul@258 | 951 | { |
paul@258 | 952 | // Obtain OCR (operating conditions register) values for any IO card. |
paul@258 | 953 | // Without a response, the card may have inactivated itself due to voltage |
paul@258 | 954 | // range incompatibility reasons. |
paul@258 | 955 | |
paul@258 | 956 | if (!send_command(Command_io_send_op_cond, ocr)) |
paul@258 | 957 | return; |
paul@258 | 958 | |
paul@258 | 959 | read_response(buffer, Response_size_R4); |
paul@258 | 960 | |
paul@258 | 961 | // Finish if no IO functions provided. |
paul@258 | 962 | // NOTE: Should only need to check this the first time. |
paul@258 | 963 | |
paul@258 | 964 | if (r->number_io_functions == 0) |
paul@258 | 965 | return; |
paul@258 | 966 | |
paul@258 | 967 | if (r->ocr != Ocr_default_voltage_range) |
paul@258 | 968 | { |
paul@258 | 969 | ocr = Ocr_default_voltage_range; |
paul@258 | 970 | continue; |
paul@258 | 971 | } |
paul@258 | 972 | } |
paul@258 | 973 | while (!r->ready); |
paul@258 | 974 | } |
paul@258 | 975 | |
paul@258 | 976 | void |
paul@258 | 977 | Msc_channel::init_sdmem() |
paul@258 | 978 | { |
paul@258 | 979 | uint16_t buffer[Response_size_R3]; |
paul@258 | 980 | struct R3 *r = (struct R3 *) buffer; |
paul@258 | 981 | |
paul@258 | 982 | // Incorporate the HCS bit into the OCR for SDMEM. |
paul@258 | 983 | |
paul@258 | 984 | uint32_t ocr = Ocr_high_capacity_storage; |
paul@258 | 985 | |
paul@258 | 986 | do |
paul@258 | 987 | { |
paul@258 | 988 | if (!send_app_command(App_command_sd_send_op_cond, ocr)) |
paul@258 | 989 | return; |
paul@258 | 990 | |
paul@258 | 991 | read_response(buffer, Response_size_R3); |
paul@258 | 992 | |
paul@258 | 993 | if (r->ocr != Ocr_default_voltage_range) |
paul@258 | 994 | { |
paul@258 | 995 | ocr = Ocr_default_voltage_range | Ocr_high_capacity_storage; |
paul@258 | 996 | continue; |
paul@258 | 997 | } |
paul@258 | 998 | } |
paul@258 | 999 | while (!(r->ocr & Ocr_card_powered_up)); |
paul@258 | 1000 | } |
paul@258 | 1001 | |
paul@258 | 1002 | void |
paul@258 | 1003 | Msc_channel::init_mmc() |
paul@258 | 1004 | { |
paul@258 | 1005 | // Obtain OCR (operating conditions register) values for each card using |
paul@258 | 1006 | // send_op_cond command variants without argument, or assert operating |
paul@258 | 1007 | // conditions with argument to avoid handling card responses. Where responses |
paul@258 | 1008 | // are solicited, the host must determine a suitable argument and reissue the |
paul@258 | 1009 | // command. |
paul@258 | 1010 | |
paul@258 | 1011 | uint16_t buffer[Response_size_R3]; |
paul@258 | 1012 | struct R3 *r = (struct R3 *) buffer; |
paul@258 | 1013 | uint32_t ocr = 0; |
paul@258 | 1014 | |
paul@258 | 1015 | do |
paul@258 | 1016 | { |
paul@258 | 1017 | if (!send_command(Command_send_op_cond, ocr)) |
paul@258 | 1018 | return; |
paul@258 | 1019 | |
paul@258 | 1020 | read_response(buffer, Response_size_R3); |
paul@258 | 1021 | |
paul@258 | 1022 | if (r->ocr != Ocr_default_voltage_range) |
paul@258 | 1023 | { |
paul@258 | 1024 | ocr = Ocr_default_voltage_range; |
paul@258 | 1025 | continue; |
paul@258 | 1026 | } |
paul@258 | 1027 | } |
paul@258 | 1028 | while (!(r->ocr & Ocr_card_powered_up)); |
paul@258 | 1029 | } |
paul@258 | 1030 | |
paul@258 | 1031 | void |
paul@258 | 1032 | Msc_channel::identify_cards() |
paul@258 | 1033 | { |
paul@258 | 1034 | uint16_t buffer[Response_size_R2]; |
paul@258 | 1035 | struct R2 *r = (struct R2 *) buffer; |
paul@258 | 1036 | |
paul@258 | 1037 | _cards = 0; |
paul@258 | 1038 | |
paul@258 | 1039 | while (send_command(Command_all_send_cid, 0)) |
paul@258 | 1040 | { |
paul@258 | 1041 | read_response(buffer, Response_size_R2); |
paul@258 | 1042 | |
paul@258 | 1043 | memcpy(&_cid[_cards], r->payload.raw, sizeof(r->payload.raw)); |
paul@258 | 1044 | |
paul@258 | 1045 | printf("card: %d\n", _cards); |
paul@258 | 1046 | printf("date: %d %d\n", r->payload.cid.month, r->payload.cid.year); |
paul@258 | 1047 | printf("serial: %d\n", r->payload.cid.serial); |
paul@258 | 1048 | printf("revision: %d\n", r->payload.cid.revision); |
paul@258 | 1049 | printf("name: %c%c%c%c%c\n", r->payload.cid.name[4], r->payload.cid.name[3], |
paul@258 | 1050 | r->payload.cid.name[2], r->payload.cid.name[1], |
paul@258 | 1051 | r->payload.cid.name[0]); |
paul@258 | 1052 | printf("oem: %d\n", r->payload.cid.oem); |
paul@258 | 1053 | printf("manufacturer: %d\n", r->payload.cid.manufacturer); |
paul@258 | 1054 | |
paul@258 | 1055 | // Try and obtain a card-issued address. |
paul@258 | 1056 | |
paul@258 | 1057 | if (send_command(Command_send_relative_addr, 0)) |
paul@258 | 1058 | { |
paul@258 | 1059 | uint16_t addr_buffer[Response_size_R6]; |
paul@258 | 1060 | struct R6 *ar = (struct R6 *) addr_buffer; |
paul@258 | 1061 | |
paul@258 | 1062 | read_response(addr_buffer, Response_size_R6); |
paul@258 | 1063 | |
paul@258 | 1064 | memcpy(&_rca[_cards], &ar->rca, sizeof(ar->rca)); |
paul@258 | 1065 | } |
paul@258 | 1066 | |
paul@258 | 1067 | // Try and assign an address. |
paul@258 | 1068 | // Employ 1-based relative addressing. |
paul@258 | 1069 | |
paul@258 | 1070 | else if (send_command(Command_set_relative_addr, _cards + 1)) |
paul@258 | 1071 | _rca[_cards] = _cards + 1; |
paul@258 | 1072 | |
paul@258 | 1073 | // Otherwise, stop identification. |
paul@258 | 1074 | |
paul@258 | 1075 | else |
paul@258 | 1076 | return; |
paul@258 | 1077 | |
paul@258 | 1078 | _cards++; |
paul@258 | 1079 | } |
paul@258 | 1080 | } |
paul@258 | 1081 | |
paul@258 | 1082 | void |
paul@258 | 1083 | Msc_channel::query_cards() |
paul@258 | 1084 | { |
paul@258 | 1085 | uint16_t buffer[Response_size_R2]; |
paul@258 | 1086 | struct R2 *r = (struct R2 *) buffer; |
paul@258 | 1087 | uint8_t card; |
paul@258 | 1088 | |
paul@258 | 1089 | for (card = 0; card < _cards; card++) |
paul@258 | 1090 | { |
paul@258 | 1091 | // Employ 1-based relative addressing. |
paul@258 | 1092 | |
paul@258 | 1093 | if (!send_command(Command_send_csd, _rca[card] << 16)) |
paul@258 | 1094 | return; |
paul@258 | 1095 | |
paul@258 | 1096 | read_response(buffer, Response_size_R2); |
paul@258 | 1097 | |
paul@258 | 1098 | memcpy(&_csd[card], r->payload.raw, sizeof(r->payload.raw)); |
paul@258 | 1099 | |
paul@258 | 1100 | printf("card: %d\n", card); |
paul@258 | 1101 | printf("csd: %d\n", r->payload.csd.csd); |
paul@258 | 1102 | printf("copy: %s\n", r->payload.csd.copy ? "copied" : "original"); |
paul@258 | 1103 | printf("card command classes: %03x\n", r->payload.csd.card_command_classes); |
paul@258 | 1104 | printf("device (size multiplier): %d %d\n", r->payload.csd.device_size + 1, |
paul@258 | 1105 | 1 << (r->payload.csd.device_size_multiplier + 2)); |
paul@258 | 1106 | printf("device size: %d\n", (1 << r->payload.csd.read_blocklen) * |
paul@258 | 1107 | (r->payload.csd.device_size + 1) * |
paul@258 | 1108 | (1 << (r->payload.csd.device_size_multiplier + 2))); |
paul@258 | 1109 | printf("transfer speed: %d MHz\n", r->payload.csd.tran_speed == 0x32 ? 25 : 50); |
paul@258 | 1110 | printf("format group: %d %d\n", r->payload.csd.format, r->payload.csd.format_group); |
paul@258 | 1111 | printf("write time factor: %d\n", 1 << r->payload.csd.write_time_factor); |
paul@258 | 1112 | printf("write protect (temp perm): %s %s\n", r->payload.csd.temp_write_prot ? "yes" : "no", |
paul@258 | 1113 | r->payload.csd.perm_write_prot ? "yes" : "no"); |
paul@258 | 1114 | printf("write protect group (enable size): %s %d\n", r->payload.csd.write_prot_group_enable ? "yes" : "no", |
paul@258 | 1115 | r->payload.csd.write_prot_group_size + 1); |
paul@258 | 1116 | printf("write block (partial length): %s %d\n", r->payload.csd.write_block_partial ? "yes" : "no", |
paul@258 | 1117 | 1 << r->payload.csd.write_blocklen); |
paul@258 | 1118 | printf("read block (partial length): %s %d\n", r->payload.csd.read_block_partial ? "yes" : "no", |
paul@258 | 1119 | 1 << r->payload.csd.read_blocklen); |
paul@258 | 1120 | printf("erase: sector single: %d %s\n", r->payload.csd.erase_sector_size + 1, |
paul@258 | 1121 | r->payload.csd.erase_single_block_enable ? "yes" : "no"); |
paul@258 | 1122 | printf("misalign: read write: %s %s\n", r->payload.csd.read_block_misalign ? "yes" : "no", |
paul@258 | 1123 | r->payload.csd.write_block_misalign ? "yes" : "no"); |
paul@258 | 1124 | printf("max read current (min max): %d %d\n", r->payload.csd.max_read_current_min, |
paul@258 | 1125 | r->payload.csd.max_read_current_max); |
paul@258 | 1126 | printf("max write current (min max): %d %d\n", r->payload.csd.max_write_current_min, |
paul@258 | 1127 | r->payload.csd.max_write_current_max); |
paul@258 | 1128 | printf("read access time (1 2): %d %d\n", r->payload.csd.data_read_access_time_1, |
paul@258 | 1129 | r->payload.csd.data_read_access_time_2); |
paul@258 | 1130 | printf("DSR: %s\n", r->payload.csd.dsr_implemented ? "yes" : "no"); |
paul@258 | 1131 | } |
paul@258 | 1132 | } |
paul@258 | 1133 | |
paul@258 | 1134 | uint32_t |
paul@258 | 1135 | Msc_channel::recv_data(l4re_dma_space_dma_addr_t paddr, uint32_t count) |
paul@258 | 1136 | { |
paul@258 | 1137 | return transfer(_msc_start + Msc_recv_data_fifo, paddr, true, count); |
paul@258 | 1138 | } |
paul@258 | 1139 | |
paul@258 | 1140 | uint32_t |
paul@258 | 1141 | Msc_channel::send_data(l4re_dma_space_dma_addr_t paddr, uint32_t count) |
paul@258 | 1142 | { |
paul@258 | 1143 | return transfer(paddr, _msc_start + Msc_trans_data_fifo, false, count); |
paul@258 | 1144 | } |
paul@258 | 1145 | |
paul@258 | 1146 | uint32_t |
paul@258 | 1147 | Msc_channel::read_block(uint8_t card, l4re_dma_space_dma_addr_t paddr) |
paul@258 | 1148 | { |
paul@258 | 1149 | uint32_t block_size = 1 << _csd[card].read_blocklen; |
paul@258 | 1150 | uint16_t buffer[Response_size_R1]; |
paul@258 | 1151 | struct R1 *r = (struct R1 *) buffer; |
paul@258 | 1152 | |
paul@258 | 1153 | //printf("read_block: card %d -> %d\n", card, _rca[card]); |
paul@258 | 1154 | |
paul@258 | 1155 | // Select the requested card. |
paul@258 | 1156 | |
paul@258 | 1157 | if (_current_rca != _rca[card]) |
paul@258 | 1158 | { |
paul@258 | 1159 | if (!send_command(Command_select_deselect_card, _rca[card] << 16)) |
paul@258 | 1160 | return 0; |
paul@258 | 1161 | |
paul@258 | 1162 | read_response(buffer, Response_size_R1); |
paul@258 | 1163 | |
paul@258 | 1164 | if (r->status & R1_status_error_mask) |
paul@258 | 1165 | return 0; |
paul@258 | 1166 | |
paul@258 | 1167 | _current_rca = _rca[card]; |
paul@258 | 1168 | } |
paul@258 | 1169 | |
paul@258 | 1170 | #if 0 |
paul@258 | 1171 | // NOTE: SMEM cards should allow bus width setting. |
paul@258 | 1172 | // NOTE: SDIO cards have their bus width set in CCCR via CMD52. |
paul@258 | 1173 | |
paul@258 | 1174 | printf("set bus width -> %s\n", |
paul@258 | 1175 | send_app_command(App_command_set_bus_width, Bus_width_4bit) ? "set" : "not set"); |
paul@258 | 1176 | #endif |
paul@258 | 1177 | |
paul@258 | 1178 | if (!send_command(Command_set_blocklen, block_size)) |
paul@258 | 1179 | return 0; |
paul@258 | 1180 | |
paul@258 | 1181 | read_response(buffer, Response_size_R1); |
paul@258 | 1182 | |
paul@258 | 1183 | if (r->status & R1_status_error_mask) |
paul@258 | 1184 | return 0; |
paul@258 | 1185 | |
paul@258 | 1186 | // Apply block count and size properties to the issued command. |
paul@258 | 1187 | |
paul@258 | 1188 | _regs[Msc_block_count] = 1; |
paul@258 | 1189 | _regs[Msc_block_length] = block_size; |
paul@258 | 1190 | |
paul@258 | 1191 | // NOTE: Support an actual address. |
paul@258 | 1192 | // NOTE: Where CCS = 0, byte addressing is used. Otherwise, block addressing is used. |
paul@258 | 1193 | |
paul@258 | 1194 | if (!send_command(Command_read_single_block, 0)) |
paul@258 | 1195 | return 0; |
paul@258 | 1196 | |
paul@258 | 1197 | read_response(buffer, Response_size_R1); |
paul@258 | 1198 | |
paul@258 | 1199 | if (r->status & R1_status_error_mask) |
paul@258 | 1200 | return 0; |
paul@258 | 1201 | |
paul@258 | 1202 | return recv_data(paddr, block_size); |
paul@258 | 1203 | } |
paul@258 | 1204 | |
paul@258 | 1205 | // Wait indefinitely for an interrupt request, returning true if one was delivered. |
paul@258 | 1206 | |
paul@258 | 1207 | bool |
paul@258 | 1208 | Msc_channel::wait_for_irq(uint32_t flags) |
paul@258 | 1209 | { |
paul@258 | 1210 | return !l4_error(l4_irq_receive(_irq, L4_IPC_NEVER)) && |
paul@258 | 1211 | (_regs[Msc_interrupt_flag] & flags); |
paul@258 | 1212 | } |
paul@258 | 1213 | |
paul@258 | 1214 | // Wait up to the given timeout (in microseconds) for an interrupt request, |
paul@258 | 1215 | // returning true if one was delivered. |
paul@258 | 1216 | |
paul@258 | 1217 | bool |
paul@258 | 1218 | Msc_channel::wait_for_irq(uint32_t flags, unsigned int timeout) |
paul@258 | 1219 | { |
paul@258 | 1220 | return !l4_error(l4_irq_receive(_irq, l4_timeout(L4_IPC_TIMEOUT_NEVER, |
paul@258 | 1221 | l4util_micros2l4to(timeout)))) && |
paul@258 | 1222 | (_regs[Msc_interrupt_flag] & flags); |
paul@258 | 1223 | } |
paul@258 | 1224 | |
paul@258 | 1225 | |
paul@258 | 1226 | |
paul@258 | 1227 | // Peripheral abstraction. |
paul@258 | 1228 | |
paul@258 | 1229 | Msc_chip::Msc_chip(l4_addr_t msc_start, l4_addr_t start, l4_addr_t end) |
paul@258 | 1230 | : _msc_start(msc_start), _start(start), _end(end) |
paul@258 | 1231 | { |
paul@258 | 1232 | } |