ARMLinux driver RTC (real-time clock) driver analysis

Publisher:创意梦者Latest update time:2016-07-08 Source: eefocusKeywords:ARM Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
Hardware platform: FL2440 (S3C2440)

Kernel version: Linux 2.6.28

Host platform: Ubuntu 11.04

Kernel version: Linux 2.6.39

Cross compiler version: arm-linux-gcc 3.4.1

Original work, please indicate the source when reprinting http://blog.csdn.net/yming0221/article/details/6584285

1. Real-time clock overview

The real-time clock (RTC) unit can continue to keep time using a button battery when the power is off. The RTC uses the STRB/LDRB ARM operation to transmit 8 bits of binary decimal data to the CPU. The data includes time information such as seconds, minutes, hours, date, day, month, and year. The alarm function can be executed.

2. Real-time clock operation

Below is the circuit diagram of the RTC module

ARMLinux driver RTC (real-time clock) driver analysis
 

3. Introduction to RTC registers

Real Time Clock Control Register (RTCCON) - REAL TIME CLOCK CONTROL REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

ARMLinux driver RTC (real-time clock) driver analysis
 

Tick ​​Time Count Register (TICNT) - TICK TIME COUNT REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

RTC Alarm Control Register (RTCALM) - RTC ALARM CONTROL REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

Alarm Seconds Register (ALMSEC) - ALARM SECOND DATA REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

Alarm Minute Count Register (ALMMIN) - ALARM MIN DATA REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

ALARM HOUR DATA REGISTER

ARMLinux driver RTC (real-time clock) driver analysis

ALARM DATE DATA REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

Alarm Month Data Register (ALMMON) - ALARM MON DATA REGISTER 

ARMLinux driver RTC (real-time clock) driver analysis
 

ALARM YEAR DATA REGISTER

ARMLinux driver RTC (real-time clock) driver analysis
 

The format of the BCD data register is the same as the alarm register structure, except that the corresponding addresses are different.

BCD Second Register (BCDSEC) - BCD SECOND REGISTER Address: 0x57000070 (L) 0x57000073 (B)

BCD MINUTE REGISTER Address: 0x57000074 (L) 0x57000077 (B)

BCD Hour Register (BCDHOUR) - BCD HOUR REGISTER Address: 0x57000078 (L) 0x5700007B (B)

BCD Date Register (BCDDATE) - BCD DATE REGISTER Address: 0x5700007C (L) 0x5700007F (B)

BCD Day Register (BCDDAY) - BCD DAY REGISTER Address: 0x57000080 (L) 0x57000083 (B)

BCD Month Register (BCDMON) - BCD MONTH REGISTER Address: 0x57000084 (L) 0x57000087 (B)

BCD Year Register (BCDYEAR) - BCD YEAR REGISTER Address: 0x57000088 (L) 0x5700008B (B)

4. Driver Example Analysis

In order to make the driver easier to understand, the current RTC driver only completes the timing function, without adding the corresponding alarm function or power management function. The missing functions will be improved in the future.

Let's first have an overall understanding of the driver:

First is the structure of the RTC driver, in /include/linux/platform_device.h, as follows

 

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 (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct pm_ext_ops *pm;
	struct device_driver driver;
};
Define the corresponding structure in the driver

 

 

static struct platform_driver s3c2410_rtc_driver = {
	.probe = s3c_rtc_probe, //RTC detection function
	.remove = __devexit_p(s3c_rtc_remove), //RTC removal function
	.driver		= {
		.name	= "s3c2410-rtc",
		.owner	= THIS_MODULE,
	},
};
The following are the initialization and exit functions of the driver in the driver

 

 

static int __init s3c_rtc_init(void)
{
	printk(banner);
	return platform_driver_register(&s3c2410_rtc_driver);
}

static void __exit s3c_rtc_exit(void)
{
	platform_driver_unregister(&s3c2410_rtc_driver);
}

The platform_driver_register() and platform_driver_unregister() functions are implemented in /drivers/base/platform.c.

It can be seen that the role of the platform_driver_register() function is to provide interface functions for probe, remove, etc. in the driver in platform_driver

 

 

int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;
	if (drv->suspend)
		drv->driver.suspend = platform_drv_suspend;
	if (drv->resume)
		drv->driver.resume = platform_drv_resume;
	if (drv->pm)
		drv->driver.pm = &drv->pm->base;
	return driver_register(&drv->driver);//Register the old driver
}
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}

