18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  linux/drivers/mfd/mcp-sa11x0.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2001-2005 Russell King
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  SA11x0 MCP (Multimedia Communications Port) driver.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *  MCP read/write timeouts from Jordi Colomer, rehacked by rmk.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/errno.h>
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/delay.h>
168c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
178c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
188c2ecf20Sopenharmony_ci#include <linux/pm.h>
198c2ecf20Sopenharmony_ci#include <linux/mfd/mcp.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <mach/hardware.h>
228c2ecf20Sopenharmony_ci#include <asm/mach-types.h>
238c2ecf20Sopenharmony_ci#include <linux/platform_data/mfd-mcp-sa11x0.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define DRIVER_NAME "sa11x0-mcp"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct mcp_sa11x0 {
288c2ecf20Sopenharmony_ci	void __iomem	*base0;
298c2ecf20Sopenharmony_ci	void __iomem	*base1;
308c2ecf20Sopenharmony_ci	u32		mccr0;
318c2ecf20Sopenharmony_ci	u32		mccr1;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/* Register offsets */
358c2ecf20Sopenharmony_ci#define MCCR0(m)	((m)->base0 + 0x00)
368c2ecf20Sopenharmony_ci#define MCDR0(m)	((m)->base0 + 0x08)
378c2ecf20Sopenharmony_ci#define MCDR1(m)	((m)->base0 + 0x0c)
388c2ecf20Sopenharmony_ci#define MCDR2(m)	((m)->base0 + 0x10)
398c2ecf20Sopenharmony_ci#define MCSR(m)		((m)->base0 + 0x18)
408c2ecf20Sopenharmony_ci#define MCCR1(m)	((m)->base1 + 0x00)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define priv(mcp)	((struct mcp_sa11x0 *)mcp_priv(mcp))
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic void
458c2ecf20Sopenharmony_cimcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	divisor /= 32;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	m->mccr0 &= ~0x00007f00;
528c2ecf20Sopenharmony_ci	m->mccr0 |= divisor << 8;
538c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic void
578c2ecf20Sopenharmony_cimcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	divisor /= 32;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	m->mccr0 &= ~0x0000007f;
648c2ecf20Sopenharmony_ci	m->mccr0 |= divisor;
658c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci/*
698c2ecf20Sopenharmony_ci * Write data to the device.  The bit should be set after 3 subframe
708c2ecf20Sopenharmony_ci * times (each frame is 64 clocks).  We wait a maximum of 6 subframes.
718c2ecf20Sopenharmony_ci * We really should try doing something more productive while we
728c2ecf20Sopenharmony_ci * wait.
738c2ecf20Sopenharmony_ci */
748c2ecf20Sopenharmony_cistatic void
758c2ecf20Sopenharmony_cimcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
788c2ecf20Sopenharmony_ci	int ret = -ETIME;
798c2ecf20Sopenharmony_ci	int i;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	writel_relaxed(reg << 17 | MCDR2_Wr | (val & 0xffff), MCDR2(m));
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	for (i = 0; i < 2; i++) {
848c2ecf20Sopenharmony_ci		udelay(mcp->rw_timeout);
858c2ecf20Sopenharmony_ci		if (readl_relaxed(MCSR(m)) & MCSR_CWC) {
868c2ecf20Sopenharmony_ci			ret = 0;
878c2ecf20Sopenharmony_ci			break;
888c2ecf20Sopenharmony_ci		}
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (ret < 0)
928c2ecf20Sopenharmony_ci		printk(KERN_WARNING "mcp: write timed out\n");
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci/*
968c2ecf20Sopenharmony_ci * Read data from the device.  The bit should be set after 3 subframe
978c2ecf20Sopenharmony_ci * times (each frame is 64 clocks).  We wait a maximum of 6 subframes.
988c2ecf20Sopenharmony_ci * We really should try doing something more productive while we
998c2ecf20Sopenharmony_ci * wait.
1008c2ecf20Sopenharmony_ci */
1018c2ecf20Sopenharmony_cistatic unsigned int
1028c2ecf20Sopenharmony_cimcp_sa11x0_read(struct mcp *mcp, unsigned int reg)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
1058c2ecf20Sopenharmony_ci	int ret = -ETIME;
1068c2ecf20Sopenharmony_ci	int i;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	writel_relaxed(reg << 17 | MCDR2_Rd, MCDR2(m));
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	for (i = 0; i < 2; i++) {
1118c2ecf20Sopenharmony_ci		udelay(mcp->rw_timeout);
1128c2ecf20Sopenharmony_ci		if (readl_relaxed(MCSR(m)) & MCSR_CRC) {
1138c2ecf20Sopenharmony_ci			ret = readl_relaxed(MCDR2(m)) & 0xffff;
1148c2ecf20Sopenharmony_ci			break;
1158c2ecf20Sopenharmony_ci		}
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (ret < 0)
1198c2ecf20Sopenharmony_ci		printk(KERN_WARNING "mcp: read timed out\n");
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	return ret;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic void mcp_sa11x0_enable(struct mcp *mcp)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	writel(-1, MCSR(m));
1298c2ecf20Sopenharmony_ci	m->mccr0 |= MCCR0_MCE;
1308c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic void mcp_sa11x0_disable(struct mcp *mcp)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	m->mccr0 &= ~MCCR0_MCE;
1388c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci/*
1428c2ecf20Sopenharmony_ci * Our methods.
1438c2ecf20Sopenharmony_ci */
1448c2ecf20Sopenharmony_cistatic struct mcp_ops mcp_sa11x0 = {
1458c2ecf20Sopenharmony_ci	.set_telecom_divisor	= mcp_sa11x0_set_telecom_divisor,
1468c2ecf20Sopenharmony_ci	.set_audio_divisor	= mcp_sa11x0_set_audio_divisor,
1478c2ecf20Sopenharmony_ci	.reg_write		= mcp_sa11x0_write,
1488c2ecf20Sopenharmony_ci	.reg_read		= mcp_sa11x0_read,
1498c2ecf20Sopenharmony_ci	.enable			= mcp_sa11x0_enable,
1508c2ecf20Sopenharmony_ci	.disable		= mcp_sa11x0_disable,
1518c2ecf20Sopenharmony_ci};
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistatic int mcp_sa11x0_probe(struct platform_device *dev)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct mcp_plat_data *data = dev_get_platdata(&dev->dev);
1568c2ecf20Sopenharmony_ci	struct resource *mem0, *mem1;
1578c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m;
1588c2ecf20Sopenharmony_ci	struct mcp *mcp;
1598c2ecf20Sopenharmony_ci	int ret;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	if (!data)
1628c2ecf20Sopenharmony_ci		return -ENODEV;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0);
1658c2ecf20Sopenharmony_ci	mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1);
1668c2ecf20Sopenharmony_ci	if (!mem0 || !mem1)
1678c2ecf20Sopenharmony_ci		return -ENXIO;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (!request_mem_region(mem0->start, resource_size(mem0),
1708c2ecf20Sopenharmony_ci				DRIVER_NAME)) {
1718c2ecf20Sopenharmony_ci		ret = -EBUSY;
1728c2ecf20Sopenharmony_ci		goto err_mem0;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	if (!request_mem_region(mem1->start, resource_size(mem1),
1768c2ecf20Sopenharmony_ci				DRIVER_NAME)) {
1778c2ecf20Sopenharmony_ci		ret = -EBUSY;
1788c2ecf20Sopenharmony_ci		goto err_mem1;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	mcp = mcp_host_alloc(&dev->dev, sizeof(struct mcp_sa11x0));
1828c2ecf20Sopenharmony_ci	if (!mcp) {
1838c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1848c2ecf20Sopenharmony_ci		goto err_alloc;
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	mcp->owner		= THIS_MODULE;
1888c2ecf20Sopenharmony_ci	mcp->ops		= &mcp_sa11x0;
1898c2ecf20Sopenharmony_ci	mcp->sclk_rate		= data->sclk_rate;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	m = priv(mcp);
1928c2ecf20Sopenharmony_ci	m->mccr0 = data->mccr0 | 0x7f7f;
1938c2ecf20Sopenharmony_ci	m->mccr1 = data->mccr1;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	m->base0 = ioremap(mem0->start, resource_size(mem0));
1968c2ecf20Sopenharmony_ci	m->base1 = ioremap(mem1->start, resource_size(mem1));
1978c2ecf20Sopenharmony_ci	if (!m->base0 || !m->base1) {
1988c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1998c2ecf20Sopenharmony_ci		goto err_ioremap;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, mcp);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/*
2058c2ecf20Sopenharmony_ci	 * Initialise device.  Note that we initially
2068c2ecf20Sopenharmony_ci	 * set the sampling rate to minimum.
2078c2ecf20Sopenharmony_ci	 */
2088c2ecf20Sopenharmony_ci	writel_relaxed(-1, MCSR(m));
2098c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr1, MCCR1(m));
2108c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/*
2138c2ecf20Sopenharmony_ci	 * Calculate the read/write timeout (us) from the bit clock
2148c2ecf20Sopenharmony_ci	 * rate.  This is the period for 3 64-bit frames.  Always
2158c2ecf20Sopenharmony_ci	 * round this time up.
2168c2ecf20Sopenharmony_ci	 */
2178c2ecf20Sopenharmony_ci	mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) /
2188c2ecf20Sopenharmony_ci			  mcp->sclk_rate;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	ret = mcp_host_add(mcp, data->codec_pdata);
2218c2ecf20Sopenharmony_ci	if (ret == 0)
2228c2ecf20Sopenharmony_ci		return 0;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci err_ioremap:
2258c2ecf20Sopenharmony_ci	iounmap(m->base1);
2268c2ecf20Sopenharmony_ci	iounmap(m->base0);
2278c2ecf20Sopenharmony_ci	mcp_host_free(mcp);
2288c2ecf20Sopenharmony_ci err_alloc:
2298c2ecf20Sopenharmony_ci	release_mem_region(mem1->start, resource_size(mem1));
2308c2ecf20Sopenharmony_ci err_mem1:
2318c2ecf20Sopenharmony_ci	release_mem_region(mem0->start, resource_size(mem0));
2328c2ecf20Sopenharmony_ci err_mem0:
2338c2ecf20Sopenharmony_ci	return ret;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic int mcp_sa11x0_remove(struct platform_device *dev)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	struct mcp *mcp = platform_get_drvdata(dev);
2398c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
2408c2ecf20Sopenharmony_ci	struct resource *mem0, *mem1;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	if (m->mccr0 & MCCR0_MCE)
2438c2ecf20Sopenharmony_ci		dev_warn(&dev->dev,
2448c2ecf20Sopenharmony_ci			 "device left active (missing disable call?)\n");
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0);
2478c2ecf20Sopenharmony_ci	mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	mcp_host_del(mcp);
2508c2ecf20Sopenharmony_ci	iounmap(m->base1);
2518c2ecf20Sopenharmony_ci	iounmap(m->base0);
2528c2ecf20Sopenharmony_ci	mcp_host_free(mcp);
2538c2ecf20Sopenharmony_ci	release_mem_region(mem1->start, resource_size(mem1));
2548c2ecf20Sopenharmony_ci	release_mem_region(mem0->start, resource_size(mem0));
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	return 0;
2578c2ecf20Sopenharmony_ci}
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
2608c2ecf20Sopenharmony_cistatic int mcp_sa11x0_suspend(struct device *dev)
2618c2ecf20Sopenharmony_ci{
2628c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev));
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	if (m->mccr0 & MCCR0_MCE)
2658c2ecf20Sopenharmony_ci		dev_warn(dev, "device left active (missing disable call?)\n");
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	writel(m->mccr0 & ~MCCR0_MCE, MCCR0(m));
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	return 0;
2708c2ecf20Sopenharmony_ci}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistatic int mcp_sa11x0_resume(struct device *dev)
2738c2ecf20Sopenharmony_ci{
2748c2ecf20Sopenharmony_ci	struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev));
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr1, MCCR1(m));
2778c2ecf20Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	return 0;
2808c2ecf20Sopenharmony_ci}
2818c2ecf20Sopenharmony_ci#endif
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_cistatic const struct dev_pm_ops mcp_sa11x0_pm_ops = {
2848c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
2858c2ecf20Sopenharmony_ci	.suspend = mcp_sa11x0_suspend,
2868c2ecf20Sopenharmony_ci	.freeze = mcp_sa11x0_suspend,
2878c2ecf20Sopenharmony_ci	.poweroff = mcp_sa11x0_suspend,
2888c2ecf20Sopenharmony_ci	.resume_noirq = mcp_sa11x0_resume,
2898c2ecf20Sopenharmony_ci	.thaw_noirq = mcp_sa11x0_resume,
2908c2ecf20Sopenharmony_ci	.restore_noirq = mcp_sa11x0_resume,
2918c2ecf20Sopenharmony_ci#endif
2928c2ecf20Sopenharmony_ci};
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic struct platform_driver mcp_sa11x0_driver = {
2958c2ecf20Sopenharmony_ci	.probe		= mcp_sa11x0_probe,
2968c2ecf20Sopenharmony_ci	.remove		= mcp_sa11x0_remove,
2978c2ecf20Sopenharmony_ci	.driver		= {
2988c2ecf20Sopenharmony_ci		.name	= DRIVER_NAME,
2998c2ecf20Sopenharmony_ci		.pm	= &mcp_sa11x0_pm_ops,
3008c2ecf20Sopenharmony_ci	},
3018c2ecf20Sopenharmony_ci};
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci/*
3048c2ecf20Sopenharmony_ci * This needs re-working
3058c2ecf20Sopenharmony_ci */
3068c2ecf20Sopenharmony_cimodule_platform_driver(mcp_sa11x0_driver);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME);
3098c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
3108c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SA11x0 multimedia communications port driver");
3118c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
312