ARM-Linux駆動--RTC(リアルタイムクロック)駆動解析
26368 ワード
ハードウェアプラットフォーム:FL 2440(S 3 C 2440)
カーネルバージョン:Linux 2.6.28
ホストプラットフォーム:Ubuntu 11.04
カーネルバージョン:Linux 2.6.39
クロスコンパイラバージョン:arm-linux-gcc 3.4.1
オリジナル作品、転載は出典http://blog.csdn.net/yming0221/article/details/6584285を明記してください
1、リアルタイムクロックの概要
リアルタイムクロック(RTC)ユニットは、電源が切れた場合にボタンバッテリを用いて計時動作を継続することができる.RTCは、STRB/DRB ARM動作を使用して、CPUにバイナリ10進数の8ビットデータを送信する.このデータには、秒、分、時、日付、日、月、年の時間情報が含まれます.アラーム機能を実行できます.
2、リアルタイムクロック操作
以下はRTCモジュールの回路図です
3、RTCレジスタ紹介
リアルタイムクロック制御レジスタ(RTCCON)-REAL TIME CLOCK CONTROL REGISTER
タクトタイムカウントレジスタ(TICNT)-TICK TIME COUNT REGISTER
RTCアラーム制御レジスタ(RTCALM)-RTC ALARM CONTROL REGISTER
アラーム秒数レジスタ(ALMSEC)-ALARM SECOND DATA REGISTER
アラーム分カウントレジスタ(ALMMIN)-ALARM MIN DATA REGISTER
アラーム時データレジスタ(ALMHOUR)-ALARM HOUR DATA REGISTER
アラーム日付データレジスタ(ALMDATE)-ALARM DATE DATA REGISTER
アラーム月数データレジスタ(ALMMON)-ALARM MON DATA REGISTER
アラーム年数データレジスタ(ALMYEAR)-ALARM YEAR DATA REGISTER
BCDデータレジスタのフォーマットはアラームレジスタと同じ構造であるが,対応するアドレスが異なる.
BCD秒レジスタ(BCDSEC)-BCD SECOND REGISTERアドレス:0 x 57000070(L)0 x 57000073(B)
BCD分レジスタ(BCDMIN)-BCD MINUTE REGISTERアドレス:0 x 57000074(L)0 x 57000077(B)
BCD時間レジスタ(BCDHOUR)-BCD HOUR REGISTERアドレス:0 x 57000078(L)0 x 5700007 B(B)
BCD日付レジスタ(BCDDATE)-BCD DATE REGISTERアドレス:0 x 5700007 C(L)0 x 5700007 F(B)
BCDデーレジスタ(BCDDAY)-BCD DAY REGISTERアドレス:0 x 57000080(L)0 x 57000083(B)
BCD月レジスタ(BCDMON)-BCD MONTH REGISTERアドレス:0 x 57000084(L)0 x 57000087(B)
BCD年レジスタ(BCDYEAR)-BCD YEAR REGISTERアドレス:0 x 57000088(L)0 x 5700008 B(B)
4、駆動インスタンス分析
駆動を理解しやすくするため、現在このRTC駆動は計時機能のみが完了しており、対応するアラーム機能は追加されておらず、電源管理機能も追加されておらず、不足している機能は今後改善されます.
次に、ドライバの概要を説明します.
まずRTC駆動の構造体、/include/linux/platform_device.hにおいて、以下のように
駆動中に対応する構造体を定義する
platform_driver_register()とplatform_driver_unregister()関数は/drivers/base/platform.cで実現した.
platform_driver_register()関数の役割はplatform_ですdriverのdriverのprobe、removeなどはインタフェース関数を提供します
次にRTCプラットフォーム駆動プローブ関数s 3 c_rtc_probe、次の関数定義に使用しました_devinitの役割は、コンパイラにコードを最適化し、それを和のメモリ位置に配置し、メモリの消費量を削減し、カーネルの効率を高めることです.
probe関数はplarform_を受信しましたdeviceというパラメータは,そこから必要な情報を抽出する必要がある.カーネルから提供されるplatformを呼び出すのが一般的です.get_义齿get_IRqなどの関数を使用して、関連情報を取得します.platformを介してget_resourceはデバイスの開始アドレスを取得した後、request_を行うことができます.mem_regionやioremapなどの操作を行い、アプリケーションが操作できるようにします.platformでget_IRqはデバイスの割り込み番号を取得するとrequest_を呼び出すことができるIRq関数はシステムに割り込みを申請する.これらの操作は、デバイスドライバで一般的に完了します.
次はrtc_class_opsは、RTCデバイスクラスがRTC駆動コア部分で定義したRTCデバイスクラスを操作する構造体であり、文字デバイスの駆動中のfile_に類似するOperationsが文字デバイスを操作するという意味です.この構造体はrtc.hでは、RTCに対する操作は、主にオン、オフ、設定または取得時間、設定または取得アラーム、ビート時間カウント値の設定などがあり、この構成内インタフェース関数の実現は以下の通りである
Makefileファイル
make後にディレクトリの下でrtcを生成する.ko駆動は、NFSを利用してターゲットボードに掛ける、insmod rtc.koドライバはロードでき、hwclockコマンドを実行し、ハードウェアのRTCを読み取ることができるかどうかを確認します.
カーネルバージョン:Linux 2.6.28
ホストプラットフォーム:Ubuntu 11.04
カーネルバージョン:Linux 2.6.39
クロスコンパイラバージョン:arm-linux-gcc 3.4.1
オリジナル作品、転載は出典http://blog.csdn.net/yming0221/article/details/6584285を明記してください
1、リアルタイムクロックの概要
リアルタイムクロック(RTC)ユニットは、電源が切れた場合にボタンバッテリを用いて計時動作を継続することができる.RTCは、STRB/DRB ARM動作を使用して、CPUにバイナリ10進数の8ビットデータを送信する.このデータには、秒、分、時、日付、日、月、年の時間情報が含まれます.アラーム機能を実行できます.
2、リアルタイムクロック操作
以下はRTCモジュールの回路図です
3、RTCレジスタ紹介
リアルタイムクロック制御レジスタ(RTCCON)-REAL TIME CLOCK CONTROL REGISTER
タクトタイムカウントレジスタ(TICNT)-TICK TIME COUNT REGISTER
RTCアラーム制御レジスタ(RTCALM)-RTC ALARM CONTROL REGISTER
アラーム秒数レジスタ(ALMSEC)-ALARM SECOND DATA REGISTER
アラーム分カウントレジスタ(ALMMIN)-ALARM MIN DATA REGISTER
アラーム時データレジスタ(ALMHOUR)-ALARM HOUR DATA REGISTER
アラーム日付データレジスタ(ALMDATE)-ALARM DATE DATA REGISTER
アラーム月数データレジスタ(ALMMON)-ALARM MON DATA REGISTER
アラーム年数データレジスタ(ALMYEAR)-ALARM YEAR DATA REGISTER
BCDデータレジスタのフォーマットはアラームレジスタと同じ構造であるが,対応するアドレスが異なる.
BCD秒レジスタ(BCDSEC)-BCD SECOND REGISTERアドレス:0 x 57000070(L)0 x 57000073(B)
BCD分レジスタ(BCDMIN)-BCD MINUTE REGISTERアドレス:0 x 57000074(L)0 x 57000077(B)
BCD時間レジスタ(BCDHOUR)-BCD HOUR REGISTERアドレス:0 x 57000078(L)0 x 5700007 B(B)
BCD日付レジスタ(BCDDATE)-BCD DATE REGISTERアドレス:0 x 5700007 C(L)0 x 5700007 F(B)
BCDデーレジスタ(BCDDAY)-BCD DAY REGISTERアドレス:0 x 57000080(L)0 x 57000083(B)
BCD月レジスタ(BCDMON)-BCD MONTH REGISTERアドレス:0 x 57000084(L)0 x 57000087(B)
BCD年レジスタ(BCDYEAR)-BCD YEAR REGISTERアドレス:0 x 57000088(L)0 x 5700008 B(B)
4、駆動インスタンス分析
駆動を理解しやすくするため、現在このRTC駆動は計時機能のみが完了しており、対応するアラーム機能は追加されておらず、電源管理機能も追加されておらず、不足している機能は今後改善されます.
次に、ドライバの概要を説明します.
まずRTC駆動の構造体、/include/linux/platform_device.hにおいて、以下のように
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;
};
駆動中に対応する構造体を定義する
static struct platform_driver s3c2410_rtc_driver = {
.probe = s3c_rtc_probe,//RTC
.remove = __devexit_p(s3c_rtc_remove),//RTC
.driver = {
.name = "s3c2410-rtc",
.owner = THIS_MODULE,
},
};
以下は、駆動中の駆動の初期化および終了関数です.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);
}
platform_driver_register()とplatform_driver_unregister()関数は/drivers/base/platform.cで実現した.
platform_driver_register()関数の役割はplatform_ですdriverのdriverのprobe、removeなどはインタフェース関数を提供します
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);//
}
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
次にRTCプラットフォーム駆動プローブ関数s 3 c_rtc_probe、次の関数定義に使用しました_devinitの役割は、コンパイラにコードを最適化し、それを和のメモリ位置に配置し、メモリの消費量を削減し、カーネルの効率を高めることです.
probe関数はplarform_を受信しましたdeviceというパラメータは,そこから必要な情報を抽出する必要がある.カーネルから提供されるplatformを呼び出すのが一般的です.get_义齿get_IRqなどの関数を使用して、関連情報を取得します.platformを介してget_resourceはデバイスの開始アドレスを取得した後、request_を行うことができます.mem_regionやioremapなどの操作を行い、アプリケーションが操作できるようにします.platformでget_IRqはデバイスの割り込み番号を取得するとrequest_を呼び出すことができるIRq関数はシステムに割り込みを申請する.これらの操作は、デバイスドライバで一般的に完了します.
static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;// rtc_device , /include/linux/rtc.h
struct resource *res;// , /include/linux/ioport.h
int ret;
pr_debug("%s: probe=%p
", __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
");
return -ENOENT;
}
/* get the memory region */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);// RTC IO
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory region resource
");
return -ENOENT;
}
// ,res struct resource ,
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
");
ret = -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()
");
ret = -EINVAL;
goto err_nomap;
}
/* check to see if everything is setup correctly */
s3c_rtc_enable(pdev, 1);// RTCCON ,
pr_debug("s3c2410_rtc: RTCCON=%02x
",
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);// RTC RTC , s3c_rtcops
if (IS_ERR(rtc)) {
dev_err(&pdev->dev, "cannot attach rtc
");
ret = PTR_ERR(rtc);
goto err_nortc;
}
rtc->max_user_freq = 128;// RTC TICNT
// RTC , /include/linux/platform_device.h
//#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data)), /include/linux/device.h ,
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 ret;
}
以下は/include/linux/ioport.hにおけるstruct resource構造体定義struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
これはdev_ですset_drvdata()の関数定義:static inline void dev_set_drvdata(struct device *dev, void *data)
{
dev->driver_data = data;
}
次はs 3 c_rtc_Probe()関数で使用される2つの関数s 3 c_rtc_enable()とs 3 c_rtc_setfreq() static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
void __iomem *base = s3c_rtc_base;//__iomem
unsigned int tmp;
if (s3c_rtc_base == NULL)
return;
//en en==0,
if (!en) {
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);// RTCCON , RTC ,
tmp = readb(base + S3C2410_TICNT);
writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT);// TICNT ,
} else {
/* re-enable the device, and check it is ok */
//en!=0 , , RTC
if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){//RTCCON 0 0, 1,
dev_info(&pdev->dev, "rtc disabled, re-enabling
");
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
");
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);// RTCCON 2 0, BCD BCD
}
if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
dev_info(&pdev->dev, "removing RTCCON_CLKRST
");
tmp = readb(base + S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);//RTC
}
}
}
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;
}
は、RTCデバイスクラスの動作に続く.次はrtc_class_opsは、RTCデバイスクラスがRTC駆動コア部分で定義したRTCデバイスクラスを操作する構造体であり、文字デバイスの駆動中のfile_に類似するOperationsが文字デバイスを操作するという意味です.この構造体はrtc.hでは、RTCに対する操作は、主にオン、オフ、設定または取得時間、設定または取得アラーム、ビート時間カウント値の設定などがあり、この構成内インタフェース関数の実現は以下の通りである
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オープンデバイス関数s 3 c_rtc_open() static int s3c_rtc_open(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);// RTC
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
", s3c_rtc_tickno, ret);
goto tick_err;
}
tick_err:
return ret;
}
RTC TICKタクトタイム割込みサービスプログラム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オフデバイス関数s 3 c_rtc_release() static void s3c_rtc_release(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);// RTC
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);
}
s3c_rtc_setpie()関数、この関数の主な役割はパラメータに基づいてTICNTレジスタの最高位を設定し、パラメータは0、禁止イネーブル、パラメータは1、イネーブルstatic int s3c_rtc_setpie(struct device *dev, int enabled)
{
unsigned int tmp;
pr_debug("%s: pie=%d
", __func__, enabled);
spin_lock_irq(&s3c_rtc_pie_lock);
tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;// TICNT 0
if (enabled)
tmp |= S3C2410_TICNT_ENABLE;
writeb(tmp, s3c_rtc_base + S3C2410_TICNT);//
spin_unlock_irq(&s3c_rtc_pie_lock);
return 0;
}
次の2つの関数はBCDレジスタの設定と読み出しの時間であり、論理は簡単であり、対応するレジスタの値を読み出して設定するだけである.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
",
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
",
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
");
return -EINVAL;
}
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;
}
ここではRTC駆動の計時機能が実現され、アラーム機能はまだ完了していない.次はこのドライバソースコードです#include <linux/module.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/clk.h>
#include <linux/log2.h>
#include <mach/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/plat-s3c/regs-rtc.h>
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
", __func__, to);
tmp = readb(s3c_rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;
if (to)
tmp |= S3C2410_RTCALM_ALMEN;
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
", __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
",
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
",
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
");
return -EINVAL;
}
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
", s3c_rtc_tickno, ret);
goto tick_err;
}
tick_err:
return ret;
}
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
");
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
");
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
");
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
", __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
");
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
");
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
");
ret = -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()
");
ret = -EINVAL;
goto err_nomap;
}
/* check to see if everything is setup correctly */
s3c_rtc_enable(pdev, 1);
pr_debug("s3c2410_rtc: RTCCON=%02x
",
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
");
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 ret;
}
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
";
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 - [email protected]");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c2410-rtc");
Makefileファイル
obj-m := rtc.o
KERNELDIR ?= /arm/linux-2.6.28.7-2440
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -f *.o *.ko *.order *.symvers
make後にディレクトリの下でrtcを生成する.ko駆動は、NFSを利用してターゲットボードに掛ける、insmod rtc.koドライバはロードでき、hwclockコマンドを実行し、ハードウェアのRTCを読み取ることができるかどうかを確認します.