linux驱动摸索 --DS1302芯片移植(基于总线驱动设备模型)

内核版本:linux-2.6.32.2
开发板:mini2440


基于总线驱动设备模型基本框架:

bus_drv_dev模型(是一种机制)

对于device来说:

1.把device相关信息放入bus的dev链表

2.从bus的drv链表取出每一个drv,用bus的match函数判断drv能否支持dev

3.若能支持,调用drv的probe函数。


对于driver来说

1.把driver放入drv链表

2.从dev链表中取出,利用mach函数一一比较,若发现匹配的,调用probe函数。


最近在弄DS1302芯片,对于单片机的程序,可以参考我的另外一篇博文stm8及DS1302的一些操作,然后想移植到linux系统中来,在源码中包含了一个DS1302的驱动文件(\linux-2.6.32.2\drivers\rtc\rtc-ds1302.c

打开该文件,首先查看他的模块init函数:

static int __init ds1302_rtc_init(void)
{
return platform_driver_probe(&ds1302_platform_driver, ds1302_rtc_probe);
}

继续追踪,在platform_driver_probe函数里,看到了retval = code = platform_driver_register(drv);确定该实现,运用了总线驱动设备模型。

如果我要用它的驱动的话可以自己先完成platform_device_registerv的注册,先确定硬件相关的接口,我把ds1302模块飞线接在了mini2440开发板的GPIOF0,1,2三个IO口上。

创建一个ds1302_dev.c文件,完成platform_device_registerv的注册。注意,在rtc-ds1302.c中ds1302的驱动name是"rtc-ds1302",所以该文件中dev name也一定是"rtc-ds1302",只有两者匹配,最后才会调用probe函数。

ds1302_dev.c的源码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <mach/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>

static void ds1302_release(struct device * dev)
{
}
static struct resource ds1302_resource[] = {
    [0] = {
        .start = 0x56000050,
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
    	.name  = "ds1302_rst",
        .start = 0,
        .end   = 0,
        .flags = IORESOURCE_IO,
    },
	[2] = {
    	.name  = "ds1302_io",
        .start = 1,
        .end   = 1,
        .flags = IORESOURCE_IO,
    },
	[3] = {
    	.name  = "ds1302_clk",
        .start = 2,
        .end   = 2,
        .flags = IORESOURCE_IO,
    },

};


static struct platform_device ds1302_device_driver = {
    .name         = "rtc-ds1302",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(ds1302_resource),
    .resource     = ds1302_resource,
    .dev = { 
    	.release = ds1302_release, 
	},
};



static int ds1302_dev_init(void)
{

	return platform_device_register(&ds1302_device_driver);

}


static void ds1302_dev_exit(void)
{

	platform_device_unregister(&ds1302_device_driver);

}


module_init(ds1302_dev_init);
module_exit(ds1302_dev_exit);

MODULE_LICENSE("GPL");

整个文件代码很简单,设置硬件相关的配置,dev注册等等,在此不做分析。


为了方便,我把rtc-ds1302.c文件单独取出来,和ds1302_dev.c放一起,改名为ds1302_drv.c..。

简单修改编译后,分别加载ds1302_dev.ko,ds1302_drv.ko。从打印信息上看,probe函数已经被调用,但出错了:

[root@FriendlyARM /]# insmod ds1302_dev.ko
[root@FriendlyARM /]# insmod ds1302_drv.ko
ds1302_rtc_probe
rtc_reset pin = 0
rtc_iodata pin = 1
rtc_sclk pin = 2
iomap over 
insmod: cannot insert 'ds1302_drv.ko': No such device

查看源码 发现在probe函数中有这样一句:

	ds1302_writebyte(RTC_ADDR_RAM0, 0x42);
	if (ds1302_readbyte(RTC_ADDR_RAM0) != 0x42)
		return -ENODEV;

往DS1302的ram0空间写一个数据,再读出来,只有正确读到才会往下去注册设备,不然在这一步就退出了。


现在添加在适合S3C2440的一些代码:

在文件的开头 有这样的定义:

//#define	RTC_RESET	0x1000
//#define	RTC_IODATA	0x0800
//#define	RTC_SCLK	0x0400

#ifdef CONFIG_SH_SECUREEDGE5410
#include <mach/snapgear.h>
#define set_dp(x)	SECUREEDGE_WRITE_IOPORT(x, 0x1c00)
#define get_dp()	SECUREEDGE_READ_IOPORT()
#else
...........
#endif

显然我要在#else中添加支持自己平台的代码,为了保持源码的最小改动,我按照代码的原来框架定义了若干参数:

//#define	RTC_RESET	0x1000
//#define	RTC_IODATA	0x0800
//#define	RTC_SCLK	0x0400

#ifdef CONFIG_SH_SECUREEDGE5410
#include <mach/snapgear.h>
#define set_dp(x)	SECUREEDGE_WRITE_IOPORT(x, 0x1c00)
#define get_dp()	SECUREEDGE_READ_IOPORT()
#else
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
unsigned int rtc_pinmap[16]={0x0001,0x0002,0x0004,0x0008,
	                         0x0010,0x0020,0x0040,0x0080,
	                         0x0100,0x0200,0x0400,0x0800,
	                         0x1000,0x2000,0x4000,0x8000,};
static unsigned int RTC_RESET;
static unsigned int RTC_IODATA;
static unsigned int RTC_SCLK;

static unsigned int rtc_reset_num;
static unsigned int rtc_iodata_num;
static unsigned int rtc_sclk_num;


#define set_dp(x)	*gpio_dat=(x)
#define get_dp()	*gpio_dat

static void write_io(void)
{
	*gpio_con &= ~(0x3<<(rtc_iodata_num)*2);
	*gpio_con |=  (0x1<<(rtc_iodata_num)*2);

}

static void read_io(void)
{
	*gpio_con &= ~(0x3<<(rtc_iodata_num)*2);
}

#endif

因为ds1302的IO口是个双向IO,对于2440的GPIO有个方向设置,我添加了write_io和read_io两个函数,用于对DS1302io口的设置。


在ds1302的probe函数中首先要ioremap寄存器地址,故要从ds1302_dev.c中获取相关寄存器配置:

static int __init ds1302_rtc_probe(struct platform_device *pdev)
{
	struct rtc_device *rtc;	
//====================================================
    struct resource 	*res;
    int k =0;
    printk("ds1302_rtc_probe\n");
/* 根据platform_device的资源进行ioremap */
  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  gpio_con = ioremap(res->start, res->end - res->start + 1);
  gpio_dat = gpio_con + 1;

  while ((res = platform_get_resource(pdev, IORESOURCE_IO, k))) {
      if(strcmp(res->name,"ds1302_rst") == 0)
      {
		 rtc_reset_num = res->start;
		 RTC_RESET = rtc_pinmap[rtc_reset_num];
		 printk("rtc_reset pin = %d\n",rtc_reset_num);
		 
      }
	  else if (strcmp(res->name,"ds1302_io") == 0)
	  {
	     rtc_iodata_num = res->start;
		 RTC_IODATA= rtc_pinmap[rtc_iodata_num];
		 printk("rtc_iodata pin = %d\n",rtc_iodata_num);
	  }
	  else if (strcmp(res->name,"ds1302_clk") == 0)
	  {
		 rtc_sclk_num = res->start;
		 RTC_SCLK = rtc_pinmap[rtc_sclk_num];
		 printk("rtc_sclk pin = %d\n",rtc_sclk_num);
	  }
	  k++;
  }

  	/* 配置为输出 */
	*gpio_con &= ~((0x3<<(rtc_reset_num*2))|(0x3<<(rtc_iodata_num*2))|\
	               (0x3<<(rtc_sclk_num*2)));
	*gpio_con |=  ((0x1<<(rtc_reset_num*2))|(0x1<<(rtc_iodata_num*2))|\
	               (0x1<<(rtc_sclk_num*2)));

   printk("iomap over \n");
//=====================================================
........
}

编译加载后,发现还是错在

if (ds1302_readbyte(RTC_ADDR_RAM0) != 0x42)
		return -ENODEV;

这一步。

因为之前仔细的看了DS1302的相关操作,很容易就发现了代码的一个bug,在写数据前,应该去掉DS1302的写保护功能(在CONTROL寄存器里写0x00)。

定义RTC_ADDR_CONTROL,参照代码其他寄存器的宏定义,并查看1302的datasheet,应该这样定义:

#define RTC_ADDR_CONTROL 0x07

#define	RTC_CMD_READ	0x81		/* Read command */
#define	RTC_CMD_WRITE	0x80		/* Write command */

#define RTC_ADDR_RAM0	0x20		/* Address of RAM0 */
#define RTC_ADDR_TCR	0x08		/* Address of trickle charge register */
#define	RTC_ADDR_YEAR	0x06		/* Address of year register */
#define	RTC_ADDR_DAY	0x05		/* Address of day of week register */
#define	RTC_ADDR_MON	0x04		/* Address of month register */
#define	RTC_ADDR_DATE	0x03		/* Address of day of month register */
#define	RTC_ADDR_HOUR	0x02		/* Address of hour register */
#define	RTC_ADDR_MIN	0x01		/* Address of minute register */
#define	RTC_ADDR_SEC	0x00		/* Address of second register */

并在写ram之前,取消写保护操作:

	/* Reset */
	set_dp(get_dp() & ~(RTC_RESET | RTC_IODATA | RTC_SCLK));

	/* Write a magic value to the DS1302 RAM, and see if it sticks. */
	ds1302_writebyte(RTC_ADDR_CONTROL,0x00); //关闭写保护 
	ds1302_writebyte(RTC_ADDR_RAM0, 0x42);
	if (ds1302_readbyte(RTC_ADDR_RAM0) != 0x42)
		return -ENODEV;

编译,加载,此时:

[root@FriendlyARM /]# insmod ds1302_drv.ko
ds1302_rtc_probe
rtc_reset pin = 0
rtc_iodata pin = 1
rtc_sclk pin = 2
iomap over 
rtc-ds1302 rtc-ds1302: rtc core: registered ds1302 as rtc1
[root@FriendlyARM /]# insmod ds1302_drv.ko

发现这一步已经成功,1302的读写操作已经成功,注册了ds1302的设备。


后面的操作应该就没啥问题了。


注意:在读写DS302时要注意IO方向的切换。

          在写DS1302寄存器时,应该先取消写保护,写完数据,使能写保护功能(ds1302_writebyte(RTC_ADDR_CONTROL,0x80);)。










你可能感兴趣的:(mini2440,linux驱动,时钟芯片)