一、sdhci core说明

1、sdhci说明

具体参考《host(第一章)——概述》

SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的设计标准,其寄存器偏移以及意义都有一定的规范,并且提供了对应的驱动程序,方便vendor进行host controller的开发。

vendor按照这套标准设计host controller之后,可以直接使用sdhci driver来实现host controller的使用,(qcom和samsung都使用了这套标准)。而vendor只需要实现平台相关的部分、如clock、pinctrl、power等等的部分即可。

关于这个标准,我们可以参考《SDHC_Ver3.00_Final_110225》。

注意,强调一下,这是一种mmc host controller的设计标准,其本质上还是属于mmc host。并且,其兼容mmc type card,而不是说只能使用于sd type card。

2、sdhci core

因为sdhci driver并不是某个特定host的driver,而是提供了一些接口和操作集方法给对应的host driver使用。

因此,我们将sdhci.c的代码部分称之为sdhci core用以和host driver区分。

其主要功能如下:

  • 为host driver提供分配、释放sdhci_host的接口
  • 为host driver提供注册、卸载sdhci_host的接口
  • 实现sdhci_host和mmc_host的对接(也就是mmc core的对接)
  • 实现host关于SDHCI标准的通用操作(sdhci_ops)
  • 实现host的通用电源管理操作

注意,clock和pinctrl是由host driver自己管理,sdhci core并不参与。

3、代码位置

drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

二、数据结构

1、struct sdhci_host

sdhci core将host抽象出struct sdhci_host来进行管理和维护。

数据结构如下:

struct sdhci_host {
/* Data set by hardware interface driver */
const char *hw_name; /* Hardware bus name */ // 名称
unsigned int quirks; /* Deviations from spec. */ // 癖好,可以理解为硬件sdhci controller和标准sdhci规范不符合的地方。
unsigned int quirks2; /* More deviations from spec. */ // 癖好2,可以理解为硬件sdhci controller和标准sdhci规范不符合的地方。 int irq; /* Device IRQ */ // sdhci的中断
void __iomem *ioaddr; /* Mapped address */ // sdhci寄存器的基地址
const struct sdhci_ops *ops; /* Low level hw interface */ // 底层硬件的操作接口 struct regulator *vmmc; /* Power regulator (vmmc) */ // sdhci core的LDO
struct regulator *vqmmc; /* Signaling regulator (vccq) */ // 给sdhci io供电的LDO /* Internal data */
struct mmc_host *mmc; /* MMC structure */ // struct mmc_host,用于注册到mmc subsystem中
u64 dma_mask; /* custom DMA mask */ spinlock_t lock; /* Mutex */ // 自旋锁
int flags; /* Host attributes */ // sdhci的一些标识
unsigned int version; /* SDHCI spec. version */ // 当前sdhci的硬件版本
unsigned int max_clk; /* Max possible freq (MHz) */ // 该sdhci支持的最大电压
unsigned int timeout_clk; /* Timeout freq (KHz) */ // 超时频率
unsigned int clk_mul; /* Clock Muliplier value */ // 当前倍频值
unsigned int clock; /* Current clock (MHz) */ // 当前工作频率
u8 pwr; /* Current voltage */ // 当前工作电压
bool runtime_suspended; /* Host is runtime suspended */ // 是否处于runtime suspend状态
struct mmc_request *mrq; /* Current request */ // 当前正在处理的请求
struct mmc_command *cmd; /* Current command */ // 当前的命令请求
struct mmc_data *data; /* Current data request */ // 当前的数据请求
unsigned int data_early:1; /* Data finished before cmd */ // 表示在CMD处理完成前,data已经处理完成 struct sg_mapping_iter sg_miter; /* SG state for PIO */
unsigned int blocks; /* remaining PIO blocks */
int sg_count; /* Mapped sg entries */
u8 *adma_desc; /* ADMA descriptor table */
u8 *align_buffer; /* Bounce buffer */
unsigned int adma_desc_sz; /* ADMA descriptor table size */
unsigned int adma_desc_line_sz; /* ADMA descriptor line size */
unsigned int align_buf_sz; /* Bounce buffer size */
unsigned int align_bytes; /* Alignment bytes (4/8 for 32-bit/64-bit) */
unsigned int adma_max_desc; /* Max ADMA descriptos (max sg segments) */
dma_addr_t adma_addr; /* Mapped ADMA descr. table */
dma_addr_t align_addr; /* Mapped bounce buffer */ struct tasklet_struct card_tasklet; /* Tasklet structures */ // card tasklet,用于处理card的插入或者拔出事件
struct tasklet_struct finish_tasklet; // finsh tasklet,用来通知上层一个请求处理完成(包括出错的情况) struct timer_list timer; /* Timer for timeouts */ // 超时定时器链表 u32 caps; /* Alternative CAPABILITY_0 */ // 表示该sdhci controller的属性
u32 caps1; /* Alternative CAPABILITY_1 */ // 表示该sdhci controller的属性 unsigned int ocr_avail_sdio; /* OCR bit masks */ // 在该sdhci controller上可用的sdio card的ocr值掩码(代表了其可用电压)
unsigned int ocr_avail_sd; // 在该sdhci controller上可用的sd card的ocr值掩码(代表了其可用电压)
unsigned int ocr_avail_mmc; /// 在该sdhci controller上可用的mmc card的ocr值掩码(代表了其可用电压) /* 以下和mmc的tuning相关 */
wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */
unsigned int tuning_done; /* Condition flag set when CMD19 succeeds */
unsigned int tuning_count; /* Timer count for re-tuning */
unsigned int tuning_mode; /* Re-tuning mode supported by host */
#define SDHCI_TUNING_MODE_1 0
struct timer_list tuning_timer; /* Timer for tuning */ /* 以下和sdhci的qos相关 */
struct sdhci_host_qos host_qos[SDHCI_QOS_MAX_POLICY];
enum sdhci_host_qos_policy last_qos_policy;
bool host_use_default_qos;
unsigned int pm_qos_timeout_us; /* timeout for PM QoS request */
struct device_attribute pm_qos_tout;
struct delayed_work pm_qos_work; struct sdhci_next next_data;
ktime_t data_start_time;
struct mutex ios_mutex;
enum sdhci_power_policy power_policy; bool irq_enabled; /* host irq status flag */ // 表示中断是否使能?
bool async_int_supp; /* async support to rxv int, when clks are off */
bool disable_sdio_irq_deferred; /* status of disabling sdio irq */
u32 auto_cmd_err_sts;
struct ratelimit_state dbg_dump_rs;
int reset_wa_applied; /* reset workaround status */
ktime_t reset_wa_t; /* time when the reset workaround is applied */
int reset_wa_cnt; /* total number of times workaround is used */ unsigned long private[0] ____cacheline_aligned; // 私有数据指针
};
  • 癖好1(sdhci_host->quirks)各个位意义如下:
/* Controller doesn't honor resets unless we touch the clock register */
#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
/* Controller has bad caps bits, but really supports DMA */
#define SDHCI_QUIRK_FORCE_DMA (1<<1)
/* Controller doesn't like to be reset when there is no card inserted. */
#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)
/* Controller doesn't like clearing the power reg before a change */
#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)
/* Controller has flaky internal state so reset it on each ios change */
#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4)
/* Controller has an unusable DMA engine */
#define SDHCI_QUIRK_BROKEN_DMA (1<<5)
/* Controller has an unusable ADMA engine */
#define SDHCI_QUIRK_BROKEN_ADMA (1<<6)
/* Controller can only DMA from 32-bit aligned addresses */
#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<7)
/* Controller can only DMA chunk sizes that are a multiple of 32 bits */
#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<8)
/* Controller can only ADMA chunks that are a multiple of 32 bits */
#define SDHCI_QUIRK_32BIT_ADMA_SIZE (1<<9)
/* Controller needs to be reset after each request to stay stable */
#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<10)
/* Controller needs voltage and power writes to happen separately */
#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER (1<<11)
/* Controller provides an incorrect timeout value for transfers */
#define SDHCI_QUIRK_BROKEN_TIMEOUT_VAL (1<<12)
/* Controller has an issue with buffer bits for small transfers */
#define SDHCI_QUIRK_BROKEN_SMALL_PIO (1<<13)
/* Controller does not provide transfer-complete interrupt when not busy */
#define SDHCI_QUIRK_NO_BUSY_IRQ (1<<14)
/* Controller has unreliable card detection */
#define SDHCI_QUIRK_BROKEN_CARD_DETECTION (1<<15)
/* Controller reports inverted write-protect state */
#define SDHCI_QUIRK_INVERTED_WRITE_PROTECT (1<<16)
/* Controller has nonstandard clock management */
#define SDHCI_QUIRK_NONSTANDARD_CLOCK (1<<17)
/* Controller does not like fast PIO transfers */
#define SDHCI_QUIRK_PIO_NEEDS_DELAY (1<<18)
/* Controller losing signal/interrupt enable states after reset */
#define SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET (1<<19)
/* Controller has to be forced to use block size of 2048 bytes */
#define SDHCI_QUIRK_FORCE_BLK_SZ_2048 (1<<20)
/* Controller cannot do multi-block transfers */
#define SDHCI_QUIRK_NO_MULTIBLOCK (1<<21)
/* Controller can only handle 1-bit data transfers */
#define SDHCI_QUIRK_FORCE_1_BIT_DATA (1<<22)
/* Controller needs 10ms delay between applying power and clock */
#define SDHCI_QUIRK_DELAY_AFTER_POWER (1<<23)
/* Controller uses SDCLK instead of TMCLK for data timeouts */
#define SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK (1<<24)
/* Controller reports wrong base clock capability */
#define SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN (1<<25)
/* Controller cannot support End Attribute in NOP ADMA descriptor */
#define SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC (1<<26)
/* Controller is missing device caps. Use caps provided by host */
#define SDHCI_QUIRK_MISSING_CAPS (1<<27)
/* Controller uses Auto CMD12 command to stop the transfer */
#define SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 (1<<28)
/* Controller doesn't have HISPD bit field in HI-SPEED SD card */
#define SDHCI_QUIRK_NO_HISPD_BIT (1<<29)
/* Controller treats ADMA descriptors with length 0000h incorrectly */
#define SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC (1<<30)
/* The read-only detection via SDHCI_PRESENT_STATE register is unstable */
#define SDHCI_QUIRK_UNSTABLE_RO_DETECT (1<<31)
  • 癖好2(sdhci_host->quirks2)各个位意义如下:
#define SDHCI_QUIRK2_HOST_OFF_CARD_ON           (1<<0)
#define SDHCI_QUIRK2_HOST_NO_CMD23 (1<<1)
/* The system physically doesn't support 1.8v, even if the host does */
#define SDHCI_QUIRK2_NO_1_8_V (1<<2)
#define SDHCI_QUIRK2_PRESET_VALUE_BROKEN (1<<3)
/*
* Read Transfer Active/ Write Transfer Active may be not
* de-asserted after end of transaction. Issue reset for DAT line.
*/
#define SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT (1<<4)
/*
* Slow interrupt clearance at 400KHz may cause
* host controller driver interrupt handler to
* be called twice.
*/
#define SDHCI_QUIRK2_SLOW_INT_CLR (1<<5)
/*
* If the base clock can be scalable, then there should be no further
* clock dividing as the input clock itself will be scaled down to
* required frequency.
*/
#define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<6)
/*
* Dont use the max_discard_to in sdhci driver so that the maximum discard
* unit gets picked by the mmc queue. Otherwise, it takes a long time for
* secure discard kind of operations to complete.
*/
#define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<7)
/*
* Ignore data timeout error for R1B commands as there will be no
* data associated and the busy timeout value for these commands
* could be lager than the maximum timeout value that controller
* can handle.
*/
#define SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD (1<<8)
/*
* The preset value registers are not properly initialized by
* some hardware and hence preset value must not be enabled for
* such controllers.
*/
#define SDHCI_QUIRK2_BROKEN_PRESET_VALUE (1<<9)
/*
* Some controllers define the usage of 0xF in data timeout counter
* register (0x2E) which is actually a reserved bit as per
* specification.
*/
#define SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT (1<<10)
/*
* This is applicable for controllers that advertize timeout clock
* value in capabilities register (bit 5-0) as just 50MHz whereas the
* base clock frequency is 200MHz. So, the controller internally
* multiplies the value in timeout control register by 4 with the
* assumption that driver always uses fixed timeout clock value from
* capabilities register to calculate the timeout. But when the driver
* uses SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK base clock frequency is directly
* controller by driver and it's rate varies upto max. 200MHz. This new quirk
* will be used in such cases to avoid controller mulplication when timeout is
* calculated based on the base clock.
*/
#define SDHCI_QUIRK2_DIVIDE_TOUT_BY_4 (1 << 11)
/*
* Some SDHC controllers are unable to handle data-end bit error in
* 1-bit mode of SDIO.
*/
#define SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR (1<<12) /*
* Some SDHC controllers do not require data buffers alignment, skip
* the bounce buffer logic when preparing data
*/
#define SDHCI_QUIRK2_ADMA_SKIP_DATA_ALIGNMENT (1<<13)
/* Some controllers doesn't have have any LED control */
#define SDHCI_QUIRK2_BROKEN_LED_CONTROL (1 << 14)
/* Use reset workaround in case sdhci reset timeouts */
#define SDHCI_QUIRK2_USE_RESET_WORKAROUND (1 << 15)
  • sdhci host的一些标识(sdhci_host->flags)如下:
