📄 imxmmc.c
字号:
/* This is unfortunately required */ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data running STATUS = 0x%x\n", stat); udelay(20); /* required for clocks < 8MHz*/ if(host->dma_dir == DMA_FROM_DEVICE) { imxmci_busy_wait_for_status(host, &stat, STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE, 50, "imxmci_cpu_driven_data read"); while((stat & (STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE)) && (host->data_cnt < 512)) { udelay(20); /* required for clocks < 8MHz*/ for(i = burst_len; i>=2 ; i-=2) { u16 data; data = MMC_BUFFER_ACCESS; udelay(10); /* required for clocks < 8MHz*/ if(host->data_cnt+2 <= host->dma_size) { *(host->data_ptr++) = data; } else { if(host->data_cnt < host->dma_size) *(u8*)(host->data_ptr) = data; } host->data_cnt += 2; } stat = MMC_STATUS; dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data read %d burst %d STATUS = 0x%x\n", host->data_cnt, burst_len, stat); } if((stat & STATUS_DATA_TRANS_DONE) && (host->data_cnt >= 512)) trans_done = 1; if(host->dma_size & 0x1ff) stat &= ~STATUS_CRC_READ_ERR; } else { imxmci_busy_wait_for_status(host, &stat, STATUS_APPL_BUFF_FE, 20, "imxmci_cpu_driven_data write"); while((stat & STATUS_APPL_BUFF_FE) && (host->data_cnt < host->dma_size)) { if(burst_len >= host->dma_size - host->data_cnt) { burst_len = host->dma_size - host->data_cnt; host->data_cnt = host->dma_size; trans_done = 1; } else { host->data_cnt += burst_len; } for(i = burst_len; i>0 ; i-=2) MMC_BUFFER_ACCESS = *(host->data_ptr++); stat = MMC_STATUS; dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data write burst %d STATUS = 0x%x\n", burst_len, stat); } } *pstat = stat; return trans_done;}static void imxmci_dma_irq(int dma, void *devid){ struct imxmci_host *host = devid; uint32_t stat = MMC_STATUS; atomic_set(&host->stuck_timeout, 0); host->status_reg = stat; set_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events); tasklet_schedule(&host->tasklet);}static irqreturn_t imxmci_irq(int irq, void *devid){ struct imxmci_host *host = devid; uint32_t stat = MMC_STATUS; int handled = 1; MMC_INT_MASK = host->imask | INT_MASK_SDIO | INT_MASK_AUTO_CARD_DETECT; atomic_set(&host->stuck_timeout, 0); host->status_reg = stat; set_bit(IMXMCI_PEND_IRQ_b, &host->pending_events); set_bit(IMXMCI_PEND_STARTED_b, &host->pending_events); tasklet_schedule(&host->tasklet); return IRQ_RETVAL(handled);;}static void imxmci_tasklet_fnc(unsigned long data){ struct imxmci_host *host = (struct imxmci_host *)data; u32 stat; unsigned int data_dir_mask = 0; /* STATUS_WR_CRC_ERROR_CODE_MASK */ int timeout = 0; if(atomic_read(&host->stuck_timeout) > 4) { char *what; timeout = 1; stat = MMC_STATUS; host->status_reg = stat; if (test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) what = "RESP+DMA"; else what = "RESP"; else if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) if(test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events)) what = "DATA"; else what = "DMA"; else what = "???"; dev_err(mmc_dev(host->mmc), "%s TIMEOUT, hardware stucked STATUS = 0x%04x IMASK = 0x%04x\n", what, stat, MMC_INT_MASK); dev_err(mmc_dev(host->mmc), "CMD_DAT_CONT = 0x%04x, MMC_BLK_LEN = 0x%04x, MMC_NOB = 0x%04x, DMA_CCR = 0x%08x\n", MMC_CMD_DAT_CONT, MMC_BLK_LEN, MMC_NOB, CCR(host->dma)); dev_err(mmc_dev(host->mmc), "CMD%d, prevCMD%d, bus %d-bit, dma_size = 0x%x\n", host->cmd?host->cmd->opcode:0, host->prev_cmd_code, 1<<host->actual_bus_width, host->dma_size); } if(!host->present || timeout) host->status_reg = STATUS_TIME_OUT_RESP | STATUS_TIME_OUT_READ | STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR; if(test_bit(IMXMCI_PEND_IRQ_b, &host->pending_events) || timeout) { clear_bit(IMXMCI_PEND_IRQ_b, &host->pending_events); stat = MMC_STATUS; /* * This is not required in theory, but there is chance to miss some flag * which clears automatically by mask write, FreeScale original code keeps * stat from IRQ time so do I */ stat |= host->status_reg; if(test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) { imxmci_busy_wait_for_status(host, &stat, STATUS_END_CMD_RESP | STATUS_ERR_MASK, 20, "imxmci_tasklet_fnc resp (ERRATUM #4)"); } if(stat & (STATUS_END_CMD_RESP | STATUS_ERR_MASK)) { if(test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) imxmci_cmd_done(host, stat); if(host->data && (stat & STATUS_ERR_MASK)) imxmci_data_done(host, stat); } if(test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events)) { stat |= MMC_STATUS; if(imxmci_cpu_driven_data(host, &stat)){ if(test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) imxmci_cmd_done(host, stat); atomic_clear_mask(IMXMCI_PEND_IRQ_m|IMXMCI_PEND_CPU_DATA_m, &host->pending_events); imxmci_data_done(host, stat); } } } if(test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events) && !test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) { stat = MMC_STATUS; /* Same as above */ stat |= host->status_reg; if(host->dma_dir == DMA_TO_DEVICE) { data_dir_mask = STATUS_WRITE_OP_DONE; } else { data_dir_mask = STATUS_DATA_TRANS_DONE; } if(stat & data_dir_mask) { clear_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events); imxmci_data_done(host, stat); } } if(test_and_clear_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events)) { if(host->cmd) imxmci_cmd_done(host, STATUS_TIME_OUT_RESP); if(host->data) imxmci_data_done(host, STATUS_TIME_OUT_READ | STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR); if(host->req) imxmci_finish_request(host, host->req); mmc_detect_change(host->mmc, msecs_to_jiffies(100)); }}static void imxmci_request(struct mmc_host *mmc, struct mmc_request *req){ struct imxmci_host *host = mmc_priv(mmc); unsigned int cmdat; WARN_ON(host->req != NULL); host->req = req; cmdat = 0; if (req->data) { imxmci_setup_data(host, req->data); cmdat |= CMD_DAT_CONT_DATA_ENABLE; if (req->data->flags & MMC_DATA_WRITE) cmdat |= CMD_DAT_CONT_WRITE; if (req->data->flags & MMC_DATA_STREAM) { cmdat |= CMD_DAT_CONT_STREAM_BLOCK; } } imxmci_start_cmd(host, req->cmd, cmdat);}#define CLK_RATE 19200000static void imxmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios){ struct imxmci_host *host = mmc_priv(mmc); int prescaler; if( ios->bus_width==MMC_BUS_WIDTH_4 ) { host->actual_bus_width = MMC_BUS_WIDTH_4; imx_gpio_mode(PB11_PF_SD_DAT3); }else{ host->actual_bus_width = MMC_BUS_WIDTH_1; imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11); } if ( host->power_mode != ios->power_mode ) { switch (ios->power_mode) { case MMC_POWER_OFF: break; case MMC_POWER_UP: set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events); break; case MMC_POWER_ON: break; } host->power_mode = ios->power_mode; } if ( ios->clock ) { unsigned int clk; /* The prescaler is 5 for PERCLK2 equal to 96MHz * then 96MHz / 5 = 19.2 MHz */ clk=imx_get_perclk2(); prescaler=(clk+(CLK_RATE*7)/8)/CLK_RATE; switch(prescaler) { case 0: case 1: prescaler = 0; break; case 2: prescaler = 1; break; case 3: prescaler = 2; break; case 4: prescaler = 4; break; default: case 5: prescaler = 5; break; } dev_dbg(mmc_dev(host->mmc), "PERCLK2 %d MHz -> prescaler %d\n", clk, prescaler); for(clk=0; clk<8; clk++) { int x; x = CLK_RATE / (1<<clk); if( x <= ios->clock) break; } MMC_STR_STP_CLK |= STR_STP_CLK_ENABLE; /* enable controller */ imxmci_stop_clock(host); MMC_CLK_RATE = (prescaler<<3) | clk; /* * Under my understanding, clock should not be started there, because it would * initiate SDHC sequencer and send last or random command into card */ /*imxmci_start_clock(host);*/ dev_dbg(mmc_dev(host->mmc), "MMC_CLK_RATE: 0x%08x\n", MMC_CLK_RATE); } else { imxmci_stop_clock(host); }}static const struct mmc_host_ops imxmci_ops = { .request = imxmci_request, .set_ios = imxmci_set_ios,};static struct resource *platform_device_resource(struct platform_device *dev, unsigned int mask, int nr){ int i; for (i = 0; i < dev->num_resources; i++) if (dev->resource[i].flags == mask && nr-- == 0) return &dev->resource[i]; return NULL;}static int platform_device_irq(struct platform_device *dev, int nr){ int i; for (i = 0; i < dev->num_resources; i++) if (dev->resource[i].flags == IORESOURCE_IRQ && nr-- == 0) return dev->resource[i].start; return NO_IRQ;}static void imxmci_check_status(unsigned long data){ struct imxmci_host *host = (struct imxmci_host *)data; if( host->pdata->card_present() != host->present ) { host->present ^= 1; dev_info(mmc_dev(host->mmc), "card %s\n", host->present ? "inserted" : "removed"); set_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events); tasklet_schedule(&host->tasklet); } if(test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events) || test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) { atomic_inc(&host->stuck_timeout); if(atomic_read(&host->stuck_timeout) > 4) tasklet_schedule(&host->tasklet); } else { atomic_set(&host->stuck_timeout, 0); } mod_timer(&host->timer, jiffies + (HZ>>1));}static int imxmci_probe(struct platform_device *pdev){ struct mmc_host *mmc; struct imxmci_host *host = NULL; struct resource *r; int ret = 0, irq; printk(KERN_INFO "i.MX mmc driver\n"); r = platform_device_resource(pdev, IORESOURCE_MEM, 0); irq = platform_device_irq(pdev, 0); if (!r || irq == NO_IRQ) return -ENXIO; r = request_mem_region(r->start, 0x100, "IMXMCI"); if (!r) return -EBUSY; mmc = mmc_alloc_host(sizeof(struct imxmci_host), &pdev->dev); if (!mmc) { ret = -ENOMEM; goto out; } mmc->ops = &imxmci_ops; mmc->f_min = 150000; mmc->f_max = CLK_RATE/2; mmc->ocr_avail = MMC_VDD_32_33; mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_BYTEBLOCK; /* MMC core transfer sizes tunable parameters */ mmc->max_hw_segs = 64; mmc->max_phys_segs = 64; mmc->max_sectors = 64; /* default 1 << (PAGE_CACHE_SHIFT - 9) */ mmc->max_seg_size = 64*512; /* default PAGE_CACHE_SIZE */ host = mmc_priv(mmc); host->mmc = mmc; host->dma_allocated = 0; host->pdata = pdev->dev.platform_data; spin_lock_init(&host->lock); host->res = r; host->irq = irq; imx_gpio_mode(PB8_PF_SD_DAT0); imx_gpio_mode(PB9_PF_SD_DAT1); imx_gpio_mode(PB10_PF_SD_DAT2); /* Configured as GPIO with pull-up to ensure right MCC card mode */ /* Switched to PB11_PF_SD_DAT3 if 4 bit bus is configured */ imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11); /* imx_gpio_mode(PB11_PF_SD_DAT3); */ imx_gpio_mode(PB12_PF_SD_CLK); imx_gpio_mode(PB13_PF_SD_CMD); imxmci_softreset(); if ( MMC_REV_NO != 0x390 ) { dev_err(mmc_dev(host->mmc), "wrong rev.no. 0x%08x. aborting.\n", MMC_REV_NO); goto out; } MMC_READ_TO = 0x2db4; /* recommended in data sheet */ host->imask = IMXMCI_INT_MASK_DEFAULT; MMC_INT_MASK = host->imask; if(imx_dma_request_by_prio(&host->dma, DRIVER_NAME, DMA_PRIO_LOW)<0){ dev_err(mmc_dev(host->mmc), "imx_dma_request_by_prio failed\n"); ret = -EBUSY; goto out; } host->dma_allocated=1; imx_dma_setup_handlers(host->dma, imxmci_dma_irq, NULL, host); tasklet_init(&host->tasklet, imxmci_tasklet_fnc, (unsigned long)host); host->status_reg=0; host->pending_events=0; ret = request_irq(host->irq, imxmci_irq, 0, DRIVER_NAME, host); if (ret) goto out; host->present = host->pdata->card_present(); init_timer(&host->timer); host->timer.data = (unsigned long)host; host->timer.function = imxmci_check_status; add_timer(&host->timer); mod_timer(&host->timer, jiffies + (HZ>>1)); platform_set_drvdata(pdev, mmc); mmc_add_host(mmc); return 0;out: if (host) { if(host->dma_allocated){ imx_dma_free(host->dma); host->dma_allocated=0; } } if (mmc) mmc_free_host(mmc); release_resource(r); return ret;}static int imxmci_remove(struct platform_device *pdev){ struct mmc_host *mmc = platform_get_drvdata(pdev); platform_set_drvdata(pdev, NULL); if (mmc) { struct imxmci_host *host = mmc_priv(mmc); tasklet_disable(&host->tasklet); del_timer_sync(&host->timer); mmc_remove_host(mmc); free_irq(host->irq, host); if(host->dma_allocated){ imx_dma_free(host->dma); host->dma_allocated=0; } tasklet_kill(&host->tasklet); release_resource(host->res); mmc_free_host(mmc); } return 0;}#ifdef CONFIG_PMstatic int imxmci_suspend(struct platform_device *dev, pm_message_t state){ struct mmc_host *mmc = platform_get_drvdata(dev); int ret = 0; if (mmc) ret = mmc_suspend_host(mmc, state); return ret;}static int imxmci_resume(struct platform_device *dev){ struct mmc_host *mmc = platform_get_drvdata(dev); struct imxmci_host *host; int ret = 0; if (mmc) { host = mmc_priv(mmc); if(host) set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events); ret = mmc_resume_host(mmc); } return ret;}#else#define imxmci_suspend NULL#define imxmci_resume NULL#endif /* CONFIG_PM */static struct platform_driver imxmci_driver = { .probe = imxmci_probe, .remove = imxmci_remove, .suspend = imxmci_suspend, .resume = imxmci_resume, .driver = { .name = DRIVER_NAME, }};static int __init imxmci_init(void){ return platform_driver_register(&imxmci_driver);}static void __exit imxmci_exit(void){ platform_driver_unregister(&imxmci_driver);}module_init(imxmci_init);module_exit(imxmci_exit);MODULE_DESCRIPTION("i.MX Multimedia Card Interface Driver");MODULE_AUTHOR("Sascha Hauer, Pengutronix");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -