两年前实习时的文档——MMC学习总结

高通sdcard 启动流程 当 mmc_sd_init_card 成功后, 调用mmc_add_card (bus.c), 这里主要是识别卡是SD、SDXC 还是SDHC的卡,通过mmc_card_blockaddr 和 mmc_card_ext_capacity状态来分辨。然后mmc_select_voltage,此时会通过host->ocr_avail 对ocr 处理,拿到的数值rocr,作为参数给到mmc_sd_init_card (CONFIG_MMC_PARANOID_SD_INIT) 阅读详情


1 
概述

驱动程序实际上是硬件与应用程序之间的中间层。在Linux操作系统中,设备驱动程序对各种不同的设备提供了一致的访问接口,把设备映射成一个特殊的设备文件,用户程序可以像其他文件一样对设备文件进行操作。

Linux2.6引入了新的设备管理机制kobject,通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密联系,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。

在这些内核对象机制的基础上,Linux的设备模型包括设备结构device、驱动程序driver、总线bus和设备类结构class几个关键组件。

一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附于此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。Platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。

Platform设备概念的引入是能够更好地描述设备的资源信息。Platform设备是系统中自治的实体,包括基于端口的设备、外围总线和集成入片上系统平台的大多数控制器,它们通常直接通过CPU的总线寻址。每个platform设备被赋予一个名称,并分配一定数量的资源。

Platform总线对加入到该总线的设备和驱动分别封装了结构体——platform_device和platform_driver并且提供了对应的注册函数。

图1 Platform虚拟总线

由上图可知,在platform虚拟总线上我们分别对device和driver进行注册,这样我们能够更加方便的进行驱动设备的管理。这样当有总线或者设备注册到该虚拟总线上时,内核自动的调用platform_match函数将platform_device绑定到platform_driver上。


2  SDIO启动过程

在kernel启动时,内核会自动的调用MODULE_INIT宏对模块进行加载,MODULE_INIT声明了模块的入口函数。在MMC中我们模块的执行顺序如下图所示:


图2 SDIO启动过程

在这个过程中内核首先调用xxx_init函数,init函数对device进行注册,无论什么设备在内核中都会调用driver_register函数,driver_register函数经过一系列的调用,最终会调用探测函数probe(后面详细讲解)。probe函数会对模块进行探测工作,不断的对SD/MMC/SDIO卡进行扫描。


3  platform相关

3.1  platform数据结构

Linux在启动的时候就注册了platform总线,看内核源码:

struct bus_type platform_bus_type = {

       .name             ="platform",

       .dev_attrs       = platform_dev_attrs,

       .match            =platform_match,

       .uevent           =platform_uevent,

       .pm         =&platform_dev_pm_ops,

};

可以看到,总线中定义了.match函数,当有总线或者设备注册到platform总线时,内核自动调用.match函数,判断device和driver的name是否一致。

platform_device的结构体定义如下:

struct platform_device {

       constchar       * name;//设备名字,这将代替device->dev_id,用作sys/device下显示目录名

       int          id;//设备ID,用于给插入该总线并且具有相同name的设备编号,如果只有一个设备的话填-1

       structdevice   dev;//结构体中内嵌的device结构体

       u32         num_resources;//资源数

       structresource * resource;//用于存放资源的数组

 

       conststruct platform_device_id     *id_entry;

 

       /*arch specific additions */

       structpdev_archdata      archdata;

};

可以看出,在platform_device中定义了name,并且内嵌了structdevice结构体。另外,包含的structresource如下:

struct resource {

       resource_size_tstart;

       resource_size_tend;

       constchar *name;

       unsignedlong flags;

       structresource *parent, *sibling, *child;

};

platform_driver的结构体定义如下:

struct platform_driver {

       int(*probe)(struct platform_device *);

       int(*remove)(struct platform_device *);

       void(*shutdown)(struct platform_device *);

       int(*suspend)(struct platform_device *, pm_message_t state);

       int(*resume)(struct platform_device *);

       structdevice_driver driver;

       conststruct platform_device_id *id_table;

};

可以看出,在platform_driver中内嵌了structdevice_driver,以及一些回调函数。structdevice_driver的具体定义如下:

struct device_driver {

       constchar              *name;

 

       int(*probe) (struct device *dev);

       int(*remove) (struct device *dev);

       void(*shutdown) (struct device *dev);

       int(*suspend) (struct device *dev, pm_message_t state);

       int(*resume) (struct device *dev);

       conststruct attribute_group **groups;

 

