| /* |
| * Copyright (c) 2011, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <stdlib.h> |
| #include <reg.h> |
| |
| #include "adm.h" |
| #include <platform/adm.h> |
| |
| /* TODO: This ADM implementation is very specific for use by MMC. |
| * Replace with a generic ADM driver that can also be used |
| * by other peripherals such as usb/uart/nand/display etc. |
| */ |
| |
| /* TODO: |
| * adm module shouldn't have to include mmc.h. |
| * clean this up when generic adm interface is implemented. |
| */ |
| #include "mmc.h" |
| |
| extern void mdelay(unsigned msecs); |
| extern void dmb(void); |
| |
| /* limit the max_row_len to fifo size so that |
| * the same src and dst row attributes can be used. |
| */ |
| #define MAX_ROW_LEN MMC_BOOT_MCI_FIFO_SIZE |
| #define MAX_ROW_NUM 0xFFFF |
| |
| /* Structures for use with ADM: |
| * Must be aligned on 8 byte boundary. |
| */ |
| static uint32_t adm_cmd_ptr_list[8] __attribute__ ((aligned(8))); |
| static uint32_t box_mode_entry[8] __attribute__ ((aligned(8))); |
| |
| adm_result_t adm_transfer_start(uint32_t adm_chn, uint32_t * cmd_ptr_list); |
| |
| /* CRCI - mmc slot mapping. */ |
| extern uint8_t sdc_crci_map[5]; |
| |
| /* TODO: |
| * This interface is very specific to MMC. |
| * We need a generic ADM interface that can be easily |
| * used by other modules such as usb/uart/nand. |
| */ |
| adm_result_t |
| adm_transfer_mmc_data(unsigned char slot, |
| unsigned char *data_ptr, |
| unsigned int data_len, unsigned char direction) |
| { |
| uint32_t num_rows; |
| uint16_t row_len; |
| uint16_t row_offset; |
| uint32_t row_num; |
| uint32_t adm_crci_num; |
| adm_result_t result = ADM_RESULT_SUCCESS; |
| |
| /* Make sure slot value is in the range 1..4 */ |
| ASSERT((slot >= 1) && (slot <= 4)); |
| |
| adm_crci_num = sdc_crci_map[slot]; |
| row_len = MMC_BOOT_MCI_FIFO_SIZE; |
| num_rows = data_len / MMC_BOOT_MCI_FIFO_SIZE; |
| |
| /* While there is data to be transferred */ |
| while (data_len) { |
| if (data_len <= MAX_ROW_LEN) { |
| row_len = data_len; |
| row_offset = 0; |
| row_num = 1; |
| } else { |
| row_len = MAX_ROW_LEN; |
| row_offset = MAX_ROW_LEN; |
| row_num = data_len / MAX_ROW_LEN; |
| |
| /* Limit the number of row to the max value allowed */ |
| if (row_num > MAX_ROW_NUM) { |
| row_num = MAX_ROW_NUM; |
| } |
| } |
| |
| /* Program ADM registers and initiate data transfer */ |
| |
| /* Initialize the Box Mode command entry (single entry) */ |
| box_mode_entry[0] = (ADM_CMD_LIST_LC | |
| (adm_crci_num << 3) | ADM_ADDR_MODE_BOX); |
| |
| if (direction == ADM_MMC_READ) { |
| box_mode_entry[1] = MMC_BOOT_MCI_FIFO; /* SRC addr */ |
| box_mode_entry[2] = (uint32_t) data_ptr; /* DST addr */ |
| box_mode_entry[3] = ((row_len << 16) | /* SRC row len */ |
| (row_len << 0)); /* DST row len */ |
| box_mode_entry[4] = ((row_num << 16) | /* SRC row # */ |
| (row_num << 0)); /* DST row # */ |
| box_mode_entry[5] = ((0 << 16) | /* SRC offset */ |
| (row_offset << 0)); /* DST offset */ |
| } else { |
| box_mode_entry[1] = (uint32_t) data_ptr; /* SRC addr */ |
| box_mode_entry[2] = MMC_BOOT_MCI_FIFO; /* DST addr */ |
| box_mode_entry[3] = ((row_len << 16) | /* SRC row len */ |
| (row_len << 0)); /* DST row len */ |
| box_mode_entry[4] = ((row_num << 16) | /* SRC row # */ |
| (row_num << 0)); /* DST row # */ |
| box_mode_entry[5] = ((row_offset << 16) | /* SRC offset */ |
| (0 << 0)); /* DST offset */ |
| } |
| |
| /* Initialize the ADM Command Pointer List (single entry) */ |
| adm_cmd_ptr_list[0] = (ADM_CMD_PTR_LP | |
| ADM_CMD_PTR_CMD_LIST | |
| (((uint32_t) (&box_mode_entry[0])) >> |
| 3)); |
| |
| /* Start ADM transfer, this is a blocking call. */ |
| result = adm_transfer_start(ADM_CHN, adm_cmd_ptr_list); |
| |
| if (result != ADM_RESULT_SUCCESS) { |
| break; |
| } |
| |
| /* Update the data ptr and data len by the amount |
| * we just transferred. |
| */ |
| data_ptr += (row_len * row_num); |
| data_len -= (row_len * row_num); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Start the ADM data transfer and return the result of the transfer. |
| * Blocks until transfer is completed. |
| */ |
| adm_result_t adm_transfer_start(uint32_t adm_chn, uint32_t * cmd_ptr_list) |
| { |
| uint32_t reg_value; |
| uint32_t timeout = 1; |
| uint32_t delay_count = 100; |
| |
| /* Memory barrier to ensure that all ADM command list structure |
| * writes have completed before starting the ADM transfer. |
| */ |
| dmb(); |
| |
| /* Start the ADM transfer by writing the command ptr */ |
| writel(((uint32_t) cmd_ptr_list) >> 3, |
| ADM_REG_CMD_PTR(adm_chn, ADM_SD)); |
| |
| /* Poll the status register to check for transfer complete. |
| * Bail out if transfer is not finished within 1 sec. |
| * Note: This time depends on the amount of data being transferred. |
| * Increase the delay_count if this is not sufficient. |
| */ |
| do { |
| reg_value = readl(ADM_REG_STATUS(adm_chn, ADM_SD)); |
| if ((reg_value & ADM_REG_STATUS__RSLT_VLD___M) != 0) { |
| timeout = 0; |
| break; |
| } |
| |
| /* 10ms wait */ |
| mdelay(10); |
| |
| } |
| while (delay_count--); |
| |
| /* Read out the IRQ register to clear the interrupt. |
| * Even though we are not using interrupts, |
| * kernel is not clearing the interupts during its |
| * ADM initialization, causing it to crash. |
| */ |
| reg_value = readl(ADM_REG_IRQ(ADM_SD)); |
| |
| if (timeout) { |
| return ADM_RESULT_TIMEOUT; |
| } else { |
| /* Get the result from the RSLT FIFO */ |
| reg_value = readl(ADM_REG_RSLT(adm_chn, ADM_SD)); |
| |
| /* Check for any error */ |
| if (((reg_value & ADM_REG_RSLT__ERR___M) != 0) || |
| ((reg_value & ADM_REG_RSLT__TPD___M) == 0) || |
| ((reg_value & ADM_REG_RSLT__V___M) == 0)) { |
| return ADM_RESULT_FAILURE; |
| } |
| } |
| |
| return ADM_RESULT_SUCCESS; |
| } |