linux设备模型之mmc,sd子系统<一>

来源:互联网 发布:诲女知之乎悔的意思 编辑:程序博客网 时间:2024/04/29 19:33

------------------------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://blog.csdn.net/gdt_a20
------------------------------------------------------------

sd卡driver最关键的是host部分,各个厂商需要根据自己平台的特性,定制自己的host

部分,当然内核也会提供一个专用的描述结构,在这里就是:

struct mmc_host {
171         struct device           *parent;         
172         struct device           class_dev;
173         int                     index;                                //编号
174         const struct mmc_host_ops *ops;         //特定控制器的操作函数,这个很重要
175         unsigned int            f_min;
176         unsigned int            f_max;
177         unsigned int            f_init;
178         u32                     ocr_avail;                      //支持电压范围
179         u32                     ocr_avail_sdio; /* SDIO-specific OCR */
180         u32                     ocr_avail_sd;   /* SD-specific OCR */
181         u32                     ocr_avail_mmc;  /* MMC-specific OCR */
182         struct notifier_block   pm_notify;
183
184 #define MMC_VDD_165_195         0x00000080      /* VDD voltage 1.65 - 1.95 */
185 #define MMC_VDD_20_21           0x00000100      /* VDD voltage 2.0 ~ 2.1 */
186 #define MMC_VDD_21_22           0x00000200      /* VDD voltage 2.1 ~ 2.2 */
187 #define MMC_VDD_22_23           0x00000400      /* VDD voltage 2.2 ~ 2.3 */
188 #define MMC_VDD_23_24           0x00000800      /* VDD voltage 2.3 ~ 2.4 */
189 #define MMC_VDD_24_25           0x00001000      /* VDD voltage 2.4 ~ 2.5 */
190 #define MMC_VDD_25_26           0x00002000      /* VDD voltage 2.5 ~ 2.6 */
191 #define MMC_VDD_26_27           0x00004000      /* VDD voltage 2.6 ~ 2.7 */
192 #define MMC_VDD_27_28           0x00008000      /* VDD voltage 2.7 ~ 2.8 */
193 #define MMC_VDD_28_29           0x00010000      /* VDD voltage 2.8 ~ 2.9 */
194 #define MMC_VDD_29_30           0x00020000      /* VDD voltage 2.9 ~ 3.0 */
195 #define MMC_VDD_30_31           0x00040000      /* VDD voltage 3.0 ~ 3.1 */
196 #define MMC_VDD_31_32           0x00080000      /* VDD voltage 3.1 ~ 3.2 */
197 #define MMC_VDD_32_33           0x00100000      /* VDD voltage 3.2 ~ 3.3 */
198 #define MMC_VDD_33_34           0x00200000      /* VDD voltage 3.3 ~ 3.4 */
199 #define MMC_VDD_34_35           0x00400000      /* VDD voltage 3.4 ~ 3.5 */
200 #define MMC_VDD_35_36           0x00800000      /* VDD voltage 3.5 ~ 3.6 */
201
202         unsigned long           caps;           /* Host capabilities */
203
204 #define MMC_CAP_4_BIT_DATA      (1 << 0)        /* Can the host do 4 bit transfers */
205 #define MMC_CAP_MMC_HIGHSPEED   (1 << 1)        /* Can do MMC high-speed timing */
206 #define MMC_CAP_SD_HIGHSPEED    (1 << 2)        /* Can do SD high-speed timing */
207 #define MMC_CAP_SDIO_IRQ        (1 << 3)        /* Can signal pending SDIO IRQs */
208 #define MMC_CAP_SPI             (1 << 4)        /* Talks only SPI protocols */
209 #define MMC_CAP_NEEDS_POLL      (1 << 5)        /* Needs polling for card-detection */
210 #define MMC_CAP_8_BIT_DATA      (1 << 6)        /* Can the host do 8 bit transfers */
211 #define MMC_CAP_DISABLE         (1 << 7)        /* Can the host be disabled */
212 #define MMC_CAP_NONREMOVABLE    (1 << 8)        /* Nonremovable e.g. eMMC */
213 #define MMC_CAP_WAIT_WHILE_BUSY (1 << 9)        /* Waits while card is busy */
214 #define MMC_CAP_ERASE           (1 << 10)       /* Allow erase/trim commands */
215 #define MMC_CAP_1_8V_DDR        (1 << 11)       /* can support */
216                                                 /* DDR mode at 1.8V */
217 #define MMC_CAP_1_2V_DDR        (1 << 12)       /* can support */
218                                                 /* DDR mode at 1.2V */
219 #define MMC_CAP_POWER_OFF_CARD  (1 << 13)       /* Can power off after boot */
220 #define MMC_CAP_BUS_WIDTH_TEST  (1 << 14)       /* CMD14/CMD19 bus width ok */
221 #define MMC_CAP_UHS_SDR12       (1 << 15)       /* Host supports UHS SDR12 mode */
222 #define MMC_CAP_UHS_SDR25       (1 << 16)       /* Host supports UHS SDR25 mode */
223 #define MMC_CAP_UHS_SDR50       (1 << 17)       /* Host supports UHS SDR50 mode */
224 #define MMC_CAP_UHS_SDR104      (1 << 18)       /* Host supports UHS SDR104 mode */
225 #define MMC_CAP_UHS_DDR50       (1 << 19)       /* Host supports UHS DDR50 mode */
226 #define MMC_CAP_SET_XPC_330     (1 << 20)       /* Host supports >150mA current at 3.3V */
227 #define MMC_CAP_SET_XPC_300     (1 << 21)       /* Host supports >150mA current at 3.0V */
228 #define MMC_CAP_SET_XPC_180     (1 << 22)       /* Host supports >150mA current at 1.8V */
229 #define MMC_CAP_DRIVER_TYPE_A   (1 << 23)       /* Host supports Driver Type A */
230 #define MMC_CAP_DRIVER_TYPE_C   (1 << 24)       /* Host supports Driver Type C */
231 #define MMC_CAP_DRIVER_TYPE_D   (1 << 25)       /* Host supports Driver Type D */
232 #define MMC_CAP_MAX_CURRENT_200 (1 << 26)       /* Host max current limit is 200mA */
233 #define MMC_CAP_MAX_CURRENT_400 (1 << 27)       /* Host max current limit is 400mA */
234 #define MMC_CAP_MAX_CURRENT_600 (1 << 28)       /* Host max current limit is 600mA */
235 #define MMC_CAP_MAX_CURRENT_800 (1 << 29)       /* Host max current limit is 800mA */
236 #define MMC_CAP_CMD23           (1 << 30)       /* CMD23 supported. */
237 #define MMC_CAP_HW_RESET        (1 << 31)       /* Hardware reset */
238
239         unsigned int            caps2;          /* More host capabilities */
240
241 #define MMC_CAP2_BOOTPART_NOACC (1 << 0)        /* Boot partition no access */
242 #define MMC_CAP2_CACHE_CTRL     (1 << 1)        /* Allow cache control */
243 #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2)       /* Notify poweroff supported */
244 #define MMC_CAP2_NO_MULTI_READ  (1 << 3)        /* Multiblock reads don't work */
245
246         mmc_pm_flag_t           pm_caps;        /* supported pm features */
247         unsigned int        power_notify_type;
248 #define MMC_HOST_PW_NOTIFY_NONE         0
249 #define MMC_HOST_PW_NOTIFY_SHORT        1
250 #define MMC_HOST_PW_NOTIFY_LONG         2
251
252 #ifdef CONFIG_MMC_CLKGATE
253         int                     clk_requests;   /* internal reference counter */
254         unsigned int            clk_delay;      /* number of MCI clk hold cycles */
255         bool                    clk_gated;      /* clock gated */
256         struct work_struct      clk_gate_work; /* delayed clock gate */
257         unsigned int            clk_old;        /* old clock value cache */
258         spinlock_t              clk_lock;       /* lock for clk fields */
259         struct mutex            clk_gate_mutex; /* mutex for clock gating */
260 #endif
261
262         /* host specific block data */
263         unsigned int            max_seg_size;   /* see blk_queue_max_segment_size */
264         unsigned short          max_segs;       /* see blk_queue_max_segments */
265         unsigned short          unused;
266         unsigned int            max_req_size;   /* maximum number of bytes in one req */
267         unsigned int            max_blk_size;   /* maximum size of one mmc block */
268         unsigned int            max_blk_count;  /* maximum number of blocks in one req */
269         unsigned int            max_discard_to; /* max. discard timeout in ms */
270
271         /* private data */
272         spinlock_t              lock;           /* lock for claim and bus ops */
273
274         struct mmc_ios          ios;            /* current io bus settings */
275         u32                     ocr;            /* the current OCR setting */
276
277         /* group bitfields together to minimize padding */
278         unsigned int            use_spi_crc:1;
279         unsigned int            claimed:1;      /* host exclusively claimed */
280         unsigned int            bus_dead:1;     /* bus has been released */
281 #ifdef CONFIG_MMC_DEBUG
282         unsigned int            removed:1;      /* host is being removed */
283 #endif
284
285         /* Only used with MMC_CAP_DISABLE */
286         int                     enabled;        /* host is enabled */
287         int                     rescan_disable; /* disable card detection */
288         int                     nesting_cnt;    /* "enable" nesting count */
289         int                     en_dis_recurs;  /* detect recursion */
290         unsigned int            disable_delay;  /* disable delay in msecs */
291         struct delayed_work     disable;        /* disabling work */
292
293         struct mmc_card         *card;          /* device attached to this host */
294
295         wait_queue_head_t       wq;
296         struct task_struct      *claimer;       /* task that has host claimed */
297         int                     claim_cnt;      /* "claim" nesting count */
298
299         struct delayed_work     detect;
300
301         const struct mmc_bus_ops *bus_ops;      /* current bus driver */
302         unsigned int            bus_refs;       /* reference counter */
303
304         unsigned int            sdio_irqs;
305         struct task_struct      *sdio_irq_thread;
306         atomic_t                sdio_irq_thread_abort;
307
308         mmc_pm_flag_t           pm_flags;       /* requested pm features */
309
310 #ifdef CONFIG_LEDS_TRIGGERS
311         struct led_trigger      *led;           /* activity led */
312 #endif
313
314 #ifdef CONFIG_REGULATOR
315         bool                    regulator_enabled; /* regulator state */
316 #endif
317
318         struct dentry           *debugfs_root;
319
320         struct mmc_async_req    *areq;          /* active async req */
321
322 #ifdef CONFIG_FAIL_MMC_REQUEST
323         struct fault_attr       fail_mmc_request;
324 #endif
325
326         unsigned long           private[0] ____cacheline_aligned;
327 };