       conststruct dev_pm_ops *pm;

 

       structdriver_private *p;

};

此处定义了name用于和platform_device匹配。因为platform_device和platform_driver的匹配就是通过内嵌的structdevice和structdevice_driver的name进行匹配的。

static int platform_match(structdevice *dev, struct device_driver *drv)

{

       structplatform_device *pdev = to_platform_device(dev);

       structplatform_driver *pdrv = to_platform_driver(drv);

 

       /*match against the id table first */

       if(pdrv->id_table)

              returnplatform_match_id(pdrv->id_table, pdev) != NULL;

 

       /*fall-back to driver name match */

       return(strcmp(pdev->name, drv->name) == 0);

}

在数据结构设计上,总线、设备及驱动三者相互关联platform  device包含device,根据device可以获得相应的bus及driver。

设备添加到总线上之后形成一个双向循环链表,根据总线可以获得其上挂接的所有device,进而获得了platform  device。根据device也可以获得驱动该总线上所有设备相应的driver。Platform包含driver,根据driver获得相应的bus,进而获得bus上所有的device,进一步获得platformdevice。根据name对driver和platform  device进行匹配,匹配成功后将device与相应的driver关联起来,即实现了platformdevice与platformdriver的关联。

匹配成功后调用driver的probe进而调用platformdriver的probe,在probe里实现驱动特定的功能。

Match函数只是简单的进行字符串匹配,这就是强调platformdevice和platform  driver中那么属性需要一致的原因。

3.2  platform device

3.2.1  register

注册一个platform层的设备。注册后,会在sys/device目录下创建一个以name命名的目录,并且创建软连接到/sys/bus/platform/device下。

int platform_device_register(structplatform_device *pdev)

{

       device_initialize(&pdev->dev);

       returnplatform_device_add(pdev);

}

其中,第一步device_initialize(在kernel文件夹下)用于初始化一个structdevice。函数的定义如下:

void device_initialize(struct device*dev)

{

       dev->kobj.kset= devices_kset;

       kobject_init(&dev->kobj,&device_ktype);

       INIT_LIST_HEAD(&dev->dma_pools);

       mutex_init(&dev->mutex);

       lockdep_set_novalidate_class(&dev->mutex);

       spin_lock_init(&dev->devres_lock);

       INIT_LIST_HEAD(&dev->devres_head);

       device_pm_init(dev);

       set_dev_node(dev,-1);

}

第二步,添加一个platform device到device层 。函数的定义如下:

int platform_device_add(structplatform_device *pdev)

{

       inti, ret = 0;

       if(!pdev)

              return -EINVAL;

       if(!pdev->dev.parent)

       pdev->dev.parent= &platform_bus;//如果p->dev.parent不存在则赋值&platform_bus

       pdev->dev.bus= &platform_bus_type;//设置pdev->dev.bus的bus类型

       if(pdev->id != -1)    //pdev->id!=-1说明存在多于一个的设备

              dev_set_name(&pdev->dev,"%s.%d", pdev->name, pdev->id);

       Else   //否则对唯一的设备进行命名

              dev_set_name(&pdev->dev,"%s", pdev->name);

 

       for(i = 0; i < pdev->num_resources; i++) {

    //遍历资源并且资源加入到资源数组中

              struct resource *p, *r =&pdev->resource[i];

              if (r->name == NULL)

                     r->name= dev_name(&pdev->dev);

              p = r->parent;

              if (!p) {

                     if(resource_type(r) == IORESOURCE_MEM)

                            p = &iomem_resource;

                     elseif (resource_type(r) == IORESOURCE_IO)

                            p = &ioport_resource;

              }

              if (p && insert_resource(p, r)) {

                     printk(KERN_ERR

                            "%s: failed to claim resource%d\n",

                            dev_name(&pdev->dev), i);

                     ret= -EBUSY;

                     gotofailed;

              }

       }

       pr_debug("Registeringplatform device '%s'. Parent at %s\n",

               dev_name(&pdev->dev),dev_name(pdev->dev.parent));

 

       ret= device_add(&pdev->dev);//(在core/core.c中定义)

       if(ret == 0)

              return ret;

 failed:

       while(--i >= 0) {

              struct resource *r =&pdev->resource[i];

              unsigned long type = resource_type(r);

 

              if (type == IORESOURCE_MEM || type ==IORESOURCE_IO)

                     release_resource(r);

       }

       returnret;

}

3.2.2  unregister

