381 lines · c
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 by8// Phytec Messtechnik GmbH, https://www.phytec.de9 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 23static struct clk *audmux_clk;24static void __iomem *audmux_base;25static u32 *regcache;26static 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_FS32static struct dentry *audmux_debugfs_root;33 34/* There is an annoying discontinuity in the SSI numbering with regard35 * to the Linux number of the devices */36static 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 56static 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(audmux_clk);65 if (ret)66 return ret;67 68 ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port));69 pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port));70 71 clk_disable_unprepare(audmux_clk);72 73 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);74 if (!buf)75 return -ENOMEM;76 77 ret = sysfs_emit(buf, "PDCR: %08x\nPTCR: %08x\n", pdcr, ptcr);78 79 if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR)80 ret += scnprintf(buf + ret, PAGE_SIZE - ret,81 "TxFS output from %s, ",82 audmux_port_string((ptcr >> 27) & 0x7));83 else84 ret += scnprintf(buf + ret, PAGE_SIZE - ret,85 "TxFS input, ");86 87 if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR)88 ret += scnprintf(buf + ret, PAGE_SIZE - ret,89 "TxClk output from %s",90 audmux_port_string((ptcr >> 22) & 0x7));91 else92 ret += scnprintf(buf + ret, PAGE_SIZE - ret,93 "TxClk input");94 95 ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");96 97 if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) {98 ret += scnprintf(buf + ret, PAGE_SIZE - ret,99 "Port is symmetric");100 } else {101 if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR)102 ret += scnprintf(buf + ret, PAGE_SIZE - ret,103 "RxFS output from %s, ",104 audmux_port_string((ptcr >> 17) & 0x7));105 else106 ret += scnprintf(buf + ret, PAGE_SIZE - ret,107 "RxFS input, ");108 109 if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR)110 ret += scnprintf(buf + ret, PAGE_SIZE - ret,111 "RxClk output from %s",112 audmux_port_string((ptcr >> 12) & 0x7));113 else114 ret += scnprintf(buf + ret, PAGE_SIZE - ret,115 "RxClk input");116 }117 118 ret += scnprintf(buf + ret, PAGE_SIZE - ret,119 "\nData received from %s\n",120 audmux_port_string((pdcr >> 13) & 0x7));121 122 ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);123 124 kfree(buf);125 126 return ret;127}128 129static const struct file_operations audmux_debugfs_fops = {130 .open = simple_open,131 .read = audmux_read_file,132 .llseek = default_llseek,133};134 135static void audmux_debugfs_init(void)136{137 uintptr_t i;138 char buf[20];139 140 audmux_debugfs_root = debugfs_create_dir("audmux", NULL);141 142 for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {143 snprintf(buf, sizeof(buf), "ssi%lu", i);144 debugfs_create_file(buf, 0444, audmux_debugfs_root,145 (void *)i, &audmux_debugfs_fops);146 }147}148 149static void audmux_debugfs_remove(void)150{151 debugfs_remove_recursive(audmux_debugfs_root);152}153#else154static inline void audmux_debugfs_init(void)155{156}157 158static inline void audmux_debugfs_remove(void)159{160}161#endif162 163static enum imx_audmux_type {164 IMX21_AUDMUX,165 IMX31_AUDMUX,166} audmux_type;167 168static 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};173MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids);174 175static const uint8_t port_mapping[] = {176 0x0, 0x4, 0x8, 0x10, 0x14, 0x1c,177};178 179int 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(pcr, audmux_base + port_mapping[port]);191 192 return 0;193}194EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port);195 196int 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(audmux_clk);208 if (ret)209 return ret;210 211 writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port));212 writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port));213 214 clk_disable_unprepare(audmux_clk);215 216 return 0;217}218EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port);219 220static 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(child, "fsl,audmux-port", &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(child, "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(child,247 "fsl,port-config", i, &val)) == 0;248 ++i) {249 if (audmux_type == IMX31_AUDMUX) {250 if (i % 2)251 pdcr |= val;252 else253 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 280static int imx_audmux_probe(struct platform_device *pdev)281{282 audmux_base = devm_platform_ioremap_resource(pdev, 0);283 if (IS_ERR(audmux_base))284 return PTR_ERR(audmux_base);285 286 audmux_clk = devm_clk_get(&pdev->dev, "audmux");287 if (IS_ERR(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(&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(&pdev->dev, sizeof(u32) * reg_max, GFP_KERNEL);309 if (!regcache)310 return -ENOMEM;311 312 imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node);313 314 return 0;315}316 317static void imx_audmux_remove(struct platform_device *pdev)318{319 if (audmux_type == IMX31_AUDMUX)320 audmux_debugfs_remove();321}322 323static int imx_audmux_suspend(struct device *dev)324{325 int i;326 327 clk_prepare_enable(audmux_clk);328 329 for (i = 0; i < reg_max; i++)330 regcache[i] = readl(audmux_base + i * 4);331 332 clk_disable_unprepare(audmux_clk);333 334 return 0;335}336 337static int imx_audmux_resume(struct device *dev)338{339 int i;340 341 clk_prepare_enable(audmux_clk);342 343 for (i = 0; i < reg_max; i++)344 writel(regcache[i], audmux_base + i * 4);345 346 clk_disable_unprepare(audmux_clk);347 348 return 0;349}350 351static const struct dev_pm_ops imx_audmux_pm = {352 SYSTEM_SLEEP_PM_OPS(imx_audmux_suspend, imx_audmux_resume)353};354 355static struct platform_driver imx_audmux_driver = {356 .probe = imx_audmux_probe,357 .remove = imx_audmux_remove,358 .driver = {359 .name = DRIVER_NAME,360 .pm = pm_sleep_ptr(&imx_audmux_pm),361 .of_match_table = imx_audmux_dt_ids,362 }363};364 365static int __init imx_audmux_init(void)366{367 return platform_driver_register(&imx_audmux_driver);368}369subsys_initcall(imx_audmux_init);370 371static void __exit imx_audmux_exit(void)372{373 platform_driver_unregister(&imx_audmux_driver);374}375module_exit(imx_audmux_exit);376 377MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver");378MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");379MODULE_LICENSE("GPL v2");380MODULE_ALIAS("platform:" DRIVER_NAME);381