#厂商不会直接拿这个结构体来用,一般都是在这基础上封装出自己的结构,还是拿满街都是的mini来看吧,
struct s3cmci_host {
    struct platform_device    *pdev;
    struct s3c24xx_mci_pdata *pdata;
    struct mmc_host        *mmc;           //内嵌标准的结构
    struct resource        *mem;
    struct clk        *clk;           、
    void __iomem        *base;
    int            irq;
    int            irq_cd;
    int            dma;            //dma通道

    unsigned long        clk_rate;       //频率
    unsigned long        clk_div;        //分频
    unsigned long        real_rate;
    u8            prescaler;

    int            is2440;         //平台判断
    unsigned        sdiimsk;
    unsigned        sdidata;
    int            dodma;
    int            dmatogo;

    bool            irq_disabled;   //irq相关
    bool            irq_enabled;
    bool            irq_state;
    int            sdio_irqen;

    struct mmc_request    *mrq;
    int            cmd_is_stop;

    spinlock_t        complete_lock;
    enum s3cmci_waitfor    complete_what;

    int            dma_complete;

    u32            pio_sgptr;
    u32            pio_bytes;
    u32            pio_count;
    u32            *pio_ptr;
#define XFER_NONE 0
#define XFER_READ 1
#define XFER_WRITE 2
    u32            pio_active;

