162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2020 Intel Corporation 462306a36Sopenharmony_ci * Author: Johannes Berg <johannes@sipsolutions.net> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/platform_device.h> 762306a36Sopenharmony_ci#include <linux/time-internal.h> 862306a36Sopenharmony_ci#include <linux/suspend.h> 962306a36Sopenharmony_ci#include <linux/err.h> 1062306a36Sopenharmony_ci#include <linux/rtc.h> 1162306a36Sopenharmony_ci#include <kern_util.h> 1262306a36Sopenharmony_ci#include <irq_kern.h> 1362306a36Sopenharmony_ci#include <os.h> 1462306a36Sopenharmony_ci#include "rtc.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic time64_t uml_rtc_alarm_time; 1762306a36Sopenharmony_cistatic bool uml_rtc_alarm_enabled; 1862306a36Sopenharmony_cistatic struct rtc_device *uml_rtc; 1962306a36Sopenharmony_cistatic int uml_rtc_irq_fd, uml_rtc_irq; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic void uml_rtc_time_travel_alarm(struct time_travel_event *ev) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci uml_rtc_send_timetravel_alarm(); 2662306a36Sopenharmony_ci} 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct time_travel_event uml_rtc_alarm_event = { 2962306a36Sopenharmony_ci .fn = uml_rtc_time_travel_alarm, 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci#endif 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int uml_rtc_read_time(struct device *dev, struct rtc_time *tm) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct timespec64 ts; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* Use this to get correct time in time-travel mode */ 3862306a36Sopenharmony_ci read_persistent_clock64(&ts); 3962306a36Sopenharmony_ci rtc_time64_to_tm(timespec64_to_ktime(ts) / NSEC_PER_SEC, tm); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci return 0; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int uml_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci rtc_time64_to_tm(uml_rtc_alarm_time, &alrm->time); 4762306a36Sopenharmony_ci alrm->enabled = uml_rtc_alarm_enabled; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci return 0; 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int uml_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci unsigned long long secs; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (!enable && !uml_rtc_alarm_enabled) 5762306a36Sopenharmony_ci return 0; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci uml_rtc_alarm_enabled = enable; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci secs = uml_rtc_alarm_time - ktime_get_real_seconds(); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (time_travel_mode == TT_MODE_OFF) { 6462306a36Sopenharmony_ci if (!enable) { 6562306a36Sopenharmony_ci uml_rtc_disable_alarm(); 6662306a36Sopenharmony_ci return 0; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* enable or update */ 7062306a36Sopenharmony_ci return uml_rtc_enable_alarm(secs); 7162306a36Sopenharmony_ci } else { 7262306a36Sopenharmony_ci time_travel_del_event(¨_rtc_alarm_event); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci if (enable) 7562306a36Sopenharmony_ci time_travel_add_event_rel(¨_rtc_alarm_event, 7662306a36Sopenharmony_ci secs * NSEC_PER_SEC); 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci return 0; 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int uml_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci uml_rtc_alarm_irq_enable(dev, 0); 8562306a36Sopenharmony_ci uml_rtc_alarm_time = rtc_tm_to_time64(&alrm->time); 8662306a36Sopenharmony_ci uml_rtc_alarm_irq_enable(dev, alrm->enabled); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic const struct rtc_class_ops uml_rtc_ops = { 9262306a36Sopenharmony_ci .read_time = uml_rtc_read_time, 9362306a36Sopenharmony_ci .read_alarm = uml_rtc_read_alarm, 9462306a36Sopenharmony_ci .alarm_irq_enable = uml_rtc_alarm_irq_enable, 9562306a36Sopenharmony_ci .set_alarm = uml_rtc_set_alarm, 9662306a36Sopenharmony_ci}; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic irqreturn_t uml_rtc_interrupt(int irq, void *data) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci unsigned long long c = 0; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci /* alarm triggered, it's now off */ 10362306a36Sopenharmony_ci uml_rtc_alarm_enabled = false; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci os_read_file(uml_rtc_irq_fd, &c, sizeof(c)); 10662306a36Sopenharmony_ci WARN_ON(c == 0); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci pm_system_wakeup(); 10962306a36Sopenharmony_ci rtc_update_irq(uml_rtc, 1, RTC_IRQF | RTC_AF); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return IRQ_HANDLED; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int uml_rtc_setup(void) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci int err; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci err = uml_rtc_start(time_travel_mode != TT_MODE_OFF); 11962306a36Sopenharmony_ci if (WARN(err < 0, "err = %d\n", err)) 12062306a36Sopenharmony_ci return err; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci uml_rtc_irq_fd = err; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci err = um_request_irq(UM_IRQ_ALLOC, uml_rtc_irq_fd, IRQ_READ, 12562306a36Sopenharmony_ci uml_rtc_interrupt, 0, "rtc", NULL); 12662306a36Sopenharmony_ci if (err < 0) { 12762306a36Sopenharmony_ci uml_rtc_stop(time_travel_mode != TT_MODE_OFF); 12862306a36Sopenharmony_ci return err; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci irq_set_irq_wake(err, 1); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci uml_rtc_irq = err; 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic void uml_rtc_cleanup(void) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci um_free_irq(uml_rtc_irq, NULL); 14062306a36Sopenharmony_ci uml_rtc_stop(time_travel_mode != TT_MODE_OFF); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int uml_rtc_probe(struct platform_device *pdev) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci int err; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci err = uml_rtc_setup(); 14862306a36Sopenharmony_ci if (err) 14962306a36Sopenharmony_ci return err; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci uml_rtc = devm_rtc_allocate_device(&pdev->dev); 15262306a36Sopenharmony_ci if (IS_ERR(uml_rtc)) { 15362306a36Sopenharmony_ci err = PTR_ERR(uml_rtc); 15462306a36Sopenharmony_ci goto cleanup; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci uml_rtc->ops = ¨_rtc_ops; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci device_init_wakeup(&pdev->dev, 1); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci err = devm_rtc_register_device(uml_rtc); 16262306a36Sopenharmony_ci if (err) 16362306a36Sopenharmony_ci goto cleanup; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return 0; 16662306a36Sopenharmony_cicleanup: 16762306a36Sopenharmony_ci uml_rtc_cleanup(); 16862306a36Sopenharmony_ci return err; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int uml_rtc_remove(struct platform_device *pdev) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci device_init_wakeup(&pdev->dev, 0); 17462306a36Sopenharmony_ci uml_rtc_cleanup(); 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic struct platform_driver uml_rtc_driver = { 17962306a36Sopenharmony_ci .probe = uml_rtc_probe, 18062306a36Sopenharmony_ci .remove = uml_rtc_remove, 18162306a36Sopenharmony_ci .driver = { 18262306a36Sopenharmony_ci .name = "uml-rtc", 18362306a36Sopenharmony_ci }, 18462306a36Sopenharmony_ci}; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic int __init uml_rtc_init(void) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct platform_device *pdev; 18962306a36Sopenharmony_ci int err; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci err = platform_driver_register(¨_rtc_driver); 19262306a36Sopenharmony_ci if (err) 19362306a36Sopenharmony_ci return err; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci pdev = platform_device_alloc("uml-rtc", 0); 19662306a36Sopenharmony_ci if (!pdev) { 19762306a36Sopenharmony_ci err = -ENOMEM; 19862306a36Sopenharmony_ci goto unregister; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci err = platform_device_add(pdev); 20262306a36Sopenharmony_ci if (err) 20362306a36Sopenharmony_ci goto unregister; 20462306a36Sopenharmony_ci return 0; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ciunregister: 20762306a36Sopenharmony_ci platform_device_put(pdev); 20862306a36Sopenharmony_ci platform_driver_unregister(¨_rtc_driver); 20962306a36Sopenharmony_ci return err; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_cidevice_initcall(uml_rtc_init); 212