Linux驱动分析之MMC Host驱动

简介: 前面聊了MMC子系统《[Linux驱动分析之MMC子系统框架》,接下来我们拿个MMC Host驱动分析一下,来看看大致流程是怎样的。文章很长,代码很多,看个流程即可。

前言

前面聊了MMC子系统《Linux驱动分析之MMC子系统框架》,接下来我们拿个MMC Host驱动分析一下,来看看大致流程是怎样的。文章很长,代码很多,看个流程即可。

Host驱动分析

Linux版本:5.15

芯片平台:RK3288

部分源码路径:

drivers/mmc/host/dw_mmc-rockchip.c

drivers/mmc/host/dw_mmc_pltfm.c

drivers/mmc/host/dw_mmc.c

1.装载和卸载函数

//驱动数据
static const struct dw_mci_drv_data rk3288_drv_data = {
  .caps      = dw_mci_rk3288_dwmmc_caps,
  .num_caps    = ARRAY_SIZE(dw_mci_rk3288_dwmmc_caps),
  .set_ios    = dw_mci_rk3288_set_ios,
  .execute_tuning    = dw_mci_rk3288_execute_tuning,
  .parse_dt    = dw_mci_rk3288_parse_dt,
  .init      = dw_mci_rockchip_init,
};

//dts匹配表
static const struct of_device_id dw_mci_rockchip_match[] = {
  { .compatible = "rockchip,rk3288-dw-mshc",
    .data = &rk3288_drv_data },
  {},
};
MODULE_DEVICE_TABLE(of, dw_mci_rockchip_match);
//host控制器使用platform_driver
static struct platform_driver dw_mci_rockchip_pltfm_driver = {
  .probe    = dw_mci_rockchip_probe,
  .remove    = dw_mci_rockchip_remove,
  .driver    = {
    .name    = "dwmmc_rockchip",
    .probe_type  = PROBE_PREFER_ASYNCHRONOUS,
    .of_match_table  = dw_mci_rockchip_match, //dts匹配
    .pm    = &dw_mci_rockchip_dev_pm_ops,
  },
};
//封装module_init和module_exit
module_platform_driver(dw_mci_rockchip_pltfm_driver);

2.probe()函数

static int dw_mci_rockchip_probe(struct platform_device *pdev)
{
  const struct dw_mci_drv_data *drv_data;
  const struct of_device_id *match;
  int ret;

  if (!pdev->dev.of_node)
    return -ENODEV;

  match = of_match_node(dw_mci_rockchip_match, pdev->dev.of_node);
  drv_data = match->data; //获取驱动数据

  pm_runtime_get_noresume(&pdev->dev);
  pm_runtime_set_active(&pdev->dev);
  pm_runtime_enable(&pdev->dev);
  pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
  pm_runtime_use_autosuspend(&pdev->dev);

  ret = dw_mci_pltfm_register(pdev, drv_data); //调用dw的注册函数进行注册
  if (ret) {
    pm_runtime_disable(&pdev->dev);
    pm_runtime_set_suspended(&pdev->dev);
    pm_runtime_put_noidle(&pdev->dev);
    return ret;
  }

  pm_runtime_put_autosuspend(&pdev->dev);

  return 0;
}

真正的host注册是在dw_mci_pltfm_register()中,这是dw提供的统一注册接口。

int dw_mci_pltfm_register(struct platform_device *pdev,
        const struct dw_mci_drv_data *drv_data)
{
  struct dw_mci *host;
  struct resource  *regs;
  //分配dw_mci, 对mmc_host的封装
  host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL);
  if (!host)
    return -ENOMEM;

  host->irq = platform_get_irq(pdev, 0); //中断号
  if (host->irq < 0)
    return host->irq;

  host->drv_data = drv_data; //驱动数据
  host->dev = &pdev->dev;
  host->irq_flags = 0;
  host->pdata = pdev->dev.platform_data; //平台数据

  regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取IO地址, 即寄存器地址
  host->regs = devm_ioremap_resource(&pdev->dev, regs); //将IO地址映射为虚拟地址
  if (IS_ERR(host->regs))
    return PTR_ERR(host->regs);

  //获取寄存器的物理基地址
  host->phy_regs = regs->start;

  platform_set_drvdata(pdev, host);
  return dw_mci_probe(host); //调用dw probe
}
int dw_mci_probe(struct dw_mci *host)
{
  //省略....(各种寄存器配置,时钟,中断等)        

  //卡槽初始化
  ret = dw_mci_init_slot(host);
  if (ret) {
    dev_dbg(host->dev, "slot %d init failed\n", i);
    goto err_dmaunmap;
  }

  /* Now that slots are all setup, we can enable card detect */
  dw_mci_enable_cd(host);

  return 0;
  //省略.....
}
static int dw_mci_init_slot(struct dw_mci *host)
{
  struct mmc_host *mmc;
  struct dw_mci_slot *slot;
  int ret;
  //分配mmc_host
  mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev);
  if (!mmc)
    return -ENOMEM;

  slot = mmc_priv(mmc);
  slot->id = 0;
  slot->sdio_id = host->sdio_id0 + slot->id;
  slot->mmc = mmc;
  slot->host = host;
  host->slot = slot;

  mmc->ops = &dw_mci_ops;  //mmc_host_ops, 操作函数集

  /*if there are external regulators, get them*/
  ret = mmc_regulator_get_supply(mmc);
  if (ret)
    goto err_host_allocated;

  if (!mmc->ocr_avail)
    mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;

  ret = mmc_of_parse(mmc);
  if (ret)
    goto err_host_allocated;
  //设置卡槽特性
  ret = dw_mci_init_slot_caps(slot);
  if (ret)
    goto err_host_allocated;

  /* Useful defaults if platform data is unset. */
  if (host->use_dma == TRANS_MODE_IDMAC) {
    mmc->max_segs = host->ring_size;
    mmc->max_blk_size = 65535;
    mmc->max_seg_size = 0x1000;
    mmc->max_req_size = mmc->max_seg_size * host->ring_size;
    mmc->max_blk_count = mmc->max_req_size / 512;
  } else if (host->use_dma == TRANS_MODE_EDMAC) {
    mmc->max_segs = 64;
    mmc->max_blk_size = 65535;
    mmc->max_blk_count = 65535;
    mmc->max_req_size =
        mmc->max_blk_size * mmc->max_blk_count;
    mmc->max_seg_size = mmc->max_req_size;
  } else {
    /* TRANS_MODE_PIO */
    mmc->max_segs = 64;
    mmc->max_blk_size = 65535; /* BLKSIZ is 16 bits */
    mmc->max_blk_count = 512;
    mmc->max_req_size = mmc->max_blk_size *
            mmc->max_blk_count;
    mmc->max_seg_size = mmc->max_req_size;
  }
  //检查卡是否存在
  dw_mci_get_cd(mmc);
  //注册host
  ret = mmc_add_host(mmc);
  //省略.....
}