    int            bus_width;

    char             dbgmsg_cmd[301];
    char             dbgmsg_dat[301];
    char            *status;

    unsigned int        ccnt, dcnt;
    struct tasklet_struct    pio_tasklet;

#ifdef CONFIG_DEBUG_FS
    struct dentry        *debug_root;
    struct dentry        *debug_state;
    struct dentry        *debug_regs;
#endif

#ifdef CONFIG_CPU_FREQ
    struct notifier_block    freq_transition;
#endif
};


#看一下host的注册过程吧,怎么进入mmc框架的,
#host对应的文件driver/mmc/host/s3cmci.c

#host的初始化

static int __init s3cmci_init(void)
{
    return platform_driver_register(&s3cmci_driver);
}

static struct platform_driver s3cmci_driver = {
    .driver    = {
        .name    = "s3c-sdi",
        .owner    = THIS_MODULE,
        .pm    = s3cmci_pm_ops,
    },
    .id_table    = s3cmci_driver_ids,    //支持的列表
    .probe        = s3cmci_probe,
    .remove        = __devexit_p(s3cmci_remove),
    .shutdown    = s3cmci_shutdown,
};


#有了driver,把设备也翻出来,
#mach-mini2440.c
/* MMC/SD  */



platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

static struct platform_device *mini2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_rtc,
    &s3c_device_usbgadget,
    &mini2440_device_eth,
    &mini2440_led1,
    &mini2440_led2,
    &mini2440_led3,
    &mini2440_led4,
    &mini2440_button_device,
    &s3c_device_nand,
    &s3c_device_sdi,
    &s3c_device_iis,
    &uda1340_codec,
    &mini2440_audio,
    &samsung_asoc_dma,
};

