linux mmc驱动

插曲:

因为使用的平台是telechips的tcc803x,其芯片用户手册描述寄存器都是四字节寻址的,但是在代码驱动中用的很可能是单字节寻址,咋一看,有可能有的地址在芯片手册上没有或者感觉写错了,其实不是,这个需要注意一下。

简单流程:

mmc host主控器注册完成之后,会分别生成一个底层硬件相关的主控制设备(struct)sdhci_host和通用抽象的主控制器设备(struct)mmc_host,当检测到有sd卡等mmc从设备插入时,mmc  host主控制器会向sd卡等mmc从设备发起会话,sd卡等从设备作出相应的应答。mmc host主控制建立会话的机制是先通过通用抽象的(struct)mmc_host主设备的通用操作集(struct)mmc_host_ops,再由通用操作集(struct)mmc_host_ops进一步调用底层硬件相关的操作集(struct)sdhci_ops实现主从设备的通信的。

转载:linux MMC framework(2) - sdhci host driver_Hacker_Albert的博客-CSDN博客

注册mmc主控制器:

static int sdhci_tcc_probe(struct platform_device *pdev)
{
    const struct of_device_id *match;
	const struct sdhci_tcc_soc_data *soc_data;
	struct sdhci_host *host;
	struct sdhci_pltfm_host *pltfm_host;
	struct sdhci_tcc *tcc = NULL;

    match = of_match_device(sdhci_tcc_of_match_table, &pdev->dev);

    soc_data = match->data;

    host = sdhci_pltfm_init(pdev, soc_data->pdata, sizeof(*tcc));

    pltfm_host = sdhci_priv(host);
	tcc = sdhci_pltfm_priv(pltfm_host);

    sdhci_get_of_property(pdev);
    mmc_of_parse(host->mmc);
    sdhci_tcc_parse_configs(pdev, host);

    //设置SDMMC1的基础时钟,即PCLKCTL_IO2,输出到外设SDMMC1的时钟
    tcc->soc_data->set_core_clock(host);

    sdhci_add_host(host);
}

sdhci_tcc_of_match_table表:

static const struct of_device_id sdhci_tcc_of_match_table[] = {
	{ .compatible = "telechips,tcc803x-sdhci,module-only", .data = &soc_data_tcc803x},
	{}
};

表里的soc_data_tcc803x结构:

static const struct sdhci_tcc_soc_data soc_data_tcc803x = {
	.pdata = &sdhci_tcc803x_pdata,
	.parse_channel_configs = sdhci_tcc803x_parse_channel_configs,
	.set_channel_configs = sdhci_tcc803x_set_channel_configs,
	.set_core_clock = sdhci_tcc803x_set_core_clock,
	.sdhci_tcc_quirks = 0,
};

sdhci_tcc803x_pdata平台数据结构:

static const struct sdhci_pltfm_data sdhci_tcc803x_pdata = {
	.ops	= &sdhci_tcc803x_ops,
    //重点关注quirks,表示异于通常特性
	.quirks	= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
	.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
			SDHCI_QUIRK2_STOP_WITH_TC,
};

sdhci_tcc803x_ops操作集:

static const struct sdhci_ops sdhci_tcc803x_ops = {
	.get_max_clock = sdhci_tcc803x_clk_get_max_clock,
	.set_clock = sdhci_tcc_set_clock,
	.set_bus_width = sdhci_set_bus_width,
	.reset = sdhci_tcc_reset,
	.hw_reset = sdhci_tcc_hw_reset,
	.set_uhs_signaling = sdhci_set_uhs_signaling,
	.get_ro = sdhci_tcc_get_ro,

};

sdhci_ops与主控制硬件打交道,所以是再host主控制器驱动中实现的。

重新回到probe函数,看sdhci_pltfm_init函数:

struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
				    const struct sdhci_pltfm_data *pdata,
				    size_t priv_size)
{
    struct sdhci_host *host;

    //分配一个struct sdhci_host结构空间,附带分配一个struct sdhci_pltfm_host结构空间
    //和一个priv_size大小的空间
    host = sdhci_alloc_host(&pdev->dev,sizeof(struct sdhci_pltfm_host) + priv_size);

    if (pdata && pdata->ops)
		    host->ops = pdata->ops;  //对应上面定义的struct sdhci_ops sdhci_tcc803x_ops变量

    return host;
}

sdhci_alloc_host函数:

struct sdhci_host *sdhci_alloc_host(struct device *dev,
	size_t priv_size)
{
	struct mmc_host *mmc;
	struct sdhci_host *host;
    
    //在分配一个mmc_host内存空间的同时也分配了一个sdhci_host内存空间以及sdhci_host附属空间
    mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);

    host = mmc_priv(mmc);
	host->mmc = mmc;
	host->mmc_host_ops = sdhci_ops;
	mmc->ops = &host->mmc_host_ops;

    return host;
}

