entity UART is
port(
A:in std_logic_vector(0 to 2);
UART_A:out std_logic_vector(0 to 2);
D:inout std_logic_vector(0 to 7);
UART_D:inout std_logic_vector(0 to 7);
GPIO_CS4:in std_logic;
CS4_B:in std_logic;
OE_B:in std_logic;
RW_B:in std_logic;
UART_CSA_B:out std_logic;
UART_CSB_B:out std_logic;
UART_OE_B:out std_logic;
UART_RW_B:out std_logic
);
end;
architecture control of UART is
begin
-- Chip Select Outputs
UART_CSA_B <= '0' when (CS4_B = '0' and GPIO_CS4 = '0') else '1';
UART_CSB_B <= '0' when (CS4_B = '0' and GPIO_CS4 = '1') else '1';
-- level shift
UART_A <=A;
UART_OE_B <= '0' when (OE_B = '0' and (UART_CSA = '0' or UART_CSB = '0')) else '1';
UART_RW_B <= '0' when (RW_B = '0' and (UART_CSA = '0' or UART_CSB = '0')) else '1';
process(D,UART_D,UART_OE_B,UART_RW_B,UART_OE_B)
begin
if (UART_OE_B = '0') and (UART_RW_B = '1') then
D <= UART_D;
elsif (UART_OE_B = '1') and (UART_RW_B = '0') then
UART_D <= D;
else
D <="ZZZZZZZZ";
end if;
end process;
end control;
软件的设计
sc16c652是一款集成了2路标准异步串行收发器的串口扩展芯片,也就是通常所说的8250兼容串口。它的操作方法和寄存器功能与8250完全兼容,因此我们可以用基于linux内经典的8250驱动来驱动sc16c652,只需要根据硬件的设计,稍微修改一下标准的8250驱动,下面我们就在8250驱动的基础上根据硬件的设计来实现sc16c652的驱动程序
首先来看看硬件上是如何来实现双串口的收发流程的。根据cpld程序的逻辑,当驱动访问 0xD400_0000----0xD5FF_FFFF 这32M地址空间的时候,CS4_B引脚会自动变为低电平。然后可以通过控制GPIO_CS4来选择串口号,当GPIO_CS4为高电平的时候,CPLD的输出为低的时候UART_CSB_B为低,选中串口A,当GPIO_CS4为低电平的时候,CPLD输出UART_CSA_B为低,选择串口B,这样就实现了串口号的逻辑选择,然后数据接收采用中断方式,我们硬件设计如下
MX27_PIN_SSI1_FS: gpio 输出 做串口芯片的复位引脚,下降沿复位100us
MX27_PIN_SSI1_TXDAT: gpio 输入 禁止上拉 上升沿触发 串口1的接收中断引脚
MX27_PIN_SSI1_RXDAT: gpio 输入 禁止上拉 上升沿触发 串口2的接收中断引脚
由以下两个引脚来决定选中那个串口号
MX27_PIN_SSI1_CLK : gpio 输出 也就是GPIO_CS4 可以用来选择串口号 [0---> 串口1 1--> 串口2]
CS4_B: 输出,当我们0xD400_0000----0xD5FF_FFFF这段地址的时候,始终保持为低电平
硬件设计清楚后,然后,我们来设计软件部分,首先需要在板级初始化文件 arch/arm/mach-mx27/mx27mdk27v0.c中,做如下初始化
1.根据硬件设计来分配串口的资源数据
/*!
* The serial port definition structure. The fields contain:
* {UART, CLK, PORT, IRQ, FLAGS}
*/
static struct plat_serial8250_port serial_platform_data[] = {
{
.membase = (void __iomem *)(CS4_BASE_ADDR_VIRT), //驱动中必须用内核虚拟地址来访问寄存器
.mapbase = (unsigned long)(CS4_BASE_ADDR), //寄存器实际的物理地址
.irq = IOMUX_TO_IRQ(MX27_PIN_SSI1_TXDAT), //获得该GPIO引脚所对应的中断编号,mx27的每个GPIO口都可以配置为中断模式
.uartclk = 14745600, //根据实际外接的晶振来设置
.regshift = 1, //根据硬件设计和WEIM口数据总线位数来决定
.iotype = UPIO_MEM, //按字节方式读写寄存器
.flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST,
/*.pm = serial_platform_pm, */
},
{
.membase = (void __iomem *)(CS4_BASE_ADDR_VIRT + 0x10),
.mapbase = (unsigned long)(CS4_BASE_ADDR + 0x10),
.irq = IOMUX_TO_IRQ(MX27_PIN_SSI1_RXDAT),
.uartclk = 14745600,
.regshift = 1,
.iotype = UPIO_MEM,
.flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST,
/*.pm = serial_platform_pm, */
},
{},
};
/*!
* REVISIT: document me
*/
static struct platform_device serial_device = {
.name = "serial8250",
.id = 0,
.dev = {
.platform_data = serial_platform_data,
},
};
2.初始化相关各个引脚的功能和初始化状态,复位串口芯片,添加和注册一个平台驱动设备
static int __init mxc_init_extuart(void)
{
int ret = 0;
//将MX27_PIN_SSI1_FS 做GPIO用,做串口复位引脚
ret = gpio_request_mux(MX27_PIN_SSI1_FS, GPIO_MUX_GPIO);
if (ret) {
printk(KERN_ERR "Request MUX SSI1_FS failed.\n");
}
//设置为输出
mxc_set_gpio_direction(MX27_PIN_SSI1_FS, 0);
//给串口芯片一个复位信号,重新复位芯片
mxc_set_gpio_dataout(MX27_PIN_SSI1_FS, 1);
udelay(1000);
mxc_set_gpio_dataout(MX27_PIN_SSI1_FS, 0);
//将 MX27_PIN_SSI1_TXDAT 做串口1的接收中断引脚
ret = gpio_request_mux(MX27_PIN_SSI1_TXDAT, GPIO_MUX_GPIO);
if (ret) {
printk(KERN_ERR "Request MUX SSI1_TXDAT failed.\n");
}
//设置为输入
mxc_set_gpio_direction(MX27_PIN_SSI1_TXDAT, 1);
//禁止上拉
gpio_set_puen(MX27_PIN_SSI1_TXDAT, 0);
set_irq_type(IOMUX_TO_IRQ(MX27_PIN_SSI1_TXDAT), IRQT_RISING);
//将 MX27_PIN_SSI1_CLK 做片选输出引脚也就是原理图上的GPIO_CS4
ret = gpio_request_mux(MX27_PIN_SSI1_CLK, GPIO_MUX_GPIO);
if (ret) {
printk(KERN_ERR "Request MUX SSI1_CLK failed.\n");
}
//设置为输出
mxc_set_gpio_direction(MX27_PIN_SSI1_CLK, 0);
mxc_set_gpio_dataout(MX27_PIN_SSI1_CLK, 1);
//将 MX27_PIN_SSI1_RXDAT 做串口2的接收中断引脚
ret = gpio_request_mux(MX27_PIN_SSI1_RXDAT, GPIO_MUX_GPIO);
if (ret) {
printk(KERN_ERR "Request MUX SSI1_RXDAT failed.\n");
}
//做输入用
mxc_set_gpio_direction(MX27_PIN_SSI1_RXDAT, 1);
gpio_set_puen(MX27_PIN_SSI1_RXDAT, 0);
set_irq_type(IOMUX_TO_IRQ(MX27_PIN_SSI1_RXDAT), IRQT_RISING);
//注册平台设备
ret = platform_device_register(&serial_device);
if (ret < 0) {
pr_info("Error: register external uart failure\n");
}
return ret;
}
}
3.编写驱动程序
drivers/serial/8250.c就是8250兼容芯片的驱动程序,只需要根据硬件特性,修改该文件即可
1.在读取和设置寄存器的时候,根据串口编号来控制GPIO_CS4的输出,根据CPLD中预设的逻辑,当GPIO_CS4输出高电平的时候,片选选中串口1,输出低电平的时候,片选选中串口2,所以我们做出如下修改
static unsigned int serial_in(struct uart_8250_port *up, int offset)
{
unsigned int tmp;
unsigned long flags;
unsigned int ret;
int locked = spin_trylock_irqsave(&up-> port.lock, flags);
offset = map_8250_in_reg(up, offset) < < up-> port.regshift;
#if 1
if (up-> port.line <= 1) {
mxc_set_gpio_dataout(MX27_PIN_SSI1_CLK, (up-> port.line == 0) ? 0 : 1);
} else {
printk(KERN_ERR "sc16c652 has just 2 port but line index is %d\n", up-> port.line);
if (locked) {
spin_unlock_irqrestore(&up-> port.lock, flags);
}
return -EINVAL;
}
#endif
}
static void serial_out(struct uart_8250_port *up, int offset, int value)
{
/* Save the offset before it's remapped */
int save_offset = offset;
unsigned long flags;
int locked = spin_trylock_irqsave(&up-> port.lock, flags);
offset = map_8250_out_reg(up, offset) < < up-> port.regshift;
#if 1
if (up-> port.line <= 1) {
mxc_set_gpio_dataout(MX27_PIN_SSI1_CLK, up-> port.line);
} else {
printk(KERN_ERR "sc16c652 has just 2 port but line index is %d\n", up-> port.line);
if (locked) {
spin_unlock_irqrestore(&up-> port.lock, flags);
}
return;
}
#endif
}
在模块初始化函数中,最好在复位一下芯片
static int __init serial8250_init(void)
{
mxc_set_gpio_dataout(MX27_PIN_SSI1_CLK, 0);
mxc_set_gpio_dataout(MX27_PIN_SSI1_FS, 1);
udelay(100);
mxc_set_gpio_dataout(MX27_PIN_SSI1_FS, 0);
}
这样就完成了sc16c552的串口驱动,驱动加载成功后的设备文件名是 /dev/ttyS0 和 /dev/ttyS1,也可以通过修改udev的规则文件来修改设备文件名
驱动加载成功后,打印的调试信息如下
Serial: 8250/16550 driver $Revision: 1.1.1.1 $ 2 ports, IRQ sharing disabled
serial8250.0: ttyS0 at MMIO 0xd4000000 (irq = 150) is a ST16650V2
serial8250.0: ttyS1 at MMIO 0xd4000010 (irq = 149) is a ST16650V2
驱动侦测出来的设备类型是 ST16650V2,这个芯片和SC16C552是一样的
关于如何来调试 :
进入控制台后,使用cat /proc/tty/driver/serial("serial"是8250默认的平台驱动名),看看IO地址,中断是否设置正常,如果有错就继续改改相关位置的代码即可。