S3C2440上MMC/SD卡驱动实例开发讲解(二)

Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇 回顾一下前面的知识,MMC 子系统范围三个部分: HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。 CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。 CARD 部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。 它们 阅读详情


嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。

一、开发环境

  • 主  机:VMWare--Fedora 9
  • 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 编译器:arm-linux-gcc-4.3.2

上接:S3C2440上MMC/SD卡驱动实例开发讲解(一)

6. s3cmci_ops SDI主机控制器操作接口函数功能分析:

static struct mmc_host_ops s3cmci_ops = 
{
    .request = s3cmci_request,//实现host的请求处理(即:命令和数据的发送和接收)
    .set_ios = s3cmci_set_ios,//通过核心层传递过来的ios,配置host寄存器(使能时钟、总线带宽等)
    .get_ro  = s3cmci_get_ro,//通过读取GPIO端口来判断卡是否写有保护
    .get_cd  = s3cmci_card_present,//通过读取GPIO端口来判断卡是否存在
};

mmc_host_ops结构体定义了对host主机进行操作的各种方法,其定义在Core核心层的host.h中,也就是Core核心层对Host主机层提供的接口函数。这里各种方法的函数原型如下:

void  (*request)(struct mmc_host *host, struct mmc_request *req);
void  (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int   (*get_ro)(struct mmc_host *host);
int   (*get_cd)(struct mmc_host *host);


从各函数原型上看,他们都将mmc_host结构体作为参数,所以我在刚开始的时候就说过mmc_host结构体是MMC/SD卡驱动中比较重要的数据结构。 可以这样说,他是Core层与Host层进行数据交换的载体。那么,这些接口函数何时会被调用呢?答案可以在Core层的core.c和sd.c中找到,我们可以看到如下部分代码:

static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
    ......
    host->ops->request(host, mrq);//导致s3cmci_request被调用
}

static inline void mmc_set_ios(struct mmc_host *host)
{
    ......
    host->ops->set_ios(host, ios);//导致s3cmci_set_ios被调用
}

void mmc_rescan(struct work_struct *work)
{
    ......//导致s3cmci_card_present被调用
    if (host->ops->get_cd && host->ops->get_cd(host) == 0)
            goto out;
    ......
}

static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
    struct mmc_card *oldcard)
{
    ......
    /* Check if read-only switch is active.*/
    if (!oldcard) 
    {   //导致s3cmci_get_ro被调用
        if (!host->ops->get_ro || host->ops->get_ro(host) < 0) 
        {
            printk(KERN_WARNING "%s: host does not "
                "support reading read-only "
                "switch. assuming write-enable.\n",
                mmc_hostname(host));
        } 
        else 
        {
            if (host->ops->get_ro(host) > 0)
                mmc_card_set_readonly(card);
        }
    }
    ......
}


好了,我们开始分析每个接口函数的具体实现吧,从简单的开始吧。 判断卡是否存在,如下代码:

static int s3cmci_card_present(struct mmc_host *mmc)
{
    
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    struct s3c24xx_mci_pdata *pdata = host->pdata;
    int ret;

    
//判断有无设置卡检测引脚端口,引脚在s3cmci_probe函数中已设置
    if (pdata->gpio_detect == 0)
        return -ENOSYS;

    
//从设置的卡检测引脚中读出当前的电平值,来判断卡是插入存在的还是被拔出不存在的
    ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;
    return ret ^ pdata->detect_invert;
}

获取卡是否写有保护,其实实现跟卡检查类似,代码如下:

static int s3cmci_get_ro(struct mmc_host *mmc)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    struct s3c24xx_mci_pdata *pdata = host->pdata;
    int ret;

    //判断有无设置卡写保护引脚端口,引脚在s3cmci_probe函数中已设置
    if (pdata->gpio_wprotect == 0)
        return 0;

    //从设置的卡写保护引脚中读出当前的电平值,来判断卡是否写有保护
    ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);

    if (pdata->wprotect_invert)
        ret = !ret;

    return ret;
}

配置host寄存器的时钟和总线宽度,代码如下:

static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    u32 mci_con;

    //读取SDI控制寄存器的值
    mci_con = readl(host->base + S3C2410_SDICON);

    //ios结构体参数从Core层传递过来,根据不同的电源状态来配置SDI各寄存器
    switch (ios->power_mode) 
    {
        case MMC_POWER_ON:
        case MMC_POWER_UP:
            //根据开发板引脚连接情况配置SDI控制器的各信号线,包括:时钟线、命令线和四条数据线
            s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
            s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
            s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
            s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
            s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
            s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
    
            if (host->pdata->set_power)
                host->pdata->set_power(ios->power_mode, ios->vdd);
    
            break;
    
        case MMC_POWER_OFF:
        default:
            //如果电源状态为关闭或者默认情况下,关闭SDI的时钟信号
            s3c2410_gpio_setpin(S3C2410_GPE5, 0);
            s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
    
            //根据数据手册的SDICON寄存器位的介绍,此处是将整个sdmmc时钟复位
            mci_con |= S3C2440_SDICON_SDRESET;
    
            if (host->pdata->set_power)
                host->pdata->set_power(ios->power_mode, ios->vdd);
    
            break;
    }

    //设置SDI波特率预定标器寄存器以确定时钟,看其定义部分
    s3cmci_set_clk(host, ios);

    //根据SDI当前的时钟频率来设置寄存器的使能时钟位
    if (ios->clock)
        mci_con |= S3C2410_SDICON_CLOCKTYPE;
    else
        mci_con &= ~S3C2410_SDICON_CLOCKTYPE;

    //将计算好的值写回SDI控制寄存器
    writel(mci_con, host->base + S3C2410_SDICON);

    //下面只是一些调试信息,可以不要
    if ((ios->power_mode == MMC_POWER_ON) || (ios->power_mode == MMC_POWER_UP)) 
    {
        dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",
            host->real_rate/1000, ios->clock/1000);
    } 
    else 
    {
        dbg(host, dbg_conf, "powered down.\n");
    }

    //设置总线宽度
    host->bus_width = ios->bus_width;
}