mmc_alloc_host函数:分配一个mmc_host结构体内存空间,也分配了其它以外的内存空间。

struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
	int err;
	struct mmc_host *host;

     //除了分配mmc_host内存空间,也分配了其它以外的内存空间
	host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);

	/* scanning will be enabled when we're ready */
	host->rescan_disable = 1;

	dev_set_name(&host->class_dev, "mmc%d", host->index);

    //mmc_host的父设备是probe函数层层传下来的pdev,就是sdhc主控制器设备,即对应sdhc设备节点
	host->parent = dev;
	host->class_dev.parent = dev;
	host->class_dev.class = &mmc_host_class;
	device_initialize(&host->class_dev);

	init_waitqueue_head(&host->wq);
	INIT_DELAYED_WORK(&host->detect, mmc_rescan);
	INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);
	setup_timer(&host->retune_timer, mmc_retune_timer, (unsigned long)host);

	return host;
}

mmc_priv函数:

static inline void *mmc_priv(struct mmc_host *host)
{
	return (void *)host->private;
}

mmc_host结构体:

struct mmc_host {
	struct device		*parent;
	struct device		class_dev;
    const struct mmc_host_ops *ops;
    unsigned int		f_min;
	unsigned int		f_max;
	unsigned int		f_init;
    ......
    unsigned long		private[0] ____cacheline_aligned;
}

回到probe函数,看sdhci_priv函数:

static inline void *sdhci_priv(struct sdhci_host *host)
{
	return host->private;
}

sdhci_host结构:

struct sdhci_host {
	/* Data set by hardware interface driver */
	const char *hw_name;	/* Hardware bus name */
    const struct sdhci_ops *ops;	/* Low level hw interface */
	/* Internal data */
	struct mmc_host *mmc;	/* MMC structure */
	struct mmc_host_ops mmc_host_ops;	/* MMC host ops */
    ......
    unsigned long private[0] ____cacheline_aligned;
}

回到probe函数,继续往下看:

sdhci_get_of_property函数:

void sdhci_get_of_property(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct sdhci_host *host = platform_get_drvdata(pdev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	u32 bus_width;

	if (of_get_property(np, "sdhci,auto-cmd12", NULL))
		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;

	if (of_get_property(np, "sdhci,1-bit-only", NULL) ||
	    (of_property_read_u32(np, "bus-width", &bus_width) == 0 &&
	    bus_width == 1))
		host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;

	if (sdhci_of_wp_inverted(np))
		host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;

	if (of_get_property(np, "broken-cd", NULL))
		host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

	if (of_get_property(np, "no-1-8-v", NULL))
		host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;

    ......
}

mmc_of_parse函数:

mmc_of_parse
    device_property_read_u32(dev, "bus-width", &bus_width);
    ......
    device_property_read_u32(dev, "max-frequency", &host->f_max);
    if (device_property_read_bool(dev, "cap-sd-highspeed"))
		host->caps |= MMC_CAP_SD_HIGHSPEED;
    ......

sdhci_tcc803x_set_core_clock函数:

sdhci_tcc803x_set_core_clock
    //成员变量mmc的数据类型是struct mmc_host*
    clk_set_rate(pltfm_host->clk, host->mmc->f_max);
        clk_core_set_rate_nolock
            clk_change_rate
                core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);
static struct clk_ops tcc_peri_ops = {
        .set_rate       = tcc_peri_set_rate,
        ......
};
tcc_peri_set_rate
    //rate传到安全核设置
    arm_smccc_smc(SIP_CLK_SET_PCLKCTRL, tcc->id, 1, rate, vflags, 0, 0, 0, &res);

 sdhci_add_host函数:

int sdhci_add_host(struct sdhci_host *host)
{
	sdhci_setup_host(host);
	__sdhci_add_host(host);
	return 0;
}

sdhci_setup_host函数:

sdhci_setup_host
    sdhci_read_caps(host);
    ......
    if (host->caps & SDHCI_CAN_DO_HISPD)
	    mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;

    ......

    //host->quirks2见上面pdata有定义
    if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
	}

    if (host->caps1 & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50))
		mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25;
    
    if (host->caps1 & SDHCI_SUPPORT_SDR104) {
		mmc->caps |= MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50;
	} else if (host->caps1 & SDHCI_SUPPORT_SDR50) {
		mmc->caps |= MMC_CAP_UHS_SDR50;
	}

    if ((host->caps1 & SDHCI_SUPPORT_DDR50) &&!(host->quirks2 & 
                                   SDHCI_QUIRK2_BROKEN_DDR50))
		mmc->caps |= MMC_CAP_UHS_DDR50;
    

