Commit | Line | Data |
---|---|---|
453ed428 SH |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Microchip Sparx5 Switch Reset driver | |
3 | * | |
4 | * Copyright (c) 2020 Microchip Technology Inc. and its subsidiaries. | |
5 | * | |
6 | * The Sparx5 Chip Register Model can be browsed at this location: | |
7 | * https://github.com/microchip-ung/sparx-5_reginfo | |
8 | */ | |
9 | #include <linux/mfd/syscon.h> | |
bad8a8af | 10 | #include <linux/of.h> |
0e2268f8 | 11 | #include <linux/of_address.h> |
453ed428 SH |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> | |
bad8a8af | 14 | #include <linux/property.h> |
453ed428 SH |
15 | #include <linux/regmap.h> |
16 | #include <linux/reset-controller.h> | |
17 | ||
8c81620a HV |
18 | struct reset_props { |
19 | u32 protect_reg; | |
20 | u32 protect_bit; | |
21 | u32 reset_reg; | |
22 | u32 reset_bit; | |
23 | }; | |
453ed428 SH |
24 | |
25 | struct mchp_reset_context { | |
26 | struct regmap *cpu_ctrl; | |
27 | struct regmap *gcb_ctrl; | |
28 | struct reset_controller_dev rcdev; | |
8c81620a | 29 | const struct reset_props *props; |
453ed428 SH |
30 | }; |
31 | ||
32 | static struct regmap_config sparx5_reset_regmap_config = { | |
33 | .reg_bits = 32, | |
34 | .val_bits = 32, | |
35 | .reg_stride = 4, | |
36 | }; | |
37 | ||
51fd1914 | 38 | static int sparx5_switch_reset(struct mchp_reset_context *ctx) |
453ed428 | 39 | { |
453ed428 SH |
40 | u32 val; |
41 | ||
42 | /* Make sure the core is PROTECTED from reset */ | |
8c81620a HV |
43 | regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg, |
44 | ctx->props->protect_bit, ctx->props->protect_bit); | |
453ed428 SH |
45 | |
46 | /* Start soft reset */ | |
8c81620a HV |
47 | regmap_write(ctx->gcb_ctrl, ctx->props->reset_reg, |
48 | ctx->props->reset_bit); | |
453ed428 SH |
49 | |
50 | /* Wait for soft reset done */ | |
8c81620a HV |
51 | return regmap_read_poll_timeout(ctx->gcb_ctrl, ctx->props->reset_reg, val, |
52 | (val & ctx->props->reset_bit) == 0, | |
453ed428 SH |
53 | 1, 100); |
54 | } | |
55 | ||
51fd1914 MW |
56 | static int sparx5_reset_noop(struct reset_controller_dev *rcdev, |
57 | unsigned long id) | |
58 | { | |
59 | return 0; | |
60 | } | |
61 | ||
453ed428 | 62 | static const struct reset_control_ops sparx5_reset_ops = { |
51fd1914 | 63 | .reset = sparx5_reset_noop, |
453ed428 SH |
64 | }; |
65 | ||
0426a920 HC |
66 | static const struct regmap_config mchp_lan966x_syscon_regmap_config = { |
67 | .reg_bits = 32, | |
68 | .val_bits = 32, | |
69 | .reg_stride = 4, | |
70 | }; | |
71 | ||
72 | static struct regmap *mchp_lan966x_syscon_to_regmap(struct device *dev, | |
73 | struct device_node *syscon_np) | |
74 | { | |
75 | struct regmap_config regmap_config = mchp_lan966x_syscon_regmap_config; | |
0e2268f8 | 76 | struct resource res; |
0426a920 | 77 | void __iomem *base; |
0e2268f8 HV |
78 | int err; |
79 | ||
80 | err = of_address_to_resource(syscon_np, 0, &res); | |
81 | if (err) | |
82 | return ERR_PTR(err); | |
0426a920 | 83 | |
0e2268f8 HV |
84 | /* It is not possible to use devm_of_iomap because this resource is |
85 | * shared with other drivers. | |
86 | */ | |
87 | base = devm_ioremap(dev, res.start, resource_size(&res)); | |
88 | if (!base) | |
89 | return ERR_PTR(-ENOMEM); | |
0426a920 | 90 | |
0e2268f8 | 91 | regmap_config.max_register = resource_size(&res) - 4; |
0426a920 HC |
92 | |
93 | return devm_regmap_init_mmio(dev, base, ®map_config); | |
94 | } | |
95 | ||
453ed428 SH |
96 | static int mchp_sparx5_map_syscon(struct platform_device *pdev, char *name, |
97 | struct regmap **target) | |
98 | { | |
99 | struct device_node *syscon_np; | |
100 | struct regmap *regmap; | |
101 | int err; | |
102 | ||
103 | syscon_np = of_parse_phandle(pdev->dev.of_node, name, 0); | |
104 | if (!syscon_np) | |
105 | return -ENODEV; | |
0426a920 HC |
106 | |
107 | /* | |
108 | * The syscon API doesn't support syscon device removal. | |
109 | * When used in LAN966x PCI device, the cpu-syscon device needs to be | |
110 | * removed when the PCI device is removed. | |
111 | * In case of LAN966x, map the syscon device locally to support the | |
112 | * device removal. | |
113 | */ | |
114 | if (of_device_is_compatible(pdev->dev.of_node, "microchip,lan966x-switch-reset")) | |
115 | regmap = mchp_lan966x_syscon_to_regmap(&pdev->dev, syscon_np); | |
116 | else | |
117 | regmap = syscon_node_to_regmap(syscon_np); | |
453ed428 SH |
118 | of_node_put(syscon_np); |
119 | if (IS_ERR(regmap)) { | |
120 | err = PTR_ERR(regmap); | |
121 | dev_err(&pdev->dev, "No '%s' map: %d\n", name, err); | |
122 | return err; | |
123 | } | |
124 | *target = regmap; | |
125 | return 0; | |
126 | } | |
127 | ||
128 | static int mchp_sparx5_map_io(struct platform_device *pdev, int index, | |
129 | struct regmap **target) | |
130 | { | |
131 | struct resource *res; | |
132 | struct regmap *map; | |
133 | void __iomem *mem; | |
134 | ||
135 | mem = devm_platform_get_and_ioremap_resource(pdev, index, &res); | |
91105ed6 | 136 | if (IS_ERR(mem)) { |
453ed428 | 137 | dev_err(&pdev->dev, "Could not map resource %d\n", index); |
91105ed6 | 138 | return PTR_ERR(mem); |
453ed428 SH |
139 | } |
140 | sparx5_reset_regmap_config.name = res->name; | |
141 | map = devm_regmap_init_mmio(&pdev->dev, mem, &sparx5_reset_regmap_config); | |
142 | if (IS_ERR(map)) | |
143 | return PTR_ERR(map); | |
144 | *target = map; | |
145 | return 0; | |
146 | } | |
147 | ||
148 | static int mchp_sparx5_reset_probe(struct platform_device *pdev) | |
149 | { | |
150 | struct device_node *dn = pdev->dev.of_node; | |
151 | struct mchp_reset_context *ctx; | |
152 | int err; | |
153 | ||
154 | ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); | |
155 | if (!ctx) | |
156 | return -ENOMEM; | |
157 | ||
158 | err = mchp_sparx5_map_syscon(pdev, "cpu-syscon", &ctx->cpu_ctrl); | |
159 | if (err) | |
160 | return err; | |
161 | err = mchp_sparx5_map_io(pdev, 0, &ctx->gcb_ctrl); | |
162 | if (err) | |
163 | return err; | |
164 | ||
165 | ctx->rcdev.owner = THIS_MODULE; | |
37b395c2 | 166 | ctx->rcdev.dev = &pdev->dev; |
453ed428 SH |
167 | ctx->rcdev.nr_resets = 1; |
168 | ctx->rcdev.ops = &sparx5_reset_ops; | |
169 | ctx->rcdev.of_node = dn; | |
8c81620a | 170 | ctx->props = device_get_match_data(&pdev->dev); |
453ed428 | 171 | |
51fd1914 MW |
172 | /* Issue the reset very early, our actual reset callback is a noop. */ |
173 | err = sparx5_switch_reset(ctx); | |
174 | if (err) | |
175 | return err; | |
176 | ||
453ed428 SH |
177 | return devm_reset_controller_register(&pdev->dev, &ctx->rcdev); |
178 | } | |
179 | ||
8c81620a HV |
180 | static const struct reset_props reset_props_sparx5 = { |
181 | .protect_reg = 0x84, | |
182 | .protect_bit = BIT(10), | |
183 | .reset_reg = 0x0, | |
184 | .reset_bit = BIT(1), | |
185 | }; | |
186 | ||
187 | static const struct reset_props reset_props_lan966x = { | |
188 | .protect_reg = 0x88, | |
189 | .protect_bit = BIT(5), | |
190 | .reset_reg = 0x0, | |
191 | .reset_bit = BIT(1), | |
192 | }; | |
193 | ||
453ed428 SH |
194 | static const struct of_device_id mchp_sparx5_reset_of_match[] = { |
195 | { | |
196 | .compatible = "microchip,sparx5-switch-reset", | |
8c81620a HV |
197 | .data = &reset_props_sparx5, |
198 | }, { | |
199 | .compatible = "microchip,lan966x-switch-reset", | |
200 | .data = &reset_props_lan966x, | |
453ed428 SH |
201 | }, |
202 | { } | |
203 | }; | |
996737ef | 204 | MODULE_DEVICE_TABLE(of, mchp_sparx5_reset_of_match); |
453ed428 SH |
205 | |
206 | static struct platform_driver mchp_sparx5_reset_driver = { | |
207 | .probe = mchp_sparx5_reset_probe, | |
208 | .driver = { | |
209 | .name = "sparx5-switch-reset", | |
210 | .of_match_table = mchp_sparx5_reset_of_match, | |
211 | }, | |
212 | }; | |
213 | ||
214 | static int __init mchp_sparx5_reset_init(void) | |
215 | { | |
216 | return platform_driver_register(&mchp_sparx5_reset_driver); | |
217 | } | |
218 | ||
51fd1914 MW |
219 | /* |
220 | * Because this is a global reset, keep this postcore_initcall() to issue the | |
221 | * reset as early as possible during the kernel startup. | |
222 | */ | |
453ed428 SH |
223 | postcore_initcall(mchp_sparx5_reset_init); |
224 | ||
225 | MODULE_DESCRIPTION("Microchip Sparx5 switch reset driver"); | |
226 | MODULE_AUTHOR("Steen Hegelund <steen.hegelund@microchip.com>"); | |
996737ef | 227 | MODULE_LICENSE("GPL"); |