platform_device_unregister用于注销一个platform-leveldevice。函数的定义如下:

voidplatform_device_unregister(struct platform_device *pdev)

{

       platform_device_del(pdev);

       platform_device_put(pdev);

}

在注销一个设备时,第一步调用platform_device_del函数。此函数的定义如下:

void platform_device_del(struct platform_device*pdev)

{

       inti;

 

       if(pdev) {

              device_del(&pdev->dev);

//遍历所有的resource如果是IORESOURCE_MEM或者IORESOURCE_IO类型则调用release_resource函数释放掉resource。

              for (i = 0; i <pdev->num_resources; i++) {

                     structresource *r = &pdev->resource[i];

                     unsignedlong type = resource_type(r);

 

                     if(type == IORESOURCE_MEM || type == IORESOURCE_IO)

                            release_resource(r);

              }

       }

}

此函数的作用是:移除一个platform-leveldevice。其中的IOSOURCE_MEM和IORESOURCE_IR是CPU对外设I/O端口物理地址的两种编制方式。Resource是一个指向platform资源数组的指针,该数组有num_resource个资源,下面是资源结构体的定义(linux/ioport.h):

struct resource {

       resource_size_tstart;  //起始地址

       resource_size_tend;   //终止地址

       constchar *name;     //名称

       unsignedlong flags;    //标志

       structresource *parent, *sibling, *child;

};

在structplatform_device中可以设置多种资源信息。资源的flags标志包括:

#define  IORESOURCE_IO      0x00000100   //IO资源

#define  IORESOURCE_MEM   0x00000200  //内存资源

#define  IORESOURCE_IRQ    0x00000400  //中断资源

#define  IORESOURCE_DMA   0x00000800  //DMA资源

第二步,调用platform_device_put函数。

void platform_device_put(structplatform_device *pdev)

{

       if(pdev)

              put_device(&pdev->dev);

}

销毁一个platformdevice,并且释放所有与这个platformdevice相关的内存。

3.3  platform driver

3.3.1  register

通过调用函数platform_driver_register实现为platformlevel的设备注册一个驱动。注册成功后,内核会在/sys/bus/platform/driver/目录下创建一个名字为driver->name的目录。具体的函数定义如下:

int platform_driver_register(structplatform_driver *drv)

{

       drv->driver.bus= &platform_bus_type;

       if(drv->probe)

              drv->driver.probe =platform_drv_probe;

       if(drv->remove)

              drv->driver.remove =platform_drv_remove;

       if(drv->shutdown)

              drv->driver.shutdown =platform_drv_shutdown;

 

       returndriver_register(&drv->driver);

}

此函数首先对struct  platform_driver变量的driver进行赋值。然后调用driver_register并且返回。driver_register函数的定义如下:

int driver_register(structdevice_driver *drv)

{

       intret;

       structdevice_driver *other;

 

       BUG_ON(!drv->bus->p);

//如果driver的方法和总线上的方法不能匹配则驱动的名称需要更新

       if((drv->bus->probe && drv->probe) ||

           (drv->bus->remove &&drv->remove) ||

           (drv->bus->shutdown &&drv->shutdown))

              printk(KERN_WARNING "Driver '%s'needs updating - please use "

                     "bus_typemethods\n", drv->name);

 

       other= driver_find(drv->name, drv->bus);

       if(other) {

              put_driver(other);

              printk(KERN_ERR "Error: Driver '%s'is already registered, "

                     "aborting...\n",drv->name);

              return -EBUSY;

       }

 

       ret= bus_add_driver(drv);//将驱动加载到总线上,如果成功就返回

       if(ret)

              return ret;

       ret= driver_add_groups(drv, drv->groups);

       if(ret)

              bus_remove_driver(drv);

       returnret;

}

3.3.2  unregister

通过调用函数platform_driver_unregister函数实现platform_driver级设备的注销。

void platform_driver_unregister(structplatform_driver *drv)

{

       driver_unregister(&drv->driver);

}

在此函数中调用了driver_unregister函数,将驱动从系统中移除。函数的具体定义如下:

void driver_unregister(structdevice_driver *drv)

{

       if(!drv || !drv->p) {

              WARN(1, "Unexpected driverunregister!\n");

              return;

       }

       driver_remove_groups(drv,drv->groups);

       bus_remove_driver(drv);

}

分两步走,第一步调用driver_remove_groups。函数的具体定义如下:

static voiddriver_remove_groups(struct device_driver *drv,

                             const struct attribute_group **groups)