//设置SDI波特率预定标器寄存器以确定时钟
static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios)
{
    u32 mci_psc;

    //根据SDI工作时钟频率范围来确定时钟预分频器值
    for (mci_psc = 0; mci_psc < 255; mci_psc++) 
    {
        host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));

        if (host->real_rate <= ios->clock)
            break;
    }

    //根据数据手册描述,SDI波特率预定标器寄存器只有8个位,所以最大值为255
    if (mci_psc > 255)
        mci_psc = 255;

    host->prescaler = mci_psc;//确定的预分频器值
    
    //将预分频器值写于SDI波特率预定标器寄存器中
    writel(host->prescaler, host->base + S3C2410_SDIPRE);

    if (ios->clock == 0)
        host->real_rate = 0;
}

MMC/SD请求处理,这是Host驱动中比较重要的一部分。请求处理的整个流程请参考(一)中的流程图,他很好的描述了一个请求是怎样从Host层发出,通过Core层提交到Card层被块设备处理的。下面看代码:

static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);

    //s3cmci_host结构体定义的status主要是记录请求过程所处的阶段及状态,方便调试时使用
    host->status = "mmc request";
    //请求处理主要包括MMC/SD命令和数据处理,所以定义cmd_is_stop来区分是哪种请求
    host->cmd_is_stop = 0;
    //将Core层的mmc_request对象保存到Host层中以备使用
    host->mrq = mrq;

    //在开始发出一个请求前先要检测一下卡是否还存在,否则提交到了块设备层而没有请求处理的对象发生错误
    if (s3cmci_card_present(mmc) == 0) 
    {
        dbg(host, dbg_err, "%s: no medium present\n", __func__);
        host->mrq->cmd->error = -ENOMEDIUM;
        mmc_request_done(mmc, mrq);//如果卡不存在则马上结束这次请求
    } 
    else
    {
        s3cmci_send_request(mmc);//如果卡还存在则发出请求
    }
}

//发送请求
static void s3cmci_send_request(struct mmc_host *mmc)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    //取出在s3cmci_request函数中保存的mmc_request对象以使用
    struct mmc_request *mrq = host->mrq;
    //在s3cmci_request函数中设置的cmd_is_stop初始值为0,表示当前是命令请求
    struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;

    //清空SDI命令状态寄存器、数据状态寄存器和FIFO状态寄存器
    writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
    writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
    writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);

    //如果当前这次的请求是数据请求
    if (cmd->data) 
    {
        //进入数据请求处理设置,主要是数据控制寄存器的配置
        int res = s3cmci_setup_data(host, cmd->data);

        if (res) 
        {
            //如果在数据请求设置中出现异常,则马上结束这次请求
            dbg(host, dbg_err, "setup data error %d\n", res);
            cmd->error = res;
            cmd->data->error = res;

            mmc_request_done(mmc, mrq);
            return;
        }

        //判断数据处理的方式是DAM还是FIFO,在s3cmci_probe函数中初始的是0,所以没有使用DMA的方式
        if (host->dodma)
            res = s3cmci_prepare_dma(host, cmd->data);
        else
            res = s3cmci_prepare_pio(host, cmd->data);

        if (res) 
        {
            //如果请求处理数据失败则也要马上结束这次请求
            dbg(host, dbg_err, "data prepare error %d\n", res);
            cmd->error = res;
            cmd->data->error = res;

            mmc_request_done(mmc, mrq);
            return;
        }
    }

    //否则这次请求是命令请求
    s3cmci_send_command(host, cmd);

    //还记得在s3cmci_probe中SDI未准备好是屏蔽了SD中断,所以这里就使能中断
    enable_irq(host->irq);
}

