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