全志D1开发(九)SD驱动之控制器硬件设置

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2022年5月3日16:48:11 评论 861

1. 硬件初始化

本文章不会对全志控制器的具体寄存器做详细的分析,只会关注接口封装或者设置时一般的流程或者共性的东西,详细的寄存器意义大家可以查阅数据手册或者源码理解。

控制器的硬件初始化主要包括以下三个方面:

  • 引脚复用!!!:需要设置引脚寄存器,将其设置为SD引脚使用,否则你可能调试半天发现时钟也没有,数据也没有,到底咋回事啊(来自某位同事的亲身经历)!!!但是由于我们这次使用的开发板uboot已经将SD卡引脚复用初始化好了,为了简单起见,我们就不再去重复设置引脚复用了。
  • 时钟初始化:需要设置时钟树打开SD模块时钟输入,在全志D1芯片中就是设置ccu模块。
  • 控制器复位和初步设置:一般需要复位控制器,然后做一些基础的设置。

我们首先需要对ccu操作封装出两个接口,一个是使能控制器时钟输入,一个是设置具体的时钟频率,如下所示:

/*
 * ccu.c
 *
 *  Created on: Apr 26, 2022
 *      Author: gewenbin
 */
#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <linux/compat.h>

#define CCU_REG_BASE            (0x02001000)
#define CCU_REG_SMHC0_CLK       (CCU_REG_BASE + 0x830)
#define CCU_REG_SMHC_BUS        (CCU_REG_BASE + 0x84c)

void ccu_smhc0_bus_gate_enable (int enable)
{
    u32 val;
    val = readl(CCU_REG_SMHC_BUS);

    if (enable) {
        // de assert smhc0
        writel(val | (1 << 16), CCU_REG_SMHC_BUS);
        usleep(100);
        // smhc0 gate pass
        writel(val | (1 << 0), CCU_REG_SMHC_BUS);
    } else {
        // smhc0 gate mask
        writel(val & (~(1 << 0)), CCU_REG_SMHC_BUS);
    }
}

int ccu_smhc0_clock_set (unsigned int hz)
{
    unsigned int pll, pll_hz, div, n;

    if (hz <= 24000000) {
        pll = (0 << 24); // select HOSC
        pll_hz = 24000000;
    } else {
        pll = (2 << 24); // select PLL_PERI(2X)
        pll_hz = 1200000000; // PLL_PERI(2X) default 1.2GHz
    }

    div = pll_hz / hz;
    if (pll_hz % hz)
        div++;

    n = 0;
    while (div > 16) {
        n++;
        div = (div + 1) / 2;
    }

    if (n > 3) {
        _PrintFormat("mmc0 error cannot set clock to %d\n", hz);
        return -1;
    }

    writel((1 << 31) | pll | (n << 8) | (div << 0), CCU_REG_SMHC0_CLK);

    return 0;
}

设置时钟频率的代码是直接参考的uboot下的sd驱动,由于D1的ccu比较简单,大家自行查阅手册理解代码。同样复位控制器和设置基础初始化代码封装为 sunxi_mmc0_init 供外部使用:

void sunxi_mmc0_init(void)
{
    u32 rval;

    // TODO:初始化引脚复用

    // 初始化时钟
    ccu_smhc0_bus_gate_enable(1);
    ccu_smhc0_clock_set(24000000);

    // 复位控制器
    writel(SUNXI_MMC_GCTRL_RESET, &reg->gctrl);
    usleep(1000);

    // 使能 NEWTIMING
    rval = readl(&reg->ntsr);
    writel(rval | SUNXI_MMC_NTSR_MODE_SEL_NEW, &reg->width);

    // 不使用 DDR 模式
    rval = readl(&reg->gctrl);
    rval &= ~SUNXI_MMC_GCTRL_DDR_MODE;
    writel(rval, &reg->gctrl);
}

2. 控制器线宽设置

设置线宽比较简单,根据手册设置对应的寄存器即可:

void sunxi_mmc0_buswidth_set(int width)
{
    switch (width) {
    case SDARG_SETBUSWIDTH_8:
        writel(0x2, &reg->width);
        break;
    case SDARG_SETBUSWIDTH_4:
        writel(0x1, &reg->width);
        break;
    case SDARG_SETBUSWIDTH_1:
        writel(0x0, &reg->width);
        break;
    }
}

参数为SD协议栈传下来的线宽值宏。

3. 控制器时钟频率设置

时钟频率设置代码同样参考uboot,如下所示:

static int mmc_update_clk(void)
{
    unsigned int cmd;
    unsigned long expire = API_TimeGet() + LW_MSECOND_TO_TICK_1(2000);

    cmd = SUNXI_MMC_CMD_START |
          SUNXI_MMC_CMD_UPCLK_ONLY |
          SUNXI_MMC_CMD_WAIT_PRE_OVER;

    writel(cmd, &reg->cmd);
    while (readl(&reg->cmd) & SUNXI_MMC_CMD_START) {
        if (API_TimeGet() > expire)
            return -1;
    }

    /* clock update sets various irq status bits, clear these */
    writel(readl(&reg->rint), &reg->rint);

    return 0;
}

