当前位置 博文首页 > 想要去旅行:u-boot 中使用另一个 uart 与外设通信

    想要去旅行:u-boot 中使用另一个 uart 与外设通信

    作者:[db:作者] 时间:2021-08-31 15:53

    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中还是有不一样的

    • find_device_xx 后并不能直接用,因为没有进行 device_probe , get_device_xx 后才的可以
    • u-boot 的dts 里虽然写明了时钟和引脚复用,但u-boot 并不一定会去处理,使用还是需要自己初始化时钟和引脚

    最终成品代码如下:

    #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
    
    下一篇:没有了