162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  linux/arch/alpha/kernel/rtc.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 1991, 1992, 1995, 1999, 2000  Linus Torvalds
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This file contains date handling.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/param.h>
1362306a36Sopenharmony_ci#include <linux/string.h>
1462306a36Sopenharmony_ci#include <linux/mc146818rtc.h>
1562306a36Sopenharmony_ci#include <linux/bcd.h>
1662306a36Sopenharmony_ci#include <linux/rtc.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "proto.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/*
2362306a36Sopenharmony_ci * Support for the RTC device.
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * We don't want to use the rtc-cmos driver, because we don't want to support
2662306a36Sopenharmony_ci * alarms, as that would be indistinguishable from timer interrupts.
2762306a36Sopenharmony_ci *
2862306a36Sopenharmony_ci * Further, generic code is really, really tied to a 1900 epoch.  This is
2962306a36Sopenharmony_ci * true in __get_rtc_time as well as the users of struct rtc_time e.g.
3062306a36Sopenharmony_ci * rtc_tm_to_time.  Thankfully all of the other epochs in use are later
3162306a36Sopenharmony_ci * than 1900, and so it's easy to adjust.
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic unsigned long rtc_epoch;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int __init
3762306a36Sopenharmony_cispecifiy_epoch(char *str)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	unsigned long epoch = simple_strtoul(str, NULL, 0);
4062306a36Sopenharmony_ci	if (epoch < 1900)
4162306a36Sopenharmony_ci		printk("Ignoring invalid user specified epoch %lu\n", epoch);
4262306a36Sopenharmony_ci	else
4362306a36Sopenharmony_ci		rtc_epoch = epoch;
4462306a36Sopenharmony_ci	return 1;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci__setup("epoch=", specifiy_epoch);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void __init
4962306a36Sopenharmony_ciinit_rtc_epoch(void)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	int epoch, year, ctrl;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (rtc_epoch != 0) {
5462306a36Sopenharmony_ci		/* The epoch was specified on the command-line.  */
5562306a36Sopenharmony_ci		return;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	/* Detect the epoch in use on this computer.  */
5962306a36Sopenharmony_ci	ctrl = CMOS_READ(RTC_CONTROL);
6062306a36Sopenharmony_ci	year = CMOS_READ(RTC_YEAR);
6162306a36Sopenharmony_ci	if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
6262306a36Sopenharmony_ci		year = bcd2bin(year);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* PC-like is standard; used for year >= 70 */
6562306a36Sopenharmony_ci	epoch = 1900;
6662306a36Sopenharmony_ci	if (year < 20) {
6762306a36Sopenharmony_ci		epoch = 2000;
6862306a36Sopenharmony_ci	} else if (year >= 20 && year < 48) {
6962306a36Sopenharmony_ci		/* NT epoch */
7062306a36Sopenharmony_ci		epoch = 1980;
7162306a36Sopenharmony_ci	} else if (year >= 48 && year < 70) {
7262306a36Sopenharmony_ci		/* Digital UNIX epoch */
7362306a36Sopenharmony_ci		epoch = 1952;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci	rtc_epoch = epoch;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	printk(KERN_INFO "Using epoch %d for rtc year %d\n", epoch, year);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic int
8162306a36Sopenharmony_cialpha_rtc_read_time(struct device *dev, struct rtc_time *tm)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	int ret = mc146818_get_time(tm, 10);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (ret < 0) {
8662306a36Sopenharmony_ci		dev_err_ratelimited(dev, "unable to read current time\n");
8762306a36Sopenharmony_ci		return ret;
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/* Adjust for non-default epochs.  It's easier to depend on the
9162306a36Sopenharmony_ci	   generic __get_rtc_time and adjust the epoch here than create
9262306a36Sopenharmony_ci	   a copy of __get_rtc_time with the edits we need.  */
9362306a36Sopenharmony_ci	if (rtc_epoch != 1900) {
9462306a36Sopenharmony_ci		int year = tm->tm_year;
9562306a36Sopenharmony_ci		/* Undo the century adjustment made in __get_rtc_time.  */
9662306a36Sopenharmony_ci		if (year >= 100)
9762306a36Sopenharmony_ci			year -= 100;
9862306a36Sopenharmony_ci		year += rtc_epoch - 1900;
9962306a36Sopenharmony_ci		/* Redo the century adjustment with the epoch in place.  */
10062306a36Sopenharmony_ci		if (year <= 69)
10162306a36Sopenharmony_ci			year += 100;
10262306a36Sopenharmony_ci		tm->tm_year = year;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int
10962306a36Sopenharmony_cialpha_rtc_set_time(struct device *dev, struct rtc_time *tm)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct rtc_time xtm;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (rtc_epoch != 1900) {
11462306a36Sopenharmony_ci		xtm = *tm;
11562306a36Sopenharmony_ci		xtm.tm_year -= rtc_epoch - 1900;
11662306a36Sopenharmony_ci		tm = &xtm;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return mc146818_set_time(tm);
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic int
12362306a36Sopenharmony_cialpha_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	switch (cmd) {
12662306a36Sopenharmony_ci	case RTC_EPOCH_READ:
12762306a36Sopenharmony_ci		return put_user(rtc_epoch, (unsigned long __user *)arg);
12862306a36Sopenharmony_ci	case RTC_EPOCH_SET:
12962306a36Sopenharmony_ci		if (arg < 1900)
13062306a36Sopenharmony_ci			return -EINVAL;
13162306a36Sopenharmony_ci		rtc_epoch = arg;
13262306a36Sopenharmony_ci		return 0;
13362306a36Sopenharmony_ci	default:
13462306a36Sopenharmony_ci		return -ENOIOCTLCMD;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct rtc_class_ops alpha_rtc_ops = {
13962306a36Sopenharmony_ci	.read_time = alpha_rtc_read_time,
14062306a36Sopenharmony_ci	.set_time = alpha_rtc_set_time,
14162306a36Sopenharmony_ci	.ioctl = alpha_rtc_ioctl,
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/*
14562306a36Sopenharmony_ci * Similarly, except do the actual CMOS access on the boot cpu only.
14662306a36Sopenharmony_ci * This requires marshalling the data across an interprocessor call.
14762306a36Sopenharmony_ci */
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci#if defined(CONFIG_SMP) && \
15062306a36Sopenharmony_ci    (defined(CONFIG_ALPHA_GENERIC) || defined(CONFIG_ALPHA_MARVEL))
15162306a36Sopenharmony_ci# define HAVE_REMOTE_RTC 1
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ciunion remote_data {
15462306a36Sopenharmony_ci	struct rtc_time *tm;
15562306a36Sopenharmony_ci	long retval;
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void
15962306a36Sopenharmony_cido_remote_read(void *data)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	union remote_data *x = data;
16262306a36Sopenharmony_ci	x->retval = alpha_rtc_read_time(NULL, x->tm);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic int
16662306a36Sopenharmony_ciremote_read_time(struct device *dev, struct rtc_time *tm)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	union remote_data x;
16962306a36Sopenharmony_ci	if (smp_processor_id() != boot_cpuid) {
17062306a36Sopenharmony_ci		x.tm = tm;
17162306a36Sopenharmony_ci		smp_call_function_single(boot_cpuid, do_remote_read, &x, 1);
17262306a36Sopenharmony_ci		return x.retval;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci	return alpha_rtc_read_time(NULL, tm);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic void
17862306a36Sopenharmony_cido_remote_set(void *data)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	union remote_data *x = data;
18162306a36Sopenharmony_ci	x->retval = alpha_rtc_set_time(NULL, x->tm);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic int
18562306a36Sopenharmony_ciremote_set_time(struct device *dev, struct rtc_time *tm)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	union remote_data x;
18862306a36Sopenharmony_ci	if (smp_processor_id() != boot_cpuid) {
18962306a36Sopenharmony_ci		x.tm = tm;
19062306a36Sopenharmony_ci		smp_call_function_single(boot_cpuid, do_remote_set, &x, 1);
19162306a36Sopenharmony_ci		return x.retval;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci	return alpha_rtc_set_time(NULL, tm);
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic const struct rtc_class_ops remote_rtc_ops = {
19762306a36Sopenharmony_ci	.read_time = remote_read_time,
19862306a36Sopenharmony_ci	.set_time = remote_set_time,
19962306a36Sopenharmony_ci	.ioctl = alpha_rtc_ioctl,
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci#endif
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic int __init
20462306a36Sopenharmony_cialpha_rtc_init(void)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	struct platform_device *pdev;
20762306a36Sopenharmony_ci	struct rtc_device *rtc;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	init_rtc_epoch();
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	pdev = platform_device_register_simple("rtc-alpha", -1, NULL, 0);
21262306a36Sopenharmony_ci	rtc = devm_rtc_allocate_device(&pdev->dev);
21362306a36Sopenharmony_ci	if (IS_ERR(rtc))
21462306a36Sopenharmony_ci		return PTR_ERR(rtc);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	platform_set_drvdata(pdev, rtc);
21762306a36Sopenharmony_ci	rtc->ops = &alpha_rtc_ops;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci#ifdef HAVE_REMOTE_RTC
22062306a36Sopenharmony_ci	if (alpha_mv.rtc_boot_cpu_only)
22162306a36Sopenharmony_ci		rtc->ops = &remote_rtc_ops;
22262306a36Sopenharmony_ci#endif
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return devm_rtc_register_device(rtc);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_cidevice_initcall(alpha_rtc_init);
227