probe调用流程:

dw_mci_rockchip_probe

  --》dw_mci_pltfm_register

    --》dw_mci_probe //寄存器设置(时钟,速率,中断等)

      --》dw_mci_init_slot //注册mmc_host

3.热插拔

首先看一下中断的注册流程:

mmc_add_host

  --》mmc_start_host

    --》mmc_gpiod_request_cd_irq //中断处理函数mmc_gpio_cd_irqt

当卡插入时,产生中断,就会调用中断处理函数mmc_gpio_cd_irqt

static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
  /* Schedule a card detection after a debounce timeout */
  struct mmc_host *host = dev_id;
  struct mmc_gpio *ctx = host->slot.handler_priv;

  host->trigger_card_event = true;
  mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms)); //调用mmc_detect_change

  return IRQ_HANDLED;
}
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
  _mmc_detect_change(host, delay, true);
}
void _mmc_detect_change(struct mmc_host *host, unsigned long delay, bool cd_irq)
{

  if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL))
    __pm_wakeup_event(host->ws, 5000);

  host->detect_change = 1;
  mmc_schedule_delayed_work(&host->detect, delay); //中断下半部使用workqueue
}

为了消抖,中断最终在下半部进行处理,里面使用了workqueue。所以接下来来看一下host->detect这个工作队列。