//数据请求处理设置,主要是数据控制寄存器的配置
static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
{
    u32 dcon, imsk, stoptries = 3;

    /*如果不是数据处理请求则清零SDI数据控制寄存器*/
    if (!data) 
    {
        writel(0, host->base + S3C2410_SDIDCON);
        return 0;
    }

    //根据SDI模块大小寄存器描述,如果在多模块下BlkSize必须分配字大小即:BlkSize[1:0]=00
    //所以这里与上3(即:二进制的11)来判断的是单模块
    if ((data->blksz & 3) != 0) 
    {
        //如果在单模块处理的情况下,模块数大于1了,就出现异常
        if (data->blocks > 1) 
        {
            pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n",__func__, data->blksz);
            return -EINVAL;
        }
    }

    //循环判断数据是否正在传输中(发送或者接收)
    while (readl(host->base + S3C2410_SDIDSTA) & (S3C2410_SDIDSTA_TXDATAON |S3C2410_SDIDSTA_RXDATAON)) 
    {
        dbg(host, dbg_err, "mci_setup_data() transfer stillin progress.\n");

        //如果正在传输中则立刻停止传输
        writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
        //接着立刻复位整个MMC/SD时钟
        s3cmci_reset(host);

        //这里应该是起到一个延迟的效果。因为硬件停止传输到复位MMC/SD需要一点时间,而循环判断非常快。
        //如果在这个时间内硬件还处在数据传输中而没有复位好,则异常
        if ((stoptries--) == 0) 
        {
            return -EINVAL;
        }
    }

    dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;

    //如果使用DMA传输,则使能SDI数据控制寄存器的DMA
    if (host->dodma)
        dcon |= S3C2410_SDIDCON_DMAEN;

    //如果设置总线宽度为4线,则使能SDI数据控制寄存器的总线宽度模式为宽总线模式(即:4线模式)
    if (host->bus_width == MMC_BUS_WIDTH_4)
        dcon |= S3C2410_SDIDCON_WIDEBUS;

    //配置SDI数据控制寄存器的数据传输模式为模块数据传输
    if (!(data->flags & MMC_DATA_STREAM))
        dcon |= S3C2410_SDIDCON_BLOCKMODE;

    if (data->flags & MMC_DATA_WRITE) 
    {
        //数据发送命令响应收到后开始数据传输
        dcon |= S3C2410_SDIDCON_TXAFTERRESP;
        //数据发送模式
        dcon |= S3C2410_SDIDCON_XFER_TXSTART;
    }

    if (data->flags & MMC_DATA_READ) 
    {
        //数据发送命令响应收到后开始数据接收
        dcon |= S3C2410_SDIDCON_RXAFTERCMD;
        //数据接收模式
        dcon |= S3C2410_SDIDCON_XFER_RXSTART;
    }

    //FIFO传输的大小使用字传输类型
    dcon |= S3C2440_SDIDCON_DS_WORD;
    
    //数据传输开始
    dcon |= S3C2440_SDIDCON_DATSTART;

    //将以上配置的值写入SDI数据控制寄存器生效
    writel(dcon, host->base + S3C2410_SDIDCON);

    //配置模块大小寄存器的块大小值
    writel(data->blksz, host->base + S3C2410_SDIBSIZE);

    //出现FIFO失败SDI中断使能;数据接收CRC错误SDI中断使能;数据接收超时SDI中断使能;数据计时器为0SDI中断使能
    imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
    enable_imask(host, imsk);
//使能中断


    //将配置的值写入SDI中断屏蔽寄存器,使之生效
    writel(0x007FFFFF, host->base + S3C2410_SDITIMER);

    return 0;
}

//复位整个MMC/SD时钟
static void s3cmci_reset(struct s3cmci_host *host)
{
    u32 con = readl(host->base + S3C2410_SDICON
);

    con |= S3C2440_SDICON_SDRESET;
    writel(con, host->base + S3C2410_SDICON);
}


//使能中断
static inline u32 enable_imask(struct s3cmci_host *host, u32 imask)
{
    u32 newmask;

    newmask = readl(host->base + host->sdiimsk);
    newmask |= imask;

    writel(newmask, host->base + host->sdiimsk);

    return newmask;
}

//屏蔽中断
static inline u32 disable_imask(struct s3cmci_host *host, u32 imask)
{
    u32 newmask;

    newmask = readl(host->base + host->sdiimsk);
    newmask &= ~imask;

    writel(newmask, host->base + host->sdiimsk);

    return newmask;
}

//清空中断屏蔽寄存器
static inline void clear_imask(struct s3cmci_host *host)
{
    writel(0, host->base + host->sdiimsk);
}

//使用DMA传输数据方式,注意:这里就不讲如何使用DMA的具体细节了,以后再讲。
//对于驱动中相关DMA操作的方法都在plat-s3c24xx/dma.c中定义了。
static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
    int dma_len, i;
    
    //判断DMA传输的方向是读还是写
    int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;

    //根据传输的方向来配置DMA相关寄存器
    s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
    //s3c2410_dma_ctrl函数将根据标志flag来控制DMA传输的开始、停止等操作
    s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);

    //合并data->sg上相邻的段,映射一个发散/汇聚DMA操作

    //返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同。
    dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
             (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

    if (dma_len == 0)
        return -ENOMEM;

    host->dma_complete = 0;//初始DMA操作的状态
    host->dmatogo = dma_len;//保存合并后的段数

    for (i = 0; i < dma_len; i++) 
    {
        int res;

        //分配一个数据段管理结构体,并将各数据段穿成单向链表,以及加载一个数据段到DMA通道

        //sg_dma_address返回的是总线(DMA)的地址,sg_dma_len返回的是缓存区的长度
        res = s3c2410_dma_enqueue(host->dma, (void *) host, sg_dma_address(&data->sg[i]),sg_dma_len(&data->sg[i]));

        if (res) 
        {
            s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
            return -EBUSY;
        }
    }

    //开始DMA数据传输,数据传输会在接收到请求后真正开始
    s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);

    return 0;
}

//根据传输的方向来配置DMA相关寄存器,详细描述请查看数据手册DMA章节
static void s3cmci_dma_setup(struct s3cmci_host *host, enum s3c2410_dmasrc source)
{
    static enum s3c2410_dmasrc last_source = -1;
    static int setup_ok;

    if (last_source == source)
        return;

    last_source = source;

    //配置DMA源或者目标硬件类型和地址,这里DMA使用的是物理地址,不是虚拟地址。
    s3c2410_dma_devconfig(host->dma, source, 3, host->mem->start + host->sdidata);

    //这个判断的作用是让下面的代码只执行一次,以后不在被执行
    if (!setup_ok) 
    {
        //配置DMA控制寄存器中的传输数据大小单位
        s3c2410_dma_config(host->dma, 4, 0);
        //设置DMA回调函数为s3cmci_dma_done_callback,当一段数据传输完后该函数被调用
        s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback);
        s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART);
        setup_ok = 1;
    }
}

 

//DMA回调函数, 当一段数据传输完后该函数被调用
static void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id, intsize,
                 enum s3c2410_dma_buffresult result)
{
    struct s3cmci_host *host = buf_id;
//这个s3cmci_host类型的参数是在s3c2410_dma_enqueue的时候传递进来的

    unsigned long iflags;
    u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt;

