当前位置 博文首页 > 想要去旅行:u-boot 中使用另一个 uart 与外设通信
我有一个板子( sun8i h3),uart_0 是console , uart_1 与一个外设连接,要在uboot 命令行中读写 uart_1 与外设通信。
dts 修改:
--- a/arch/arm/dts/sun8i-h3-nanopi.dtsi
+++ b/arch/arm/dts/sun8i-h3-nanopi.dtsi
@@ -51,6 +51,7 @@
/ {
aliases {
serial0 = &uart0;
+ serial1 = &uart1;
};
chosen {
@@ -132,6 +133,13 @@
status = "okay";
};
+&uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart1_pins_a>;
+ status = "okay";
+};
代码中如何找到 uart1 对应的设备?
drivers\core\uclass.c 中提供了几个接口
int uclass_find_device(enum uclass_id id, int index, struct udevice **devp);
int uclass_find_device_by_name(enum uclass_id id, const char *name,
struct udevice **devp);
int uclass_find_device_by_ofnode(enum uclass_id id, ofnode node,
struct udevice **devp)
于是我写了如下代码:
struct udevice *dev;
uclass_find_device(UCLASS_SERIAL, 1, &dev);
struct dm_serial_ops *ops = serial_get_ops(dev);
int err;
err = ops->putc(dev, 'r');
执行时:报错data abort , 根据PC 指针和反汇编最终定位到出错的地方在 ns16550_readb 函数中
static int ns16550_readb(NS16550_t port, int offset)
{
struct ns16550_platdata *plat = port->plat;
unsigned char *addr;
offset *= 1 << plat->reg_shift;
addr = (unsigned char *)plat->base + offset;
return serial_in_shift(addr + plat->reg_offset, plat->reg_shift);
}
结合 r0 = 000000 , 可知是 port 指针为 空导致的 data abort 。
而 port 指针 是在probe 函数中初始化。
原来在u-boot 中光找到设备还不行,还需要手动执行 device_probe , drivers\core\uclass.c 中提供了另一个接口
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp)
{
struct udevice *dev;
int ret;
*devp = NULL;
ret = uclass_find_device(id, index, &dev);
return uclass_get_device_tail(dev, ret, devp); // 这里面执行 device_probe
}
因此我的代码改为:
struct udevice *dev;
uclass_get_device(UCLASS_SERIAL, 1, &dev); // + uclass_find_device 改为 uclass_get_device
struct dm_serial_ops *ops = serial_get_ops(dev);
int err;
err = ops->putc(dev, 'r');
执行发现:代码卡在了 device_probe 里没有返回!
跟踪发现:device_probe 调用 drv-probe , - > ns16550_serial_probe -> NS16550_init , 卡在 NS16550_init 函数中。
void NS16550_init(NS16550_t com_port, int baud_divisor)
{
while (!(serial_in(&com_port->lsr) & UART_LSR_TEMT)) //<--------卡在这里
;
serial_out(CONFIG_SYS_NS16550_IER, &com_port->ier);
serial_out(UART_MCRVAL, &com_port->mcr);
serial_out(ns16550_getfcr(com_port), &com_port->fcr);
if (baud_divisor != -1)
NS16550_setbrg(com_port, baud_divisor);
}
#define UART_LSR_TEMT 0x40 /* Xmitter empty */
这里一直等发送缓冲为空,为什么一直不为空?
。。。
。。。
。。。
最终参考 uart0 的初始化相关代码,u-boot-2017.11.git\arch\arm\mach-sunxi\clock_sun6i.c 中有对 uart0 时钟的相关操作
void clock_init_uart(void)
{
#if CONFIG_CONS_INDEX < 5
struct sunxi_ccm_reg *const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
/* uart clock source is apb2 */
writel(APB2_CLK_SRC_OSC24M|
APB2_CLK_RATE_N_1|
APB2_CLK_RATE_M(1),
&ccm->apb2_div);
/* open the clock for uart */
setbits_le32(&ccm->apb2_gate,
CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT +
CONFIG_CONS_INDEX - 1));
/* deassert uart reset */
setbits_le32(&ccm->apb2_reset_cfg,
1 << (APB2_RESET_UART_SHIFT +
CONFIG_CONS_INDEX - 1));
#else
/* enable R_PIO and R_UART clocks, and de-assert resets */
prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_UART);
#endif
}
因此uart_1 的时钟也要在 probe 之前打开:
static void uart1_clock_init(void)
{
struct sunxi_ccm_reg *const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
/* uart clock source is apb2 */
writel(APB2_CLK_SRC_OSC24M|
APB2_CLK_RATE_N_1|
APB2_CLK_RATE_M(1),
&ccm->apb2_div);
/* open the clock for uart1 */
setbits_le32(&ccm->apb2_gate,
CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT +
2 - 1));
/* deassert uart reset uart1 */
setbits_le32(&ccm->apb2_reset_cfg,
1 << (APB2_RESET_UART_SHIFT +
2 - 1));
}
修改代码为:
struct udevice *dev;
uart1_clock_init(); // + probe 前对 uart1 时钟初始化
uclass_get_device(UCLASS_SERIAL, 1, &dev);
struct dm_serial_ops *ops = serial_get_ops(dev);
int err;
err = ops->putc(dev, 'r');
测试又发现数据收发不正常。
。。。
。。。
。。。
最终发现是 uart_1 的 tx , rx 引脚复用需要配置。
u-boot 的 gpio 复用设置是在 u-boot-2017.11.git\arch\arm\mach-sunxi\board.c 中
static int gpio_init(void)
{
...
#elif CONFIG_CONS_INDEX == 1 && defined(CONFIG_MACH_SUNXI_H3_H5)
sunxi_gpio_set_cfgpin(SUNXI_GPA(4), SUN8I_H3_GPA_UART0);
sunxi_gpio_set_cfgpin(SUNXI_GPA(5), SUN8I_H3_GPA_UART0);
sunxi_gpio_set_pull(SUNXI_GPA(5), SUNXI_GPIO_PULL_UP);
...
}
相应的增加 uart_1 gpio 的设置
static void uart1_gpio_init(void)
{
/*uart 1*/
sunxi_gpio_set_cfgpin(SUNXI_GPG(6), SUN8I_H3_GPA_UART0);
sunxi_gpio_set_cfgpin(SUNXI_GPG(7), SUN8I_H3_GPA_UART0);
sunxi_gpio_set_pull(SUNXI_GPG(7), SUNXI_GPIO_PULL_UP);
}
修改代码为:
struct udevice *dev;
uart1_clock_init();
uart1_gpio_init(); // + 增加 uart_1 gpio 的设置
uclass_get_device(UCLASS_SERIAL, 1, &dev);
struct dm_serial_ops *ops = serial_get_ops(dev);
int err;
err = ops->putc(dev, 'r');
之后发现 ops->putc 并不是一定返回成功,因为有时返回 -EAGAIN 。
参考 u-boot-2017.11.git\drivers\serial\serial-uclass.c , 对 ops->putc 进行封装
static void _serial_putc(struct udevice *dev, char ch)
{
struct dm_serial_ops *ops = serial_get_ops(dev);
int err;
if (ch == '\n')
_serial_putc(dev, '\r');
do {
err = ops->putc(dev, ch);
} while (err == -EAGAIN);
}
一路磕磕碰碰,最终使用 uart_1 与外设通信上了。
小结:
u-boot 中对设备的操作,跟linux中还是有不一样的
最终成品代码如下:
#include <common.h>
#include <command.h>
#include <stdio_dev.h>
#include <serial.h>
#include <dm.h>
#include <dm/device.h>
#include <dm/device-internal.h>
#include <dm/uclass.h>
#include <dm/uclass-internal.h>
#include <errno.h>
#include <os.h>
#include <watchdog.h>
#include <dm/lists.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/gpio.h>
extern void show_hex(unsigned char* buff, int len);
static void uart1_clock_init(void)
{
struct sunxi_ccm_reg *const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE