linux2.6.28块设备mmc_sd卡s3c-sdhci驱动sdhci-s3c.c注册

发布时间:2026/5/20 12:08:08

linux2.6.28块设备mmc_sd卡s3c-sdhci驱动sdhci-s3c.c注册 参考原文http://blog.csdn.net/zoe6553/article/details/6588306图片///第一条线 mmc子系统核心初始化drivers/mmc/core/core.c//子系统初始化 subsys_initcall(mmc_init);第二条线 mmc控制器平台驱动注册module_init(sdhci_drv_init);第三条线 磁盘设备驱动注册drivers/mmc/card/block.cmodule_init(mmc_blk_init);图片图片///////////////////////////////////////////////////////////////////////////////////////////////////static struct platform_driver sdhci_s3c_driver {.probe sdhci_s3c_probe,.remove __devexit_p(sdhci_s3c_remove),.suspend sdhci_s3c_suspend,.resume sdhci_s3c_resume,.driver {.owner THIS_MODULE,//platform_device 定义分布定义在// s3c_device_hsmmc1 .name “s3c-sdhci”,.name “s3c-sdhci”,},};static int __init sdhci_s3c_init(void){return platform_driver_register(sdhci_s3c_driver);}module_init(sdhci_s3c_init);///////////////////////////////////////////////////////////////////struct sdhci_s3c {//新创建的host控制器struct sdhci_host *host;struct platform_device *pdev;struct resource *ioarea;struct s3c_sdhci_platdata *pdata;unsigned int cur_clk;struct clk *clk_io; /* clock for io bus */ struct clk *clk_bus[MAX_BUS_CLK];};///////////////////////////////////////////////static int __devinit sdhci_s3c_probe(struct platform_device *pdev){struct s3c_sdhci_platdata *pdata pdev-dev.platform_data;struct device *dev pdev-dev;struct sdhci_host *host;struct sdhci_s3c *sc;struct resource *res;int ret, irq, ptr, clks;if (!pdata) { dev_err(dev, no device data specified\n); return -ENOENT; }//platform_get_irq调用platform_get_resource 会返回一个start, 即可用的中断号。irq platform_get_irq(pdev, 0);if (irq 0) {dev_err(dev, “no irq specified\n”);return irq;}//获取IO内存资源res platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {dev_err(dev, “no memory specified\n”);return -ENOENT;}//host就是通过sdhci_alloc_host函数来构造出来的//也分配了sdhci_s3c结构体内存空间//sdhci_alloc_host分配了sdhci_host和mmc_host两个结构体并将sdhci_host的mmc成员指向mmc_hosthost sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));if (IS_ERR(host)) {dev_err(dev, “sdhci_alloc_host() failed\n”);return PTR_ERR(host);}//sc host-private//host-private已经被sdhci_alloc_host函数alloc了额外的sizeof(struct sdhci_s3c)给private成员 sc sdhci_priv(host);platform_set_drvdata(pdev, host);sc-host host; sc-pdev pdev; sc-pdata pdata; sc-clk_io clk_get(dev, hsmmc); if (IS_ERR(sc-clk_io)) { dev_err(dev, failed to get io clock\n); ret PTR_ERR(sc-clk_io); goto err_io_clk; } /* enable the local io clock and keep it running for the moment. */ clk_enable(sc-clk_io); for (clks 0, ptr 0; ptr MAX_BUS_CLK; ptr) { struct clk *clk; char *name pdata-clocks[ptr]; if (name NULL) continue; clk clk_get(dev, name); if (IS_ERR(clk)) { dev_err(dev, failed to get clock %s\n, name); continue; } clks; sc-clk_bus[ptr] clk; clk_enable(clk); dev_info(dev, clock source %d: %s (%ld Hz)\n, ptr, name, clk_get_rate(clk)); } if (clks 0) { dev_err(dev, failed to find any bus clocks\n); ret -ENOENT; goto err_no_busclks; }//申请I/O内存的函数sc-ioarea request_mem_region(res-start, resource_size(res),mmc_hostname(host-mmc));if (!sc-ioarea) {dev_err(dev, “failed to reserve register area\n”);ret -ENXIO;goto err_req_regs;}//把内存映射到CPU空间//这个版本的ioremap确保这些内存在CPU是不可缓冲的host-ioaddr ioremap_nocache(res-start, resource_size(res));if (!host-ioaddr) {dev_err(dev, “failed to map registers\n”);ret -ENXIO;goto err_req_regs;}/* Ensure we have minimal gpio selected CMD/CLK/Detect */ if (pdata-cfg_gpio) pdata-cfg_gpio(pdev, 0); sdhci_s3c_check_sclk(host); host-hw_name samsung-hsmmc; host-ops sdhci_s3c_ops; host-quirks 0; host-irq irq; /* Setup quirks for the controller */ host-flags SDHCI_USE_DMA; /* It seems we do not get an DATA transfer complete on non-busy * transfers, not sure if this is a problem with this specific * SDHCI block, or a missing configuration that needs to be set. */ host-quirks | SDHCI_QUIRK_NO_TCIRQ_ON_NOT_BUSY; host-quirks | (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE); if (pdata-host_caps) host-mmc-caps pdata-host_caps; else host-mmc-caps 0; /* to add external irq as a card detect signal */ printk([SDHCI]to add external irq as a card detect signal......\n); if (pdata-cfg_ext_cd) { printk([SDHCI]if (pdata-cfg_ext_cd)......\n); pdata-cfg_ext_cd(); if (pdata-detect_ext_cd()) host-flags | SDHCI_DEVICE_ALIVE; }//通过sdhci_add_host函数来注册sdhci_host// sdhci_add_host调用了mmc_add_host将这个控制器设备添加到内核中//将host添加入mmc管理其本质即是添加host的class_devret sdhci_add_host(host);if (ret) {dev_err(dev, “sdhci_add_host() failed\n”);goto err_add_host;}/* register external irq here (after all init is done) */ if (pdata-cfg_ext_cd) { printk([SDHCI]request_irq......\n); ret request_irq(pdata-ext_cd, sdhci_irq_cd, IRQF_SHARED, mmc_hostname(host-mmc), sc); } return 0;err_add_host:release_resource(sc-ioarea);kfree(sc-ioarea);err_req_regs:for (ptr 0; ptr MAX_BUS_CLK; ptr) {clk_disable(sc-clk_bus[ptr]);clk_put(sc-clk_bus[ptr]);}err_no_busclks:clk_disable(sc-clk_io);clk_put(sc-clk_io);err_io_clk:sdhci_free_host(host);return ret;}///////////////////////////////////////////////////////////////////int sdhci_add_host(struct sdhci_host *host){struct mmc_host *mmc;unsigned int caps;int ret;WARN_ON(host NULL); if (host NULL) return -EINVAL; mmc host-mmc; if (debug_quirks) host-quirks debug_quirks; sdhci_reset(host, SDHCI_RESET_ALL); host-version readw(host-ioaddr SDHCI_HOST_VERSION); host-version (host-version SDHCI_SPEC_VER_MASK) SDHCI_SPEC_VER_SHIFT; if (host-version SDHCI_SPEC_200) { printk(KERN_ERR %s: Unknown controller version (%d). You may experience problems.\n, mmc_hostname(mmc), host-version); }图片//#define SDHCI_CAPABILITIES 0x40caps readl(host-ioaddr SDHCI_CAPABILITIES);if (host-quirks SDHCI_QUIRK_FORCE_DMA) host-flags | SDHCI_USE_DMA; else if (!(caps SDHCI_CAN_DO_DMA)) DBG(Controller doesnt have DMA capability\n); else host-flags | SDHCI_USE_DMA; if ((host-quirks SDHCI_QUIRK_BROKEN_DMA) (host-flags SDHCI_USE_DMA)) { DBG(Disabling DMA as it is marked broken\n); host-flags ~SDHCI_USE_DMA; } if (host-flags SDHCI_USE_DMA) { if ((host-version SDHCI_SPEC_200) (caps SDHCI_CAN_DO_ADMA2)) host-flags | SDHCI_USE_ADMA; } if ((host-quirks SDHCI_QUIRK_BROKEN_ADMA) (host-flags SDHCI_USE_ADMA)) { DBG(Disabling ADMA as it is marked broken\n); host-flags ~SDHCI_USE_ADMA; } if (host-flags SDHCI_USE_DMA) { if (host-ops-enable_dma) { if (host-ops-enable_dma(host)) { printk(KERN_WARNING %s: No suitable DMA available. Falling back to PIO.\n, mmc_hostname(mmc)); host-flags ~(SDHCI_USE_DMA | SDHCI_USE_ADMA); } } } if (host-flags SDHCI_USE_ADMA) { /* * We need to allocate descriptors for all sg entries * (128) and potentially one alignment transfer for * each of those entries. */ host-adma_desc kmalloc((128 * 2 1) * 4, GFP_KERNEL); host-align_buffer kmalloc(128 * 4, GFP_KERNEL); if (!host-adma_desc || !host-align_buffer) { kfree(host-adma_desc); kfree(host-align_buffer); printk(KERN_WARNING %s: Unable to allocate ADMA buffers. Falling back to standard DMA.\n, mmc_hostname(mmc)); host-flags ~SDHCI_USE_ADMA; } } /* * If we use DMA, then its up to the caller to set the DMA * mask, but PIO does not need the hw shim so we set a new * mask here in that case. */ if (!(host-flags SDHCI_USE_DMA)) { host-dma_mask DMA_BIT_MASK(64); mmc_dev(host-mmc)-dma_mask host-dma_mask; } if (host-ops-get_max_clock) host-max_clk host-ops-get_max_clock(host); else { host-max_clk (caps SDHCI_CLOCK_BASE_MASK) SDHCI_CLOCK_BASE_SHIFT; host-max_clk * 1000000; } if (host-max_clk 0) { printk(KERN_ERR %s: Hardware doesnt specify base clock frequency.\n, mmc_hostname(mmc)); return -ENODEV; } if (host-ops-get_timeout_clock) host-timeout_clk host-ops-get_timeout_clock(host); else host-timeout_clk (caps SDHCI_TIMEOUT_CLK_MASK) SDHCI_TIMEOUT_CLK_SHIFT; if (host-timeout_clk 0) { printk(KERN_ERR %s: Hardware doesnt specify timeout clock frequency.\n, mmc_hostname(mmc)); return -ENODEV; } if (caps SDHCI_TIMEOUT_CLK_UNIT) host-timeout_clk * 1000; /* * Set host parameters. */ mmc-ops sdhci_ops; mmc-f_min host-max_clk / 256; mmc-f_max host-max_clk;#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE)mmc-caps | MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;#elsemmc-caps MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;#endifif ((caps SDHCI_CAN_DO_HISPD) ||(host-quirks SDHCI_QUIRK_FORCE_HIGHSPEED))mmc-caps | (MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED);mmc-ocr_avail 0; if (caps SDHCI_CAN_VDD_330) mmc-ocr_avail | MMC_VDD_32_33|MMC_VDD_33_34; if (caps SDHCI_CAN_VDD_300) mmc-ocr_avail | MMC_VDD_29_30|MMC_VDD_30_31; if (caps SDHCI_CAN_VDD_180) mmc-ocr_avail | MMC_VDD_165_195; if (mmc-ocr_avail 0) { printk(KERN_ERR %s: Hardware doesnt report any support voltages.\n, mmc_hostname(mmc)); return -ENODEV; } spin_lock_init(host-lock); /* * Maximum number of segments. Depends on if the hardware * can do scatter/gather or not. */ if (host-flags SDHCI_USE_ADMA) mmc-max_hw_segs 128; else if (host-flags SDHCI_USE_DMA) mmc-max_hw_segs 1; else /* PIO */ mmc-max_hw_segs 128; mmc-max_phys_segs 128; /* * Maximum number of sectors in one transfer. Limited by DMA boundary * size (512KiB). */ mmc-max_req_size 524288; /* * Maximum segment size. Could be one segment with the maximum number * of bytes. When doing hardware scatter/gather, each entry cannot * be larger than 64 KiB though. */ if (host-flags SDHCI_USE_ADMA) mmc-max_seg_size 65536; else mmc-max_seg_size mmc-max_req_size; /* * Maximum block size. This varies from controller to controller and * is specified in the capabilities register. */ mmc-max_blk_size (caps SDHCI_MAX_BLOCK_MASK) SDHCI_MAX_BLOCK_SHIFT; if (mmc-max_blk_size 3) { printk(KERN_WARNING %s: Invalid maximum block size, assuming 512 bytes\n, mmc_hostname(mmc)); mmc-max_blk_size 512; } else mmc-max_blk_size 512 mmc-max_blk_size; /* * Maximum block count. */ mmc-max_blk_count 65535; /* * Init tasklets. *///card_tasklet用于处理MMC插槽上的状态变化tasklet_init(host-card_tasklet,sdhci_tasklet_card, (unsigned long)host);//finish_tasklet用于命令传输完成后的处理tasklet_init(host-finish_tasklet,sdhci_tasklet_finish, (unsigned long)host);//Timer用于等待硬件中断//命令发送超时的相关处理setup_timer(host-timer, sdhci_timeout_timer, (unsigned long)host);//中断注册函request_irq的第一个参数中断号就取自于s3c_device_hsmmc3.resource里面的irq参数//sdhci_irq就是中断服务程序 该中断函数一般在插卡、拔卡或者从设备反馈给host信息时会被调用//命令数据收发中断的处理//见ret request_irq(host-irq, sdhci_irq, IRQF_SHARED,mmc_hostname(mmc), host);if (ret)goto untasklet;//使能SD 中断寄存器 和中断信号寄存器sdhci_init(host);#ifdef CONFIG_MMC_DEBUGsdhci_dumpregs(host);#endif#ifdef CONFIG_LEDS_CLASShost-led.name mmc_hostname(mmc);host-led.brightness LED_OFF;host-led.default_trigger mmc_hostname(mmc);host-led.brightness_set sdhci_led_control;ret led_classdev_register(mmc_dev(mmc), host-led); if (ret) goto reset;#endifmmiowb(); mmc_add_host(mmc); printk(KERN_INFO %s: SDHCI controller on %s [%s] using %s%s\n, mmc_hostname(mmc), host-hw_name, dev_name(mmc_dev(mmc)), (host-flags SDHCI_USE_ADMA)?A:, (host-flags SDHCI_USE_DMA)?DMA:PIO); return 0;#ifdef CONFIG_LEDS_CLASSreset:sdhci_reset(host, SDHCI_RESET_ALL);free_irq(host-irq, host);#endifuntasklet:tasklet_kill(host-card_tasklet);tasklet_kill(host-finish_tasklet);return ret;}///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////int sdhci_add_host(struct sdhci_host *host){mmc-ops sdhci_ops;}///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////static const struct mmc_host_ops sdhci_ops {//request函数指针指向的函数用来处理host向从设备发送命令的请求.request sdhci_request,//set_ios用来设置电源、时钟等等之类需要重点关注.set_ios sdhci_set_ios,//get_ro用来判断是否写保护.get_ro sdhci_get_ro,//使能SD/MMC IRQ中断.enable_sdio_irq sdhci_enable_sdio_irq,};/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////中断注册函request_irq的第一个参数中断号就取自于s3c_device_hsmmc3.resource里面的irq参数//sdhci_irq就是中断服务程序 该中断函数一般在插卡、拔卡或者从设备反馈给host信息时会被调用//见static irqreturn_t sdhci_irq(int irq, voiddev_id){irqreturn_t result;struct sdhci_hosthost dev_id;u32 intmask;int cardint 0;spin_lock(host-lock);////#define SDHCI_INT_STATUS 0x30图片intmask readl(host-ioaddr SDHCI_INT_STATUS);if (!intmask || intmask 0xffffffff) { result IRQ_NONE; goto out; } DBG(*** %s got interrupt: 0x%08x\n, mmc_hostname(host-mmc), intmask);//判断 insert或 remove标志位if (intmask (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {//写1清0 清 中断writel(intmask (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE),host-ioaddr SDHCI_INT_STATUS);//调度tasklet运行tasklet_schedule(host-card_tasklet);}intmask ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);//if (intmask SDHCI_INT_CMD_MASK) {#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE)/* read until all status bit is up. by scsuh */int i;for (i0; i0x1000000; i) {intmask readl(host-ioaddr SDHCI_INT_STATUS);if (intmask SDHCI_INT_RESPONSE)break;}if (0x1000000 i) {printk(“FAIL: waiting for status update.\n”);}#endifwritel(intmask SDHCI_INT_CMD_MASK,host-ioaddr SDHCI_INT_STATUS);sdhci_cmd_irq(host, intmask SDHCI_INT_CMD_MASK);}////命令处理中断和数据处理中断是不同的一条既有命令又有数据的mmc cmd//////会至少激活2次中断1次给命令1次给数据。if (intmask SDHCI_INT_DATA_MASK) {writel(intmask SDHCI_INT_DATA_MASK,host-ioaddr SDHCI_INT_STATUS);sdhci_data_irq(host, intmask SDHCI_INT_DATA_MASK);}intmask ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); intmask ~SDHCI_INT_ERROR; if (intmask SDHCI_INT_BUS_POWER) { printk(KERN_ERR %s: Card is consuming too much power!\n, mmc_hostname(host-mmc)); writel(SDHCI_INT_BUS_POWER, host-ioaddr SDHCI_INT_STATUS); } intmask ~SDHCI_INT_BUS_POWER; if (intmask SDHCI_INT_CARD_INT) cardint 1; intmask ~SDHCI_INT_CARD_INT; if (intmask) { printk(KERN_ERR %s: Unexpected interrupt 0x%08x.\n, mmc_hostname(host-mmc), intmask); sdhci_dumpregs(host); writel(intmask, host-ioaddr SDHCI_INT_STATUS); } result IRQ_HANDLED; mmiowb();out:spin_unlock(host-lock);/* * We have to delay this as it calls back into the driver. */ if (cardint) mmc_signal_sdio_irq(host-mmc); return result;}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////**mmc_add_host - initialise host hardwarehost: mmc hostRegister the host with the driver model. The host must beprepared to start servicing requests before this functioncompletes.*///将host添加入mmc管理其本质即是添加host的class_devint mmc_add_host(struct mmc_host *host){int err;//host-caps host capabilities 主机能力 高速低速WARN_ON((host-caps MMC_CAP_SDIO_IRQ) !host-ops-enable_sdio_irq);led_trigger_register_simple(dev_name(host-class_dev), host-led);////struct platform_device s3c_device_hsmmc1 - .dev??//这里添加的device_add是这个吗err device_add(host-class_dev);if (err)return err;#ifdef CONFIG_DEBUG_FSmmc_add_host_debugfs(host);#endif//启动hostmmc_start_host(host);return 0;}/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////启动hostvoid mmc_start_host(struct mmc_hosthost){//mmc_power_off 会调用mmc_set_ios(host); —调用host-ops-set_ios(host, ios);////static const struct mmc_host_ops sdhci_ops { 里赋值了set_ios成员//set_ios用来设置电源、时钟等等之类需要重点关注//关闭MMCmmc_power_off(host);//include/linux/mmc/host.h://#define MMC_CAP_BOOT_ONTHEFLY (1 8) /Can detect device at boot time *///在mmc_alloc_host时存在下面一行code//INIT_DELAYED_WORK(host-detect, mmc_rescan);//到此就会发现mmc_start_host一定会执行mmc_rescan//mmc_detect_change只不过一个是延时后执行要么直接调用mmc_rescan一个是没有延时执行。if (host-caps MMC_CAP_BOOT_ONTHEFLY) mmc_rescan(host-detect.work);//功能就是扫描所插入的卡 else mmc_detect_change(host, 0);//处理状态改变}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////**mmc_detect_change - process change of state on a MMC sockethost: host which changed state.delay: optional delay to wait before detection (jiffies)MMC drivers should call this when they detect a card has beeninserted or removed. The MMC layer will confirm that anypresent card is still functional, and initialize any newlyinserted.*/void mmc_detect_change(struct mmc_host *host, unsigned long delay){//queue_delayed_work把host-detect提交到workqueue工作对列中。//所以当此delayed_work执行的时候mmc_rescan将会被调用// Schedule delayed work in the MMC work queue.//类似于INIT_WORK和schedule_work的关系(工作队列)mmc_schedule_delayed_work(host-detect, delay);}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*Internal function. Schedule delayed work in the MMC work queue.*/static int mmc_schedule_delayed_work(struct delayed_work *work,unsigned long delay){//////drivers/mmc/core/core.c//static int __init mmc_init(void)函数里有////用于创建workqueue只创建一个内核线程 的工作队列////workqueue create_singlethread_workqueue(“kmmcd”);///drivers/mmc/core/host.c:mmc_alloc_host函数中调用//// INIT_DELAYED_WORK(host-detect, mmc_rescan); 初始化delayedwork并将函数与delay工作队列绑定return queue_delayed_work(workqueue, work, delay);}/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////void mmc_rescan(struct work_struct *work){struct mmc_host *host container_of(work, struct mmc_host, detect.work);u32 ocr;int err;mmc_bus_get(host);//如果为第一次扫描总线上设备if (host-bus_ops NULL) {/** Only we can add a new handler, so it’s safe to* release the lock here.*/mmc_bus_put(host);if (host-ops-get_cd host-ops-get_cd(host) 0) goto out;/* 驱动中使用mmc_claim_host(host);来得知,当前mmc控制器是否被占用,当前mmc控制器如果被占用,那么 host-claimed 1;否则为0,如果为1,那么会在for(;;)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 mmc_release_host()的时候,会激活登记到等待队列host-wq中的其他程序获得mmc主控制器的物理使用权*///这个函数和mmc_release_host(host);配对使用相当于一把锁就是在同一个时间只有一个sd卡可以保持和主控制器通讯mmc_claim_host(host);////最开始卡是power_off//开启host电源mmc_power_up(host);设置host为idle模式mmc_go_idle(host);///是用于验证SD卡接口操作状态的有效性命令CMD8。//如果 SD_SEND_IF_COND指示为符合SD2.0标准的卡//则设置操作状态寄存器ocrbit30指示能 够处理块地址SDHC卡//在SD子系统中所有的数据传输均是由host发起。//Host发送完一命令则会等待中断的产生在这里采用完成量来实现进程的阻塞。mmc_send_if_cond(host, host-ocr_avail);/* * First we search for SDIO... *///判断是否是SDIO协议卡//通过发送命令CMD5//CMD5协议与SD接口中的ACMD41类似用于检查是否支持SDIO的电压。//CMD5的回应中MP为0表示是SDIO卡如果MP为1表示不但是SDIO卡并且是SD卡。err mmc_send_io_op_cond(host, 0, ocr);if (!err) {if (mmc_attach_sdio(host, ocr))mmc_power_off(host);goto out;}/* * ...then normal SD... *///判断是否是SD协议卡//ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值//發送 CMD41 CMD55 讀取 OCR 的值//#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */err mmc_send_app_op_cond(host, 0, ocr);//if (!err) {//装载 绑定 SD卡设备if (mmc_attach_sd(host, ocr))mmc_power_off(host);goto out;}/* * ...and finally MMC. *///判断是否是MMC协议卡err mmc_send_op_cond(host, 0, ocr);if (!err) {if (mmc_attach_mmc(host, ocr))mmc_power_off(host);goto out;}////释放卡对主机的持有权mmc_release_host(host);mmc_power_off(host);} else {if (host-bus_ops-detect !host-bus_dead)host-bus_ops-detect(host);mmc_bus_put(host); }out:if (host-caps MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(host-detect, HZ);}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////static struct sdhci_ops sdhci_s3c_ops {.get_max_clock sdhci_s3c_get_max_clk,.get_timeout_clock sdhci_s3c_get_timeout_clk,.change_clock sdhci_s3c_change_clock,//用来设置电源、时钟等等之类.set_ios sdhci_s3c_set_ios,};

相关新闻