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, ®->gctrl); usleep(1000); // 使能 NEWTIMING rval = readl(®->ntsr); writel(rval | SUNXI_MMC_NTSR_MODE_SEL_NEW, ®->width); // 不使用 DDR 模式 rval = readl(®->gctrl); rval &= ~SUNXI_MMC_GCTRL_DDR_MODE; writel(rval, ®->gctrl); }
2. 控制器线宽设置
设置线宽比较简单,根据手册设置对应的寄存器即可:
void sunxi_mmc0_buswidth_set(int width) { switch (width) { case SDARG_SETBUSWIDTH_8: writel(0x2, ®->width); break; case SDARG_SETBUSWIDTH_4: writel(0x1, ®->width); break; case SDARG_SETBUSWIDTH_1: writel(0x0, ®->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, ®->cmd); while (readl(®->cmd) & SUNXI_MMC_CMD_START) { if (API_TimeGet() > expire) return -1; } /* clock update sets various irq status bits, clear these */ writel(readl(®->rint), ®->rint); return 0; } int sunxi_mmc0_clock_set(int hz) { unsigned rval = readl(®->clkcr); /* Disable Clock */ rval &= ~SUNXI_MMC_CLK_ENABLE; writel(rval, ®->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, ®->clkcr); // calibration writel(SUNXI_MMC_CAL_DL_SW_EN, ®->samp_dl); /* Re-enable Clock */ rval |= SUNXI_MMC_CLK_ENABLE; writel(rval, ®->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。
评论