summaryrefslogtreecommitdiff
path: root/drivers/watchdog/mt7620_wdt.c
blob: fe89721ddb90997c8c5f100dbf24b6b2d408de84 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 MediaTek Inc.
 *
 * Author:  Weijie Gao <weijie.gao@mediatek.com>
 *
 * Watchdog timer for MT7620 and earlier SoCs
 */

#include <div64.h>
#include <dm.h>
#include <reset.h>
#include <wdt.h>
#include <linux/bitops.h>
#include <linux/io.h>

struct mt7620_wdt {
	void __iomem *regs;
	u64 timeout;
};

#define TIMER_FREQ			40000000
#define TIMER_MASK			0xffff
#define TIMER_PRESCALE			65536

#define TIMER_LOAD			0x00
#define TIMER_CTL			0x08

#define TIMER_ENABLE			BIT(7)
#define TIMER_MODE_SHIFT		4
#define   TIMER_MODE_WDT		3
#define TIMER_PRESCALE_SHIFT		0
#define   TIMER_PRESCALE_65536		15

static void mt7620_wdt_ping(struct mt7620_wdt *priv)
{
	u64 val;

	val = (TIMER_FREQ / TIMER_PRESCALE) * priv->timeout;
	do_div(val, 1000);

	if (val > TIMER_MASK)
		val = TIMER_MASK;

	writel(val, priv->regs + TIMER_LOAD);
}

static int mt7620_wdt_start(struct udevice *dev, u64 ms, ulong flags)
{
	struct mt7620_wdt *priv = dev_get_priv(dev);

	priv->timeout = ms;
	mt7620_wdt_ping(priv);

	writel(TIMER_ENABLE | (TIMER_MODE_WDT << TIMER_MODE_SHIFT) |
	       (TIMER_PRESCALE_65536 << TIMER_PRESCALE_SHIFT),
	       priv->regs + TIMER_CTL);

	return 0;
}

static int mt7620_wdt_stop(struct udevice *dev)
{
	struct mt7620_wdt *priv = dev_get_priv(dev);

	mt7620_wdt_ping(priv);

	clrbits_32(priv->regs + TIMER_CTL, TIMER_ENABLE);

	return 0;
}

static int mt7620_wdt_reset(struct udevice *dev)
{
	struct mt7620_wdt *priv = dev_get_priv(dev);

	mt7620_wdt_ping(priv);

	return 0;
}

static int mt7620_wdt_expire_now(struct udevice *dev, ulong flags)
{
	struct mt7620_wdt *priv = dev_get_priv(dev);

	mt7620_wdt_start(dev, 1, flags);

	/*
	 * 0 will disable the timer directly, a positive number must be used
	 * instead. Since the timer is a countdown timer, 1 (tick) is used.
	 *
	 * For a timer with input clock = 40MHz, 1 timer tick is short
	 * enough to trigger a timeout immediately.
	 *
	 * Restore prescale to 1, and load timer with 1 to trigger timeout.
	 */
	writel(TIMER_ENABLE | (TIMER_MODE_WDT << TIMER_MODE_SHIFT),
	       priv->regs + TIMER_CTL);
	writel(1, priv->regs + TIMER_LOAD);

	return 0;
}

static int mt7620_wdt_probe(struct udevice *dev)
{
	struct mt7620_wdt *priv = dev_get_priv(dev);
	struct reset_ctl reset_wdt;
	int ret;

	ret = reset_get_by_index(dev, 0, &reset_wdt);
	if (!ret)
		reset_deassert(&reset_wdt);

	priv->regs = dev_remap_addr(dev);
	if (!priv->regs)
		return -EINVAL;

	mt7620_wdt_stop(dev);

	return 0;
}

static const struct wdt_ops mt7620_wdt_ops = {
	.start = mt7620_wdt_start,
	.reset = mt7620_wdt_reset,
	.stop = mt7620_wdt_stop,
	.expire_now = mt7620_wdt_expire_now,
};

static const struct udevice_id mt7620_wdt_ids[] = {
	{ .compatible = "mediatek,mt7620-wdt" },
	{}
};

U_BOOT_DRIVER(mt7620_wdt) = {
	.name = "mt7620_wdt",
	.id = UCLASS_WDT,
	.of_match = mt7620_wdt_ids,
	.probe = mt7620_wdt_probe,
	.priv_auto = sizeof(struct mt7620_wdt),
	.ops = &mt7620_wdt_ops,
};