    mci_csta = readl(host->base + S3C2410_SDICMDSTAT);//命令状态寄存器的值
    mci_dsta = readl(host->base + S3C2410_SDIDSTA);//数据状态寄存器的值
    mci_fsta = readl(host->base + S3C2410_SDIFSTA);//FIFO状态寄存器的值
    mci_dcnt = readl(host->base + S3C2410_SDIDCNT);//数据保留计数器寄存器的值

    spin_lock_irqsave(&host->complete_lock, iflags);

    //如果DMA返回错误,则调到错误处理处进行错误处理
    if (result != S3C2410_RES_OK) 
    {
        goto fail_request;
    }

    host->dmatogo--; 
//合并data->sg上相邻后的段数递减

    
    //如果合并的段数不为0,即所有的段还没有处理完
    if (host->dmatogo) 
    {
        goto out;
    }

    //否则,标识这次DMA操作真正完成了
    host->complete_what = COMPLETION_FINALIZE;

out:
    //切换到中断底半部执行
    tasklet_schedule(&host->pio_tasklet);
    spin_unlock_irqrestore(&host->complete_lock, iflags);
    return;

fail_request:
    host->mrq->data->error = -EINVAL;
    host->complete_what = COMPLETION_FINALIZE;
    //如果DMA请求失败,则屏蔽SDI中断
    writel(0, host->base + host->sdiimsk);
    goto out;
}

//使用FIFO传输数据方式。具体操作就是调用do_pio_write往FIFO中填充数据,当64字节的FIFO少于33字节时就会产生中断;
//或者是从SD读数据,则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read读出FIFO的数据。
static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data)
{
    //跟DMA类似,这里同样要判断FIFO传输的方向是读还是写
    int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;

    host->pio_sgptr = 0;
    host->pio_bytes = 0;
    host->pio_count = 0;
    host->pio_active = rw ? XFER_WRITE : XFER_READ;
//记录FIFO操作状态共三种:读、写和无操作,定义在驱动头文件中

    if (rw) //写
    {
        //FIFO写操作
        do_pio_write(host);
        //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当发送FIFO半填满就产生SDI中断
        enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
    } 
    else //读
    {
        //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当接收FIFO半填满或者接收FIFO有最后数据就产生SDI中断
        enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST);
    }

    return 0;
}

//FIFO写操作(即填充FIFO)
static void do_pio_write(struct s3cmci_host *host)
{
    void __iomem *to_ptr;
    int res;
    u32 fifo;
    u32 *ptr;

    //SDI数据寄存器的虚拟地址
    to_ptr = host->base + host->sdidata;

    //检查FIFO中当前的剩余空间
    while ((fifo = fifo_free(host)) > 3) 
    {
        if (!host->pio_bytes) 
        {
            //从分散聚集列表中获取要写的数据缓存,这里主要是获取缓存的长度和开始地址
            res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr);
            if (res) 
            {
                host->pio_active = XFER_NONE;
                return;
            }
        }

        //如果FIFO剩余空间比这一次要写入的数据段长度要大
        if (fifo >= host->pio_bytes)
            fifo = host->pio_bytes;
        else
            fifo -= fifo & 3;

        host->pio_bytes -= fifo;//更新还剩下没写完的缓存长度
        host->pio_count += fifo;
        fifo = (fifo + 3) >> 2;//将字节数转化为字数
        ptr = host->pio_ptr;
        
        while (fifo--)//写入FIFO
            writel(*ptr++, to_ptr);
            
        host->pio_ptr = ptr;
//更新当前地址指针的位置

    }

    //FIFO半填满时发生MMC/SD中断
    enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
}

//FIFO读操作
static void do_pio_read(struct s3cmci_host *host)
{
    int res;
    u32 fifo;
    u32 *ptr;
    u32 fifo_words;
    void __iomem *from_ptr;

    //设置SDI波特率预定标器寄存器的值
    writel(host->prescaler, host->base + S3C2410_SDIPRE);

    //SDI数据寄存器的虚拟地址
    from_ptr = host->base + host->sdidata;

    //检测FIFO中当前的数据个数
    while ((fifo = fifo_count(host))) 
    {
        if (!host->pio_bytes)
        {
            //从分散聚集列表中获取要读数据缓存,这里主要是获取缓存的长度和开始地址的指针位置
            res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr);
            if (res) 
            {
                host->pio_active = XFER_NONE;
                host->complete_what = COMPLETION_FINALIZE;
                return;
            }
        }

        //如果FIFO中当前的数据个数比这一次要读出的数据段长度要大
        if (fifo >= host->pio_bytes)
            fifo = host->pio_bytes;
        else
            fifo -= fifo & 3;

        host->pio_bytes -= fifo;//更新还剩下没读完的缓存长度
        host->pio_count += fifo;
        fifo_words = fifo >> 2;//将字节数转化为字数
        ptr = host->pio_ptr;
        
        while (fifo_words--)//从FIFO中读出数据
            *ptr++ = readl(from_ptr);
            
        host->pio_ptr = ptr;
//更新当前地址指针的位置


        //如果fifo中的数据非字对齐则读取非对齐部分
        if (fifo & 3) 
        {
            u32 n = fifo & 3;
            u32 data = readl(from_ptr);
            u8 *p = (u8 *)host->pio_ptr;

            while (n--) 
            {
                *p++ = data;
                data >>= 8;
            }
        }
    }

    //请求的数据已读完
    if (!host->pio_bytes) 
    {
        res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr);
        if (res) 
        {
            host->pio_active = XFER_NONE;
            host->complete_what = COMPLETION_FINALIZE;
            return;
        }
    }

    //接收FIFO半满或者接收FIFO有最后数据时发生MMC/SD中断
    enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST);
}