mmc_alloc_host中初始化了host->detect

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

所以下半部最终会调用mmc_rescan

void mmc_rescan(struct work_struct *work)
{
  struct mmc_host *host =
    container_of(work, struct mmc_host, detect.work);
  int i;

  if (host->rescan_disable)
    return;

  //不可移除的卡只扫描一次
  if (!mmc_card_is_removable(host) && host->rescan_entered)
    return;
  host->rescan_entered = 1;
 //处理卡事件
 //host->ops在dw_mci_init_slot中初始化
  if (host->trigger_card_event && host->ops->card_event) {
    mmc_claim_host(host);
    host->ops->card_event(host);
    mmc_release_host(host);
    host->trigger_card_event = false;
  }

  //验证已注册卡的功能,否则将其删除
  if (host->bus_ops)
    host->bus_ops->detect(host);

  host->detect_change = 0;

  //如果有卡存在的话则退出
  if (host->bus_ops != NULL)
    goto out;

  mmc_claim_host(host);
  if (mmc_card_is_removable(host) && host->ops->get_cd &&
      host->ops->get_cd(host) == 0) {
    mmc_power_off(host);
    mmc_release_host(host);
    goto out;
  }

  /* If an SD express card is present, then leave it as is. */
  if (mmc_card_sd_express(host)) {
    mmc_release_host(host);
    goto out;
  }

  for (i = 0; i < ARRAY_SIZE(freqs); i++) {
    unsigned int freq = freqs[i];
    if (freq > host->f_max) {
      if (i + 1 < ARRAY_SIZE(freqs))
        continue;
      freq = host->f_max;
    }
    //进行卡初始化,卡初始化时时钟频率不能超过400KHz
    if (!mmc_rescan_try_freq(host, max(freq, host->f_min)))
      break;
    if (freqs[i] <= host->f_min)
      break;
  }
  mmc_release_host(host);

 out:
  if (host->caps & MMC_CAP_NEEDS_POLL)
    mmc_schedule_delayed_work(&host->detect, HZ); //轮询的话会再重新启动
}

(1)host->ops在dw_mci_init_slot中初始化

static const struct mmc_host_ops dw_mci_ops = {
  .request    = dw_mci_request,
  .pre_req    = dw_mci_pre_req,
  .post_req    = dw_mci_post_req,
  .set_ios    = dw_mci_set_ios,
  .get_ro      = dw_mci_get_ro,
  .get_cd      = dw_mci_get_cd,
  .hw_reset               = dw_mci_hw_reset,
  .enable_sdio_irq  = dw_mci_enable_sdio_irq,
  .ack_sdio_irq    = dw_mci_ack_sdio_irq,
  .execute_tuning    = dw_mci_execute_tuning,
  .card_busy    = dw_mci_card_busy,
  .start_signal_voltage_switch = dw_mci_switch_voltage,
  .init_card    = dw_mci_init_card,
  .prepare_hs400_tuning  = dw_mci_prepare_hs400_tuning,
};
mmc->ops = &dw_mci_ops;

(2)host->bus_ops根据不同类型(sd,mmc,sdio)分别定义在sd.c/mmc.c/sdio.c中

(3) 假如是SD类型,host->bus_ops->detect对应mmc_sd_detect()函数

static void mmc_sd_detect(struct mmc_host *host)
{
  int err;

  mmc_get_card(host->card, NULL);

  //检查卡是否移除
  err = _mmc_detect_card_removed(host);

  mmc_put_card(host->card, NULL);
  //移除后则释放资源

  if (err) {
    mmc_sd_remove(host);

    mmc_claim_host(host);
    mmc_detach_bus(host);
    mmc_power_off(host);
    mmc_release_host(host);
  }
}