Next is the RTC platform driver detection function s3c_rtc_probe. The function definition below uses __devinit to enable the compiler to optimize the code and place it in the correct memory location to reduce memory usage and improve kernel efficiency.

After the probe function receives the parameter plarform_device, it needs to extract the required information from it. It generally obtains relevant information by calling functions such as platform_get_resource and platform_get_irq provided by the kernel. For example, after obtaining the starting address of the device through platform_get_resource, operations such as request_mem_region and ioremap can be performed on it so that the application can operate it. After obtaining the interrupt number of the device through platform_get_irq, the request_irq function can be called to request an interrupt from the system. These operations are generally completed in the device driver.

 

static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
	struct rtc_device *rtc; //define rtc_device structure, defined in /include/linux/rtc.h
	struct resource *res; //define resource structure, defined in /include/linux/ioport.h
	int ret;

	pr_debug("%s: probe=%p\n", __func__, pdev);

	/* find the IRQs */

	s3c_rtc_tickno = platform_get_irq(pdev, 1); //Get the interrupt number in the platform device defined by the system
	if (s3c_rtc_tickno < 0) {//Exception handling
		dev_err(&pdev->dev, "no irq for rtc tick\n");
		return -ENOENT;
	}

	/* get the memory region */

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //Get the IO resources used by the RTC platform
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory region resource\n");
		return -ENOENT;
	}
       //Apply for memory area, res is struct resource type, see the back of this function
	s3c_rtc_mem = request_mem_region(res->start,
					 res->end-res->start+1,
					 pdev->name);

	if (s3c_rtc_mem == NULL) {//Error in applying for memory
		dev_err(&pdev->dev, "failed to reserve memory region\n");
		right = -ENOENT;
		goto err_nores;
	}
        //Map register addresses into virtual addresses for easy access
	s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
	if (s3c_rtc_base == NULL) {
		dev_err(&pdev->dev, "failed ioremap()\n");
		ret = -ONE;
		goto err_nomap;
	}

	/* check to see if everything is setup correctly */

	s3c_rtc_enable(pdev, 1); //Set the RTCCON register, see the function implementation below for details

 	pr_debug("s3c2410_rtc: RTCCON=%02x\n",
		 readb(s3c_rtc_base + S3C2410_RTCCON));

	s3c_rtc_setfreq(&pdev->dev, 1);//For details, see the function implementation below

	/* register RTC and exit */

	rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
				  THIS_MODULE); //Register RTC as RTC device, where s3c_rtcops is defined as follows

	if (IS_ERR(rtc)) {
		dev_err(&pdev->dev, "cannot attach rtc\n");
		ret = PTR_ERR(rtc);
		goto err_nortc;
	}

	rtc->max_user_freq = 128; //Set the user's maximum relative value of the beat time count value of the RTC beat time count register TICNT
        //Pass the RTC class device data to the system device in /include/linux/platform_device.h
       //#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data)), this function is defined in /include/linux/device.h, see below this function
       platform_set_drvdata(pdev, rtc);
	return 0;
//Exception handling
 err_nortc:
	s3c_rtc_enable(pdev, 0);
	iounmap(s3c_rtc_base);

 err_nomap:
	release_resource(s3c_rtc_mem);
 err_nores:
	return right;
}
The following is the definition of struct resource structure in /include/linux/ioport.h
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};
This is the function definition of dev_set_drvdata():

 

 

static inline void dev_set_drvdata(struct device *dev, void *data)
{
	dev->driver_data = data;
}
Next are the two functions s3c_rtc_enable() and s3c_rtc_setfreq() used in the s3c_rtc_probe() function.

 

 

