| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * An RTC test device/driver | |
| * Copyright (C) 2005 Tower Technologies | |
| * Author: Alessandro Zummo <a.zummo@towertech.it> | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/err.h> | |
| #include <linux/rtc.h> | |
| #include <linux/platform_device.h> | |
| #define MAX_RTC_TEST 3 | |
| struct rtc_test_data { | |
| struct rtc_device *rtc; | |
| time64_t offset; | |
| struct timer_list alarm; | |
| bool alarm_en; | |
| }; | |
| static struct platform_device *pdev[MAX_RTC_TEST]; | |
| static int test_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
| { | |
| struct rtc_test_data *rtd = dev_get_drvdata(dev); | |
| time64_t alarm; | |
| alarm = (rtd->alarm.expires - jiffies) / HZ; | |
| alarm += ktime_get_real_seconds() + rtd->offset; | |
| rtc_time64_to_tm(alarm, &alrm->time); | |
| alrm->enabled = rtd->alarm_en; | |
| return 0; | |
| } | |
| static int test_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
| { | |
| struct rtc_test_data *rtd = dev_get_drvdata(dev); | |
| ktime_t timeout; | |
| u64 expires; | |
| timeout = rtc_tm_to_time64(&alrm->time) - ktime_get_real_seconds(); | |
| timeout -= rtd->offset; | |
| timer_delete(&rtd->alarm); | |
| expires = jiffies + timeout * HZ; | |
| if (expires > U32_MAX) | |
| expires = U32_MAX; | |
| rtd->alarm.expires = expires; | |
| if (alrm->enabled) | |
| add_timer(&rtd->alarm); | |
| rtd->alarm_en = alrm->enabled; | |
| return 0; | |
| } | |
| static int test_rtc_read_time(struct device *dev, struct rtc_time *tm) | |
| { | |
| struct rtc_test_data *rtd = dev_get_drvdata(dev); | |
| rtc_time64_to_tm(ktime_get_real_seconds() + rtd->offset, tm); | |
| return 0; | |
| } | |
| static int test_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
| { | |
| struct rtc_test_data *rtd = dev_get_drvdata(dev); | |
| rtd->offset = rtc_tm_to_time64(tm) - ktime_get_real_seconds(); | |
| return 0; | |
| } | |
| static int test_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) | |
| { | |
| struct rtc_test_data *rtd = dev_get_drvdata(dev); | |
| rtd->alarm_en = enable; | |
| if (enable) | |
| add_timer(&rtd->alarm); | |
| else | |
| timer_delete(&rtd->alarm); | |
| return 0; | |
| } | |
| static const struct rtc_class_ops test_rtc_ops_noalm = { | |
| .read_time = test_rtc_read_time, | |
| .set_time = test_rtc_set_time, | |
| .alarm_irq_enable = test_rtc_alarm_irq_enable, | |
| }; | |
| static const struct rtc_class_ops test_rtc_ops = { | |
| .read_time = test_rtc_read_time, | |
| .set_time = test_rtc_set_time, | |
| .read_alarm = test_rtc_read_alarm, | |
| .set_alarm = test_rtc_set_alarm, | |
| .alarm_irq_enable = test_rtc_alarm_irq_enable, | |
| }; | |
| static void test_rtc_alarm_handler(struct timer_list *t) | |
| { | |
| struct rtc_test_data *rtd = timer_container_of(rtd, t, alarm); | |
| rtc_update_irq(rtd->rtc, 1, RTC_AF | RTC_IRQF); | |
| } | |
| static int test_probe(struct platform_device *plat_dev) | |
| { | |
| struct rtc_test_data *rtd; | |
| rtd = devm_kzalloc(&plat_dev->dev, sizeof(*rtd), GFP_KERNEL); | |
| if (!rtd) | |
| return -ENOMEM; | |
| platform_set_drvdata(plat_dev, rtd); | |
| rtd->rtc = devm_rtc_allocate_device(&plat_dev->dev); | |
| if (IS_ERR(rtd->rtc)) | |
| return PTR_ERR(rtd->rtc); | |
| switch (plat_dev->id) { | |
| case 0: | |
| rtd->rtc->ops = &test_rtc_ops_noalm; | |
| break; | |
| default: | |
| rtd->rtc->ops = &test_rtc_ops; | |
| device_init_wakeup(&plat_dev->dev, true); | |
| } | |
| timer_setup(&rtd->alarm, test_rtc_alarm_handler, 0); | |
| rtd->alarm.expires = 0; | |
| return devm_rtc_register_device(rtd->rtc); | |
| } | |
| static struct platform_driver test_driver = { | |
| .probe = test_probe, | |
| .driver = { | |
| .name = "rtc-test", | |
| }, | |
| }; | |
| static int __init test_init(void) | |
| { | |
| int i, err; | |
| err = platform_driver_register(&test_driver); | |
| if (err) | |
| return err; | |
| err = -ENOMEM; | |
| for (i = 0; i < MAX_RTC_TEST; i++) { | |
| pdev[i] = platform_device_alloc("rtc-test", i); | |
| if (!pdev[i]) | |
| goto exit_free_mem; | |
| } | |
| for (i = 0; i < MAX_RTC_TEST; i++) { | |
| err = platform_device_add(pdev[i]); | |
| if (err) | |
| goto exit_device_del; | |
| } | |
| return 0; | |
| exit_device_del: | |
| for (; i > 0; i--) | |
| platform_device_del(pdev[i - 1]); | |
| exit_free_mem: | |
| for (i = 0; i < MAX_RTC_TEST; i++) | |
| platform_device_put(pdev[i]); | |
| platform_driver_unregister(&test_driver); | |
| return err; | |
| } | |
| static void __exit test_exit(void) | |
| { | |
| int i; | |
| for (i = 0; i < MAX_RTC_TEST; i++) | |
| platform_device_unregister(pdev[i]); | |
| platform_driver_unregister(&test_driver); | |
| } | |
| MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); | |
| MODULE_DESCRIPTION("RTC test driver/device"); | |
| MODULE_LICENSE("GPL v2"); | |
| module_init(test_init); | |
| module_exit(test_exit); |