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