//检测FIFO中当前的数据个数
static inline u32 fifo_count(struct s3cmci_host *host)
{
    //读取SDI FIFO状态寄存器
    u32 fifostat = readl(host->base + S3C2410_SDIFSTA);

    //FIFO中的数据个数是保存在寄存器的0-6位,所以与上S3C2410_SDIFSTA_COUNTMASK得出数据个数值
    //S3C2410_SDIFSTA_COUNTMASK定义在regs-sdi.h中为:0x7f,即:1111111
    fifostat &= S3C2410_SDIFSTA_COUNTMASK;
    return fifostat;
}

//检查FIFO中当前的剩余空间
static inline u32 fifo_free(struct s3cmci_host *host)
{
    //这里跟检测FIFO中当前的数据个数是一样的
    u32 fifostat = readl(host->base + S3C2410_SDIFSTA);

    fifostat &= S3C2410_SDIFSTA_COUNTMASK;
    return 63 - fifostat;//用FIFO的总容量-FIFO中当前的数据个数=剩余空间
}

//MMC/SD核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集,
//使用这种方法,使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题
static inline int get_data_buffer(struct s3cmci_host *host, u32 *bytes, u32 **pointer)
{
    struct scatterlist *sg;

    //FIFO当前的操作状态验证
    if (host->pio_active == XFER_NONE)
        return -EINVAL;

    //MMC/SD请求及数据有效性验证
    if ((!host->mrq) || (!host->mrq->data))
        return -EINVAL;

    //数据缓存的入口有没有超过分散列表的范围
    if (host->pio_sgptr >= host->mrq->data->sg_len) 
        return -EBUSY;

    //从分散聚集列表中获取一段数据缓存
    sg = &host->mrq->data->sg[host->pio_sgptr];

    *bytes = sg->length;//该段数据缓存的长度
    *pointer = sg_virt(sg);
//该段数据缓存的入口地址(为虚拟地址),相当于一个游标的意思

    host->pio_sgptr++;
//准备下一段数据缓存的入口

    return 0;
}

//以上三段代码是对发送数据请求处理的,下面是发送命令请求
static void s3cmci_send_command(struct s3cmci_host *host, struct mmc_command *cmd)
{
    u32 ccon, imsk;

    //出现CRC状态错误|命令响应超时|接收命令响应|命令发出|响应CRC校验失败时,将产生SDI中断
    imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
        S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
        S3C2410_SDIIMSK_RESPONSECRC;

    //将值写入SDI中断屏蔽寄存器中
    enable_imask(host, imsk);

    //判断请求所处在何种状态
    if (cmd->data)
        //如果有数据传输,则设当前任务为完成数据传输且接收命令响应状态
        host->complete_what = COMPLETION_XFERFINISH_RSPFIN;
    else if (cmd->flags & MMC_RSP_PRESENT)
        host->complete_what = COMPLETION_RSPFIN;
    else
        //命令发送状态
        host->complete_what = COMPLETION_CMDSENT;

    //设置命令参数寄存器
    writel(cmd->arg, host->base + S3C2410_SDICMDARG);

    ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;
    ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;//命令操作开始

    if (cmd->flags & MMC_RSP_PRESENT)
        ccon |= S3C2410_SDICMDCON_WAITRSP;//主设备等待响应

    if (cmd->flags & MMC_RSP_136)
        ccon |= S3C2410_SDICMDCON_LONGRSP;
//主设备接收一个136位长的响应


    //设置命令控制寄存器,开始命令的传输
    writel(ccon, host->base + S3C2410_SDICMDCON);
}

7. s3cmci_irq_cd SDI的卡检测中断服务功能

//当MMC/SD卡插入卡槽时引发的中断
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
    //这个dev_id参数是申请中断时传递过来的
    struct s3cmci_host *host = (struct s3cmci_host *)dev_id;

    //调用核心层中的方法将将struct delayed_work detect加入共享工作队列,
    //其处理函数为核心层中的mmc_rescan方法,用于卡的识别并初始化。
    mmc_detect_change(host->mmc, msecs_to_jiffies(500));

    return IRQ_HANDLED;
}

8. s3cmci_irq SDI的中断服务功能。我们从第6小节中对MMC/SD各种请求处理的代码中和(一)中“命令、数据发送流程图”中可以看出,在这个中断服务中将要处理很多请求相关的事情。但对于中断服务来说,这样会严重影响系统的性能,所以这正是为什么要在驱动中实现中断的底半部机制。下面看代码进行分析。

