u-boot 中使用另一个 uart 与外设通信

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;

	/* 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));

}

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);
}

/*
//host: 'r'/reg/num             //mcu: dat[num]
//host: 'w'/reg/num/dat[num]
*/
static int my_getc(struct udevice *dev)
{
	struct dm_serial_ops *ops = serial_get_ops(dev);
	int err;

	do {
		err = ops->getc(dev);
		if (err == -EAGAIN)
			WATCHDOG_RESET();
	} while (err == -EAGAIN);

	return err >= 0 ? err : 0;
}

static void my_putc(struct udevice *dev, char ch)
{
	struct dm_serial_ops *ops = serial_get_ops(dev);
	int err;

	if (ch == '\n')
		my_putc(dev, '\r');

	do {
		err = ops->putc(dev, ch);
	} while (err == -EAGAIN);
}


static int dev_read_reg(struct udevice *dev ,int reg, int num, unsigned char *buf)
{
	int i;	

	if(!buf)
		return -1;
	//ops->setbrg(dev, 115200);	
	my_putc(dev, 'r');	
	my_putc(dev, reg&0xFF);
	my_putc(dev, num);

	udelay(50000);

	for(i = 0 ; i < num ; i++ )
	{
		buf[i] = my_getc(dev);
		//printf("%02x:%02x\n",i,buf[i]);
	}
	
	return 0;
}

static int dev_write_reg(int reg , int value)
{

	return -1;
}

static int do_dev(cmd_tbl_t * cmd, int flag, int argc, char * const argv[])
{
	int reg,value;
	struct udevice *dev = NULL;
	unsigned char buf[256]={0};

	memset(buf, 0, sizeof(buf));
	
	if(argc < 2)
	{
		return -1;
	}

	uart1_clock_init();
	uart1_gpio_init();

	uclass_get_device(UCLASS_SERIAL, 1, &dev);
	//printf("name:%s \n",dev->name);	

	if(!strcmp(argv[1],"read"))
	{
		reg = simple_strtoul(argv[2],NULL,16);
		dev_read_reg(dev,reg,1,buf);
		
		printf("reg 0x%02x = 0x%02x\n",reg,buf[0]);
		return 0;
	}	
	
	return -1;
}

U_BOOT_CMD(
	dev,	9,	1,	do_dev,
	"dev tool",
	"dev read <reg>\n"	
);

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值