162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * arch/sh/boards/dreamcast/rtc.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Dreamcast AICA RTC routines.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (c) 2001, 2002 M. R. Brown <mrbrown@0xd6.org>
862306a36Sopenharmony_ci * Copyright (c) 2002 Paul Mundt <lethal@chaoticdreams.org>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/time.h>
1262306a36Sopenharmony_ci#include <linux/rtc.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* The AICA RTC has an Epoch of 1/1/1950, so we must subtract 20 years (in
1762306a36Sopenharmony_ci   seconds) to get the standard Unix Epoch when getting the time, and add
1862306a36Sopenharmony_ci   20 years when setting the time. */
1962306a36Sopenharmony_ci#define TWENTY_YEARS ((20 * 365LU + 5) * 86400)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* The AICA RTC is represented by a 32-bit seconds counter stored in 2 16-bit
2262306a36Sopenharmony_ci   registers.*/
2362306a36Sopenharmony_ci#define AICA_RTC_SECS_H		0xa0710000
2462306a36Sopenharmony_ci#define AICA_RTC_SECS_L		0xa0710004
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/**
2762306a36Sopenharmony_ci * aica_rtc_gettimeofday - Get the time from the AICA RTC
2862306a36Sopenharmony_ci * @dev: the RTC device (ignored)
2962306a36Sopenharmony_ci * @tm: pointer to resulting RTC time structure
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci * Grabs the current RTC seconds counter and adjusts it to the Unix Epoch.
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_cistatic int aica_rtc_gettimeofday(struct device *dev, struct rtc_time *tm)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	unsigned long val1, val2;
3662306a36Sopenharmony_ci	time64_t t;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	do {
3962306a36Sopenharmony_ci		val1 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
4062306a36Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci		val2 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
4362306a36Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
4462306a36Sopenharmony_ci	} while (val1 != val2);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/* normalize to 1970..2106 time range */
4762306a36Sopenharmony_ci	t = (u32)(val1 - TWENTY_YEARS);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	rtc_time64_to_tm(t, tm);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	return 0;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/**
5562306a36Sopenharmony_ci * aica_rtc_settimeofday - Set the AICA RTC to the current time
5662306a36Sopenharmony_ci * @dev: the RTC device (ignored)
5762306a36Sopenharmony_ci * @tm: pointer to new RTC time structure
5862306a36Sopenharmony_ci *
5962306a36Sopenharmony_ci * Adjusts the given @tv to the AICA Epoch and sets the RTC seconds counter.
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_cistatic int aica_rtc_settimeofday(struct device *dev, struct rtc_time *tm)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	unsigned long val1, val2;
6462306a36Sopenharmony_ci	time64_t secs = rtc_tm_to_time64(tm);
6562306a36Sopenharmony_ci	u32 adj = secs + TWENTY_YEARS;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	do {
6862306a36Sopenharmony_ci		__raw_writel((adj & 0xffff0000) >> 16, AICA_RTC_SECS_H);
6962306a36Sopenharmony_ci		__raw_writel((adj & 0xffff), AICA_RTC_SECS_L);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		val1 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
7262306a36Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		val2 = ((__raw_readl(AICA_RTC_SECS_H) & 0xffff) << 16) |
7562306a36Sopenharmony_ci			(__raw_readl(AICA_RTC_SECS_L) & 0xffff);
7662306a36Sopenharmony_ci	} while (val1 != val2);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	return 0;
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic const struct rtc_class_ops rtc_generic_ops = {
8262306a36Sopenharmony_ci	.read_time = aica_rtc_gettimeofday,
8362306a36Sopenharmony_ci	.set_time = aica_rtc_settimeofday,
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int __init aica_time_init(void)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	struct platform_device *pdev;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	pdev = platform_device_register_data(NULL, "rtc-generic", -1,
9162306a36Sopenharmony_ci					     &rtc_generic_ops,
9262306a36Sopenharmony_ci					     sizeof(rtc_generic_ops));
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(pdev);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ciarch_initcall(aica_time_init);
97