//MMC/SD卡中断服务程序
static irqreturn_t s3cmci_irq(int irq, void *dev_id)
{
    //dev_id参数是申请中断的时候传递过来的s3cmci_host结构体,void类型的指针可以存放任何的数据类型
    struct s3cmci_host *host = dev_id;
    struct mmc_command *cmd;
    u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
    u32 mci_cclear, mci_dclear;
    unsigned long iflags;

    //关中断并保持状态字
    spin_lock_irqsave(&host->complete_lock, iflags);

    //分别读命令状态、数据状态、数据保留计数器、FIFO状态、中断屏蔽寄存器的值
    mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
    mci_dsta = readl(host->base + S3C2410_SDIDSTA);
    mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
    mci_fsta = readl(host->base + S3C2410_SDIFSTA);
    mci_imsk = readl(host->base + host->sdiimsk);
    mci_cclear = 0;
    mci_dclear = 0;

    //如果当前没有请求状态或者请求已经完成了,则恢复中断什么都不做
    if ((host->complete_what == COMPLETION_NONE) || (host->complete_what ==COMPLETION_FINALIZE)) 
    {
        host->status = "nothing to complete";
        clear_imask(host);
        goto irq_out;
    }

    //如果核心层无MMC/SD请求,则恢复中断什么都不做
    if (!host->mrq) 
    {
        host->status = "no active mrq";
        clear_imask(host);
        goto irq_out;
    }

    //获取当前发送命令有无完成
    cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;

    //如果发送命令完成了,则恢复中断什么都不做
    if (!cmd) 
    {
        host->status = "no active cmd";
        clear_imask(host);
        goto irq_out;
    }

    //判断在数据传输状态时使用的传输方式
    if (!host->dodma) 
    {
        //不是DMA传输。如果是FIFO写,则切换到底半部去进行FIFO的写操作
        if ((host->pio_active == XFER_WRITE) && (mci_fsta & S3C2410_SDIFSTA_TFDET)) 
        {
            disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
            tasklet_schedule(&host->pio_tasklet);
            host->status = "pio tx";
        }

        //如果是FIFO读,则切换到底半部去进行FIFO的读操作
        if ((host->pio_active == XFER_READ) && (mci_fsta & S3C2410_SDIFSTA_RFDET)) 
        {
            disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST);
            tasklet_schedule(&host->pio_tasklet);
            host->status = "pio rx";
        }
    }

    //命令响应超时
    if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) 
    {
        dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
        cmd->error = -ETIMEDOUT;
        host->status = "error: command timeout";
        goto fail_transfer;
    }

    //命令发送结束
    if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) 
    {
        if (host->complete_what == COMPLETION_CMDSENT) 
        {
            host->status = "ok: command sent";
            goto close_transfer;
        }

        mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
    }

    //收到命令响应,CRC校验失败
    if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) 
    {
        if (cmd->flags & MMC_RSP_CRC) 
        {
            if (host->mrq->cmd->flags & MMC_RSP_136) 
            {
                dbg(host, dbg_irq, "fixup: ignore CRC fail with long rsp\n");
            } else {
                
/* note, we used to fail the transfer
                 * here, but it seems that this is just
                 * the hardware getting it wrong.
                 *
                 * cmd->error = -EILSEQ;
                 * host->status = "error: bad command crc";
                 * goto fail_transfer;
                */

            }
        }

        mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
    }

    //收到命令响应,响应结束
    if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) 
    {
        //如果当前任务是完成,接收命令响应
        if (host->complete_what == COMPLETION_RSPFIN) 
        {
            host->status = "ok: command response received";
            goto close_transfer;
//停止传输

        }
        
        //当前任务是完成数据传输和接收命令响应
        if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
            //标记当前任务为完成数据传输
            host->complete_what = COMPLETION_XFERFINISH;

        //清除收到命令响应标志
        mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
    }

    if (!cmd->data)
        goto clear_status_bits;

    //FIFO失败
    if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) 
    {
        dbg(host, dbg_err, "FIFO failure\n");
        host->mrq->data->error = -EILSEQ;
        host->status = "error: 2440 fifo failure";
        goto fail_transfer;
    }

    //接收CRC错误
    if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) 
    {
        dbg(host, dbg_err, "bad data crc (outgoing)\n");
        cmd->data->error = -EILSEQ;
        host->status = "error: bad data crc (outgoing)";
        goto fail_transfer;
    }

    //发送数据后,CRC状态错误
    if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) 
    {
        dbg(host, dbg_err, "bad data crc (incoming)\n");
        cmd->data->error = -EILSEQ;
        host->status = "error: bad data crc (incoming)";
        goto fail_transfer;
    }

    //数据/忙接收超时
    if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) 
    {
        dbg(host, dbg_err, "data timeout\n");
        cmd->data->error = -ETIMEDOUT;
        host->status = "error: data timeout";
        goto fail_transfer;
    }

    //数据计数器为0,和本次请求的全部数据传输结束
    if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) 
    {
        //如果当前任务是完成数据传输则结束数据传输
        if (host->complete_what == COMPLETION_XFERFINISH) 
        {
            host->status = "ok: data transfer completed";
            goto close_transfer;
        }

        //如果当前任务是完成数据传输和接收命令响应
        if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
            //标记当前任务为完成 接收命令响应
            host->complete_what = COMPLETION_RSPFIN;

        //清除数据传输完标志
        mci_dclear |= S3C2410_SDIDSTA_XFERFINISH;
    }

 //清除状态字
clear_status_bits:
    writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
    writel(mci_dclear, host->base + S3C2410_SDIDSTA);

    goto irq_out;

//传输失败
fail_transfer:
    host->pio_active = XFER_NONE;

//传输结束
close_transfer:
    host->complete_what = COMPLETION_FINALIZE;

    clear_imask(host);
    tasklet_schedule(&host->pio_tasklet);

    goto irq_out;

irq_out:
    dbg(host, dbg_irq, "csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s.\n",
     mci_csta, mci_dsta, mci_fsta, mci_dcnt, host->status);

    //开中断并恢复状态字
    spin_unlock_irqrestore(&host->complete_lock, iflags);
    return IRQ_HANDLED;
}

//MMC/SD卡中断底半部程序
static void pio_tasklet(unsigned long data)
{
    //data参数是在s3cmci_probe中的tasklet_init的时候传递过来的
    struct s3cmci_host *host = (struct s3cmci_host *) data;

    //在执行底半部程序的时候屏蔽中断
    disable_irq(host->irq);

    //判断如果当前存在FIFO的写状态,则进行FIFO的写操作
    if (host->pio_active == XFER_WRITE)
        do_pio_write(host);

    //判断如果当前存在FIFO的读状态,则进行FIFO的读操作
    if (host->pio_active == XFER_READ)
        do_pio_read(host);

    //判断如果当前的请求状态为完成状态,则准备进行完成请求处理
    if (host->complete_what == COMPLETION_FINALIZE) 
    {
        //清空中断屏蔽寄存器
        clear_imask(host);
        
        //FIFO状态验证
        if (host->pio_active != XFER_NONE) 
        {
            if (host->mrq->data)
                host->mrq->data->error = -EINVAL;
        }

        //完成请求处理
        finalize_request(host);
    } 
    else
        //当前请求状态为其他,则使能中断继续请求处理
        enable_irq(host->irq);
}

