diff options
author | Saeed Bishara <saeed@marvell.com> | 2009-12-15 19:46:08 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:19:59 -0500 |
commit | aeedacaeaf9c02dddfeb6af87bef80c96f9050cb (patch) | |
tree | 3f780d0bf4fd8adf6ec5f37d042e550053a95017 | |
parent | a766ae3ebd3575cf8c6b9a39fd5aa66856a578b4 (diff) |
rtc-mv: add support for Alarm
This patch adds the Alarm support, this mode enabled when adding
IORESOURCE_IRQ to the platform device resources.
The patch also enables the wakeup mode, so the wakealarm sysfs file (under
/sys/class/rtc/rtcX/) can be used to configure the alarm clock.
Signed-off-by: Saeed Bishara <saeed@marvell.com>
Signed-off-by: Nicolas Pitre <nico@marvell.com>
Signed-off-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/rtc/rtc-mv.c | 157 |
1 files changed, 154 insertions, 3 deletions
diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index e0263d2005ee..dc052ce6e63a 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c | |||
@@ -27,10 +27,17 @@ | |||
27 | #define RTC_MONTH_OFFS 8 | 27 | #define RTC_MONTH_OFFS 8 |
28 | #define RTC_YEAR_OFFS 16 | 28 | #define RTC_YEAR_OFFS 16 |
29 | 29 | ||
30 | #define RTC_ALARM_TIME_REG_OFFS 8 | ||
31 | #define RTC_ALARM_DATE_REG_OFFS 0xc | ||
32 | #define RTC_ALARM_VALID (1 << 7) | ||
33 | |||
34 | #define RTC_ALARM_INTERRUPT_MASK_REG_OFFS 0x10 | ||
35 | #define RTC_ALARM_INTERRUPT_CASUE_REG_OFFS 0x14 | ||
30 | 36 | ||
31 | struct rtc_plat_data { | 37 | struct rtc_plat_data { |
32 | struct rtc_device *rtc; | 38 | struct rtc_device *rtc; |
33 | void __iomem *ioaddr; | 39 | void __iomem *ioaddr; |
40 | int irq; | ||
34 | }; | 41 | }; |
35 | 42 | ||
36 | static int mv_rtc_set_time(struct device *dev, struct rtc_time *tm) | 43 | static int mv_rtc_set_time(struct device *dev, struct rtc_time *tm) |
@@ -84,12 +91,134 @@ static int mv_rtc_read_time(struct device *dev, struct rtc_time *tm) | |||
84 | return rtc_valid_tm(tm); | 91 | return rtc_valid_tm(tm); |
85 | } | 92 | } |
86 | 93 | ||
94 | static int mv_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
95 | { | ||
96 | struct rtc_plat_data *pdata = dev_get_drvdata(dev); | ||
97 | void __iomem *ioaddr = pdata->ioaddr; | ||
98 | u32 rtc_time, rtc_date; | ||
99 | unsigned int year, month, day, hour, minute, second, wday; | ||
100 | |||
101 | rtc_time = readl(ioaddr + RTC_ALARM_TIME_REG_OFFS); | ||
102 | rtc_date = readl(ioaddr + RTC_ALARM_DATE_REG_OFFS); | ||
103 | |||
104 | second = rtc_time & 0x7f; | ||
105 | minute = (rtc_time >> RTC_MINUTES_OFFS) & 0x7f; | ||
106 | hour = (rtc_time >> RTC_HOURS_OFFS) & 0x3f; /* assume 24 hours mode */ | ||
107 | wday = (rtc_time >> RTC_WDAY_OFFS) & 0x7; | ||
108 | |||
109 | day = rtc_date & 0x3f; | ||
110 | month = (rtc_date >> RTC_MONTH_OFFS) & 0x3f; | ||
111 | year = (rtc_date >> RTC_YEAR_OFFS) & 0xff; | ||
112 | |||
113 | alm->time.tm_sec = bcd2bin(second); | ||
114 | alm->time.tm_min = bcd2bin(minute); | ||
115 | alm->time.tm_hour = bcd2bin(hour); | ||
116 | alm->time.tm_mday = bcd2bin(day); | ||
117 | alm->time.tm_wday = bcd2bin(wday); | ||
118 | alm->time.tm_mon = bcd2bin(month) - 1; | ||
119 | /* hw counts from year 2000, but tm_year is relative to 1900 */ | ||
120 | alm->time.tm_year = bcd2bin(year) + 100; | ||
121 | |||
122 | if (rtc_valid_tm(&alm->time) < 0) { | ||
123 | dev_err(dev, "retrieved alarm date/time is not valid.\n"); | ||
124 | rtc_time_to_tm(0, &alm->time); | ||
125 | } | ||
126 | |||
127 | alm->enabled = !!readl(ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int mv_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
132 | { | ||
133 | struct rtc_plat_data *pdata = dev_get_drvdata(dev); | ||
134 | void __iomem *ioaddr = pdata->ioaddr; | ||
135 | u32 rtc_reg = 0; | ||
136 | |||
137 | if (alm->time.tm_sec >= 0) | ||
138 | rtc_reg |= (RTC_ALARM_VALID | bin2bcd(alm->time.tm_sec)) | ||
139 | << RTC_SECONDS_OFFS; | ||
140 | if (alm->time.tm_min >= 0) | ||
141 | rtc_reg |= (RTC_ALARM_VALID | bin2bcd(alm->time.tm_min)) | ||
142 | << RTC_MINUTES_OFFS; | ||
143 | if (alm->time.tm_hour >= 0) | ||
144 | rtc_reg |= (RTC_ALARM_VALID | bin2bcd(alm->time.tm_hour)) | ||
145 | << RTC_HOURS_OFFS; | ||
146 | |||
147 | writel(rtc_reg, ioaddr + RTC_ALARM_TIME_REG_OFFS); | ||
148 | |||
149 | if (alm->time.tm_mday >= 0) | ||
150 | rtc_reg = (RTC_ALARM_VALID | bin2bcd(alm->time.tm_mday)) | ||
151 | << RTC_MDAY_OFFS; | ||
152 | else | ||
153 | rtc_reg = 0; | ||
154 | |||
155 | if (alm->time.tm_mon >= 0) | ||
156 | rtc_reg |= (RTC_ALARM_VALID | bin2bcd(alm->time.tm_mon + 1)) | ||
157 | << RTC_MONTH_OFFS; | ||
158 | |||
159 | if (alm->time.tm_year >= 0) | ||
160 | rtc_reg |= (RTC_ALARM_VALID | bin2bcd(alm->time.tm_year % 100)) | ||
161 | << RTC_YEAR_OFFS; | ||
162 | |||
163 | writel(rtc_reg, ioaddr + RTC_ALARM_DATE_REG_OFFS); | ||
164 | writel(0, ioaddr + RTC_ALARM_INTERRUPT_CASUE_REG_OFFS); | ||
165 | writel(alm->enabled ? 1 : 0, | ||
166 | ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); | ||
167 | |||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | static int mv_rtc_ioctl(struct device *dev, unsigned int cmd, | ||
172 | unsigned long arg) | ||
173 | { | ||
174 | struct platform_device *pdev = to_platform_device(dev); | ||
175 | struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | ||
176 | void __iomem *ioaddr = pdata->ioaddr; | ||
177 | |||
178 | if (pdata->irq < 0) | ||
179 | return -ENOIOCTLCMD; /* fall back into rtc-dev's emulation */ | ||
180 | switch (cmd) { | ||
181 | case RTC_AIE_OFF: | ||
182 | writel(0, ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); | ||
183 | break; | ||
184 | case RTC_AIE_ON: | ||
185 | writel(1, ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); | ||
186 | break; | ||
187 | default: | ||
188 | return -ENOIOCTLCMD; | ||
189 | } | ||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static irqreturn_t mv_rtc_interrupt(int irq, void *data) | ||
194 | { | ||
195 | struct rtc_plat_data *pdata = data; | ||
196 | void __iomem *ioaddr = pdata->ioaddr; | ||
197 | |||
198 | /* alarm irq? */ | ||
199 | if (!readl(ioaddr + RTC_ALARM_INTERRUPT_CASUE_REG_OFFS)) | ||
200 | return IRQ_NONE; | ||
201 | |||
202 | /* clear interrupt */ | ||
203 | writel(0, ioaddr + RTC_ALARM_INTERRUPT_CASUE_REG_OFFS); | ||
204 | rtc_update_irq(pdata->rtc, 1, RTC_IRQF | RTC_AF); | ||
205 | return IRQ_HANDLED; | ||
206 | } | ||
207 | |||
87 | static const struct rtc_class_ops mv_rtc_ops = { | 208 | static const struct rtc_class_ops mv_rtc_ops = { |
88 | .read_time = mv_rtc_read_time, | 209 | .read_time = mv_rtc_read_time, |
89 | .set_time = mv_rtc_set_time, | 210 | .set_time = mv_rtc_set_time, |
90 | }; | 211 | }; |
91 | 212 | ||
92 | static int __init mv_rtc_probe(struct platform_device *pdev) | 213 | static const struct rtc_class_ops mv_rtc_alarm_ops = { |
214 | .read_time = mv_rtc_read_time, | ||
215 | .set_time = mv_rtc_set_time, | ||
216 | .read_alarm = mv_rtc_read_alarm, | ||
217 | .set_alarm = mv_rtc_set_alarm, | ||
218 | .ioctl = mv_rtc_ioctl, | ||
219 | }; | ||
220 | |||
221 | static int __devinit mv_rtc_probe(struct platform_device *pdev) | ||
93 | { | 222 | { |
94 | struct resource *res; | 223 | struct resource *res; |
95 | struct rtc_plat_data *pdata; | 224 | struct rtc_plat_data *pdata; |
@@ -130,12 +259,31 @@ static int __init mv_rtc_probe(struct platform_device *pdev) | |||
130 | } | 259 | } |
131 | } | 260 | } |
132 | 261 | ||
262 | pdata->irq = platform_get_irq(pdev, 0); | ||
263 | |||
133 | platform_set_drvdata(pdev, pdata); | 264 | platform_set_drvdata(pdev, pdata); |
134 | pdata->rtc = rtc_device_register(pdev->name, &pdev->dev, | 265 | |
135 | &mv_rtc_ops, THIS_MODULE); | 266 | if (pdata->irq >= 0) { |
267 | device_init_wakeup(&pdev->dev, 1); | ||
268 | pdata->rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
269 | &mv_rtc_alarm_ops, | ||
270 | THIS_MODULE); | ||
271 | } else | ||
272 | pdata->rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
273 | &mv_rtc_ops, THIS_MODULE); | ||
136 | if (IS_ERR(pdata->rtc)) | 274 | if (IS_ERR(pdata->rtc)) |
137 | return PTR_ERR(pdata->rtc); | 275 | return PTR_ERR(pdata->rtc); |
138 | 276 | ||
277 | if (pdata->irq >= 0) { | ||
278 | writel(0, pdata->ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); | ||
279 | if (devm_request_irq(&pdev->dev, pdata->irq, mv_rtc_interrupt, | ||
280 | IRQF_DISABLED | IRQF_SHARED, | ||
281 | pdev->name, pdata) < 0) { | ||
282 | dev_warn(&pdev->dev, "interrupt not available.\n"); | ||
283 | pdata->irq = -1; | ||
284 | } | ||
285 | } | ||
286 | |||
139 | return 0; | 287 | return 0; |
140 | } | 288 | } |
141 | 289 | ||
@@ -143,6 +291,9 @@ static int __exit mv_rtc_remove(struct platform_device *pdev) | |||
143 | { | 291 | { |
144 | struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 292 | struct rtc_plat_data *pdata = platform_get_drvdata(pdev); |
145 | 293 | ||
294 | if (pdata->irq >= 0) | ||
295 | device_init_wakeup(&pdev->dev, 0); | ||
296 | |||
146 | rtc_device_unregister(pdata->rtc); | 297 | rtc_device_unregister(pdata->rtc); |
147 | return 0; | 298 | return 0; |
148 | } | 299 | } |