18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * arch/sh/boards/dreamcast/rtc.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Dreamcast AICA RTC routines.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (c) 2001, 2002 M. R. Brown <mrbrown@0xd6.org>
88c2ecf20Sopenharmony_ci * Copyright (c) 2002 Paul Mundt <lethal@chaoticdreams.org>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/time.h>
128c2ecf20Sopenharmony_ci#include <linux/rtc.h>
138c2ecf20Sopenharmony_ci#include <linux/io.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci/* The AICA RTC has an Epoch of 1/1/1950, so we must subtract 20 years (in
178c2ecf20Sopenharmony_ci   seconds) to get the standard Unix Epoch when getting the time, and add
188c2ecf20Sopenharmony_ci   20 years when setting the time. */
198c2ecf20Sopenharmony_ci#define TWENTY_YEARS ((20 * 365LU + 5) * 86400)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/* The AICA RTC is represented by a 32-bit seconds counter stored in 2 16-bit
228c2ecf20Sopenharmony_ci   registers.*/
238c2ecf20Sopenharmony_ci#define AICA_RTC_SECS_H		0xa0710000
248c2ecf20Sopenharmony_ci#define AICA_RTC_SECS_L		0xa0710004
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/**
278c2ecf20Sopenharmony_ci * aica_rtc_gettimeofday - Get the time from the AICA RTC
288c2ecf20Sopenharmony_ci * @dev: the RTC device (ignored)
298c2ecf20Sopenharmony_ci * @tm: pointer to resulting RTC time structure
308c2ecf20Sopenharmony_ci *
318c2ecf20Sopenharmony_ci * Grabs the current RTC seconds counter and adjusts it to the Unix Epoch.
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_cistatic int aica_rtc_gettimeofday(struct device *dev, struct rtc_time *tm)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	unsigned long val1, val2;
368c2ecf20Sopenharmony_ci	time64_t t;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	do {
398c2ecf20Sopenharmony_ci		val1 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
408c2ecf20Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci		val2 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
438c2ecf20Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
448c2ecf20Sopenharmony_ci	} while (val1 != val2);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	/* normalize to 1970..2106 time range */
478c2ecf20Sopenharmony_ci	t = (u32)(val1 - TWENTY_YEARS);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	rtc_time64_to_tm(t, tm);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	return 0;
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/**
558c2ecf20Sopenharmony_ci * aica_rtc_settimeofday - Set the AICA RTC to the current time
568c2ecf20Sopenharmony_ci * @dev: the RTC device (ignored)
578c2ecf20Sopenharmony_ci * @tm: pointer to new RTC time structure
588c2ecf20Sopenharmony_ci *
598c2ecf20Sopenharmony_ci * Adjusts the given @tv to the AICA Epoch and sets the RTC seconds counter.
608c2ecf20Sopenharmony_ci */
618c2ecf20Sopenharmony_cistatic int aica_rtc_settimeofday(struct device *dev, struct rtc_time *tm)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	unsigned long val1, val2;
648c2ecf20Sopenharmony_ci	time64_t secs = rtc_tm_to_time64(tm);
658c2ecf20Sopenharmony_ci	u32 adj = secs + TWENTY_YEARS;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	do {
688c2ecf20Sopenharmony_ci		__raw_writel((adj & 0xffff0000) >> 16, AICA_RTC_SECS_H);
698c2ecf20Sopenharmony_ci		__raw_writel((adj & 0xffff), AICA_RTC_SECS_L);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		val1 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
728c2ecf20Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci		val2 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
758c2ecf20Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
768c2ecf20Sopenharmony_ci	} while (val1 != val2);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	return 0;
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic const struct rtc_class_ops rtc_generic_ops = {
828c2ecf20Sopenharmony_ci	.read_time = aica_rtc_gettimeofday,
838c2ecf20Sopenharmony_ci	.set_time = aica_rtc_settimeofday,
848c2ecf20Sopenharmony_ci};
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic int __init aica_time_init(void)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	struct platform_device *pdev;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	pdev = platform_device_register_data(NULL, "rtc-generic", -1,
918c2ecf20Sopenharmony_ci					     &rtc_generic_ops,
928c2ecf20Sopenharmony_ci					     sizeof(rtc_generic_ops));
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(pdev);
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ciarch_initcall(aica_time_init);
97