//完成请求处理
static void finalize_request(struct s3cmci_host *host)
{
    struct mmc_request *mrq = host->mrq;
    struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
    int debug_as_failure = 0;

    //如果当前请求状态不为完成状态,则为错误
    if (host->complete_what != COMPLETION_FINALIZE)
        return;

    if (!mrq)
        return;

    if (cmd->data && (cmd->error == 0) && (cmd->data->error == 0)) 
    {
        if (host->dodma && (!host->dma_complete)) 
        {
            dbg(host, dbg_dma, "DMA Missing!\n");
            return;
        }
    }

    //读响应寄存器
    cmd->resp[0] = readl(host->base + S3C2410_SDIRSP0);
    cmd->resp[1] = readl(host->base + S3C2410_SDIRSP1);
    cmd->resp[2] = readl(host->base + S3C2410_SDIRSP2);
    cmd->resp[3] = readl(host->base + S3C2410_SDIRSP3);

    writel(host->prescaler, host->base + S3C2410_SDIPRE);

    if (cmd->error)
        debug_as_failure = 1;

    if (cmd->data && cmd->data->error)
        debug_as_failure = 1;

    dbg_dumpcmd(host, cmd, debug_as_failure);

    //清空命令参数、数据配置、命令配置、中断屏蔽寄存器
    writel(0, host->base + S3C2410_SDICMDARG);
    writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
    writel(0, host->base + S3C2410_SDICMDCON);
    writel(0, host->base + host->sdiimsk);

    if (cmd->data && cmd->error)
        cmd->data->error = cmd->error;

    //有数据请求,有传输停止命令,数据传输命令已发送
    if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) 
    {
        host->cmd_is_stop = 1;
        s3cmci_send_request(host->mmc);
//传输停止命令

        return;
    }

    if (!mrq->data)
        goto request_done;

    //计算已传输的数据量
    if (mrq->data->error == 0) 
    {
        mrq->data->bytes_xfered = (mrq->data->blocks * mrq->data->blksz);
    } 
    else 
    {
        mrq->data->bytes_xfered = 0;
    }

    if (mrq->data->error != 0) 
    {
        if (host->dodma)
            s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);

        //清除和复位FIFO状态寄存器
        writel(S3C2440_SDIFSTA_FIFORESET | S3C2440_SDIFSTA_FIFOFAIL, host->base +S3C2410_SDIFSTA);
    }

//完成请求
request_done:
    host->complete_what = COMPLETION_NONE;
    host->mrq = NULL;
    mmc_request_done(host->mmc, mrq);
}

S3C2440平台SD卡驱动开发实战详解 SD卡驱动是嵌入式系统中实现主机处理器与SD存储卡通信的关键软件模块,运行于操作系统内核层,负责抽象底层硬件差异并提供统一访问接口。其主要职责包括:上电初始化、卡类型识别(MMC/SD/SDHC)、命令发送与响应解析、块数据读写控制以及错误处理与恢复机制。驱动通过标准总线协议(如MMC/SD规范)与SD控制器交互,控制CLK、CMD和DAT线上的时序与电平变化,完成OCR协商、CID/CSD寄存器读取、RCA分配等关键流程,并支持PIO与DMA两种数据传输模式,确保数据完整性与系统效率。 阅读详情

相关推荐

电力电子技术(13)——大功率可控整流电路

目录引言2.6.1 带平衡电抗器的双反星形可控整流电路电路结构的特点接平衡电抗器的原因平衡电抗器使得两组三相半波整流电路同时导电的原理分析谐波分析2.6.2 多重化整流电路概述原理目标1)移相多重联结2)多重联结电路的顺序控制3重晶闸管整流桥顺序控制带平衡电抗器的双反星形可控整流电路(是多重化整流电路的重要形式之一)的特点:适用于低电压、大电流的场合。多重化整流电路的特点:绕组的极性相反的目的:消除直流磁通势如图所示,虽然两组相电流的瞬时值不同,但是平均电流相等而绕组的极性相反,所以直流安匝(电流*匝数)互

