18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * An RTC test device/driver
48c2ecf20Sopenharmony_ci * Copyright (C) 2005 Tower Technologies
58c2ecf20Sopenharmony_ci * Author: Alessandro Zummo <a.zummo@towertech.it>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/err.h>
108c2ecf20Sopenharmony_ci#include <linux/rtc.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define MAX_RTC_TEST 3
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistruct rtc_test_data {
168c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
178c2ecf20Sopenharmony_ci	time64_t offset;
188c2ecf20Sopenharmony_ci	struct timer_list alarm;
198c2ecf20Sopenharmony_ci	bool alarm_en;
208c2ecf20Sopenharmony_ci};
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic struct platform_device *pdev[MAX_RTC_TEST];
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic int test_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = dev_get_drvdata(dev);
278c2ecf20Sopenharmony_ci	time64_t alarm;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	alarm = (rtd->alarm.expires - jiffies) / HZ;
308c2ecf20Sopenharmony_ci	alarm += ktime_get_real_seconds() + rtd->offset;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	rtc_time64_to_tm(alarm, &alrm->time);
338c2ecf20Sopenharmony_ci	alrm->enabled = rtd->alarm_en;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	return 0;
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int test_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = dev_get_drvdata(dev);
418c2ecf20Sopenharmony_ci	ktime_t timeout;
428c2ecf20Sopenharmony_ci	u64 expires;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	timeout = rtc_tm_to_time64(&alrm->time) - ktime_get_real_seconds();
458c2ecf20Sopenharmony_ci	timeout -= rtd->offset;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	del_timer(&rtd->alarm);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	expires = jiffies + timeout * HZ;
508c2ecf20Sopenharmony_ci	if (expires > U32_MAX)
518c2ecf20Sopenharmony_ci		expires = U32_MAX;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	pr_err("ABE: %s +%d %s\n", __FILE__, __LINE__, __func__);
548c2ecf20Sopenharmony_ci	rtd->alarm.expires = expires;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (alrm->enabled)
578c2ecf20Sopenharmony_ci		add_timer(&rtd->alarm);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	rtd->alarm_en = alrm->enabled;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	return 0;
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic int test_rtc_read_time(struct device *dev, struct rtc_time *tm)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = dev_get_drvdata(dev);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	rtc_time64_to_tm(ktime_get_real_seconds() + rtd->offset, tm);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	return 0;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int test_rtc_set_time(struct device *dev, struct rtc_time *tm)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = dev_get_drvdata(dev);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	rtd->offset = rtc_tm_to_time64(tm) - ktime_get_real_seconds();
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return 0;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic int test_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = dev_get_drvdata(dev);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	rtd->alarm_en = enable;
878c2ecf20Sopenharmony_ci	if (enable)
888c2ecf20Sopenharmony_ci		add_timer(&rtd->alarm);
898c2ecf20Sopenharmony_ci	else
908c2ecf20Sopenharmony_ci		del_timer(&rtd->alarm);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic const struct rtc_class_ops test_rtc_ops_noalm = {
968c2ecf20Sopenharmony_ci	.read_time = test_rtc_read_time,
978c2ecf20Sopenharmony_ci	.set_time = test_rtc_set_time,
988c2ecf20Sopenharmony_ci	.alarm_irq_enable = test_rtc_alarm_irq_enable,
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic const struct rtc_class_ops test_rtc_ops = {
1028c2ecf20Sopenharmony_ci	.read_time = test_rtc_read_time,
1038c2ecf20Sopenharmony_ci	.set_time = test_rtc_set_time,
1048c2ecf20Sopenharmony_ci	.read_alarm = test_rtc_read_alarm,
1058c2ecf20Sopenharmony_ci	.set_alarm = test_rtc_set_alarm,
1068c2ecf20Sopenharmony_ci	.alarm_irq_enable = test_rtc_alarm_irq_enable,
1078c2ecf20Sopenharmony_ci};
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void test_rtc_alarm_handler(struct timer_list *t)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd = from_timer(rtd, t, alarm);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	rtc_update_irq(rtd->rtc, 1, RTC_AF | RTC_IRQF);
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int test_probe(struct platform_device *plat_dev)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	struct rtc_test_data *rtd;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	rtd = devm_kzalloc(&plat_dev->dev, sizeof(*rtd), GFP_KERNEL);
1218c2ecf20Sopenharmony_ci	if (!rtd)
1228c2ecf20Sopenharmony_ci		return -ENOMEM;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	platform_set_drvdata(plat_dev, rtd);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	rtd->rtc = devm_rtc_allocate_device(&plat_dev->dev);
1278c2ecf20Sopenharmony_ci	if (IS_ERR(rtd->rtc))
1288c2ecf20Sopenharmony_ci		return PTR_ERR(rtd->rtc);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	switch (plat_dev->id) {
1318c2ecf20Sopenharmony_ci	case 0:
1328c2ecf20Sopenharmony_ci		rtd->rtc->ops = &test_rtc_ops_noalm;
1338c2ecf20Sopenharmony_ci		break;
1348c2ecf20Sopenharmony_ci	default:
1358c2ecf20Sopenharmony_ci		rtd->rtc->ops = &test_rtc_ops;
1368c2ecf20Sopenharmony_ci		device_init_wakeup(&plat_dev->dev, 1);
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	timer_setup(&rtd->alarm, test_rtc_alarm_handler, 0);
1408c2ecf20Sopenharmony_ci	rtd->alarm.expires = 0;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return rtc_register_device(rtd->rtc);
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic struct platform_driver test_driver = {
1468c2ecf20Sopenharmony_ci	.probe	= test_probe,
1478c2ecf20Sopenharmony_ci	.driver = {
1488c2ecf20Sopenharmony_ci		.name = "rtc-test",
1498c2ecf20Sopenharmony_ci	},
1508c2ecf20Sopenharmony_ci};
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic int __init test_init(void)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	int i, err;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	err = platform_driver_register(&test_driver);
1578c2ecf20Sopenharmony_ci	if (err)
1588c2ecf20Sopenharmony_ci		return err;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	err = -ENOMEM;
1618c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_RTC_TEST; i++) {
1628c2ecf20Sopenharmony_ci		pdev[i] = platform_device_alloc("rtc-test", i);
1638c2ecf20Sopenharmony_ci		if (!pdev[i])
1648c2ecf20Sopenharmony_ci			goto exit_free_mem;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_RTC_TEST; i++) {
1688c2ecf20Sopenharmony_ci		err = platform_device_add(pdev[i]);
1698c2ecf20Sopenharmony_ci		if (err)
1708c2ecf20Sopenharmony_ci			goto exit_device_del;
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	return 0;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ciexit_device_del:
1768c2ecf20Sopenharmony_ci	for (; i > 0; i--)
1778c2ecf20Sopenharmony_ci		platform_device_del(pdev[i - 1]);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ciexit_free_mem:
1808c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_RTC_TEST; i++)
1818c2ecf20Sopenharmony_ci		platform_device_put(pdev[i]);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	platform_driver_unregister(&test_driver);
1848c2ecf20Sopenharmony_ci	return err;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic void __exit test_exit(void)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	int i;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_RTC_TEST; i++)
1928c2ecf20Sopenharmony_ci		platform_device_unregister(pdev[i]);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	platform_driver_unregister(&test_driver);
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
1988c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("RTC test driver/device");
1998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cimodule_init(test_init);
2028c2ecf20Sopenharmony_cimodule_exit(test_exit);
203