1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (c) 2022 BayLibre, SAS.
4 */
5
6#include <clk.h>
7#include <dm.h>
8#include <dm/device_compat.h>
9#include <reset.h>
10#include <wdt.h>
11#include <asm/io.h>
12#include <linux/bitops.h>
13
14#define GXBB_WDT_CTRL_REG			0x0
15#define GXBB_WDT_TCNT_REG			0x8
16#define GXBB_WDT_RSET_REG			0xc
17
18#define GXBB_WDT_CTRL_SYS_RESET_NOW		BIT(26)
19#define GXBB_WDT_CTRL_CLKDIV_EN			BIT(25)
20#define GXBB_WDT_CTRL_CLK_EN			BIT(24)
21#define GXBB_WDT_CTRL_EE_RESET			BIT(21)
22#define GXBB_WDT_CTRL_EN			BIT(18)
23
24#define GXBB_WDT_CTRL_DIV_MASK			GENMASK(17, 0)
25#define GXBB_WDT_TCNT_SETUP_MASK		GENMASK(15, 0)
26
27
28struct amlogic_wdt_priv {
29	void __iomem *reg_base;
30};
31
32static int amlogic_wdt_set_timeout(struct udevice *dev, u64 timeout_ms)
33{
34	struct amlogic_wdt_priv *data = dev_get_priv(dev);
35
36	if (timeout_ms > GXBB_WDT_TCNT_SETUP_MASK) {
37		dev_warn(dev, "%s: timeout_ms=%llu: maximum watchdog timeout exceeded\n",
38		         __func__, timeout_ms);
39		timeout_ms = GXBB_WDT_TCNT_SETUP_MASK;
40	}
41
42	writel(timeout_ms, data->reg_base + GXBB_WDT_TCNT_REG);
43
44	return 0;
45}
46
47static int amlogic_wdt_stop(struct udevice *dev)
48{
49	struct amlogic_wdt_priv *data = dev_get_priv(dev);
50
51	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
52	       data->reg_base + GXBB_WDT_CTRL_REG);
53
54	return 0;
55}
56
57static int amlogic_wdt_start(struct udevice *dev, u64 time_ms, ulong flags)
58{
59	struct amlogic_wdt_priv *data = dev_get_priv(dev);
60
61	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
62	       data->reg_base + GXBB_WDT_CTRL_REG);
63
64	return amlogic_wdt_set_timeout(dev, time_ms);
65}
66
67static int amlogic_wdt_reset(struct udevice *dev)
68{
69	struct amlogic_wdt_priv *data = dev_get_priv(dev);
70
71	writel(0, data->reg_base + GXBB_WDT_RSET_REG);
72
73	return 0;
74}
75
76static int amlogic_wdt_expire_now(struct udevice *dev, ulong flags)
77{
78	struct amlogic_wdt_priv *data = dev_get_priv(dev);
79
80	writel(0, data->reg_base + GXBB_WDT_CTRL_SYS_RESET_NOW);
81
82	return 0;
83}
84
85static int amlogic_wdt_probe(struct udevice *dev)
86{
87	struct amlogic_wdt_priv *data = dev_get_priv(dev);
88	int ret;
89
90	data->reg_base = dev_remap_addr(dev);
91	if (!data->reg_base)
92		return -EINVAL;
93
94	struct clk clk;
95
96	ret = clk_get_by_index(dev, 0, &clk);
97	if (ret)
98		return ret;
99
100	ret = clk_enable(&clk);
101	if (ret)
102		return ret;
103
104	/* Setup with 1ms timebase */
105	writel(((clk_get_rate(&clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
106	       GXBB_WDT_CTRL_EE_RESET |
107	       GXBB_WDT_CTRL_CLK_EN |
108	       GXBB_WDT_CTRL_CLKDIV_EN,
109	       data->reg_base + GXBB_WDT_CTRL_REG);
110
111	return 0;
112}
113
114static const struct wdt_ops amlogic_wdt_ops = {
115	.start = amlogic_wdt_start,
116	.reset = amlogic_wdt_reset,
117	.stop = amlogic_wdt_stop,
118	.expire_now = amlogic_wdt_expire_now,
119};
120
121static const struct udevice_id amlogic_wdt_ids[] = {
122	{ .compatible = "amlogic,meson-gxbb-wdt" },
123	{}
124};
125
126U_BOOT_DRIVER(amlogic_wdt) = {
127	.name = "amlogic_wdt",
128	.id = UCLASS_WDT,
129	.of_match = amlogic_wdt_ids,
130	.priv_auto = sizeof(struct amlogic_wdt_priv),
131	.probe = amlogic_wdt_probe,
132	.ops = &amlogic_wdt_ops,
133	.flags = DM_FLAG_PRE_RELOC,
134};
135