static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
	void __iomem *base = s3c_rtc_base; //The function of __iomem is to enable the compiler to better optimize compilation
	unsigned int tmp;

	if (s3c_rtc_base == NULL)
		return;
        //en is passed as a parameter. If en==0, the situation before turning off the power
	if (!en) {
		tmp = readb(base + S3C2410_RTCCON);
		writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON); //Set the RTCCON register to disable RTC. Please refer to the definition of registers in the data sheet.

		tmp = readb(base + S3C2410_TICNT);
		writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT); //Set TICNT register to enable the beat time interrupt
	} else {
		/* re-enable the device, and check it is ok */
                //en!=0 means the system is reset and the RTC driver is enabled again
		if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){//RTCCON bit 0 is 0, set it to 1 and re-enable
			dev_info(&pdev->dev, "rtc disabled, re-enabling\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
		}

		if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
			dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON); //Set the second bit of RTCCON to 0, and set the BCD count to mixed BCD count
		}

		if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
			dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON); //RTC clock counter reset
		}
	}
}
static int s3c_rtc_setfreq(struct device *dev, int freq) //Set the beat time count value
{
	unsigned int tmp;

	spin_lock_irq(&s3c_rtc_pie_lock); //Get spin lock and mutually exclusive access to resources

	tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE; //Beat time enable is valid
	tmp |= (128 / freq)-1;

	writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
	spin_unlock_irq(&s3c_rtc_pie_lock);//Unlock

	return 0;
}
Next is the operation of the RTC device class.

 

The following is rtc_class_ops, which is a structure defined in the core part of the RTC driver to operate the RTC device class, similar to the meaning of file_operations in the character device driver to operate the character device. This structure is defined in rtc.h. The operations on the RTC mainly include opening, closing, setting or getting time, setting or getting alarms, setting the beat time count value, etc. The implementation of the interface functions in this structure are all below

 

static const struct rtc_class_ops s3c_rtcops = {
	.open		= s3c_rtc_open,
	.release	= s3c_rtc_release,
	.read_time	= s3c_rtc_gettime,
	.set_time	= s3c_rtc_settime,
	.irq_set_freq	= s3c_rtc_setfreq,
	.irq_set_state	= s3c_rtc_setpie,
};
RTC opens the device function s3c_rtc_open()

 

 

static int s3c_rtc_open(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev); //Get the RTC device class data from the platform device
	struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
	int ret;

	ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
			  IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev); // Request interrupt

	if (ret) {
		dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
		goto tick_err;
	}

 tick_err:
	return right;
}
RTC TICK beat time interrupt service routine

 

 

static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
{
	struct rtc_device *rdev = id;

	rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);
	return IRQ_HANDLED;
}
RTC close device function s3c_rtc_release()

 

 

static void s3c_rtc_release(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev); //Get the RTC device class data from the platform device

	struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

	/* do not clear AIE here, it may be needed for wake */

	s3c_rtc_setpie(dev, 0); //Function definition see below
	free_irq(s3c_rtc_tickno, rtc_dev);
}
The s3c_rtc_setpie() function is mainly used to set the highest bit of the TICNT register according to the parameter. If the parameter is 0, it is disabled, and if the parameter is 1, it is enabled.
static int s3c_rtc_setpie(struct device *dev, int enabled)
{
	unsigned int tmp;

	pr_debug("%s: pie=%d\n", __func__, enabled);

	spin_lock_irq(&s3c_rtc_pie_lock);
	tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE; //Read the value of TICNT and clear the highest bit to 0

	if (enabled)
		tmp |= S3C2410_TICNT_ENABLE;

	writeb(tmp, s3c_rtc_base + S3C2410_TICNT); //Write the new value after calculation
	spin_unlock_irq(&s3c_rtc_pie_lock);

	return 0;
}
The following two functions are used to set and read the time of the BCD register. The logic is very simple, just reading and setting the value of the corresponding register

 

 

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
	unsigned int have_retried = 0;
	void __iomem *base = s3c_rtc_base;

 retry_get_time:
	rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);
	rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
	rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
	rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);
	rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
	rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);

	/* the only way to work out wether the system was mid-update
	 * when we read it is to check the second counter, and if it
	 * is zero, then we re-try the entire read
	 */

	if (rtc_tm->tm_sec == 0 && !have_retried) {
		have_retried = 1;
		goto retry_get_time;
	}

	pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
		 rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
		 rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);

	rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
	rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
	rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
	rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
	rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
	rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);

	rtc_tm->tm_year += 100;
	rtc_tm->tm_mon -= 1;

	return 0;
}

