公司开发个网站有哪些,镇江网站制作服务,做个小网站 虚拟空间 买服务器,地产网站设计目录
五、PWM
六、RTC 五、PWM PWM(Pulse Width Modulation#xff0c;脉宽调制器)#xff0c;顾名思义就是一个输出脉冲宽度可以调整的硬件器件#xff0c;其实它不仅脉冲宽度可调#xff0c;频率也可以调整。它的核心部件是一个硬件定时器#xff0c;其工作原理可以用…目录
五、PWM
六、RTC 五、PWM PWM(Pulse Width Modulation脉宽调制器)顾名思义就是一个输出脉冲宽度可以调整的硬件器件其实它不仅脉冲宽度可调频率也可以调整。它的核心部件是一个硬件定时器其工作原理可以用下图来说明。 PWM 管脚默认输出高电平在图中的时刻1将数值设为 109比较值设为109在时刻2启动定时器PWM 立即输出低电平在时钟的作用下计数器开始做减法计数当计数值减到和比较值一致时(时刻 3)输出翻转之后一直输出高电平。当计数到达0后(时刻4)再完成一次计数在时刻5 重新从 109 开始计数输出再次变成低电平如此周而复始就形成一个矩形波。波形的周期由计数值决定占空比由比较值决定。在图中占空比为 110/160如果用于计数的时钟频率为 freq那么波形的频率就为freg/160。 FS4412使用了其中一路PWM 输出 (PWMO对应管脚是GPD0.0)接蜂鸣器其电路原理图如图所示。
PWM0的内部结构如图所示。 PWM 的输入时钟是 PCLK经过8位的预分频后再经过第二次分频的时钟最终给到PWMO所对应的计数器0。TCNTB0是计数值寄存器用于控制PWM输出波形的频率TCMPB0是比较寄存器用于控制 PWM 输出波形的占空比其输出还可以选择是否反向是否有死区控制等(关于死区暂时不做介绍感兴趣的同学可以了解一下电机控制)。 接下来以PWM0为例来讨论 PWM 的各寄存器重点关注相关位) TCON寄存器主要用timer0 TCON寄存器的比特1比较特殊当要手动更新TCNTBO或TCMPBO的值时先将对应的值写入寄存器然后将 TCON 寄存器的比特1先置1再清0这样新的值才会生效。 设备树节点的源码如下。 beep139D0000 {compatible fs4412,fspwm;reg0x139D0000 0x24;clocksclock 336;clock-namestimers;pinctrl-0pwm0 out;pinctrI-names default;
}; 因为 PWM 使用到了一个时钟在这里的 clocks 属性指定了 PWM 所使用的时钟clock-names 属性则给该时钟取了一个名字叫 timers方便在驱动中获取该时钟。时钟的编号可以查看Documentation/devicetree/bindings/clock/exynos4-clock.txt内核文档。pinctrl-0属性则描述了PWM使用的GPIO管脚它指定管脚是pwm0_out相应的设备树节点定义在arch/arm/boot/dts/exynos4x12-pinctrldtsi内容如下。 pwm0_out:pwm0-out {samsung,pins gpd0-0;samsung,pin-function 2;samsung,pin-drv 0;samsung,pin-drv0;}; 有了这个节点的定义后我们在驱动中可以利用 pinctrl 子系统的API接口函数快捷地将对应管脚设置为想要的配置方式(我们这里就不详细讨论 pinctrl 子系统但最常用的一个API将会在后面说明)。上面的节点表示将 GPD0.0管脚配置为功能2即PWM0 的输出不上拉驱动强度为最低级别。pinctrl-names 属性是给管脚命名方便在驱动中获取。 下面就是时钟子系统和 pinctrl 子系统中最常用的函数。
struct cik *clk_get(struct device *dev, const char *id);
void clk_put (struct clk *clk);
unsigned long clk_get_rate(strut clk *clk);
int clk_set_rate(struct clk *clk, unsigned long rate);
int clk_prepare_enable(struct clk *clk);
void clk_disable_unprepare(struct clk *clk);
struct pinctrl * devm_pinctrl_get_select_default(struct device *dev); cik_get: 从dev 中的设备节点中获取名字为 id 的时钟返回 structclk 结构对象地址用ISERR宏判断是否错误用PTR ERR返回错误代码。 clkput:释放clk。 clk_get_rate:获取时钟 clk 的频率. clk_set_rate:设置时钟clk 的频率。 clk_prepare_enable:使能时钟。 clk_disable_unprepare:禁止时钟。 devm_pinctrl_get_select_default: 从 dev 中的设备节点中获取 pinctrl 管脚并进行指定的配置。 有了上面的基础之后就可以编写相应的驱动代码了。下面的驱动用于驱动蜂鸣器发声所以应用层应该能够启动 PWM、停止PWM、设置 PWM 输出波形的频率占空比恒定为50%。关于频率的设置可以利用下面的公式: out freg PCIK /(Prescaler0 1)/ Divider MUX0 / (TCNTBO1) 主要的代码如下 #include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/fs.h
#include linux/cdev.h#include linux/slab.h
#include linux/ioctl.h
#include linux/uaccess.h#include linux/io.h
#include linux/ioport.h
#include linux/platform_device.h#include linux/of.h
#include linux/clk.h
#include linux/pinctrl/consumer.h#include fspwm.h#define FSPWM_MAJOR 256
#define FSPWM_MINOR 7
#define FSPWM_DEV_NAME fspwmstruct fspwm_dev {unsigned int __iomem *tcfg0;unsigned int __iomem *tcfg1;unsigned int __iomem *tcon;unsigned int __iomem *tcntb0;unsigned int __iomem *tcmpb0;unsigned int __iomem *tcnto0;struct clk *clk;unsigned long freq;struct pinctrl *pctrl;atomic_t available;struct cdev cdev;
};static int fspwm_open(struct inode *inode, struct file *filp)
{struct fspwm_dev *fspwm container_of(inode-i_cdev, struct fspwm_dev, cdev);filp-private_data fspwm;if (atomic_dec_and_test(fspwm-available))return 0;else {atomic_inc(fspwm-available);return -EBUSY;}
}static int fspwm_release(struct inode *inode, struct file *filp)
{struct fspwm_dev *fspwm filp-private_data;atomic_inc(fspwm-available);return 0;
}static long fspwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct fspwm_dev *fspwm filp-private_data;unsigned int div;if (_IOC_TYPE(cmd) ! FSPWM_MAGIC)return -ENOTTY;switch (cmd) {case FSPWM_START:writel(readl(fspwm-tcon) | 0x1, fspwm-tcon);break;case FSPWM_STOP:writel(readl(fspwm-tcon) ~0x1, fspwm-tcon);break;case FSPWM_SET_FREQ:if (arg fspwm-freq || arg 0)return -ENOTTY;div fspwm-freq / arg - 1;writel(div, fspwm-tcntb0);writel(div / 2, fspwm-tcmpb0);writel(readl(fspwm-tcon) | 0x2, fspwm-tcon);writel(readl(fspwm-tcon) ~0x2, fspwm-tcon);break;default:return -ENOTTY;}return 0;
}static struct file_operations fspwm_ops {.owner THIS_MODULE,.open fspwm_open,.release fspwm_release,.unlocked_ioctl fspwm_ioctl,
};static int fspwm_probe(struct platform_device *pdev)
{int ret;dev_t dev;struct fspwm_dev *fspwm;struct resource *res;unsigned int prescaler0;dev MKDEV(FSPWM_MAJOR, FSPWM_MINOR);ret register_chrdev_region(dev, 1, FSPWM_DEV_NAME);if (ret)goto reg_err;fspwm kzalloc(sizeof(struct fspwm_dev), GFP_KERNEL);if (!fspwm) {ret -ENOMEM;goto mem_err;}platform_set_drvdata(pdev, fspwm);cdev_init(fspwm-cdev, fspwm_ops);fspwm-cdev.owner THIS_MODULE;ret cdev_add(fspwm-cdev, dev, 1);if (ret)goto add_err;res platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {ret -ENOENT;goto res_err;}fspwm-tcfg0 ioremap(res-start, resource_size(res));if (!fspwm-tcfg0) {ret -EBUSY;goto map_err;}fspwm-tcfg1 fspwm-tcfg0 1;fspwm-tcon fspwm-tcfg0 2;fspwm-tcntb0 fspwm-tcfg0 3;fspwm-tcmpb0 fspwm-tcfg0 4;fspwm-tcnto0 fspwm-tcfg0 5;fspwm-clk clk_get(pdev-dev, timers);if (IS_ERR(fspwm-clk)) {ret PTR_ERR(fspwm-clk);goto get_clk_err;}ret clk_prepare_enable(fspwm-clk);if (ret 0)goto enable_clk_err;fspwm-freq clk_get_rate(fspwm-clk);prescaler0 readl(fspwm-tcfg0) 0xFF;writel((readl(fspwm-tcfg1) ~0xF) | 0x4, fspwm-tcfg1); /* 1/16 */fspwm-freq / (prescaler0 1) * 16; /* 3125000 */writel((readl(fspwm-tcon) ~0xF) | 0x8, fspwm-tcon); /* auto-reload */fspwm-pctrl devm_pinctrl_get_select_default(pdev-dev);atomic_set(fspwm-available, 1);return 0;enable_clk_err:clk_put(fspwm-clk);
get_clk_err:iounmap(fspwm-tcfg0);
map_err:
res_err:cdev_del(fspwm-cdev);
add_err:kfree(fspwm);
mem_err:unregister_chrdev_region(dev, 1);
reg_err:return ret;
}static int fspwm_remove(struct platform_device *pdev)
{dev_t dev;struct fspwm_dev *fspwm platform_get_drvdata(pdev);dev MKDEV(FSPWM_MAJOR, FSPWM_MINOR);clk_disable_unprepare(fspwm-clk);clk_put(fspwm-clk);iounmap(fspwm-tcfg0);cdev_del(fspwm-cdev);kfree(fspwm);unregister_chrdev_region(dev, 1);return 0;
}static const struct of_device_id fspwm_of_matches[] {{ .compatible fs4412,fspwm, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fspwm_of_matches);struct platform_driver fspwm_drv { .driver { .name fspwm,.owner THIS_MODULE,.of_match_table of_match_ptr(fspwm_of_matches),}, .probe fspwm_probe,.remove fspwm_remove,
};module_platform_driver(fspwm_drv);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name e-mail);
MODULE_DESCRIPTION(PWM driver);#ifndef _FSPWM_H
#define _FSPWM_H#define FSPWM_MAGIC f#define FSPWM_START _IO(FSPWM_MAGIC, 0)
#define FSPWM_STOP _IO(FSPWM_MAGIC, 1)
#define FSPWM_SET_FREQ _IOW(FSPWM_MAGIC, 2, unsigned int)#endif代码第 27 行至第 32 行是对应的寄存器虚拟地址成员变量。代码第 33 行是获得的PCLK时钟对象指针。代码第 34 行是计算后得到的送入定时器0的时钟频率。代码第35行是PWM输出管脚所对应的pinctrl对象指针。 在 fspwm_probe 函数中和前面一样也是注册字符设备、获取IO资源并进行映射等操作。代码第 142行至第 151 行是获取 PCLK 时钟然后使能和获取频率的代码。代码第 153 行获得了预分频值。代码第 154 行将二级分频设置为 16代码第 155 行则计算得到了输入到定时器0的时钟频率。代码第 156 行将定时器设置为自动重装模式用于持续输出PWM 波形。代码第158 行将 GPDO.0管脚设置为 PWMO的输出。 在fspwm_ioctl 函数中FSPWM_START 是启动 PWM 的命令将TCON 的比特0置1即可。FSPWM_STOP 是停止 PWM 的命令将 TCON 的比特0清0即可FSPWM_SET_FREQ 是设置频率的命令首先判断了要设置的频率是否超过了范围和是否合法接下来根据前面的公式计算出了计数值然后设置了 TCNTBO和TCMPBO最后根据前面的描述操作 TCON 的比特 1更新新的计数值和比较值。 测试的应用层头文件代码如下。
#ifndef _MUSIC_H
#define _MUSIC_Htypedef struct
{int pitch; int dimation;
} note;// 1 2 3 4 5 6 7
// C D E F G A B
// 261.6256 293.6648 329.6276 349.2282 391.9954 440 493.8833// C调
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SOL 392
#define LA 440
#define SI 494#define BEAT (60000000 / 120)const note HappyNewYear[] {{DO, BEAT/2}, {DO, BEAT/2}, {DO, BEAT}, {SOL/2, BEAT},{MI, BEAT/2}, {MI, BEAT/2}, {MI, BEAT}, {DO, BEAT},{DO, BEAT/2}, {MI, BEAT/2}, {SOL, BEAT}, {SOL, BEAT},{FA, BEAT/2}, {MI, BEAT/2}, {RE, BEAT}, {RE, BEAT},{RE, BEAT/2}, {MI, BEAT/2}, {FA, BEAT}, {FA, BEAT},{MI, BEAT/2}, {RE, BEAT/2}, {MI, BEAT}, {DO, BEAT},{DO, BEAT/2}, {MI, BEAT/2}, {RE, BEAT}, {SOL/2, BEAT},{SI/2, BEAT/2}, {RE, BEAT/2}, {DO, BEAT}, {DO, BEAT},
};#endifnote 表示的是一个音符pitch 表示音高dimation 表示音符演奏的时间HappyNewYear是《新年好》歌曲的各音符表示。测试的应用层代码如下
#include stdio.h
#include stdlib.h
#include sys/types.h
#include sys/stat.h
#include sys/ioctl.h
#include fcntl.h
#include errno.h#include fspwm.h
#include music.h#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))int main(int argc, char *argv[])
{int i;int fd;int ret;unsigned int freq;fd open(/dev/pwm, O_RDWR);if (fd -1)goto fail;ret ioctl(fd, FSPWM_START);if (ret -1)goto fail;for (i 0; i ARRAY_SIZE(HappyNewYear); i) {ret ioctl(fd, FSPWM_SET_FREQ, HappyNewYear[i].pitch);if (ret -1)goto fail;usleep(HappyNewYear[i].dimation);}ret ioctl(fd, FSPWM_STOP);if (ret -1)goto fail;exit(EXIT_SUCCESS);
fail:perror(pwm test);exit(EXIT_FAILURE);
} 代码中首先打开了设备然后启动了 PWM 输出在 for 循环中依次取出乐曲中的各个音符然后设置频率再延时指定的时间这就完成了乐曲的演奏最后停止了 PWM。 编译和测试的命令如下如果工作正常会听到《新年好》的音乐声。 效果我发了个视频还没审核过感兴趣的可以后面看我在csdn上的视频
六、RTC RTC(Real Time Clock实时时钟) 用于产生年、月、日、时、分、秒的硬件器件。现在的计算机系统上几乎都包含了这个器件有的 RTC 还带闹钟功能。它的工作原理也非常简单就是将 1Hz 的时钟用于计数按照不同的进制产生进位从而生成上面的时间。 Exynos4412 上自带一个RTC带闹钟的功能为了简单我们省略对这部分内容的讨论只关心和时间相关的寄存器见下表。 上面只列出了秒时间的寄存器类似的还有分、天、月等。上面涉及一个BCD 码,所谓的BCD码就是用十六进制来表示十进制比如0x59就是十进制的59。Linux内核提供了两者之间相互转换的宏bcd2bin 是将 BCD码转换成一般的整形数bin2bcd 则相反。 下面是RTC的设备树节点。 rtc100700000 {compatible fs4412,fsrtcreg 0x10070000 0x100;clocks clock 346;clock-names rtc;status okay;}; 时钟属性请参照 Documentation/devicetree/bindings/clock/exynos4-clock.txt 内核文档status属性设定为okay是因为该节点在arch/arm/boot/dts/exynos4.dtsi中已经定义过了在那里status 的值为 disabled表示禁止要使能该节点就需要将 status 属性改为 okay。 驱动的实现比较简单在时间的设置方面首先将 RTCCON 寄存器的比特0置1然后将时间值转换成 BCD 码再写入到相应的寄存器最后将 RTCCON 寄存器的比特0清0即可。时间获取则读出寄存器的值然后将 BCD码转成一般的整数即可。关于时间定义了一个结构 struct rtc_time请参见源码的头文件。关键的驱动代码如下
#include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/fs.h
#include linux/cdev.h#include linux/slab.h
#include linux/ioctl.h
#include linux/uaccess.h#include linux/io.h
#include linux/ioport.h
#include linux/platform_device.h#include linux/of.h
#include linux/clk.h
#include linux/pinctrl/consumer.h
#include linux/bcd.h#include fsrtc.h#define FSRTC_MAJOR 256
#define FSRTC_MINOR 8
#define FSRTC_DEV_NAME fsrtcstruct fsrtc_dev {unsigned int __iomem *rtccon;unsigned int __iomem *bcdsec;unsigned int __iomem *bcdmin;unsigned int __iomem *bcdhour;unsigned int __iomem *bcdday;unsigned int __iomem *bcdmon;unsigned int __iomem *bcdyear;struct clk *clk;atomic_t available;struct cdev cdev;
};static int fsrtc_open(struct inode *inode, struct file *filp)
{struct fsrtc_dev *fsrtc container_of(inode-i_cdev, struct fsrtc_dev, cdev);filp-private_data fsrtc;if (atomic_dec_and_test(fsrtc-available))return 0;else {atomic_inc(fsrtc-available);return -EBUSY;}
}static int fsrtc_release(struct inode *inode, struct file *filp)
{struct fsrtc_dev *fsrtc filp-private_data;atomic_inc(fsrtc-available);return 0;
}static long fsrtc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct fsrtc_dev *fsrtc filp-private_data;struct rtc_time time;if (_IOC_TYPE(cmd) ! FSRTC_MAGIC)return -ENOTTY;switch (cmd) {case FSRTC_SET:if (copy_from_user(time, (struct rtc_time __user *)arg, sizeof(struct rtc_time)))return -ENOTTY;writel(readl(fsrtc-rtccon) | 0x1, fsrtc-rtccon);writel(bin2bcd(time.tm_sec ), fsrtc-bcdsec);writel(bin2bcd(time.tm_min ), fsrtc-bcdmin);writel(bin2bcd(time.tm_hour), fsrtc-bcdhour);writel(bin2bcd(time.tm_mday), fsrtc-bcdday);writel(bin2bcd(time.tm_mon ), fsrtc-bcdmon);writel(bin2bcd(time.tm_year - 2000), fsrtc-bcdyear);writel(readl(fsrtc-rtccon) ~0x1, fsrtc-rtccon);break;case FSRTC_GET:time.tm_sec bcd2bin(readl(fsrtc-bcdsec));time.tm_min bcd2bin(readl(fsrtc-bcdmin));time.tm_hour bcd2bin(readl(fsrtc-bcdhour));time.tm_mday bcd2bin(readl(fsrtc-bcdday));time.tm_mon bcd2bin(readl(fsrtc-bcdmon));time.tm_year bcd2bin(readl(fsrtc-bcdyear)) 2000;if (copy_to_user((struct rtc_time __user *)arg, time, sizeof(struct rtc_time)))return -ENOTTY;break;default:return -ENOTTY;}return 0;
}static struct file_operations fsrtc_ops {.owner THIS_MODULE,.open fsrtc_open,.release fsrtc_release,.unlocked_ioctl fsrtc_ioctl,
};static int fsrtc_probe(struct platform_device *pdev)
{int ret;dev_t dev;struct fsrtc_dev *fsrtc;struct resource *res;unsigned int __iomem *regbase;dev MKDEV(FSRTC_MAJOR, FSRTC_MINOR);ret register_chrdev_region(dev, 1, FSRTC_DEV_NAME);if (ret)goto reg_err;fsrtc kzalloc(sizeof(struct fsrtc_dev), GFP_KERNEL);if (!fsrtc) {ret -ENOMEM;goto mem_err;}platform_set_drvdata(pdev, fsrtc);cdev_init(fsrtc-cdev, fsrtc_ops);fsrtc-cdev.owner THIS_MODULE;ret cdev_add(fsrtc-cdev, dev, 1);if (ret)goto add_err;res platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {ret -ENOENT;goto res_err;}regbase ioremap(res-start, resource_size(res));if (!regbase) {ret -EBUSY;goto map_err;}fsrtc-rtccon regbase 16;fsrtc-bcdsec regbase 28;fsrtc-bcdmin regbase 29;fsrtc-bcdhour regbase 30;fsrtc-bcdday regbase 31;fsrtc-bcdmon regbase 33;fsrtc-bcdyear regbase 34;fsrtc-clk clk_get(pdev-dev, rtc);if (IS_ERR(fsrtc-clk)) {ret PTR_ERR(fsrtc-clk);goto get_clk_err;}ret clk_prepare_enable(fsrtc-clk);if (ret 0)goto enable_clk_err;writel(0, fsrtc-rtccon);atomic_set(fsrtc-available, 1);return 0;enable_clk_err:clk_put(fsrtc-clk);
get_clk_err:iounmap(fsrtc-rtccon - 16);
map_err:
res_err:cdev_del(fsrtc-cdev);
add_err:kfree(fsrtc);
mem_err:unregister_chrdev_region(dev, 1);
reg_err:return ret;
}static int fsrtc_remove(struct platform_device *pdev)
{dev_t dev;struct fsrtc_dev *fsrtc platform_get_drvdata(pdev);dev MKDEV(FSRTC_MAJOR, FSRTC_MINOR);clk_disable_unprepare(fsrtc-clk);clk_put(fsrtc-clk);iounmap(fsrtc-rtccon - 16);cdev_del(fsrtc-cdev);kfree(fsrtc);unregister_chrdev_region(dev, 1);return 0;
}static const struct of_device_id fsrtc_of_matches[] {{ .compatible fs4412,fsrtc, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsrtc_of_matches);struct platform_driver fsrtc_drv { .driver { .name fsrtc,.owner THIS_MODULE,.of_match_table of_match_ptr(fsrtc_of_matches),}, .probe fsrtc_probe,.remove fsrtc_remove,
};module_platform_driver(fsrtc_drv);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name e-mail);
MODULE_DESCRIPTION(RTC driver);#ifndef _FSRTC_H
#define _FSRTC_Hstruct rtc_time {int tm_sec;int tm_min;int tm_hour;int tm_mday;int tm_mon;int tm_year;int tm_wday;int tm_yday;
};#define FSRTC_MAGIC f#define FSRTC_SET _IOW(FSRTC_MAGIC, 0, struct rtc_time)
#define FSRTC_GET _IOR(FSRTC_MAGIC, 1, struct rtc_time)#endif上面的代码比较简单在此不再多解释。需要注意的是手册给出的 BCDDAYWEEK和BCDDAY 两个寄存器的地址交换了需要交换过来。另外寄存器存放的年份只有3位所以固定添加了 2000 的偏移。应用层的测试代码也请参见下载资源里面的源码。 测试的结果如下。 其实 Linux 内核针对 RTC有一个现成的框架类似于输入子系统一样我们只需要使用相应的API然后再实现要求的接口函数即可。Exynos4412的 RTC 驱动在内核中也已经实现好了请参见内核源码 drivers/rtc/rtc-s3c.c这个源码留给大家自己去分析。那么我们要如何使用这个驱动呢?首先将设备树节点改为下面的样子。 没错确实这么简单因为在arch/arm/boot/dts/exynos4.dtsi中已经定义过该节点了现在只需要使能该节点即可。另外就是要确认内核中驱动已经被选配参见下面的面置项。 重新编译设备树和内核后复制文件到 TFTP 服务器指定的目录启动开发板使用下面的命令可以确认驱动工作正常。 /* drivers/rtc/rtc-s3c.c** Copyright (c) 2010 Samsung Electronics Co., Ltd.* http://www.samsung.com/** Copyright (c) 2004,2006 Simtec Electronics* Ben Dooks, bensimtec.co.uk* 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.** S3C2410/S3C2440/S3C24XX Internal RTC Driver
*/#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 linux/slab.h
#include linux/of.h
#include linux/uaccess.h
#include linux/io.h#include asm/irq.h
#include rtc-s3c.henum s3c_cpu_type {TYPE_S3C2410,TYPE_S3C2416,TYPE_S3C2443,TYPE_S3C64XX,
};struct s3c_rtc_drv_data {int cpu_type;
};/* I have yet to find an S3C implementation with more than one* of these rtc blocks in */static struct clk *rtc_clk;
static void __iomem *s3c_rtc_base;
static int s3c_rtc_alarmno NO_IRQ;
static int s3c_rtc_tickno NO_IRQ;
static enum s3c_cpu_type s3c_rtc_cpu_type;static DEFINE_SPINLOCK(s3c_rtc_pie_lock);static void s3c_rtc_alarm_clk_enable(bool enable)
{static DEFINE_SPINLOCK(s3c_rtc_alarm_clk_lock);static bool alarm_clk_enabled;unsigned long irq_flags;spin_lock_irqsave(s3c_rtc_alarm_clk_lock, irq_flags);if (enable) {if (!alarm_clk_enabled) {clk_enable(rtc_clk);alarm_clk_enabled true;}} else {if (alarm_clk_enabled) {clk_disable(rtc_clk);alarm_clk_enabled false;}}spin_unlock_irqrestore(s3c_rtc_alarm_clk_lock, irq_flags);
}/* IRQ Handlers */static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
{struct rtc_device *rdev id;clk_enable(rtc_clk);rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);if (s3c_rtc_cpu_type TYPE_S3C64XX)writeb(S3C2410_INTP_ALM, s3c_rtc_base S3C2410_INTP);clk_disable(rtc_clk);s3c_rtc_alarm_clk_enable(false);return IRQ_HANDLED;
}static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
{struct rtc_device *rdev id;clk_enable(rtc_clk);rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);if (s3c_rtc_cpu_type TYPE_S3C64XX)writeb(S3C2410_INTP_TIC, s3c_rtc_base S3C2410_INTP);clk_disable(rtc_clk);return IRQ_HANDLED;
}/* Update control registers */
static int s3c_rtc_setaie(struct device *dev, unsigned int enabled)
{unsigned int tmp;dev_dbg(dev, %s: aie%d\n, __func__, enabled);clk_enable(rtc_clk);tmp readb(s3c_rtc_base S3C2410_RTCALM) ~S3C2410_RTCALM_ALMEN;if (enabled)tmp | S3C2410_RTCALM_ALMEN;writeb(tmp, s3c_rtc_base S3C2410_RTCALM);clk_disable(rtc_clk);s3c_rtc_alarm_clk_enable(enabled);return 0;
}static int s3c_rtc_setfreq(struct device *dev, int freq)
{struct platform_device *pdev to_platform_device(dev);struct rtc_device *rtc_dev platform_get_drvdata(pdev);unsigned int tmp 0;int val;if (!is_power_of_2(freq))return -EINVAL;clk_enable(rtc_clk);spin_lock_irq(s3c_rtc_pie_lock);if (s3c_rtc_cpu_type ! TYPE_S3C64XX) {tmp readb(s3c_rtc_base S3C2410_TICNT);tmp S3C2410_TICNT_ENABLE;}val (rtc_dev-max_user_freq / freq) - 1;if (s3c_rtc_cpu_type TYPE_S3C2416 || s3c_rtc_cpu_type TYPE_S3C2443) {tmp | S3C2443_TICNT_PART(val);writel(S3C2443_TICNT1_PART(val), s3c_rtc_base S3C2443_TICNT1);if (s3c_rtc_cpu_type TYPE_S3C2416)writel(S3C2416_TICNT2_PART(val), s3c_rtc_base S3C2416_TICNT2);} else {tmp | val;}writel(tmp, s3c_rtc_base S3C2410_TICNT);spin_unlock_irq(s3c_rtc_pie_lock);clk_disable(rtc_clk);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;clk_enable(rtc_clk);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 whether 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;}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;dev_dbg(dev, read time %04d.%02d.%02d %02d:%02d:%02d\n,1900 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_mon - 1;clk_disable(rtc_clk);return rtc_valid_tm(rtc_tm);
}static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{void __iomem *base s3c_rtc_base;int year tm-tm_year - 100;dev_dbg(dev, set time %04d.%02d.%02d %02d:%02d:%02d\n,1900 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 -EINVAL;}clk_enable(rtc_clk);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);clk_disable(rtc_clk);return 0;
}static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{struct rtc_time *alm_tm alrm-time;void __iomem *base s3c_rtc_base;unsigned int alm_en;clk_enable(rtc_clk);alm_tm-tm_sec readb(base S3C2410_ALMSEC);alm_tm-tm_min readb(base S3C2410_ALMMIN);alm_tm-tm_hour readb(base S3C2410_ALMHOUR);alm_tm-tm_mon readb(base S3C2410_ALMMON);alm_tm-tm_mday readb(base S3C2410_ALMDATE);alm_tm-tm_year readb(base S3C2410_ALMYEAR);alm_en readb(base S3C2410_RTCALM);alrm-enabled (alm_en S3C2410_RTCALM_ALMEN) ? 1 : 0;dev_dbg(dev, read alarm %d, %04d.%02d.%02d %02d:%02d:%02d\n,alm_en,1900 alm_tm-tm_year, alm_tm-tm_mon, alm_tm-tm_mday,alm_tm-tm_hour, alm_tm-tm_min, alm_tm-tm_sec);/* decode the alarm enable field */if (alm_en S3C2410_RTCALM_SECEN)alm_tm-tm_sec bcd2bin(alm_tm-tm_sec);elsealm_tm-tm_sec -1;if (alm_en S3C2410_RTCALM_MINEN)alm_tm-tm_min bcd2bin(alm_tm-tm_min);elsealm_tm-tm_min -1;if (alm_en S3C2410_RTCALM_HOUREN)alm_tm-tm_hour bcd2bin(alm_tm-tm_hour);elsealm_tm-tm_hour -1;if (alm_en S3C2410_RTCALM_DAYEN)alm_tm-tm_mday bcd2bin(alm_tm-tm_mday);elsealm_tm-tm_mday -1;if (alm_en S3C2410_RTCALM_MONEN) {alm_tm-tm_mon bcd2bin(alm_tm-tm_mon);alm_tm-tm_mon - 1;} else {alm_tm-tm_mon -1;}if (alm_en S3C2410_RTCALM_YEAREN)alm_tm-tm_year bcd2bin(alm_tm-tm_year);elsealm_tm-tm_year -1;clk_disable(rtc_clk);return 0;
}static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{struct rtc_time *tm alrm-time;void __iomem *base s3c_rtc_base;unsigned int alrm_en;clk_enable(rtc_clk);dev_dbg(dev, s3c_rtc_setalarm: %d, %04d.%02d.%02d %02d:%02d:%02d\n,alrm-enabled,1900 tm-tm_year, tm-tm_mon 1, tm-tm_mday,tm-tm_hour, tm-tm_min, tm-tm_sec);alrm_en readb(base S3C2410_RTCALM) S3C2410_RTCALM_ALMEN;writeb(0x00, base S3C2410_RTCALM);if (tm-tm_sec 60 tm-tm_sec 0) {alrm_en | S3C2410_RTCALM_SECEN;writeb(bin2bcd(tm-tm_sec), base S3C2410_ALMSEC);}if (tm-tm_min 60 tm-tm_min 0) {alrm_en | S3C2410_RTCALM_MINEN;writeb(bin2bcd(tm-tm_min), base S3C2410_ALMMIN);}if (tm-tm_hour 24 tm-tm_hour 0) {alrm_en | S3C2410_RTCALM_HOUREN;writeb(bin2bcd(tm-tm_hour), base S3C2410_ALMHOUR);}dev_dbg(dev, setting S3C2410_RTCALM to %08x\n, alrm_en);writeb(alrm_en, base S3C2410_RTCALM);s3c_rtc_setaie(dev, alrm-enabled);clk_disable(rtc_clk);return 0;
}static int s3c_rtc_proc(struct device *dev, struct seq_file *seq)
{unsigned int ticnt;clk_enable(rtc_clk);if (s3c_rtc_cpu_type TYPE_S3C64XX) {ticnt readw(s3c_rtc_base S3C2410_RTCCON);ticnt S3C64XX_RTCCON_TICEN;} else {ticnt readb(s3c_rtc_base S3C2410_TICNT);ticnt S3C2410_TICNT_ENABLE;}seq_printf(seq, periodic_IRQ\t: %s\n, ticnt ? yes : no);clk_disable(rtc_clk);return 0;
}static const struct rtc_class_ops s3c_rtcops {.read_time s3c_rtc_gettime,.set_time s3c_rtc_settime,.read_alarm s3c_rtc_getalarm,.set_alarm s3c_rtc_setalarm,.proc s3c_rtc_proc,.alarm_irq_enable s3c_rtc_setaie,
};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;clk_enable(rtc_clk);if (!en) {tmp readw(base S3C2410_RTCCON);if (s3c_rtc_cpu_type TYPE_S3C64XX)tmp ~S3C64XX_RTCCON_TICEN;tmp ~S3C2410_RTCCON_RTCEN;writew(tmp, base S3C2410_RTCCON);if (s3c_rtc_cpu_type ! TYPE_S3C64XX) {tmp readb(base S3C2410_TICNT);tmp ~S3C2410_TICNT_ENABLE;writeb(tmp, base S3C2410_TICNT);}} else {/* re-enable the device, and check it is ok */if ((readw(baseS3C2410_RTCCON) S3C2410_RTCCON_RTCEN) 0) {dev_info(pdev-dev, rtc disabled, re-enabling\n);tmp readw(base S3C2410_RTCCON);writew(tmp | S3C2410_RTCCON_RTCEN,base S3C2410_RTCCON);}if ((readw(base S3C2410_RTCCON) S3C2410_RTCCON_CNTSEL)) {dev_info(pdev-dev, removing RTCCON_CNTSEL\n);tmp readw(base S3C2410_RTCCON);writew(tmp ~S3C2410_RTCCON_CNTSEL,base S3C2410_RTCCON);}if ((readw(base S3C2410_RTCCON) S3C2410_RTCCON_CLKRST)) {dev_info(pdev-dev, removing RTCCON_CLKRST\n);tmp readw(base S3C2410_RTCCON);writew(tmp ~S3C2410_RTCCON_CLKRST,base S3C2410_RTCCON);}}clk_disable(rtc_clk);
}static int s3c_rtc_remove(struct platform_device *dev)
{s3c_rtc_setaie(dev-dev, 0);clk_unprepare(rtc_clk);rtc_clk NULL;return 0;
}static const struct of_device_id s3c_rtc_dt_match[];static inline int s3c_rtc_get_driver_data(struct platform_device *pdev)
{
#ifdef CONFIG_OFstruct s3c_rtc_drv_data *data;if (pdev-dev.of_node) {const struct of_device_id *match;match of_match_node(s3c_rtc_dt_match, pdev-dev.of_node);data (struct s3c_rtc_drv_data *) match-data;return data-cpu_type;}
#endifreturn platform_get_device_id(pdev)-driver_data;
}static int s3c_rtc_probe(struct platform_device *pdev)
{struct rtc_device *rtc;struct rtc_time rtc_tm;struct resource *res;int ret;int tmp;dev_dbg(pdev-dev, %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 s3c_rtc_tickno;}s3c_rtc_alarmno platform_get_irq(pdev, 0);if (s3c_rtc_alarmno 0) {dev_err(pdev-dev, no irq for alarm\n);return s3c_rtc_alarmno;}dev_dbg(pdev-dev, s3c2410_rtc: tick irq %d, alarm irq %d\n,s3c_rtc_tickno, s3c_rtc_alarmno);/* get the memory region */res platform_get_resource(pdev, IORESOURCE_MEM, 0);s3c_rtc_base devm_ioremap_resource(pdev-dev, res);if (IS_ERR(s3c_rtc_base))return PTR_ERR(s3c_rtc_base);rtc_clk devm_clk_get(pdev-dev, rtc);if (IS_ERR(rtc_clk)) {dev_err(pdev-dev, failed to find rtc clock source\n);ret PTR_ERR(rtc_clk);rtc_clk NULL;return ret;}clk_prepare_enable(rtc_clk);/* check to see if everything is setup correctly */s3c_rtc_enable(pdev, 1);dev_dbg(pdev-dev, s3c2410_rtc: RTCCON%02x\n,readw(s3c_rtc_base S3C2410_RTCCON));device_init_wakeup(pdev-dev, 1);/* register RTC and exit */rtc devm_rtc_device_register(pdev-dev, s3c, s3c_rtcops,THIS_MODULE);if (IS_ERR(rtc)) {dev_err(pdev-dev, cannot attach rtc\n);ret PTR_ERR(rtc);goto err_nortc;}s3c_rtc_cpu_type s3c_rtc_get_driver_data(pdev);/* Check RTC Time */s3c_rtc_gettime(NULL, rtc_tm);if (rtc_valid_tm(rtc_tm)) {rtc_tm.tm_year 100;rtc_tm.tm_mon 0;rtc_tm.tm_mday 1;rtc_tm.tm_hour 0;rtc_tm.tm_min 0;rtc_tm.tm_sec 0;s3c_rtc_settime(NULL, rtc_tm);dev_warn(pdev-dev, warning: invalid RTC value so initializing it\n);}if (s3c_rtc_cpu_type ! TYPE_S3C2410)rtc-max_user_freq 32768;elsertc-max_user_freq 128;if (s3c_rtc_cpu_type TYPE_S3C2416 || s3c_rtc_cpu_type TYPE_S3C2443) {tmp readw(s3c_rtc_base S3C2410_RTCCON);tmp | S3C2443_RTCCON_TICSEL;writew(tmp, s3c_rtc_base S3C2410_RTCCON);}platform_set_drvdata(pdev, rtc);s3c_rtc_setfreq(pdev-dev, 1);ret devm_request_irq(pdev-dev, s3c_rtc_alarmno, s3c_rtc_alarmirq,0, s3c2410-rtc alarm, rtc);if (ret) {dev_err(pdev-dev, IRQ%d error %d\n, s3c_rtc_alarmno, ret);goto err_nortc;}ret devm_request_irq(pdev-dev, s3c_rtc_tickno, s3c_rtc_tickirq,0, s3c2410-rtc tick, rtc);if (ret) {dev_err(pdev-dev, IRQ%d error %d\n, s3c_rtc_tickno, ret);goto err_nortc;}clk_disable(rtc_clk);return 0;err_nortc:s3c_rtc_enable(pdev, 0);clk_disable_unprepare(rtc_clk);return ret;
}#ifdef CONFIG_PM_SLEEP
/* RTC Power management control */static int ticnt_save, ticnt_en_save;
static bool wake_en;static int s3c_rtc_suspend(struct device *dev)
{struct platform_device *pdev to_platform_device(dev);clk_enable(rtc_clk);/* save TICNT for anyone using periodic interrupts */if (s3c_rtc_cpu_type TYPE_S3C64XX) {ticnt_en_save readw(s3c_rtc_base S3C2410_RTCCON);ticnt_en_save S3C64XX_RTCCON_TICEN;ticnt_save readl(s3c_rtc_base S3C2410_TICNT);} else {ticnt_save readb(s3c_rtc_base S3C2410_TICNT);}s3c_rtc_enable(pdev, 0);if (device_may_wakeup(dev) !wake_en) {if (enable_irq_wake(s3c_rtc_alarmno) 0)wake_en true;elsedev_err(dev, enable_irq_wake failed\n);}clk_disable(rtc_clk);return 0;
}static int s3c_rtc_resume(struct device *dev)
{struct platform_device *pdev to_platform_device(dev);unsigned int tmp;clk_enable(rtc_clk);s3c_rtc_enable(pdev, 1);if (s3c_rtc_cpu_type TYPE_S3C64XX) {writel(ticnt_save, s3c_rtc_base S3C2410_TICNT);if (ticnt_en_save) {tmp readw(s3c_rtc_base S3C2410_RTCCON);writew(tmp | ticnt_en_save,s3c_rtc_base S3C2410_RTCCON);}} else {writeb(ticnt_save, s3c_rtc_base S3C2410_TICNT);}if (device_may_wakeup(dev) wake_en) {disable_irq_wake(s3c_rtc_alarmno);wake_en false;}clk_disable(rtc_clk);return 0;
}
#endifstatic SIMPLE_DEV_PM_OPS(s3c_rtc_pm_ops, s3c_rtc_suspend, s3c_rtc_resume);#ifdef CONFIG_OF
static struct s3c_rtc_drv_data s3c_rtc_drv_data_array[] {[TYPE_S3C2410] { TYPE_S3C2410 },[TYPE_S3C2416] { TYPE_S3C2416 },[TYPE_S3C2443] { TYPE_S3C2443 },[TYPE_S3C64XX] { TYPE_S3C64XX },
};static const struct of_device_id s3c_rtc_dt_match[] {{.compatible samsung,s3c2410-rtc,.data s3c_rtc_drv_data_array[TYPE_S3C2410],}, {.compatible samsung,s3c2416-rtc,.data s3c_rtc_drv_data_array[TYPE_S3C2416],}, {.compatible samsung,s3c2443-rtc,.data s3c_rtc_drv_data_array[TYPE_S3C2443],}, {.compatible samsung,s3c6410-rtc,.data s3c_rtc_drv_data_array[TYPE_S3C64XX],},{},
};
MODULE_DEVICE_TABLE(of, s3c_rtc_dt_match);
#endifstatic struct platform_device_id s3c_rtc_driver_ids[] {{.name s3c2410-rtc,.driver_data TYPE_S3C2410,}, {.name s3c2416-rtc,.driver_data TYPE_S3C2416,}, {.name s3c2443-rtc,.driver_data TYPE_S3C2443,}, {.name s3c64xx-rtc,.driver_data TYPE_S3C64XX,},{ }
};MODULE_DEVICE_TABLE(platform, s3c_rtc_driver_ids);static struct platform_driver s3c_rtc_driver {.probe s3c_rtc_probe,.remove s3c_rtc_remove,.id_table s3c_rtc_driver_ids,.driver {.name s3c-rtc,.owner THIS_MODULE,.pm s3c_rtc_pm_ops,.of_match_table of_match_ptr(s3c_rtc_dt_match),},
};module_platform_driver(s3c_rtc_driver);MODULE_DESCRIPTION(Samsung S3C RTC Driver);
MODULE_AUTHOR(Ben Dooks bensimtec.co.uk);
MODULE_LICENSE(GPL);
MODULE_ALIAS(platform:s3c2410-rtc);/** Copyright (c) 2003 Simtec Electronics linuxsimtec.co.uk* http://www.simtec.co.uk/products/SWLINUX/** 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.** S3C2410 Internal RTC register definition
*/#ifndef __ASM_ARCH_REGS_RTC_H
#define __ASM_ARCH_REGS_RTC_H __FILE__#define S3C2410_RTCREG(x) (x)
#define S3C2410_INTP S3C2410_RTCREG(0x30)
#define S3C2410_INTP_ALM (1 1)
#define S3C2410_INTP_TIC (1 0)#define S3C2410_RTCCON S3C2410_RTCREG(0x40)
#define S3C2410_RTCCON_RTCEN (1 0)
#define S3C2410_RTCCON_CNTSEL (1 2)
#define S3C2410_RTCCON_CLKRST (1 3)
#define S3C2443_RTCCON_TICSEL (1 4)
#define S3C64XX_RTCCON_TICEN (1 8)#define S3C2410_TICNT S3C2410_RTCREG(0x44)
#define S3C2410_TICNT_ENABLE (1 7)/* S3C2443: tick count is 15 bit wide* TICNT[6:0] contains upper 7 bits* TICNT1[7:0] contains lower 8 bits*/
#define S3C2443_TICNT_PART(x) ((x 0x7f00) 8)
#define S3C2443_TICNT1 S3C2410_RTCREG(0x4C)
#define S3C2443_TICNT1_PART(x) (x 0xff)/* S3C2416: tick count is 32 bit wide* TICNT[6:0] contains bits [14:8]* TICNT1[7:0] contains lower 8 bits* TICNT2[16:0] contains upper 17 bits*/
#define S3C2416_TICNT2 S3C2410_RTCREG(0x48)
#define S3C2416_TICNT2_PART(x) ((x 0xffff8000) 15)#define S3C2410_RTCALM S3C2410_RTCREG(0x50)
#define S3C2410_RTCALM_ALMEN (1 6)
#define S3C2410_RTCALM_YEAREN (1 5)
#define S3C2410_RTCALM_MONEN (1 4)
#define S3C2410_RTCALM_DAYEN (1 3)
#define S3C2410_RTCALM_HOUREN (1 2)
#define S3C2410_RTCALM_MINEN (1 1)
#define S3C2410_RTCALM_SECEN (1 0)#define S3C2410_ALMSEC S3C2410_RTCREG(0x54)
#define S3C2410_ALMMIN S3C2410_RTCREG(0x58)
#define S3C2410_ALMHOUR S3C2410_RTCREG(0x5c)#define S3C2410_ALMDATE S3C2410_RTCREG(0x60)
#define S3C2410_ALMMON S3C2410_RTCREG(0x64)
#define S3C2410_ALMYEAR S3C2410_RTCREG(0x68)#define S3C2410_RTCSEC S3C2410_RTCREG(0x70)
#define S3C2410_RTCMIN S3C2410_RTCREG(0x74)
#define S3C2410_RTCHOUR S3C2410_RTCREG(0x78)
#define S3C2410_RTCDATE S3C2410_RTCREG(0x7c)
#define S3C2410_RTCMON S3C2410_RTCREG(0x84)
#define S3C2410_RTCYEAR S3C2410_RTCREG(0x88)#endif /* __ASM_ARCH_REGS_RTC_H */