summaryrefslogtreecommitdiffstats
path: root/arch/m68k/kernel/time.c
blob: a97600b2af5021df6243c6e609b8aa8a0c2ba4e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  linux/arch/m68k/kernel/time.c
 *
 *  Copyright (C) 1991, 1992, 1995  Linus Torvalds
 *
 * This file contains the m68k-specific time handling details.
 * Most of the stuff is located in the machine specific files.
 *
 * 1997-09-10	Updated NTP code according to technical memorandum Jan '96
 *		"A Kernel Model for Precision Timekeeping" by Dave Mills
 */

#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sched/loadavg.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>

#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/irq_regs.h>

#include <linux/time.h>
#include <linux/timex.h>
#include <linux/profile.h>


unsigned long (*mach_random_get_entropy)(void);
EXPORT_SYMBOL_GPL(mach_random_get_entropy);

#ifdef CONFIG_HEARTBEAT
void timer_heartbeat(void)
{
	/* use power LED as a heartbeat instead -- much more useful
	   for debugging -- based on the version for PReP by Cort */
	/* acts like an actual heart beat -- ie thump-thump-pause... */
	if (mach_heartbeat) {
	    static unsigned cnt = 0, period = 0, dist = 0;

	    if (cnt == 0 || cnt == dist)
		mach_heartbeat( 1 );
	    else if (cnt == 7 || cnt == dist+7)
		mach_heartbeat( 0 );

	    if (++cnt > period) {
		cnt = 0;
		/* The hyperbolic function below modifies the heartbeat period
		 * length in dependency of the current (5min) load. It goes
		 * through the points f(0)=126, f(1)=86, f(5)=51,
		 * f(inf)->30. */
		period = ((672<<FSHIFT)/(5*avenrun[0]+(7<<FSHIFT))) + 30;
		dist = period / 4;
	    }
	}
}
#endif /* CONFIG_HEARTBEAT */

#ifdef CONFIG_M68KCLASSIC
/* machine dependent timer functions */
int (*mach_hwclk) (int, struct rtc_time*);
EXPORT_SYMBOL(mach_hwclk);

int (*mach_get_rtc_pll)(struct rtc_pll_info *);
int (*mach_set_rtc_pll)(struct rtc_pll_info *);
EXPORT_SYMBOL(mach_get_rtc_pll);
EXPORT_SYMBOL(mach_set_rtc_pll);

#if !IS_BUILTIN(CONFIG_RTC_DRV_GENERIC)
void read_persistent_clock64(struct timespec64 *ts)
{
	struct rtc_time time;

	ts->tv_sec = 0;
	ts->tv_nsec = 0;

	if (!mach_hwclk)
		return;

	mach_hwclk(0, &time);

	ts->tv_sec = mktime64(time.tm_year + 1900, time.tm_mon + 1, time.tm_mday,
			      time.tm_hour, time.tm_min, time.tm_sec);
}
#endif

#if IS_ENABLED(CONFIG_RTC_DRV_GENERIC)
static int rtc_generic_get_time(struct device *dev, struct rtc_time *tm)
{
	mach_hwclk(0, tm);
	return 0;
}

static int rtc_generic_set_time(struct device *dev, struct rtc_time *tm)
{
	if (mach_hwclk(1, tm) < 0)
		return -EOPNOTSUPP;
	return 0;
}

static int rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{
	struct rtc_pll_info pll;
	struct rtc_pll_info __user *argp = (void __user *)arg;

	switch (cmd) {
	case RTC_PLL_GET:
		if (!mach_get_rtc_pll || mach_get_rtc_pll(&pll))
			return -EINVAL;
		return copy_to_user(argp, &pll, sizeof pll) ? -EFAULT : 0;

	case RTC_PLL_SET:
		if (!mach_set_rtc_pll)
			return -EINVAL;
		if (!capable(CAP_SYS_TIME))
			return -EACCES;
		if (copy_from_user(&pll, argp, sizeof(pll)))
			return -EFAULT;
		return mach_set_rtc_pll(&pll);
	}

	return -ENOIOCTLCMD;
}

static const struct rtc_class_ops generic_rtc_ops = {
	.ioctl = rtc_ioctl,
	.read_time = rtc_generic_get_time,
	.set_time = rtc_generic_set_time,
};

static int __init rtc_init(void)
{
	struct platform_device *pdev;

	if (!mach_hwclk)
		return -ENODEV;

	pdev = platform_device_register_data(NULL, "rtc-generic", -1,
					     &generic_rtc_ops,
					     sizeof(generic_rtc_ops));
	return PTR_ERR_OR_ZERO(pdev);
}

module_init(rtc_init);
#endif /* CONFIG_RTC_DRV_GENERIC */
#endif /* CONFIG M68KCLASSIC */

void __init time_init(void)
{
	mach_sched_init();
}