static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{
	void __iomem *base = s3c_rtc_base;
	int year = tm->tm_year - 100;

	pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n",
		 tm->tm_year, tm->tm_mon, tm->tm_mday,
		 tm->tm_hour, tm->tm_min, tm->tm_sec);

	/* we get around y2k by simply not supporting it */

	if (year < 0 || year >= 100) {
		dev_err(dev, "rtc only supports 100 years\n");
		return -SINGLE SELECT;
	}

	writeb(bin2bcd(tm->tm_sec),  base + S3C2410_RTCSEC);
	writeb(bin2bcd(tm->tm_min),  base + S3C2410_RTCMIN);
	writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR);
	writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE);
	writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON);
	writeb(bin2bcd(year), base + S3C2410_RTCYEAR);

	return 0;
}
At this point, the timing function of the RTC driver has been implemented, but the alarm function has not yet been completed. Below is the source code of this driver

 

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 


static struct resource *s3c_rtc_mem;

static void __iomem *s3c_rtc_base;

static int s3c_rtc_tickno  = NO_IRQ;

static DEFINE_SPINLOCK(s3c_rtc_pie_lock);


static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
{
	struct rtc_device *rdev = id;

	rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);
	return IRQ_HANDLED;
}

/* Update control registers */
static void s3c_rtc_setaie(int to)
{
	unsigned int tmp;

	pr_debug("%s: aie=%d\n", __func__, to);

	tmp = readb(s3c_rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;

	if (to)
		tmp |= S3C2410_RTCALM_GENERAL;

	writeb(tmp, s3c_rtc_base + S3C2410_RTCALM);
}

static int s3c_rtc_setpie(struct device *dev, int enabled)
{
	unsigned int tmp;

	pr_debug("%s: pie=%d\n", __func__, enabled);

	spin_lock_irq(&s3c_rtc_pie_lock);
	tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;

	if (enabled)
		tmp |= S3C2410_TICNT_ENABLE;

	writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
	spin_unlock_irq(&s3c_rtc_pie_lock);

	return 0;
}

static int s3c_rtc_setfreq(struct device *dev, int freq)
{
	unsigned int tmp;

	spin_lock_irq(&s3c_rtc_pie_lock);

	tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
	tmp |= (128 / freq)-1;

	writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
	spin_unlock_irq(&s3c_rtc_pie_lock);

	return 0;
}

/* Time read/write */

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
	unsigned int have_retried = 0;
	void __iomem *base = s3c_rtc_base;

 retry_get_time:
	rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);
	rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
	rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
	rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);
	rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
	rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);

	/* the only way to work out wether the system was mid-update
	 * when we read it is to check the second counter, and if it
	 * is zero, then we re-try the entire read
	 */

	if (rtc_tm->tm_sec == 0 && !have_retried) {
		have_retried = 1;
		goto retry_get_time;
	}

	pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
		 rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
		 rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);

	rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
	rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
	rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
	rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
	rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
	rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);

	rtc_tm->tm_year += 100;
	rtc_tm->tm_mon -= 1;

	return 0;
}

static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{
	void __iomem *base = s3c_rtc_base;
	int year = tm->tm_year - 100;

	pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n",
		 tm->tm_year, tm->tm_mon, tm->tm_mday,
		 tm->tm_hour, tm->tm_min, tm->tm_sec);

	/* we get around y2k by simply not supporting it */

	if (year < 0 || year >= 100) {
		dev_err(dev, "rtc only supports 100 years\n");
		return -SINGLE SELECT;
	}

	writeb(bin2bcd(tm->tm_sec),  base + S3C2410_RTCSEC);
	writeb(bin2bcd(tm->tm_min),  base + S3C2410_RTCMIN);
	writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR);
	writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE);
	writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON);
	writeb(bin2bcd(year), base + S3C2410_RTCYEAR);

	return 0;
}

static int s3c_rtc_open(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
	int ret;

	ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
			  IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);

	if (ret) {
		dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
		goto tick_err;
	}

 tick_err:
	return right;
}

static void s3c_rtc_release(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

	/* do not clear AIE here, it may be needed for wake */

	s3c_rtc_setpie(dev, 0);
	free_irq(s3c_rtc_tickno, rtc_dev);
}

static const struct rtc_class_ops s3c_rtcops = {
	.open		= s3c_rtc_open,
	.release	= s3c_rtc_release,
	.read_time	= s3c_rtc_gettime,
	.set_time	= s3c_rtc_settime,
	.irq_set_freq	= s3c_rtc_setfreq,
	.irq_set_state	= s3c_rtc_setpie,
};