{

       inti;

 

       if(groups)

              for (i = 0; groups[i]; i++)

                     sysfs_remove_group(&drv->p->kobj,groups[i]);

}

其中,调用了

static inline voidsysfs_remove_group(struct kobject *kobj,

                                 const struct attribute_group *grp)

{

}

第二步从总线中将驱动删除。调用如下函数:

void bus_remove_driver(structdevice_driver *drv)

{

       if(!drv->bus)

              return;

 

       if(!drv->suppress_bind_attrs)

              remove_bind_files(drv);

       driver_remove_attrs(drv->bus,drv);

       driver_remove_file(drv,&driver_attr_uevent);

       klist_remove(&drv->p->knode_bus);

       pr_debug("bus:'%s': remove driver %s\n", drv->bus->name, drv->name);

       driver_detach(drv);

       module_remove_driver(drv);

       kobject_put(&drv->p->kobj);

       bus_put(drv->bus);

}

此函数的作用是:将驱动从它控制的设备中卸载,并且从驱动的总线链表中将它移除。


 

4 probe函数

 在kernel加载模块的时候启动了设备注册函数,因为所有的设备的driver都继承自device_driver,所以从driver_register看起,函数的源码如下:

int driver_register(structdevice_driver * drv)
{
 if ((drv->bus->probe &&drv->probe) ||
     (drv->bus->remove &&drv->remove) ||
     (drv->bus->shutdown &&drv->shutdown)) {
   printk(KERN_WARNING "Driver '%s'needs updating - please use bus_type methods\n", drv->name);
 }
 klist_init(&drv->klist_devices,NULL, NULL);
 return bus_add_driver(drv);
}

klist_init不相关,不用管他,具体再去看bus_add_driver:

int bus_add_driver(structdevice_driver *drv)
{
1.先kobject_set_name(&drv->kobj,"%s", drv->name);
2.再kobject_register(&drv->kobj)
3.然后调用了:driver_attach(drv)
}


int driver_attach(struct device_driver * drv)
{
 return bus_for_each_dev(drv->bus,NULL, drv, __driver_attach);
}

真正起作用的是__driver_attach:

static int __driver_attach(structdevice * dev, void * data)
{
……
 if (!dev->driver)
   driver_probe_device(drv, dev);
……
}


int driver_probe_device(struct device_driver * drv, struct device * dev)
{
……

//1.先是判断bus是否match:
 if (drv->bus->match &&!drv->bus->match(dev, drv))
   goto done;
//2.再具体执行probe:
 ret = really_probe(dev, drv);
……

}

really_probe才是我们要找的函数:
static int really_probe(struct device *dev, struct device_driver *drv)
{
……

//1.先是调用的驱动所属总线的probe函数:
 if (dev->bus->probe) {
   ret = dev->bus->probe(dev);
   if (ret)
    goto probe_failed;

   } else if (drv->probe) {
//2.再调用你的驱动中的probe函数:
   ret = drv->probe(dev);
   if (ret)
    goto probe_failed;
 }
……
}

其中,drv->probe(dev),才是真正调用的驱动实现的具体的probe函数。从此出开始probe函数正式被调用。

至此,platform成功挂接到platform  bus上了,并与特定的设备实现了绑定,并对设备进行了probe处理。


5 request函数

当SDIO设备启动之后,probe函数会调用mmc_alloc_host函数不断的检测连接的MMC/SD/SDIO卡,并且通过device_add完成对设备的添加,当设备添加完成之后,会调用mmc_blk_probe完成驱动定义的特定功能。函数的调用关系图如下:


图3 request调用过程

Request函数的定义如下:

static void

v8sdio_request( struct mmc_host *mmc,struct mmc_request *mrq )

