/* * 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 #include #include "adm.h" #include /* 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; }