- 了解 sdhci host driver.
1.概述
The MultiMediaCard (MMC)/ Secure Digital (SD)/ Secure Digital Input Output (SDIO) host driver implements a standard Linux driver interface to the ultra MMC/SD host controller (microSDHC). The host driver is part of the Linux kernel MMC framework.
1.1.Kernel configuration
You can manage the MMC driver support through the kernel configuration options:
- MMC/SD/SDIO (CONFIG_MMC)
- MMC block (CONFIG_MMC_BLOCK)
- Secure Digital Host Controller Interface support (CONFIG_MMC_SDHCI)
- SDHCI support on the platform-specific bus (CONFIG_MMC_SDHCI_PLTFM)
2.数据结构
2.1.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.2.struct sdhci_ops (host driver要实现的核心内容)
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);
};
2.3. struct mmc_host_ops sdhci_ops
static const struct mmc_host_ops sdhci_ops = {
// post_req和pre_req是为了实现异步请求处理而设置的
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
.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状态
};
注意:这里的sdhci_ops是一个变量名,和上述的struct sdhci_ops不是同一个概念
3.APIs
3.1.sdhci_host 分配和释放 APIs
struct sdhci_host *sdhci_alloc_host(struct device *dev, size_t priv_size)
void sdhci_free_host(struct sdhci_host *host)
- sdhci_alloc_host:为host driver分配一个sdhci_host和mmc_host,并实现其初始化,以及sdhci_host和mmc_host的关联。
- sdhci_free_host:释放一个sdhci_host。
3.1.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;
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;
host->flags = SDHCI_SIGNALING_330;
host->cqe_ier = SDHCI_CQE_INT_MASK;
host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;
host->tuning_delay = -1;
host->tuning_loop_count = MAX_TUNING_LOOP;
host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG;
host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;
return host;
}
关键代码调用流程:
sdhci_alloc_host
-->mmc_alloc_host:分配、设置mmc_host(卡检测的扫描工作队列)
3.2.sdhci_host 注册、卸载
int sdhci_add_host(struct sdhci_host *host);
void sdhci_remove_host(struct sdhci_host *host, int dead);
- 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中被卸载。
3.2.1.分析sdhci_add_host:
- sdhci_setup_host:设置sdhci_host;
- 设置host->flags;
- 设置DMA;
- 设置host clock;
- 等等
- __sdhci_add_host:注册sdhci_host。
- 设置请求处理完成时调用的任务队列处理函数sdhci_tasklet_finish
- 设置当前请求命令的响应定时处理函数sdhci_timeout_timer
- 设置当前数据交互的响应定时sdhci_timeout_data_timer
- 设置等待队列的缓冲区读准备中断
- 设置sdhci_host
- 设置外部中断(sdhci_set_default_irqs),并注册sdhci_irq和sdhci_thread_irq handler
- 注册LED灯
- 调用mmc_add_host
- 使能卡检测sdhci_enable_card_detection
3.2.2.__sdhci_add_host:
int __sdhci_add_host(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
int ret;
tasklet_init(&host->finish_tasklet,
sdhci_tasklet_finish, (unsigned long)host);
setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
setup_timer(&host->data_timer, sdhci_timeout_data_timer,
(unsigned long)host);
init_waitqueue_head(&host->buf_ready_int);
sdhci_init(host, 0);
ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
ret = sdhci_led_register(host);
mmiowb();
ret = mmc_add_host(mmc);
sdhci_enable_card_detection(host);
return 0;
}
refer to
- https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842090/SD+controller
来源:CSDN
作者:Hacker_Albert
链接:https://blog.csdn.net/weixin_41028621/article/details/103778586