imx-audmux.c 7.56 KB
Newer Older
1
/*
2 3
 * Copyright 2012 Freescale Semiconductor, Inc.
 * Copyright 2012 Linaro Ltd.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * 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>
21
#include <linux/debugfs.h>
22 23 24
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
25 26
#include <linux/of.h>
#include <linux/of_device.h>
27
#include <linux/platform_device.h>
28
#include <linux/slab.h>
29
#include <linux/pinctrl/consumer.h>
30 31

#include "imx-audmux.h"
32 33

#define DRIVER_NAME "imx-audmux"
34 35 36 37

static struct clk *audmux_clk;
static void __iomem *audmux_base;

38 39
#define IMX_AUDMUX_V2_PTCR(x)		((x) * 8)
#define IMX_AUDMUX_V2_PDCR(x)		((x) * 8 + 4)
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
#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)
78
		clk_prepare_enable(audmux_clk);
79

80 81
	ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port));
	pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port));
82 83

	if (audmux_clk)
84
		clk_disable_unprepare(audmux_clk);
85 86 87 88

	ret = snprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n",
		       pdcr, ptcr);

89
	if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR)
90 91 92 93 94 95 96
		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, ");

97
	if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR)
98 99 100 101 102 103 104 105 106
		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");

107
	if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) {
108 109 110
		ret += snprintf(buf + ret, PAGE_SIZE - ret,
				"Port is symmetric");
	} else {
111
		if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR)
112 113 114 115 116 117 118
			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, ");

119
		if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR)
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
			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 = {
140
	.open = simple_open,
141
	.read = audmux_read_file,
142
	.llseek = default_llseek,
143 144
};

145
static void __init audmux_debugfs_init(void)
146 147 148 149 150 151 152 153 154 155
{
	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;
	}

156
	for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {
157 158 159 160 161 162 163
		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);
	}
}
164

165
static void audmux_debugfs_remove(void)
166 167 168
{
	debugfs_remove_recursive(audmux_debugfs_root);
}
169 170 171 172
#else
static inline void audmux_debugfs_init(void)
{
}
173 174 175 176

static inline void audmux_debugfs_remove(void)
{
}
177 178
#endif

179
static enum imx_audmux_type {
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
	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);

197 198 199 200 201 202 203
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);

204 205 206 207
static const uint8_t port_mapping[] = {
	0x0, 0x4, 0x8, 0x10, 0x14, 0x1c,
};

208
int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr)
209
{
210 211 212
	if (audmux_type != IMX21_AUDMUX)
		return -EINVAL;

213 214 215 216 217 218 219 220 221 222
	if (!audmux_base)
		return -ENOSYS;

	if (port >= ARRAY_SIZE(port_mapping))
		return -EINVAL;

	writel(pcr, audmux_base + port_mapping[port]);

	return 0;
}
223
EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port);
224

225
int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
226 227
		unsigned int pdcr)
{
228 229 230
	if (audmux_type != IMX31_AUDMUX)
		return -EINVAL;

231 232 233 234
	if (!audmux_base)
		return -ENOSYS;

	if (audmux_clk)
235
		clk_prepare_enable(audmux_clk);
236

237 238
	writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port));
	writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port));
239 240

	if (audmux_clk)
241
		clk_disable_unprepare(audmux_clk);
242 243 244

	return 0;
}
245
EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port);
246

247
static int imx_audmux_probe(struct platform_device *pdev)
248
{
249
	struct resource *res;
250
	struct pinctrl *pinctrl;
251 252
	const struct of_device_id *of_id =
			of_match_device(imx_audmux_dt_ids, &pdev->dev);
253 254 255 256 257 258

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	audmux_base = devm_request_and_ioremap(&pdev->dev, res);
	if (!audmux_base)
		return -EADDRNOTAVAIL;

259 260 261 262 263 264
	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
	if (IS_ERR(pinctrl)) {
		dev_err(&pdev->dev, "setup pinctrl failed!");
		return PTR_ERR(pinctrl);
	}

265 266 267 268 269
	audmux_clk = 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;
270
	}
271

272 273
	if (of_id)
		pdev->id_entry = of_id->data;
274 275
	audmux_type = pdev->id_entry->driver_data;
	if (audmux_type == IMX31_AUDMUX)
276
		audmux_debugfs_init();
277

278 279 280
	return 0;
}

281
static int imx_audmux_remove(struct platform_device *pdev)
282 283 284 285 286 287 288 289 290 291
{
	if (audmux_type == IMX31_AUDMUX)
		audmux_debugfs_remove();
	clk_put(audmux_clk);

	return 0;
}

static struct platform_driver imx_audmux_driver = {
	.probe		= imx_audmux_probe,
292
	.remove		= imx_audmux_remove,
293 294 295 296
	.id_table	= imx_audmux_ids,
	.driver	= {
		.name	= DRIVER_NAME,
		.owner	= THIS_MODULE,
297
		.of_match_table = imx_audmux_dt_ids,
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
	}
};

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);