#define SDHCI_USE_SDMA      (1<<0)  /* Host is SDMA capable */
#define SDHCI_USE_ADMA (1<<1) /* Host is ADMA capable */
#define SDHCI_REQ_USE_DMA (1<<2) /* Use DMA for this req. */
#define SDHCI_DEVICE_DEAD (1<<3) /* Device unresponsive */
#define SDHCI_SDR50_NEEDS_TUNING (1<<4) /* SDR50 needs tuning */
#define SDHCI_NEEDS_RETUNING (1<<5) /* Host needs retuning */
#define SDHCI_AUTO_CMD12 (1<<6) /* Auto CMD12 support */
#define SDHCI_AUTO_CMD23 (1<<7) /* Auto CMD23 support */
#define SDHCI_PV_ENABLED (1<<8) /* Preset value enabled */
#define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */
#define SDHCI_HS200_NEEDS_TUNING (1<<10) /* HS200 needs tuning */
#define SDHCI_USING_RETUNING_TIMER (1<<11) /* Host is using a retuning timer for the card */
#define SDHCI_HS400_NEEDS_TUNING (1<<12) /* HS400 needs tuning */
#define SDHCI_USE_ADMA_64BIT (1<<13)/* Host is 64-bit ADMA capable */

2、struct sdhci_ops结构体

sdhci core只是提供了一些接口和符合mmc core的操作集方法给对应的host driver使用。由于各个host的硬件有所差异,所以实际和硬件交互的驱动部分还是在host driver中实现。

所以sdhci core要求host提供标准的访问硬件的一些方法。而这些方法就被定义在了struct sdhci_ops结构体内部。

结构体如下:

struct sdhci_ops {
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
// 表示host另外提供了一套访问寄存器的方法,没有定义的话,则说明使用通用的读写寄存器的方法
u32 (*read_l)(struct sdhci_host *host, int reg);
u16 (*read_w)(struct sdhci_host *host, int reg);
u8 (*read_b)(struct sdhci_host *host, int reg);
void (*write_l)(struct sdhci_host *host, u32 val, int reg);
void (*write_w)(struct sdhci_host *host, u16 val, int reg);
void (*write_b)(struct sdhci_host *host, u8 val, int reg);
#endif void (*set_clock)(struct sdhci_host *host, unsigned int clock); // 设置时钟频率 int (*enable_dma)(struct sdhci_host *host); // 使能DMA
unsigned int (*get_max_clock)(struct sdhci_host *host); // 获取支持的最大时钟频率
unsigned int (*get_min_clock)(struct sdhci_host *host); // 获取支持的最小时钟频率
unsigned int (*get_timeout_clock)(struct sdhci_host *host);
int (*platform_bus_width)(struct sdhci_host *host, int width);
void (*platform_send_init_74_clocks)(struct sdhci_host *host,
u8 power_mode);
unsigned int (*get_ro)(struct sdhci_host *host); // 获取
void (*platform_reset_enter)(struct sdhci_host *host, u8 mask); // 进入平台复位的方法
void (*platform_reset_exit)(struct sdhci_host *host, u8 mask); // 退出平台复位的方法
int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); // 设置uhs方式
void (*hw_reset)(struct sdhci_host *host); // 硬件复位的方法
void (*platform_suspend)(struct sdhci_host *host); // 平台host的suspend方法
void (*platform_resume)(struct sdhci_host *host); // 平台host的resume方法
void (*adma_workaround)(struct sdhci_host *host, u32 intmask);
void (*platform_init)(struct sdhci_host *host); // 平台host的初始化方法
void (*check_power_status)(struct sdhci_host *host, u32 req_type); // 检测总线的电源状态
#define REQ_BUS_OFF (1 << 0)
#define REQ_BUS_ON (1 << 1)
#define REQ_IO_LOW (1 << 2)
#define REQ_IO_HIGH (1 << 3)
int (*execute_tuning)(struct sdhci_host *host, u32 opcode); // 执行tuning操作的的方法
void (*toggle_cdr)(struct sdhci_host *host, bool enable);
unsigned int (*get_max_segments)(void);
void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); // 平台总线投票的方法
void (*disable_data_xfer)(struct sdhci_host *host);
void (*dump_vendor_regs)(struct sdhci_host *host);
int (*config_auto_tuning_cmd)(struct sdhci_host *host,
bool enable,
u32 type);
int (*enable_controller_clock)(struct sdhci_host *host);
void (*reset_workaround)(struct sdhci_host *host, u32 enable);
};

这个结构体也就是host driver要实现的核心内容。

