S3C2440驱动简析——串口驱动
对于驱动的学习停歇了几乎一周的时间,期间忙于补习Linux应用编程和搜索驱动、内核相关书籍,以便之后更进一步地学习。在之前友善提供的驱动例程里面,涉及的知识面非常有限,需要研究更多的驱动源码,了解更多的驱动知识,是当务之急。研究别人代码的同时,当然不忘自己也要动手练习。以下贴出串口驱动程序,并在程序里附上简要注释。
[c-sharp] view plain copy
- /* linux/drivers/serial/s3c2440.c
- *
- * Driver for Samsung S3C2440 and S3C2442 SoC onboard UARTs.
- *
- * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics
- * http://armlinux.simtec.co.uk/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
- #include <linux/module.h>
- #include <linux/ioport.h>
- #include <linux/io.h>
- #include <linux/platform_device.h>
- #include <linux/init.h>
- #include <linux/serial_core.h>
- #include <linux/serial.h>
-
- #include <asm/irq.h>
- #include <mach/hardware.h>
-
- #include <plat/regs-serial.h>
- #include <mach/regs-gpio.h>
-
- #include "samsung.h"
-
-
- static int s3c2440_serial_setsource(struct uart_port *port,
- struct s3c24xx_uart_clksrc *clk)
- { //本函数选定串口端口和时钟源
- unsigned long ucon = rd_regl(port, S3C2410_UCON); //读取寄存器UCON
-
- /* todo - proper fclk<>nonfclk switch. */
-
- ucon &= ~S3C2440_UCON_CLKMASK; //#define S3C2440_UCON_CLKMASK (3<<10)
-
- if (strcmp(clk->name, "uclk") == 0) //选择时钟源
- ucon |= S3C2440_UCON_UCLK;
- else if (strcmp(clk->name, "pclk") == 0)
- ucon |= S3C2440_UCON_PCLK;
- else if (strcmp(clk->name, "fclk") == 0)
- ucon |= S3C2440_UCON_FCLK;
- else {
- printk(KERN_ERR "unknown clock source %s/n", clk->name);
- return -EINVAL;
- }
-
- wr_regl(port, S3C2410_UCON, ucon); //把设置过的ucon写回串口控制寄存器
- return 0;
- }
-
-
- static int s3c2440_serial_getsource(struct uart_port *port,
- struct s3c24xx_uart_clksrc *clk)
- { //设置时钟源和对应预分频值
- unsigned long ucon = rd_regl(port, S3C2410_UCON);
- unsigned long ucon0, ucon1, ucon2;
-
- switch (ucon & S3C2440_UCON_CLKMASK) {
- case S3C2440_UCON_UCLK:
- clk->divisor = 1;
- clk->name = "uclk";
- break;
-
- case S3C2440_UCON_PCLK:
- case S3C2440_UCON_PCLK2:
- clk->divisor = 1;
- clk->name = "pclk";
- break;
-
- case S3C2440_UCON_FCLK:
- /* the fun of calculating the uart divisors on
- * the s3c2440 */
-
- ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON);
- ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON);
- ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON);
-
- printk("ucons: %08lx, %08lx, %08lx/n", ucon0, ucon1, ucon2);
-
- ucon0 &= S3C2440_UCON0_DIVMASK;
- ucon1 &= S3C2440_UCON1_DIVMASK;
- ucon2 &= S3C2440_UCON2_DIVMASK;
-
- if (ucon0 != 0) {
- clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT;
- clk->divisor += 6;
- } else if (ucon1 != 0) {
- clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT;
- clk->divisor += 21;
- } else if (ucon2 != 0) {
- clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT;
- clk->divisor += 36;
- } else {
- /* manual calims 44, seems to be 9 */
- clk->divisor = 9;
- }
-
- clk->name = "fclk";
- break;
- }
-
- return 0;
- }
-
- static int s3c2440_serial_resetport(struct uart_port *port,
- struct s3c2410_uartcfg *cfg)
- { //重设串口
- unsigned long ucon = rd_regl(port, S3C2410_UCON);
-
- dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p/n",
- port, port->mapbase, cfg);
-
- /* ensure we don't change the clock settings... */
-
- ucon &= (S3C2440_UCON0_DIVMASK | (3<<10));
-
- wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); //重新设置寄存器UCON
- wr_regl(port, S3C2410_ULCON, cfg->ulcon); //重新设置寄存器ULCON
-
- /* reset both fifos */
-
- wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); //重启fifo
- wr_regl(port, S3C2410_UFCON, cfg->ufcon); //重新设定寄存器UFCON
-
- return 0;
- }
-
- static struct s3c24xx_uart_info s3c2440_uart_inf = { //串口设备环境信息和提供的操作函数
- .name = "Samsung S3C2440 UART",
- .type = PORT_S3C2440,
- .fifosize = 64,
- .rx_fifomask = S3C2440_UFSTAT_RXMASK,
- .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
- .rx_fifofull = S3C2440_UFSTAT_RXFULL,
- .tx_fifofull = S3C2440_UFSTAT_TXFULL,
- .tx_fifomask = S3C2440_UFSTAT_TXMASK,
- .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
- .get_clksrc = s3c2440_serial_getsource,
- .set_clksrc = s3c2440_serial_setsource,
- .reset_port = s3c2440_serial_resetport,
- };
-
- /* device management */
-
- static int s3c2440_serial_probe(struct platform_device *dev)
- { //完成串口的添加
- dbg("s3c2440_serial_probe: dev=%p/n", dev);
- return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
- }
-
- static struct platform_driver s3c2440_serial_driver = { //注册串口设备
- .probe = s3c2440_serial_probe,
- .remove = __devexit_p(s3c24xx_serial_remove),
- .driver = {
- .name = "s3c2440-uart",
- .owner = THIS_MODULE,
- },
- };
-
- s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
-
- static int __init s3c2440_serial_init(void)
- { //初始化模块
- return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
- }
-
- static void __exit s3c2440_serial_exit(void)
- { //退出模块
- platform_driver_unregister(&s3c2440_serial_driver); //注销串口设备
- }
-
- module_init(s3c2440_serial_init);
- module_exit(s3c2440_serial_exit);
-
- MODULE_DESCRIPTION("Samsung S3C2440,S3C2442 SoC Serial port driver");
- MODULE_AUTHOR("Ben Dooks <[email protected]>");
- MODULE_LICENSE("GPL v2");
- MODULE_ALIAS("platform:s3c2440-uart");
几个问题需要我们注意:
1.设备如何注册、注销
串口驱动被作为一个单独的模块被加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体。
注册:
[c-sharp] view plain copy
- static struct platform_driver s3c2440_serial_driver = {
- .probe = s3c2440_serial_probe,
- .remove = __devexit_p(s3c24xx_serial_remove),
- .driver = {
- .name = "s3c2440-uart",
- .owner = THIS_MODULE,
- },
- };
注销:
[c-sharp] view plain copy
- platform_driver_unregister(&s3c2440_serial_driver);
2.几个非常重要的结构体
s3c2410_uartcfg :保存ucon ulcon ufcon三个串口寄存器的值
[c-sharp] view plain copy
- struct s3c2410_uartcfg {
- unsigned char hwport; /* hardware port number */
- unsigned char unused;
- unsigned short flags;
- upf_t uart_flags; /* default uart flags */
-
- unsigned int has_fracval;
-
- unsigned long ucon; /* value of ucon for port */
- unsigned long ulcon; /* value of ulcon for port */
- unsigned long ufcon; /* value of ufcon for port */
-
- struct s3c24xx_uart_clksrc *clocks;
- unsigned int clocks_size;
- };
s3c24xx_uart_info :提供串口设备环境信息,并提供三个函数的接口
[c-sharp] view plain copy
- struct s3c24xx_uart_info {
- char *name;
- unsigned int type;
- unsigned int fifosize;
- unsigned long rx_fifomask;
- unsigned long rx_fifoshift;
- unsigned long rx_fifofull;
- unsigned long tx_fifomask;
- unsigned long tx_fifoshift;
- unsigned long tx_fifofull;
-
- /* uart port features */
-
- unsigned int has_divslot:1;
-
- /* clock source control */
-
- int (*get_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk);
- int (*set_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk);
-
- /* uart controls */
- int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *);
- };
platform_device :设备的信息
[c-sharp] view plain copy
- struct platform_device {
- const char * name;
- int id;
- struct device dev;
- u32 num_resources;
- struct resource * resource;
-
- const struct platform_device_id *id_entry;
-
- /* arch specific additions */
- struct pdev_archdata archdata;
- };
platform_driver :设备注册用
[c-sharp] view plain copy
- struct platform_driver {
- int (*probe)(struct platform_device *);
- int (*remove)(struct platform_device *);
- void (*shutdown)(struct platform_device *);
- int (*suspend)(struct platform_device *, pm_message_t state);
- int (*resume)(struct platform_device *);
- struct device_driver driver;
- const struct platform_device_id *id_table;
- };
3.读写寄存器的宏定义
(1)读寄存器
unsigned long ucon = rd_regl(port, S3C2410_UCON);
#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg)))
static unsigned char __raw_readb(unsigned int ptr)
{
return *((volatile unsigned char *)ptr);
}
#define portaddr(port, reg) ((port)->membase + (reg))
(2)写寄存器
wr_regl(port, S3C2410_UCON, ucon);
#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg))
#define portaddr(port, reg) ((port)->membase + (reg))
#define __raw_writel(v,p) (*(unsigned long *)(p) = (v))
4.函数的注册方式
细心的朋友可能会发现,我们之前一直使用的是传统的 device driver 机制(通过 driver_register 函数进行注册)本串口所使用的是一个设备用 Platform_device 表示,驱动用 Platform_driver 进行注册的机制。而后者是在内核2.6版本所提出来的新事物,其优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过 platform device 提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。关于这两种机制更深入的分析,请看以下链接:http://blog.csdn.net/jarvis_xian/archive/2011/05/23/6440649.aspx