struct platform_device s3c_device_sdi = {
    .name        = "s3c2410-sdi",
    .id        = -1,
    .num_resources    = ARRAY_SIZE(s3c_sdi_resource),
    .resource    = s3c_sdi_resource,
};
static struct resource s3c_sdi_resource[] = {
    [0] = DEFINE_RES_MEM(S3C24XX_PA_SDI, S3C24XX_SZ_SDI),
    [1] = DEFINE_RES_IRQ(IRQ_SDI),
};


#匹配后会调用probe,
#即:

static int __devinit s3cmci_probe(struct platform_device *pdev)
{
    struct s3cmci_host *host;                                                                  //封装的结构体
    struct mmc_host    *mmc;                                                                 //标准mmc host
    int ret;
    int is2440;
    int i;

    is2440 = platform_get_device_id(pdev)->driver_data;                      //得到id号,判断具体平台

    mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);  //为host结构分配空间,id号,加入sys文件系统,还有个很重要的操作
    if (!mmc) {                                                                                        //初始化了工作队列,mmc_rescan.
        ret = -ENOMEM;
        goto probe_out;
    }

    for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {           //特定于平台的gpio配置
        ret = gpio_request(i, dev_name(&pdev->dev));
        if (ret) {
            dev_err(&pdev->dev, "failed to get gpio %d\n", i);

            for (i--; i >= S3C2410_GPE(5); i--)
                gpio_free(i);

            goto probe_free_host;
        }
    }

    host = mmc_priv(mmc);
    host->mmc     = mmc;
    host->pdev    = pdev;
    host->is2440    = is2440;

    host->pdata = pdev->dev.platform_data;
    if (!host->pdata) {
        pdev->dev.platform_data = &s3cmci_def_pdata;
        host->pdata = &s3cmci_def_pdata;
    }

    spin_lock_init(&host->complete_lock);
    tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);

    if (is2440) {
        host->sdiimsk    = S3C2440_SDIIMSK;      
        host->sdidata    = S3C2440_SDIDATA;
        host->clk_div    = 1;
    } else {
        host->sdiimsk    = S3C2410_SDIIMSK;
        host->sdidata    = S3C2410_SDIDATA;
        host->clk_div    = 2;
    }

    host->complete_what     = COMPLETION_NONE;      
    host->pio_active     = XFER_NONE;