(4) 卡插入的情况,这会调用mmc_rescan_try_freq对卡进行初始化

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
    host->f_init = freq;
    
    pr_debug("%s: %s: trying to init card at %u Hz\n",
    mmc_hostname(host), __func__, host->f_init);
    
    //上电
    mmc_power_up(host, host->ocr_avail);
    
    //硬件复位
    mmc_hw_reset_for_init(host);
    
    //SDIO需要发送CMD52进行卡复位, SD/eMMC卡会忽略
    if (!(host->caps2 & MMC_CAP2_NO_SDIO))
        sdio_reset(host);
    
    //进入IDLE状态
    mmc_go_idle(host);
    
    if (!(host->caps2 & MMC_CAP2_NO_SD)) {
        if (mmc_send_if_cond_pcie(host, host->ocr_avail))
            goto out;
        if (mmc_card_sd_express(host))
            return 0;
    }
    
    //调用attach, 优先级SDIO > SD > MMC
    if (!(host->caps2 & MMC_CAP2_NO_SDIO))
        if (!mmc_attach_sdio(host))
            return 0;
    
    if (!(host->caps2 & MMC_CAP2_NO_SD))
        if (!mmc_attach_sd(host))
            return 0;
    
    if (!(host->caps2 & MMC_CAP2_NO_MMC))
        if (!mmc_attach_mmc(host))
            return 0; 
out:
    mmc_power_off(host);
    return -EIO;
}

不同类型设备的attach对应如下:

SDIO -》mmc_attach_sdio

SD -》mmc_attach_sd

mmc -》mmc_attach_mmc

接下来以SDIO卡为例:

int mmc_attach_sdio(struct mmc_host *host)
{
  int err, i, funcs;
  u32 ocr, rocr;
  struct mmc_card *card;

  WARN_ON(!host->claimed);
  //发送CMD5,获取ocr寄存器
  err = mmc_send_io_op_cond(host, 0, &ocr);

  mmc_attach_bus(host, &mmc_sdio_ops); //设置mmc_bus_ops
  if (host->ocr_avail_sdio)
    host->ocr_avail = host->ocr_avail_sdio;

  //根据ocr寄存器值选择电压
  rocr = mmc_select_voltage(host, ocr);
  //检查并初始化卡
  err = mmc_sdio_init_card(host, rocr, NULL);

  card = host->card;

  //省略.....

  //确定功能(function)数量
  funcs = (ocr & 0x70000000) >> 28;  //Response R4
  card->sdio_funcs = 0;

  //初始化所有function
  for (i = 0; i < funcs; i++, card->sdio_funcs++) {
    err = sdio_init_func(host->card, i + 1);
    if (err)
      goto remove;

    /*
     * Enable Runtime PM for this func (if supported)
     */
    if (host->caps & MMC_CAP_POWER_OFF_CARD)
      pm_runtime_enable(&card->sdio_func[i]->dev);
  }

  //添加卡到驱动模型
  mmc_release_host(host);
  err = mmc_add_card(host->card);
  if (err)
    goto remove_added;

  //添加所有function
  for (i = 0;i < funcs;i++) {
    err = sdio_add_func(host->card->sdio_func[i]);
    if (err)
      goto remove_added;
  }

  if (host->caps & MMC_CAP_POWER_OFF_CARD)
    pm_runtime_put(&card->dev);

  mmc_claim_host(host);
  return 0;
 //省略.....        
}

sdio_add_func会在/sys/bus/sdio/devices中生成功能节点文件

mmc1:0001:1 表示host控制器1:连接的设备1:设备1上功能1

mmc1:0001:2 表示host控制器1:连接的设备1:设备1上功能2

4.SDIO卡初始化

static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
                  struct mmc_card *oldcard)
{
    struct mmc_card *card;
    int err;
    int retries = 10;
    u32 rocr = 0;
    u32 ocr_card = ocr;