{

       unsignedlong iflags = 0;

       structv8sdio_host *host = mmc_priv( mmc );//首先将struct  mmc_host设备转化成

                                       //struct  v8sdio_host

#if IRQ_STAT_DBG  

       if(host->id == DEBUG_CHN )

       {

              do_gettimeofday( &tv_now );//获取时间

              timersub( &tv_now, &tv_last,&tv_delta );

              if( tv_delta.tv_sec >= 5 )

              {

                     u32i = 0;

 

                     tv_last.tv_sec  = tv_now.tv_sec;

//                   tv_last.tv_usec= tv_now.tv_usec;

 

                     printk("\n" );

                     printk("[sdio%d_irq]: irq_ALL = %u\n", host->id, host->irq_cnt[0] );

                     printk("[sdio%d_irq]: irq_ERR = %u\n", host->id, host->irq_cnt[1] );

                     printk("[sdio%d_irq]: irq_DMA = %u\n", host->id, host->irq_cnt[2] );

                     printk("[sdio%d_irq]: irq_CMD = %u\n", host->id, host->irq_cnt[3] );

                     printk("[sdio%d_irq]: irq_TRN = %u\n", host->id, host->irq_cnt[4] );

                     for(i = 0; i <= MAX_OPCODE; i++ )

                     {

                            if( cmd_stats[i] )

                            {

                            printk( "[sdio%d_cmd]: cmd[%02u] =%u\n", host->id, i, cmd_stats[i] );

                            }

                     }    

              }

       }

#endif

 

       V8LOGV(V8TAG_SDIO, "" );

 

       if(host->mrq )

              V8LOGW( V8TAG_SDIO, "[Ch%d]host->mrq is NOT NULL.", host->id );

 

       clk_enable(host->aclk );   //使能主机aclk

 

       host->mrq= mrq;        //请求队列赋值

       local_irq_save(iflags);    //保存本地中断标志

       v8sdio_prepare_data(host, mrq );  //开启DMA通道,并且填充lli

       v8sdio_start_command(host, mrq->cmd, mrq->data );  //开始执行命令

       local_irq_restore(iflags);            //重新保存本地中断标志

}


6 rescan过程

内核通过mmc_rescan(drivers/mmc/core/core.c)不断扫描MMC/SD卡:

void mmc_rescan(struct work_struct*work)

{

       structmmc_host *host =

              container_of(work, struct mmc_host,detect.work);

       u32ocr;

       interr;

       unsignedlong flags;

       intextend_wakelock = 0;

 

       spin_lock_irqsave(&host->lock,flags);

 

       if(host->rescan_disable) {

              spin_unlock_irqrestore(&host->lock,flags);

              return;

       }

 

       spin_unlock_irqrestore(&host->lock,flags);

 

 

       mmc_bus_get(host);             //取得总线

 

       /*如果是个已经注册过的卡, 检查它是否存在 */

       if((host->bus_ops != NULL) && host->bus_ops->detect &&!host->bus_dead)

              host->bus_ops->detect(host);

 

       /*如果卡已经被移除,总线将被标记为死卡

        * —声明唤醒锁

        * 使得用户空间能够相应*/

       if(host->bus_dead)

              extend_wakelock = 1;

 

       mmc_bus_put(host);

 

 

       mmc_bus_get(host);

 

       /*如果当前还有卡,将它停止*/

       if(host->bus_ops != NULL) {

              mmc_bus_put(host);

              goto out;

       }

 

       /*检查新插入的卡 */

 

       /*

        * 只有我们能够添加新的处理器, 所以在这里释放锁是安全的。

         */

       mmc_bus_put(host);

 

       if(host->ops->get_cd && host->ops->get_cd(host) == 0)

              goto out;

 

       mmc_claim_host(host);

 

       mmc_power_up(host);

#ifdef CONFIG_MMC_VC088X

              if(host->caps & MMC_CAP_SDIO_IRQ){

                     sdio_reset(host);

              }

#else

       sdio_reset(host);

#endif

       mmc_go_idle(host);//发送CMD0使卡进入IDLE状态

 

       mmc_send_if_cond(host,host->ocr_avail);

 

       /*

        * 首先我们搜索SDIO...

        */

       err= mmc_send_io_op_cond(host, 0, &ocr);

       if(!err) {

              if (mmc_attach_sdio(host, ocr))

                     mmc_power_off(host);

              extend_wakelock = 1;

              goto out;

       }

 

       /*

        * ...然后普通的SD...

        */

       err= mmc_send_app_op_cond(host, 0, &ocr);

       if(!err) {

              if (mmc_attach_sd(host, ocr))

                     mmc_power_off(host);

              extend_wakelock = 1;

              goto out;

       }

 

       /*

        * ...最后MMC.

        */

       err= mmc_send_op_cond(host, 0, &ocr);

       if(!err) {

              if (mmc_attach_mmc(host, ocr))

                     mmc_power_off(host);

              extend_wakelock = 1;

              goto out;

       }

 

       mmc_release_host(host);

       mmc_power_off(host);

 

out:

       if(extend_wakelock)

              wake_lock_timeout(&mmc_delayed_work_wake_lock,HZ / 2);

       else

              wake_unlock(&mmc_delayed_work_wake_lock);

 

       if(host->caps & MMC_CAP_NEEDS_POLL)

              mmc_schedule_delayed_work(&host->detect,HZ);

}