#ifdef CONFIG_MMC_S3C_PIODMA
    host->dodma        = host->pdata->use_dma;
#endif

    host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);                        //io资源映射以及配置
    if (!host->mem) {
        dev_err(&pdev->dev,
            "failed to get io memory region resouce.\n");

        ret = -ENOENT;
        goto probe_free_gpio;
    }

    host->mem = request_mem_region(host->mem->start,
                       resource_size(host->mem), pdev->name);

    if (!host->mem) {
        dev_err(&pdev->dev, "failed to request io memory region.\n");
        ret = -ENOENT;
        goto probe_free_gpio;
    }

    host->base = ioremap(host->mem->start, resource_size(host->mem));
    if (!host->base) {
        dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
        ret = -EINVAL;
        goto probe_free_mem_region;
    }

    host->irq = platform_get_irq(pdev, 0);                                                                          //中断的申请及配置,这个是控制器
                                                                                                                                          //的中断信息,如命令成功与否,crc校验信息错误
    if (host->irq == 0) {
        dev_err(&pdev->dev, "failed to get interrupt resouce.\n");
        ret = -EINVAL;
        goto probe_iounmap;
    }

    if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
        dev_err(&pdev->dev, "failed to request mci interrupt.\n");
        ret = -ENOENT;
        goto probe_iounmap;
    }

    /* We get spurious interrupts even when we have set the IMSK
     * register to ignore everything, so use disable_irq() to make
     * ensure we don't lock the system with un-serviceable requests. */

    disable_irq(host->irq);                             //先disable该中断
    host->irq_state = false;

    if (!host->pdata->no_detect) {                 //插拔卡探测中断
        ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
        if (ret) {
            dev_err(&pdev->dev, "failed to get detect gpio\n");
            goto probe_free_irq;
        }

        host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);

        if (host->irq_cd >= 0) {                               //必须双延触发,假如插入后为高,拔出则为低
            if (request_irq(host->irq_cd, s3cmci_irq_cd,
                    IRQF_TRIGGER_RISING |
                    IRQF_TRIGGER_FALLING,
                    DRIVER_NAME, host)) {
                dev_err(&pdev->dev,
                    "can't get card detect irq.\n");
                ret = -ENOENT;
                goto probe_free_gpio_cd;
            }
        } else {
            dev_warn(&pdev->dev,
                 "host detect has no irq available\n");
            gpio_direction_input(host->pdata->gpio_detect);
        }
    } else
        host->irq_cd = -1;
                                              //写保护???
    if (!host->pdata->no_wprotect) {
        ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
        if (ret) {
            dev_err(&pdev->dev, "failed to get writeprotect\n");
            goto probe_free_irq_cd;
        }

        gpio_direction_input(host->pdata->gpio_wprotect);
    }

    /* depending on the dma state, get a dma channel to use. */
                                              //dma通道请求
    if (s3cmci_host_usedma(host)) {
        host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
                        host);
        if (host->dma < 0) {
            dev_err(&pdev->dev, "cannot get DMA channel.\n");
            if (!s3cmci_host_canpio()) {
                ret = -EBUSY;
                goto probe_free_gpio_wp;
            } else {
                dev_warn(&pdev->dev, "falling back to PIO.\n");
                host->dodma = 0;
            }
        }
    }
                                              //io clk
    host->clk = clk_get(&pdev->dev, "sdi");
    if (IS_ERR(host->clk)) {
        dev_err(&pdev->dev, "failed to find clock source.\n");
        ret = PTR_ERR(host->clk);
        host->clk = NULL;
        goto probe_free_dma;
    }
                                              //使能
    ret = clk_enable(host->clk);
    if (ret) {
        dev_err(&pdev->dev, "failed to enable clock source.\n");
        goto clk_free;
    }
                                             
    host->clk_rate = clk_get_rate(host->clk);

    mmc->ops     = &s3cmci_ops;                                                                 //mmc操作函数,重要
    mmc->ocr_avail    = MMC_VDD_32_33 | MMC_VDD_33_34;          //支持电压范围,预定义值
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
    mmc->caps    = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
    mmc->caps    = MMC_CAP_4_BIT_DATA;