    WARN_ON(!host->claimed);

    //查询卡是否支持1.8V
    if (mmc_host_uhs(host))
        ocr |= R4_18V_PRESENT;

try_again:
    if (!retries) {
        pr_warn("%s: Skipping voltage switch\n", mmc_hostname(host));
        ocr &= ~R4_18V_PRESENT;
    }

    //查询电压范围
    err = mmc_send_io_op_cond(host, ocr, &rocr);
    if (err)
        return err;

     //对于SPI,可以使能CRC
    if (mmc_host_is_spi(host)) {
        err = mmc_spi_set_crc(host, use_spi_crc);
        if (err)
            return err;
    }

    //分配mmc_card
    card = mmc_alloc_card(host, &sdio_type);
    if (IS_ERR(card))
        return PTR_ERR(card);

    //确定是组合卡还是SDIO卡
    if ((rocr & R4_MEMORY_PRESENT) &&
        mmc_sd_get_cid(host, ocr & rocr, card->raw_cid, NULL) == 0) {
        card->type = MMC_TYPE_SD_COMBO;

        if (oldcard && (oldcard->type != MMC_TYPE_SD_COMBO ||
            memcmp(card->raw_cid, oldcard->raw_cid, sizeof(card->raw_cid)) != 0)) {
            err = -ENOENT;
            goto mismatch;
        }
    } else {
        card->type = MMC_TYPE_SDIO;

        if (oldcard && oldcard->type != MMC_TYPE_SDIO) {
            err = -ENOENT;
            goto mismatch;
        }
    }

    //调用host的init_card
    if (host->ops->init_card)
        host->ops->init_card(host, card);

    //如果卡支持UHS-I模式则请求切换为1.8V信号电平
    if (rocr & ocr & R4_18V_PRESENT) {
        err = mmc_set_uhs_voltage(host, ocr_card);
        if (err == -EAGAIN) {
            mmc_sdio_pre_init(host, ocr_card, card);
            retries--;
            goto try_again;
        } else if (err) {
            ocr &= ~R4_18V_PRESENT;
        }
    }

    /*
     * For native busses:  set card RCA and quit open drain mode.
     */
    if (!mmc_host_is_spi(host)) {
        err = mmc_send_relative_addr(host, &card->rca);
        if (err)
            goto remove;

        if (oldcard)
            oldcard->rca = card->rca;
    }

     //选择卡之前,读取CSD
    if (!oldcard && card->type == MMC_TYPE_SD_COMBO) {
        err = mmc_sd_get_csd(card);
        if (err)
            goto remove;

        mmc_decode_cid(card);
    }

    //选择卡
    if (!mmc_host_is_spi(host)) {
        err = mmc_select_card(card);
        if (err)
            goto remove;
    }

    if (card->quirks & MMC_QUIRK_NONSTD_SDIO) {
         //非标准SDIO设备,没有CIA寄存器,host在init_card()中填充cccr和cis结构体。
        mmc_set_clock(host, card->cis.max_dtr);

        if (card->cccr.high_speed) {
            mmc_set_timing(card->host, MMC_TIMING_SD_HS);
        }

        if (oldcard)
            mmc_remove_card(card);
        else
            host->card = card;

        return 0;
    }

    //读取cccr寄存器
    err = sdio_read_cccr(card, ocr);
    if (err) {
        mmc_sdio_pre_init(host, ocr_card, card);
        if (ocr & R4_18V_PRESENT) {
            /* Retry init sequence, but without R4_18V_PRESENT. */
            retries = 0;
            goto try_again;
        }
        return err;
    }

    //读取CIS tuple
    err = sdio_read_common_cis(card);
    if (err)
        goto remove;

    if (oldcard) {
        if (card->cis.vendor == oldcard->cis.vendor &&
            card->cis.device == oldcard->cis.device) {
            mmc_remove_card(card);
            card = oldcard;
        } else {
            err = -ENOENT;
            goto mismatch;
        }
    }
    card->ocr = ocr_card;
    mmc_fixup_device(card, sdio_fixup_methods);