在设置完每个卡进入空状态,而不管当前卡是存在于何种状态之后调用了mmc_send_if_cond命令,此命令的定义如下:

int mmc_send_if_cond(struct mmc_host*host, u32 ocr)

{

       structmmc_command cmd;

       interr;

       staticconst u8 test_pattern = 0xAA;

       u8result_pattern;

       /*

        * To support SD 2.0 cards, we must alwaysinvoke SD_SEND_IF_COND

        * before SD_APP_OP_COND. This command willharmlessly fail for

        * SD 1.0 cards.

        */

       cmd.opcode= SD_SEND_IF_COND;

       cmd.arg= ((ocr & 0xFF8000) != 0) << 8 | test_pattern;

       cmd.flags= MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;

 

       err= mmc_wait_for_cmd(host, &cmd, 0);

       if(err)

              return err;

 

       if(mmc_host_is_spi(host))

              result_pattern = cmd.resp[1] & 0xFF;

       else

              result_pattern = cmd.resp[0] & 0xFF;

 

       if(result_pattern != test_pattern)

              return -EIO;

 

       return0;

}

流程图如下:

                              图7  SD卡的状态图

⑴取得总线
    ⑵检查总线操作结构指针bus_ops,如果为空,则重新利用各总线对端口进行扫描,检测顺序依次为:SDIO、NormalSD、MMC。当检测到相应的卡类型后,就使用mmc_attach_bus()把相对应的总线操作与host连接起来。

voidmmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
    ...
    host->bus_ops = ops;
    ...
}

⑶初始化卡按以下流程初始化:
    ①发送CMD0使卡进入IDLE状态
    ②发送CMD8,检查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0Spec中提出了先发送CMD8,如响应为无效命令,则卡为SD1.1,否则就是SD2.0(请参考SD2.0Spec)。
   ③发送CMD5读取OCR寄存器。
   ④发送ACMD55、CMD41,使卡进入工作状态。MMC卡并不支持ACMD55、CMD41,如果这步通过了,则证明这张卡是SD卡。
   ⑤如果d步骤错误,则发送CMD1判断卡是否为MMC。SD卡不支持CMD1,而MMC卡支持,这就是SD和MMC类型的判断依据。
   ⑥如果ACMD41和CMD1都不能通过,那这张卡恐怕就是无效卡了,初始化失败。

 

 

 

 

 

 

 

 

 

 

 

 

MMC整流器(Matlab),技术文档 本文所介绍的MMC整流器采用双闭环控制、环流抑制、NLM调制和均压排序等技术手段,测试结果表明系统稳定性和可靠性得到了保证,桥臂电流THD由9.57%降至1.93%,网侧电流THD=1.47% 阅读详情

相关推荐

Linux内核4.14版本——mmc core(6)——mmc core主模块(2)card检测

emmc框架分析

yangguoyu8023的博客 2286

Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇

废话不多说,直接切进主题:        Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下 可见,core文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的服务sd.c, sd_ops.c等等。其中,最为核心的一个函数便是之前提到的位于core.c的mmc_rescan

知秋一叶 1万+

Linux内核4.14版本——mmc框架——mmc硬件总线扫描流程(以sd card为例)

emmc

yangguoyu8023的博客 3175

mmc rescan简析

mmc rescan是mmc host的detect work的功能函数,该函数

Never give up ,Hearts dream! 6039

Linux内核4.14版本——mmc core(3)——host模块

1. 前言 本文是Linux MMC framework的第二篇,将从驱动工程师的角度,介绍MMC host controller driver有关的知识,学习并掌握如何在MMC framework的框架下,编写MMC控制器的驱动程序。同时,通过本篇文章,我们会进一步的理解MMC、SD、SDIO等有关的基础知识。 2. MMC host驱动介绍 MMC的host driver,是用于驱动MMC host控制器的程序,位于“drivers/mmc/host”目录。从大的流...

yangguoyu8023的博客 1940

别再只接个放大器了!压电传感器测量电路(电压/电荷放大器)的5个实战配置要点

本文深入解析压电传感器测量电路中的5个关键实战配置要点,包括电缆长度选择、输入阻抗匹配、过载保护设计、低频噪声抑制及放大器选型策略。特别针对电压放大器和电荷放大器的应用场景差异提供工程实践建议,帮助工程师有效解决信号失真、噪声干扰等常见问题,提升测量精度和系统可靠性。

