U-boot 硬件相关部分移植
u-boot的基本功能是用来初始化部分系统,然后加载并启动内核。
根据具体项目的需求,我们的系统由SD卡启动。可以通过读卡器直接更新SD中的文件,因此不需要TFTP来下载和更新内核,也就不需要u-boot的网络功能。
因此具体移植的u-boot的功能相对简单, 其外设只需要SD卡和串口输出,不需要以太网, USB等其他功能外设。
仔细细分, 需要移植的功能有:
(1)增加自己板子的版号支持;Ti的SDK中,u-boot通过读板子上的一个I2C接口的EEPROM中的硬件信息来区别不同的板子,因此需要修改这部分代码来绕过这部分的判定。
(2)SD卡,串口的功能管脚复用设定;
(3)DDR SDRM参数的修改;
板子ID
首先在include/configs/am335x_cfe.h中定义:
#define SYSTEMCORP_CFE //define our own baord
在board/set/common/board_detect.c中的函数ti_i2c_eeprom_am_get()是用来读取EEPROM中的数据的。因为我们没有采用EEPROM,所以我们添加代码来人为写入数据给结构体TI_EEPROM_DATA:
int __maybe_unused ti_i2c_eeprom_am_get(int bus_addr, int dev_addr)
{
int rc;
struct ti_am_eeprom am_ep;
struct ti_common_eeprom *ep;
ep = TI_EEPROM_DATA;
if (ep->header == TI_EEPROM_HEADER_MAGIC)
goto already_read;
//如果定义了SYSTEMCORP_CFE, 就认为人为给ep赋值。
#if defined SYSTEMCORP_CFE
//if ((bus_addr == -1) && (dev_addr == CONFIG_SYS_I2C_EEPROM_ADDR))
//{
ep->header = TI_EEPROM_HEADER_MAGIC;
//板子的name为SET_CFE, 后续的板号判定就以该名字为标准。
strlcpy(ep->name, "SET_CFE", TI_EEPROM_HDR_NAME_LEN + 1);
ti_eeprom_string_cleanup(ep->name);
strlcpy(ep->version, "1.0", TI_EEPROM_HDR_REV_LEN + 1);
ti_eeprom_string_cleanup(ep->version);
strlcpy(ep->serial, "00000000", TI_EEPROM_HDR_SERIAL_LEN + 1);
ti_eeprom_string_cleanup(ep->serial);
strlcpy(ep->config, "SKU#01", TI_EEPROM_HDR_CONFIG_LEN + 1);
ti_eeprom_string_cleanup(ep->config);
char mac_addr[TI_EEPROM_HDR_NO_OF_MAC_ADDR][TI_EEPROM_HDR_ETH_ALEN] = {"000001","000002","000003"};
memcpy(ep->mac_addr, mac_addr,
TI_EEPROM_HDR_NO_OF_MAC_ADDR * TI_EEPROM_HDR_ETH_ALEN);
goto already_read;
//}
#endif
#if defined SYSTEMCORP_LCD
//if ((bus_addr == -1) && (dev_addr == CONFIG_SYS_I2C_EEPROM_ADDR))
//{
ep->header = TI_EEPROM_HEADER_MAGIC;
strlcpy(ep->name, "SET_LCD", TI_EEPROM_HDR_NAME_LEN + 1);
ti_eeprom_string_cleanup(ep->name);
strlcpy(ep->version, "1.0", TI_EEPROM_HDR_REV_LEN + 1);
ti_eeprom_string_cleanup(ep->version);
strlcpy(ep->serial, "00000000", TI_EEPROM_HDR_SERIAL_LEN + 1);
ti_eeprom_string_cleanup(ep->serial);
strlcpy(ep->config, "SKU#01", TI_EEPROM_HDR_CONFIG_LEN + 1);
ti_eeprom_string_cleanup(ep->config);
char mac_addr[TI_EEPROM_HDR_NO_OF_MAC_ADDR][TI_EEPROM_HDR_ETH_ALEN] = {"000001","000002","000003"};
memcpy(ep->mac_addr, mac_addr,
TI_EEPROM_HDR_NO_OF_MAC_ADDR * TI_EEPROM_HDR_ETH_ALEN);
goto already_read;
//}
#endif
/* Initialize with a known bad marker for i2c fails.. */
ep->header = TI_DEAD_EEPROM_MAGIC;
ep->name[0] = 0x0;
ep->version[0] = 0x0;
ep->serial[0] = 0x0;
rc = ti_i2c_eeprom_get(bus_addr, dev_addr, TI_EEPROM_HEADER_MAGIC,
sizeof(am_ep), (uint8_t *)&am_ep);
if (rc)
return rc;
ep->header = am_ep.header;
strlcpy(ep->name, am_ep.name, TI_EEPROM_HDR_NAME_LEN + 1);
ti_eeprom_string_cleanup(ep->name);
/* BeagleBone Green '1' eeprom, board_rev: 0x1a 0x00 0x00 0x00 */
if (am_ep.version[0] == 0x1a && am_ep.version[1] == 0x00 &&
am_ep.version[2] == 0x00 && am_ep.version[3] == 0x00)
strlcpy(ep->version, "BBG1", TI_EEPROM_HDR_REV_LEN + 1);
else
strlcpy(ep->version, am_ep.version, TI_EEPROM_HDR_REV_LEN + 1);
ti_eeprom_string_cleanup(ep->version);
strlcpy(ep->serial, am_ep.serial, TI_EEPROM_HDR_SERIAL_LEN + 1);
ti_eeprom_string_cleanup(ep->serial);
strlcpy(ep->config, am_ep.config, TI_EEPROM_HDR_CONFIG_LEN + 1);
ti_eeprom_string_cleanup(ep->config);
memcpy(ep->mac_addr, am_ep.mac_addr,
TI_EEPROM_HDR_NO_OF_MAC_ADDR * TI_EEPROM_HDR_ETH_ALEN);
already_read:
return 0;
}
接着在board/set/am335_cfe/board.h中定义函数board_is_set_cfe()用来判定TI_EEPROM_DATA中的名字是否是SET_CFE。
static inline int board_is_set_cfe(void)
{
return board_ti_is("SET_CFE"); //判定板子的名字是不是SET_CFE
}
而board_ti_is()定义在board/set/common/board_detect.c中
bool __maybe_unused board_ti_is(char *name_tag)
{
struct ti_common_eeprom *ep = TI_EEPROM_DATA;
if (ep->header == TI_DEAD_EEPROM_MAGIC)
return false;
return !strncmp(ep->name, name_tag, TI_EEPROM_HDR_NAME_LEN);//判定TI_EEPROM_DATA结构体中的名字是否是传入进来的name_tag, 也即是否是上面的SET_CFE。
}
综上, 可以通过调用函数board_is_set_cfe()来以判定是否是我们自己的板子。
管脚复用
管脚复用主要是确认SD卡和串口的配置是否正确。
进入文件board/set/am335-cfe/mux.c的函数enable_board_pin_mux(),在靠后位置添加自己板子的复用管脚信息。
void enable_board_pin_mux(void)
{
/* Do board-specific muxes. */
.......
} else if (board_is_icev2()) {
configure_module_pin_mux(mmc0_pin_mux);
configure_module_pin_mux(gpio0_18_pin_mux);
configure_module_pin_mux(uart3_icev2_pin_mux);
configure_module_pin_mux(rmii1_pin_mux);
configure_module_pin_mux(spi0_pin_mux);
} else if (board_is_set_cfe()) { //添加自己板子的mmc0管脚
configure_module_pin_mux(mmc0_pin_mux_cfe);
configure_module_pin_mux(rgmii1_pin_mux); //如果不添加以太网接口, u-boot运行时会报出错信息,因此添加上该管脚复用,实际中没有使用以太网。
}
else {
puts("Unknown board, cannot configure pinmux.");
hang();
}
}
当然需要在mux.c文件中添加上自己的数据结构mmc0_pin_mux_cfe。
static struct module_pin_mux mmc0_pin_mux_cfe[] = {
{OFFSET(mmc0_dat3), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_DAT3 */
{OFFSET(mmc0_dat2), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_DAT2 */
{OFFSET(mmc0_dat1), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_DAT1 */
{OFFSET(mmc0_dat0), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_DAT0 */
{OFFSET(mmc0_clk), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_CLK */
{OFFSET(mmc0_cmd), (MODE(0) | RXACTIVE | PULLUP_EN)}, /* MMC0_CMD */
//{OFFSET(mcasp0_aclkr), (MODE(4) | RXACTIVE)}, /* MMC0_WP */
//{OFFSET(spi0_cs1), (MODE(7) | RXACTIVE | PULLUP_EN)}, /* GPIO0_6 */
{OFFSET(ecap0_in_pwm0_out), (MODE(5) | RXACTIVE)}, /* MMC0_WP */
{OFFSET(spi0_cs1), (MODE(5) | RXACTIVE | PULLUP_EN)}, /* GPIO0_6 */
{-1},
};
对于串口管脚,TiSDK的u-boot是把它单独列出来的,在board/set/am335-cfe/board.c文件中,
函数set_uart_mux_conf()用来配置用户所选择的串口。
宏CONFIG_CONS_INDEX是通过menuconfig菜单ARM architecture->UART used for console来设定的。用户可以输入1~6来分别选择AM33596个UART的其中之一。常见硬件设计一般将UART0选择为调式终端。因此CONFIG_CONS_INDEX == 1。
void set_uart_mux_conf(void)
{
#if CONFIG_CONS_INDEX == 1
enable_uart0_pin_mux();
#elif CONFIG_CONS_INDEX == 2
enable_uart1_pin_mux();
#elif CONFIG_CONS_INDEX == 3
enable_uart2_pin_mux();
#elif CONFIG_CONS_INDEX == 4
enable_uart3_pin_mux();
#elif CONFIG_CONS_INDEX == 5
enable_uart4_pin_mux();
#elif CONFIG_CONS_INDEX == 6
enable_uart5_pin_mux();
#endif
}
函数enable_uart0_pin_mux()的定义在board/set/am335-cfe/mux.c中:
void enable_uart0_pin_mux(void)
{
configure_module_pin_mux(uart0_pin_mux);
}
需要确定的是数据结构体uart0_pin_mux是否和硬件一致:
static struct module_pin_mux uart0_pin_mux[] = {
{OFFSET(uart0_rxd), (MODE(0) | PULLUP_EN | RXACTIVE)}, /* UART0_RXD */
{OFFSET(uart0_txd), (MODE(0) | PULLUDEN)}, /* UART0_TXD */
{-1},
};
DDR SDRAM参数移植
我们采用的DDR芯片是samsung的K4B2G1646Q-BY,
根据芯片的datasheet计算寄存器的值,具体信息可以参考:
http://processors.wiki.ti.com/index.php/AM335x_DDR_PHY_register_configuration_for_DDR3_using_Software_Leveling
因为本人手上有一个比较低版本的u-boot代码可以在自己的系统上运行, 因此直接copy其数据值。
在arch/arm/include/asm/arch-am33xx/ddr_defs.h中添加:
/* Samsung K4B2G1646Q-BY */
#define K4B2G1646QBY_EMIF_READ_LATENCY 0x07
#define K4B2G1646QBY_EMIF_TIM1 0x0AAAD4DB
#define K4B2G1646QBY_EMIF_TIM2 0x26437FDA
#define K4B2G1646QBY_EMIF_TIM3 0x501F83FF
#define K4B2G1646QBY_EMIF_SDCFG 0x61C052B2
#define K4B2G1646QBY_EMIF_SDREF 0x0000049E
#define K4B2G1646QBY_ZQ_CFG 0x50074BE4
#define K4B2G1646QBY_DLL_LOCK_DIFF 0x1
#define K4B2G1646QBY_RATIO 0x80
#define K4B2G1646QBY_INVERT_CLKOUT 0x0
#define K4B2G1646QBY_RD_DQS 0x3B
#define K4B2G1646QBY_WR_DQS 0x3E
#define K4B2G1646QBY_PHY_FIFO_WE 0x98
#define K4B2G1646QBY_PHY_WR_DATA 0x78
#define K4B2G1646QBY_IOCTRL_VALUE 0x18B
将DDR的参数赋值给u-boot中的DDR 数据结构体。
在board/set/am335-cfe/board.c中添加:
static const struct ddr_data ddr3_set_cfe_data = {
.datardsratio0 = K4B2G1646QBY_RD_DQS,
.datawdsratio0 = K4B2G1646QBY_WR_DQS,
.datafwsratio0 = K4B2G1646QBY_PHY_FIFO_WE,
.datawrsratio0 = K4B2G1646QBY_PHY_WR_DATA,
};
static const struct cmd_control ddr3_set_cfe_cmd_ctrl_data = {
.cmd0csratio = K4B2G1646QBY_RATIO,
.cmd0iclkout = K4B2G1646QBY_INVERT_CLKOUT,
.cmd1csratio = K4B2G1646QBY_RATIO,
.cmd1iclkout = K4B2G1646QBY_INVERT_CLKOUT,
.cmd2csratio = K4B2G1646QBY_RATIO,
.cmd2iclkout = K4B2G1646QBY_INVERT_CLKOUT,
};
static struct emif_regs ddr3_set_cfe_emif_reg_data = {
.sdram_config = K4B2G1646QBY_EMIF_SDCFG,
.ref_ctrl = K4B2G1646QBY_EMIF_SDREF,
.sdram_tim1 = K4B2G1646QBY_EMIF_TIM1,
.sdram_tim2 = K4B2G1646QBY_EMIF_TIM2,
.sdram_tim3 = K4B2G1646QBY_EMIF_TIM3,
.zq_config = K4B2G1646QBY_ZQ_CFG,
.emif_ddr_phy_ctlr_1 = K4B2G1646QBY_EMIF_READ_LATENCY,
};
const struct ctrl_ioregs ioregs_set_cfe = {
.cm0ioctl = K4B2G1646QBY_IOCTRL_VALUE,
.cm1ioctl = K4B2G1646QBY_IOCTRL_VALUE,
.cm2ioctl = K4B2G1646QBY_IOCTRL_VALUE,
.dt0ioctl = K4B2G1646QBY_IOCTRL_VALUE,
.dt1ioctl = K4B2G1646QBY_IOCTRL_VALUE,
};
...
void sdram_init(void)
{
if (read_eeprom() < 0)
puts("Could not get board ID.\n");
...
if (board_is_set_cfe())//判断是自己的板子后,写入DDR寄存器的参数。ioregs_set_cfe,ddr3_set_cfe_data,ddr3_set_cfe_cmd_ctrl_data,ddr3_set_cfe_emif_reg_data即为上面定义的数据结构体。
config_ddr(400, &ioregs_set_cfe, &ddr3_set_cfe_data,
&ddr3_set_cfe_cmd_ctrl_data, &ddr3_set_cfe_emif_reg_data,
0);
else
config_ddr(266, &ioregs, &ddr2_data,
&ddr2_cmd_ctrl_data, &ddr2_emif_reg_data, 0);
}
测试
经过上述的移植,重新编译:
rm -fr am335x_cfe/
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=am335x_cfe am335x_set_cfe_defconfig all
通过menuconfig命令来确认串口选择是否是UART0
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=am335x_cfe menuconfig
编译通过后, 在板子上测试,串口有输出,表示u-boot的移植基本成功。
附注:
在调试的过程中, 如果串口没有输出, 很大可能是DDR 参数配置不对。
本人调试过程中还碰到其他一些问题,但是属于个例,没有在此列出。