sdhci_read_caps函数:

sdhci_read_caps
    __sdhci_read_caps(host, NULL, NULL, NULL);
        of_property_read_u64(mmc_dev(host->mmc)->of_node,"sdhci-caps-mask",                        
                                   &dt_caps_mask);
	    of_property_read_u64(mmc_dev(host->mmc)->of_node,"sdhci-caps", &dt_caps);
        ......
        host->caps = sdhci_readl(host, SDHCI_CAPABILITIES);
		host->caps &= ~lower_32_bits(dt_caps_mask);
		host->caps |= lower_32_bits(dt_caps);
        host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1);
		host->caps1 &= ~upper_32_bits(dt_caps_mask);
		host->caps1 |= upper_32_bits(dt_caps);

__sdhci_add_host函数:

int __sdhci_add_host(struct sdhci_host *host)
{
	struct mmc_host *mmc = host->mmc;

    .....

	sdhci_init(host, 0);

	request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
				   IRQF_SHARED,	mmc_hostname(mmc), host);

    .....

	mmc_add_host(mmc);

    ......

	return 0;
}

sdhc主控制器的中断处理函数分为上半部分sdhci_irq和下半部分sdhci_thread_irq。

sdhci_irq:

static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
	irqreturn_t result = IRQ_NONE;
	struct sdhci_host *host = dev_id;
	u32 intmask, mask, unexpected = 0;
    int max_loops = 16;

	intmask = sdhci_readl(host, SDHCI_INT_STATUS);
    if (!intmask || intmask == 0xffffffff) {
		result = IRQ_NONE;
		goto out;
	}

	do {

		if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
			u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT;
            ......
			result = IRQ_WAKE_THREAD;
		}

		if (intmask & SDHCI_INT_CMD_MASK){
			sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK, &intmask);
		}
        ......
cont:
		if (result == IRQ_NONE)
			result = IRQ_HANDLED;

		intmask = sdhci_readl(host, SDHCI_INT_STATUS);
	} while (intmask && --max_loops);
out:
	return result;
}

 sdhci_cmd_irq函数:

static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
{
    ......
    //#define SDHCI_INT_RESPONSE 0x00000001
	if (intmask & SDHCI_INT_RESPONSE){
		sdhci_finish_command(host);
	}
}

 中断状态寄存器:

 

 auto-cmd12、auto-cmd23补充:

软件设置控制器相关寄存器,让控制器自行发 stop transfer的命令,即 Auto CMD23或 Auto CMD12。

sdhci_set_transfer_mode函数解析 - xxxdk's blog

struct mmc_request的变量 sbc,即SET_BLOCK_COUNT。

 mmc_add_host函数:

mmc_add_host
    mmc_start_host
        mmc_gpiod_request_cd_irq
        _mmc_detect_change
            //对应上面的INIT_DELAYED_WORK(&host->detect, mmc_rescan);
            mmc_schedule_delayed_work(&host->detect, delay);

mmc_rescan函数:该函数是detect工作队列的处理函数,该工作队列只在两个地方被调用,一个是在函数_mmc_detect_change里被调用(该函数虽然可能会被多个地方调用,但是正常时候只在初始化的时候被mmc_sart_host函数调用一次),一个是在函数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 there is a non-removable card registered, only scan once */
	if (!mmc_card_is_removable(host) && host->rescan_entered)
		return;
	host->rescan_entered = 1;

    .....

	 /* if there is a _removable_ card registered, check whether it is still present*/
	if (host->bus_ops && !host->bus_dead && mmc_card_is_removable(host))
		host->bus_ops->detect(host);

    ......

	for (i = 0; i < ARRAY_SIZE(freqs); i++) {
		if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
			break;
		if (freqs[i] <= host->f_min)
			break;
	}

    .....

 out:
	if (host->caps & MMC_CAP_NEEDS_POLL) //只有SD卡主控制器会递归调用轮询扫描
		mmc_schedule_delayed_work(&host->detect, HZ);
}

mmc_rescan_try_freq函数:

mmc_rescan_try_freq
    mmc_attach_sdio
        mmc_sdio_init_card
            mmc_set_clock(host, mmc_sdio_get_max_clock(card));

 mmc_set_clock函数:设置实际频率,除了sdio设备,emmc也是调用该函数设置时钟频率。

void mmc_set_clock(struct mmc_host *host, unsigned int hz)
{
    //hz:最高频率,host->f_max:实际设置的频率,在设备树中定义,
    //对应max-frequency属性,上面有对该属性值获取
	if (hz > host->f_max)
		hz = host->f_max;

	host->ios.clock = hz; //注意:是ios.clock
	mmc_set_ios(host);
}

