18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * RTC subsystem, dev interface
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2005 Tower Technologies
68c2ecf20Sopenharmony_ci * Author: Alessandro Zummo <a.zummo@towertech.it>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * based on arch/arm/common/rtctime.c
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/compat.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/rtc.h>
168c2ecf20Sopenharmony_ci#include <linux/sched/signal.h>
178c2ecf20Sopenharmony_ci#include "rtc-core.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic dev_t rtc_devt;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int rtc_dev_open(struct inode *inode, struct file *file)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	struct rtc_device *rtc = container_of(inode->i_cdev,
268c2ecf20Sopenharmony_ci					struct rtc_device, char_dev);
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))
298c2ecf20Sopenharmony_ci		return -EBUSY;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	file->private_data = rtc;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	spin_lock_irq(&rtc->irq_lock);
348c2ecf20Sopenharmony_ci	rtc->irq_data = 0;
358c2ecf20Sopenharmony_ci	spin_unlock_irq(&rtc->irq_lock);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	return 0;
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
418c2ecf20Sopenharmony_ci/*
428c2ecf20Sopenharmony_ci * Routine to poll RTC seconds field for change as often as possible,
438c2ecf20Sopenharmony_ci * after first RTC_UIE use timer to reduce polling
448c2ecf20Sopenharmony_ci */
458c2ecf20Sopenharmony_cistatic void rtc_uie_task(struct work_struct *work)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	struct rtc_device *rtc =
488c2ecf20Sopenharmony_ci		container_of(work, struct rtc_device, uie_task);
498c2ecf20Sopenharmony_ci	struct rtc_time tm;
508c2ecf20Sopenharmony_ci	int num = 0;
518c2ecf20Sopenharmony_ci	int err;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	err = rtc_read_time(rtc, &tm);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	spin_lock_irq(&rtc->irq_lock);
568c2ecf20Sopenharmony_ci	if (rtc->stop_uie_polling || err) {
578c2ecf20Sopenharmony_ci		rtc->uie_task_active = 0;
588c2ecf20Sopenharmony_ci	} else if (rtc->oldsecs != tm.tm_sec) {
598c2ecf20Sopenharmony_ci		num = (tm.tm_sec + 60 - rtc->oldsecs) % 60;
608c2ecf20Sopenharmony_ci		rtc->oldsecs = tm.tm_sec;
618c2ecf20Sopenharmony_ci		rtc->uie_timer.expires = jiffies + HZ - (HZ / 10);
628c2ecf20Sopenharmony_ci		rtc->uie_timer_active = 1;
638c2ecf20Sopenharmony_ci		rtc->uie_task_active = 0;
648c2ecf20Sopenharmony_ci		add_timer(&rtc->uie_timer);
658c2ecf20Sopenharmony_ci	} else if (schedule_work(&rtc->uie_task) == 0) {
668c2ecf20Sopenharmony_ci		rtc->uie_task_active = 0;
678c2ecf20Sopenharmony_ci	}
688c2ecf20Sopenharmony_ci	spin_unlock_irq(&rtc->irq_lock);
698c2ecf20Sopenharmony_ci	if (num)
708c2ecf20Sopenharmony_ci		rtc_handle_legacy_irq(rtc, num, RTC_UF);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic void rtc_uie_timer(struct timer_list *t)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct rtc_device *rtc = from_timer(rtc, t, uie_timer);
768c2ecf20Sopenharmony_ci	unsigned long flags;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	spin_lock_irqsave(&rtc->irq_lock, flags);
798c2ecf20Sopenharmony_ci	rtc->uie_timer_active = 0;
808c2ecf20Sopenharmony_ci	rtc->uie_task_active = 1;
818c2ecf20Sopenharmony_ci	if ((schedule_work(&rtc->uie_task) == 0))
828c2ecf20Sopenharmony_ci		rtc->uie_task_active = 0;
838c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&rtc->irq_lock, flags);
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic int clear_uie(struct rtc_device *rtc)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	spin_lock_irq(&rtc->irq_lock);
898c2ecf20Sopenharmony_ci	if (rtc->uie_irq_active) {
908c2ecf20Sopenharmony_ci		rtc->stop_uie_polling = 1;
918c2ecf20Sopenharmony_ci		if (rtc->uie_timer_active) {
928c2ecf20Sopenharmony_ci			spin_unlock_irq(&rtc->irq_lock);
938c2ecf20Sopenharmony_ci			del_timer_sync(&rtc->uie_timer);
948c2ecf20Sopenharmony_ci			spin_lock_irq(&rtc->irq_lock);
958c2ecf20Sopenharmony_ci			rtc->uie_timer_active = 0;
968c2ecf20Sopenharmony_ci		}
978c2ecf20Sopenharmony_ci		if (rtc->uie_task_active) {
988c2ecf20Sopenharmony_ci			spin_unlock_irq(&rtc->irq_lock);
998c2ecf20Sopenharmony_ci			flush_scheduled_work();
1008c2ecf20Sopenharmony_ci			spin_lock_irq(&rtc->irq_lock);
1018c2ecf20Sopenharmony_ci		}
1028c2ecf20Sopenharmony_ci		rtc->uie_irq_active = 0;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci	spin_unlock_irq(&rtc->irq_lock);
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic int set_uie(struct rtc_device *rtc)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct rtc_time tm;
1118c2ecf20Sopenharmony_ci	int err;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	err = rtc_read_time(rtc, &tm);
1148c2ecf20Sopenharmony_ci	if (err)
1158c2ecf20Sopenharmony_ci		return err;
1168c2ecf20Sopenharmony_ci	spin_lock_irq(&rtc->irq_lock);
1178c2ecf20Sopenharmony_ci	if (!rtc->uie_irq_active) {
1188c2ecf20Sopenharmony_ci		rtc->uie_irq_active = 1;
1198c2ecf20Sopenharmony_ci		rtc->stop_uie_polling = 0;
1208c2ecf20Sopenharmony_ci		rtc->oldsecs = tm.tm_sec;
1218c2ecf20Sopenharmony_ci		rtc->uie_task_active = 1;
1228c2ecf20Sopenharmony_ci		if (schedule_work(&rtc->uie_task) == 0)
1238c2ecf20Sopenharmony_ci			rtc->uie_task_active = 0;
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci	rtc->irq_data = 0;
1268c2ecf20Sopenharmony_ci	spin_unlock_irq(&rtc->irq_lock);
1278c2ecf20Sopenharmony_ci	return 0;
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ciint rtc_dev_update_irq_enable_emul(struct rtc_device *rtc, unsigned int enabled)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	if (enabled)
1338c2ecf20Sopenharmony_ci		return set_uie(rtc);
1348c2ecf20Sopenharmony_ci	else
1358c2ecf20Sopenharmony_ci		return clear_uie(rtc);
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ciEXPORT_SYMBOL(rtc_dev_update_irq_enable_emul);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci#endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic ssize_t
1428c2ecf20Sopenharmony_cirtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
1478c2ecf20Sopenharmony_ci	unsigned long data;
1488c2ecf20Sopenharmony_ci	ssize_t ret;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
1518c2ecf20Sopenharmony_ci		return -EINVAL;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	add_wait_queue(&rtc->irq_queue, &wait);
1548c2ecf20Sopenharmony_ci	do {
1558c2ecf20Sopenharmony_ci		__set_current_state(TASK_INTERRUPTIBLE);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		spin_lock_irq(&rtc->irq_lock);
1588c2ecf20Sopenharmony_ci		data = rtc->irq_data;
1598c2ecf20Sopenharmony_ci		rtc->irq_data = 0;
1608c2ecf20Sopenharmony_ci		spin_unlock_irq(&rtc->irq_lock);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		if (data != 0) {
1638c2ecf20Sopenharmony_ci			ret = 0;
1648c2ecf20Sopenharmony_ci			break;
1658c2ecf20Sopenharmony_ci		}
1668c2ecf20Sopenharmony_ci		if (file->f_flags & O_NONBLOCK) {
1678c2ecf20Sopenharmony_ci			ret = -EAGAIN;
1688c2ecf20Sopenharmony_ci			break;
1698c2ecf20Sopenharmony_ci		}
1708c2ecf20Sopenharmony_ci		if (signal_pending(current)) {
1718c2ecf20Sopenharmony_ci			ret = -ERESTARTSYS;
1728c2ecf20Sopenharmony_ci			break;
1738c2ecf20Sopenharmony_ci		}
1748c2ecf20Sopenharmony_ci		schedule();
1758c2ecf20Sopenharmony_ci	} while (1);
1768c2ecf20Sopenharmony_ci	set_current_state(TASK_RUNNING);
1778c2ecf20Sopenharmony_ci	remove_wait_queue(&rtc->irq_queue, &wait);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	if (ret == 0) {
1808c2ecf20Sopenharmony_ci		if (sizeof(int) != sizeof(long) &&
1818c2ecf20Sopenharmony_ci		    count == sizeof(unsigned int))
1828c2ecf20Sopenharmony_ci			ret = put_user(data, (unsigned int __user *)buf) ?:
1838c2ecf20Sopenharmony_ci				sizeof(unsigned int);
1848c2ecf20Sopenharmony_ci		else
1858c2ecf20Sopenharmony_ci			ret = put_user(data, (unsigned long __user *)buf) ?:
1868c2ecf20Sopenharmony_ci				sizeof(unsigned long);
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci	return ret;
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_cistatic __poll_t rtc_dev_poll(struct file *file, poll_table *wait)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
1948c2ecf20Sopenharmony_ci	unsigned long data;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	poll_wait(file, &rtc->irq_queue, wait);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	data = rtc->irq_data;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	return (data != 0) ? (EPOLLIN | EPOLLRDNORM) : 0;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic long rtc_dev_ioctl(struct file *file,
2048c2ecf20Sopenharmony_ci			  unsigned int cmd, unsigned long arg)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	int err = 0;
2078c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
2088c2ecf20Sopenharmony_ci	const struct rtc_class_ops *ops = rtc->ops;
2098c2ecf20Sopenharmony_ci	struct rtc_time tm;
2108c2ecf20Sopenharmony_ci	struct rtc_wkalrm alarm;
2118c2ecf20Sopenharmony_ci	void __user *uarg = (void __user *)arg;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	err = mutex_lock_interruptible(&rtc->ops_lock);
2148c2ecf20Sopenharmony_ci	if (err)
2158c2ecf20Sopenharmony_ci		return err;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	/* check that the calling task has appropriate permissions
2188c2ecf20Sopenharmony_ci	 * for certain ioctls. doing this check here is useful
2198c2ecf20Sopenharmony_ci	 * to avoid duplicate code in each driver.
2208c2ecf20Sopenharmony_ci	 */
2218c2ecf20Sopenharmony_ci	switch (cmd) {
2228c2ecf20Sopenharmony_ci	case RTC_EPOCH_SET:
2238c2ecf20Sopenharmony_ci	case RTC_SET_TIME:
2248c2ecf20Sopenharmony_ci		if (!capable(CAP_SYS_TIME))
2258c2ecf20Sopenharmony_ci			err = -EACCES;
2268c2ecf20Sopenharmony_ci		break;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	case RTC_IRQP_SET:
2298c2ecf20Sopenharmony_ci		if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
2308c2ecf20Sopenharmony_ci			err = -EACCES;
2318c2ecf20Sopenharmony_ci		break;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	case RTC_PIE_ON:
2348c2ecf20Sopenharmony_ci		if (rtc->irq_freq > rtc->max_user_freq &&
2358c2ecf20Sopenharmony_ci		    !capable(CAP_SYS_RESOURCE))
2368c2ecf20Sopenharmony_ci			err = -EACCES;
2378c2ecf20Sopenharmony_ci		break;
2388c2ecf20Sopenharmony_ci	}
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	if (err)
2418c2ecf20Sopenharmony_ci		goto done;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	/*
2448c2ecf20Sopenharmony_ci	 * Drivers *SHOULD NOT* provide ioctl implementations
2458c2ecf20Sopenharmony_ci	 * for these requests.  Instead, provide methods to
2468c2ecf20Sopenharmony_ci	 * support the following code, so that the RTC's main
2478c2ecf20Sopenharmony_ci	 * features are accessible without using ioctls.
2488c2ecf20Sopenharmony_ci	 *
2498c2ecf20Sopenharmony_ci	 * RTC and alarm times will be in UTC, by preference,
2508c2ecf20Sopenharmony_ci	 * but dual-booting with MS-Windows implies RTCs must
2518c2ecf20Sopenharmony_ci	 * use the local wall clock time.
2528c2ecf20Sopenharmony_ci	 */
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	switch (cmd) {
2558c2ecf20Sopenharmony_ci	case RTC_ALM_READ:
2568c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci		err = rtc_read_alarm(rtc, &alarm);
2598c2ecf20Sopenharmony_ci		if (err < 0)
2608c2ecf20Sopenharmony_ci			return err;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci		if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
2638c2ecf20Sopenharmony_ci			err = -EFAULT;
2648c2ecf20Sopenharmony_ci		return err;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	case RTC_ALM_SET:
2678c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci		if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
2708c2ecf20Sopenharmony_ci			return -EFAULT;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci		alarm.enabled = 0;
2738c2ecf20Sopenharmony_ci		alarm.pending = 0;
2748c2ecf20Sopenharmony_ci		alarm.time.tm_wday = -1;
2758c2ecf20Sopenharmony_ci		alarm.time.tm_yday = -1;
2768c2ecf20Sopenharmony_ci		alarm.time.tm_isdst = -1;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci		/* RTC_ALM_SET alarms may be up to 24 hours in the future.
2798c2ecf20Sopenharmony_ci		 * Rather than expecting every RTC to implement "don't care"
2808c2ecf20Sopenharmony_ci		 * for day/month/year fields, just force the alarm to have
2818c2ecf20Sopenharmony_ci		 * the right values for those fields.
2828c2ecf20Sopenharmony_ci		 *
2838c2ecf20Sopenharmony_ci		 * RTC_WKALM_SET should be used instead.  Not only does it
2848c2ecf20Sopenharmony_ci		 * eliminate the need for a separate RTC_AIE_ON call, it
2858c2ecf20Sopenharmony_ci		 * doesn't have the "alarm 23:59:59 in the future" race.
2868c2ecf20Sopenharmony_ci		 *
2878c2ecf20Sopenharmony_ci		 * NOTE:  some legacy code may have used invalid fields as
2888c2ecf20Sopenharmony_ci		 * wildcards, exposing hardware "periodic alarm" capabilities.
2898c2ecf20Sopenharmony_ci		 * Not supported here.
2908c2ecf20Sopenharmony_ci		 */
2918c2ecf20Sopenharmony_ci		{
2928c2ecf20Sopenharmony_ci			time64_t now, then;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci			err = rtc_read_time(rtc, &tm);
2958c2ecf20Sopenharmony_ci			if (err < 0)
2968c2ecf20Sopenharmony_ci				return err;
2978c2ecf20Sopenharmony_ci			now = rtc_tm_to_time64(&tm);
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci			alarm.time.tm_mday = tm.tm_mday;
3008c2ecf20Sopenharmony_ci			alarm.time.tm_mon = tm.tm_mon;
3018c2ecf20Sopenharmony_ci			alarm.time.tm_year = tm.tm_year;
3028c2ecf20Sopenharmony_ci			err  = rtc_valid_tm(&alarm.time);
3038c2ecf20Sopenharmony_ci			if (err < 0)
3048c2ecf20Sopenharmony_ci				return err;
3058c2ecf20Sopenharmony_ci			then = rtc_tm_to_time64(&alarm.time);
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci			/* alarm may need to wrap into tomorrow */
3088c2ecf20Sopenharmony_ci			if (then < now) {
3098c2ecf20Sopenharmony_ci				rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
3108c2ecf20Sopenharmony_ci				alarm.time.tm_mday = tm.tm_mday;
3118c2ecf20Sopenharmony_ci				alarm.time.tm_mon = tm.tm_mon;
3128c2ecf20Sopenharmony_ci				alarm.time.tm_year = tm.tm_year;
3138c2ecf20Sopenharmony_ci			}
3148c2ecf20Sopenharmony_ci		}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci		return rtc_set_alarm(rtc, &alarm);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	case RTC_RD_TIME:
3198c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci		err = rtc_read_time(rtc, &tm);
3228c2ecf20Sopenharmony_ci		if (err < 0)
3238c2ecf20Sopenharmony_ci			return err;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci		if (copy_to_user(uarg, &tm, sizeof(tm)))
3268c2ecf20Sopenharmony_ci			err = -EFAULT;
3278c2ecf20Sopenharmony_ci		return err;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	case RTC_SET_TIME:
3308c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci		if (copy_from_user(&tm, uarg, sizeof(tm)))
3338c2ecf20Sopenharmony_ci			return -EFAULT;
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci		return rtc_set_time(rtc, &tm);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	case RTC_PIE_ON:
3388c2ecf20Sopenharmony_ci		err = rtc_irq_set_state(rtc, 1);
3398c2ecf20Sopenharmony_ci		break;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	case RTC_PIE_OFF:
3428c2ecf20Sopenharmony_ci		err = rtc_irq_set_state(rtc, 0);
3438c2ecf20Sopenharmony_ci		break;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	case RTC_AIE_ON:
3468c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3478c2ecf20Sopenharmony_ci		return rtc_alarm_irq_enable(rtc, 1);
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	case RTC_AIE_OFF:
3508c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3518c2ecf20Sopenharmony_ci		return rtc_alarm_irq_enable(rtc, 0);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	case RTC_UIE_ON:
3548c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3558c2ecf20Sopenharmony_ci		return rtc_update_irq_enable(rtc, 1);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	case RTC_UIE_OFF:
3588c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3598c2ecf20Sopenharmony_ci		return rtc_update_irq_enable(rtc, 0);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	case RTC_IRQP_SET:
3628c2ecf20Sopenharmony_ci		err = rtc_irq_set_freq(rtc, arg);
3638c2ecf20Sopenharmony_ci		break;
3648c2ecf20Sopenharmony_ci	case RTC_IRQP_READ:
3658c2ecf20Sopenharmony_ci		err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
3668c2ecf20Sopenharmony_ci		break;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	case RTC_WKALM_SET:
3698c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3708c2ecf20Sopenharmony_ci		if (copy_from_user(&alarm, uarg, sizeof(alarm)))
3718c2ecf20Sopenharmony_ci			return -EFAULT;
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci		return rtc_set_alarm(rtc, &alarm);
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	case RTC_WKALM_RD:
3768c2ecf20Sopenharmony_ci		mutex_unlock(&rtc->ops_lock);
3778c2ecf20Sopenharmony_ci		err = rtc_read_alarm(rtc, &alarm);
3788c2ecf20Sopenharmony_ci		if (err < 0)
3798c2ecf20Sopenharmony_ci			return err;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci		if (copy_to_user(uarg, &alarm, sizeof(alarm)))
3828c2ecf20Sopenharmony_ci			err = -EFAULT;
3838c2ecf20Sopenharmony_ci		return err;
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	default:
3868c2ecf20Sopenharmony_ci		/* Finally try the driver's ioctl interface */
3878c2ecf20Sopenharmony_ci		if (ops->ioctl) {
3888c2ecf20Sopenharmony_ci			err = ops->ioctl(rtc->dev.parent, cmd, arg);
3898c2ecf20Sopenharmony_ci			if (err == -ENOIOCTLCMD)
3908c2ecf20Sopenharmony_ci				err = -ENOTTY;
3918c2ecf20Sopenharmony_ci		} else {
3928c2ecf20Sopenharmony_ci			err = -ENOTTY;
3938c2ecf20Sopenharmony_ci		}
3948c2ecf20Sopenharmony_ci		break;
3958c2ecf20Sopenharmony_ci	}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_cidone:
3988c2ecf20Sopenharmony_ci	mutex_unlock(&rtc->ops_lock);
3998c2ecf20Sopenharmony_ci	return err;
4008c2ecf20Sopenharmony_ci}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
4038c2ecf20Sopenharmony_ci#define RTC_IRQP_SET32		_IOW('p', 0x0c, __u32)
4048c2ecf20Sopenharmony_ci#define RTC_IRQP_READ32		_IOR('p', 0x0b, __u32)
4058c2ecf20Sopenharmony_ci#define RTC_EPOCH_SET32		_IOW('p', 0x0e, __u32)
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_cistatic long rtc_dev_compat_ioctl(struct file *file,
4088c2ecf20Sopenharmony_ci				 unsigned int cmd, unsigned long arg)
4098c2ecf20Sopenharmony_ci{
4108c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
4118c2ecf20Sopenharmony_ci	void __user *uarg = compat_ptr(arg);
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	switch (cmd) {
4148c2ecf20Sopenharmony_ci	case RTC_IRQP_READ32:
4158c2ecf20Sopenharmony_ci		return put_user(rtc->irq_freq, (__u32 __user *)uarg);
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	case RTC_IRQP_SET32:
4188c2ecf20Sopenharmony_ci		/* arg is a plain integer, not pointer */
4198c2ecf20Sopenharmony_ci		return rtc_dev_ioctl(file, RTC_IRQP_SET, arg);
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	case RTC_EPOCH_SET32:
4228c2ecf20Sopenharmony_ci		/* arg is a plain integer, not pointer */
4238c2ecf20Sopenharmony_ci		return rtc_dev_ioctl(file, RTC_EPOCH_SET, arg);
4248c2ecf20Sopenharmony_ci	}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci	return rtc_dev_ioctl(file, cmd, (unsigned long)uarg);
4278c2ecf20Sopenharmony_ci}
4288c2ecf20Sopenharmony_ci#endif
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_cistatic int rtc_dev_fasync(int fd, struct file *file, int on)
4318c2ecf20Sopenharmony_ci{
4328c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	return fasync_helper(fd, file, on, &rtc->async_queue);
4358c2ecf20Sopenharmony_ci}
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_cistatic int rtc_dev_release(struct inode *inode, struct file *file)
4388c2ecf20Sopenharmony_ci{
4398c2ecf20Sopenharmony_ci	struct rtc_device *rtc = file->private_data;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	/* We shut down the repeating IRQs that userspace enabled,
4428c2ecf20Sopenharmony_ci	 * since nothing is listening to them.
4438c2ecf20Sopenharmony_ci	 *  - Update (UIE) ... currently only managed through ioctls
4448c2ecf20Sopenharmony_ci	 *  - Periodic (PIE) ... also used through rtc_*() interface calls
4458c2ecf20Sopenharmony_ci	 *
4468c2ecf20Sopenharmony_ci	 * Leave the alarm alone; it may be set to trigger a system wakeup
4478c2ecf20Sopenharmony_ci	 * later, or be used by kernel code, and is a one-shot event anyway.
4488c2ecf20Sopenharmony_ci	 */
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	/* Keep ioctl until all drivers are converted */
4518c2ecf20Sopenharmony_ci	rtc_dev_ioctl(file, RTC_UIE_OFF, 0);
4528c2ecf20Sopenharmony_ci	rtc_update_irq_enable(rtc, 0);
4538c2ecf20Sopenharmony_ci	rtc_irq_set_state(rtc, 0);
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);
4568c2ecf20Sopenharmony_ci	return 0;
4578c2ecf20Sopenharmony_ci}
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_cistatic const struct file_operations rtc_dev_fops = {
4608c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
4618c2ecf20Sopenharmony_ci	.llseek		= no_llseek,
4628c2ecf20Sopenharmony_ci	.read		= rtc_dev_read,
4638c2ecf20Sopenharmony_ci	.poll		= rtc_dev_poll,
4648c2ecf20Sopenharmony_ci	.unlocked_ioctl	= rtc_dev_ioctl,
4658c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
4668c2ecf20Sopenharmony_ci	.compat_ioctl	= rtc_dev_compat_ioctl,
4678c2ecf20Sopenharmony_ci#endif
4688c2ecf20Sopenharmony_ci	.open		= rtc_dev_open,
4698c2ecf20Sopenharmony_ci	.release	= rtc_dev_release,
4708c2ecf20Sopenharmony_ci	.fasync		= rtc_dev_fasync,
4718c2ecf20Sopenharmony_ci};
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci/* insertion/removal hooks */
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_civoid rtc_dev_prepare(struct rtc_device *rtc)
4768c2ecf20Sopenharmony_ci{
4778c2ecf20Sopenharmony_ci	if (!rtc_devt)
4788c2ecf20Sopenharmony_ci		return;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	if (rtc->id >= RTC_DEV_MAX) {
4818c2ecf20Sopenharmony_ci		dev_dbg(&rtc->dev, "too many RTC devices\n");
4828c2ecf20Sopenharmony_ci		return;
4838c2ecf20Sopenharmony_ci	}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
4888c2ecf20Sopenharmony_ci	INIT_WORK(&rtc->uie_task, rtc_uie_task);
4898c2ecf20Sopenharmony_ci	timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
4908c2ecf20Sopenharmony_ci#endif
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci	cdev_init(&rtc->char_dev, &rtc_dev_fops);
4938c2ecf20Sopenharmony_ci	rtc->char_dev.owner = rtc->owner;
4948c2ecf20Sopenharmony_ci}
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_civoid __init rtc_dev_init(void)
4978c2ecf20Sopenharmony_ci{
4988c2ecf20Sopenharmony_ci	int err;
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_ci	err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
5018c2ecf20Sopenharmony_ci	if (err < 0)
5028c2ecf20Sopenharmony_ci		pr_err("failed to allocate char dev region\n");
5038c2ecf20Sopenharmony_ci}
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_civoid __exit rtc_dev_exit(void)
5068c2ecf20Sopenharmony_ci{
5078c2ecf20Sopenharmony_ci	if (rtc_devt)
5088c2ecf20Sopenharmony_ci		unregister_chrdev_region(rtc_devt, RTC_DEV_MAX);
5098c2ecf20Sopenharmony_ci}
510