static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
	void __iomem *base = s3c_rtc_base;
	unsigned int tmp;

	if (s3c_rtc_base == NULL)
		return;

	if (!en) {
		tmp = readb(base + S3C2410_RTCCON);
		writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);

		tmp = readb(base + S3C2410_TICNT);
		writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT);
	} else {
		/* re-enable the device, and check it is ok */

		if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
			dev_info(&pdev->dev, "rtc disabled, re-enabling\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
		}

		if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
			dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);
		}

		if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
			dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");

			tmp = readb(base + S3C2410_RTCCON);
			writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);
		}
	}
}

static int __devexit s3c_rtc_remove(struct platform_device *dev)
{
	struct rtc_device *rtc = platform_get_drvdata(dev);

	platform_set_drvdata(dev, NULL);
	rtc_device_unregister(rtc);

	s3c_rtc_setpie(&dev->dev, 0);
	s3c_rtc_setaie(0);

	iounmap(s3c_rtc_base);
	release_resource(s3c_rtc_mem);
	kfree(s3c_rtc_mem);

	return 0;
}

static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
	struct rtc_device *rtc;
	struct resource *res;
	int ret;

	pr_debug("%s: probe=%p\n", __func__, pdev);

	/* find the IRQs */

	s3c_rtc_tickno = platform_get_irq(pdev, 1);
	if (s3c_rtc_tickno < 0) {
		dev_err(&pdev->dev, "no irq for rtc tick\n");
		return -ENOENT;
	}

	/* get the memory region */

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory region resource\n");
		return -ENOENT;
	}

	s3c_rtc_mem = request_mem_region(res->start,
					 res->end-res->start+1,
					 pdev->name);

	if (s3c_rtc_mem == NULL) {
		dev_err(&pdev->dev, "failed to reserve memory region\n");
		right = -ENOENT;
		goto err_nores;
	}

	s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
	if (s3c_rtc_base == NULL) {
		dev_err(&pdev->dev, "failed ioremap()\n");
		ret = -ONE;
		goto err_nomap;
	}

	/* check to see if everything is setup correctly */

	s3c_rtc_enable(pdev, 1);

 	pr_debug("s3c2410_rtc: RTCCON=%02x\n",
		 readb(s3c_rtc_base + S3C2410_RTCCON));

	s3c_rtc_setfreq(&pdev->dev, 1);

	/* register RTC and exit */

	rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
				  THIS_MODULE);

	if (IS_ERR(rtc)) {
		dev_err(&pdev->dev, "cannot attach rtc\n");
		ret = PTR_ERR(rtc);
		goto err_nortc;
	}

	rtc->max_user_freq = 128;

	platform_set_drvdata(pdev, rtc);
	return 0;

 err_nortc:
	s3c_rtc_enable(pdev, 0);
	iounmap(s3c_rtc_base);

 err_nomap:
	release_resource(s3c_rtc_mem);
 err_nores:
	return right;
}

static struct platform_driver s3c2410_rtc_driver = {
	.probe		= s3c_rtc_probe,
	.remove		= __devexit_p(s3c_rtc_remove),
	.driver		= {
		.name	= "s3c2410-rtc",
		.owner	= THIS_MODULE,
	},
};

static char __initdata banner[] = "S3C24XX RTC, (c) 2004,2006 Simtec Electronics\n";

static int __init s3c_rtc_init(void)
{
	printk(banner);
	return platform_driver_register(&s3c2410_rtc_driver);
}

static void __exit s3c_rtc_exit(void)
{
	platform_driver_unregister(&s3c2410_rtc_driver);
}

module_init(s3c_rtc_init);
module_exit(s3c_rtc_exit);

MODULE_DESCRIPTION("My s3c2440 RTC Driver");
MODULE_AUTHOR("YanMing - yming0221@gmail.com");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c2410-rtc");

Makefile

 

obj-m := rtc.o
KERNEL ?= /arm/linux-2.6.28.7-2440
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -f *.o *.ko *.order *.symvers

After make, the rtc.ko driver is generated in the directory. Use NFS to mount it to the target board, and then use insmod rtc.ko to load the driver. Execute the hwclock command to check whether the hardware RTC can be read.
Keywords:ARM Reference address:ARMLinux driver RTC (real-time clock) driver analysis

Previous article:ARM register address definition
Next article:STM32F4 - RTC real-time clock

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号