如何修改tiny4412 驱动串口驱动.让他支持485

作者:彭东林
开发板:tiny4412ADK+S700 4GB Flash
主机:Wind7 64位
虚拟机:Vmware+Ubuntu12_04
u-boot:U-Boot 2010.12
Linux内核版本:linux-3.0.31
Android版本:android-4.1.2
tiny4412默认是从uart0来输出和读取信息的,而tiny4412上留了两个串口,分别对应的是uart0和uart3,下面我们修改配置,使控制终端从uart0变成uart3
修改u-boot配置
在前面分析u-boot串口驱动的时候发现,在tiny4412.h中定义了
#define CONFIG_SERIAL0& 1
然后再初始化串口的时候会根据这个宏找到uart0的控制器基地址,然后配置其波特率等等,并且u-boot的printf最终也会通过向uart0的发送数据寄存器写数据来实现数据的发送,综上,对于u-boot,我们只需要修改tiny4412.h,将
#define CONFIG_SERIAL0& 1
#define CONFIG_SERIAL3& 1
修改u-boot给内核传递的参数bootargs
原来的bootargs是:
console=ttySAC0, androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70
由于改成了uart3,我们需要把bootargs改成:
console=ttySAC3, androidboot.console=ttySAC3 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70
修改内核配置
这里主要是内核在自解压的时候打印信息,在这个时候Linux内核还尚未启动,还是直接操作底层的硬件寄存器,并且不再初始化串口,因为之前u-boot已经将uart3初始化了,这里只需要告诉Linux内核的自解压程序使用uart3输出解压信息即可,通过分析之前的Linux下的串口驱动发现,需要将CONFIG_S3C_LOWLEVEL_UART_PORT改成3即可,测试发现,如果不修改这里,内核也无法正常启动,因为在执行putstr时会卡死,此时可以将解压时putstr注释掉即可。
此外,如果打开了Kernel low-level debugging functions,那么还需要如下配置内核:
否则,由于在调用printk的时候,会调用printascii,而printascii是用汇编实现的,他怎么知道应该从哪个串口输出呢(bootargs传的参数tty对其无效)?通过分析arch/arm/mach-exynos/include/mach/debug-macro.S 可以发现,他通过判断宏CONFIG_DEBUG_S3C_UART来知道从哪个串口输出。而上图中S3C UART to use for low-level debug 就是设置宏CONFIG_DEBUG_S3C_UART的。
阅读(...) 评论()作者:彭东林
开发板:tiny4412ADK+S700 4GB Flash
主机:Wind7 64位
虚拟机:Vmware+Ubuntu12_04
u-boot:U-Boot 2010.12
Linux内核版本:linux-3.0.31
Android版本:android-4.1.2
在上面我们知道了/dev/ttySACx是如何生成的,此外还可以看到在/dev下还有设备结点/dev/console,以及/dev/tty等设备结点。
可以看到向/dev/ttySAC0、/dev/console和/dev/tty输入字符,然后这些字符会输出到串口终端上:
但是如果使用adb shell登陆后,现象不同:
其中,左边的窗口是在adb shell下,右边的窗口是串口终端的显示,可以看到如果在adb shell下向 /dev/ttySAC0和 /dev/console下写入字符的话,这个字符并没有在adb shell终端下显示,相反却在串口终端中显示出来,当在adb shell终端下向/dev/tty下写入字符时,就在adb shell终端下显示出来了,并没有影响到串口终端的显示。
上面的这些现象背后的原因是什么呢?下面我们开始分析内核源码来解释。
首先需要知道这些设备结点是怎么生成的:
late_initcall(chr_dev_init);
&&&& ---- tty_init()&& (drivers/tty/tty_io.c)
int __init tty_init(void)
cdev_init(&tty_cdev, &tty_fops);
if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") & 0)
panic("Couldn't register /dev/tty driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
cdev_init(&console_cdev, &console_fops);
if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") & 0)
panic("Couldn't register /dev/console driver\n");
consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,
"console");
if (IS_ERR(consdev))
consdev = NULL;
WARN_ON(device_create_file(consdev, &dev_attr_active) & 0);
在这里会在/dev/下生成console和tty两个设备结点,他们对应的fops分别是tty_fops和console_fops。
在上面分析的tty_register_device函数会生成/dev/ttySACx,它对应的fops在函数tty_register_driver中设置为了tty_fops。所以这里的关键是分析tty_fops和console_fops是如何实现的。
对比发现,console_fops和tty_fops是一样的:
static const struct file_operations tty_fops = {
= no_llseek,
= tty_read,
= tty_write,
= tty_poll,
.unlocked_ioctl
= tty_ioctl,
.compat_ioctl
= tty_compat_ioctl,
= tty_open,
= tty_release,
= tty_fasync,
static const struct file_operations console_fops = {
= no_llseek,
= tty_read,
= redirected_tty_write,
= tty_poll,
.unlocked_ioctl
= tty_ioctl,
.compat_ioctl
= tty_compat_ioctl,
= tty_open,
= tty_release,
= tty_fasync,
执行echo &peng& & /dev/ttySAC0的时候,会先调用tty_open然后调用tty_write,最后调用tty_release。
阅读(...) 评论()http://blog.csdn.net/morixinguan/article/details/上节,我们明白了proc文件系统的作用,接下来我们在友善之臂已经写好的led驱动的基础上,在proc目录下创建一个文件夹,然后加入led驱动的版本信息读取。我们在init函数的最后加入...
这一节,我们将从零开始写tiny4412自带的触摸屏驱动法条ft5x06,写这节博客之前,先了解下需要什么知识:1、i2c驱动相关的知识2、输入子系统3、中断4、工作队列关于i2c驱动相关的知识,在后期的博文里会专门写几篇博文来进行总结,这里就不再说i2c相关的知识,我们先知道怎么用就行了。首先,...
要想用uboot启动内核,我推荐一种方法,用dnw下载内核到开发板上,然后用uboot命令启动:首先我在网上随便下了一个dnw工具,经过移植修改后,代码如下:/*
YYX---&for tiny4412 dnw
前面我们学习了鼠标是如何如何通过应用程序来读取事件和坐标值的,后面也写了一个简单的input系统的按键驱动程序。博文如下,讲的内容非常清楚,给小白来入手当然是非常容易的:http://blog.csdn.net/morixinguan/article/details/这节,我们来学...
在上一节里,我们用一个应用程序实现了鼠标的控制,并控制鼠标用相对位移不断的画一个正方形,感觉非常有意思,这一节,我们将通过一个简单按键实例来真正的实现一个input设备驱动程序。http://blog.csdn.net/morixinguan/article/details/在写I...
这节,我们来说下input子系统,什么是input子系统?input子系统就是输入子系统。
输入子系统是 Linux内核用于管理各种输入设备 (键盘,鼠标,遥控杆,书写板等等 )的部分,用户通过输入子系统进行内核,命令行,图形接口之间的交换。输入子系统在内核里实现,因为设备经常要通过特定的...
上节,我们讲到如何来实现tasklet小任务机制http://blog.csdn.net/morixinguan/article/details/这节,我们来实现一下中断下半部的工作队列:在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API。需要包含的头文件:#in...
上节:http://blog.csdn.net/morixinguan/article/details/在上一节博文中,教会了大家如何来写一个Linux设备的中断程序,实现也非常简单,我们来回顾一下具体的操作流程,只要遵循以下几个步骤即可实现最简单的中断处理程序:使用中断相关的AP...
今天,我们要来实现一个基于tiny4412开发板上的最简本的按键中断驱动程序,那么,写这个程序之前,我们先来了解下Linux中断的基本知识。在Linux内核中,每一个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有现在存在的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相...
这个专题我们来说下Linux中的定时器。在Linux内核中,有这样的一个定时器,叫做内核定时器,内核定时器用于控制某个函数,也就是定时器将要处理的函数在未来的某个特定的时间内执行。内核定时器注册的处理函数只执行一次,即不是循环执行的。如果对延迟的精度要求不高的话,最简单的实现方法如下---忙等待:...
上一节,我教大家实现了一个最简单的MISC设备驱动,那么这节,我们将用一个实例来驱动蜂鸣器,这里为了方便,我就不再写应用程序进行测试,直接在驱动里调用open函数,这个程序是在Android系统里跑起来,后面我会教大家如何在Android下写应用测试程序。我们参考以前写的蜂鸣器驱动程序,将它移植到...
关于如何来写一个misc设备,在前面有篇文章已经介绍了大致的流程,现在就让我们来实现一个最简单的misc设备驱动。http://blog.csdn.net/morixinguan/article/details/关于前面的字符设备有以下四篇文章,可以做参考:http://blog....
前面,我们基本已经学会怎么去编写一个简单的字符设备驱动程序了,这节,我们来看看友善之臂中提供的led驱动。参考之前写的文章,我们已经知道LED的GPIO口,和一些配置信息:http://blog.csdn.net/morixinguan/article/details/在友善之臂提...
这一节,我们再来看看新的知识点,这一次,我们将进一步完善这个字符设备的驱动程序。首先,将上一节的代码做下修改:#include
//创建一个字符设备
struct char_...
上节,我们讲解了如何写第一个linux字符设备驱动程序,这节,我们将代码做一下修改。如下:#include
dev_t dev_
static int __init ...
从这篇博文开始,我将开始手把手教会大家写linux设备驱动程序这是开篇,如何来写第一个字符设备驱动程序。首先,写一个最简单的字符设备驱动程序需要什么?或者说我们需要了解什么?1、每一个字符设备至少需要有一个设备号2、设备号 = 主设备号 + 次设备号3、同一类设备的主设备号一般是相同的,但不是绝对...
Kconfig怎么写的在上节就已经教大家写了。这节我们来写写增强版的,因为Kconfig有太多太多可以配置的,所以这里我就不给出图片演示了,请参考上节的文章,再来看这节大家就会大彻大悟,然后自己去尝试吧。基本上最常见的配置就是以下的这些。文章链接如下:http://blog.csdn.net/mo...
关于系统调用,相信学习过操作系统的同学应该都不陌生。那么,什么是系统调用?百度的权威解释如下:点击打开链接 由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。
今天,我就来教大家写写最简单的Kconfig,什么是Kconfig?简单的说吧,它是一个Linux驱动的配置文件,配置什么东西呢?在linux设备驱动中,我们写的驱动程序最终会加入到内核的Makefile中进行编译,那么具体要编译成什么呢?要编译成.o文件还是.ko文件,还是不编译?由那个命令来进...
1、arm-none-eabi-g++:是编译ARM裸板用的编译器,不依赖于操作系统。2、-Xlinker -T &../LF3Kmonitor.ld& -Xlinker -Map=&Bogota_ICT_V.map&-ram-hosted.ld -mc
文章:130篇
阅读:313715
文章:45篇
阅读:105866
文章:43篇
阅读:94446
文章:23篇
阅读:19199
文章:13篇
阅读:31253
文章:23篇
阅读:48087
文章:34篇
阅读:36897
文章:16篇
阅读:16882
文章:35篇
阅读:60619tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)
时间: 16:44:18
&&&& 阅读:259
&&&& 评论:
&&&& 收藏:0
标签:作者:彭东林
开发板:tiny4412ADK+S700 4GB Flash
主机:Wind7 64位
虚拟机:Vmware+Ubuntu12_04
u-boot:U-Boot 2010.12
Linux内核版本:linux-3.0.31
Android版本:android-4.1.2
在arch/arm/mach-exynos/mach-tiny4412.c中:
MACHINE_START(TINY4412, "TINY4412")
.boot_params
= S5P_PA_SDRAM + 0x100,
= exynos4_init_irq,
= smdk4x12_map_io,
.init_machine
= smdk4x12_machine_init,
= &exynos4_timer,
= &exynos4_reserve,
MACHINE_END
在文件arch/arm/kernel/setup.c中:
static int __init customize_machine(void)
if (machine_desc-&init_machine)
machine_desc-&init_machine();
arch_initcall(customize_machine);
&在文件arch/arm/plat-samsung/init.c中:
static int __init s3c_arch_init(void)
ret = (cpu-&init)();
ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
arch_initcall(s3c_arch_init);
这几个函数跟uart有关,我们先看一下内核启动的时候是如何调用这个函数:
start_kernel
(init/main.c)
--- setup_arch
(arch/arm/kernel/setup.c)
--- paging_init
(arch/arm/mm/mmu-cma.c)
--- devicemaps_init (arch/arm/mm/mmu-cma.c)
--- mdesc-&map_io() ----- smdk4x12_map_io
--- rest_init
---kernel_init
--- do_pre_smp_initcalls (init/main.c)
--- do_basic_setup
--- do_initcalls
static void __init do_pre_smp_initcalls(void)
initcall_t *
for (fn = __initcall_ fn & __early_initcall_ fn++)
do_one_initcall(*fn);
static void __init do_initcalls(void)
initcall_t *
for (fn = __early_initcall_ fn & __initcall_ fn++)
do_one_initcall(*fn);
// 在这里会调用customize_machine和s3c_arch_init
在文件include/linux/init.h中:
#define __define_initcall(level,fn,id)
static initcall_t __initcall_##fn##id __used
__attribute__((__section__(".initcall" level ".init"))) = fn
#define arch_initcall(fn)
__define_initcall("3",fn,3)
对于arch_initcall(customize_machine)展开后就是:
static initcall_t __initcall_ customize_machine3 __used
__attribute__((__section__(".initcall3.init"))) = customize_machine
在arch/arm/kernel/vmlinux.lds中:
__initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcallbresume.init) *(.initcallresume.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
通过上面的分析,大致知道这几个函数的调用过程了。
下面继续分析:
static void __init smdk4x12_map_io(void)
clk_xusbxti.rate = ;
// 宏S5P_VA_CHIPID的值是S3C_ADDR(0x)
s5p_init_io(NULL, 0, S5P_VA_CHIPID);
// 获取cpu id
s3c24xx_init_clocks();
// 初始化时钟资源
s3c24xx_init_uarts(smdk4x12_uartcfgs, ARRAY_SIZE(smdk4x12_uartcfgs));
初始化uart,此时并没有进行注册,只是填充了一些结构体,提供给注册时用,数组smdk4x12_uartcfgs中是关于每一个uart控制器的寄存器默认参数,如:
static struct s3c2410_uartcfg smdk4x12_uartcfgs[] __initdata = {
= SMDK4X12_UCON_DEFAULT,
= SMDK4X12_ULCON_DEFAULT,
= SMDK4X12_UFCON_DEFAULT,
exynos4_reserve_mem();
// 预留内存
下面我们一一分析上面的每个函数
&s5p_init_io 函数
void __init s5p_init_io(struct map_desc *mach_desc,
int size, void __iomem *cpuid_addr)
/* initialize the io descriptors we need for initialization */
数组s5p_iodesc中记录了一些物理地址和虚拟地址的对应关系,如:
static struct map_desc s5p_iodesc[] __initdata = {
= (unsigned long)S5P_VA_CHIPID,
= __phys_to_pfn(S5P_PA_CHIPID),
= MT_DEVICE,
iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc)); // 静态映射内存资源
if (mach_desc)
iotable_init(mach_desc, size);
/* detect cpu id and rev.
这里cpuid_addr的值是S5P_VA_CHIPID,从上面的代码可以知道S5P_VA_CHIPID对应的物理地址是S5P_PA_CHIPID,即0x,这个是exynos4412的PRO_ID寄存器,通过在u-boot读取到寄存器0x的值,注意:由于在u-boot中开启了mmu,需要判断这个物理地址0x对应的虚拟机地址是多少,还好,我们的u-boot中将物理地址0x -- 0x1FFF_FFFF 映射到了0x -- 0x1FFF_FFFF,所以我们直接使用命令 md 0x 即可,我试了一下,结果如下:
TINY4412 # md 0xx4
. A............
s5p_init_cpu(cpuid_addr);
// 读取cpu_id
下面是函数s5p_init_cpu的实现
void __init s5p_init_cpu(void __iomem *cpuid_addr)
samsung_cpu_id = __raw_readl(cpuid_addr);
samsung_cpu_rev = samsung_cpu_id & 0xFF;
所以samsung_cpu_id的值是 0xe4412011,samsung_cpu_id的值是0x11
s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));
数组cpu_ids中列出了一些类samsung的soc芯片的信息,关于exynos4412的信息如下:
static struct cpu_table cpu_ids[] __initdata = {
= EXYNOS4412_CPU_ID,
// 0xE4412200
= EXYNOS_CPU_MASK,
// 0xFFFE0000
= exynos4_map_io,
.init_clocks
= exynos4_init_clocks,
.init_uarts
= exynos4_init_uarts,
= exynos4_init,
= name_exynos4412,
// "EXYNOS4412"
下面的函数的目的是从cpu_ids中找到与samsung_cpu_id匹配的数组元素
void __init s3c_init_cpu(unsigned long idcode,
struct cpu_table *cputab, unsigned int cputab_size)
cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);
/* 下面是函数s3c_lookup_cpu的实现,目的就是从上面的cpu_ids中找到了跟刚才读到的cpu id相等的数组元素
static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,
struct cpu_table *tab,
unsigned int count)
for (; count != 0; count--, tab++) {
if ((idcode & tab-&idmask) == (tab-&idcode & tab-&idmask))
return NULL;
通过上面的循环就可以找到exynos4412对应的cpu_ids
cpu-&map_io();
// 调用的是exynos4_map_io
s3c24xx_init_clocks 函数
void __init s3c24xx_init_clocks(int xtal)
(cpu-&init_clocks)(xtal); // 调用的是exynos4_init_clocks,初始化系统时钟资源
s3c24xx_init_uarts
cfg指向了一个数组,no是数组的元素个数,cfg数组中每一项对应了一个uart控制器的寄存器默认配置参数
void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
(cpu-&init_uarts)(cfg, no);
// 调用函数exynos4_init_uarts
在文件arch/arm/plat-s5p/include/plat/exynos4.h中:
#define exynos4_init_uarts exynos_common_init_uarts
void __init exynos_common_init_uarts(struct s3c2410_uartcfg *cfg, int no)
struct s3c2410_uartcfg *tcfg =
// 下面这个循环的目的是为每一个uart指定之中源
for (ucnt = 0; ucnt & ucnt++, tcfg++) {
if (!tcfg-&clocks) {
tcfg-&has_fracval = 1;
tcfg-&clocks = exynos_serial_
tcfg-&clocks_size = ARRAY_SIZE(exynos_serial_clocks);
tcfg-&flags |= NO_NEED_CHECK_CLKSRC;
// s5p_uart_resources数组中存放的是每一个uart控制器的使用的寄存器资源、中断资源
s3c24xx_init_uartdevs("s5pv210-uart", s5p_uart_resources, cfg, no);
下面这个函数的目的是填充每一个uart控制器对应的platform_device
void __init s3c24xx_init_uartdevs(char *name,
struct s3c24xx_uart_resources *res,
struct s3c2410_uartcfg *cfg, int no)
struct platform_device *
struct s3c2410_uartcfg *cfgptr = uart_
struct s3c24xx_uart_resources *
memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);
for (uart = 0; uart & uart++, cfg++, cfgptr++) {
platdev = s3c24xx_uart_src[cfgptr-&hwport]; // 每一个uart对应一个platform_device
resp = res + cfgptr-&
// 找到编号为cfgptr-&hwport的uart对应的res资源地址
s3c24xx_uart_devs[uart] = // 将来会注册其中的每一个platform_device
platdev-&name =
// "s5pv210-uart",将来会执行同名的platform_driver的probe函数
platdev-&resource = resp-& // platform_device对应的资源
platdev-&num_resources = resp-&nr_ // platform_device对应的资源格式
platdev-&dev.platform_data =
// 每个uart控制器的寄存器默认值
nr_uarts =
// uart个数
至此,注册uart设备所需要的条件都已准备好,其实就是填充每一个uart对应的platform_device结构体,下面开始注册:
static int __init s3c_arch_init(void)
ret = (cpu-&init)();
// 调用函数exynos4_init
ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
至此uart对应的platform_device已经注册完成了,下面开始分析uart对应的platform_driver,关于部分代码在drivers/tty/serial/s5pv210.c中:
static struct platform_driver s5p_serial_driver = {
= s5p_serial_probe,
= __devexit_p(s3c24xx_serial_remove),
= "s5pv210-uart",
= THIS_MODULE,
static int __init s5p_serial_init(void)
// s5p_uart_inf 数组中每个元素对应一个uart控制器的fifo配置
return s3c24xx_serial_init(&s5p_serial_driver, *s5p_uart_inf);
static struct s3c24xx_uart_info *s5p_uart_inf[] = {
[0] = &s5p_port_fifo256,
static struct s3c24xx_uart_info s5p_port_fifo256 = {
S5PV210_UART_DEFAULT_INFO(256),
#define S5PV210_UART_DEFAULT_INFO(fifo_size)
= "Samsung S5PV210 UART0",
= PORT_S3C6400,
= fifo_size,
.has_divslot
.rx_fifomask
= S5PV210_UFSTAT_RXMASK,
.rx_fifoshift
= S5PV210_UFSTAT_RXSHIFT,
.rx_fifofull
= S5PV210_UFSTAT_RXFULL,
.tx_fifofull
= S5PV210_UFSTAT_TXFULL,
.tx_fifomask
= S5PV210_UFSTAT_TXMASK,
.tx_fifoshift
= S5PV210_UFSTAT_TXSHIFT,
.get_clksrc
= s5pv210_serial_getsource,
.set_clksrc
= s5pv210_serial_setsource,
.reset_port
= s5pv210_serial_resetport
看来驱动的注册是在s3c24xx_serial_init中完成的:
int s3c24xx_serial_init(struct platform_driver *drv,
struct s3c24xx_uart_info *info)
drv-&suspend = s3c24xx_serial_
// 休眠函数
drv-&resume = s3c24xx_serial_
// 唤醒函数
return platform_driver_register(drv);
然后s5p_serial_probe就会获得执行,可以知道,由于有4个uart,所以这个函数会执行四次。
static int s5p_serial_probe(struct platform_device *pdev)
return s3c24xx_serial_probe(pdev, s5p_uart_inf[pdev-&id]);
在文件drivers/tty/serial/samsung.c中:
static int probe_
int s3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
struct s3c24xx_uart_port *
ourport = &s3c24xx_serial_ports[probe_index];
probe_index++;
ret = s3c24xx_serial_init_port(ourport, info, dev);
uart_add_one_port(&s3c24xx_uart_drv, &ourport-&port);
platform_set_drvdata(dev, &ourport-&port);
ret = device_create_file(&dev-&dev, &dev_attr_clock_source);
ret = s3c24xx_serial_cpufreq_register(ourport);
要继续分析这个函数,就需要先分析drivers/tty/serial/samsung.c,因为uart_add_one_port依赖uart_register_driver。
下面我们分析samsung.c:
static int __init s3c24xx_serial_modinit(void)
ret = uart_register_driver(&s3c24xx_uart_drv);
结构体s3c24xx_uart_drv定义如下:
static struct uart_driver s3c24xx_uart_drv = {
= THIS_MODULE,
.driver_name
= "s3c2410_serial",
= CONFIG_SERIAL_SAMSUNG_UARTS,
= S3C24XX_SERIAL_CONSOLE,
= S3C24XX_SERIAL_NAME,
// &ttySAC&
= S3C24XX_SERIAL_MAJOR,
= S3C24XX_SERIAL_MINOR,
#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console
下面这个结构体会在register_console时调用,将来内核中的printk就会调用这个结构体中的write函数向串口终端中输出信息
static struct console s3c24xx_serial_console = {
= S3C24XX_SERIAL_NAME,
// &ttySAC&
= uart_console_device,
= CON_PRINTBUFFER,
= s3c24xx_serial_console_write,
= s3c24xx_serial_console_setup,
= &s3c24xx_uart_drv,
下面分析uart_register_driver
int uart_register_driver(struct uart_driver *drv)
struct tty_driver *
drv-&state = kzalloc(sizeof(struct uart_state) * drv-&nr, GFP_KERNEL); // 4
normal = alloc_tty_driver(drv-&nr);
// normal-&num = 4;
/* 函数alloc_tty_driver的定义如下:
struct tty_driver *alloc_tty_driver(int lines)
struct tty_driver *
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
if (driver) {
kref_init(&driver-&kref);
driver-&magic = TTY_DRIVER_MAGIC;
driver-&num =
/* later we‘ll move allocation of tables here */
drv-&tty_driver =
normal-&owner
normal-&driver_name
= drv-&driver_
// "s3c2410_serial"
normal-&name
= drv-&dev_
// &ttySAC&
normal-&major
normal-&minor_start
normal-&type
= TTY_DRIVER_TYPE_SERIAL;
normal-&subtype
= SERIAL_TYPE_NORMAL;
normal-&init_termios
= tty_std_
normal-&init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal-&init_termios.c_ispeed = normal-&init_termios.c_ospeed = 9600;
normal-&flags
= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal-&driver_state
tty_set_operations(normal, &uart_ops);
// normal-&ops = &uart_ops
for (i = 0; i & drv-& i++) {
struct uart_state *state = drv-&state +
struct tty_port *port = &state-&
tty_port_init(port);
port-&ops = &uart_port_
port-&close_delay
/* .5 seconds */
port-&closing_wait
/* 30 seconds */
tasklet_init(&state-&tlet, uart_tasklet_action,
(unsigned long)state);
retval = tty_register_driver(normal);
put_tty_driver(normal);
out_kfree:
kfree(drv-&state);
return -ENOMEM;
下面分析函数tty_register_driver:
int tty_register_driver(struct tty_driver *driver)
void **p = NULL;
struct device *d;
if (!(driver-&flags & TTY_DRIVER_DEVPTS_MEM) && driver-&num) {
p = kzalloc(driver-&num * 2 * sizeof(void *), GFP_KERNEL);
if (!driver-&major) {
这里会申请4个设备号,从204开始,被名为&ttySAC&的设备占有
通过命令 cat /proc/devices 可以看到,但是此时还没有在/dev/下生成ttySAC0~4这些设备号
dev = MKDEV(driver-&major, driver-&minor_start);
error = register_chrdev_region(dev, driver-&num, driver-&name);
driver-&ttys = (struct tty_struct **)p;
driver-&termios = (struct ktermios **)(p + driver-&num);
cdev_init(&driver-&cdev, &tty_fops);
driver-&cdev.owner = driver-&
error = cdev_add(&driver-&cdev, dev, driver-&num);
list_add(&driver-&tty_drivers, &tty_drivers);
// tty_drivers 是一个全局双向循环链表
proc_tty_register_driver(driver); // 在/proc下生成相关的的文件
driver-&flags |= TTY_DRIVER_INSTALLED;
下面分析proc_tty_register_driver
void proc_tty_register_driver(struct tty_driver *driver)
struct proc_dir_entry *
// driver_name是&s3c2410_serial&
ent = proc_create_data(driver-&driver_name, 0, proc_tty_driver,
driver-&ops-&proc_fops, driver);
driver-&proc_entry =
其中,proc_tty_driver是其父结点,通过看他的父结点的创建可以知道将来到/proc下的去找名为& s3c2410_serial&的结点
start_kernel
&& ----& proc_root_init&& (fs/proc/root.c)
&&&&&&&&& ---- proc_tty_init& (fs/proc/proc_tty.c)
void __init proc_tty_init(void)
if (!proc_mkdir("tty", NULL))
proc_tty_ldisc = proc_mkdir("tty/ldisc", NULL);
proc_tty_driver = proc_mkdir_mode("tty/driver", S_IRUSR|S_IXUSR, NULL);
proc_create("tty/ldiscs", 0, NULL, &tty_ldiscs_proc_fops);
proc_create("tty/drivers", 0, NULL, &proc_tty_drivers_operations);
从这里可以知道,将来会在/proc/tty/driver下面生成结点s3c2410_serial
:/ # ls /proc/tty/driver/ -l
-r--r--r-- root
0 2014-01-01 19:20 s3c2410_serial
-r--r--r-- root
0 2014-01-01 19:20 serial
-r--r--r-- root
0 2014-01-01 19:20 usbserial
下面我们还必须分析一下tty线路规程的注册,线路规程在tty驱动架构中的位置如下:
线路规划层的目的是:以协议转换的方式,格式化从一个用户或硬件收到的数据, 如PPP协议或蓝牙协议。
下面我们以我们用到的线路规划层的注册为例分析:
start_kernel
&& --- console_init&&& (drivers/tty/tty_io.c)
&&&&&&& --- tty_ldisc_begin& (drivers/tty/tty_ldisc.c)
void tty_ldisc_begin(void)
// 在Linux中可以有多中线路规程,N_TTY只是其中一种,是默认的TTY线路规程
N_TTY是一个宏,为0
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
struct tty_ldisc_ops tty_ldisc_N_TTY = {
= TTY_LDISC_MAGIC,
= "n_tty",
= n_tty_open,
= n_tty_close,
.flush_buffer
= n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
= n_tty_read,
= n_tty_write,
= n_tty_ioctl,
.set_termios
= n_tty_set_termios,
= n_tty_poll,
.receive_buf
= n_tty_receive_buf,
.write_wakeup
= n_tty_write_wakeup
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
unsigned long
int ret = 0;
tty_ldiscs[disc] = new_
// tty_ldiscs是一个全局静态变量数组,记录了当前可以的线路规程
// 在tty_open的时候会设置相应的线路规程
new_ldisc-&num =
new_ldisc-&refcount = 0;
好了,我们继续分析drivers/tty/serial/samsung.c
static int probe_
int s3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
struct s3c24xx_uart_port *
ourport = &s3c24xx_serial_ports[probe_index];
probe_index++;
// info 数组中每个元素对应一个uart控制器的的配置方法
ret = s3c24xx_serial_init_port(ourport, info, dev);
uart_add_one_port(&s3c24xx_uart_drv, &ourport-&port);
platform_set_drvdata(dev, &ourport-&port);
ret = device_create_file(&dev-&dev, &dev_attr_clock_source);
ret = s3c24xx_serial_cpufreq_register(ourport);
下面是结构体s3c24xx_serial_ports 的定义:
static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
= __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
= UPIO_MEM,
= IRQ_S3CUART_RX0,
= &s3c24xx_serial_ops,
= UPF_BOOT_AUTOCONF,
下面分析一下上面的函数:
static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
struct s3c24xx_uart_info *info,
struct platform_device *platdev)
struct uart_port *port = &ourport-&
struct s3c2410_uartcfg *
struct resource *
cfg = s3c24xx_dev_to_cfg(&platdev-&dev); // 存放的是每个uart控制器的寄存器默认配置
/* setup info for port */
= &platdev-&
ourport-&info
/* copy the info in from provided structure */
ourport-&port.fifosize = info-&
port-&uartclk = 1;
res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
port-&mapbase = res-&
// 获得uart控制寄存器的物理基地址
port-&membase = S3C_VA_UART + (res-&start & 0xfffff);
// 计算虚拟地址,因为之前已经执行了静态映射,就不用ioremap了
ret = platform_get_irq(platdev, 0);
// 获取中断资源
if (ret & 0)
port-&irq = 0;
port-&irq =
ourport-&rx_irq =
ourport-&tx_irq = ret + 1;
ret = platform_get_irq(platdev, 1);
if (ret & 0)
ourport-&tx_irq =
ourport-&clk
= clk_get(&platdev-&dev, "uart"); // 获取时钟资源
/* reset the fifos (and setup the uart) */
//利用默认配置设置uart控制器,但是此时没有设置波特率和每帧的位数
s3c24xx_serial_resetport(port, cfg);
static inline int s3c24xx_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
return (info-&reset_port)(port, cfg); // 调用函数s5pv210_serial_resetport
下面的函数的作用是利用默认参数配置uart控制器,这里从cfg的参数中没有看到设置波特率以及每帧的数据位,这个会在register_console中匹配成功时调用。
static int s5pv210_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
unsigned long ucon = rd_regl(port, S3C2410_UCON);
ucon &= S5PV210_UCON_CLKMASK;
wr_regl(port, S3C2410_UCON,
ucon | cfg-&ucon);
wr_regl(port, S3C2410_ULCON, cfg-&ulcon);
/* reset both fifos */
wr_regl(port, S3C2410_UFCON, cfg-&ufcon | S3C2410_UFCON_RESETBOTH);
wr_regl(port, S3C2410_UFCON, cfg-&ufcon);
wr_regl(port, S3C64XX_UINTM, 0xf);
wr_regl(port, S3C64XX_UINTP, 0xf);
/* It is need to delay when reset FIFO register */
udelay(1);
wr_regl是如何实现的呢?
#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg))
#define portaddr(port, reg) ((port)-&membase + (reg))
下面分析uart_add_one_port
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
struct uart_state *
struct tty_port *
int ret = 0;
struct device *tty_
state = drv-&state + uport-&
// 每一个uart控制器在uart_driver中都有一个state与之对应
port = &state-&
state-&uart_port =
state-&pm_state = -1;
uport-&cons = drv-&
uport-&state =
#define uart_console(port)
((port)-&cons && (port)-&cons-&index == (port)-&line)
这里的cons就是s3c24xx_serial_console,只有一个,它的index初始值是-1,在register_console中如果跟console_cmdline的参数匹配成功,cons的index会被设置为相应的编号,如果bootargs中console=ttySAC3,那么将来cons的index会被设置为3
if (!(uart_console(uport) && (uport-&cons-&flags & CON_ENABLED))) {
spin_lock_init(&uport-&lock);
lockdep_set_class(&uport-&lock, &port_lock_key);
uart_configure_port(drv, state, uport);
tty_dev = tty_register_device(drv-&tty_driver, uport-&line, uport-&dev);
if (likely(!IS_ERR(tty_dev))) {
device_init_wakeup(tty_dev, 1);
device_set_wakeup_enable(tty_dev, 0);
printk(KERN_ERR "Cannot register tty device on line %d\n",
uport-&line);
uport-&flags &= ~UPF_DEAD;
下面分析uart_configure_port函数的实现
static void
uart_configure_port(struct uart_driver *drv, struct uart_state *state,
struct uart_port *port)
unsigned int
flags = 0;
if (port-&flags & UPF_AUTO_IRQ)
flags |= UART_CONFIG_IRQ;
if (port-&flags & UPF_BOOT_AUTOCONF) {
// 这里成立
if (!(port-&flags & UPF_FIXED_TYPE)) {
port-&type = PORT_UNKNOWN;
flags |= UART_CONFIG_TYPE;
port-&ops-&config_port(port, flags);
// 调用函数s3c24xx_serial_config_port
static void s3c24xx_serial_config_port(struct uart_port *port, int flags)
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
if (flags & UART_CONFIG_TYPE &&
s3c24xx_serial_request_port(port) == 0)
// 给port-&mapbase赋值
port-&type = info-&
// PORT_S3C6400
if (port-&type != PORT_UNKNOWN) {
// 条件成立
unsigned long
uart_report_port(drv, port);
这个函数只是打印的一些信息,没有做什么工作
static inline void
uart_report_port(struct uart_driver *drv, struct uart_port *port)
char address[64];
switch (port-&iotype) {
case UPIO_MEM:
snprintf(address, sizeof(address),
"MMIO 0x%llx", (unsigned long long)port-&mapbase);
printk(KERN_INFO "%s%s%s%d at %s (irq = %d) is a %s\n",
port-&dev ? dev_name(port-&dev) : "",
port-&dev ? ": " : "",
drv-&dev_name,
drv-&tty_driver-&name_base + port-&line,
address, port-&irq, uart_type(port));
/* Power up port for set_mctrl() */
uart_change_pm(state, 0);
在这里调用了register_console,对于tiny4412有四个串口,如果传入的console参数的是ttySAC0,由于是uart0先注册的,所以第一次就匹配成功了并且cons-&flags会置位CON_ENABLED,后面在注册uart1~3的时候这里的条件不成立。因为这里的cons都执行了全局变量s3c24xx_serial_console。如果u-boot传入的console参数是ttySAC3的话,这里register_console就会执行四次,因为uart0~2都没有匹配成功。在上面的分析中有一个疑问,何时设置波特率以及位宽,其实就是在register_console中设置的,但是他只给与传入的console参数匹配的uart设置波特率和位宽,当然这里的波特率和位宽也是u-boot传给内核的。tiny4412的u-boot传给内核的参数是:
set bootargs ‘console=ttySAC0, androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70‘
在上面分析内核解析console参数的时候,知道对于上面的参数console=ttySAC0,,会有一个结构体被赋值:
console_cmdline[0].name = &ttySAC&
console_cmdline[0].index = 0
console_cmdline[0].options = &&
if (port-&cons && !(port-&cons-&flags & CON_ENABLED))
register_console(port-&cons);
if (!uart_console(port))
uart_change_pm(state, 3);
下面看一下,register_console是如何设置波特率和位宽以及disable boot console的,关于这部分请参考前面分析register_console的代码,这里把关键部分列出来:
for (i = 0; i & MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
if (strcmp(console_cmdline[i].name, newcon-&name) != 0)
if (newcon-&index &= 0 &&
newcon-&index != console_cmdline[i].index)
if (newcon-&index & 0)
newcon-&index = console_cmdline[i].
if (newcon-&setup &&
newcon-&setup(newcon, console_cmdline[i].options) != 0)
newcon-&flags |= CON_ENABLED;
newcon-&index = console_cmdline[i].
if (i == selected_console) {
// selected_console是从bootargs中解析出来的
newcon-&flags |= CON_CONSDEV;
preferred_console = selected_
可以看到,当匹配成功后,会调用函数setup,对于tiny4412就是:s3c24xx_serial_console_setup
static int __init
s3c24xx_serial_console_setup(struct console *co, char *options)
struct uart_port *
int baud = 9600; // 初始波特率
int bits = 8;
// 初始帧宽
int parity = ‘n‘;
// 无奇偶校验
int flow = ‘n‘; // 无流控
port = &s3c24xx_serial_ports[co-&index].
cons_uart =
// 对于tiny4412,这里解析出来的options是&&
// uart_parse_options会解析这个字符串,然后给相应的成员赋值,对于没有指定的参数,使用初始值
// 如果bootargs中没有指定波特率等参数,就会通过函数s3c24xx_serial_get_options动态计算出这些参数,这些参数就不可预知了,所以最好在bootargs中执行用哪个tty设备,同时指定波特率等参数,否则可能会乱码
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
s3c24xx_serial_get_options(port, &baud, &parity, &bits);
return uart_set_options(port, co, baud, parity, bits, flow);
uart_set_options(struct uart_port *port, struct console *co,
int baud, int parity, int bits, int flow)
static struct
* Ensure that the serial console lock is initialised
spin_lock_init(&port-&lock);
lockdep_set_class(&port-&lock, &port_lock_key);
memset(&termios, 0, sizeof(struct ktermios));
termios.c_cflag = CREAD | HUPCL | CLOCAL;
* Construct a cflag setting.
for (i = 0; baud_rates[i]. i++)
if (baud_rates[i].rate &= baud)
termios.c_cflag |= baud_rates[i].
if (bits == 7)
termios.c_cflag |= CS7;
termios.c_cflag |= CS8;
switch (parity) {
case ‘o‘: case ‘O‘:
termios.c_cflag |= PARODD;
/*fall through*/
case ‘e‘: case ‘E‘:
termios.c_cflag |= PARENB;
if (flow == ‘r‘)
termios.c_cflag |= CRTSCTS;
port-&mctrl |= TIOCM_DTR;
port-&ops-&set_termios(port, &termios, &dummy);
// 调用函数s3c24xx_serial_set_termios设置
co-&cflag = termios.c_
当register_console执行完成后,通过printk输出的信息就可以通过选定的串口输出了,调用的就是下面这个结构体中的write函数
static struct console s3c24xx_serial_console = {
= S3C24XX_SERIAL_NAME,
= uart_console_device,
= CON_PRINTBUFFER,
= s3c24xx_serial_console_write,
= s3c24xx_serial_console_setup,
= &s3c24xx_uart_drv,
static void
s3c24xx_serial_console_write(struct console *co, const char *s,
unsigned int count)
uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
void uart_console_write(struct uart_port *port, const char *s,
unsigned int count,
void (*putchar)(struct uart_port *, int))
unsigned int
for (i = 0; i & i++, s++) {
if (*s == ‘\n‘)
putchar(port, ‘\r‘);
putchar(port, *s);
static void
s3c24xx_serial_console_putchar(struct uart_port *port, int ch)
unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON);
while (!s3c24xx_serial_console_txrdy(port, ufcon))
barrier();
wr_regb(cons_uart, S3C2410_UTXH, ch);
这样,字符就通过串口输出到终端了。
&标签:原文地址:http://www.cnblogs.com/pengdonglin137/p/4339918.html
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!

我要回帖

更多关于 tiny4412 的文章

 

随机推荐