S3C2440驱动简析——串口驱动

     对于驱动的学习停歇了几乎一周的时间,期间忙于补习Linux应用编程和搜索驱动、内核相关书籍,以便之后更进一步地学习。在之前友善提供的驱动例程里面,涉及的知识面非常有限,需要研究更多的驱动源码,了解更多的驱动知识,是当务之急。研究别人代码的同时,当然不忘自己也要动手练习。以下贴出串口驱动程序,并在程序里附上简要注释。

 

/* 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结构体。

注册:

static struct platform_driver s3c2440_serial_driver = { .probe = s3c2440_serial_probe, .remove = __devexit_p(s3c24xx_serial_remove), .driver = { .name = "s3c2440-uart", .owner = THIS_MODULE, }, };

 

注销:

platform_driver_unregister(&s3c2440_serial_driver);

 

2.几个非常重要的结构体

s3c2410_uartcfg :保存ucon ulcon ufcon三个串口寄存器的值

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 :提供串口设备环境信息,并提供三个函数的接口

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 :设备的信息

 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 :设备注册用

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

你可能感兴趣的:(c,linux,struct,Module,table,features)