8. [mmc subsystem] host(第二章)——sdhci

来源:https://www.cnblogs.com/linhaostudy/archive/2019/05/05/10813200.html
-Advertisement-
Play Games

一、sdhci core說明 1、sdhci說明 具體參考《host(第一章)——概述》 SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的設計標準,其寄存器偏移以及意義都有一定的規範,並且提供了對應的驅動程式,方便vendor進行host ...


一、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,
};

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近開始用DevExpress組件,發現很好的經驗總結博客,在這裡轉載分享 原作者:https://www.cnblogs.com/wordgao/p/4517011.html 一、如何解決單擊記錄整行選中的問題 View->OptionsBehavior->EditorShowMode 設置為:C ...
  • 問題 最近在進行硬體上位機開發的時候,經常會遇到將 16 進位字元串轉換為 的情況,除了這種需求以外,還需要判定一個字元串是否是有效的 16 進位數據。 解決 字元串轉 的情況可以使用 來解決,16 進位數據的判定則可以結合正則和長度來進行處理。 在這裡我是只接受以下兩種形式的 16 進位字元串,並 ...
  • windows 文件操作命令 cd 切換文件目錄 dir 顯示文件目錄內容 md 創建文件夾 rd 刪除文件夾 copy 拷貝文件 move 移動文件 del 刪除文件 replace 替換文件 mklinnk 創建符號鏈接 attrib 查看或修改文件或目錄屬性 type 查看文件內容 more ...
  • @ "TOC" 1.首先下載虛擬機管理軟體(推薦使用VMware Workstation) 可以在這裡下載 https://blog.51cto.com/happynews/2285644 然後安裝 安裝完成後,打開時推薦使用右鍵,用管理員運行 2.下載你所需要的系統鏡像文件 其實不同版本的linu ...
  • 這裡以Centos7為例: 使用tree命令查看/目錄結構如下: 下麵我們主要探討如下主要目錄: /:根目錄不必多說,文件系統的最頂端,存放系統所有目錄。 bin:該目錄主要存放系統運行所需要的重要命令,和普通用戶可以使用的絕大部分命令。 sbin:主要存放系統管理員使用的系統管理的命令。 etc: ...
  • @ "TOC" quickLook插件是Mac上的快速瀏覽的一個功能,現在win10系統上也能安裝插件,這個插件可以快速瀏覽txt,doc,圖片,表格等文件如下圖: 我認為最方便的地方是你需要查看某一個文件內容不需要編輯的時候,又不想用wps打開時就可以用這個簡單的插件。 安裝方式也很簡單,但是需要 ...
  • 一、echo 1.顯示普通字元串: 這裡的雙引號可以省略。 2.顯示轉義字元: 3.顯示變數: read 命令從標準輸入中讀取一行,並把輸入行的每個欄位的值指定給 shell 變數 輸出: 4.顯示換行: 輸出: 5.顯示不換行: 輸出: 6.顯示定向至文件: 輸出: 7.原樣輸出字元串,不進行轉義 ...
  • [20190505]ts 命令在哪裡.txt--//在論壇問一下ts命令在哪裡?沒人解答,自己也google看了一下:https://unix.stackexchange.com/questions/272433/piping-into-moreutils-ts-with-nanosecond-pr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...