18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2011 Zhao Zhang <zhzhl555@gmail.com>
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Derived from driver/rtc/rtc-au1xxx.c
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/rtc.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/types.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci#include <loongson1.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define LS1X_RTC_REG_OFFSET	(LS1X_RTC_BASE + 0x20)
198c2ecf20Sopenharmony_ci#define LS1X_RTC_REGS(x) \
208c2ecf20Sopenharmony_ci		((void __iomem *)KSEG1ADDR(LS1X_RTC_REG_OFFSET + (x)))
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/*RTC programmable counters 0 and 1*/
238c2ecf20Sopenharmony_ci#define SYS_COUNTER_CNTRL		(LS1X_RTC_REGS(0x20))
248c2ecf20Sopenharmony_ci#define SYS_CNTRL_ERS			(1 << 23)
258c2ecf20Sopenharmony_ci#define SYS_CNTRL_RTS			(1 << 20)
268c2ecf20Sopenharmony_ci#define SYS_CNTRL_RM2			(1 << 19)
278c2ecf20Sopenharmony_ci#define SYS_CNTRL_RM1			(1 << 18)
288c2ecf20Sopenharmony_ci#define SYS_CNTRL_RM0			(1 << 17)
298c2ecf20Sopenharmony_ci#define SYS_CNTRL_RS			(1 << 16)
308c2ecf20Sopenharmony_ci#define SYS_CNTRL_BP			(1 << 14)
318c2ecf20Sopenharmony_ci#define SYS_CNTRL_REN			(1 << 13)
328c2ecf20Sopenharmony_ci#define SYS_CNTRL_BRT			(1 << 12)
338c2ecf20Sopenharmony_ci#define SYS_CNTRL_TEN			(1 << 11)
348c2ecf20Sopenharmony_ci#define SYS_CNTRL_BTT			(1 << 10)
358c2ecf20Sopenharmony_ci#define SYS_CNTRL_E0			(1 << 8)
368c2ecf20Sopenharmony_ci#define SYS_CNTRL_ETS			(1 << 7)
378c2ecf20Sopenharmony_ci#define SYS_CNTRL_32S			(1 << 5)
388c2ecf20Sopenharmony_ci#define SYS_CNTRL_TTS			(1 << 4)
398c2ecf20Sopenharmony_ci#define SYS_CNTRL_TM2			(1 << 3)
408c2ecf20Sopenharmony_ci#define SYS_CNTRL_TM1			(1 << 2)
418c2ecf20Sopenharmony_ci#define SYS_CNTRL_TM0			(1 << 1)
428c2ecf20Sopenharmony_ci#define SYS_CNTRL_TS			(1 << 0)
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/* Programmable Counter 0 Registers */
458c2ecf20Sopenharmony_ci#define SYS_TOYTRIM		(LS1X_RTC_REGS(0))
468c2ecf20Sopenharmony_ci#define SYS_TOYWRITE0		(LS1X_RTC_REGS(4))
478c2ecf20Sopenharmony_ci#define SYS_TOYWRITE1		(LS1X_RTC_REGS(8))
488c2ecf20Sopenharmony_ci#define SYS_TOYREAD0		(LS1X_RTC_REGS(0xC))
498c2ecf20Sopenharmony_ci#define SYS_TOYREAD1		(LS1X_RTC_REGS(0x10))
508c2ecf20Sopenharmony_ci#define SYS_TOYMATCH0		(LS1X_RTC_REGS(0x14))
518c2ecf20Sopenharmony_ci#define SYS_TOYMATCH1		(LS1X_RTC_REGS(0x18))
528c2ecf20Sopenharmony_ci#define SYS_TOYMATCH2		(LS1X_RTC_REGS(0x1C))
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/* Programmable Counter 1 Registers */
558c2ecf20Sopenharmony_ci#define SYS_RTCTRIM		(LS1X_RTC_REGS(0x40))
568c2ecf20Sopenharmony_ci#define SYS_RTCWRITE0		(LS1X_RTC_REGS(0x44))
578c2ecf20Sopenharmony_ci#define SYS_RTCREAD0		(LS1X_RTC_REGS(0x48))
588c2ecf20Sopenharmony_ci#define SYS_RTCMATCH0		(LS1X_RTC_REGS(0x4C))
598c2ecf20Sopenharmony_ci#define SYS_RTCMATCH1		(LS1X_RTC_REGS(0x50))
608c2ecf20Sopenharmony_ci#define SYS_RTCMATCH2		(LS1X_RTC_REGS(0x54))
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#define LS1X_SEC_OFFSET		(4)
638c2ecf20Sopenharmony_ci#define LS1X_MIN_OFFSET		(10)
648c2ecf20Sopenharmony_ci#define LS1X_HOUR_OFFSET	(16)
658c2ecf20Sopenharmony_ci#define LS1X_DAY_OFFSET		(21)
668c2ecf20Sopenharmony_ci#define LS1X_MONTH_OFFSET	(26)
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci#define LS1X_SEC_MASK		(0x3f)
708c2ecf20Sopenharmony_ci#define LS1X_MIN_MASK		(0x3f)
718c2ecf20Sopenharmony_ci#define LS1X_HOUR_MASK		(0x1f)
728c2ecf20Sopenharmony_ci#define LS1X_DAY_MASK		(0x1f)
738c2ecf20Sopenharmony_ci#define LS1X_MONTH_MASK		(0x3f)
748c2ecf20Sopenharmony_ci#define LS1X_YEAR_MASK		(0xffffffff)
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci#define ls1x_get_sec(t)		(((t) >> LS1X_SEC_OFFSET) & LS1X_SEC_MASK)
778c2ecf20Sopenharmony_ci#define ls1x_get_min(t)		(((t) >> LS1X_MIN_OFFSET) & LS1X_MIN_MASK)
788c2ecf20Sopenharmony_ci#define ls1x_get_hour(t)	(((t) >> LS1X_HOUR_OFFSET) & LS1X_HOUR_MASK)
798c2ecf20Sopenharmony_ci#define ls1x_get_day(t)		(((t) >> LS1X_DAY_OFFSET) & LS1X_DAY_MASK)
808c2ecf20Sopenharmony_ci#define ls1x_get_month(t)	(((t) >> LS1X_MONTH_OFFSET) & LS1X_MONTH_MASK)
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci#define RTC_CNTR_OK (SYS_CNTRL_E0 | SYS_CNTRL_32S)
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic int ls1x_rtc_read_time(struct device *dev, struct rtc_time *rtm)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	unsigned long v;
878c2ecf20Sopenharmony_ci	time64_t t;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	v = readl(SYS_TOYREAD0);
908c2ecf20Sopenharmony_ci	t = readl(SYS_TOYREAD1);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	memset(rtm, 0, sizeof(struct rtc_time));
938c2ecf20Sopenharmony_ci	t  = mktime64((t & LS1X_YEAR_MASK), ls1x_get_month(v),
948c2ecf20Sopenharmony_ci			ls1x_get_day(v), ls1x_get_hour(v),
958c2ecf20Sopenharmony_ci			ls1x_get_min(v), ls1x_get_sec(v));
968c2ecf20Sopenharmony_ci	rtc_time64_to_tm(t, rtm);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return 0;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int ls1x_rtc_set_time(struct device *dev, struct  rtc_time *rtm)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	unsigned long v, t, c;
1048c2ecf20Sopenharmony_ci	int ret = -ETIMEDOUT;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	v = ((rtm->tm_mon + 1)  << LS1X_MONTH_OFFSET)
1078c2ecf20Sopenharmony_ci		| (rtm->tm_mday << LS1X_DAY_OFFSET)
1088c2ecf20Sopenharmony_ci		| (rtm->tm_hour << LS1X_HOUR_OFFSET)
1098c2ecf20Sopenharmony_ci		| (rtm->tm_min  << LS1X_MIN_OFFSET)
1108c2ecf20Sopenharmony_ci		| (rtm->tm_sec  << LS1X_SEC_OFFSET);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	writel(v, SYS_TOYWRITE0);
1138c2ecf20Sopenharmony_ci	c = 0x10000;
1148c2ecf20Sopenharmony_ci	/* add timeout check counter, for more safe */
1158c2ecf20Sopenharmony_ci	while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TS) && --c)
1168c2ecf20Sopenharmony_ci		usleep_range(1000, 3000);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (!c) {
1198c2ecf20Sopenharmony_ci		dev_err(dev, "set time timeout!\n");
1208c2ecf20Sopenharmony_ci		goto err;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	t = rtm->tm_year + 1900;
1248c2ecf20Sopenharmony_ci	writel(t, SYS_TOYWRITE1);
1258c2ecf20Sopenharmony_ci	c = 0x10000;
1268c2ecf20Sopenharmony_ci	while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TS) && --c)
1278c2ecf20Sopenharmony_ci		usleep_range(1000, 3000);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	if (!c) {
1308c2ecf20Sopenharmony_ci		dev_err(dev, "set time timeout!\n");
1318c2ecf20Sopenharmony_ci		goto err;
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_cierr:
1358c2ecf20Sopenharmony_ci	return ret;
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic const struct rtc_class_ops  ls1x_rtc_ops = {
1398c2ecf20Sopenharmony_ci	.read_time	= ls1x_rtc_read_time,
1408c2ecf20Sopenharmony_ci	.set_time	= ls1x_rtc_set_time,
1418c2ecf20Sopenharmony_ci};
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int ls1x_rtc_probe(struct platform_device *pdev)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	struct rtc_device *rtcdev;
1468c2ecf20Sopenharmony_ci	unsigned long v;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	v = readl(SYS_COUNTER_CNTRL);
1498c2ecf20Sopenharmony_ci	if (!(v & RTC_CNTR_OK)) {
1508c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "rtc counters not working\n");
1518c2ecf20Sopenharmony_ci		return -ENODEV;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/* set to 1 HZ if needed */
1558c2ecf20Sopenharmony_ci	if (readl(SYS_TOYTRIM) != 32767) {
1568c2ecf20Sopenharmony_ci		v = 0x100000;
1578c2ecf20Sopenharmony_ci		while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS) && --v)
1588c2ecf20Sopenharmony_ci			usleep_range(1000, 3000);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		if (!v) {
1618c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "time out\n");
1628c2ecf20Sopenharmony_ci			return -ETIMEDOUT;
1638c2ecf20Sopenharmony_ci		}
1648c2ecf20Sopenharmony_ci		writel(32767, SYS_TOYTRIM);
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci	/* this loop coundn't be endless */
1678c2ecf20Sopenharmony_ci	while (readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS)
1688c2ecf20Sopenharmony_ci		usleep_range(1000, 3000);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	rtcdev = devm_rtc_allocate_device(&pdev->dev);
1718c2ecf20Sopenharmony_ci	if (IS_ERR(rtcdev))
1728c2ecf20Sopenharmony_ci		return PTR_ERR(rtcdev);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, rtcdev);
1758c2ecf20Sopenharmony_ci	rtcdev->ops = &ls1x_rtc_ops;
1768c2ecf20Sopenharmony_ci	rtcdev->range_min = RTC_TIMESTAMP_BEGIN_1900;
1778c2ecf20Sopenharmony_ci	rtcdev->range_max = RTC_TIMESTAMP_END_2099;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return rtc_register_device(rtcdev);
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic struct platform_driver  ls1x_rtc_driver = {
1838c2ecf20Sopenharmony_ci	.driver		= {
1848c2ecf20Sopenharmony_ci		.name	= "ls1x-rtc",
1858c2ecf20Sopenharmony_ci	},
1868c2ecf20Sopenharmony_ci	.probe		= ls1x_rtc_probe,
1878c2ecf20Sopenharmony_ci};
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cimodule_platform_driver(ls1x_rtc_driver);
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ciMODULE_AUTHOR("zhao zhang <zhzhl555@gmail.com>");
1928c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
193