brintos

brintos / linux-shallow public Read only

0
0
Text · 8.5 KiB · 43e14f2 Raw
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