    if (card->type == MMC_TYPE_SD_COMBO) { //组合卡
        err = mmc_sd_setup_card(host, card, oldcard != NULL);
        //如果存储初始化失败,则当成仅有SDIO的卡
        if (err) {
            mmc_go_idle(host);
            if (mmc_host_is_spi(host))
                /* should not fail, as it worked previously */
                mmc_spi_set_crc(host, use_spi_crc);
            card->type = MMC_TYPE_SDIO;
        } else
            card->dev.type = &sd_type;
    }

     //断开卡检测的上拉电阻
    err = sdio_disable_cd(card);
    if (err)
        goto remove;

    //初始化UHS-I的卡
    if ((ocr & R4_18V_PRESENT) && card->sw_caps.sd3_bus_mode) {
        err = mmc_sdio_init_uhs_card(card);
        if (err)
            goto remove;
    } else {
         //切为高速
        err = sdio_enable_hs(card);
        if (err > 0)
            mmc_set_timing(card->host, MMC_TIMING_SD_HS);
        else if (err)
            goto remove;

         //设置卡的最大速率
        mmc_set_clock(host, mmc_sdio_get_max_clock(card));
        //设置线宽
        err = sdio_enable_4bit_bus(card);
        if (err)
            goto remove;
    }

    if (host->caps2 & MMC_CAP2_AVOID_3_3V &&
        host->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
        pr_err("%s: Host failed to negotiate down from 3.3V\n",
            mmc_hostname(host));
        err = -EINVAL;
        goto remove;
    }

    host->card = card;
    return 0;
    //省略.....
}

这些初始化就跟SDIO规范相关了,可以参考官方规范。

5.发送命令/数据

在sd_ops.c/sdio_ops.c/mmc_ops.c中封装了一些命令的请求函数。比如SDIO中的mmc_send_io_op_cond, 其调用关系如下:

mmc_send_io_op_cond

  --》mmc_wait_for_cmd

    --》mmc_wait_for_req

      --》mmc_wait_for_req

        --》__mmc_start_req

          --》mmc_start_request

            --》__mmc_start_request

static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
   //省略......
   //mmc_request包含了命令和数据,如果只有命令,data域则为NULL
    host->ops->request(host, mrq);  //调用mmc_host_ops中的request函数
}

这里host->ops->request对应的就是dw_mci_request

static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
    struct dw_mci_slot *slot = mmc_priv(mmc);
    struct dw_mci *host = slot->host;

    WARN_ON(slot->mrq);
    //检查卡是否存在
    if (!dw_mci_get_cd(mmc)) {
        mrq->cmd->error = -ENOMEDIUM;
        mmc_request_done(mmc, mrq);
        return;
    }

    spin_lock_bh(&host->lock);
    //发送请求
    dw_mci_queue_request(host, slot, mrq);

    spin_unlock_bh(&host->lock);
}
static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
                 struct mmc_request *mrq)
{
    dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
         host->state);

    slot->mrq = mrq;
    //省略......
    //处于IDLE状态,可以直接发,非IDLE则放入链表中
    if (host->state == STATE_IDLE) {
        host->state = STATE_SENDING_CMD;
        dw_mci_start_request(host, slot);
    } else {
        list_add_tail(&slot->queue_node, &host->queue);
    }
}
static void dw_mci_start_request(struct dw_mci *host,
                 struct dw_mci_slot *slot)
{
    struct mmc_request *mrq = slot->mrq;
    struct mmc_command *cmd;

    cmd = mrq->sbc ? mrq->sbc : mrq->cmd;
    __dw_mci_start_request(host, slot, cmd);
}
static void dw_mci_start_request(struct dw_mci *host,
                 struct dw_mci_slot *slot)
{
    struct mmc_request *mrq = slot->mrq;
    struct mmc_command *cmd;