weixin_26869081的博客 302

【MMC驱动模块设计文档】

MMC驱动模块是一个完整的MMC/SD/SDIO存储和通信设备驱动框架,支持多种主机控制器(HC15、HC16),提供统一的设备访问接口。该模块采用分层架构设计,实现了从硬件抽象层到应用层的完整驱动栈。

weixin_40146547的博客 779

百度智能云训练图像分割模型+python调用模型并分割图像

目录 训练模型阶段 发布图像分割API 调用图像分割API 创建应用后显示以下两个代码 在pycharm中可以调用API mask解码 依赖库pycocotools安装步骤: 依赖库urllib2的安装 源码下载: 最近上课听老师介绍了一个训练图像分割模型的网站,把数据集上传之后做图像标注,然后进行训练就能得到准确率等指标。 训练模型阶段 图像分割:https://ai.baidu.com/easydl/vision/ 图像分割-->创建模型-->上传数据--&gt

icecreamdinner的博客 1389

MMC子系统分析

MMC子系统从协议到源码分析

gy794627991的博客 4506

一文搞懂LDO !

Start-up 时间是 IC 做死的,如果想要延时启动,最简单的办法是在 EN 上接一个大一些的电容到地,构成一个简单的 RC 电路。电容太小,电压跌落大,响应快。输出电流高,功率大;外围器件少,电路简单,成本低,负载响应快,输出纹波小;将采集的电压输入到比较器反向输入端,与正向输入端的基准电压(期望输出的电压)进行比较,再将比较结果进行放大;是指在给定负载变化下的输出电压的变化,这里的负载变化通常是从无负载到满负载。芯片的静态电流大小与其他性能成反关系,如低噪声,高电源电压抑制比,静态电流大。

weixin_46969363的博客 4384

中国各城市土地利用类型(城市功能)数据集(shp)

中国各城市土地利用类型(城市功能)数据集时间:2018年全国范围的城市用地类型数据(居住/商业/交通用地等共计11类)分类:居住用地、商业用地、工业用地、医疗设施用地、体育文化设施用地、交通场站用地、绿地等用地类型含城市编码、一级分类5个、二级分类11个1、上海市。

你好,我是小张同学! 1337

SD/MMC 协议官方原版英文文档

SD/MMC 协议官方原版英文文档 【下载地址】SDMMC协议官方原版英文文档 探索SD/MMC存储协议的核心技术!本资源提供官方原版英文文档,涵盖JEDEC标准与出版物的全部内容,经过JEDEC董事会和法律顾问的严格审查与批准。这份文档详细解析了SD/MMC卡的通信机制、命令集和数据结构,是开发者和工程师深入研究的必备...

gitblog_06731的博客 492

【异常】Claude Code 在 Windows CMD 中出现重影现象的排查与解决

Windows CMD运行Claude Code时出现文字重影、乱码等问题,原因是传统CMD对现代ANSI终端控制序列支持不足。解决方案包括:1)推荐安装Windows Terminal(完全支持ANSI标准);2)使用PowerShell 7;3)优化CMD设置(启用虚拟终端、UTF-8编码);4)关闭Claude Code视觉特效。建议优先使用Windows Terminal以获得最佳体验,这是最彻底的解决方案。其他方案仅能部分改善或缓解问题。

奶爸的编程之路,也就一周冷个三天 1961

Win10注册表没有MMC怎么办?

日常办公我们经常会找不到电脑中的组策略,遇到这种问题一般都是通过注册表重新设置“值”来解决。那如果在设置注册表值的时候找不到MMC,该怎么办?5、双击运行“1.reg”文件即可在注册表中找到MMC文件。1、首先,右键桌面选择“新建”→“文本文档”。4、修改后缀名为“.reg”,点“保存”。3、找到左上方“文件”,选择“另存为”。

xitongzhijianet的博客 4863

Dify之八添加各种在线大模型

Dify之八添加各种在线大模型

yilvyangguang520的博客 939

uniapp - 实现 App 端 “半屏幕“ 连续扫码功能,页面只有半个屏幕是扫码框并且可持续连续扫码获取数据(不使用官方 uni.scanCode API,因为无法连续扫码!)详细示例源码插件教程