3、struct mmc_host_ops sdhci_ops

注意:这里的sdhci_ops是一个变量名,和上述的struct sdhci_ops不是同一个概念。搞不懂为什么这么命名,容易混淆。

sdhci core使用sdhci_ops作为sdhci host抽象出来的mmc host的操作集,所以其是一个struct mmc_host_ops结构体。

后续mmc core关于这个host的操作也都是基于这个操作集上实现的,包括使能host(enable方法)、禁用host(disable方法)、发送请求(request方法)。

具体参考《mmc core》系列。

具体实现如下,具体意义参考《mmc core(第二章)——数据结构和宏定义说明》:

static const struct mmc_host_ops sdhci_ops = {
// post_req和pre_req是为了实现异步请求处理而设置的
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
// 具体参考《mmc core主模块》
.pre_req = sdhci_pre_req,
.post_req = sdhci_post_req,
.request = sdhci_request, // host处理mmc请求的方法,在mmc_start_request中会调用
.set_ios = sdhci_set_ios, // 设置host的总线的io setting
.get_cd = sdhci_get_cd, // 检测host的卡槽中card的插入状态
.get_ro = sdhci_get_ro, // 获取host上的card的读写属性
.hw_reset = sdhci_hw_reset, // 硬件复位
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch, // 切换信号电压的方法
.execute_tuning = sdhci_execute_tuning, // 执行tuning操作,为card选择一个合适的采样点
.card_event = sdhci_card_event,
.card_busy = sdhci_card_busy, // 用于检测card是否处于busy状态
.enable = sdhci_enable, // 使能host,当host被占用时(第一次调用mmc_claim_host)调用
.disable = sdhci_disable, // 禁用host,当host被释放时(第一次调用mmc_release_host)调用
.stop_request = sdhci_stop_request, // 停止请求处理的方法
.get_xfer_remain = sdhci_get_xfer_remain,
.notify_load = sdhci_notify_load,
};

三、API总览

1、sdhci_host分配和释放相关

  • sdhci_alloc_host & sdhci_free_host

由底层host driver调用。

sdhci_alloc_host为host driver分配一个sdhci_host和mmc_host,并实现其初始化,以及sdhci_host和mmc_host的关联。

sdhci_free_host则是用来释放一个sdhci_host。

原型:struct sdhci_host *sdhci_alloc_host(struct device *dev, size_t priv_size)
参数说明:struct device *dev——》对应host的device结构体
size_t priv_size——》要分配的sdhci_host的私有数据的长度,一般是平台自己定制的host的长度。 原型:void sdhci_free_host(struct sdhci_host *host)

2、sdhci_host的注册和卸载相关

  • sdhci_add_host & sdhci_remove_host

由底层host driver调用。

sdhci_add_host用于向sdhci core注册一个sdhci_host。会根据sdhci的寄存器以及部分标识设置其mmc_host,最终将mmc_host注册到mmc core中。

因此,在调用sdhci_add_host之前,必须准备好sdhci的所有硬件环境。

sdhci_free_host则用于从sdhci core中卸载一个sdhci_host,对应的mmc_host也会从mmc core中被卸载。

    原型:int sdhci_add_host(struct sdhci_host *host);
原型:void sdhci_remove_host(struct sdhci_host *host, int dead);

四、接口代码说明

1、sdhci_alloc_host

struct sdhci_host *sdhci_alloc_host(struct device *dev,
size_t priv_size)
{
struct mmc_host *mmc;
struct sdhci_host *host; WARN_ON(dev == NULL); /* 实现mmc_host和sdhci_host的分配 */
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev); // 分配一个struct mmc_host
// 分配mmc_host的同时也分配了sizeof(struct sdhci_host) + priv_size的私有数据空间,这部分就是作为sdhci_host及其私有数据使用的。
// 具体参考《mmc core——host模块说明》
if (!mmc)
return ERR_PTR(-ENOMEM); /* 实现mmc_host和sdhci_host的关联操作 */
host = mmc_priv(mmc); // 将sdhci_host作为mmc_host的私有数据,mmc_host->private = sdhci_host
host->mmc = mmc; // 关联sdhci_host和mmc_host,sdhci_host->mmc = mmc_host /* sdhci_host的锁的初始化工作 */
spin_lock_init(&host->lock); // 初始化sdhci_host 的占有锁
mutex_init(&host->ios_mutex); // 初始化sdhci_host 设置io setting的互斥锁 return host; // 将struct sdhci_host 返回
}

综上,

mmc_host->private = sdhci_host
sdhci_host->mmc = mmc_host

2、sdhci_add_host

(0)底层传上来的sdhci_host中应该包含什么信息

  • sdhci的寄存器的映射过后的基地址(sdhci_host->ioaddr)
  • sdhci的癖好quirks、quirks2(sdhci_host->quirks,sdhci_host->quirks2)
  • sdhci的中断号(sdhci_host->irq)
  • host提供给sdhci core用来操作硬件的操作集(sdhci_host->ops)

(1)主要完成工作如下:

  • sdhci host复位