weixin_45589295的博客 1万+

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)[转]

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)(转自:http://www.cublog.cn/u3/101649/showart_2232724.html)嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。共享资源,欢迎转载:http://hbhuanggang.cublog.cn一、开发环境主  机:VMWare--Fedora 9开发板:Mini2440-

yhguo2008的专栏 2270

东方财富网API接口逆向分析:手把手教你破解那个带jQuery回调的股票数据接口

本文详细解析了东方财富网股票数据API接口的逆向工程实战技巧,重点介绍了JSONP接口的核心机制、参数解析及数据处理方法。通过Python代码示例演示了如何构造请求、处理分页数据及应对反爬策略,帮助开发者高效获取股票数据。文章还提供了完整的爬虫实现案例和常见问题排查指南。

weixin_30790841的博客 715

Linux kernel:移植linux-4.20.9到正点原子imx6ull mini开发板

1、移植Linux内核 首先去下载一份Linux源码,然后将它解压编译: tar xzf linux-4.20.9.tar.gz cd linux-4.20.9/ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j96 ls -l arch/arm/boot/zImage arch/arm/boot/dts/imx6ul

weixin_44498318的博客 7936

SpringBoot3.X及SpringAi和jdk17以上版本集成MCP协议供大模型调用sse协议

MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 推出的开源协议,旨在实现大型语言模型(LLM)与外部数据源和工具的无缝集成,用来在大模型和数据源之间建立安全双向的链接。目标是成为 AI 领域的“HTTP 协议”,推动 LLM 应用的标准化和去中心化。本文主要讲解SpringBoot3.X版本和jdk17版本如何集成MCP协议 后续还有一个SpringBoot2.7和jdk1.8通过solon来实现@Service。

weixin_47247523的博客 788

从零实战:基于华中8型数控系统的CNC数据采集与二次开发

本文详细介绍了基于华中8型数控系统的CNC数据采集与二次开发实战指南。从开发环境准备到核心数据采集实现,涵盖Windows平台配置、实时数据采集、报警处理等关键技术,帮助开发者快速掌握华中数控系统的数据采集能力,提升智能制造水平。

weixin_42561143的博客 813

iMX6UL之 OpenWRT移植与编译

选择 OpenWRT在最近的时期分裂出了LEDE出来,但还是决定使用OpenWRT,并且使用master上面新的代码来移植编译。 OpenWRT配置 本身LEDE与OpenWRT里面已经有一个imx6 Cortex-A9的arch支持,因此直接选择这个为base来做Porting与Compile较为合适。配置如下: Compile Tuning 因为imx6...

8083

Windows——Nessus安装(无限制)

Nessus手把手图文安装,解除ip限制~

网安小白的博客 9448

FIFO存储器和结构模型

FPGA或者ASIC中使用到的FIFO一般指的是对数据的存储具有先进先出特性的一个存储器,常被。,其中双时钟FIFO又可以分为普通双时钟(DCFIFO)和混合宽度双时钟FIFO(DCFIFO_MIXED_WIDTHS)。混合:输入数据和输出数据的数据宽度不同。

Tschome的博客 861

deepstream车道线检测自定义插件化,其中要注意pipeline的配置,如果插件位置配置不对,会报input format should be RGBA when using blur-obje

以上代码中,我们先从对象池中获取一个 DsUserMeta 对象,然后使用 get_lane_line_from_frame 函数获取车道线检测结果,并使用 add_lane_line_meta 函数将其添加到 DsUserMeta 对象中。注意,上述代码中的 get_lane_line_from_frame 函数和 add_lane_line_meta 函数需要你自己实现,用于从视频帧中提取车道线检测结果,并将其添加到 DsUserMeta 中。

pvmsmfchcs的博客 690

线结构光相机原理详解以及精度控制总结

摘要:线阵结构光相机是工业三维检测的核心设备,结合线阵相机的高分辨率与结构光的主动三维成像能力。其工作原理基于线激光投射、逐线扫描、亚像素级条纹提取及三角测量法实现三维重建。精度受硬件、校准、环境和算法四因素影响,需通过优化硬件选型(如小像元相机、窄线宽激光)、精准校准(相机内参、激光平面)、环境控制(遮光、隔振、恒温)及算法优化(Steger算法、点云后处理)来提升。典型应用(卷材、轨道检测)中深度精度可达5-20μm,需根据场景需求平衡参数配置。

start_up_go的博客 940

STM32使用HAL库IO模拟SPI驱动SD卡

STM32使用HAL库IO模拟SPI驱动SD卡 废话不多说,直接上代码! #include "mmc_sd.h" #include "delay.h" uint8_t SD_Type = 0; //SD卡的类型 /////////////////////////////////////////////////////// 移植修改区函数 ////////////////////////////////////////////////////////// /** * SD卡SPI接口读写一个字节

^_^ 3026

发那科机器人通信库使用指南与踩坑记录

GitHubNuGet(如本文基于该库,在 C# / .NET 下通过 TCP 与 FANUC 机器人控制器通信,实现读当前位姿、PR 寄存器、以及 DI/DO/GI/GO 等 IO 的读写。GitHubNuGet(如本文基于该库,在 C# / .NET 下通过 TCP 与 FANUC 机器人控制器通信,实现读当前位姿、PR 寄存器、以及 DI/DO/GI/GO 等 IO 的读写。

songhuangong123的博客 323

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二) (转自:http://www.cublog.cn/u3/101649/showart_2232724.html) 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 共享资源,欢迎转载:http

飞天鱼 2139

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

linux编译mmc驱动,嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。一、开发环境主 机:VMWare--Fedora 9开发板:Mini2440--64MB Nand, Kernel:2.6.30.4编译器:arm-linux-gcc-4.3.26. s3cmci_ops SDI主机控制器操作接口函...

weixin_39548193的博客 458

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

嵌入式linux sd卡读写,嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)...

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。一、开发环境主 机:VMWare--Fedora 9开发板:Mini2440--64MB Nand, Kernel:2.6.30.4编译器:arm-linux-gcc-4.3.26. s3cmci_ops SDI主机控制器操作接口函...

weixin_27224119的博客 739

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

S3C2440上 MMC/SD卡驱动实例开发讲解

首先我们来理清几个概念: 1. MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年推出的多媒体记忆卡标准。 2. SD:(Secure Digital Memory Card)由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制的新一代记忆卡标准,已完全兼容MMC标准。 3. SDIO:(Secure Digital Input and Output Card)安全数字输入输出卡。SDIO是在SD标 准上定义了一种外设接

menuconfig的专栏 2350

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(一)

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 共享资源,欢迎转载:http://hbhuanggang.cublog.cn 一、开发环境 主  机:VMWare--Fedora 9开发板:Mini2440--64MB Nand, Kernel:2.6.30.4

dengwenq的专栏 2387
上一篇: S3C2440上MMC/SD卡驱动实例开发讲解(一) - 内核、驱动开发篇
下一篇: 驱动程序异步通知应用程序,数据是否可读
ADC0809608
博客等级 码龄17年 44粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值