aboutsummaryrefslogtreecommitdiffstats
blob: 6b97631f548932158e7e84b29fdc5cc63f5e2049 (plain) (blame)
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
/*
 * Copyright (C) 2015 Alban Bedel <albeu@free.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reset-controller.h>
#include <linux/reboot.h>

struct ath79_reset {
	struct reset_controller_dev rcdev;
	struct notifier_block restart_nb;
	void __iomem *base;
	spinlock_t lock;
};

#define FULL_CHIP_RESET 24

static int ath79_reset_update(struct reset_controller_dev *rcdev,
			unsigned long id, bool assert)
{
	struct ath79_reset *ath79_reset =
		container_of(rcdev, struct ath79_reset, rcdev);
	unsigned long flags;
	u32 val;

	spin_lock_irqsave(&ath79_reset->lock, flags);
	val = readl(ath79_reset->base);
	if (assert)
		val |= BIT(id);
	else
		val &= ~BIT(id);
	writel(val, ath79_reset->base);
	spin_unlock_irqrestore(&ath79_reset->lock, flags);

	return 0;
}

static int ath79_reset_assert(struct reset_controller_dev *rcdev,
			unsigned long id)
{
	return ath79_reset_update(rcdev, id, true);
}

static int ath79_reset_deassert(struct reset_controller_dev *rcdev,
				unsigned long id)
{
	return ath79_reset_update(rcdev, id, false);
}

static int ath79_reset_status(struct reset_controller_dev *rcdev,
			unsigned long id)
{
	struct ath79_reset *ath79_reset =
		container_of(rcdev, struct ath79_reset, rcdev);
	u32 val;

	val = readl(ath79_reset->base);

	return !!(val & BIT(id));
}

static const struct reset_control_ops ath79_reset_ops = {
	.assert = ath79_reset_assert,
	.deassert = ath79_reset_deassert,
	.status = ath79_reset_status,
};

static int ath79_reset_restart_handler(struct notifier_block *nb,
				unsigned long action, void *data)
{
	struct ath79_reset *ath79_reset =
		container_of(nb, struct ath79_reset, restart_nb);

	ath79_reset_assert(&ath79_reset->rcdev, FULL_CHIP_RESET);

	return NOTIFY_DONE;
}

static int ath79_reset_probe(struct platform_device *pdev)
{
	struct ath79_reset *ath79_reset;
	struct resource *res;
	int err;

	ath79_reset = devm_kzalloc(&pdev->dev,
				sizeof(*ath79_reset), GFP_KERNEL);
	if (!ath79_reset)
		return -ENOMEM;

	platform_set_drvdata(pdev, ath79_reset);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	ath79_reset->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(ath79_reset->base))
		return PTR_ERR(ath79_reset->base);

	spin_lock_init(&ath79_reset->lock);
	ath79_reset->rcdev.ops = &ath79_reset_ops;
	ath79_reset->rcdev.owner = THIS_MODULE;
	ath79_reset->rcdev.of_node = pdev->dev.of_node;
	ath79_reset->rcdev.of_reset_n_cells = 1;
	ath79_reset->rcdev.nr_resets = 32;

	err = devm_reset_controller_register(&pdev->dev, &ath79_reset->rcdev);
	if (err)
		return err;

	ath79_reset->restart_nb.notifier_call = ath79_reset_restart_handler;
	ath79_reset->restart_nb.priority = 128;

	err = register_restart_handler(&ath79_reset->restart_nb);
	if (err)
		dev_warn(&pdev->dev, "Failed to register restart handler\n");

	return 0;
}

static int ath79_reset_remove(struct platform_device *pdev)
{
	struct ath79_reset *ath79_reset = platform_get_drvdata(pdev);

	unregister_restart_handler(&ath79_reset->restart_nb);

	return 0;
}

static const struct of_device_id ath79_reset_dt_ids[] = {
	{ .compatible = "qca,ar7100-reset", },
	{ },
};
MODULE_DEVICE_TABLE(of, ath79_reset_dt_ids);

static struct platform_driver ath79_reset_driver = {
	.probe	= ath79_reset_probe,
	.remove = ath79_reset_remove,
	.driver = {
		.name		= "ath79-reset",
		.of_match_table	= ath79_reset_dt_ids,
	},
};
module_platform_driver(ath79_reset_driver);

MODULE_AUTHOR("Alban Bedel <albeu@free.fr>");
MODULE_DESCRIPTION("AR71xx Reset Controller Driver");
MODULE_LICENSE("GPL");