调用sdhci_reset

  • 读取该host的sdhci的信息(从sdhci相关寄存器中读取)并设置sdhci_host相关成员

    • 版本(sdhci_host->version) : 从SDHCI_HOST_VERSION寄存器中读取
    • 支持的属性 : 从SDHCI_CAPABILITIES、SDHCI_CAPABILITIES_1寄存器中读取
    • 标识(sdhci_host->version) : 根据sdhci_host->quirks和quirks2来设置
    • 支持的最大频率和倍频(sdhci_host->max_clk & sdhci_host->clk_mul)


      对应SDHCI_CAPABILITIES寄存器中的SDHCI_CLOCK_BASE_SHIFT位


      对应SDHCI_CAPABILITIES寄存器中的SDHCI_CLOCK_MUL_SHIFT位
    • sdhci使用的regulator(sdhci_host->vqmmc)


      从节点中的命名为”vmmc”的regulator属性中获取
    • card插入状态发生变化时调用的tasklet(sdhci_host->card_tasklet)


      设置为sdhci_tasklet_card
    • 请求处理完成时调用的tasklet(sdhci_host->finish_tasklet)


      设置为sdhci_tasklet_finish
    • 请求的处理超时定时器(sdhci_host->timer)


      设置为sdhci_timeout_timer
    • qos处理的工作(sdhci_host->pm_qos_work)


      设置为sdhci_pm_qos_remove_work
  • 设置mmc_host的相关成员

    • 操作集(mmc_host->ops)


      设置为sdhci_ops,上面已经说明过了
    • 最大频率(mmc_host->f_max)


      用sdhci_host->max_clk的值来设置
    • host的属性(mmc_host->caps & mmc_host->caps2)


      通过sdhci_host->quirks和quirks2、以及SDHCI_CAPABILITIES、SDHCI_CAPABILITIES_1寄存器中的属性进行设置
    • 各个电压下的最大电流值(mmc_host->max_current_330 & mmc_host->max_current_300 & mmc_host->max_current_180)


      从SDHCI_MAX_CURRENT寄存器中读取
    • 可用电压(mmc->ocr_avail & mmc->ocr_avail_sdio & mmc->ocr_avail_sd & mmc->ocr_avail_mmc)


      从SDHCI_CAPABILITIES寄存器中的SDHCI_CAN_VDD_330、SDHCI_CAN_VDD_300、SDHCI_CAN_VDD_180位获取
    • 一些块和段size的设置
  • 中断的注册


    将sdhci_host的中断处理函数注册为sdhci_irq

  • sdhci host初始化


    调用sdhci_init

  • 注册mmc_host到mmc core中


    调用mmc_add_host

  • 使能card插入状态的检测


    调用sdhci_enable_card_detection

(2)代码如下:

int sdhci_add_host(struct sdhci_host *host)
{
// 以下变量要注意区分
// host是指要注册的sdhci host
// mmc是指要注册到mmc subsystem的host,封装在sdhci host中
struct mmc_host *mmc;
u32 caps[2] = {0, 0};
u32 max_current_caps;
unsigned int ocr_avail;
int ret; WARN_ON(host == NULL);
if (host == NULL)
return -EINVAL; mmc = host->mmc; // 获取struct mmc_host /* 执行复位操作 */
sdhci_reset(host, SDHCI_RESET_ALL);
// 执行reset操作,会调用到sdhci_host->ops->platform_reset_enter,msm并没有实现这个方法 /********************************* 获取sdhci信息并设置sdhci_host的相应成员***********************/
/* 获取sdhci controller版本号 */
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
host->version = (host->version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT;
// 获取sdhci host的硬件版本号 /* 获取sdhci controller支持的属性 */
caps[0] = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ? host->caps : sdhci_readl(host, SDHCI_CAPABILITIES);
// SDHCI_QUIRK_MISSING_CAPS:Controller is missing device caps. Use caps provided by host
// sdhci控制器没有devices属性的话,由底层host提供,否则,从sdhci controller的SDHCI_CAPABILITIES读取属性 if (host->version >= SDHCI_SPEC_300)
caps[1] = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ?host->caps1 : sdhci_readl(host, SDHCI_CAPABILITIES_1);
// 从sdhci controller的SDHCI_CAPABILITIES_1读取属性 /* 设置sdhci_host->flags中和DMA相关的flag */
if (host->quirks & SDHCI_QUIRK_FORCE_DMA)
host->flags |= SDHCI_USE_SDMA;
else if (!(caps[0] & SDHCI_CAN_DO_SDMA))
DBG("Controller doesn't have SDMA capability\n");
else
host->flags |= SDHCI_USE_SDMA;
// SDHCI_QUIRK_FORCE_DMA : Controller has bad caps bits, but really supports DMA
// 设置sdhci_host->flags中的SDHCI_USE_SDMA标识
//............................
if (host->flags & SDHCI_USE_ADMA) {
// sdhci_host ->adma_max_desc
// sdhci_host ->adma_desc_line_sz
// sdhci_host ->align_bytes
// sdhci_host ->adma_desc_sz
// sdhci_host ->align_buf_sz
// sdhci_host ->adma_desc
// sdhci_host ->align_buffer
} host->next_data.cookie = 1; /* 获取sdhci controller支持的最大频率以及倍频 */
if (host->version >= SDHCI_SPEC_300)
host->max_clk = (caps[0] & SDHCI_CLOCK_V3_BASE_MASK)
>> SDHCI_CLOCK_BASE_SHIFT; // 从sdhci controller的SDHCI_CLOCK_V3_BASE_MASK读取最大clock(单位是MHZ)
else
host->max_clk = (caps[0] & SDHCI_CLOCK_BASE_MASK)
>> SDHCI_CLOCK_BASE_SHIFT; host->max_clk *= 1000000;(转化为hz)
// 设置sdhci_host->max_clk
sdhci_update_power_policy(host, SDHCI_PERFORMANCE_MODE_INIT);
// 设置sdhci_host->power_policy为SDHCI_PERFORMANCE_MODE_INIT
if (host->max_clk == 0 || host->quirks & SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN) {
host->max_clk = host->ops->get_max_clock(host); // 调用sdhci_host->ops->get_max_clock获得最大时钟
} host->clk_mul = (caps[1] & SDHCI_CLOCK_MUL_MASK) >> SDHCI_CLOCK_MUL_SHIFT;
if (host->clk_mul)
host->clk_mul += 1;
// 设置sdhci_host->clk_mul,clock的倍频实行 /*************************** 以下对mmc_host和sdhci_host进行设置操作 ***************************/
/* 以下设置mmc_host,ops、f_max、f_min */
mmc->ops = &sdhci_ops; // 设置mmc_host的操作集为sdhci_ops
mmc->f_max = host->max_clk; // 设置最大时钟频率mmc_host->f_max
if (host->ops->get_min_clock)
mmc->f_min = host->ops->get_min_clock(host); // 调用sdhci_host->ops->get_min_clock获得最小时钟频率mmc_host->f_min host->timeout_clk = (caps[0] & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT;
// 从sdhci controller的SDHCI_TIMEOUT_CLK_MASK读取最大timeout
// 设置到sdhci_host->timeout_clk
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
host->timeout_clk = mmc->f_max / 1000; if (!(host->quirks2 & SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE))
mmc->max_discard_to = (1 << 27) / host->timeout_clk;
// 设置mmc_host->max_discard_to /* 设置mmc_host->caps,也就是属性 */
mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
if (!(host->quirks & SDHCI_QUIRK_FORCE_1_BIT_DATA))
mmc->caps |= MMC_CAP_4_BIT_DATA;
if (host->quirks2 & SDHCI_QUIRK2_HOST_NO_CMD23)
mmc->caps &= ~MMC_CAP_CMD23;
if (caps[0] & SDHCI_CAN_DO_HISPD)
mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) &&
!(host->mmc->caps & MMC_CAP_NONREMOVABLE) &&
(mmc_gpio_get_cd(host->mmc) < 0) &&
!(host->mmc->caps2 & MMC_CAP2_NONHOTPLUG))
mmc->caps |= MMC_CAP_NEEDS_POLL; /* 获取vqmmc regulater并使能 */
/* If vqmmc regulator and no 1.8V signalling, then there's no UHS */
host->vqmmc = regulator_get(mmc_dev(mmc), "vqmmc");
if (IS_ERR_OR_NULL(host->vqmmc)) {
....
} else {
ret = regulator_enable(host->vqmmc);
if (!regulator_is_supported_voltage(host->vqmmc, 1700000,1950000))
caps[1] &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
}
if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V)
caps[1] &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50); /* 设置mmc_host->caps和传输模式相关的属性 */
/* Any UHS-I mode in caps implies SDR12 and SDR25 support. */
if (caps[1] & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
SDHCI_SUPPORT_DDR50))
mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; /* SDR104 supports also implies SDR50 support */
if (caps[1] & SDHCI_SUPPORT_SDR104)
mmc->caps |= MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50;
else if (caps[1] & SDHCI_SUPPORT_SDR50)
mmc->caps |= MMC_CAP_UHS_SDR50; if (caps[1] & SDHCI_SUPPORT_DDR50)
mmc->caps |= MMC_CAP_UHS_DDR50; /* 设置sdhci_host->flags中和tuning相关的flag */
/* Does the host need tuning for SDR50? */
if (caps[1] & SDHCI_USE_SDR50_TUNING)
host->flags |= SDHCI_SDR50_NEEDS_TUNING;
/* Does the host need tuning for HS200? */
if (mmc->caps2 & MMC_CAP2_HS200)
host->flags |= SDHCI_HS200_NEEDS_TUNING;
/* Does the host need tuning for HS400? */
if (mmc->caps2 & MMC_CAP2_HS400)
host->flags |= SDHCI_HS400_NEEDS_TUNING; /* 设置mmc_host->caps和驱动类型相关的属性 */
/* Driver Type(s) (A, C, D) supported by the host */
if (caps[1] & SDHCI_DRIVER_TYPE_A)
mmc->caps |= MMC_CAP_DRIVER_TYPE_A;
if (caps[1] & SDHCI_DRIVER_TYPE_C)
mmc->caps |= MMC_CAP_DRIVER_TYPE_C;
if (caps[1] & SDHCI_DRIVER_TYPE_D)
mmc->caps |= MMC_CAP_DRIVER_TYPE_D; /* 获取sdhci controller的tuning计数(tuning_count 、tuning_mode )*/
host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >>
SDHCI_RETUNING_TIMER_COUNT_SHIFT;
if (host->tuning_count)
host->tuning_count = 1 << (host->tuning_count - 1); host->tuning_mode = (caps[1] & SDHCI_RETUNING_MODE_MASK) >> SDHCI_RETUNING_MODE_SHIFT; ocr_avail = 0; /* 获取vmmc regulater,设置caps[0]支持的电压值 */
host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
#ifdef CONFIG_REGULATOR
/*
* Voltage range check makes sense only if regulator reports
* any voltage value.
*/
if (host->vmmc && regulator_get_voltage(host->vmmc) > 0) {
ret = regulator_is_supported_voltage(host->vmmc, 2700000,
3600000);
if ((ret <= 0) || (!(caps[0] & SDHCI_CAN_VDD_330)))
caps[0] &= ~SDHCI_CAN_VDD_330;
if ((ret <= 0) || (!(caps[0] & SDHCI_CAN_VDD_300)))
caps[0] &= ~SDHCI_CAN_VDD_300;
ret = regulator_is_supported_voltage(host->vmmc, 1700000,
1950000);
if ((ret <= 0) || (!(caps[0] & SDHCI_CAN_VDD_180)))
caps[0] &= ~SDHCI_CAN_VDD_180;
}
#endif /* CONFIG_REGULATOR */ /* 设置各个电压下的最大电流值(max_current_330、max_current_330 、max_current_180 )*/
/* 设置可用电压域 */
max_current_caps = sdhci_readl(host, SDHCI_MAX_CURRENT);
if (!max_current_caps && host->vmmc) {
u32 curr = regulator_get_current_limit(host->vmmc);
//....................
} if (caps[0] & SDHCI_CAN_VDD_330) {
ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34; mmc->max_current_330 = ((max_current_caps &
SDHCI_MAX_CURRENT_330_MASK) >>
SDHCI_MAX_CURRENT_330_SHIFT) *
SDHCI_MAX_CURRENT_MULTIPLIER;
}
//.........
mmc->ocr_avail = ocr_avail;
mmc->ocr_avail_sdio = ocr_avail;
// ...... /*********************************** sdhci的初始化工作**************************************/
/* 初始化sdhci工作过程中会使用到的tasklet */
tasklet_init(&host->card_tasklet, sdhci_tasklet_card, (unsigned long)host); // host上发生card插入或者拔出时调用
tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host); // 完成一个request时调用 setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); // command的超时定时器 /* 初始化qos处理的工作 */
INIT_DELAYED_WORK(&host->pm_qos_work, sdhci_pm_qos_remove_work); /* 中断注册和使能 */
ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,mmc_hostname(mmc), host);
host->irq_enabled = true; /* 对该sdhci controller进行初始化 */
sdhci_init(host, 0); mmiowb();
/* sdhci关于qos的请求和操作的设置 */
if (host->host_qos[SDHCI_QOS_READ_WRITE].cpu_dma_latency_us) {
// .........
} /*********************************** 将mmc_host注册到mmc subsystem中 *******************************/
mmc_add_host(mmc); /*********************************** 开始使能sdhci和并且开始检测card状态******************************/
sdhci_enable_card_detection(host); return 0;
}

