[mmc subsystem] host(第二章)——sdhci
来源:互联网 发布:知乎同济大学环境工程 编辑:程序博客网 时间:2024/06/08 09:15
mmc subsystem系列(持续更新中):
[mmc subsystem] 概念与框架
[mmc subsystem] mmc core(第一章)——概述
[mmc subsystem] mmc core(第二章)——数据结构和宏定义说明
[mmc subsystem] mmc core(第三章)——bus模块说明
[mmc subsystem] mmc core(第四章)——host模块说明
[mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)
[mmc subsystem] mmc core(第六章)——mmc core主模块
[mmc subsystem] host(第一章)——概述
[mmc subsystem] host(第二章)——sdhci
[mmc subsystem] host(第三章)——sdhci-pltfm说明
[mmc subsystem] host(第四章)——host实例(sdhci-msm说明)
建议先参考《[mmc subsystem] 概念与框架》对整体有一个了解。
==========================================================================================================
一、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
- 版本(sdhci_host->version)
设置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的设置
- 操作集(mmc_host->ops)
中断的注册
将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寄存器
2、sdhci_irq
sdhci_irq是sdhci host的中断的处理函数。
可能造成中断的事件如下:
- SDHCI_INT_CARD_INSERT & SDHCI_INT_CARD_REMOVE
检测到card插入或者移除产生了中断 - SDHCI_INT_CMD_MASK
命令处理产生的相关中断 - SDHCI_INT_DATA_MASK
数据处理产生的相关中断 - SDHCI_INT_BUS_POWER
总线电源状态发生变化产生的中断 - SDHCI_INT_CARD_INT
card发出的中断
- SDHCI_INT_CARD_INSERT & SDHCI_INT_CARD_REMOVE
对应代码如下:
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
当sdhci controller处理完一个cmd(data)或者处理过程中出现错误,都会产生SDHCI_INT_CMD_MASK、SDHCI_INT_DATA_MASK中断。
在sdhci_irq中会调用sdhci_cmd_irq、sdhci_data_irq进行处理,但是最终都会执行host->finish_tasklet,也就是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,};
- [mmc subsystem] host(第二章)——sdhci
- [mmc subsystem] host(第四章)——host实例(sdhci-msm说明)
- [mmc subsystem] host(第三章)——sdhci-pltfm说明
- [mmc subsystem] mmc core(第四章)——host模块说明
- [mmc subsystem] host(第一章)——概述
- [mmc subsystem] mmc core(第二章)——数据结构和宏定义说明
- [mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)
- [mmc subsystem] mmc core(第六章)——mmc core主模块
- [mmc subsystem] mmc core(第一章)——概述
- [mmc subsystem] mmc core(第三章)——bus模块说明
- Linux MMC子系统(1)-- MMC Host层(2.6.28)
- [mmc subsystem] 概念与框架
- MMC驱动之mmc host
- 编写自己的SD/MMC Host驱动(一):注册
- mmc host 之内核接口
- 基于QualComm的mmc driver解析(Kernel-3.10)——(1)mmc bus
- Linux USB subsystem --- EHCI host controller register
- Linux USB subsystem --- EHCI host controller register
- Oracle中存储过程和函数中IS和AS的区别
- 关于 android 中 postDelayed方法的讲解
- 地址无关码
- stl的vector和list的push_back效率比较
- 业余时间学习安排
- [mmc subsystem] host(第二章)——sdhci
- POJ3181-Dollar Dayz-完全背包+高精度
- 图像处理基础(4):高斯滤波器详解
- [缓慢前行]C# OLEDB方式访问Excel之bug
- eclipse 启动失败,报错org.eclipse.swt.SWTException: Failed to execute runnable
- java String.valueOf()的作用
- Myeclipse clean的作用
- BZOJ 3884: 上帝与集合的正确用法
- [mmc subsystem] host(第三章)——sdhci-pltfm说明