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
3. Introduction to RTC registers
Real Time Clock Control Register (RTCCON) - REAL TIME CLOCK CONTROL REGISTER
Tick Time Count Register (TICNT) - TICK TIME COUNT REGISTER
RTC Alarm Control Register (RTCALM) - RTC ALARM CONTROL REGISTER
Alarm Seconds Register (ALMSEC) - ALARM SECOND DATA REGISTER
Alarm Minute Count Register (ALMMIN) - ALARM MIN DATA REGISTER
ALARM HOUR DATA REGISTER
ALARM DATE DATA REGISTER
Alarm Month Data Register (ALMMON) - ALARM MON DATA REGISTER
ALARM YEAR DATA REGISTER
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.
Previous article:ARM register address definition
Next article:STM32F4 - RTC real-time clock
- Popular Resources
- Popular amplifiers
- Learn ARM development(14)
- Learn ARM development(15)
- Learn ARM development(16)
- Learn ARM development(17)
- Learn ARM development(18)
- Embedded system debugging simulation tool
- A small question that has been bothering me recently has finally been solved~~
- Learn ARM development (1)
- Learn ARM development (2)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- Learn ARM development(14)
- Learn ARM development(15)
- Analysis of the application of several common contact parts in high-voltage connectors of new energy vehicles
- Wiring harness durability test and contact voltage drop test method
- From probes to power supplies, Tektronix is leading the way in comprehensive innovation in power electronics testing
- From probes to power supplies, Tektronix is leading the way in comprehensive innovation in power electronics testing
- Sn-doped CuO nanostructure-based ethanol gas sensor for real-time drunk driving detection in vehicles
- Design considerations for automotive battery wiring harness
- Do you know all the various motors commonly used in automotive electronics?
- What are the functions of the Internet of Vehicles? What are the uses and benefits of the Internet of Vehicles?
- Moving bricks after work
- Caution! Don’t be fooled by long-term drift and hysteresis of voltage references
- Typical circuit of LM258 op amp
- [Lianshengde W801-KIT Experience] Unofficial debugger debugging experience
- 【Canaan K510】+Data acquisition and development environment construction
- When the company became the leader in microcontroller distribution, I was still doing the agency business.
- Determine direction based on the X-NUCLEO-IKS01A3 magnetic field strength value
- How to determine the power or IPP of a TVS tube
- Half-bridge driver
- [Reprint] PWM series: complementary PWM and center-aligned mode