1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* |
3 | * SP7021 reset driver |
4 | * |
5 | * Copyright (C) Sunplus Technology Co., Ltd. |
6 | * All rights reserved. |
7 | */ |
8 | |
9 | #include <linux/io.h> |
10 | #include <linux/init.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/reset-controller.h> |
14 | #include <linux/reboot.h> |
15 | |
16 | /* HIWORD_MASK_REG BITS */ |
17 | #define BITS_PER_HWM_REG 16 |
18 | |
19 | /* resets HW info: reg_index_shift */ |
20 | static const u32 sp_resets[] = { |
21 | /* SP7021: mo_reset0 ~ mo_reset9 */ |
22 | 0x00, |
23 | 0x02, |
24 | 0x03, |
25 | 0x04, |
26 | 0x05, |
27 | 0x06, |
28 | 0x07, |
29 | 0x08, |
30 | 0x09, |
31 | 0x0a, |
32 | 0x0b, |
33 | 0x0d, |
34 | 0x0e, |
35 | 0x0f, |
36 | 0x10, |
37 | 0x12, |
38 | 0x14, |
39 | 0x15, |
40 | 0x16, |
41 | 0x17, |
42 | 0x18, |
43 | 0x19, |
44 | 0x1a, |
45 | 0x1b, |
46 | 0x1c, |
47 | 0x1d, |
48 | 0x1e, |
49 | 0x1f, |
50 | 0x20, |
51 | 0x21, |
52 | 0x22, |
53 | 0x23, |
54 | 0x24, |
55 | 0x25, |
56 | 0x26, |
57 | 0x2a, |
58 | 0x2b, |
59 | 0x2d, |
60 | 0x2e, |
61 | 0x30, |
62 | 0x31, |
63 | 0x32, |
64 | 0x33, |
65 | 0x3d, |
66 | 0x3e, |
67 | 0x3f, |
68 | 0x42, |
69 | 0x44, |
70 | 0x4b, |
71 | 0x4c, |
72 | 0x4d, |
73 | 0x4e, |
74 | 0x4f, |
75 | 0x50, |
76 | 0x55, |
77 | 0x60, |
78 | 0x61, |
79 | 0x6a, |
80 | 0x6f, |
81 | 0x70, |
82 | 0x73, |
83 | 0x74, |
84 | 0x86, |
85 | 0x8a, |
86 | 0x8b, |
87 | 0x8d, |
88 | 0x8e, |
89 | 0x8f, |
90 | 0x90, |
91 | 0x92, |
92 | 0x93, |
93 | 0x94, |
94 | 0x95, |
95 | 0x96, |
96 | 0x97, |
97 | 0x98, |
98 | 0x99, |
99 | }; |
100 | |
101 | struct sp_reset { |
102 | struct reset_controller_dev rcdev; |
103 | struct notifier_block notifier; |
104 | void __iomem *base; |
105 | }; |
106 | |
107 | static inline struct sp_reset *to_sp_reset(struct reset_controller_dev *rcdev) |
108 | { |
109 | return container_of(rcdev, struct sp_reset, rcdev); |
110 | } |
111 | |
112 | static int sp_reset_update(struct reset_controller_dev *rcdev, |
113 | unsigned long id, bool assert) |
114 | { |
115 | struct sp_reset *reset = to_sp_reset(rcdev); |
116 | int index = sp_resets[id] / BITS_PER_HWM_REG; |
117 | int shift = sp_resets[id] % BITS_PER_HWM_REG; |
118 | u32 val; |
119 | |
120 | val = (1 << (16 + shift)) | (assert << shift); |
121 | writel(val, addr: reset->base + (index * 4)); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static int sp_reset_assert(struct reset_controller_dev *rcdev, |
127 | unsigned long id) |
128 | { |
129 | return sp_reset_update(rcdev, id, assert: true); |
130 | } |
131 | |
132 | static int sp_reset_deassert(struct reset_controller_dev *rcdev, |
133 | unsigned long id) |
134 | { |
135 | return sp_reset_update(rcdev, id, assert: false); |
136 | } |
137 | |
138 | static int sp_reset_status(struct reset_controller_dev *rcdev, |
139 | unsigned long id) |
140 | { |
141 | struct sp_reset *reset = to_sp_reset(rcdev); |
142 | int index = sp_resets[id] / BITS_PER_HWM_REG; |
143 | int shift = sp_resets[id] % BITS_PER_HWM_REG; |
144 | u32 reg; |
145 | |
146 | reg = readl(addr: reset->base + (index * 4)); |
147 | |
148 | return !!(reg & BIT(shift)); |
149 | } |
150 | |
151 | static const struct reset_control_ops sp_reset_ops = { |
152 | .assert = sp_reset_assert, |
153 | .deassert = sp_reset_deassert, |
154 | .status = sp_reset_status, |
155 | }; |
156 | |
157 | static int sp_restart(struct notifier_block *nb, unsigned long mode, |
158 | void *cmd) |
159 | { |
160 | struct sp_reset *reset = container_of(nb, struct sp_reset, notifier); |
161 | |
162 | sp_reset_assert(rcdev: &reset->rcdev, id: 0); |
163 | sp_reset_deassert(rcdev: &reset->rcdev, id: 0); |
164 | |
165 | return NOTIFY_DONE; |
166 | } |
167 | |
168 | static int sp_reset_probe(struct platform_device *pdev) |
169 | { |
170 | struct device *dev = &pdev->dev; |
171 | struct sp_reset *reset; |
172 | struct resource *res; |
173 | int ret; |
174 | |
175 | reset = devm_kzalloc(dev, size: sizeof(*reset), GFP_KERNEL); |
176 | if (!reset) |
177 | return -ENOMEM; |
178 | |
179 | reset->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
180 | if (IS_ERR(ptr: reset->base)) |
181 | return PTR_ERR(ptr: reset->base); |
182 | |
183 | reset->rcdev.ops = &sp_reset_ops; |
184 | reset->rcdev.owner = THIS_MODULE; |
185 | reset->rcdev.of_node = dev->of_node; |
186 | reset->rcdev.nr_resets = resource_size(res) / 4 * BITS_PER_HWM_REG; |
187 | |
188 | ret = devm_reset_controller_register(dev, rcdev: &reset->rcdev); |
189 | if (ret) |
190 | return ret; |
191 | |
192 | reset->notifier.notifier_call = sp_restart; |
193 | reset->notifier.priority = 192; |
194 | |
195 | return register_restart_handler(&reset->notifier); |
196 | } |
197 | |
198 | static const struct of_device_id sp_reset_dt_ids[] = { |
199 | {.compatible = "sunplus,sp7021-reset" ,}, |
200 | { /* sentinel */ }, |
201 | }; |
202 | |
203 | static struct platform_driver sp_reset_driver = { |
204 | .probe = sp_reset_probe, |
205 | .driver = { |
206 | .name = "sunplus-reset" , |
207 | .of_match_table = sp_reset_dt_ids, |
208 | .suppress_bind_attrs = true, |
209 | }, |
210 | }; |
211 | builtin_platform_driver(sp_reset_driver); |
212 | |