forked from Minki/linux
0d20e9fb12
BSM or Backup Switch Mode is a common feature on RTCs, allowing to select how the RTC will decide when to switch from its primary power supply to the backup power supply. It is necessary to be able to set it from userspace as there are uses cases where it has to be done dynamically. Supported values are: RTC_BSM_DISABLED: disabled RTC_BSM_DIRECT: switching will happen as soon as Vbackup > Vdd RTC_BSM_LEVEL: switching will happen around a threshold, usually with an hysteresis RTC_BSM_STANDBY: switching will not happen until Vdd > Vbackup, this is useful to ensure the RTC doesn't draw any power until the device is first powered on. Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Link: https://lore.kernel.org/r/20211018151933.76865-6-alexandre.belloni@bootlin.com
575 lines
13 KiB
C
575 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* RTC subsystem, dev interface
|
|
*
|
|
* Copyright (C) 2005 Tower Technologies
|
|
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
|
*
|
|
* based on arch/arm/common/rtctime.c
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/module.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/sched/signal.h>
|
|
#include "rtc-core.h"
|
|
|
|
static dev_t rtc_devt;
|
|
|
|
#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
|
|
|
|
static int rtc_dev_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rtc_device *rtc = container_of(inode->i_cdev,
|
|
struct rtc_device, char_dev);
|
|
|
|
if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))
|
|
return -EBUSY;
|
|
|
|
file->private_data = rtc;
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
rtc->irq_data = 0;
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
|
/*
|
|
* Routine to poll RTC seconds field for change as often as possible,
|
|
* after first RTC_UIE use timer to reduce polling
|
|
*/
|
|
static void rtc_uie_task(struct work_struct *work)
|
|
{
|
|
struct rtc_device *rtc =
|
|
container_of(work, struct rtc_device, uie_task);
|
|
struct rtc_time tm;
|
|
int num = 0;
|
|
int err;
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
if (rtc->stop_uie_polling || err) {
|
|
rtc->uie_task_active = 0;
|
|
} else if (rtc->oldsecs != tm.tm_sec) {
|
|
num = (tm.tm_sec + 60 - rtc->oldsecs) % 60;
|
|
rtc->oldsecs = tm.tm_sec;
|
|
rtc->uie_timer.expires = jiffies + HZ - (HZ / 10);
|
|
rtc->uie_timer_active = 1;
|
|
rtc->uie_task_active = 0;
|
|
add_timer(&rtc->uie_timer);
|
|
} else if (schedule_work(&rtc->uie_task) == 0) {
|
|
rtc->uie_task_active = 0;
|
|
}
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
if (num)
|
|
rtc_handle_legacy_irq(rtc, num, RTC_UF);
|
|
}
|
|
|
|
static void rtc_uie_timer(struct timer_list *t)
|
|
{
|
|
struct rtc_device *rtc = from_timer(rtc, t, uie_timer);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&rtc->irq_lock, flags);
|
|
rtc->uie_timer_active = 0;
|
|
rtc->uie_task_active = 1;
|
|
if ((schedule_work(&rtc->uie_task) == 0))
|
|
rtc->uie_task_active = 0;
|
|
spin_unlock_irqrestore(&rtc->irq_lock, flags);
|
|
}
|
|
|
|
static int clear_uie(struct rtc_device *rtc)
|
|
{
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
if (rtc->uie_irq_active) {
|
|
rtc->stop_uie_polling = 1;
|
|
if (rtc->uie_timer_active) {
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
del_timer_sync(&rtc->uie_timer);
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
rtc->uie_timer_active = 0;
|
|
}
|
|
if (rtc->uie_task_active) {
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
flush_scheduled_work();
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
}
|
|
rtc->uie_irq_active = 0;
|
|
}
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int set_uie(struct rtc_device *rtc)
|
|
{
|
|
struct rtc_time tm;
|
|
int err;
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
if (err)
|
|
return err;
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
if (!rtc->uie_irq_active) {
|
|
rtc->uie_irq_active = 1;
|
|
rtc->stop_uie_polling = 0;
|
|
rtc->oldsecs = tm.tm_sec;
|
|
rtc->uie_task_active = 1;
|
|
if (schedule_work(&rtc->uie_task) == 0)
|
|
rtc->uie_task_active = 0;
|
|
}
|
|
rtc->irq_data = 0;
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
return 0;
|
|
}
|
|
|
|
int rtc_dev_update_irq_enable_emul(struct rtc_device *rtc, unsigned int enabled)
|
|
{
|
|
if (enabled)
|
|
return set_uie(rtc);
|
|
else
|
|
return clear_uie(rtc);
|
|
}
|
|
EXPORT_SYMBOL(rtc_dev_update_irq_enable_emul);
|
|
|
|
#endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */
|
|
|
|
static ssize_t
|
|
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct rtc_device *rtc = file->private_data;
|
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
unsigned long data;
|
|
ssize_t ret;
|
|
|
|
if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
|
|
return -EINVAL;
|
|
|
|
add_wait_queue(&rtc->irq_queue, &wait);
|
|
do {
|
|
__set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
spin_lock_irq(&rtc->irq_lock);
|
|
data = rtc->irq_data;
|
|
rtc->irq_data = 0;
|
|
spin_unlock_irq(&rtc->irq_lock);
|
|
|
|
if (data != 0) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
ret = -EAGAIN;
|
|
break;
|
|
}
|
|
if (signal_pending(current)) {
|
|
ret = -ERESTARTSYS;
|
|
break;
|
|
}
|
|
schedule();
|
|
} while (1);
|
|
set_current_state(TASK_RUNNING);
|
|
remove_wait_queue(&rtc->irq_queue, &wait);
|
|
|
|
if (ret == 0) {
|
|
if (sizeof(int) != sizeof(long) &&
|
|
count == sizeof(unsigned int))
|
|
ret = put_user(data, (unsigned int __user *)buf) ?:
|
|
sizeof(unsigned int);
|
|
else
|
|
ret = put_user(data, (unsigned long __user *)buf) ?:
|
|
sizeof(unsigned long);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static __poll_t rtc_dev_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct rtc_device *rtc = file->private_data;
|
|
unsigned long data;
|
|
|
|
poll_wait(file, &rtc->irq_queue, wait);
|
|
|
|
data = rtc->irq_data;
|
|
|
|
return (data != 0) ? (EPOLLIN | EPOLLRDNORM) : 0;
|
|
}
|
|
|
|
static long rtc_dev_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
int err = 0;
|
|
struct rtc_device *rtc = file->private_data;
|
|
const struct rtc_class_ops *ops = rtc->ops;
|
|
struct rtc_time tm;
|
|
struct rtc_wkalrm alarm;
|
|
struct rtc_param param;
|
|
void __user *uarg = (void __user *)arg;
|
|
|
|
err = mutex_lock_interruptible(&rtc->ops_lock);
|
|
if (err)
|
|
return err;
|
|
|
|
/* check that the calling task has appropriate permissions
|
|
* for certain ioctls. doing this check here is useful
|
|
* to avoid duplicate code in each driver.
|
|
*/
|
|
switch (cmd) {
|
|
case RTC_EPOCH_SET:
|
|
case RTC_SET_TIME:
|
|
case RTC_PARAM_SET:
|
|
if (!capable(CAP_SYS_TIME))
|
|
err = -EACCES;
|
|
break;
|
|
|
|
case RTC_IRQP_SET:
|
|
if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
|
|
err = -EACCES;
|
|
break;
|
|
|
|
case RTC_PIE_ON:
|
|
if (rtc->irq_freq > rtc->max_user_freq &&
|
|
!capable(CAP_SYS_RESOURCE))
|
|
err = -EACCES;
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
goto done;
|
|
|
|
/*
|
|
* Drivers *SHOULD NOT* provide ioctl implementations
|
|
* for these requests. Instead, provide methods to
|
|
* support the following code, so that the RTC's main
|
|
* features are accessible without using ioctls.
|
|
*
|
|
* RTC and alarm times will be in UTC, by preference,
|
|
* but dual-booting with MS-Windows implies RTCs must
|
|
* use the local wall clock time.
|
|
*/
|
|
|
|
switch (cmd) {
|
|
case RTC_ALM_READ:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
err = rtc_read_alarm(rtc, &alarm);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
|
|
err = -EFAULT;
|
|
return err;
|
|
|
|
case RTC_ALM_SET:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
|
|
return -EFAULT;
|
|
|
|
alarm.enabled = 0;
|
|
alarm.pending = 0;
|
|
alarm.time.tm_wday = -1;
|
|
alarm.time.tm_yday = -1;
|
|
alarm.time.tm_isdst = -1;
|
|
|
|
/* RTC_ALM_SET alarms may be up to 24 hours in the future.
|
|
* Rather than expecting every RTC to implement "don't care"
|
|
* for day/month/year fields, just force the alarm to have
|
|
* the right values for those fields.
|
|
*
|
|
* RTC_WKALM_SET should be used instead. Not only does it
|
|
* eliminate the need for a separate RTC_AIE_ON call, it
|
|
* doesn't have the "alarm 23:59:59 in the future" race.
|
|
*
|
|
* NOTE: some legacy code may have used invalid fields as
|
|
* wildcards, exposing hardware "periodic alarm" capabilities.
|
|
* Not supported here.
|
|
*/
|
|
{
|
|
time64_t now, then;
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
if (err < 0)
|
|
return err;
|
|
now = rtc_tm_to_time64(&tm);
|
|
|
|
alarm.time.tm_mday = tm.tm_mday;
|
|
alarm.time.tm_mon = tm.tm_mon;
|
|
alarm.time.tm_year = tm.tm_year;
|
|
err = rtc_valid_tm(&alarm.time);
|
|
if (err < 0)
|
|
return err;
|
|
then = rtc_tm_to_time64(&alarm.time);
|
|
|
|
/* alarm may need to wrap into tomorrow */
|
|
if (then < now) {
|
|
rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
|
|
alarm.time.tm_mday = tm.tm_mday;
|
|
alarm.time.tm_mon = tm.tm_mon;
|
|
alarm.time.tm_year = tm.tm_year;
|
|
}
|
|
}
|
|
|
|
return rtc_set_alarm(rtc, &alarm);
|
|
|
|
case RTC_RD_TIME:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
err = rtc_read_time(rtc, &tm);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (copy_to_user(uarg, &tm, sizeof(tm)))
|
|
err = -EFAULT;
|
|
return err;
|
|
|
|
case RTC_SET_TIME:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
|
|
if (copy_from_user(&tm, uarg, sizeof(tm)))
|
|
return -EFAULT;
|
|
|
|
return rtc_set_time(rtc, &tm);
|
|
|
|
case RTC_PIE_ON:
|
|
err = rtc_irq_set_state(rtc, 1);
|
|
break;
|
|
|
|
case RTC_PIE_OFF:
|
|
err = rtc_irq_set_state(rtc, 0);
|
|
break;
|
|
|
|
case RTC_AIE_ON:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return rtc_alarm_irq_enable(rtc, 1);
|
|
|
|
case RTC_AIE_OFF:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return rtc_alarm_irq_enable(rtc, 0);
|
|
|
|
case RTC_UIE_ON:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return rtc_update_irq_enable(rtc, 1);
|
|
|
|
case RTC_UIE_OFF:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return rtc_update_irq_enable(rtc, 0);
|
|
|
|
case RTC_IRQP_SET:
|
|
err = rtc_irq_set_freq(rtc, arg);
|
|
break;
|
|
case RTC_IRQP_READ:
|
|
err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
|
|
break;
|
|
|
|
case RTC_WKALM_SET:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
if (copy_from_user(&alarm, uarg, sizeof(alarm)))
|
|
return -EFAULT;
|
|
|
|
return rtc_set_alarm(rtc, &alarm);
|
|
|
|
case RTC_WKALM_RD:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
err = rtc_read_alarm(rtc, &alarm);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (copy_to_user(uarg, &alarm, sizeof(alarm)))
|
|
err = -EFAULT;
|
|
return err;
|
|
|
|
case RTC_PARAM_GET:
|
|
if (copy_from_user(¶m, uarg, sizeof(param))) {
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch(param.param) {
|
|
long offset;
|
|
case RTC_PARAM_FEATURES:
|
|
if (param.index != 0)
|
|
err = -EINVAL;
|
|
param.uvalue = rtc->features[0];
|
|
break;
|
|
|
|
case RTC_PARAM_CORRECTION:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
if (param.index != 0)
|
|
return -EINVAL;
|
|
err = rtc_read_offset(rtc, &offset);
|
|
mutex_lock(&rtc->ops_lock);
|
|
if (err == 0)
|
|
param.svalue = offset;
|
|
break;
|
|
|
|
default:
|
|
if (rtc->ops->param_get)
|
|
err = rtc->ops->param_get(rtc->dev.parent, ¶m);
|
|
else
|
|
err = -EINVAL;
|
|
}
|
|
|
|
if (!err)
|
|
if (copy_to_user(uarg, ¶m, sizeof(param)))
|
|
err = -EFAULT;
|
|
|
|
break;
|
|
|
|
case RTC_PARAM_SET:
|
|
if (copy_from_user(¶m, uarg, sizeof(param))) {
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch(param.param) {
|
|
case RTC_PARAM_FEATURES:
|
|
err = -EINVAL;
|
|
break;
|
|
|
|
case RTC_PARAM_CORRECTION:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
if (param.index != 0)
|
|
return -EINVAL;
|
|
return rtc_set_offset(rtc, param.svalue);
|
|
|
|
default:
|
|
if (rtc->ops->param_set)
|
|
err = rtc->ops->param_set(rtc->dev.parent, ¶m);
|
|
else
|
|
err = -EINVAL;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
/* Finally try the driver's ioctl interface */
|
|
if (ops->ioctl) {
|
|
err = ops->ioctl(rtc->dev.parent, cmd, arg);
|
|
if (err == -ENOIOCTLCMD)
|
|
err = -ENOTTY;
|
|
} else {
|
|
err = -ENOTTY;
|
|
}
|
|
break;
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&rtc->ops_lock);
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
#define RTC_IRQP_SET32 _IOW('p', 0x0c, __u32)
|
|
#define RTC_IRQP_READ32 _IOR('p', 0x0b, __u32)
|
|
#define RTC_EPOCH_SET32 _IOW('p', 0x0e, __u32)
|
|
|
|
static long rtc_dev_compat_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct rtc_device *rtc = file->private_data;
|
|
void __user *uarg = compat_ptr(arg);
|
|
|
|
switch (cmd) {
|
|
case RTC_IRQP_READ32:
|
|
return put_user(rtc->irq_freq, (__u32 __user *)uarg);
|
|
|
|
case RTC_IRQP_SET32:
|
|
/* arg is a plain integer, not pointer */
|
|
return rtc_dev_ioctl(file, RTC_IRQP_SET, arg);
|
|
|
|
case RTC_EPOCH_SET32:
|
|
/* arg is a plain integer, not pointer */
|
|
return rtc_dev_ioctl(file, RTC_EPOCH_SET, arg);
|
|
}
|
|
|
|
return rtc_dev_ioctl(file, cmd, (unsigned long)uarg);
|
|
}
|
|
#endif
|
|
|
|
static int rtc_dev_fasync(int fd, struct file *file, int on)
|
|
{
|
|
struct rtc_device *rtc = file->private_data;
|
|
|
|
return fasync_helper(fd, file, on, &rtc->async_queue);
|
|
}
|
|
|
|
static int rtc_dev_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct rtc_device *rtc = file->private_data;
|
|
|
|
/* We shut down the repeating IRQs that userspace enabled,
|
|
* since nothing is listening to them.
|
|
* - Update (UIE) ... currently only managed through ioctls
|
|
* - Periodic (PIE) ... also used through rtc_*() interface calls
|
|
*
|
|
* Leave the alarm alone; it may be set to trigger a system wakeup
|
|
* later, or be used by kernel code, and is a one-shot event anyway.
|
|
*/
|
|
|
|
/* Keep ioctl until all drivers are converted */
|
|
rtc_dev_ioctl(file, RTC_UIE_OFF, 0);
|
|
rtc_update_irq_enable(rtc, 0);
|
|
rtc_irq_set_state(rtc, 0);
|
|
|
|
clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations rtc_dev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.read = rtc_dev_read,
|
|
.poll = rtc_dev_poll,
|
|
.unlocked_ioctl = rtc_dev_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = rtc_dev_compat_ioctl,
|
|
#endif
|
|
.open = rtc_dev_open,
|
|
.release = rtc_dev_release,
|
|
.fasync = rtc_dev_fasync,
|
|
};
|
|
|
|
/* insertion/removal hooks */
|
|
|
|
void rtc_dev_prepare(struct rtc_device *rtc)
|
|
{
|
|
if (!rtc_devt)
|
|
return;
|
|
|
|
if (rtc->id >= RTC_DEV_MAX) {
|
|
dev_dbg(&rtc->dev, "too many RTC devices\n");
|
|
return;
|
|
}
|
|
|
|
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
|
|
|
|
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
|
INIT_WORK(&rtc->uie_task, rtc_uie_task);
|
|
timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
|
|
#endif
|
|
|
|
cdev_init(&rtc->char_dev, &rtc_dev_fops);
|
|
rtc->char_dev.owner = rtc->owner;
|
|
}
|
|
|
|
void __init rtc_dev_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
|
|
if (err < 0)
|
|
pr_err("failed to allocate char dev region\n");
|
|
}
|
|
|
|
void __exit rtc_dev_exit(void)
|
|
{
|
|
if (rtc_devt)
|
|
unregister_chrdev_region(rtc_devt, RTC_DEV_MAX);
|
|
}
|