#endif
    mmc->f_min     = host->clk_rate / (host->clk_div * 256);
    mmc->f_max     = host->clk_rate / host->clk_div;

    if (host->pdata->ocr_avail)
        mmc->ocr_avail = host->pdata->ocr_avail;                                         //平台相关自己设定值

    mmc->max_blk_count    = 4095;                                                            //块数量,大小
    mmc->max_blk_size    = 4095;
    mmc->max_req_size    = 4095 * 512;
    mmc->max_seg_size    = mmc->max_req_size;

    mmc->max_segs        = 128;

    dbg(host, dbg_debug,
        "probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
        (host->is2440?"2440":""),
        host->base, host->irq, host->irq_cd, host->dma);

    ret = s3cmci_cpufreq_register(host);                                                     //通知链,状态改变
    if (ret) {
        dev_err(&pdev->dev, "failed to register cpufreq\n");
        goto free_dmabuf;
    }

    ret = mmc_add_host(mmc);                                                                  //把host加入,每个平台都会调用该函数把host真正加入linux子系统,下文会介绍
    if (ret) {
        dev_err(&pdev->dev, "failed to add mmc host.\n");
        goto free_cpufreq;
    }

    s3cmci_debugfs_attach(host);

    platform_set_drvdata(pdev, mmc);
    dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
         s3cmci_host_usedma(host) ? "dma" : "pio",
         mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");

    return 0;

 free_cpufreq:
    s3cmci_cpufreq_deregister(host);

 free_dmabuf:
    clk_disable(host->clk);

 clk_free:
    clk_put(host->clk);

 probe_free_dma:
    if (s3cmci_host_usedma(host))
        s3c2410_dma_free(host->dma, &s3cmci_dma_client);

 probe_free_gpio_wp:
    if (!host->pdata->no_wprotect)
        gpio_free(host->pdata->gpio_wprotect);

 probe_free_gpio_cd:
    if (!host->pdata->no_detect)
        gpio_free(host->pdata->gpio_detect);

 probe_free_irq_cd:
    if (host->irq_cd >= 0)
        free_irq(host->irq_cd, host);

 probe_free_irq:
    free_irq(host->irq, host);

 probe_iounmap:
    iounmap(host->base);

 probe_free_mem_region:
    release_mem_region(host->mem->start, resource_size(host->mem));

 probe_free_gpio:
    for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
        gpio_free(i);

 probe_free_host:
    mmc_free_host(mmc);

 probe_out:
    return ret;
}


#重点是mmc_add_host函数,
#drivers/mmc/core/host.c


/**
 *    mmc_add_host - initialise host hardware
 *    @host: mmc host
 *
 *    Register the host with the driver model. The host must be
 *    prepared to start servicing requests before this function
 *    completes.
 */
int mmc_add_host(struct mmc_host *host)
{
    int err;

    WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
        !host->ops->enable_sdio_irq);

    err = device_add(&host->class_dev);    //加入bus
    if (err)
        return err;

    led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
                                               //用于显示卡状态的led
#ifdef CONFIG_DEBUG_FS
    mmc_add_host_debugfs(host);
#endif

    mmc_start_host(host);                  
    register_pm_notifier(&host->pm_notify); //加入块设备通知链

    return 0;
}

EXPORT_SYMBOL(mmc_add_host);


#继续mmc_start_host(host);

/drivers/mmc/core/core.c
void mmc_start_host(struct mmc_host *host)
{
    mmc_power_off(host);         //host power off
    mmc_detect_change(host, 0);  //detect
}


这两个函数都很重要,放到下文来说明。

总结:概要的说明了添加一个mmc host的过程,下文会重点分析一些很重要的函数,包括mmc_rescan.

Thanks




原创粉丝点击