uniapp app端实现半屏幕扫码,uniapp 扫一扫半个屏幕,uniapp 扫一扫,uniapp持续扫码实现,uniapp怎么自定义扫码和连续扫码,uni-app App端半屏连续扫码,uniapp 扫一扫半个屏幕,uniapp自定义扫码页面样式,实现连续扫码功能(uniapp),uniapp - 实现 App 端 "半屏幕" 连续扫码功能,页面只有半个屏幕是扫码框并且可持续连续扫码获取数据(不使用官方 uni.scanCode API,因为无法连续扫码!)详细示例源码插件教程。

✅ 精选推荐文章 1万+

Linux MMC 开发指南

介绍 Linux 内核中 SD/MMC 子系统的接口及使用方法,为 SD/MMC 设备驱动的开发提供参考。Linux 提供了 MMC 子系统来实现对各种 SD/MMC/EMMC/SDIO 设备访问,MMC 子系统由上到下可以分为三层,MMC/SD card 层,MMC/SD core 层以及 MMC/SD host 层,它们之间的层次关系如下所示。MMC/SD card 层负主要是按照 LINUX 块设备驱动程序的框架实现一个卡的块设备驱动。负责块设备请求的处理,以及请求队列的管理。

韦东山嵌入式专栏 4459

【微服务】Java 对接飞书多维表格使用详解

Java 对接飞书多维表格使用详解

congge_study的博客 1万+

LINUX MMC子系统分析之四 MMC host模块概述

本章主要介绍mmc host相关的内容,包括mmc子系统提供的mmc host相关的接口,以及mmc host与mmc card等子模块的关联等内容。我们按如下几部分进行介绍: 一、相关的数据结构体及关联关系 二、mmc host相关的接口 三、完成一个mmc host驱动需要哪些步骤 相关的数据结构及关联 该模块最重要的数据结构为mmc_host,用...

lickylin的专栏 5924

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

SD/MMC协议官方原版英文文档:开源资源助力存储技术研究

SD/MMC协议官方原版英文文档:开源资源助力存储技术研究 【下载地址】SDMMC协议官方原版英文文档 探索SD/MMC存储协议的核心技术!本资源提供官方原版英文文档,涵盖JEDEC标准与出版物的全部内容,经过JEDEC董事会和法律顾问的严格审查与批准。这份文档详细解析了SD/MMC卡的通信机制、命令集和数据结构,是开发...

gitblog_06761的博客 812

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

MMC/SD/eMMC/SDIO介绍

MMC卡(MultiMediaCard)的设计目标是提供一种“广泛应用于电子玩具、pda、照相机、智能手机、数字录音机、MP3播放器、寻呼机等等领域”的通用低成本数据存储和通信媒体。SD(Secure Digital Memory Card) 卡是在 MMC 卡基础 上发展起来的,中文名称为:安全数字存储卡。SD 卡发布之初,与 MMC 卡的最大区别就在安全(Secure)上,其支持 SDMI 标准,可提供保护 SD 卡上存储的音乐版权等功能。

qq_40569221的博客 8164

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

计算机 管理 mmc 注册表,win10注册表编辑器没有mmc怎么办_win10恢复注册表mmc文件夹的方法-win7之家...

在日常办公中有时候我们会遇到打不开组策略的情况,遇到这种问题一般都是通过注册表重新设置“值”来解决的。最近有小伙伴询问小编,自己在设置注册表值的时候找不到mmc文件这该怎么办?针对这种情况,小编接下来就教大家恢复注册表mmc文件夹的具体方法,希望能够帮助到大家。解决方法如下:1、首先,右键桌面选择“新建”→“文本文档”;2、双击打开,将下方内容复制粘贴进文档:Windows Registry Ed...

weixin_30781905的博客 7634

mmc子系统学习笔记四 mmc子系统的结构组织

从这一节起,我们正式进入mmc子系统学习笔记内容,这边walfred就根据自己的观点,整理了一份Linux下mmc子系统的结构组织框架,相信带着这个框架来理解学习mmc子系统应该会起到不错的效果。下面是mmc子系统的结构组织:          在Linux中,系统中mmc子系统本身没有任何README文档,所以此算一个"帮助文档"。其将涉及到3条总线,2块设备,2个驱动。 三条总线

dzhj11的专栏 1557

用C# 创建 MMC Snapin

下载示例 - 102 Kb 绪论The article/code snippet helps in creation of MMC Snapin using C#. 背景The framework for building management consoles for Windows NT/2000 is developed by Jim. Jim is the author for ironr

江湖·郎中·路 1822
上一篇: 两年前实习时的文档——Platform学习总结
下一篇: 存储器的重映射
野-百-合
博客等级 码龄17年 308粉丝 118原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值