/* * Copyright 2012 Freescale Semiconductor, Inc. * Copyright 2012 Linaro Ltd. * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> * * Initial development of this code was funded by * Phytec Messtechnik GmbH, http://www.phytec.de * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/clk.h> #include <linux/debugfs.h> #include <linux/err.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/pinctrl/consumer.h> #include "imx-audmux.h" #define DRIVER_NAME "imx-audmux" static struct clk *audmux_clk; static void __iomem *audmux_base; #define IMX_AUDMUX_V2_PTCR(x) ((x) * 8) #define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4) #ifdef CONFIG_DEBUG_FS static struct dentry *audmux_debugfs_root; /* There is an annoying discontinuity in the SSI numbering with regard * to the Linux number of the devices */ static const char *audmux_port_string(int port) { switch (port) { case MX31_AUDMUX_PORT1_SSI0: return "imx-ssi.0"; case MX31_AUDMUX_PORT2_SSI1: return "imx-ssi.1"; case MX31_AUDMUX_PORT3_SSI_PINS_3: return "SSI3"; case MX31_AUDMUX_PORT4_SSI_PINS_4: return "SSI4"; case MX31_AUDMUX_PORT5_SSI_PINS_5: return "SSI5"; case MX31_AUDMUX_PORT6_SSI_PINS_6: return "SSI6"; default: return "UNKNOWN"; } } static ssize_t audmux_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { ssize_t ret; char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL); int port = (int)file->private_data; u32 pdcr, ptcr; if (!buf) return -ENOMEM; if (audmux_clk) clk_prepare_enable(audmux_clk); ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port)); pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port)); if (audmux_clk) clk_disable_unprepare(audmux_clk); ret = snprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n", pdcr, ptcr); if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR) ret += snprintf(buf + ret, PAGE_SIZE - ret, "TxFS output from %s, ", audmux_port_string((ptcr >> 27) & 0x7)); else ret += snprintf(buf + ret, PAGE_SIZE - ret, "TxFS input, "); if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR) ret += snprintf(buf + ret, PAGE_SIZE - ret, "TxClk output from %s", audmux_port_string((ptcr >> 22) & 0x7)); else ret += snprintf(buf + ret, PAGE_SIZE - ret, "TxClk input"); ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) { ret += snprintf(buf + ret, PAGE_SIZE - ret, "Port is symmetric"); } else { if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR) ret += snprintf(buf + ret, PAGE_SIZE - ret, "RxFS output from %s, ", audmux_port_string((ptcr >> 17) & 0x7)); else ret += snprintf(buf + ret, PAGE_SIZE - ret, "RxFS input, "); if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR) ret += snprintf(buf + ret, PAGE_SIZE - ret, "RxClk output from %s", audmux_port_string((ptcr >> 12) & 0x7)); else ret += snprintf(buf + ret, PAGE_SIZE - ret, "RxClk input"); } ret += snprintf(buf + ret, PAGE_SIZE - ret, "\nData received from %s\n", audmux_port_string((pdcr >> 13) & 0x7)); ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); kfree(buf); return ret; } static const struct file_operations audmux_debugfs_fops = { .open = simple_open, .read = audmux_read_file, .llseek = default_llseek, }; static void __init audmux_debugfs_init(void) { int i; char buf[20]; audmux_debugfs_root = debugfs_create_dir("audmux", NULL); if (!audmux_debugfs_root) { pr_warning("Failed to create AUDMUX debugfs root\n"); return; } for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) { snprintf(buf, sizeof(buf), "ssi%d", i); if (!debugfs_create_file(buf, 0444, audmux_debugfs_root, (void *)i, &audmux_debugfs_fops)) pr_warning("Failed to create AUDMUX port %d debugfs file\n", i); } } static void audmux_debugfs_remove(void) { debugfs_remove_recursive(audmux_debugfs_root); } #else static inline void audmux_debugfs_init(void) { } static inline void audmux_debugfs_remove(void) { } #endif static enum imx_audmux_type { IMX21_AUDMUX, IMX31_AUDMUX, } audmux_type; static struct platform_device_id imx_audmux_ids[] = { { .name = "imx21-audmux", .driver_data = IMX21_AUDMUX, }, { .name = "imx31-audmux", .driver_data = IMX31_AUDMUX, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, imx_audmux_ids); static const struct of_device_id imx_audmux_dt_ids[] = { { .compatible = "fsl,imx21-audmux", .data = &imx_audmux_ids[0], }, { .compatible = "fsl,imx31-audmux", .data = &imx_audmux_ids[1], }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids); static const uint8_t port_mapping[] = { 0x0, 0x4, 0x8, 0x10, 0x14, 0x1c, }; int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr) { if (audmux_type != IMX21_AUDMUX) return -EINVAL; if (!audmux_base) return -ENOSYS; if (port >= ARRAY_SIZE(port_mapping)) return -EINVAL; writel(pcr, audmux_base + port_mapping[port]); return 0; } EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port); int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, unsigned int pdcr) { if (audmux_type != IMX31_AUDMUX) return -EINVAL; if (!audmux_base) return -ENOSYS; if (audmux_clk) clk_prepare_enable(audmux_clk); writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port)); writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port)); if (audmux_clk) clk_disable_unprepare(audmux_clk); return 0; } EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port); static int imx_audmux_probe(struct platform_device *pdev) { struct resource *res; struct pinctrl *pinctrl; const struct of_device_id *of_id = of_match_device(imx_audmux_dt_ids, &pdev->dev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); audmux_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(audmux_base)) return PTR_ERR(audmux_base); pinctrl = devm_pinctrl_get_select_default(&pdev->dev); if (IS_ERR(pinctrl)) { dev_err(&pdev->dev, "setup pinctrl failed!"); return PTR_ERR(pinctrl); } audmux_clk = devm_clk_get(&pdev->dev, "audmux"); if (IS_ERR(audmux_clk)) { dev_dbg(&pdev->dev, "cannot get clock: %ld\n", PTR_ERR(audmux_clk)); audmux_clk = NULL; } if (of_id) pdev->id_entry = of_id->data; audmux_type = pdev->id_entry->driver_data; if (audmux_type == IMX31_AUDMUX) audmux_debugfs_init(); return 0; } static int imx_audmux_remove(struct platform_device *pdev) { if (audmux_type == IMX31_AUDMUX) audmux_debugfs_remove(); return 0; } static struct platform_driver imx_audmux_driver = { .probe = imx_audmux_probe, .remove = imx_audmux_remove, .id_table = imx_audmux_ids, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = imx_audmux_dt_ids, } }; static int __init imx_audmux_init(void) { return platform_driver_register(&imx_audmux_driver); } subsys_initcall(imx_audmux_init); static void __exit imx_audmux_exit(void) { platform_driver_unregister(&imx_audmux_driver); } module_exit(imx_audmux_exit); MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" DRIVER_NAME);