mmc_set_ios函数:该函数除了被mmc_set_clock函数调用,还在mmc_rescan扫描设备的时候和更前面mmc_start_host->mmc_power_up的时候被调用设置时钟,这时的频率:host->f_init = freq;

mmc_set_ios
    host->ops->set_ios(host, ios);
    

static const struct mmc_host_ops sdhci_ops = {
	.set_ios	= sdhci_set_ios,
    ......
};

sdhci_set_ios
    //注意:第一次设置的是ios->clock,第二次设置的是host->clock
    //      host->version == SDHCI_SPEC_300
    //      host->preset_enabled == 0
    if (!ios->clock || ios->clock != host->clock) {
		 host->ops->set_clock(host, ios->clock);
		 host->clock = ios->clock;
    }

     host->ops->set_uhs_signaling(host, ios->timing);
     host->timing = ios->timing;
     if (host->version >= SDHCI_SPEC_300) {
         //set_clock对应sdhci_tcc_set_clock函数
         host->ops->set_clock(host, host->clock);
     }

sdhci_tcc_set_clock
    sdhci_set_clock(host, clock);
        host->mmc->actual_clock = 0;

	    sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
        //如果传入的clock等于0,返回
	    if (clock == 0){
		    return;
	    }
	    clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock);
	    sdhci_enable_clk(host, clk);

sdhci_calc_clk
    int div = 0; 
	int real_div = div, clk_mul = 1; //注意:clk_mul和host->clk_mul不一样
	u16 clk = 0;
	bool switch_base_clk = false;
    // host->version == SDHCI_SPEC_300
    //host->preset_enabled == 0
    //host->clk_mul == 0
    if (host->version >= SDHCI_SPEC_300) {
        if (!host->clk_mul || switch_base_clk) {
            //1、如果max_clk小于等于clock,设置div等参数,使能该频率;
            //2、否则对max_clk分频,分频后的频率小于clock,设置div等参数,使能该频率;
			if (host->max_clk <= clock){
				div = 1;
			}
			else {
				for (div = 2; div < SDHCI_MAX_DIV_SPEC_300;div += 2) {
					if ((host->max_clk / div) <= clock)
						break;
				}
			}
			real_div = div;
			div >>= 1;
     }

     if (real_div){
		*actual_clock = (host->max_clk * clk_mul) / real_div;
	 }
	clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT;
	clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN)
		   << SDHCI_DIVIDER_HI_SHIFT;

注:

quirks: 怪癖的意思,也就是说它某种特性与通常的某种设备不相同。

如果设置3.3V、高速,只要在sdhc主控制器节点上添加“no-1-8-v;”和“cap-sd-highspeed;”

SDR模式下:一个时钟采样一次,一次4bit,即0.5Byte,50MHZ,传输速度25MB/s。

卡容量:
1) 标准容量卡(SDSC):不超于2GB

2)   高容量卡 (SDHC):大于2GB又不超过32GB

3) 扩展容量卡(SDXC):大于32GB又不超过2TB的卡

工作电压范围:2.7V~3.6V

总线速率:(SDR-single Data Rate)  DDR(Double Data Rate)
1) 默认速率模式:3.3V 信号,高达 25MHz,数据速率 12.5MB/S
2) 高速率模式:3.3V 信号,高达 50MHz,数据速率 25MB/S
3) SDR12:1.8V 信号,高达 25MHz,数据速率 12.5MB/S
4) SDR25:1.8V 信号,高达 50MHz,数据速率 25MB/S
5) SDR50:1.8V 信号,高达 100MHz,数据速率 50MB/S
6) SDR104:1.8V 信号,高达 208MHz,数据速率 104MB/S
7) DDR50:1.8V 信号,高达 50MHz,双时钟沿采样数据,数据速率 50MB/S 

 参考:SD协议简介_工作使我快乐的博客-CSDN博客

ocr:

 

u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{
	int bit;

    //低7位是reserve位,清零
	if (ocr & 0x7F) {
		dev_warn(mmc_dev(host),
		"card claims to support voltages below defined range\n");
		ocr &= ~0x7F;
	}
    ......
}

参考:

第12章 SD卡和SDIO接口(一)
[mmc]Linux下MMC/SD/SDIO的识别与操作_anxuan3201的博客-CSDN博客

 UHS:Ultra High-Speed

超高速和超高清接口是下一代实现 SDHC 和 SDXC 卡高速数据传输的的总线接口。

UHS 总线接口现在共有三个版本:UHS-I 、UHS-II、UHS-III。

 参考:Linux SD/MMC/SDIO驱动分析

CCCR全称是Card Common Control Registers。

CIS:card infomation structure,在CCCR寄存器中。
 

 参考:二,sdio总线简介之Commond_sdio cmd_初雨细无声的博客-CSDN博客

其它:

SDIO Card传输分析 - TJ的技术博客

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值