    cmd = mrq->sbc ? mrq->sbc : mrq->cmd;
    __dw_mci_start_request(host, slot, cmd);
}
static void __dw_mci_start_request(struct dw_mci *host,
                   struct dw_mci_slot *slot,
                   struct mmc_command *cmd)
{
    struct mmc_request *mrq;
    struct mmc_data *data;
    u32 cmdflags;

    mrq = slot->mrq;

    host->mrq = mrq;

    host->pending_events = 0;
    host->completed_events = 0;
    host->cmd_status = 0;
    host->data_status = 0;
    host->dir_status = 0;
    
    data = cmd->data;
    if (data) { //携带有数据
        //配置寄存器
        mci_writel(host, TMOUT, 0xFFFFFFFF);
        mci_writel(host, BYTCNT, data->blksz*data->blocks);
        mci_writel(host, BLKSIZ, data->blksz);
    }

    cmdflags = dw_mci_prepare_command(slot->mmc, cmd);

    /* this is the first command, send the initialization clock */
    if (test_and_clear_bit(DW_MMC_CARD_NEED_INIT, &slot->flags))
        cmdflags |= SDMMC_CMD_INIT;

    if (data) {
        dw_mci_submit_data(host, data);
        wmb(); /* drain writebuffer */
    }
    //开始发送命令,里面也是对寄存器的配置
    dw_mci_start_command(host, cmd, cmdflags);

    //省略......
}

发送数据最终就是回到寄存器的配置,然后通过DMA进行数据发送。

DW接口调用流程:

dw_mci_request

  --》dw_mci_queue_request

    --》dw_mci_start_request

      --》__dw_mci_start_request

相关文章
|
3天前
|
存储 监控 Linux
【专栏】如何在 Linux 中列出已安装的驱动器?
【4月更文挑战第28天】在 Linux 中,了解已安装驱动器是系统管理的关键。本文介绍了三种方法:1) 使用 `lsblk` 命令显示设备名、大小和类型;2) `fdisk -l` 命令提供详细分区信息;3) `gnome-disks` 等系统管理工具展示驱动器信息。此外,还讨论了驱动器类型识别、挂载点概念及其应用。通过这些方法,用户能有效地监控和管理 Linux 系统中的驱动器。
|
16天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
68 0
|
20天前
|
JavaScript 前端开发 Linux
vue3在Linux下无法正常启动:esbuild-linux-64、cantnot start service :host version “0.13.15“,esbuild EACCESS
vue3在Linux下无法正常启动:esbuild-linux-64、cantnot start service :host version “0.13.15“,esbuild EACCESS
|
28天前
|
Prometheus 监控 数据可视化
linux分析方法与技巧
【4月更文挑战第3天】在Linux环境中,进行日志分析和系统性能分析的关键方法包括:使用`cat`, `less`, `tail`查看和过滤日志,`logrotate`管理日志文件,`rsyslog`或`syslog-ng`聚合日志,以及通过`top`, `mpstat`, `pidstat`, `free`, `iostat`, `netstat`, `strace`, `sar`, `dstat`等工具监控CPU、内存、磁盘I/O和网络。对于高级分析,可利用Brendan Gregg的性能工具,以及Grafana、Prometheus等可视化工具。
19 2
linux分析方法与技巧
|
29天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
12 0
|
2月前
|
监控 Linux Shell
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
40 0
|
2月前
|
缓存 监控 算法
Linux内存碎片深度剖析:原理、处理与分析全指南
Linux内存碎片深度剖析:原理、处理与分析全指南
125 0
Linux内存碎片深度剖析:原理、处理与分析全指南
|
2月前
|
消息中间件 存储 网络协议
Linux IPC 进程间通讯方式的深入对比与分析和权衡
Linux IPC 进程间通讯方式的深入对比与分析和权衡
69 0
|
2月前
|
存储 监控 Linux
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
50 0
|
2月前
|
存储 算法 Linux
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
136 0