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);
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;
writel(APB2_CLK_SRC_OSC24M|
APB2_CLK_RATE_N_1|
APB2_CLK_RATE_M(1),
&ccm->apb2_div);
setbits_le32(&ccm->apb2_gate,
CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT +
CONFIG_CONS_INDEX - 1));
setbits_le32(&ccm->apb2_reset_cfg,
1 << (APB2_RESET_UART_SHIFT +
CONFIG_CONS_INDEX - 1));
#else
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;
writel(APB2_CLK_SRC_OSC24M|
APB2_CLK_RATE_N_1|
APB2_CLK_RATE_M(1),
&ccm->apb2_div);
setbits_le32(&ccm->apb2_gate,
CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT +
2 - 1));
setbits_le32(&ccm->apb2_reset_cfg,
1 << (APB2_RESET_UART_SHIFT +
2 - 1));
}
修改代码为:
struct udevice *dev;
uart1_clock_init();
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)
{
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();
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;
writel(APB2_CLK_SRC_OSC24M|
APB2_CLK_RATE_N_1|
APB2_CLK_RATE_M(1),
&ccm->apb2_div);
setbits_le32(&ccm->apb2_gate,
CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT +
2 - 1));
setbits_le32(&ccm->apb2_reset_cfg,
1 << (APB2_RESET_UART_SHIFT +
2 - 1));
}
static void uart1_gpio_init(void)
{
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);
}
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;
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);
}
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);
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"
);
|