重点关注如下几个部分:

(1)sdhci_reset(host, SDHCI_RESET_ALL);
(2)mmc->ops = &sdhci_ops; // 设置mmc_host的操作集为sdhci_ops
(3)host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
(4)tasklet_init(&host->card_tasklet, sdhci_tasklet_card, (unsigned long)host); // host上发生card插入或者拔出时调用
(5)tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host); // 完成一个request时调用的tasklet
(6)ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,mmc_hostname(mmc), host);
(7)sdhci_init(host, 0); // 软初始化host
(8)sdhci_enable_card_detection(host); // 开始使能card插入状态的检测

五、sdhci core内部代码简单说明

1、sdhci_reset & sdhci_init & sdhci_enable_card_detection

  • sdhci_reset


    由sdhci core内部调用,用于复位host。
  • sdhci_init


    由sdhci core内部调用,用于初始化host
  • sdhci_enable_card_detection


    由sdhci core内部调用,使能card插入状态的检测,主要是设置SDHCI_INT_ENABLE、SDHCI_SIGNAL_ENABLE寄存器
static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
irqreturn_t result;
struct sdhci_host *host = dev_id;
u32 intmask, unexpected = 0;
int cardint = 0, max_loops = 16; spin_lock(&host->lock); /* 从SDHCI_INT_STATUS寄存器中读取中断状态 */
intmask = sdhci_readl(host, SDHCI_INT_STATUS); // 从SDHCI_INT_STATUS寄存器中读取中断状态 /* 确认是否有中断产生 */
if (!intmask || intmask == 0xffffffff) {
result = IRQ_NONE;
goto out;
} again:
DBG("*** %s got interrupt: 0x%08x\n",
mmc_hostname(host->mmc), intmask); /* 以下是对card插入或者拔出的中断进行处理 */
if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
SDHCI_CARD_PRESENT;
sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT :
SDHCI_INT_CARD_REMOVE);
sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE :
SDHCI_INT_CARD_INSERT); sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |
SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); // 重置这两个中断位
intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
tasklet_schedule(&host->card_tasklet); // 执行host->card_tasklet,也就是sdhci_tasklet_card进行处理,后面说明
} /* 以下是sdhci处理命令产生的中断进行处理,不一定是出错 */
if (intmask & SDHCI_INT_CMD_MASK) {
if (intmask & SDHCI_INT_AUTO_CMD_ERR)
host->auto_cmd_err_sts = sdhci_readw(host,
SDHCI_AUTO_CMD_ERR);
sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK,
SDHCI_INT_STATUS);
if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) &&
(host->clock <= 400000))
udelay(40);
sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); // 在sdhci_cmd_irq中会执行host->finish_tasklet, 也就是sdhci_tasklet_finish来通知上层。后面说明。
} /* 以下是sdhci处理数据产生的中断进行处理,不一定是出错 */
if (intmask & SDHCI_INT_DATA_MASK) {
sdhci_writel(host, intmask & SDHCI_INT_DATA_MASK,
SDHCI_INT_STATUS);
if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) &&
(host->clock <= 400000))
udelay(40);
sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); // 在sdhci_data_irq中会执行host->finish_tasklet, 也就是sdhci_tasklet_finish来通知上层。
} intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); intmask &= ~SDHCI_INT_ERROR; /* 以下是对总线电源状态发生变化的中断的处理 */
if (intmask & SDHCI_INT_BUS_POWER) {
pr_err("%s: Card is consuming too much power!\n",
mmc_hostname(host->mmc));
sdhci_writel(host, SDHCI_INT_BUS_POWER, SDHCI_INT_STATUS);
} intmask &= ~SDHCI_INT_BUS_POWER; if (intmask & SDHCI_INT_CARD_INT)
cardint = 1; intmask &= ~SDHCI_INT_CARD_INT; if (intmask) {
unexpected |= intmask;
sdhci_writel(host, intmask, SDHCI_INT_STATUS);
} result = IRQ_HANDLED; /* 可能不止有其他事件导致中断的产生,重复检测 */
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
if (intmask && --max_loops)
goto again;
out:
spin_unlock(&host->lock);
return result;
}

3、sdhci_tasklet_card

  • 简单流程说明:

    • 当进行卡插入或者拔出的时候,sdhci controller(硬件)会检测到其状态发生变化
    • sdhci controller(硬件)会设置中断状态寄存器中SDHCI_INT_CARD_INSERT或者SDHCI_INT_CARD_REMOVE位
    • sdhci controller(硬件)触发中段
    • sdhci core中的中断处理函数sdhci_irq被调用(软件)
    • sdhci_irq(软件)去判断出中断状态寄存器中SDHCI_INT_CARD_INSERT或者SDHCI_INT_CARD_REMOVE位被设置
    • sdhci_irq执行host->card_tasklet,也就是我们这里的sdhci_tasklet_card进行相应处理。
  • sdhci_tasklet_card实现如下:
static void sdhci_tasklet_card(unsigned long param)
{
struct sdhci_host *host = (struct sdhci_host*)param; // 提取sdhci_host结构体
sdhci_card_event(host->mmc); // 发送事件,如果此时有mmc_request正在处理,则会复位数据线和命令线,终止mmc_request处理
mmc_detect_change(host->mmc, msecs_to_jiffies(200));
// 调用mmc_detect_change通知mmc core卡槽状态发生了变化,剩下的就是mmc core的工作了
// mmc_detect_change实现具体参考《mmc core主模块说明》
}

4、sdhci_tasklet_finish

