1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Copyright 2012 Freescale Semiconductor, Inc. |
4 | // Copyright 2012 Linaro Ltd. |
5 | // Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> |
6 | // |
7 | // Initial development of this code was funded by |
8 | // Phytec Messtechnik GmbH, https://www.phytec.de |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/debugfs.h> |
12 | #include <linux/err.h> |
13 | #include <linux/io.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/slab.h> |
18 | |
19 | #include "imx-audmux.h" |
20 | |
21 | #define DRIVER_NAME "imx-audmux" |
22 | |
23 | static struct clk *audmux_clk; |
24 | static void __iomem *audmux_base; |
25 | static u32 *regcache; |
26 | static u32 reg_max; |
27 | |
28 | #define IMX_AUDMUX_V2_PTCR(x) ((x) * 8) |
29 | #define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4) |
30 | |
31 | #ifdef CONFIG_DEBUG_FS |
32 | static struct dentry *audmux_debugfs_root; |
33 | |
34 | /* There is an annoying discontinuity in the SSI numbering with regard |
35 | * to the Linux number of the devices */ |
36 | static const char *audmux_port_string(int port) |
37 | { |
38 | switch (port) { |
39 | case MX31_AUDMUX_PORT1_SSI0: |
40 | return "imx-ssi.0" ; |
41 | case MX31_AUDMUX_PORT2_SSI1: |
42 | return "imx-ssi.1" ; |
43 | case MX31_AUDMUX_PORT3_SSI_PINS_3: |
44 | return "SSI3" ; |
45 | case MX31_AUDMUX_PORT4_SSI_PINS_4: |
46 | return "SSI4" ; |
47 | case MX31_AUDMUX_PORT5_SSI_PINS_5: |
48 | return "SSI5" ; |
49 | case MX31_AUDMUX_PORT6_SSI_PINS_6: |
50 | return "SSI6" ; |
51 | default: |
52 | return "UNKNOWN" ; |
53 | } |
54 | } |
55 | |
56 | static ssize_t audmux_read_file(struct file *file, char __user *user_buf, |
57 | size_t count, loff_t *ppos) |
58 | { |
59 | ssize_t ret; |
60 | char *buf; |
61 | uintptr_t port = (uintptr_t)file->private_data; |
62 | u32 pdcr, ptcr; |
63 | |
64 | ret = clk_prepare_enable(clk: audmux_clk); |
65 | if (ret) |
66 | return ret; |
67 | |
68 | ptcr = readl(addr: audmux_base + IMX_AUDMUX_V2_PTCR(port)); |
69 | pdcr = readl(addr: audmux_base + IMX_AUDMUX_V2_PDCR(port)); |
70 | |
71 | clk_disable_unprepare(clk: audmux_clk); |
72 | |
73 | buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
74 | if (!buf) |
75 | return -ENOMEM; |
76 | |
77 | ret = sysfs_emit(buf, fmt: "PDCR: %08x\nPTCR: %08x\n" , pdcr, ptcr); |
78 | |
79 | if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR) |
80 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
81 | fmt: "TxFS output from %s, " , |
82 | audmux_port_string(port: (ptcr >> 27) & 0x7)); |
83 | else |
84 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
85 | fmt: "TxFS input, " ); |
86 | |
87 | if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR) |
88 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
89 | fmt: "TxClk output from %s" , |
90 | audmux_port_string(port: (ptcr >> 22) & 0x7)); |
91 | else |
92 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
93 | fmt: "TxClk input" ); |
94 | |
95 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, fmt: "\n" ); |
96 | |
97 | if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) { |
98 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
99 | fmt: "Port is symmetric" ); |
100 | } else { |
101 | if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR) |
102 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
103 | fmt: "RxFS output from %s, " , |
104 | audmux_port_string(port: (ptcr >> 17) & 0x7)); |
105 | else |
106 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
107 | fmt: "RxFS input, " ); |
108 | |
109 | if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR) |
110 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
111 | fmt: "RxClk output from %s" , |
112 | audmux_port_string(port: (ptcr >> 12) & 0x7)); |
113 | else |
114 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
115 | fmt: "RxClk input" ); |
116 | } |
117 | |
118 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
119 | fmt: "\nData received from %s\n" , |
120 | audmux_port_string(port: (pdcr >> 13) & 0x7)); |
121 | |
122 | ret = simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: ret); |
123 | |
124 | kfree(objp: buf); |
125 | |
126 | return ret; |
127 | } |
128 | |
129 | static const struct file_operations audmux_debugfs_fops = { |
130 | .open = simple_open, |
131 | .read = audmux_read_file, |
132 | .llseek = default_llseek, |
133 | }; |
134 | |
135 | static void audmux_debugfs_init(void) |
136 | { |
137 | uintptr_t i; |
138 | char buf[20]; |
139 | |
140 | audmux_debugfs_root = debugfs_create_dir(name: "audmux" , NULL); |
141 | |
142 | for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) { |
143 | snprintf(buf, size: sizeof(buf), fmt: "ssi%lu" , i); |
144 | debugfs_create_file(name: buf, mode: 0444, parent: audmux_debugfs_root, |
145 | data: (void *)i, fops: &audmux_debugfs_fops); |
146 | } |
147 | } |
148 | |
149 | static void audmux_debugfs_remove(void) |
150 | { |
151 | debugfs_remove_recursive(dentry: audmux_debugfs_root); |
152 | } |
153 | #else |
154 | static inline void audmux_debugfs_init(void) |
155 | { |
156 | } |
157 | |
158 | static inline void audmux_debugfs_remove(void) |
159 | { |
160 | } |
161 | #endif |
162 | |
163 | static enum imx_audmux_type { |
164 | IMX21_AUDMUX, |
165 | IMX31_AUDMUX, |
166 | } audmux_type; |
167 | |
168 | static const struct of_device_id imx_audmux_dt_ids[] = { |
169 | { .compatible = "fsl,imx21-audmux" , .data = (void *)IMX21_AUDMUX, }, |
170 | { .compatible = "fsl,imx31-audmux" , .data = (void *)IMX31_AUDMUX, }, |
171 | { /* sentinel */ } |
172 | }; |
173 | MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids); |
174 | |
175 | static const uint8_t port_mapping[] = { |
176 | 0x0, 0x4, 0x8, 0x10, 0x14, 0x1c, |
177 | }; |
178 | |
179 | int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr) |
180 | { |
181 | if (audmux_type != IMX21_AUDMUX) |
182 | return -EINVAL; |
183 | |
184 | if (!audmux_base) |
185 | return -ENOSYS; |
186 | |
187 | if (port >= ARRAY_SIZE(port_mapping)) |
188 | return -EINVAL; |
189 | |
190 | writel(val: pcr, addr: audmux_base + port_mapping[port]); |
191 | |
192 | return 0; |
193 | } |
194 | EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port); |
195 | |
196 | int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, |
197 | unsigned int pdcr) |
198 | { |
199 | int ret; |
200 | |
201 | if (audmux_type != IMX31_AUDMUX) |
202 | return -EINVAL; |
203 | |
204 | if (!audmux_base) |
205 | return -ENOSYS; |
206 | |
207 | ret = clk_prepare_enable(clk: audmux_clk); |
208 | if (ret) |
209 | return ret; |
210 | |
211 | writel(val: ptcr, addr: audmux_base + IMX_AUDMUX_V2_PTCR(port)); |
212 | writel(val: pdcr, addr: audmux_base + IMX_AUDMUX_V2_PDCR(port)); |
213 | |
214 | clk_disable_unprepare(clk: audmux_clk); |
215 | |
216 | return 0; |
217 | } |
218 | EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port); |
219 | |
220 | static int imx_audmux_parse_dt_defaults(struct platform_device *pdev, |
221 | struct device_node *of_node) |
222 | { |
223 | struct device_node *child; |
224 | |
225 | for_each_available_child_of_node(of_node, child) { |
226 | unsigned int port; |
227 | unsigned int ptcr = 0; |
228 | unsigned int pdcr = 0; |
229 | unsigned int pcr = 0; |
230 | unsigned int val; |
231 | int ret; |
232 | int i = 0; |
233 | |
234 | ret = of_property_read_u32(np: child, propname: "fsl,audmux-port" , out_value: &port); |
235 | if (ret) { |
236 | dev_warn(&pdev->dev, "Failed to get fsl,audmux-port of child node \"%pOF\"\n" , |
237 | child); |
238 | continue; |
239 | } |
240 | if (!of_property_read_bool(np: child, propname: "fsl,port-config" )) { |
241 | dev_warn(&pdev->dev, "child node \"%pOF\" does not have property fsl,port-config\n" , |
242 | child); |
243 | continue; |
244 | } |
245 | |
246 | for (i = 0; (ret = of_property_read_u32_index(np: child, |
247 | propname: "fsl,port-config" , index: i, out_value: &val)) == 0; |
248 | ++i) { |
249 | if (audmux_type == IMX31_AUDMUX) { |
250 | if (i % 2) |
251 | pdcr |= val; |
252 | else |
253 | ptcr |= val; |
254 | } else { |
255 | pcr |= val; |
256 | } |
257 | } |
258 | |
259 | if (ret != -EOVERFLOW) { |
260 | dev_err(&pdev->dev, "Failed to read u32 at index %d of child %pOF\n" , |
261 | i, child); |
262 | continue; |
263 | } |
264 | |
265 | if (audmux_type == IMX31_AUDMUX) { |
266 | if (i % 2) { |
267 | dev_err(&pdev->dev, "One pdcr value is missing in child node %pOF\n" , |
268 | child); |
269 | continue; |
270 | } |
271 | imx_audmux_v2_configure_port(port, ptcr, pdcr); |
272 | } else { |
273 | imx_audmux_v1_configure_port(port, pcr); |
274 | } |
275 | } |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static int imx_audmux_probe(struct platform_device *pdev) |
281 | { |
282 | audmux_base = devm_platform_ioremap_resource(pdev, index: 0); |
283 | if (IS_ERR(ptr: audmux_base)) |
284 | return PTR_ERR(ptr: audmux_base); |
285 | |
286 | audmux_clk = devm_clk_get(dev: &pdev->dev, id: "audmux" ); |
287 | if (IS_ERR(ptr: audmux_clk)) { |
288 | dev_dbg(&pdev->dev, "cannot get clock: %ld\n" , |
289 | PTR_ERR(audmux_clk)); |
290 | audmux_clk = NULL; |
291 | } |
292 | |
293 | audmux_type = (uintptr_t)of_device_get_match_data(dev: &pdev->dev); |
294 | |
295 | switch (audmux_type) { |
296 | case IMX31_AUDMUX: |
297 | audmux_debugfs_init(); |
298 | reg_max = 14; |
299 | break; |
300 | case IMX21_AUDMUX: |
301 | reg_max = 6; |
302 | break; |
303 | default: |
304 | dev_err(&pdev->dev, "unsupported version!\n" ); |
305 | return -EINVAL; |
306 | } |
307 | |
308 | regcache = devm_kzalloc(dev: &pdev->dev, size: sizeof(u32) * reg_max, GFP_KERNEL); |
309 | if (!regcache) |
310 | return -ENOMEM; |
311 | |
312 | imx_audmux_parse_dt_defaults(pdev, of_node: pdev->dev.of_node); |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | static void imx_audmux_remove(struct platform_device *pdev) |
318 | { |
319 | if (audmux_type == IMX31_AUDMUX) |
320 | audmux_debugfs_remove(); |
321 | } |
322 | |
323 | #ifdef CONFIG_PM_SLEEP |
324 | static int imx_audmux_suspend(struct device *dev) |
325 | { |
326 | int i; |
327 | |
328 | clk_prepare_enable(clk: audmux_clk); |
329 | |
330 | for (i = 0; i < reg_max; i++) |
331 | regcache[i] = readl(addr: audmux_base + i * 4); |
332 | |
333 | clk_disable_unprepare(clk: audmux_clk); |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static int imx_audmux_resume(struct device *dev) |
339 | { |
340 | int i; |
341 | |
342 | clk_prepare_enable(clk: audmux_clk); |
343 | |
344 | for (i = 0; i < reg_max; i++) |
345 | writel(val: regcache[i], addr: audmux_base + i * 4); |
346 | |
347 | clk_disable_unprepare(clk: audmux_clk); |
348 | |
349 | return 0; |
350 | } |
351 | #endif /* CONFIG_PM_SLEEP */ |
352 | |
353 | static const struct dev_pm_ops imx_audmux_pm = { |
354 | SET_SYSTEM_SLEEP_PM_OPS(imx_audmux_suspend, imx_audmux_resume) |
355 | }; |
356 | |
357 | static struct platform_driver imx_audmux_driver = { |
358 | .probe = imx_audmux_probe, |
359 | .remove_new = imx_audmux_remove, |
360 | .driver = { |
361 | .name = DRIVER_NAME, |
362 | .pm = &imx_audmux_pm, |
363 | .of_match_table = imx_audmux_dt_ids, |
364 | } |
365 | }; |
366 | |
367 | static int __init imx_audmux_init(void) |
368 | { |
369 | return platform_driver_register(&imx_audmux_driver); |
370 | } |
371 | subsys_initcall(imx_audmux_init); |
372 | |
373 | static void __exit imx_audmux_exit(void) |
374 | { |
375 | platform_driver_unregister(&imx_audmux_driver); |
376 | } |
377 | module_exit(imx_audmux_exit); |
378 | |
379 | MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver" ); |
380 | MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>" ); |
381 | MODULE_LICENSE("GPL v2" ); |
382 | MODULE_ALIAS("platform:" DRIVER_NAME); |
383 | |