15AH, San Francisco

California, United States.

Send Your Mail At:

tianyingkejishe@sina.cn

Working Hours

Mon-Sat: 9.30am To 7.00pm

【imx6ull】使用EIM接口驱动ST16c554扩展串口基于8250串口驱动

前言

       最近做了一个项目使用的正点原子的imx6ull核心板平台,里面涉及到了有关于使用imx6ull的EIM接口进行外部串口扩展的部分,串口扩展芯片使用的是ST16C554这个芯片。这里针对这部分内容做一个记录和分享。

一、硬件设计部分

图1. 硬件电路图

二、驱动部分

设备树部分

pinctrl_COM1_8: COM1_8 {
			fsl,pins = <
				MX6UL_PAD_LCD_DATA08__EIM_DATA00 0x10b1
				MX6UL_PAD_LCD_DATA09__EIM_DATA01 0x10b1
				MX6UL_PAD_LCD_DATA10__EIM_DATA02 0x10b1
				MX6UL_PAD_LCD_DATA11__EIM_DATA03 0x10b1
				MX6UL_PAD_LCD_DATA12__EIM_DATA04 0x10b1
				MX6UL_PAD_LCD_DATA13__EIM_DATA05 0x10b1
				MX6UL_PAD_LCD_DATA14__EIM_DATA06 0x10b1
				MX6UL_PAD_LCD_DATA15__EIM_DATA07 0x10b1

				MX6UL_PAD_CSI_PIXCLK__EIM_OE 0x10b1 /* IO_RD */
				MX6UL_PAD_CSI_VSYNC__EIM_RW  0x10b1 /* IO_WR */

				MX6UL_PAD_CSI_DATA00__EIM_AD00 0x10b1 /* A0 */
				MX6UL_PAD_CSI_DATA01__EIM_AD01 0x10b1 /* A1 */
				MX6UL_PAD_CSI_DATA02__EIM_AD02 0x10b1 /* A2 */
				MX6UL_PAD_CSI_DATA03__EIM_AD03 0x10b1 /* A3 模拟开关选择 */
				MX6UL_PAD_CSI_DATA04__EIM_AD04 0x10b1 /* A4 模拟开关选择 */
				MX6UL_PAD_CSI_DATA05__EIM_AD05 0x10b1 /* A5 模拟开关选择 */

				MX6UL_PAD_CSI_MCLK__EIM_CS0_B 0x10b1 /* CS0 8选1 */

				MX6UL_PAD_LCD_DATA20__GPIO3_IO25 0x3000 /* INTA1 */
				MX6UL_PAD_LCD_DATA19__GPIO3_IO24 0x3000 /* INTB1 */
				MX6UL_PAD_LCD_DATA21__GPIO3_IO26 0x3000 /* INTC1 */
				MX6UL_PAD_LCD_DATA18__GPIO3_IO23 0x3000 /* INTD1 */

				MX6UL_PAD_LCD_DATA17__GPIO3_IO22 0x3000 /* INTA2 */
				MX6UL_PAD_LCD_DATA16__GPIO3_IO21 0x3000 /* INTB2 */
				MX6UL_PAD_LCD_DATA22__GPIO3_IO27 0x3000 /* INTC2 */
				MX6UL_PAD_LCD_DATA23__GPIO3_IO28 0x3000 /* INTD2 */

				MX6UL_PAD_GPIO1_IO05__GPIO1_IO05 0x10b1 /* UART_RESET */
			>;

上面的是设备树的引脚属性设置部分。

&weim {
	pinctrl-names = "default";
  	pinctrl-0 = <&pinctrl_COM1_8>;
	#address-cells = <2>;
	#size-cells = <1>;
	fsl,weim-cs-gpr = <&gpr>;
	ranges = <0 0 0x50000000 0x08000000>;
	status = "okay";

	st16c554@0,8 {
		compatible = "st16c554";
		reg = <0 0 0x08000000>;
		fsl,weim-cs-timing = <0x00640081 0x00000001 0x1c022000
					0x0000c000 0x1404a38e 0x00000000>;
	};
};

上面的是设备树中关于eim接口的相关设置。这里主要说明一下fsl,weim-cs-timing属性,该属性就是设置eim通道的寄存器值的,imx6ull的eim接口根据cs计算一共4个通道,我这里通过ranges 设置只使用了通道0,fsl,weim-cs-timing的六个值就会设置为通道0的6个寄存器对应的值。设备树中的其他属性对应的意义可以参考linux的文档说明Documentation\devicetree\bindings\bus\imx-weim.txt,寄存器的具体含义参考imx6ull的参考文档即可。

内核配置

图2. 配置内核打开EIM驱动

开启8250串口驱动,使用的端口数量的话根据自己的使用你情况进行配置即可,我这里用到8个所以设置8个。还有我发现一个奇怪的现象就是设置0个端口register at runtime时内核好像就会启动失败,控制台什么都不输出了,不知道是什么情况,总之设置时下面那个数字不要设置为0。

驱动部分

#include <linux/module.h>
#include <linux/init.h>
#include <linux/serial_8250.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>

#include <asm/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>

#define ST16C554_REG_SIZE  0x08

#define IMX_GPIO_NR(bank, nr)		(((bank) - 1) * 32 + (nr))

#define CS0_BASE    0x50000000

#define CSA1_BASE    CS0_BASE
#define CSB1_BASE    (CS0_BASE | 1 << 3)   //A3 A4 A5作为8选1模拟开关选择线
#define CSC1_BASE    (CS0_BASE | 2 << 3)   
#define CSD1_BASE    (CS0_BASE | 3 << 3)   
#define CSA2_BASE    (CS0_BASE | 4 << 3)   
#define CSB2_BASE    (CS0_BASE | 5 << 3)   
#define CSC2_BASE    (CS0_BASE | 6 << 3)  
#define CSD2_BASE    (CS0_BASE | 7 << 3)

/* 寄存器地址偏移定义 */
#define PORT_REG_RHR    0
#define PORT_REG_THR    0
#define PORT_REG_IER    1
#define PORT_REG_ISR    2
#define PORT_REG_FCR    2
#define PORT_REG_LCR    3
#define PORT_REG_MCR    4
#define PORT_REG_LSR    5
#define PORT_REG_MSR    6
#define PORT_REG_SPR    7
#define PORT_REG_DLL    0  //波特率设置寄存器
#define PORT_REG_DLM    1

#define PORT(_base,_irq)				\
	{						\
        .type       = PORT_16550A,  \
        .iobase     = _base,        \
        .mapbase    = _base,         \
		.irq		= _irq,			\
        .irqflags   = IRQF_TRIGGER_RISING,  \
		.uartclk	= 1843200,		\
		.iotype		= UPIO_MEM,		\
        .regshift   = 0,                \
		.flags		= UPF_BOOT_AUTOCONF | UPF_IOREMAP,	\
	}

#define ST16C554_INTA1 gpio_to_irq(IMX_GPIO_NR(3,25))
#define ST16C554_INTB1 gpio_to_irq(IMX_GPIO_NR(3,24))
#define ST16C554_INTC1 gpio_to_irq(IMX_GPIO_NR(3,26))
#define ST16C554_INTD1 gpio_to_irq(IMX_GPIO_NR(3,23))
#define ST16C554_INTA2 gpio_to_irq(IMX_GPIO_NR(3,22))
#define ST16C554_INTB2 gpio_to_irq(IMX_GPIO_NR(3,21))
#define ST16C554_INTC2 gpio_to_irq(IMX_GPIO_NR(3,27))
#define ST16C554_INTD2 gpio_to_irq(IMX_GPIO_NR(3,28))


static struct plat_serial8250_port exar_data[] = {
	PORT(CSA1_BASE, 0),
	PORT(CSB1_BASE, 0),
	PORT(CSC1_BASE, 0),
	PORT(CSD1_BASE, 0),
    PORT(CSA2_BASE, 0),
	PORT(CSB2_BASE, 0),
	PORT(CSC2_BASE, 0),
	PORT(CSD2_BASE, 0),
	{ },
};

static struct platform_device exar_device = {
	.name			= "serial8250",
	// .id			= PLAT8250_DEV_EXAR_ST16C554,
	.dev			= {
		.platform_data	= exar_data,
	},
};

int fsl8250_handle_irq(struct uart_port *port)
{
	unsigned char lsr, orig_lsr;
	unsigned long flags;
	unsigned int iir;
	struct uart_8250_port *up = up_to_u8250p(port);

    // printk("irq = %d ", port->irq);

	spin_lock_irqsave(&up->port.lock, flags); 
	iir = port->serial_in(port, PORT_REG_ISR);
	if (iir & 0x01) {
		spin_unlock_irqrestore(&up->port.lock, flags);
		return 0;
	}
    /* This is the WAR; if last event was BRK, then read and return */
	if (unlikely(up->lsr_saved_flags & 0x10)) {
		up->lsr_saved_flags &= ~0x10;
		port->serial_in(port, PORT_REG_RHR);
		spin_unlock_irqrestore(&up->port.lock, flags);
		return 1;
	}

	lsr = orig_lsr = up->port.serial_in(&up->port, PORT_REG_LSR);
	if (lsr & 0x01)
		lsr = serial8250_rx_chars(up, lsr);

	serial8250_modem_status(up);

	if (lsr & 0x20)
		serial8250_tx_chars(up);

	up->lsr_saved_flags = orig_lsr;
	spin_unlock_irqrestore(&up->port.lock, flags);
	return 1;
}

static int __init my_serial_init(void)
{
    int ret;
    int rst_io;

    exar_data[0].irq = ST16C554_INTA1;
    exar_data[1].irq = ST16C554_INTB1;
    exar_data[2].irq = ST16C554_INTC1;
    exar_data[3].irq = ST16C554_INTD1;
    exar_data[4].irq = ST16C554_INTA2;
    exar_data[5].irq = ST16C554_INTB2;
    exar_data[6].irq = ST16C554_INTC2;
    exar_data[7].irq = ST16C554_INTD2;

    exar_data[0].handle_irq = fsl8250_handle_irq;
    exar_data[1].handle_irq = fsl8250_handle_irq;
    exar_data[2].handle_irq = fsl8250_handle_irq;
    exar_data[3].handle_irq = fsl8250_handle_irq;
    exar_data[4].handle_irq = fsl8250_handle_irq;
    exar_data[5].handle_irq = fsl8250_handle_irq;
    exar_data[6].handle_irq = fsl8250_handle_irq;
    exar_data[7].handle_irq = fsl8250_handle_irq;

    rst_io = IMX_GPIO_NR(1,5); //获得io编号
    ret = gpio_direction_output(rst_io, 0); //复位
    ret = gpio_direction_output(rst_io, 1);
    if (ret < 0) {
            printk("%s rst_io ouput error\n", __func__);
    }
    udelay(10); //延时10us
    gpio_set_value(rst_io, 0);
    udelay(1000); //延时1000us

	return platform_device_register(&exar_device);
}

static void __exit my_serial_exit(void)
{
    printk("unregister st16c554_driver\n");
	platform_device_unregister(&exar_device);
}

module_init(my_serial_init);
module_exit(my_serial_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ljf");

PORT里面的参数我捡几个主要的进行说明一下,mapbase和iobase都是实际的物理地址,这个和你的EIM总线配置以及硬件连接有关。irq就是中断号,uartclk就是外接的时钟频率我接的是1.8432M的时钟就设置为1843200,串口的波特率最大就是clk/16,iotype设置为UPIO_MEM表示8位的内存地址,regshift表示地址偏移由于我是A0对应的ST16C554的A0所以设置为0表示没有偏移,UPF_IOREMAP表示地址需要映射到虚拟地址进行访问。
然后在init函数中申请中断号,设置handle_irq,进行芯片复位,最后将其注册成platform设备。然后就会和8250驱动匹配上进行8250驱动的一套流程,这里就不分析了。handle_irq函数可以参考8250_fsl.c和8250_core.c文件,里面都有相应的示例。
虽然驱动写完了但是还是不能直接使用,使用之前还需要开启EIM的时钟,虽然设备树里面设置了status的属性为okay但是eim的时钟却没有被开启,而且我试了直接在上面驱动中的init函数里设置CCM_CCGR6寄存器的bit10-11位,但是没用,只能在内核初始化完成之后再开启对应的时钟。于是我就又写了一个开启时钟的驱动。


static int __init st16c554_cmd_init(void)
{
    void __iomem *ccm_ccgr6 = ioremap(CCM_BASE_ADDR + 0x80, 4);
    unsigned int reg;

    /* CLKCTL_CCGR6: Set emi_slow_clock to be on in all modes */
    reg = readl(ccm_ccgr6);
    reg |= 0x00000C00;
    writel(reg, ccm_ccgr6);
    reg = readl(ccm_ccgr6);
    iounmap(ccm_ccgr6);
    
    return 0;
}

static void __exit st16c554_cmd_exit(void)
{
}

module_init(st16c554_cmd_init);
module_exit(st16c554_cmd_exit);

MODULE_LICENSE("GPL");

 驱动里面直接就是映射寄存器地址然后设置值,其他的什么都不做。这个驱动会在根文件系统加载之后被调用自动开启eim总线的时钟。

三、验证

 将驱动编译进内核里面,用新的内核进行启动如果驱动加载成功之后内核启动就会打印出相应的信息来,如上图所示。查看/dev目录可以看到已经有ttyS0-7这几个设备节点了。

anyShare分享到:
本站的文章和资源来自互联网或者站长的原创,按照 CC BY -NC -SA 3.0 CN协议发布和共享,转载或引用本站文章应遵循相同协议。如果有侵犯版权的资源请尽快联系站长,我们会在24h内删除有争议的资源。欢迎大家多多交流,期待共同学习进步。
stormwind