int sunxi_mmc0_clock_set(int hz)
{
    unsigned rval = readl(&reg->clkcr);

    /* Disable Clock */
    rval &= ~SUNXI_MMC_CLK_ENABLE;
    writel(rval, &reg->clkcr);
    if (mmc_update_clk())
        goto error;

    /* Set mod_clk to new rate */
    ccu_smhc0_clock_set(hz);

    /* Clear internal divider */
    rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
    writel(rval, &reg->clkcr);

    // calibration
    writel(SUNXI_MMC_CAL_DL_SW_EN, &reg->samp_dl);

    /* Re-enable Clock */
    rval |= SUNXI_MMC_CLK_ENABLE;
    writel(rval, &reg->clkcr);
    if (mmc_update_clk())
        goto error;

    return 0;

error:
    SD_ERR("mmc_update_clk error!\r\n");
    return -1;
}

uboot采用的策略是不在sd控制器本身进行分频,而是直接在ccu时钟源处分出正确的频率,所以需要调用 ccu_smhc0_clock_set 设置好时钟源频率。

4.命令和数据传输

4.1 SylixOS SD传输消息

SylixOS下SD传输命令和数据都是通过 LW_SD_MESSAGE 数据结构来描述的:

typedef struct lw_sd_message {
    LW_SD_COMMAND  *SDMSG_psdcmdCmd;                                    /*  发送命令                    */
    LW_SD_DATA     *SDMSG_psddata;                                      /*  数据传输属性                */
    LW_SD_COMMAND  *SDMSG_psdcmdStop;                                   /*  停止命令                    */
    UINT8          *SDMSG_pucRdBuffer;                                  /*  请求缓冲(读缓冲)            */
    UINT8          *SDMSG_pucWrtBuffer;                                 /*  请求缓冲(写缓冲)            */
} LW_SD_MESSAGE, *PLW_SD_MESSAGE;
  • SDMSG_psdcmdCmd:用于描述发送的命令信息,使用 LW_SD_COMMAND 数据结构,这个数据结构下面在详细讲解。
  • SDMSG_psddata:如果传输需要携带数据,用于描述数据的大小和读写方向信息,通过LW_SD_DATA 数据结构描述。
  • SDMSG_psdcmdStop:描述停止命令信息。
  • SDMSG_pucRdBuffer:如果是读数据传输,此成员记录SD协议栈中数据缓冲区的地址。
  • SDMSG_pucWrtBuffer:如果是写数据传输,此成员记录SD协议栈中数据缓冲区的地址。

不同于uboot或者linux下的数据结构,SylixOS下如果有数据传输,其上层协议栈的缓冲区地址是放在LW_SD_MESSAGE 数据结构中记录的,而不是记录在LW_SD_DATA 中。

SylixOS下的SD命令是通过LW_SD_COMMAND 来描述的:

typedef struct lw_sd_command {
    UINT32  SDCMD_uiOpcode;                                             /*  操作码(命令)                */
    UINT32  SDCMD_uiArg;                                                /*  参数                        */
    UINT32  SDCMD_uiResp[4];                                            /*  应答(有效位最多128位)       */
    UINT32  SDCMD_uiFlag;                                               /*  属性位标 (命令和应答属性)   */
    UINT32  SDCMD_uiRetry;
} LW_SD_COMMAND, *PLW_SD_COMMAND;
  • SDCMD_uiOpcode:命令码,用于表示是SD协议中规定的哪种命令。
  • SDCMD_uiArg:如果命令带有参数,用于保存参数值。
  • SDCMD_uiResp:如果命令有应答,用于保存设备应答值。
  • SDCMD_uiFlag:属性标志。
  • SDCMD_uiRetry:如果传输错误,重试次数。

SylixOS下的SD数据是通过 LW_SD_DATA 来描述的:

typedef struct lw_sd_data {
    UINT32  SDDAT_uiBlkSize;
    UINT32  SDDAT_uiBlkNum;
    UINT32  SDDAT_uiFlags;
} LW_SD_DATA, *PLW_SD_DATA;
#define SD_DAT_WRITE   (1 << 8)
#define SD_DAT_READ    (1 << 9)
#define SD_DAT_STREAM  (1 << 10)
  • SDDAT_uiBlkSize:传输扇区大小,一般为512。
  • SDDAT_uiBlkNum:传输扇区个数。
  • SDDAT_uiFlags:属性标志,比较是读还是写操作等。

SylixOS下的这些数据结构都能在uboot下或者linux下找到对应功能的数据结构,在实际驱动移植时做对应的替换即可。

4.2 数据传输方式

全志SMHC支持DMA描述符和FIFO两种方式传输数据,本教程为了简单起见,采用cpu读写fifo的方式来传输数据,对外封装出 sunxi_mmc0_xfer 接口使用,将SD协议栈传下来的SD消息解析并设置对应的控制器寄存器,具体的代码请参见https://pan.baidu.com/s/15r0SD4ASu-j7K-vnuCEq_w?pwd=4x8j

gewenbin
  • 本文由 发表于 2022年5月3日16:48:11
  • 转载请务必保留本文链接:http://www.databusworld.cn/10699.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: