| /* |
| * drivers/mmc/host/ambarella_sd.c |
| * |
| * Author: Anthony Ginger <hfjiang@ambarella.com> |
| * Copyright (C) 2004-2009, Ambarella, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/ioport.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/clk.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/mmc/slot-gpio.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/slab.h> |
| #include <linux/blkdev.h> |
| #include <linux/scatterlist.h> |
| #include <linux/mmc/host.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/mmc/sd.h> |
| #include <linux/debugfs.h> |
| #include <asm/dma.h> |
| #include <mach/hardware.h> |
| #include <plat/fio.h> |
| #include <plat/sd.h> |
| #include <plat/event.h> |
| #include <plat/rct.h> |
| |
| static struct mmc_host *G_mmc[SD_INSTANCES * AMBA_SD_MAX_SLOT_NUM]; |
| |
| /* ==========================================================================*/ |
| #define CONFIG_SD_AMBARELLA_TIMEOUT_VAL (0xe) |
| #define CONFIG_SD_AMBARELLA_WAIT_TIMEOUT (HZ / 100) |
| #define CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT (100000) |
| #define CONFIG_SD_AMBARELLA_MAX_TIMEOUT (10 * HZ) |
| #define CONFIG_SD_AMBARELLA_VSW_PRE_SPEC (5) |
| #define CONFIG_SD_AMBARELLA_VSW_WAIT_LIMIT (1000) |
| |
| #undef CONFIG_SD_AMBARELLA_DEBUG |
| #undef CONFIG_SD_AMBARELLA_DEBUG_VERBOSE |
| #undef CONFIG_SD_AMBARELLA_TUNING_DEBUG |
| |
| #define ambsd_printk(level, phcinfo, format, arg...) \ |
| printk(level "%s.%u: " format, dev_name(phcinfo->pinfo->dev), \ |
| phcinfo->slot_id, ## arg) |
| |
| #define ambsd_err(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_ERR, phcinfo, format, ## arg) |
| #define ambsd_warn(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_WARNING, phcinfo, format, ## arg) |
| #define ambsd_info(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_INFO, phcinfo, format, ## arg) |
| #define ambsd_rtdbg(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_DEBUG, phcinfo, format, ## arg) |
| |
| #ifdef CONFIG_SD_AMBARELLA_DEBUG |
| #define ambsd_dbg(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_DEBUG, phcinfo, format, ## arg) |
| #else |
| #define ambsd_dbg(phcinfo, format, arg...) \ |
| ({ if (0) ambsd_printk(KERN_DEBUG, phcinfo, format, ##arg); 0; }) |
| #endif |
| |
| #ifdef CONFIG_SD_AMBARELLA_TUNING_DEBUG |
| #define ambsd_tuning_dbg(phcinfo, format, arg...) \ |
| ambsd_printk(KERN_DEBUG, phcinfo, format, ## arg) |
| #else |
| #define ambsd_tuning_dbg(phcinfo, format, arg...) \ |
| ({ if (0) ambsd_printk(KERN_DEBUG, phcinfo, format, ##arg); 0; }) |
| #endif |
| |
| |
| /* ==========================================================================*/ |
| enum ambarella_sd_state { |
| AMBA_SD_STATE_IDLE, |
| AMBA_SD_STATE_CMD, |
| AMBA_SD_STATE_DATA, |
| AMBA_SD_STATE_RESET, |
| AMBA_SD_STATE_ERR |
| }; |
| |
| struct ambarella_sd_phy_timing { |
| u32 mode; |
| u32 val0; |
| u32 val1; |
| }; |
| |
| struct ambarella_sd_mmc_info { |
| struct mmc_host *mmc; |
| struct mmc_request *mrq; |
| |
| wait_queue_head_t wait; |
| |
| enum ambarella_sd_state state; |
| |
| struct scatterlist *sg; |
| u32 sg_len; |
| u32 wait_tmo; |
| u16 blk_sz; |
| u16 blk_cnt; |
| u32 arg_reg; |
| u16 xfr_reg; |
| u16 cmd_reg; |
| u16 sta_reg; |
| u8 tmo; |
| u8 use_adma; |
| u32 sta_counter; |
| |
| char *buf_vaddress; |
| dma_addr_t buf_paddress; |
| u32 dma_address; |
| u32 dma_size; |
| |
| void (*pre_dma)(void *data); |
| void (*post_dma)(void *data); |
| |
| u32 slot_id; |
| struct ambarella_sd_controller_info *pinfo; |
| u32 valid; |
| int fixed_cd; |
| int fixed_wp; |
| |
| int pwr_gpio; |
| u8 pwr_gpio_active; |
| int v18_gpio; |
| u8 v18_gpio_active; |
| u32 no_1_8_v : 1, |
| caps_ddr : 1, |
| caps_adma : 1, |
| force_gpio : 1; |
| |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *state_work; |
| struct pinctrl_state *state_idle; |
| |
| struct notifier_block system_event; |
| struct semaphore system_event_sem; |
| |
| struct dentry *debugfs; |
| |
| #ifdef CONFIG_PM |
| u32 sd_nisen; |
| u32 sd_eisen; |
| u32 sd_nixen; |
| u32 sd_eixen; |
| #endif |
| }; |
| |
| struct ambarella_sd_controller_info { |
| unsigned char __iomem *regbase; |
| unsigned char __iomem *fio_reg; |
| unsigned char __iomem *timing_reg; |
| unsigned char __iomem *sbc_reg; |
| struct device *dev; |
| unsigned int irq; |
| u32 dma_fix; |
| u32 reset_error; |
| |
| u32 max_blk_sz; |
| struct kmem_cache *buf_cache; |
| |
| struct clk *clk; |
| u32 default_wait_tmo; |
| u32 switch_voltage_tmo; |
| u8 slot_num; |
| u32 phy_type; |
| struct ambarella_sd_phy_timing *phy_timing; |
| u32 phy_timing_num; |
| |
| struct ambarella_sd_mmc_info *pslotinfo[AMBA_SD_MAX_SLOT_NUM]; |
| struct mmc_ios controller_ios; |
| bool auto_tuning; |
| }; |
| |
| static const u8 tuning_blk_pattern_4bit[] = { |
| 0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc, |
| 0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef, |
| 0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb, |
| 0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef, |
| 0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c, |
| 0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee, |
| 0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff, |
| 0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde, |
| }; |
| |
| static const u8 tuning_blk_pattern_8bit[] = { |
| 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, |
| 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, |
| 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, |
| 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, |
| 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, |
| 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, |
| 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, |
| 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, |
| 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, |
| 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, |
| 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, |
| 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, |
| 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, |
| 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, |
| 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, |
| 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, |
| }; |
| |
| /* ==========================================================================*/ |
| #ifdef CONFIG_SD_AMBARELLA_DEBUG_VERBOSE |
| static void ambarella_sd_show_info(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| ambsd_dbg(pslotinfo, "Enter %s\n", __func__); |
| ambsd_dbg(pslotinfo, "sg = 0x%x.\n", (u32)pslotinfo->sg); |
| ambsd_dbg(pslotinfo, "sg_len = 0x%x.\n", pslotinfo->sg_len); |
| ambsd_dbg(pslotinfo, "tmo = 0x%x.\n", pslotinfo->tmo); |
| ambsd_dbg(pslotinfo, "blk_sz = 0x%x.\n", pslotinfo->blk_sz); |
| ambsd_dbg(pslotinfo, "blk_cnt = 0x%x.\n", pslotinfo->blk_cnt); |
| ambsd_dbg(pslotinfo, "arg_reg = 0x%x.\n", pslotinfo->arg_reg); |
| ambsd_dbg(pslotinfo, "xfr_reg = 0x%x.\n", pslotinfo->xfr_reg); |
| ambsd_dbg(pslotinfo, "cmd_reg = 0x%x.\n", pslotinfo->cmd_reg); |
| ambsd_dbg(pslotinfo, "buf_vaddress = 0x%x.\n", |
| (u32)pslotinfo->buf_vaddress); |
| ambsd_dbg(pslotinfo, "buf_paddress = 0x%x.\n", pslotinfo->buf_paddress); |
| ambsd_dbg(pslotinfo, "dma_address = 0x%x.\n", pslotinfo->dma_address); |
| ambsd_dbg(pslotinfo, "dma_size = 0x%x.\n", pslotinfo->dma_size); |
| ambsd_dbg(pslotinfo, "pre_dma = 0x%x.\n", (u32)pslotinfo->pre_dma); |
| ambsd_dbg(pslotinfo, "post_dma = 0x%x.\n", (u32)pslotinfo->post_dma); |
| ambsd_dbg(pslotinfo, "SD: state = 0x%x.\n", pslotinfo->state); |
| ambsd_dbg(pslotinfo, "Exit %s\n", __func__); |
| } |
| #endif |
| |
| void ambarella_detect_sd_slot(int slotid, int fixed_cd) |
| { |
| struct mmc_host *mmc; |
| struct ambarella_sd_mmc_info *pslotinfo; |
| |
| if (slotid >= SD_INSTANCES * AMBA_SD_MAX_SLOT_NUM) { |
| pr_err("%s: Invalid slotid: %d\n", __func__, slotid); |
| return; |
| } |
| |
| mmc = G_mmc[slotid]; |
| pslotinfo = mmc_priv(mmc); |
| pslotinfo->fixed_cd = fixed_cd; |
| |
| mmc_detect_change(mmc, 0); |
| } |
| EXPORT_SYMBOL(ambarella_detect_sd_slot); |
| |
| static ssize_t fixed_cd_get(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = file->private_data; |
| char tmp[4]; |
| |
| snprintf(tmp, sizeof(tmp), "%d\n", pslotinfo->fixed_cd); |
| tmp[3] = '\n'; |
| |
| return simple_read_from_buffer(buf, count, ppos, tmp, sizeof(tmp)); |
| } |
| |
| static ssize_t fixed_cd_set(struct file *file, const char __user *buf, |
| size_t size, loff_t *ppos) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = file->private_data; |
| char tmp[20]; |
| ssize_t len; |
| |
| len = simple_write_to_buffer(tmp, sizeof(tmp) - 1, ppos, buf, size); |
| if (len >= 0) { |
| tmp[len] = '\0'; |
| pslotinfo->fixed_cd = !!simple_strtoul(tmp, NULL, 0); |
| } |
| |
| mmc_detect_change(pslotinfo->mmc, 0); |
| |
| return len; |
| } |
| |
| static const struct file_operations fixed_cd_fops = { |
| .read = fixed_cd_get, |
| .write = fixed_cd_set, |
| .open = simple_open, |
| .llseek = default_llseek, |
| }; |
| |
| static void ambarella_sd_add_debugfs(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| struct mmc_host *mmc = pslotinfo->mmc; |
| struct dentry *root, *fixed_cd; |
| |
| if (!mmc->debugfs_root) |
| return; |
| |
| root = debugfs_create_dir("ambhost", mmc->debugfs_root); |
| if (IS_ERR_OR_NULL(root)) |
| goto err; |
| |
| pslotinfo->debugfs = root; |
| |
| fixed_cd = debugfs_create_file("fixed_cd", S_IWUSR | S_IRUGO, |
| pslotinfo->debugfs, pslotinfo, &fixed_cd_fops); |
| if (IS_ERR_OR_NULL(fixed_cd)) |
| goto err; |
| |
| return; |
| |
| err: |
| debugfs_remove_recursive(root); |
| pslotinfo->debugfs = NULL; |
| dev_err(pslotinfo->pinfo->dev, "failed to add debugfs\n"); |
| } |
| |
| static void ambarella_sd_remove_debugfs(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| debugfs_remove_recursive(pslotinfo->debugfs); |
| } |
| |
| static void ambarella_sd_check_dma_boundary(u32 address, u32 size, u32 max_size) |
| { |
| u32 start_512kb, end_512kb; |
| |
| start_512kb = (address) & (~(max_size - 1)); |
| end_512kb = (address + size - 1) & (~(max_size - 1)); |
| BUG_ON(start_512kb != end_512kb); |
| } |
| |
| static void ambarella_sd_pre_sg_to_dma(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| u32 i, offset; |
| |
| for (i = 0, offset = 0; i < pslotinfo->sg_len; i++) { |
| memcpy(pslotinfo->buf_vaddress + offset, |
| sg_virt(&pslotinfo->sg[i]), |
| pslotinfo->sg[i].length); |
| offset += pslotinfo->sg[i].length; |
| } |
| BUG_ON(offset != pslotinfo->dma_size); |
| dma_sync_single_for_device(pslotinfo->pinfo->dev, pslotinfo->buf_paddress, |
| pslotinfo->dma_size, DMA_TO_DEVICE); |
| pslotinfo->dma_address = pslotinfo->buf_paddress; |
| pslotinfo->blk_sz |= SD_BLK_SZ_512KB; |
| } |
| |
| static void ambarella_sd_pre_sg_to_adma(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| int i; |
| u32 offset; |
| u32 dma_len; |
| u32 remain_size; |
| u32 current_addr; |
| u32 word_num, byte_num; |
| |
| dma_len = dma_map_sg(pslotinfo->pinfo->dev, pslotinfo->sg, |
| pslotinfo->sg_len, DMA_TO_DEVICE); |
| for (i = 0, offset = 0; i < dma_len; i++) { |
| remain_size = sg_dma_len(&pslotinfo->sg[i]); |
| current_addr = sg_dma_address(&pslotinfo->sg[i]); |
| current_addr |= pslotinfo->pinfo->dma_fix; |
| if (sd_addr_is_unlign(current_addr)) { |
| ambsd_err(pslotinfo, "Please disable ADMA\n"); |
| BUG(); |
| } |
| |
| while (unlikely(remain_size > SD_ADMA_TBL_LINE_MAX_LEN)) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_WORD | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| current_addr += SD_ADMA_TBL_LINE_MAX_LEN; |
| remain_size -= SD_ADMA_TBL_LINE_MAX_LEN; |
| } |
| word_num = remain_size >> 2; |
| byte_num = remain_size - (word_num << 2); |
| if (word_num) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_WORD | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| (word_num << 16); |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| current_addr += (word_num << 2); |
| if (byte_num) { |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| } |
| } |
| if (byte_num) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| byte_num << 16; |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| } |
| if (unlikely(i == dma_len - 1)) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| SD_ADMA_TBL_ATTR_END; |
| } |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| } |
| dma_sync_single_for_device(pslotinfo->pinfo->dev, pslotinfo->buf_paddress, |
| offset, DMA_TO_DEVICE); |
| pslotinfo->dma_address = pslotinfo->buf_paddress; |
| pslotinfo->blk_sz |= SD_BLK_SZ_512KB; |
| } |
| |
| static void ambarella_sd_post_sg_to_dma(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| |
| dma_sync_single_for_cpu(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, |
| pslotinfo->dma_size, |
| DMA_TO_DEVICE); |
| } |
| |
| static void ambarella_sd_post_sg_to_adma(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| |
| dma_sync_single_for_cpu(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, |
| pslotinfo->dma_size, |
| DMA_FROM_DEVICE); |
| |
| dma_unmap_sg(pslotinfo->pinfo->dev, |
| pslotinfo->sg, |
| pslotinfo->sg_len, |
| DMA_TO_DEVICE); |
| } |
| |
| static void ambarella_sd_pre_dma_to_sg(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| |
| dma_sync_single_for_device(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, |
| pslotinfo->dma_size, |
| DMA_FROM_DEVICE); |
| |
| pslotinfo->dma_address = pslotinfo->buf_paddress; |
| pslotinfo->blk_sz |= SD_BLK_SZ_512KB; |
| } |
| |
| static void ambarella_sd_pre_adma_to_sg(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| int i; |
| u32 dma_len; |
| u32 offset; |
| u32 remain_size; |
| u32 current_addr; |
| u32 word_num, byte_num; |
| |
| dma_len = dma_map_sg(pslotinfo->pinfo->dev, pslotinfo->sg, |
| pslotinfo->sg_len, DMA_FROM_DEVICE); |
| for (i = 0, offset = 0; i < dma_len; i++) { |
| remain_size = sg_dma_len(&pslotinfo->sg[i]); |
| current_addr = sg_dma_address(&pslotinfo->sg[i]); |
| current_addr |= pslotinfo->pinfo->dma_fix; |
| if (sd_addr_is_unlign(current_addr)) { |
| ambsd_err(pslotinfo, "Please disable ADMA\n"); |
| BUG(); |
| } |
| |
| while (unlikely(remain_size > SD_ADMA_TBL_LINE_MAX_LEN)) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_WORD | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| current_addr += SD_ADMA_TBL_LINE_MAX_LEN; |
| remain_size -= SD_ADMA_TBL_LINE_MAX_LEN; |
| } |
| word_num = remain_size >> 2; |
| byte_num = remain_size - (word_num << 2); |
| if (word_num) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_WORD | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| (word_num << 16); |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| current_addr += (word_num << 2); |
| if (byte_num) { |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| } |
| } |
| if (byte_num) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) = |
| (SD_ADMA_TBL_ATTR_TRAN | |
| SD_ADMA_TBL_ATTR_VALID); |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| (byte_num << 16); |
| *(u32 *)(pslotinfo->buf_vaddress + offset + 4) = |
| current_addr; |
| } |
| if (unlikely(i == dma_len - 1)) { |
| *(u32 *)(pslotinfo->buf_vaddress + offset) |= |
| SD_ADMA_TBL_ATTR_END; |
| } |
| offset += SD_ADMA_TBL_LINE_SIZE; |
| } |
| dma_sync_single_for_device(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, offset, |
| DMA_TO_DEVICE); |
| pslotinfo->dma_address = pslotinfo->buf_paddress; |
| pslotinfo->blk_sz |= SD_BLK_SZ_512KB; |
| } |
| |
| static void ambarella_sd_post_dma_to_sg(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| u32 i, offset; |
| |
| dma_sync_single_for_cpu(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, |
| pslotinfo->dma_size, |
| DMA_FROM_DEVICE); |
| |
| for (i = 0, offset = 0; i < pslotinfo->sg_len; i++) { |
| memcpy(sg_virt(&pslotinfo->sg[i]), |
| pslotinfo->buf_vaddress + offset, |
| pslotinfo->sg[i].length); |
| offset += pslotinfo->sg[i].length; |
| } |
| BUG_ON(offset != pslotinfo->dma_size); |
| } |
| |
| static void ambarella_sd_post_adma_to_sg(void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = data; |
| |
| dma_sync_single_for_cpu(pslotinfo->pinfo->dev, |
| pslotinfo->buf_paddress, |
| pslotinfo->dma_size, |
| DMA_FROM_DEVICE); |
| |
| dma_unmap_sg(pslotinfo->pinfo->dev, |
| pslotinfo->sg, |
| pslotinfo->sg_len, |
| DMA_FROM_DEVICE); |
| } |
| |
| static void ambarella_sd_request_bus(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| |
| down(&pslotinfo->system_event_sem); |
| |
| if (pslotinfo->slot_id == 0) { |
| fio_select_lock(SELECT_FIO_SD); |
| } else { |
| fio_select_lock(SELECT_FIO_SDIO); |
| if (pslotinfo->force_gpio) |
| pinctrl_select_state(pslotinfo->pinctrl, pslotinfo->state_work); |
| } |
| |
| } |
| |
| static void ambarella_sd_release_bus(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| |
| if (pslotinfo->slot_id == 0) { |
| fio_unlock(SELECT_FIO_SD); |
| } else { |
| if (pslotinfo->force_gpio) |
| pinctrl_select_state(pslotinfo->pinctrl, pslotinfo->state_idle); |
| fio_unlock(SELECT_FIO_SDIO); |
| } |
| |
| up(&pslotinfo->system_event_sem); |
| } |
| |
| static void ambarella_sd_enable_int(struct mmc_host *mmc, u32 mask) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| if (pinfo->slot_num > 1) { |
| if (pslotinfo->slot_id == 0) |
| fio_amb_sd0_set_int(mask, 1); |
| else |
| fio_amb_sdio0_set_int(mask, 1); |
| } else { |
| amba_setbitsl(pinfo->regbase + SD_NISEN_OFFSET, mask); |
| amba_setbitsl(pinfo->regbase + SD_NIXEN_OFFSET, mask); |
| } |
| |
| } |
| |
| static void ambarella_sd_disable_int(struct mmc_host *mmc, u32 mask) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| if (pinfo->slot_num > 1) { |
| if (pslotinfo->slot_id == 0) |
| fio_amb_sd0_set_int(mask, 0); |
| else |
| fio_amb_sdio0_set_int(mask, 0); |
| } else { |
| amba_clrbitsl(pinfo->regbase + SD_NISEN_OFFSET, mask); |
| amba_clrbitsl(pinfo->regbase + SD_NIXEN_OFFSET, mask); |
| } |
| } |
| |
| static void ambarella_sd_set_iclk(struct mmc_host *mmc, u16 clk_div) |
| { |
| u16 clkreg; |
| u32 counter = 0; |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| clk_div <<= 8; |
| clk_div |= SD_CLK_ICLK_EN; |
| amba_writew(pinfo->regbase + SD_CLK_OFFSET, clk_div); |
| while (1) { |
| clkreg = amba_readw(pinfo->regbase + SD_CLK_OFFSET); |
| if (clkreg & SD_CLK_ICLK_STABLE) |
| break; |
| if ((clkreg & ~SD_CLK_ICLK_STABLE) != clk_div) { |
| amba_writew(pinfo->regbase + SD_CLK_OFFSET, clk_div); |
| udelay(1); |
| } |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, |
| "Wait SD_CLK_ICLK_STABLE = %d @ 0x%x\n", |
| counter, clkreg); |
| break; |
| } |
| } |
| } |
| |
| static void ambarella_sd_clear_clken(struct mmc_host *mmc) |
| { |
| u16 clkreg; |
| u32 counter = 0; |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| while (1) { |
| clkreg = amba_readw(pinfo->regbase + SD_CLK_OFFSET); |
| if (clkreg & SD_CLK_EN) { |
| amba_writew(pinfo->regbase + SD_CLK_OFFSET, |
| (clkreg & ~SD_CLK_EN)); |
| udelay(1); |
| } else { |
| break; |
| } |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, "%s(%d @ 0x%x)\n", |
| __func__, counter, clkreg); |
| break; |
| } |
| } |
| } |
| |
| static void ambarella_sd_set_clken(struct mmc_host *mmc) |
| { |
| u16 clkreg; |
| u32 counter = 0; |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| while (1) { |
| clkreg = amba_readw(pinfo->regbase + SD_CLK_OFFSET); |
| if (clkreg & SD_CLK_EN) { |
| break; |
| } else { |
| amba_writew(pinfo->regbase + SD_CLK_OFFSET, |
| (clkreg | SD_CLK_EN)); |
| udelay(1); |
| } |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, "%s(%d @ 0x%x)\n", |
| __func__, counter, clkreg); |
| break; |
| } |
| } |
| } |
| |
| static void ambarella_sd_reset_all(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 nis_flag = 0; |
| u32 eis_flag = 0; |
| u32 counter = 0; |
| u8 reset_reg; |
| |
| ambsd_dbg(pslotinfo, "Enter %s with state %u\n", |
| __func__, pslotinfo->state); |
| |
| ambarella_sd_disable_int(mmc, 0xFFFFFFFF); |
| amba_write2w(pinfo->regbase + SD_NIS_OFFSET, 0xFFFF, 0xFFFF); |
| |
| /*reset sd timing register*/ |
| if(pinfo->phy_type == 0 && pinfo->timing_reg) { |
| amba_writel(pinfo->sbc_reg, 0x0); |
| amba_writel(pinfo->timing_reg + 4, 0x0); |
| amba_writel(pinfo->timing_reg, 0x04070000); |
| } else if(pinfo->phy_type == 1) { |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_L, 0x0); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_H, 0x0); |
| } else if(pinfo->phy_type == 2 && pinfo->timing_reg) { |
| amba_writel(pinfo->timing_reg, amba_rct_readl(pinfo->timing_reg) |
| & 0x1c1f1c1f); |
| } |
| |
| amba_writeb(pinfo->regbase + SD_RESET_OFFSET, SD_RESET_ALL); |
| while (1) { |
| reset_reg = amba_readb(pinfo->regbase + SD_RESET_OFFSET); |
| if (!(reset_reg & SD_RESET_ALL)) |
| break; |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, "Wait SD_RESET_ALL....\n"); |
| break; |
| } |
| } |
| |
| ambarella_sd_set_iclk(mmc, 0x0000); |
| amba_writeb(pinfo->regbase + SD_TMO_OFFSET, |
| CONFIG_SD_AMBARELLA_TIMEOUT_VAL); |
| |
| nis_flag = SD_NISEN_REMOVAL | |
| SD_NISEN_INSERT | |
| SD_NISEN_DMA | |
| SD_NISEN_BLOCK_GAP | |
| SD_NISEN_XFR_DONE | |
| SD_NISEN_CMD_DONE; |
| eis_flag = SD_EISEN_ACMD12_ERR | |
| SD_EISEN_CURRENT_ERR | |
| SD_EISEN_DATA_BIT_ERR | |
| SD_EISEN_DATA_CRC_ERR | |
| SD_EISEN_DATA_TMOUT_ERR | |
| SD_EISEN_CMD_IDX_ERR | |
| SD_EISEN_CMD_BIT_ERR | |
| SD_EISEN_CMD_CRC_ERR | |
| SD_EISEN_CMD_TMOUT_ERR; |
| |
| if (pslotinfo->use_adma == 1) |
| eis_flag |= SD_EISEN_ADMA_ERR; |
| else |
| eis_flag &= ~SD_EISEN_ADMA_ERR; |
| |
| ambarella_sd_enable_int(mmc, (eis_flag << 16) | nis_flag); |
| |
| pslotinfo->state = AMBA_SD_STATE_RESET; |
| pinfo->reset_error = 0; |
| |
| ambsd_dbg(pslotinfo, "Exit %s with counter %u\n", __func__, counter); |
| } |
| |
| static void ambarella_sd_reset_cmd_line(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 counter = 0; |
| u8 reset_reg; |
| |
| ambsd_dbg(pslotinfo, "Enter %s with state %u\n", |
| __func__, pslotinfo->state); |
| |
| amba_writeb(pinfo->regbase + SD_RESET_OFFSET, SD_RESET_CMD); |
| while (1) { |
| reset_reg = amba_readb(pinfo->regbase + SD_RESET_OFFSET); |
| if (!(reset_reg & SD_RESET_CMD)) |
| break; |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, "Wait SD_RESET_CMD...\n"); |
| pinfo->reset_error = 1; |
| break; |
| } |
| } |
| |
| ambsd_dbg(pslotinfo, "Exit %s with counter %u\n", __func__, counter); |
| } |
| |
| static void ambarella_sd_reset_data_line(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 counter = 0; |
| u8 reset_reg; |
| |
| ambsd_dbg(pslotinfo, "Enter %s with state %u\n", |
| __func__, pslotinfo->state); |
| |
| amba_writeb(pinfo->regbase + SD_RESET_OFFSET, SD_RESET_DAT); |
| while (1) { |
| reset_reg = amba_readb(pinfo->regbase + SD_RESET_OFFSET); |
| if (!(reset_reg & SD_RESET_DAT)) |
| break; |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, "Wait SD_RESET_DAT...\n"); |
| pinfo->reset_error = 1; |
| break; |
| } |
| } |
| |
| ambsd_dbg(pslotinfo, "Exit %s with counter %u\n", __func__, counter); |
| } |
| |
| static inline void ambarella_sd_data_done( |
| struct ambarella_sd_mmc_info *pslotinfo, u16 nis, u16 eis) |
| { |
| struct mmc_data *data; |
| |
| if ((pslotinfo->state == AMBA_SD_STATE_CMD) && |
| ((pslotinfo->cmd_reg & 0x3) == SD_CMD_RSP_48BUSY)) { |
| if (eis) { |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| } else { |
| pslotinfo->state = AMBA_SD_STATE_IDLE; |
| } |
| wake_up(&pslotinfo->wait); |
| return; |
| } |
| if (pslotinfo->mrq == NULL) { |
| ambsd_dbg(pslotinfo, "%s: mrq is NULL, nis[0x%x] eis[0x%x]\n", |
| __func__, nis, eis); |
| return; |
| } |
| if (pslotinfo->mrq->data == NULL) { |
| ambsd_dbg(pslotinfo, "%s: data is NULL, nis[0x%x] eis[0x%x]\n", |
| __func__, nis, eis); |
| return; |
| } |
| |
| data = pslotinfo->mrq->data; |
| if (eis) { |
| if (eis & SD_EIS_DATA_BIT_ERR) { |
| data->error = -EILSEQ; |
| } else if (eis & SD_EIS_DATA_CRC_ERR) { |
| data->error = -EILSEQ; |
| } else if (eis & SD_EIS_ADMA_ERR) { |
| data->error = -EILSEQ; |
| } else if (eis & SD_EIS_DATA_TMOUT_ERR) { |
| data->error = -ETIMEDOUT; |
| } else { |
| data->error = -EIO; |
| } |
| #ifdef CONFIG_SD_AMBARELLA_DEBUG_VERBOSE |
| ambsd_err(pslotinfo, "%s: CMD[%u] get eis[0x%x]\n", __func__, |
| pslotinfo->mrq->cmd->opcode, eis); |
| #endif |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| wake_up(&pslotinfo->wait); |
| return; |
| } else { |
| data->bytes_xfered = pslotinfo->dma_size; |
| } |
| |
| pslotinfo->state = AMBA_SD_STATE_IDLE; |
| wake_up(&pslotinfo->wait); |
| } |
| |
| static inline void ambarella_sd_cmd_done( |
| struct ambarella_sd_mmc_info *pslotinfo, u16 nis, u16 eis) |
| { |
| struct mmc_command *cmd; |
| u32 rsp0, rsp1, rsp2, rsp3; |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u16 ac12es; |
| |
| if (pslotinfo->mrq == NULL) { |
| ambsd_dbg(pslotinfo, "%s: mrq is NULL, nis[0x%x] eis[0x%x]\n", |
| __func__, nis, eis); |
| return; |
| } |
| if (pslotinfo->mrq->cmd == NULL) { |
| ambsd_dbg(pslotinfo, "%s: cmd is NULL, nis[0x%x] eis[0x%x]\n", |
| __func__, nis, eis); |
| return; |
| } |
| |
| cmd = pslotinfo->mrq->cmd; |
| if (eis) { |
| if (eis & SD_EIS_CMD_BIT_ERR) { |
| cmd->error = -EILSEQ; |
| } else if (eis & SD_EIS_CMD_CRC_ERR) { |
| cmd->error = -EILSEQ; |
| } else if (eis & SD_EIS_CMD_TMOUT_ERR) { |
| cmd->error = -ETIMEDOUT; |
| } else if (eis & SD_EIS_ACMD12_ERR) { |
| ac12es = amba_readl(pinfo->regbase + SD_AC12ES_OFFSET); |
| if (ac12es & SD_AC12ES_TMOUT_ERROR) { |
| cmd->error = -ETIMEDOUT; |
| } else if (eis & SD_AC12ES_CRC_ERROR) { |
| cmd->error = -EILSEQ; |
| } else { |
| cmd->error = -EIO; |
| } |
| |
| if (pslotinfo->mrq->stop) { |
| pslotinfo->mrq->stop->error = cmd->error; |
| } else { |
| ambsd_err(pslotinfo, "%s NULL stop 0x%x %u\n", |
| __func__, ac12es, cmd->error); |
| } |
| } else { |
| cmd->error = -EIO; |
| } |
| #ifdef CONFIG_SD_AMBARELLA_DEBUG_VERBOSE |
| ambsd_err(pslotinfo, "%s: CMD[%u] get eis[0x%x]\n", __func__, |
| pslotinfo->mrq->cmd->opcode, eis); |
| #endif |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| wake_up(&pslotinfo->wait); |
| return; |
| } |
| |
| if (cmd->flags & MMC_RSP_136) { |
| rsp0 = amba_readl(pinfo->regbase + SD_RSP0_OFFSET); |
| rsp1 = amba_readl(pinfo->regbase + SD_RSP1_OFFSET); |
| rsp2 = amba_readl(pinfo->regbase + SD_RSP2_OFFSET); |
| rsp3 = amba_readl(pinfo->regbase + SD_RSP3_OFFSET); |
| cmd->resp[0] = ((rsp3 << 8) | (rsp2 >> 24)); |
| cmd->resp[1] = ((rsp2 << 8) | (rsp1 >> 24)); |
| cmd->resp[2] = ((rsp1 << 8) | (rsp0 >> 24)); |
| cmd->resp[3] = (rsp0 << 8); |
| } else { |
| cmd->resp[0] = amba_readl(pinfo->regbase + SD_RSP0_OFFSET); |
| } |
| |
| if ((pslotinfo->state == AMBA_SD_STATE_CMD) && |
| ((pslotinfo->cmd_reg & 0x3) != SD_CMD_RSP_48BUSY)) { |
| pslotinfo->state = AMBA_SD_STATE_IDLE; |
| wake_up(&pslotinfo->wait); |
| } |
| } |
| |
| static void ambarella_sd_set_clk(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 sd_clk, desired_clk, actual_clk, bneed_div = 1; |
| u16 clk_div = 0x0000; |
| |
| ambarella_sd_clear_clken(mmc); |
| if (ios->clock != 0) { |
| desired_clk = ios->clock; |
| if (desired_clk > mmc->f_max) |
| desired_clk = mmc->f_max; |
| |
| if (desired_clk < 10000000) { |
| /* Below 10Mhz, divide by sd controller */ |
| clk_set_rate(pinfo->clk, mmc->f_max); |
| } else { |
| clk_set_rate(pinfo->clk, desired_clk); |
| actual_clk = clk_get_rate(pinfo->clk); |
| bneed_div = 0; |
| } |
| |
| if (bneed_div) { |
| sd_clk = clk_get_rate(pinfo->clk); |
| for (clk_div = 0x0; clk_div <= 0x80;) { |
| if (clk_div == 0) |
| actual_clk = sd_clk; |
| else |
| actual_clk = sd_clk / (clk_div << 1); |
| |
| if (actual_clk <= desired_clk) |
| break; |
| |
| if (clk_div >= 0x80) |
| break; |
| |
| if (clk_div == 0x0) |
| clk_div = 0x1; |
| else |
| clk_div <<= 1; |
| } |
| } |
| ambsd_dbg(pslotinfo, "sd_pll = %lu.\n", clk_get_rate(pinfo->clk)); |
| ambsd_dbg(pslotinfo, "desired_clk = %u.\n", desired_clk); |
| ambsd_dbg(pslotinfo, "actual_clk = %u.\n", actual_clk); |
| ambsd_dbg(pslotinfo, "clk_div = %u.\n", clk_div); |
| ambarella_sd_set_iclk(mmc, clk_div); |
| ambarella_sd_set_clken(mmc); |
| } |
| } |
| |
| static void ambarella_sd_set_pwr(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| if (ios->power_mode == MMC_POWER_OFF) { |
| ambarella_sd_reset_all(pslotinfo->mmc); |
| amba_writeb(pinfo->regbase + SD_PWR_OFFSET, SD_PWR_OFF); |
| |
| if (gpio_is_valid(pslotinfo->pwr_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->pwr_gpio, |
| !pslotinfo->pwr_gpio_active); |
| msleep(300); |
| } |
| |
| if (gpio_is_valid(pslotinfo->v18_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->v18_gpio, |
| !pslotinfo->v18_gpio_active); |
| msleep(10); |
| } |
| } else if (ios->power_mode == MMC_POWER_UP) { |
| if (gpio_is_valid(pslotinfo->v18_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->v18_gpio, |
| !pslotinfo->v18_gpio_active); |
| msleep(10); |
| } |
| |
| if (gpio_is_valid(pslotinfo->pwr_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->pwr_gpio, |
| pslotinfo->pwr_gpio_active); |
| msleep(300); |
| } |
| |
| amba_writeb(pinfo->regbase + SD_PWR_OFFSET, |
| (SD_PWR_ON | SD_PWR_3_3V)); |
| } else if (ios->power_mode == MMC_POWER_ON) { |
| switch (1 << ios->vdd) { |
| case MMC_VDD_165_195: |
| case MMC_VDD_32_33: |
| case MMC_VDD_33_34: |
| break; |
| default: |
| ambsd_err(pslotinfo, "%s Wrong voltage[%u]!\n", |
| __func__, ios->vdd); |
| break; |
| } |
| } |
| msleep(1); |
| ambsd_dbg(pslotinfo, "pwr = 0x%x.\n", |
| amba_readb(pinfo->regbase + SD_PWR_OFFSET)); |
| } |
| |
| static void ambarella_sd_set_phy_timing( |
| struct ambarella_sd_controller_info *pinfo, int mode) |
| { |
| u32 i, val0, val1; |
| |
| if (pinfo->phy_type < 0 || !pinfo->phy_timing || pinfo->phy_timing_num == 0) |
| return; |
| |
| for (i = 0; i < pinfo->phy_timing_num; i++) { |
| if (pinfo->phy_timing[i].mode & (0x1 << mode)) |
| break; |
| } |
| |
| /* phy setting is not defined in DTS for this mode, so we use the |
| * default phy setting defined for DS mode. */ |
| if (i >= pinfo->phy_timing_num) |
| i = 0; |
| |
| val0 = pinfo->phy_timing[i].val0; |
| val1 = pinfo->phy_timing[i].val1; |
| |
| if (pinfo->phy_type == 0) { |
| /*using rct sd phy*/ |
| amba_writel(pinfo->timing_reg, val0 | 0x02000000); |
| amba_writel(pinfo->timing_reg, val0); |
| amba_writel(pinfo->regbase + SD_LAT_CTRL_OFFSET, val1); |
| } else if(pinfo->phy_type == 1) { |
| /*using sd controller as sd phy*/ |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_L, val0); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_H, val1); |
| } else { |
| u32 ms_delay = amba_rct_readl(pinfo->timing_reg); |
| ms_delay &= val0; |
| ms_delay |= val1; |
| amba_rct_writel(pinfo->timing_reg, ms_delay); |
| } |
| } |
| |
| static void ambarella_sd_set_bus(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u8 hostr = 0; |
| |
| hostr = amba_readb(pinfo->regbase + SD_HOST_OFFSET); |
| if (ios->bus_width == MMC_BUS_WIDTH_8) { |
| hostr |= SD_HOST_8BIT; |
| hostr &= ~(SD_HOST_4BIT); |
| } else if (ios->bus_width == MMC_BUS_WIDTH_4) { |
| hostr &= ~(SD_HOST_8BIT); |
| hostr |= SD_HOST_4BIT; |
| } else if (ios->bus_width == MMC_BUS_WIDTH_1) { |
| hostr &= ~(SD_HOST_8BIT); |
| hostr &= ~(SD_HOST_4BIT); |
| } else { |
| ambsd_err(pslotinfo, "Unknown bus_width[%u], assume 1bit.\n", |
| ios->bus_width); |
| hostr &= ~(SD_HOST_8BIT); |
| hostr &= ~(SD_HOST_4BIT); |
| } |
| |
| hostr &= ~SD_HOST_HIGH_SPEED; |
| switch (ios->timing) { |
| case MMC_TIMING_LEGACY: |
| case MMC_TIMING_MMC_HS: |
| case MMC_TIMING_MMC_HS200: |
| case MMC_TIMING_SD_HS: |
| case MMC_TIMING_UHS_SDR12: |
| case MMC_TIMING_UHS_SDR25: |
| case MMC_TIMING_UHS_SDR50: |
| case MMC_TIMING_UHS_SDR104: |
| amba_clrbitsl(pinfo->regbase + SD_XC_CTR_OFFSET, |
| SD_XC_CTR_DDR_EN); |
| amba_clrbitsw(pinfo->regbase + SD_HOST2_OFFSET, 0x0004); |
| break; |
| case MMC_TIMING_UHS_DDR50: |
| hostr |= SD_HOST_HIGH_SPEED; |
| amba_setbitsl(pinfo->regbase + SD_XC_CTR_OFFSET, |
| SD_XC_CTR_DDR_EN); |
| amba_setbitsw(pinfo->regbase + SD_HOST2_OFFSET, 0x0004); |
| break; |
| default: |
| amba_clrbitsl(pinfo->regbase + SD_XC_CTR_OFFSET, |
| SD_XC_CTR_DDR_EN); |
| amba_clrbitsw(pinfo->regbase + SD_HOST2_OFFSET, 0x0004); |
| ambsd_err(pslotinfo, "Unknown timing[%d], assume legacy.\n", |
| ios->timing); |
| break; |
| } |
| |
| amba_writeb(pinfo->regbase + SD_HOST_OFFSET, hostr); |
| ambsd_dbg(pslotinfo, "hostr = 0x%x.\n", hostr); |
| |
| if(!pinfo->auto_tuning || ios->timing == MMC_TIMING_LEGACY |
| || ios->timing == MMC_TIMING_MMC_HS || ios->timing == MMC_TIMING_SD_HS) |
| ambarella_sd_set_phy_timing(pinfo, ios->timing); |
| |
| } |
| |
| static void ambarella_sd_check_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| if ((pinfo->controller_ios.power_mode != ios->power_mode) || |
| (pinfo->controller_ios.vdd != ios->vdd) || |
| (pslotinfo->state == AMBA_SD_STATE_RESET)) { |
| ambarella_sd_set_pwr(mmc, ios); |
| pinfo->controller_ios.power_mode = ios->power_mode; |
| pinfo->controller_ios.vdd = ios->vdd; |
| } |
| |
| if ((pinfo->controller_ios.clock != ios->clock) || |
| (pslotinfo->state == AMBA_SD_STATE_RESET)) { |
| ambarella_sd_set_clk(mmc, ios); |
| pinfo->controller_ios.clock = ios->clock; |
| } |
| |
| if ((pinfo->controller_ios.bus_width != ios->bus_width) || |
| (pinfo->controller_ios.timing != ios->timing) || |
| (pslotinfo->state == AMBA_SD_STATE_RESET)) { |
| ambarella_sd_set_bus(mmc, ios); |
| pinfo->controller_ios.bus_width = ios->bus_width; |
| pinfo->controller_ios.timing = ios->timing; |
| } |
| |
| if (pslotinfo->state == AMBA_SD_STATE_RESET) { |
| pslotinfo->state = AMBA_SD_STATE_IDLE; |
| } |
| } |
| |
| static void ambarella_sd_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| ambarella_sd_request_bus(mmc); |
| ambarella_sd_check_ios(mmc, ios); |
| ambarella_sd_release_bus(mmc); |
| } |
| |
| static void ambarella_sd_recovery(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 latency = 0, sbc_reg = 0, timing_reg0 = 0, timing_reg1 = 0; |
| u32 sd_delay_sel0 = 0, sd_delay_sel1 = 0, divisor = 0; |
| |
| /*save the sd timing register*/ |
| if(pinfo->phy_type == 0 && pinfo->timing_reg) { |
| latency = amba_readl(pinfo->regbase + SD_LAT_CTRL_OFFSET); |
| timing_reg0 = amba_readl(pinfo->timing_reg); |
| timing_reg1 = amba_readl(pinfo->timing_reg + 4); |
| sbc_reg = amba_readl(pinfo->sbc_reg); |
| } else if(pinfo->phy_type == 1) { |
| sd_delay_sel0 = amba_readl(pinfo->regbase + SD_DELAY_SEL_L); |
| sd_delay_sel1 = amba_readl(pinfo->regbase + SD_DELAY_SEL_H); |
| } else if(pinfo->phy_type == 2 && pinfo->timing_reg) { |
| timing_reg0 = amba_readl(pinfo->timing_reg); |
| } |
| |
| divisor = (amba_readw(pinfo->regbase + SD_CLK_OFFSET) & 0xff00) >> 8; |
| |
| ambarella_sd_reset_all(mmc); |
| |
| /*restore clk*/ |
| ambarella_sd_clear_clken(mmc); |
| ambarella_sd_set_iclk(mmc, divisor); |
| ambarella_sd_set_clken(mmc); |
| ambarella_sd_set_bus(mmc, &pinfo->controller_ios); |
| |
| /*restore the sd timing register*/ |
| if(pinfo->phy_type == 0 && pinfo->timing_reg) { |
| amba_writel(pinfo->regbase + SD_LAT_CTRL_OFFSET, latency); |
| amba_writel(pinfo->timing_reg, timing_reg0); |
| amba_writel(pinfo->timing_reg + 4, timing_reg1); |
| amba_writel(pinfo->sbc_reg, sbc_reg); |
| } else if(pinfo->phy_type == 1) { |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_L, sd_delay_sel0); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_H, sd_delay_sel1); |
| } else if(pinfo->phy_type == 2 && pinfo->timing_reg) { |
| amba_writel(pinfo->timing_reg, timing_reg0); |
| } |
| |
| } |
| |
| static u32 ambarella_sd_check_cd(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| int cdpin; |
| |
| if (pslotinfo->fixed_cd != -1) { |
| cdpin = !!pslotinfo->fixed_cd; |
| } else { |
| cdpin = mmc_gpio_get_cd(mmc); |
| if (cdpin < 0) { |
| cdpin = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| cdpin &= SD_STA_CARD_INSERTED; |
| } |
| } |
| |
| return !!cdpin; |
| } |
| |
| static inline void ambarella_sd_prepare_tmo( |
| struct ambarella_sd_mmc_info *pslotinfo, |
| struct mmc_data *pmmcdata) |
| { |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| pslotinfo->tmo = CONFIG_SD_AMBARELLA_TIMEOUT_VAL; |
| pslotinfo->wait_tmo = min_t(u32, pinfo->default_wait_tmo, CONFIG_SD_AMBARELLA_MAX_TIMEOUT); |
| pslotinfo->sta_counter = pinfo->default_wait_tmo / CONFIG_SD_AMBARELLA_MAX_TIMEOUT + 1; |
| |
| ambsd_dbg(pslotinfo, "timeout_ns = %u, timeout_clks = %u, " |
| "wait_tmo = %u, tmo = %u, sta_counter = %u.\n", |
| pmmcdata->timeout_ns, pmmcdata->timeout_clks, |
| pslotinfo->wait_tmo, pslotinfo->tmo, pslotinfo->sta_counter); |
| } |
| |
| static inline void ambarella_sd_pre_cmd(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| pslotinfo->state = AMBA_SD_STATE_CMD; |
| pslotinfo->sg_len = 0; |
| pslotinfo->sg = NULL; |
| pslotinfo->blk_sz = 0; |
| pslotinfo->blk_cnt = 0; |
| pslotinfo->arg_reg = 0; |
| pslotinfo->cmd_reg = 0; |
| pslotinfo->sta_reg = 0; |
| pslotinfo->tmo = CONFIG_SD_AMBARELLA_TIMEOUT_VAL; |
| pslotinfo->wait_tmo = (1 * HZ); |
| pslotinfo->xfr_reg = 0; |
| pslotinfo->dma_address = 0; |
| pslotinfo->dma_size = 0; |
| |
| if (pslotinfo->mrq->stop) { |
| if (likely(pslotinfo->mrq->stop->opcode == |
| MMC_STOP_TRANSMISSION)) { |
| pslotinfo->xfr_reg = SD_XFR_AC12_EN; |
| } else { |
| ambsd_err(pslotinfo, "%s strange stop cmd%u\n", |
| __func__, pslotinfo->mrq->stop->opcode); |
| } |
| } |
| |
| if (!(pslotinfo->mrq->cmd->flags & MMC_RSP_PRESENT)) |
| pslotinfo->cmd_reg = SD_CMD_RSP_NONE; |
| else if (pslotinfo->mrq->cmd->flags & MMC_RSP_136) |
| pslotinfo->cmd_reg = SD_CMD_RSP_136; |
| else if (pslotinfo->mrq->cmd->flags & MMC_RSP_BUSY) |
| pslotinfo->cmd_reg = SD_CMD_RSP_48BUSY; |
| else |
| pslotinfo->cmd_reg = SD_CMD_RSP_48; |
| if (pslotinfo->mrq->cmd->flags & MMC_RSP_CRC) |
| pslotinfo->cmd_reg |= SD_CMD_CHKCRC; |
| if (pslotinfo->mrq->cmd->flags & MMC_RSP_OPCODE) |
| pslotinfo->cmd_reg |= SD_CMD_CHKIDX; |
| pslotinfo->cmd_reg |= SD_CMD_IDX(pslotinfo->mrq->cmd->opcode); |
| pslotinfo->arg_reg = pslotinfo->mrq->cmd->arg; |
| |
| if (pslotinfo->mrq->data) { |
| pslotinfo->state = AMBA_SD_STATE_DATA; |
| ambarella_sd_prepare_tmo(pslotinfo, pslotinfo->mrq->data); |
| pslotinfo->blk_sz = (pslotinfo->mrq->data->blksz & 0xFFF); |
| pslotinfo->dma_size = pslotinfo->mrq->data->blksz * |
| pslotinfo->mrq->data->blocks; |
| pslotinfo->sg_len = pslotinfo->mrq->data->sg_len; |
| pslotinfo->sg = pslotinfo->mrq->data->sg; |
| pslotinfo->xfr_reg |= SD_XFR_DMA_EN; |
| pslotinfo->cmd_reg |= SD_CMD_DATA; |
| pslotinfo->blk_cnt = pslotinfo->mrq->data->blocks; |
| if (pslotinfo->blk_cnt > 1) { |
| pslotinfo->xfr_reg |= SD_XFR_MUL_SEL; |
| pslotinfo->xfr_reg |= SD_XFR_BLKCNT_EN; |
| } |
| if (pslotinfo->mrq->data->flags & MMC_DATA_STREAM) { |
| pslotinfo->xfr_reg |= SD_XFR_MUL_SEL; |
| pslotinfo->xfr_reg &= ~SD_XFR_BLKCNT_EN; |
| } |
| if (pslotinfo->mrq->data->flags & MMC_DATA_WRITE) { |
| pslotinfo->xfr_reg &= ~SD_XFR_CTH_SEL; |
| pslotinfo->sta_reg = (SD_STA_WRITE_XFR_ACTIVE | |
| SD_STA_DAT_ACTIVE); |
| if (pslotinfo->use_adma == 1) { |
| pslotinfo->pre_dma = &ambarella_sd_pre_sg_to_adma; |
| pslotinfo->post_dma = &ambarella_sd_post_sg_to_adma; |
| } else { |
| pslotinfo->pre_dma = &ambarella_sd_pre_sg_to_dma; |
| pslotinfo->post_dma = &ambarella_sd_post_sg_to_dma; |
| } |
| } else { |
| pslotinfo->xfr_reg |= SD_XFR_CTH_SEL; |
| pslotinfo->sta_reg = (SD_STA_READ_XFR_ACTIVE | |
| SD_STA_DAT_ACTIVE); |
| if (pslotinfo->use_adma == 1) { |
| pslotinfo->pre_dma = &ambarella_sd_pre_adma_to_sg; |
| pslotinfo->post_dma = &ambarella_sd_post_adma_to_sg; |
| } else { |
| pslotinfo->pre_dma = &ambarella_sd_pre_dma_to_sg; |
| pslotinfo->post_dma = &ambarella_sd_post_dma_to_sg; |
| } |
| } |
| pslotinfo->pre_dma(pslotinfo); |
| if (pinfo->dma_fix) { |
| pslotinfo->dma_address |= pinfo->dma_fix; |
| } |
| } |
| } |
| |
| static inline void ambarella_sd_send_cmd(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 valid_request, sta_reg, tmpreg, counter = 0; |
| long timeout; |
| |
| ambarella_sd_request_bus(pslotinfo->mmc); |
| |
| valid_request = ambarella_sd_check_cd(pslotinfo->mmc); |
| ambsd_dbg(pslotinfo, "cmd = %u valid_request = %u.\n", |
| pslotinfo->mrq->cmd->opcode, valid_request); |
| if (!valid_request) { |
| pslotinfo->mrq->cmd->error = -ENOMEDIUM; |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| goto ambarella_sd_send_cmd_exit; |
| } |
| |
| ambarella_sd_check_ios(pslotinfo->mmc, &pslotinfo->mmc->ios); |
| if (pslotinfo->mrq->data) { |
| while (1) { |
| sta_reg = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| if ((sta_reg & SD_STA_CMD_INHIBIT_DAT) == 0) { |
| break; |
| } |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| if(pslotinfo->mrq->cmd->opcode != MMC_SEND_TUNING_BLOCK) |
| ambsd_warn(pslotinfo, |
| "Wait SD_STA_CMD_INHIBIT_DAT...\n"); |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| pslotinfo->mrq->data->error = -EILSEQ; |
| pinfo->reset_error = 1; |
| goto ambarella_sd_send_cmd_exit; |
| } |
| } |
| |
| amba_writeb(pinfo->regbase + SD_TMO_OFFSET, pslotinfo->tmo); |
| if (pslotinfo->use_adma == 1) { |
| amba_setbitsb(pinfo->regbase + SD_HOST_OFFSET, |
| SD_HOST_ADMA); |
| amba_writel(pinfo->regbase + SD_ADMA_ADDR_OFFSET, |
| pslotinfo->dma_address); |
| } else { |
| amba_clrbitsb(pinfo->regbase + SD_HOST_OFFSET, |
| SD_HOST_ADMA); |
| amba_writel(pinfo->regbase + SD_DMA_ADDR_OFFSET, |
| pslotinfo->dma_address); |
| } |
| amba_write2w(pinfo->regbase + SD_BLK_SZ_OFFSET, |
| pslotinfo->blk_sz, pslotinfo->blk_cnt); |
| amba_writel(pinfo->regbase + SD_ARG_OFFSET, pslotinfo->arg_reg); |
| amba_write2w(pinfo->regbase + SD_XFR_OFFSET, |
| pslotinfo->xfr_reg, pslotinfo->cmd_reg); |
| } else { |
| while (1) { |
| sta_reg = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| if ((sta_reg & SD_STA_CMD_INHIBIT_CMD) == 0) { |
| break; |
| } |
| counter++; |
| if (counter > CONFIG_SD_AMBARELLA_WAIT_COUNTER_LIMIT) { |
| ambsd_warn(pslotinfo, |
| "Wait SD_STA_CMD_INHIBIT_CMD...\n"); |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| pslotinfo->mrq->cmd->error = -EILSEQ; |
| pinfo->reset_error = 1; |
| goto ambarella_sd_send_cmd_exit; |
| } |
| } |
| |
| amba_writel(pinfo->regbase + SD_ARG_OFFSET, pslotinfo->arg_reg); |
| amba_write2w(pinfo->regbase + SD_XFR_OFFSET, |
| 0x00, pslotinfo->cmd_reg); |
| } |
| |
| ambarella_sd_send_cmd_exit: |
| if (pslotinfo->state == AMBA_SD_STATE_CMD) { |
| timeout = wait_event_timeout(pslotinfo->wait, |
| (pslotinfo->state != AMBA_SD_STATE_CMD), |
| pslotinfo->wait_tmo); |
| if (pslotinfo->state == AMBA_SD_STATE_CMD) { |
| ambsd_err(pslotinfo, |
| "cmd%u %u@[%ld:%u], sta=0x%04x\n", |
| pslotinfo->mrq->cmd->opcode, |
| pslotinfo->state, timeout, pslotinfo->wait_tmo, |
| amba_readl(pinfo->regbase + SD_STA_OFFSET)); |
| pslotinfo->mrq->cmd->error = -ETIMEDOUT; |
| } |
| } else if (pslotinfo->state == AMBA_SD_STATE_DATA) { |
| do { |
| timeout = wait_event_timeout(pslotinfo->wait, |
| (pslotinfo->state != AMBA_SD_STATE_DATA), |
| pslotinfo->wait_tmo); |
| sta_reg = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| if ((pslotinfo->state == AMBA_SD_STATE_DATA) && |
| (sta_reg & pslotinfo->sta_reg)) { |
| ambsd_rtdbg(pslotinfo, "data%u %u@" |
| "[%ld:%u:%u:%u], sta=0x%04x:0x%04x\n", |
| pslotinfo->mrq->cmd->opcode, |
| pslotinfo->state, timeout, |
| pslotinfo->wait_tmo, |
| pslotinfo->mrq->data->timeout_ns, |
| pslotinfo->mrq->data->timeout_clks, |
| sta_reg, pslotinfo->sta_reg); |
| ambsd_rtdbg(pslotinfo, |
| "DMA %u in %u sg [0x%08x:0x%08x]\n", |
| pslotinfo->dma_size, |
| pslotinfo->sg_len, |
| pslotinfo->dma_address, |
| pslotinfo->dma_size); |
| tmpreg = amba_readw(pinfo->regbase + |
| SD_BLK_CNT_OFFSET); |
| if (tmpreg) { |
| ambsd_rtdbg(pslotinfo, |
| "SD_DMA_ADDR_OFFSET[0x%08X]\n", |
| amba_readl(pinfo->regbase + |
| SD_DMA_ADDR_OFFSET)); |
| amba_writel((pinfo->regbase + |
| SD_DMA_ADDR_OFFSET), |
| amba_readl(pinfo->regbase + |
| SD_DMA_ADDR_OFFSET)); |
| } else { |
| ambsd_rtdbg(pslotinfo, |
| "SD_DATA_OFFSET[0x%08X]\n", |
| amba_readl(pinfo->regbase + |
| SD_DATA_OFFSET)); |
| ambsd_rtdbg(pslotinfo, |
| "SD_STA_OFFSET[0x%08X]\n", |
| amba_readl(pinfo->regbase + |
| SD_STA_OFFSET)); |
| } |
| } else { |
| break; |
| } |
| } while (pslotinfo->sta_counter--); |
| if (pslotinfo->state == AMBA_SD_STATE_DATA) { |
| ambsd_err(pslotinfo, |
| "data%u %u@%u, sta=0x%04x:0x%04x\n", |
| pslotinfo->mrq->cmd->opcode, |
| pslotinfo->state, |
| pslotinfo->wait_tmo, |
| sta_reg, pslotinfo->sta_reg); |
| pslotinfo->mrq->data->error = -ETIMEDOUT; |
| } |
| } |
| } |
| |
| static inline void ambarella_sd_post_cmd(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| if (pslotinfo->state == AMBA_SD_STATE_IDLE) { |
| ambarella_sd_release_bus(pslotinfo->mmc); |
| if (pslotinfo->mrq->data) { |
| pslotinfo->post_dma(pslotinfo); |
| } |
| } else { |
| #ifdef CONFIG_SD_AMBARELLA_DEBUG_VERBOSE |
| u32 counter = 0; |
| |
| ambsd_err(pslotinfo, "CMD%u retries[%u] state[%u].\n", |
| pslotinfo->mrq->cmd->opcode, |
| pslotinfo->mrq->cmd->retries, |
| pslotinfo->state); |
| for (counter = 0; counter < 0x100; counter += 4) { |
| ambsd_err(pslotinfo, "0x%04x: 0x%08x\n", |
| counter, amba_readl(pinfo->regbase + counter)); |
| } |
| ambarella_sd_show_info(pslotinfo); |
| #endif |
| if (pinfo->reset_error) { |
| ambarella_sd_recovery(pslotinfo->mmc); |
| //ambarella_sd_reset_all(pslotinfo->mmc); |
| } |
| ambarella_sd_release_bus(pslotinfo->mmc); |
| } |
| } |
| |
| static void ambarella_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| |
| pslotinfo->mrq = mrq; |
| ambarella_sd_pre_cmd(pslotinfo); |
| ambarella_sd_send_cmd(pslotinfo); |
| ambarella_sd_post_cmd(pslotinfo); |
| pslotinfo->mrq = NULL; |
| mmc_request_done(mmc, mrq); |
| } |
| |
| static int ambarella_sd_get_ro(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| int wpspl; |
| |
| ambarella_sd_request_bus(mmc); |
| |
| if (pslotinfo->fixed_wp != -1) { |
| wpspl = !!pslotinfo->fixed_wp; |
| } else { |
| wpspl = mmc_gpio_get_ro(mmc); |
| if (wpspl < 0) { |
| wpspl = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| wpspl &= SD_STA_WPS_PL; |
| } |
| } |
| |
| ambarella_sd_release_bus(mmc); |
| |
| ambsd_dbg(pslotinfo, "RO[%u].\n", wpspl); |
| return !!wpspl; |
| } |
| |
| static int ambarella_sd_get_cd(struct mmc_host *mmc) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| u32 cdpin; |
| |
| ambarella_sd_request_bus(mmc); |
| cdpin = ambarella_sd_check_cd(mmc); |
| ambarella_sd_release_bus(mmc); |
| |
| ambsd_dbg(pslotinfo, "CD[%u].\n", cdpin); |
| return cdpin ? 1 : 0; |
| } |
| |
| static void ambarella_sd_enable_sdio_irq(struct mmc_host *mmc, int enable) |
| { |
| if (enable) |
| ambarella_sd_enable_int(mmc, SD_NISEN_CARD); |
| else |
| ambarella_sd_disable_int(mmc, SD_NISEN_CARD); |
| } |
| |
| static int ambarella_sd_ssvs(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| int retval = 0; |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| |
| ambarella_sd_request_bus(mmc); |
| if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) { |
| amba_writeb(pinfo->regbase + SD_PWR_OFFSET, |
| (SD_PWR_ON | SD_PWR_3_3V)); |
| |
| if (gpio_is_valid(pslotinfo->v18_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->v18_gpio, |
| !pslotinfo->v18_gpio_active); |
| msleep(10); |
| } |
| } else if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180) { |
| ambarella_sd_clear_clken(mmc); |
| msleep(CONFIG_SD_AMBARELLA_VSW_PRE_SPEC); |
| amba_writeb(pinfo->regbase + SD_PWR_OFFSET, |
| (SD_PWR_ON | SD_PWR_1_8V)); |
| |
| if (gpio_is_valid(pslotinfo->v18_gpio)) { |
| gpio_set_value_cansleep(pslotinfo->v18_gpio, |
| pslotinfo->v18_gpio_active); |
| msleep(pinfo->switch_voltage_tmo); |
| } |
| ambarella_sd_set_clken(mmc); |
| } |
| ambarella_sd_release_bus(mmc); |
| |
| return retval; |
| } |
| |
| static int ambarella_sd_card_busy(struct mmc_host *mmc) |
| { |
| int retval = 0; |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 sta_reg; |
| |
| ambarella_sd_request_bus(mmc); |
| sta_reg = amba_readl(pinfo->regbase + SD_STA_OFFSET); |
| ambsd_dbg(pslotinfo, "SD_STA_OFFSET = 0x%08X.\n", sta_reg); |
| retval = !(sta_reg & 0x1F00000); |
| ambarella_sd_release_bus(mmc); |
| |
| return retval; |
| } |
| |
| static int ambarella_send_tuning_cmd(struct mmc_host *host, u32 opcode, int *cmd_error) |
| { |
| struct mmc_request mrq = {NULL}; |
| struct mmc_command cmd = {0}; |
| struct mmc_data data = {0}; |
| struct scatterlist sg; |
| struct mmc_ios *ios = &host->ios; |
| const u8 *tuning_block_pattern; |
| int size, err = 0; |
| u8 *data_buf; |
| |
| if (ios->bus_width == MMC_BUS_WIDTH_8) { |
| tuning_block_pattern = tuning_blk_pattern_8bit; |
| size = sizeof(tuning_blk_pattern_8bit); |
| } else if (ios->bus_width == MMC_BUS_WIDTH_4) { |
| tuning_block_pattern = tuning_blk_pattern_4bit; |
| size = sizeof(tuning_blk_pattern_4bit); |
| } else |
| return -EINVAL; |
| |
| data_buf = kzalloc(size, GFP_KERNEL); |
| if (!data_buf) |
| return -ENOMEM; |
| |
| mrq.cmd = &cmd; |
| mrq.data = &data; |
| |
| cmd.opcode = opcode; |
| cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; |
| |
| data.blksz = size; |
| data.blocks = 1; |
| data.flags = MMC_DATA_READ; |
| |
| /* |
| * According to the tuning specs, Tuning process |
| * is normally shorter 40 executions of CMD19, |
| * and timeout value should be shorter than 150 ms |
| */ |
| data.timeout_ns = 150 * NSEC_PER_MSEC; |
| |
| data.sg = &sg; |
| data.sg_len = 1; |
| sg_init_one(&sg, data_buf, size); |
| |
| mmc_wait_for_req(host, &mrq); |
| |
| if (cmd_error) |
| *cmd_error = cmd.error; |
| |
| if (cmd.error) { |
| err = cmd.error; |
| goto out; |
| } |
| |
| if (data.error) { |
| err = data.error; |
| goto out; |
| } |
| |
| if (memcmp(data_buf, tuning_block_pattern, size)) |
| err = -EIO; |
| |
| out: |
| kfree(data_buf); |
| return err; |
| |
| } |
| |
| static int ambarella_sd_execute_tuning(struct mmc_host *mmc, u32 opcode) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo = mmc_priv(mmc); |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| u32 tmp, misc, lat, s = -1, e = 0, middle; |
| u32 best_misc = 0, best_s = -1, best_e = 0, doing_retune = 0; |
| int dly, longest_range = 0, range = 0; |
| u32 clock = pinfo->controller_ios.clock; |
| |
| if(!pinfo->auto_tuning || !pinfo->timing_reg) |
| return 0; |
| |
| retry: |
| if (doing_retune) { |
| clock -= 10000000; |
| if (clock <= 50000000) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning: can't find any valid timing\n"); |
| return -ECANCELED; |
| } |
| |
| pinfo->controller_ios.clock = clock; |
| ambarella_sd_set_clk(mmc, &pinfo->controller_ios); |
| } |
| |
| if(pinfo->phy_type == 0) |
| goto phy_type_0; |
| else if (pinfo->phy_type == 1) |
| goto phy_type_1; |
| else if (pinfo->phy_type == 2) |
| goto phy_type_2; |
| |
| phy_type_0: |
| for (misc = 0; misc < 4; misc++) { |
| writel_relaxed(0x8001, pinfo->sbc_reg); |
| |
| tmp = readl_relaxed(pinfo->timing_reg); |
| tmp &= 0x0000ffff; |
| tmp |= ((misc >> 1) & 0x1) << 19; |
| writel_relaxed(tmp | (1 << 25), pinfo->timing_reg); |
| usleep_range(5, 10); |
| writel_relaxed(tmp, pinfo->timing_reg); |
| usleep_range(5, 10); |
| lat = ((misc >> 0) & 0x1) + 1; |
| tmp = (lat << 12) | (lat << 8) | (lat << 4) | (lat << 0); |
| writel_relaxed(tmp, pinfo->regbase + SD_LAT_CTRL_OFFSET); |
| |
| for (dly = 0; dly < 4; dly++) { |
| tmp = readl_relaxed(pinfo->sbc_reg); |
| tmp &= 0xfffffff9; |
| // tmp |= (((dly >> 6) & 0x3) << 1); |
| tmp |= dly; |
| writel_relaxed(tmp, pinfo->sbc_reg); |
| /* |
| sel = dly % 64; |
| if (sel < 0x20) |
| sel = 63 - sel; |
| else |
| sel = sel - 32; |
| |
| tmp = (sel << 16) | (sel << 8) | (sel << 0); |
| writel_relaxed(tmp, pinfo->timing_reg + 4); |
| */ |
| if (ambarella_send_tuning_cmd(mmc, opcode, NULL) == 0) { |
| /* Tuning is successful at this tuning point */ |
| if (s == -1) |
| s = dly; |
| e = dly; |
| range++; |
| } else { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| |
| if (range > longest_range) { |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| } |
| |
| /* in case the last timings are all working */ |
| if (range > longest_range) { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning last: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| |
| if (longest_range == 0) { |
| if (clock > 50000000) { |
| doing_retune = 1; |
| goto retry; |
| } |
| |
| return -EIO; |
| } |
| |
| middle = (best_s + best_e) / 2; |
| |
| // tmp = (((middle >> 6) & 0x3) << 1) | 0x8001; |
| tmp = (middle << 1) | 0x8001; |
| writel_relaxed(tmp, pinfo->sbc_reg); |
| /* |
| sel = middle % 64; |
| if (sel < 0x20) |
| sel = 63 - sel; |
| else |
| sel = sel - 32; |
| |
| tmp = (sel << 16) | (sel << 8) | (sel << 0); |
| writel_relaxed(tmp, pinfo->timing_reg + 4); |
| */ |
| tmp = readl_relaxed(pinfo->timing_reg); |
| tmp &= 0x0000ffff; |
| tmp |= ((best_misc >> 1) & 0x1) << 19; |
| writel_relaxed(tmp | (1 << 25), pinfo->timing_reg); |
| usleep_range(5, 10); |
| writel_relaxed(tmp, pinfo->timing_reg); |
| usleep_range(5, 10); |
| |
| lat = ((best_misc >> 0) & 0x1) + 1; |
| tmp = (lat << 12) | (lat << 8) | (lat << 4) | (lat << 0); |
| writel_relaxed(tmp, pinfo->regbase + SD_LAT_CTRL_OFFSET); |
| |
| ambarella_sd_recovery(mmc); |
| |
| return 0; |
| |
| phy_type_1: |
| |
| /* misc: 3 bits data out delay |
| * dly: 2 bits clk mode and 3 bits clk out delay, total 5 bits; |
| */ |
| |
| for(misc = 0; misc < 8; misc++) { |
| for(dly = 0; dly < 32; dly++) { |
| tmp = (misc << 9) | (misc << 15) | (misc << 21) | (misc << 27); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_L, tmp); |
| tmp = ((dly >> 3) << 25) | ((dly & 0x07) << 22) | (misc << 1) |
| | (misc << 7) | (misc << 13) | (misc << 19); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_H, tmp); |
| |
| if (ambarella_send_tuning_cmd(mmc, opcode, NULL) == 0) { |
| /* Tuning is successful at this tuning point */ |
| if (s == -1) |
| s = dly; |
| e = dly; |
| range++; |
| } else { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning last: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| |
| if (range > longest_range) { |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| } |
| |
| /* in case the last timings are all working */ |
| if (range > longest_range) { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning last: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| |
| if (longest_range == 0) { |
| if (clock > 50000000) { |
| doing_retune = 1; |
| goto retry; |
| } |
| |
| return -EIO; |
| } |
| |
| middle = (best_s + best_e) / 2; |
| |
| tmp = (best_misc << 9) | (best_misc << 15) | (best_misc << 21) | (best_misc << 27); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_L, tmp); |
| tmp = ((middle >> 3) << 25) | ((middle & 0x07) << 22) | (best_misc << 1) |
| | (best_misc << 7) | (best_misc << 13) | (best_misc << 19); |
| amba_writel(pinfo->regbase + SD_DELAY_SEL_H, tmp); |
| |
| ambarella_sd_recovery(mmc); |
| |
| return 0; |
| |
| phy_type_2: |
| |
| /* misc: 5 bits out clock delay |
| * dly: 2 bits out data delay and 3 bits input data delay, total 5 bits; |
| */ |
| |
| /*This is only used for s2e sdio slot*/ |
| lat = amba_readl(pinfo->timing_reg); |
| lat &= ~0xC3E0E000; |
| |
| for(misc = 0; misc < 32; misc++) { |
| for(dly = 0; dly < 32; dly++) { |
| tmp = lat | ((dly >> 3) << 30) | ((dly & 0x07) << 13) | (misc << 21); |
| amba_writel(pinfo->timing_reg, tmp); |
| if (ambarella_send_tuning_cmd(mmc, opcode, NULL) == 0) { |
| /* Tuning is successful at this tuning point */ |
| if (s == -1) |
| s = dly; |
| e = dly; |
| range++; |
| } else { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning last: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| |
| if (range > longest_range) { |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| } |
| |
| /* in case the last timings are all working */ |
| if (range > longest_range) { |
| if (range > 0) { |
| ambsd_tuning_dbg(pslotinfo, |
| "tuning last: misc[0x%x], count[%d](%d - %d)\n", |
| misc, e - s + 1, s, e); |
| } |
| best_misc = misc; |
| best_s = s; |
| best_e = e; |
| longest_range = range; |
| } |
| s = -1; |
| e = range = 0; |
| } |
| |
| if (longest_range == 0) { |
| if (clock > 50000000) { |
| doing_retune = 1; |
| goto retry; |
| } |
| |
| return -EIO; |
| } |
| |
| middle = (best_s + best_e) / 2; |
| tmp = lat | ((middle >> 3) << 30) | ((middle & 0x07) << 13) | (best_misc << 21); |
| amba_writel(pinfo->timing_reg, tmp); |
| |
| ambarella_sd_recovery(mmc); |
| |
| return 0; |
| } |
| |
| static const struct mmc_host_ops ambarella_sd_host_ops = { |
| .request = ambarella_sd_request, |
| .set_ios = ambarella_sd_ios, |
| .get_ro = ambarella_sd_get_ro, |
| .get_cd = ambarella_sd_get_cd, |
| .enable_sdio_irq = ambarella_sd_enable_sdio_irq, |
| .start_signal_voltage_switch = ambarella_sd_ssvs, |
| .card_busy = ambarella_sd_card_busy, |
| .execute_tuning = ambarella_sd_execute_tuning, |
| }; |
| |
| static int ambarella_sd_system_event(struct notifier_block *nb, |
| unsigned long val, void *data) |
| { |
| struct ambarella_sd_mmc_info *pslotinfo; |
| struct ambarella_sd_controller_info *pinfo; |
| |
| pslotinfo = container_of(nb, struct ambarella_sd_mmc_info, system_event); |
| pinfo = (struct ambarella_sd_controller_info *)pslotinfo->pinfo; |
| |
| switch (val) { |
| case AMBA_EVENT_PRE_CPUFREQ: |
| pr_debug("%s[%u]: Pre Change\n", __func__, pslotinfo->slot_id); |
| down(&pslotinfo->system_event_sem); |
| break; |
| |
| case AMBA_EVENT_POST_CPUFREQ: |
| pr_debug("%s[%u]: Post Change\n", __func__, pslotinfo->slot_id); |
| ambarella_sd_set_clk(pslotinfo->mmc, &pinfo->controller_ios); |
| up(&pslotinfo->system_event_sem); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static irqreturn_t ambarella_sd_irq(int irq, void *devid) |
| { |
| struct ambarella_sd_controller_info *pinfo = devid; |
| struct ambarella_sd_mmc_info *pslotinfo = NULL; |
| u16 nis, eis, slot_id; |
| |
| /* Read and clear the interrupt registers */ |
| amba_read2w(pinfo->regbase + SD_NIS_OFFSET, &nis, &eis); |
| amba_write2w(pinfo->regbase + SD_NIS_OFFSET, nis, eis); |
| |
| if (pinfo->slot_num > 1) { |
| u32 fio_ctrl = amba_readl(pinfo->fio_reg + FIO_CTR_OFFSET); |
| slot_id = (fio_ctrl & FIO_CTR_XD) ? 1 : 0; |
| } else |
| slot_id = 0; |
| |
| pslotinfo = pinfo->pslotinfo[slot_id]; |
| |
| ambsd_dbg(pslotinfo, "%s nis = 0x%x, eis = 0x%x & %u\n", |
| __func__, nis, eis, pslotinfo->state); |
| |
| if (nis & SD_NIS_CARD) { |
| ambsd_dbg(pslotinfo, "SD_NIS_CARD\n"); |
| mmc_signal_sdio_irq(pslotinfo->mmc); |
| } |
| |
| if(pslotinfo->fixed_cd == -1) { |
| if (nis & SD_NIS_REMOVAL) { |
| ambsd_dbg(pslotinfo, "SD_NIS_REMOVAL\n"); |
| mmc_detect_change(pslotinfo->mmc, msecs_to_jiffies(1000)); |
| } else if (nis & SD_NIS_INSERT) { |
| ambsd_dbg(pslotinfo, "SD_NIS_INSERT\n"); |
| mmc_detect_change(pslotinfo->mmc, msecs_to_jiffies(1000)); |
| } |
| } |
| |
| if (eis) { |
| if (eis & (SD_EIS_CMD_TMOUT_ERR | SD_EIS_CMD_CRC_ERR | |
| SD_EIS_CMD_BIT_ERR | SD_EIS_CMD_IDX_ERR | |
| SD_EIS_ACMD12_ERR)) { |
| ambarella_sd_reset_cmd_line(pslotinfo->mmc); |
| } |
| if (eis & (SD_EIS_DATA_TMOUT_ERR | SD_EIS_DATA_CRC_ERR)) { |
| ambarella_sd_reset_data_line(pslotinfo->mmc); |
| } |
| if (eis & (SD_EIS_DATA_BIT_ERR | SD_EIS_CURRENT_ERR)) { |
| //ambarella_sd_reset_all(pslotinfo->mmc); |
| ambarella_sd_recovery(pslotinfo->mmc); |
| } |
| if (pslotinfo->state == AMBA_SD_STATE_CMD) { |
| ambarella_sd_cmd_done(pslotinfo, nis, eis); |
| } else if (pslotinfo->state == AMBA_SD_STATE_DATA) { |
| ambarella_sd_data_done(pslotinfo, nis, eis); |
| } |
| } else { |
| if (nis & SD_NIS_CMD_DONE) { |
| ambarella_sd_cmd_done(pslotinfo, nis, eis); |
| } |
| if (nis & SD_NIS_XFR_DONE) { |
| ambarella_sd_data_done(pslotinfo, nis, eis); |
| } |
| #if 0 |
| if (nis & SD_NIS_DMA) { |
| amba_writel(pinfo->regbase + SD_DMA_ADDR_OFFSET, |
| amba_readl(pinfo->regbase + SD_DMA_ADDR_OFFSET)); |
| } |
| #endif |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| |
| /* ==========================================================================*/ |
| |
| static int ambarella_sd_init_slot(struct device_node *np, int id, |
| struct ambarella_sd_controller_info *pinfo) |
| { |
| struct device_node *save_np; |
| struct ambarella_sd_mmc_info *pslotinfo = NULL; |
| struct mmc_host *mmc; |
| enum of_gpio_flags flags; |
| u32 gpio_init_flag, hc_cap, hc_timeout_clk; |
| int global_id, retval = 0; |
| |
| mmc = mmc_alloc_host(sizeof(*pslotinfo), pinfo->dev); |
| if (!mmc) { |
| dev_err(pinfo->dev, "Failed to alloc mmc host %u!\n", id); |
| return -ENOMEM; |
| } |
| pslotinfo = mmc_priv(mmc); |
| pslotinfo->mmc = mmc; |
| |
| /* in order to use mmc_of_parse(), we reassign the parent |
| * of_node, this should be a workaroud. */ |
| save_np = mmc->parent->of_node; |
| mmc->parent->of_node = np; |
| mmc_of_parse(mmc); |
| mmc->parent->of_node = save_np; |
| |
| /* our own extra property */ |
| if (of_property_read_u32(np, "amb,fixed-wp", &pslotinfo->fixed_wp) < 0) |
| pslotinfo->fixed_wp = -1; |
| if (of_property_read_u32(np, "amb,fixed-cd", &pslotinfo->fixed_cd) < 0) |
| pslotinfo->fixed_cd = -1; |
| pslotinfo->no_1_8_v = !!of_find_property(np, "no-1-8-v", NULL); |
| pslotinfo->caps_ddr = !!of_find_property(np, "amb,caps-ddr", NULL); |
| pslotinfo->caps_adma = !!of_find_property(np, "amb,caps-adma", NULL); |
| pslotinfo->force_gpio = !!of_find_property(np, "amb,force-gpio", NULL); |
| if (pslotinfo->force_gpio) { |
| pslotinfo->pinctrl = devm_pinctrl_get(pinfo->dev); |
| if (IS_ERR(pslotinfo->pinctrl)) { |
| retval = PTR_ERR(pslotinfo->pinctrl); |
| dev_err(pinfo->dev, "Can't get pinctrl: %d\n", retval); |
| goto init_slot_err1; |
| } |
| |
| pslotinfo->state_work = |
| pinctrl_lookup_state(pslotinfo->pinctrl, "work"); |
| if (IS_ERR(pslotinfo->state_work)) { |
| retval = PTR_ERR(pslotinfo->state_work); |
| dev_err(pinfo->dev, "Can't get pinctrl state: work\n"); |
| goto init_slot_err1; |
| } |
| |
| pslotinfo->state_idle = |
| pinctrl_lookup_state(pslotinfo->pinctrl, "idle"); |
| if (IS_ERR(pslotinfo->state_idle)) { |
| retval = PTR_ERR(pslotinfo->state_idle); |
| dev_err(pinfo->dev, "Can't get pinctrl state: idle\n"); |
| goto init_slot_err1; |
| } |
| } |
| |
| /* request gpio for external power control */ |
| pslotinfo->pwr_gpio = of_get_named_gpio_flags(np, "pwr-gpios", 0, &flags); |
| pslotinfo->pwr_gpio_active = !!(flags & OF_GPIO_ACTIVE_LOW); |
| |
| if (gpio_is_valid(pslotinfo->pwr_gpio)) { |
| if (pslotinfo->pwr_gpio_active) |
| gpio_init_flag = GPIOF_OUT_INIT_LOW; |
| else |
| gpio_init_flag = GPIOF_OUT_INIT_HIGH; |
| |
| retval = devm_gpio_request_one(pinfo->dev, pslotinfo->pwr_gpio, |
| gpio_init_flag, "sd ext power"); |
| if (retval < 0) { |
| dev_err(pinfo->dev, "Failed to request pwr-gpios!\n"); |
| goto init_slot_err1; |
| } |
| } |
| |
| /* request gpio for 3.3v/1.8v switch */ |
| pslotinfo->v18_gpio = of_get_named_gpio_flags(np, "v18-gpios", 0, &flags); |
| pslotinfo->v18_gpio_active = !!(flags & OF_GPIO_ACTIVE_LOW); |
| |
| if (gpio_is_valid(pslotinfo->v18_gpio)) { |
| if (pslotinfo->v18_gpio_active) |
| gpio_init_flag = GPIOF_OUT_INIT_LOW; |
| else |
| gpio_init_flag = GPIOF_OUT_INIT_HIGH; |
| |
| retval = devm_gpio_request_one(pinfo->dev, pslotinfo->v18_gpio, |
| gpio_init_flag, "sd ext power"); |
| if (retval < 0) { |
| dev_err(pinfo->dev, "Failed to request v18-gpios!\n"); |
| goto init_slot_err1; |
| } |
| } |
| |
| mmc->f_min = mmc->f_max >> 8; |
| mmc->ops = &ambarella_sd_host_ops; |
| |
| init_waitqueue_head(&pslotinfo->wait); |
| pslotinfo->state = AMBA_SD_STATE_ERR; |
| pslotinfo->slot_id = id; |
| pslotinfo->pinfo = pinfo; |
| pinfo->pslotinfo[id] = pslotinfo; |
| sema_init(&pslotinfo->system_event_sem, 1); |
| |
| ambarella_sd_request_bus(mmc); |
| |
| ambarella_sd_reset_all(mmc); |
| |
| hc_cap = amba_readl(pinfo->regbase + SD_CAP_OFFSET); |
| |
| dev_dbg(pinfo->dev, |
| "SD Clock: base[%uMHz], min[%uHz], max[%uHz].\n", |
| SD_CAP_BASE_FREQ(hc_cap), mmc->f_min, mmc->f_max); |
| |
| // This value needs to be under 25000 to enable trim/discard support |
| // for eMMC: |
| // Trim support is broken in the current kernel version and backporting |
| // is not feasible. This sets the discard timeout to the lowest allowed |
| // for trim to work. It will be up to the app/client/user to decide |
| // how/when to trim to least impact performance |
| hc_timeout_clk = 12000; |
| |
| mmc->max_discard_to = (1 << 27) / hc_timeout_clk; |
| |
| if (hc_cap & SD_CAP_MAX_2KB_BLK) |
| mmc->max_blk_size = 2048; |
| else if (hc_cap & SD_CAP_MAX_1KB_BLK) |
| mmc->max_blk_size = 1024; |
| else if (hc_cap & SD_CAP_MAX_512B_BLK) |
| mmc->max_blk_size = 512; |
| |
| dev_dbg(pinfo->dev, "SD max_blk_size: %u.\n", mmc->max_blk_size); |
| |
| mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ | |
| MMC_CAP_ERASE | MMC_CAP_BUS_WIDTH_TEST; |
| |
| mmc->caps2 |= MMC_CAP2_HS200 | MMC_CAP2_HS200_1_8V_SDR; |
| |
| if (mmc->f_max > 25000000) { |
| mmc->caps |= MMC_CAP_SD_HIGHSPEED; |
| mmc->caps |= MMC_CAP_MMC_HIGHSPEED; |
| } |
| |
| if (!(hc_cap & SD_CAP_DMA)) { |
| ambsd_err(pslotinfo, "HW do not support DMA!\n"); |
| retval = -ENODEV; |
| goto init_slot_err1; |
| } |
| |
| if (hc_cap & SD_CAP_ADMA_SUPPORT) { |
| dev_dbg(pinfo->dev, "HW support ADMA!\n"); |
| pslotinfo->use_adma = pslotinfo->caps_adma ? 1 : 0; |
| } |
| |
| dev_dbg(pinfo->dev, "HW%s support Suspend/Resume!\n", |
| (hc_cap & SD_CAP_SUS_RES) ? "" : " do not"); |
| |
| mmc->ocr_avail = 0; |
| if (hc_cap & SD_CAP_VOL_3_3V) |
| mmc->ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34; |
| |
| if ((hc_cap & SD_CAP_VOL_1_8V) && !pslotinfo->no_1_8_v) { |
| mmc->ocr_avail |= MMC_VDD_165_195; |
| if (hc_cap & SD_CAP_HIGH_SPEED) |
| mmc->caps |= MMC_CAP_1_8V_DDR; |
| |
| mmc->caps |= MMC_CAP_UHS_SDR12; |
| if (mmc->f_max > 25000000) { |
| mmc->caps |= MMC_CAP_UHS_SDR25; |
| if (pslotinfo->caps_ddr) |
| mmc->caps |= MMC_CAP_UHS_DDR50; |
| } |
| |
| if (mmc->f_max > 50000000) |
| mmc->caps |= MMC_CAP_UHS_SDR50; |
| |
| if (mmc->f_max > 100000000) |
| mmc->caps |= MMC_CAP_UHS_SDR104; |
| } |
| |
| if (mmc->ocr_avail == 0) { |
| ambsd_err(pslotinfo, "HW report wrong voltages[0x%x]!\n", hc_cap); |
| retval = -ENODEV; |
| goto init_slot_err1; |
| } |
| |
| if (!(hc_cap & SD_CAP_INTMODE)) { |
| ambsd_err(pslotinfo, "HW do not support Interrupt mode!\n"); |
| retval = -ENODEV; |
| goto init_slot_err1; |
| } |
| |
| dev_dbg(pinfo->dev, "SD caps: 0x%x.\n", mmc->caps); |
| dev_dbg(pinfo->dev, "SD ocr: 0x%x.\n", mmc->ocr_avail); |
| |
| mmc->max_blk_count = 0xFFFF; |
| mmc->max_seg_size = pinfo->max_blk_sz; |
| mmc->max_segs = mmc->max_seg_size / PAGE_SIZE; |
| mmc->max_req_size = |
| min(mmc->max_seg_size, mmc->max_blk_size * mmc->max_blk_count); |
| |
| pslotinfo->buf_vaddress = kmem_cache_alloc(pinfo->buf_cache, GFP_KERNEL); |
| if (!pslotinfo->buf_vaddress) { |
| ambsd_err(pslotinfo, "Can't alloc DMA memory"); |
| retval = -ENOMEM; |
| goto init_slot_err1; |
| } |
| pslotinfo->buf_paddress = dma_map_single(pinfo->dev, |
| pslotinfo->buf_vaddress, |
| mmc->max_req_size, |
| DMA_BIDIRECTIONAL); |
| ambarella_sd_check_dma_boundary(pslotinfo->buf_paddress, |
| mmc->max_req_size, pinfo->max_blk_sz); |
| |
| dev_notice(pinfo->dev, "Slot%u use bounce buffer[0x%p<->0x%08x]\n", |
| pslotinfo->slot_id, pslotinfo->buf_vaddress, |
| pslotinfo->buf_paddress); |
| |
| dev_notice(pinfo->dev, "Slot%u req_size=0x%08X, " |
| "segs=%u, seg_size=0x%08X\n", |
| pslotinfo->slot_id, mmc->max_req_size, |
| mmc->max_segs, mmc->max_seg_size); |
| |
| dev_notice(pinfo->dev, "Slot%u use %sDMA\n", pslotinfo->slot_id, |
| pslotinfo->use_adma ? "A" :""); |
| |
| ambarella_sd_release_bus(mmc); |
| |
| retval = mmc_add_host(pslotinfo->mmc); |
| if (retval < 0) { |
| ambsd_err(pslotinfo, "Can't add mmc host!\n"); |
| goto init_slot_err2; |
| } |
| |
| ambarella_sd_add_debugfs(pslotinfo); |
| |
| pslotinfo->system_event.notifier_call = ambarella_sd_system_event; |
| ambarella_register_event_notifier(&pslotinfo->system_event); |
| |
| retval = of_property_read_u32(np, "global-id", &global_id); |
| if (retval < 0 || global_id >= SD_INSTANCES * AMBA_SD_MAX_SLOT_NUM) |
| global_id = 0; |
| G_mmc[global_id] = mmc; |
| |
| return 0; |
| |
| init_slot_err2: |
| dma_unmap_single(pinfo->dev, pslotinfo->buf_paddress, |
| pslotinfo->mmc->max_req_size, DMA_BIDIRECTIONAL); |
| kmem_cache_free(pinfo->buf_cache, pslotinfo->buf_vaddress); |
| |
| init_slot_err1: |
| mmc_free_host(mmc); |
| return retval; |
| } |
| |
| static int ambarella_sd_free_slot(struct ambarella_sd_mmc_info *pslotinfo) |
| { |
| struct ambarella_sd_controller_info *pinfo = pslotinfo->pinfo; |
| int retval = 0; |
| |
| ambarella_unregister_event_notifier(&pslotinfo->system_event); |
| |
| ambarella_sd_remove_debugfs(pslotinfo); |
| |
| mmc_remove_host(pslotinfo->mmc); |
| |
| if (pslotinfo->buf_paddress) { |
| dma_unmap_single(pinfo->dev, pslotinfo->buf_paddress, |
| pslotinfo->mmc->max_req_size, DMA_BIDIRECTIONAL); |
| pslotinfo->buf_paddress = (dma_addr_t)NULL; |
| } |
| |
| if (pslotinfo->buf_vaddress) { |
| kmem_cache_free(pinfo->buf_cache, pslotinfo->buf_vaddress); |
| pslotinfo->buf_vaddress = NULL; |
| } |
| |
| mmc_free_host(pslotinfo->mmc); |
| |
| return retval; |
| } |
| |
| static int ambarella_sd_of_parse(struct ambarella_sd_controller_info *pinfo) |
| { |
| struct device_node *np = pinfo->dev->of_node; |
| const __be32 *prop; |
| const char *clk_name; |
| int psize, tmo, switch_vol_tmo, retval = 0; |
| |
| retval = of_property_read_string(np, "amb,clk-name", &clk_name); |
| if (retval < 0) { |
| dev_err(pinfo->dev, "Get pll-name failed! %d\n", retval); |
| goto pasre_err; |
| } |
| |
| pinfo->clk = clk_get(NULL, clk_name); |
| if (IS_ERR(pinfo->clk)) { |
| dev_err(pinfo->dev, "Get PLL failed!\n"); |
| retval = PTR_ERR(pinfo->clk); |
| goto pasre_err; |
| } |
| |
| if (of_find_property(np, "amb,dma-addr-fix", NULL)) |
| pinfo->dma_fix = 0xc0000000; |
| |
| retval = of_property_read_u32(np, "amb,wait-tmo", &tmo); |
| if (retval < 0) |
| tmo = 0x10000; |
| |
| pinfo->default_wait_tmo = msecs_to_jiffies(tmo); |
| |
| if (pinfo->default_wait_tmo < CONFIG_SD_AMBARELLA_WAIT_TIMEOUT) |
| pinfo->default_wait_tmo = CONFIG_SD_AMBARELLA_WAIT_TIMEOUT; |
| |
| retval = of_property_read_u32(np, "amb,switch-vol-tmo", &switch_vol_tmo); |
| if (retval < 0) |
| switch_vol_tmo = 100; |
| |
| pinfo->switch_voltage_tmo = switch_vol_tmo; |
| |
| retval = of_property_read_u32(np, "amb,max-blk-size", &pinfo->max_blk_sz); |
| if (retval < 0) |
| pinfo->max_blk_sz = 0x20000; |
| |
| pinfo->auto_tuning = !!of_find_property(np, "amb,auto-tuning", NULL); |
| retval = of_property_read_u32(np, "amb,phy-type", &pinfo->phy_type); |
| if(retval) { |
| /*this controller don't support sd phy*/ |
| pinfo->phy_type = -1; |
| retval = 0; |
| } else { |
| /* amb,phy-timing must be provided when timing_reg is given */ |
| prop = of_get_property(np, "amb,phy-timing", &psize); |
| if (!prop) { |
| retval = -EINVAL; |
| goto pasre_err; |
| } |
| |
| psize /= sizeof(u32); |
| BUG_ON(psize % 3); |
| pinfo->phy_timing_num = psize / 3; |
| pinfo->phy_timing = devm_kzalloc(pinfo->dev, psize, GFP_KERNEL); |
| if (pinfo->phy_timing == NULL) { |
| retval = -ENOMEM; |
| goto pasre_err; |
| } |
| |
| retval = of_property_read_u32_array(np, "amb,phy-timing", |
| (u32 *)pinfo->phy_timing, psize); |
| |
| /* bit0 of mode must be set, and the phy setting in first row with |
| * bit0 set is the default one. */ |
| BUG_ON(!(pinfo->phy_timing[0].mode & 0x1)); |
| } |
| |
| pasre_err: |
| return retval; |
| } |
| |
| static int ambarella_sd_get_resource(struct platform_device *pdev, |
| struct ambarella_sd_controller_info *pinfo) |
| { |
| struct resource *mem; |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (mem == NULL) { |
| dev_err(&pdev->dev, "Get SD/MMC mem resource failed!\n"); |
| return -ENXIO; |
| } |
| |
| pinfo->regbase = devm_ioremap(&pdev->dev, |
| mem->start, resource_size(mem)); |
| if (pinfo->regbase == NULL) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| return -ENOMEM; |
| } |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (mem == NULL) { |
| dev_err(&pdev->dev, "Get FIO mem resource failed!\n"); |
| return -ENXIO; |
| } |
| |
| pinfo->fio_reg = devm_ioremap(&pdev->dev, |
| mem->start, resource_size(mem)); |
| if (pinfo->fio_reg == NULL) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| return -ENOMEM; |
| } |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
| if (mem == NULL) { |
| pinfo->timing_reg = NULL; |
| pinfo->auto_tuning = false; |
| } else { |
| pinfo->timing_reg = devm_ioremap(&pdev->dev, |
| mem->start, resource_size(mem)); |
| if (pinfo->timing_reg == NULL) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| return -ENOMEM; |
| } |
| } |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 3); |
| if (mem == NULL) { |
| pinfo->sbc_reg = pinfo->timing_reg; |
| } else { |
| pinfo->sbc_reg = devm_ioremap(&pdev->dev, |
| mem->start, resource_size(mem)); |
| if (pinfo->sbc_reg == NULL) { |
| dev_err(&pdev->dev, "devm_ioremap() failed for sbc_reg\n"); |
| return -ENOMEM; |
| } |
| } |
| |
| pinfo->irq = platform_get_irq(pdev, 0); |
| if (pinfo->irq < 0) { |
| dev_err(&pdev->dev, "Get SD/MMC irq resource failed!\n"); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static int ambarella_sd_probe(struct platform_device *pdev) |
| { |
| struct ambarella_sd_controller_info *pinfo; |
| struct device_node *slot_np; |
| int retval, i, slot_id = -1; |
| |
| pinfo = devm_kzalloc(&pdev->dev, sizeof(*pinfo), GFP_KERNEL); |
| if (pinfo == NULL) { |
| dev_err(&pdev->dev, "Out of memory!\n"); |
| return -ENOMEM; |
| } |
| pinfo->dev = &pdev->dev; |
| |
| retval = ambarella_sd_get_resource(pdev, pinfo); |
| if (retval < 0) |
| return retval; |
| |
| retval = ambarella_sd_of_parse(pinfo); |
| if (retval < 0) |
| return retval; |
| |
| pinfo->buf_cache = kmem_cache_create(dev_name(&pdev->dev), |
| pinfo->max_blk_sz, pinfo->max_blk_sz, 0, NULL); |
| if (!pinfo->buf_cache) { |
| dev_err(&pdev->dev, "Can't alloc DMA Cache"); |
| return -ENOMEM; |
| } |
| |
| pinfo->slot_num = 0; |
| for_each_child_of_node(pdev->dev.of_node, slot_np) { |
| if (!slot_np->name || of_node_cmp(slot_np->name, "slot")) |
| continue; |
| |
| retval = of_property_read_u32(slot_np, "reg", &slot_id); |
| if (retval < 0 || slot_id >= AMBA_SD_MAX_SLOT_NUM) |
| goto ambarella_sd_probe_free_host; |
| |
| retval = ambarella_sd_init_slot(slot_np, slot_id, pinfo); |
| if (retval < 0) |
| goto ambarella_sd_probe_free_host; |
| |
| pinfo->slot_num++; |
| } |
| |
| platform_set_drvdata(pdev, pinfo); |
| |
| retval = devm_request_irq(&pdev->dev, pinfo->irq, ambarella_sd_irq, |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| dev_name(&pdev->dev), pinfo); |
| if (retval < 0) { |
| dev_err(&pdev->dev, "Can't Request IRQ%u!\n", pinfo->irq); |
| goto ambarella_sd_probe_free_host; |
| } |
| |
| dev_info(&pdev->dev, "%u slots @ %luHz\n", |
| pinfo->slot_num, clk_get_rate(pinfo->clk)); |
| |
| return 0; |
| |
| ambarella_sd_probe_free_host: |
| for (i = pinfo->slot_num - 1; i >= 0; i--) |
| ambarella_sd_free_slot(pinfo->pslotinfo[i]); |
| |
| if (pinfo->buf_cache) { |
| kmem_cache_destroy(pinfo->buf_cache); |
| pinfo->buf_cache = NULL; |
| } |
| |
| return retval; |
| } |
| |
| static int ambarella_sd_remove(struct platform_device *pdev) |
| { |
| struct ambarella_sd_controller_info *pinfo; |
| int retval = 0, i; |
| |
| pinfo = platform_get_drvdata(pdev); |
| |
| for (i = 0; i < pinfo->slot_num; i++) |
| ambarella_sd_free_slot(pinfo->pslotinfo[i]); |
| |
| if (pinfo->buf_cache) |
| kmem_cache_destroy(pinfo->buf_cache); |
| |
| dev_notice(&pdev->dev, |
| "Remove Ambarella Media Processor SD/MMC Host Controller.\n"); |
| |
| return retval; |
| } |
| |
| #ifdef CONFIG_PM |
| static int ambarella_sd_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| struct ambarella_sd_controller_info *pinfo; |
| struct ambarella_sd_mmc_info *pslotinfo; |
| |
| int retval = 0, i; |
| |
| pinfo = platform_get_drvdata(pdev); |
| |
| for (i = 0; i < pinfo->slot_num; i++) { |
| pslotinfo = pinfo->pslotinfo[i]; |
| |
| retval = mmc_suspend_host(pslotinfo->mmc); |
| if (retval) { |
| ambsd_err(pinfo->pslotinfo[i], |
| "mmc_suspend_host[%d] failed[%d]!\n", i, retval); |
| return retval; |
| } |
| |
| if (pslotinfo->mmc->pm_caps & MMC_PM_KEEP_POWER) { |
| ambarella_sd_disable_int(pslotinfo->mmc, SD_NISEN_CARD); |
| pslotinfo->sd_nisen = amba_readw(pinfo->regbase + SD_NISEN_OFFSET); |
| pslotinfo->sd_eisen = amba_readw(pinfo->regbase + SD_EISEN_OFFSET); |
| pslotinfo->sd_nixen = amba_readw(pinfo->regbase + SD_NIXEN_OFFSET); |
| pslotinfo->sd_eixen = amba_readw(pinfo->regbase + SD_EIXEN_OFFSET); |
| } |
| } |
| |
| disable_irq(pinfo->irq); |
| dev_dbg(&pdev->dev, "%s exit with %d @ %d\n", __func__, |
| retval, state.event); |
| |
| return retval; |
| |
| } |
| |
| static int ambarella_sd_resume(struct platform_device *pdev) |
| { |
| struct ambarella_sd_controller_info *pinfo; |
| struct ambarella_sd_mmc_info *pslotinfo; |
| int retval = 0, i; |
| |
| pinfo = platform_get_drvdata(pdev); |
| |
| enable_irq(pinfo->irq); |
| |
| for (i = 0; i < pinfo->slot_num; i++) { |
| pslotinfo = pinfo->pslotinfo[i]; |
| if (gpio_is_valid(pslotinfo->pwr_gpio)){ |
| gpio_direction_output(pslotinfo->pwr_gpio, pslotinfo->pwr_gpio_active); |
| } |
| if (pslotinfo->mmc->pm_caps & MMC_PM_KEEP_POWER) { |
| amba_writew(pinfo->regbase + SD_NISEN_OFFSET, pslotinfo->sd_nisen); |
| amba_writew(pinfo->regbase + SD_EISEN_OFFSET, pslotinfo->sd_eisen); |
| amba_writew(pinfo->regbase + SD_NIXEN_OFFSET, pslotinfo->sd_nixen); |
| amba_writew(pinfo->regbase + SD_EIXEN_OFFSET, pslotinfo->sd_eixen); |
| pslotinfo->mmc->caps |= MMC_CAP_NONREMOVABLE; |
| mdelay(10); |
| ambarella_sd_set_clk(pslotinfo->mmc, &pinfo->controller_ios); |
| ambarella_sd_set_bus(pslotinfo->mmc, &pinfo->controller_ios); |
| mdelay(10); |
| ambarella_sd_enable_int(pslotinfo->mmc, SD_NISEN_CARD); |
| } else { |
| clk_set_rate(pinfo->clk, pslotinfo->mmc->f_max); |
| ambarella_sd_reset_all(pslotinfo->mmc); |
| } |
| } |
| |
| for (i = 0; i < pinfo->slot_num; i++) { |
| pslotinfo = pinfo->pslotinfo[i]; |
| retval = mmc_resume_host(pslotinfo->mmc); |
| if (retval) { |
| ambsd_err(pslotinfo, |
| "mmc_resume_host[%d] failed[%d]!\n", |
| i, retval); |
| } |
| } |
| |
| dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, retval); |
| |
| return retval; |
| } |
| #endif |
| |
| #ifdef CONFIG_AMBARELLA_EMMC_BOOT |
| void ambarella_sd_shutdown (struct platform_device *pdev) |
| { |
| struct ambarella_sd_controller_info *pinfo; |
| struct ambarella_sd_mmc_info *pslotinfo; |
| struct mmc_host *host; |
| struct mmc_command cmd = {0}; |
| int i; |
| |
| pinfo = platform_get_drvdata(pdev); |
| |
| for (i = 0; i < pinfo->slot_num; i++) { |
| pslotinfo = pinfo->pslotinfo[i]; |
| host = pslotinfo->mmc; |
| if((host == G_mmc[0]) && ((system_state == SYSTEM_RESTART) || |
| (system_state == SYSTEM_HALT))) { |
| mmc_claim_host(pslotinfo->mmc); |
| cmd.opcode = 0; |
| cmd.arg = 0xf0f0f0f0; |
| cmd.flags = MMC_RSP_NONE; |
| |
| mmc_wait_for_cmd(pslotinfo->mmc, &cmd, 0); |
| } |
| } |
| } |
| #endif |
| |
| static const struct of_device_id ambarella_mmc_dt_ids[] = { |
| { .compatible = "ambarella,sdmmc", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, ambarella_mmc_dt_ids); |
| |
| static struct platform_driver ambarella_sd_driver = { |
| .probe = ambarella_sd_probe, |
| .remove = ambarella_sd_remove, |
| #ifdef CONFIG_PM |
| .suspend = ambarella_sd_suspend, |
| .resume = ambarella_sd_resume, |
| #endif |
| |
| #ifdef CONFIG_AMBARELLA_EMMC_BOOT |
| .shutdown = ambarella_sd_shutdown, |
| #endif |
| .driver = { |
| .name = "ambarella-sd", |
| .owner = THIS_MODULE, |
| .of_match_table = ambarella_mmc_dt_ids, |
| }, |
| }; |
| |
| static int __init ambarella_sd_init(void) |
| { |
| int retval = 0; |
| |
| retval = platform_driver_register(&ambarella_sd_driver); |
| if (retval) { |
| printk(KERN_ERR "%s: Register failed %d!\n", |
| __func__, retval); |
| } |
| |
| return retval; |
| } |
| |
| static void __exit ambarella_sd_exit(void) |
| { |
| platform_driver_unregister(&ambarella_sd_driver); |
| } |
| |
| fs_initcall(ambarella_sd_init); |
| module_exit(ambarella_sd_exit); |
| |
| MODULE_DESCRIPTION("Ambarella Media Processor SD/MMC Host Controller"); |
| MODULE_AUTHOR("Anthony Ginger, <hfjiang@ambarella.com>"); |
| MODULE_LICENSE("GPL"); |
| |