1 | /* |
2 | * EIM driver for Freescale's i.MX chips |
3 | * |
4 | * Copyright (C) 2013 Freescale Semiconductor, Inc. |
5 | * |
6 | * This file is licensed under the terms of the GNU General Public |
7 | * License version 2. This program is licensed "as is" without any |
8 | * warranty of any kind, whether express or implied. |
9 | */ |
10 | #include <linux/module.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/of_device.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | struct imx_weim_devtype { |
20 | unsigned int cs_count; |
21 | unsigned int cs_regs_count; |
22 | unsigned int cs_stride; |
23 | unsigned int wcr_offset; |
24 | unsigned int wcr_bcm; |
25 | unsigned int wcr_cont_bclk; |
26 | }; |
27 | |
28 | static const struct imx_weim_devtype imx1_weim_devtype = { |
29 | .cs_count = 6, |
30 | .cs_regs_count = 2, |
31 | .cs_stride = 0x08, |
32 | }; |
33 | |
34 | static const struct imx_weim_devtype imx27_weim_devtype = { |
35 | .cs_count = 6, |
36 | .cs_regs_count = 3, |
37 | .cs_stride = 0x10, |
38 | }; |
39 | |
40 | static const struct imx_weim_devtype imx50_weim_devtype = { |
41 | .cs_count = 4, |
42 | .cs_regs_count = 6, |
43 | .cs_stride = 0x18, |
44 | .wcr_offset = 0x90, |
45 | .wcr_bcm = BIT(0), |
46 | .wcr_cont_bclk = BIT(3), |
47 | }; |
48 | |
49 | static const struct imx_weim_devtype imx51_weim_devtype = { |
50 | .cs_count = 6, |
51 | .cs_regs_count = 6, |
52 | .cs_stride = 0x18, |
53 | }; |
54 | |
55 | #define MAX_CS_REGS_COUNT 6 |
56 | #define MAX_CS_COUNT 6 |
57 | #define OF_REG_SIZE 3 |
58 | |
59 | struct cs_timing { |
60 | bool is_applied; |
61 | u32 regs[MAX_CS_REGS_COUNT]; |
62 | }; |
63 | |
64 | struct cs_timing_state { |
65 | struct cs_timing cs[MAX_CS_COUNT]; |
66 | }; |
67 | |
68 | struct weim_priv { |
69 | void __iomem *base; |
70 | struct cs_timing_state timing_state; |
71 | }; |
72 | |
73 | static const struct of_device_id weim_id_table[] = { |
74 | /* i.MX1/21 */ |
75 | { .compatible = "fsl,imx1-weim" , .data = &imx1_weim_devtype, }, |
76 | /* i.MX25/27/31/35 */ |
77 | { .compatible = "fsl,imx27-weim" , .data = &imx27_weim_devtype, }, |
78 | /* i.MX50/53/6Q */ |
79 | { .compatible = "fsl,imx50-weim" , .data = &imx50_weim_devtype, }, |
80 | { .compatible = "fsl,imx6q-weim" , .data = &imx50_weim_devtype, }, |
81 | /* i.MX51 */ |
82 | { .compatible = "fsl,imx51-weim" , .data = &imx51_weim_devtype, }, |
83 | { } |
84 | }; |
85 | MODULE_DEVICE_TABLE(of, weim_id_table); |
86 | |
87 | static int imx_weim_gpr_setup(struct platform_device *pdev) |
88 | { |
89 | struct device_node *np = pdev->dev.of_node; |
90 | struct of_range_parser parser; |
91 | struct of_range range; |
92 | struct regmap *gpr; |
93 | u32 gprvals[4] = { |
94 | 05, /* CS0(128M) CS1(0M) CS2(0M) CS3(0M) */ |
95 | 033, /* CS0(64M) CS1(64M) CS2(0M) CS3(0M) */ |
96 | 0113, /* CS0(64M) CS1(32M) CS2(32M) CS3(0M) */ |
97 | 01111, /* CS0(32M) CS1(32M) CS2(32M) CS3(32M) */ |
98 | }; |
99 | u32 gprval = 0; |
100 | u32 val; |
101 | int cs = 0; |
102 | int i = 0; |
103 | |
104 | gpr = syscon_regmap_lookup_by_phandle(np, property: "fsl,weim-cs-gpr" ); |
105 | if (IS_ERR(ptr: gpr)) { |
106 | dev_dbg(&pdev->dev, "failed to find weim-cs-gpr\n" ); |
107 | return 0; |
108 | } |
109 | |
110 | if (of_range_parser_init(parser: &parser, node: np)) |
111 | goto err; |
112 | |
113 | for_each_of_range(&parser, &range) { |
114 | cs = range.bus_addr >> 32; |
115 | val = (range.size / SZ_32M) | 1; |
116 | gprval |= val << cs * 3; |
117 | i++; |
118 | } |
119 | |
120 | if (i == 0 || i % 4) |
121 | goto err; |
122 | |
123 | for (i = 0; i < ARRAY_SIZE(gprvals); i++) { |
124 | if (gprval == gprvals[i]) { |
125 | /* Found it. Set up IOMUXC_GPR1[11:0] with it. */ |
126 | regmap_update_bits(map: gpr, IOMUXC_GPR1, mask: 0xfff, val: gprval); |
127 | return 0; |
128 | } |
129 | } |
130 | |
131 | err: |
132 | dev_err(&pdev->dev, "Invalid 'ranges' configuration\n" ); |
133 | return -EINVAL; |
134 | } |
135 | |
136 | /* Parse and set the timing for this device. */ |
137 | static int weim_timing_setup(struct device *dev, struct device_node *np, |
138 | const struct imx_weim_devtype *devtype) |
139 | { |
140 | u32 cs_idx, value[MAX_CS_REGS_COUNT]; |
141 | int i, ret; |
142 | int reg_idx, num_regs; |
143 | struct cs_timing *cst; |
144 | struct weim_priv *priv; |
145 | struct cs_timing_state *ts; |
146 | void __iomem *base; |
147 | |
148 | if (WARN_ON(devtype->cs_regs_count > MAX_CS_REGS_COUNT)) |
149 | return -EINVAL; |
150 | if (WARN_ON(devtype->cs_count > MAX_CS_COUNT)) |
151 | return -EINVAL; |
152 | |
153 | priv = dev_get_drvdata(dev); |
154 | base = priv->base; |
155 | ts = &priv->timing_state; |
156 | |
157 | ret = of_property_read_u32_array(np, propname: "fsl,weim-cs-timing" , |
158 | out_values: value, sz: devtype->cs_regs_count); |
159 | if (ret) |
160 | return ret; |
161 | |
162 | /* |
163 | * the child node's "reg" property may contain multiple address ranges, |
164 | * extract the chip select for each. |
165 | */ |
166 | num_regs = of_property_count_elems_of_size(np, propname: "reg" , OF_REG_SIZE); |
167 | if (num_regs < 0) |
168 | return num_regs; |
169 | if (!num_regs) |
170 | return -EINVAL; |
171 | for (reg_idx = 0; reg_idx < num_regs; reg_idx++) { |
172 | /* get the CS index from this child node's "reg" property. */ |
173 | ret = of_property_read_u32_index(np, propname: "reg" , |
174 | index: reg_idx * OF_REG_SIZE, out_value: &cs_idx); |
175 | if (ret) |
176 | break; |
177 | |
178 | if (cs_idx >= devtype->cs_count) |
179 | return -EINVAL; |
180 | |
181 | /* prevent re-configuring a CS that's already been configured */ |
182 | cst = &ts->cs[cs_idx]; |
183 | if (cst->is_applied && memcmp(p: value, q: cst->regs, |
184 | size: devtype->cs_regs_count * sizeof(u32))) { |
185 | dev_err(dev, "fsl,weim-cs-timing conflict on %pOF" , np); |
186 | return -EINVAL; |
187 | } |
188 | |
189 | /* set the timing for WEIM */ |
190 | for (i = 0; i < devtype->cs_regs_count; i++) |
191 | writel(val: value[i], |
192 | addr: base + cs_idx * devtype->cs_stride + i * 4); |
193 | if (!cst->is_applied) { |
194 | cst->is_applied = true; |
195 | memcpy(cst->regs, value, |
196 | devtype->cs_regs_count * sizeof(u32)); |
197 | } |
198 | } |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static int weim_parse_dt(struct platform_device *pdev) |
204 | { |
205 | const struct of_device_id *of_id = of_match_device(matches: weim_id_table, |
206 | dev: &pdev->dev); |
207 | const struct imx_weim_devtype *devtype = of_id->data; |
208 | int ret = 0, have_child = 0; |
209 | struct device_node *child; |
210 | struct weim_priv *priv; |
211 | void __iomem *base; |
212 | u32 reg; |
213 | |
214 | if (devtype == &imx50_weim_devtype) { |
215 | ret = imx_weim_gpr_setup(pdev); |
216 | if (ret) |
217 | return ret; |
218 | } |
219 | |
220 | priv = dev_get_drvdata(dev: &pdev->dev); |
221 | base = priv->base; |
222 | |
223 | if (of_property_read_bool(np: pdev->dev.of_node, propname: "fsl,burst-clk-enable" )) { |
224 | if (devtype->wcr_bcm) { |
225 | reg = readl(addr: base + devtype->wcr_offset); |
226 | reg |= devtype->wcr_bcm; |
227 | |
228 | if (of_property_read_bool(np: pdev->dev.of_node, |
229 | propname: "fsl,continuous-burst-clk" )) { |
230 | if (devtype->wcr_cont_bclk) { |
231 | reg |= devtype->wcr_cont_bclk; |
232 | } else { |
233 | dev_err(&pdev->dev, |
234 | "continuous burst clk not supported.\n" ); |
235 | return -EINVAL; |
236 | } |
237 | } |
238 | |
239 | writel(val: reg, addr: base + devtype->wcr_offset); |
240 | } else { |
241 | dev_err(&pdev->dev, "burst clk mode not supported.\n" ); |
242 | return -EINVAL; |
243 | } |
244 | } |
245 | |
246 | for_each_available_child_of_node(pdev->dev.of_node, child) { |
247 | ret = weim_timing_setup(dev: &pdev->dev, np: child, devtype); |
248 | if (ret) |
249 | dev_warn(&pdev->dev, "%pOF set timing failed.\n" , |
250 | child); |
251 | else |
252 | have_child = 1; |
253 | } |
254 | |
255 | if (have_child) |
256 | ret = of_platform_default_populate(root: pdev->dev.of_node, |
257 | NULL, parent: &pdev->dev); |
258 | if (ret) |
259 | dev_err(&pdev->dev, "%pOF fail to create devices.\n" , |
260 | pdev->dev.of_node); |
261 | return ret; |
262 | } |
263 | |
264 | static int weim_probe(struct platform_device *pdev) |
265 | { |
266 | struct weim_priv *priv; |
267 | struct clk *clk; |
268 | void __iomem *base; |
269 | int ret; |
270 | |
271 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
272 | if (!priv) |
273 | return -ENOMEM; |
274 | |
275 | /* get the resource */ |
276 | base = devm_platform_ioremap_resource(pdev, index: 0); |
277 | if (IS_ERR(ptr: base)) |
278 | return PTR_ERR(ptr: base); |
279 | |
280 | priv->base = base; |
281 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
282 | |
283 | /* get the clock */ |
284 | clk = devm_clk_get(dev: &pdev->dev, NULL); |
285 | if (IS_ERR(ptr: clk)) |
286 | return PTR_ERR(ptr: clk); |
287 | |
288 | ret = clk_prepare_enable(clk); |
289 | if (ret) |
290 | return ret; |
291 | |
292 | /* parse the device node */ |
293 | ret = weim_parse_dt(pdev); |
294 | if (ret) |
295 | clk_disable_unprepare(clk); |
296 | else |
297 | dev_info(&pdev->dev, "Driver registered.\n" ); |
298 | |
299 | return ret; |
300 | } |
301 | |
302 | #if IS_ENABLED(CONFIG_OF_DYNAMIC) |
303 | static int of_weim_notify(struct notifier_block *nb, unsigned long action, |
304 | void *arg) |
305 | { |
306 | const struct imx_weim_devtype *devtype; |
307 | struct of_reconfig_data *rd = arg; |
308 | const struct of_device_id *of_id; |
309 | struct platform_device *pdev; |
310 | int ret = NOTIFY_OK; |
311 | |
312 | switch (of_reconfig_get_state_change(action, arg: rd)) { |
313 | case OF_RECONFIG_CHANGE_ADD: |
314 | of_id = of_match_node(matches: weim_id_table, node: rd->dn->parent); |
315 | if (!of_id) |
316 | return NOTIFY_OK; /* not for us */ |
317 | |
318 | devtype = of_id->data; |
319 | |
320 | pdev = of_find_device_by_node(np: rd->dn->parent); |
321 | if (!pdev) { |
322 | pr_err("%s: could not find platform device for '%pOF'\n" , |
323 | __func__, rd->dn->parent); |
324 | |
325 | return notifier_from_errno(err: -EINVAL); |
326 | } |
327 | |
328 | if (weim_timing_setup(dev: &pdev->dev, np: rd->dn, devtype)) |
329 | dev_warn(&pdev->dev, |
330 | "Failed to setup timing for '%pOF'\n" , rd->dn); |
331 | |
332 | if (!of_node_check_flag(n: rd->dn, OF_POPULATED)) { |
333 | /* |
334 | * Clear the flag before adding the device so that |
335 | * fw_devlink doesn't skip adding consumers to this |
336 | * device. |
337 | */ |
338 | rd->dn->fwnode.flags &= ~FWNODE_FLAG_NOT_DEVICE; |
339 | if (!of_platform_device_create(np: rd->dn, NULL, parent: &pdev->dev)) { |
340 | dev_err(&pdev->dev, |
341 | "Failed to create child device '%pOF'\n" , |
342 | rd->dn); |
343 | ret = notifier_from_errno(err: -EINVAL); |
344 | } |
345 | } |
346 | |
347 | platform_device_put(pdev); |
348 | |
349 | break; |
350 | case OF_RECONFIG_CHANGE_REMOVE: |
351 | if (!of_node_check_flag(n: rd->dn, OF_POPULATED)) |
352 | return NOTIFY_OK; /* device already destroyed */ |
353 | |
354 | of_id = of_match_node(matches: weim_id_table, node: rd->dn->parent); |
355 | if (!of_id) |
356 | return NOTIFY_OK; /* not for us */ |
357 | |
358 | pdev = of_find_device_by_node(np: rd->dn); |
359 | if (!pdev) { |
360 | pr_err("Could not find platform device for '%pOF'\n" , |
361 | rd->dn); |
362 | |
363 | ret = notifier_from_errno(err: -EINVAL); |
364 | } else { |
365 | of_platform_device_destroy(dev: &pdev->dev, NULL); |
366 | platform_device_put(pdev); |
367 | } |
368 | |
369 | break; |
370 | default: |
371 | break; |
372 | } |
373 | |
374 | return ret; |
375 | } |
376 | |
377 | static struct notifier_block weim_of_notifier = { |
378 | .notifier_call = of_weim_notify, |
379 | }; |
380 | #endif /* IS_ENABLED(CONFIG_OF_DYNAMIC) */ |
381 | |
382 | static struct platform_driver weim_driver = { |
383 | .driver = { |
384 | .name = "imx-weim" , |
385 | .of_match_table = weim_id_table, |
386 | }, |
387 | .probe = weim_probe, |
388 | }; |
389 | |
390 | static int __init weim_init(void) |
391 | { |
392 | #if IS_ENABLED(CONFIG_OF_DYNAMIC) |
393 | WARN_ON(of_reconfig_notifier_register(&weim_of_notifier)); |
394 | #endif /* IS_ENABLED(CONFIG_OF_DYNAMIC) */ |
395 | |
396 | return platform_driver_register(&weim_driver); |
397 | } |
398 | module_init(weim_init); |
399 | |
400 | static void __exit weim_exit(void) |
401 | { |
402 | #if IS_ENABLED(CONFIG_OF_DYNAMIC) |
403 | of_reconfig_notifier_unregister(&weim_of_notifier); |
404 | #endif /* IS_ENABLED(CONFIG_OF_DYNAMIC) */ |
405 | |
406 | return platform_driver_unregister(&weim_driver); |
407 | |
408 | } |
409 | module_exit(weim_exit); |
410 | |
411 | MODULE_AUTHOR("Freescale Semiconductor Inc." ); |
412 | MODULE_DESCRIPTION("i.MX EIM Controller Driver" ); |
413 | MODULE_LICENSE("GPL" ); |
414 | |