static void sdhci_tasklet_finish(unsigned long param)
{
//......过滤掉前面一些根据情况决定的复位操作
mmc_request_done(host->mmc, mrq);
// 调用mmc_request_done来通知mmc core 说mrq这个mmc request已经处理完成,至于处理完成的结果由上层自己解决
// mmc_request_done实现具体参考《mmc core主模块说明》
sdhci_runtime_pm_put(host);
}

5、struct mmc_host_ops sdhci_ops各个方法简单说明

static const struct mmc_host_ops sdhci_ops = {
// post_req和pre_req是为了实现异步请求处理而设置的
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
// 具体参考《mmc core主模块》
.pre_req = sdhci_pre_req,
.post_req = sdhci_post_req,
.request = sdhci_request, // host处理mmc请求的方法,在mmc_start_request中会调用
.set_ios = sdhci_set_ios, // 设置host的总线的io setting
.get_cd = sdhci_get_cd, // 检测host的卡槽中card的插入状态
.get_ro = sdhci_get_ro, // 获取host上的card的读写属性
.hw_reset = sdhci_hw_reset, // 硬件复位
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch, // 切换信号电压的方法
.execute_tuning = sdhci_execute_tuning, // 执行tuning操作,为card选择一个合适的采样点
.card_event = sdhci_card_event,
.card_busy = sdhci_card_busy, // 用于检测card是否处于busy状态
.enable = sdhci_enable, // 使能host,当host被占用时(第一次调用mmc_claim_host)调用
.disable = sdhci_disable, // 禁用host,当host被释放时(第一次调用mmc_release_host)调用
.stop_request = sdhci_stop_request, // 停止请求处理的方法
.get_xfer_remain = sdhci_get_xfer_remain,
.notify_load = sdhci_notify_load,
};

8. [mmc subsystem] host(第二章)——sdhci的更多相关文章

  1. 7. [mmc subsystem] host(第一章)——概述

    一.host简单说明 host,也可以理解为host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线. host contro ...

  2. 10. [mmc subsystem] host(第四章)——host实例(sdhci-msm说明)

    一.说明 sdhci-msm是指高通的mmc host,其使用了标准SDHC标准.故可以使用前面说的<host(第二章)--sdhci>和<host(第三章)--sdhci-pltf ...

  3. 9. [mmc subsystem] host(第三章)——sdhci-pltfm说明

    一.sdhci-pltfm说明 sdhci-pltfm并不是实际某个host的driver. sdhci-pltfm是指在sdhci core的基础上,提供了统一对sdhci_host的必要属性进行解 ...

  4. 4. [mmc subsystem] mmc core(第四章)——host模块说明

    零.说明 对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h. 为底层host controller driver实现mmc host的申请以及注册 ...

  5. 6. [mmc subsystem] mmc core(第六章)——mmc core主模块

    一.说明 1.mmc core概述 mmc core主模块是mmc core的实现核心.也是本章的重点内容. 对应代码位置drivers/mmc/core/core.c. 其主要负责如下功能: mmc ...

  6. 5. [mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)

    零.说明(重要,需要先搞清楚概念有助于后面的理解) 1.mmc core card相关模块为对应card实现相应的操作,包括初始化操作.以及对应的总线操作集合.负责和对应card协议层相关的东西. 这 ...

  7. 1. [mmc subsystem] 概念与框架

    一.概念 1.mmc的概念 mmc有很多种意义,具体如下: mmc MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体. 主要 ...

  8. CentOS 7.4 初次手记:第二章 CentOS安装步骤

    第二章 CentOS安装步骤... 18 第一节 下载... 18 第二节 分区参考... 18 第三节 安装... 19 I Step 1:引导... 19 II Step 2:配置... 20 I ...

  9. 第二章 NIO入门

    传统的同步阻塞式I/O编程 基于NIO的非阻塞编程 基于NIO2.0的异步非阻塞(AIO)编程 为什么要使用NIO编程 为什么选择Netty 第二章 NIO 入门 2.1 传统的BIO编程 2.1.1 ...

随机推荐

  1. jsp页面格式化时间 fmt:formatDate格式化日期

    使用fmt函数需在jsp中引入 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" ...

  2. C学习笔记(6)--- 共用体,位域深入

    1.共用体(Union): 共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型.您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值.共用体提供了一种使用相同的内存位置 ...

  3. HTML 中img标签不显示

    异常 图片请求响应吗: 403, url响应如下: Request Method: GET Status Code: 403 Forbidden Remote Address: ***.***.**. ...

  4. 8. Go语言—指针类型

    一.指针类型介绍 普通类型,变量存的就是值,也叫值类型. 获取变量的地址,用&,比如:var a int ,获取a的地址:&a 指针类型,变量存的是一个地址,这个地址存的才是值(指针存 ...

  5. 3. Go语言—函数和常量

    一.函数声明和注释 1. 声明 ​ func 函数名 (参数列表)(返回值列表){} func add{ pass } func add(a int, b int){ } func add(a int ...

  6. 1. Go语言—初始

    一.golang语言特性 1. 垃圾回收 内存自动回收,再也不需要开发人员管理内存 开发人员专注业务实现,降低了心智负担 只需要new分配内存,不需要释放 2. 天然并发 从语言层面支持并发,非常简单 ...

  7. Python常用数据类型简介

    1.变量的三个基本特征 1,大印 2,判断变量值是否相等 3,判断变量id是否相等 2.常用数据类型分类 数字类型(int) 字符串类型(str) 列表类型(list) 字典类型(dict(dicti ...

  8. Html学习之十八(表格与表单学习--统计表制作)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. 如何在Pycharm中添加新的模块

    在使用Pycharm编写程序时,我们时常需要调用某些模块,但有些模块事先是没有的,我们需要把模块添加上去. 最近在学习爬虫,写了下面几行代码: 结果出现错误 错误ModuleNotFoundError ...

  10. 浅谈字符串Hash

    浅谈字符串Hash 本篇随笔讲解Hash(散列表)的一个重要应用:字符串Hash. 关于Hash Hash是一种数据结构,叫做Hash表(哈希表),也叫散列表.关于Hash的实现,其实与离散化颇为类似 ...