162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  linux/drivers/mfd/mcp-sa11x0.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2001-2005 Russell King
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  SA11x0 MCP (Multimedia Communications Port) driver.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  MCP read/write timeouts from Jordi Colomer, rehacked by rmk.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/delay.h>
1662306a36Sopenharmony_ci#include <linux/spinlock.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/pm.h>
1962306a36Sopenharmony_ci#include <linux/mfd/mcp.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <mach/hardware.h>
2262306a36Sopenharmony_ci#include <asm/mach-types.h>
2362306a36Sopenharmony_ci#include <linux/platform_data/mfd-mcp-sa11x0.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define DRIVER_NAME "sa11x0-mcp"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct mcp_sa11x0 {
2862306a36Sopenharmony_ci	void __iomem	*base0;
2962306a36Sopenharmony_ci	void __iomem	*base1;
3062306a36Sopenharmony_ci	u32		mccr0;
3162306a36Sopenharmony_ci	u32		mccr1;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* Register offsets */
3562306a36Sopenharmony_ci#define MCCR0(m)	((m)->base0 + 0x00)
3662306a36Sopenharmony_ci#define MCDR0(m)	((m)->base0 + 0x08)
3762306a36Sopenharmony_ci#define MCDR1(m)	((m)->base0 + 0x0c)
3862306a36Sopenharmony_ci#define MCDR2(m)	((m)->base0 + 0x10)
3962306a36Sopenharmony_ci#define MCSR(m)		((m)->base0 + 0x18)
4062306a36Sopenharmony_ci#define MCCR1(m)	((m)->base1 + 0x00)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define priv(mcp)	((struct mcp_sa11x0 *)mcp_priv(mcp))
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic void
4562306a36Sopenharmony_cimcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	divisor /= 32;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	m->mccr0 &= ~0x00007f00;
5262306a36Sopenharmony_ci	m->mccr0 |= divisor << 8;
5362306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic void
5762306a36Sopenharmony_cimcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	divisor /= 32;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	m->mccr0 &= ~0x0000007f;
6462306a36Sopenharmony_ci	m->mccr0 |= divisor;
6562306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/*
6962306a36Sopenharmony_ci * Write data to the device.  The bit should be set after 3 subframe
7062306a36Sopenharmony_ci * times (each frame is 64 clocks).  We wait a maximum of 6 subframes.
7162306a36Sopenharmony_ci * We really should try doing something more productive while we
7262306a36Sopenharmony_ci * wait.
7362306a36Sopenharmony_ci */
7462306a36Sopenharmony_cistatic void
7562306a36Sopenharmony_cimcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
7862306a36Sopenharmony_ci	int ret = -ETIME;
7962306a36Sopenharmony_ci	int i;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	writel_relaxed(reg << 17 | MCDR2_Wr | (val & 0xffff), MCDR2(m));
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	for (i = 0; i < 2; i++) {
8462306a36Sopenharmony_ci		udelay(mcp->rw_timeout);
8562306a36Sopenharmony_ci		if (readl_relaxed(MCSR(m)) & MCSR_CWC) {
8662306a36Sopenharmony_ci			ret = 0;
8762306a36Sopenharmony_ci			break;
8862306a36Sopenharmony_ci		}
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (ret < 0)
9262306a36Sopenharmony_ci		printk(KERN_WARNING "mcp: write timed out\n");
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci/*
9662306a36Sopenharmony_ci * Read data from the device.  The bit should be set after 3 subframe
9762306a36Sopenharmony_ci * times (each frame is 64 clocks).  We wait a maximum of 6 subframes.
9862306a36Sopenharmony_ci * We really should try doing something more productive while we
9962306a36Sopenharmony_ci * wait.
10062306a36Sopenharmony_ci */
10162306a36Sopenharmony_cistatic unsigned int
10262306a36Sopenharmony_cimcp_sa11x0_read(struct mcp *mcp, unsigned int reg)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
10562306a36Sopenharmony_ci	int ret = -ETIME;
10662306a36Sopenharmony_ci	int i;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	writel_relaxed(reg << 17 | MCDR2_Rd, MCDR2(m));
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	for (i = 0; i < 2; i++) {
11162306a36Sopenharmony_ci		udelay(mcp->rw_timeout);
11262306a36Sopenharmony_ci		if (readl_relaxed(MCSR(m)) & MCSR_CRC) {
11362306a36Sopenharmony_ci			ret = readl_relaxed(MCDR2(m)) & 0xffff;
11462306a36Sopenharmony_ci			break;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (ret < 0)
11962306a36Sopenharmony_ci		printk(KERN_WARNING "mcp: read timed out\n");
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return ret;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic void mcp_sa11x0_enable(struct mcp *mcp)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	writel(-1, MCSR(m));
12962306a36Sopenharmony_ci	m->mccr0 |= MCCR0_MCE;
13062306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic void mcp_sa11x0_disable(struct mcp *mcp)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	m->mccr0 &= ~MCCR0_MCE;
13862306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci/*
14262306a36Sopenharmony_ci * Our methods.
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_cistatic struct mcp_ops mcp_sa11x0 = {
14562306a36Sopenharmony_ci	.set_telecom_divisor	= mcp_sa11x0_set_telecom_divisor,
14662306a36Sopenharmony_ci	.set_audio_divisor	= mcp_sa11x0_set_audio_divisor,
14762306a36Sopenharmony_ci	.reg_write		= mcp_sa11x0_write,
14862306a36Sopenharmony_ci	.reg_read		= mcp_sa11x0_read,
14962306a36Sopenharmony_ci	.enable			= mcp_sa11x0_enable,
15062306a36Sopenharmony_ci	.disable		= mcp_sa11x0_disable,
15162306a36Sopenharmony_ci};
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic int mcp_sa11x0_probe(struct platform_device *dev)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	struct mcp_plat_data *data = dev_get_platdata(&dev->dev);
15662306a36Sopenharmony_ci	struct resource *mem0, *mem1;
15762306a36Sopenharmony_ci	struct mcp_sa11x0 *m;
15862306a36Sopenharmony_ci	struct mcp *mcp;
15962306a36Sopenharmony_ci	int ret;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (!data)
16262306a36Sopenharmony_ci		return -ENODEV;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0);
16562306a36Sopenharmony_ci	mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1);
16662306a36Sopenharmony_ci	if (!mem0 || !mem1)
16762306a36Sopenharmony_ci		return -ENXIO;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (!request_mem_region(mem0->start, resource_size(mem0),
17062306a36Sopenharmony_ci				DRIVER_NAME)) {
17162306a36Sopenharmony_ci		ret = -EBUSY;
17262306a36Sopenharmony_ci		goto err_mem0;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (!request_mem_region(mem1->start, resource_size(mem1),
17662306a36Sopenharmony_ci				DRIVER_NAME)) {
17762306a36Sopenharmony_ci		ret = -EBUSY;
17862306a36Sopenharmony_ci		goto err_mem1;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	mcp = mcp_host_alloc(&dev->dev, sizeof(struct mcp_sa11x0));
18262306a36Sopenharmony_ci	if (!mcp) {
18362306a36Sopenharmony_ci		ret = -ENOMEM;
18462306a36Sopenharmony_ci		goto err_alloc;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	mcp->owner		= THIS_MODULE;
18862306a36Sopenharmony_ci	mcp->ops		= &mcp_sa11x0;
18962306a36Sopenharmony_ci	mcp->sclk_rate		= data->sclk_rate;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	m = priv(mcp);
19262306a36Sopenharmony_ci	m->mccr0 = data->mccr0 | 0x7f7f;
19362306a36Sopenharmony_ci	m->mccr1 = data->mccr1;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	m->base0 = ioremap(mem0->start, resource_size(mem0));
19662306a36Sopenharmony_ci	m->base1 = ioremap(mem1->start, resource_size(mem1));
19762306a36Sopenharmony_ci	if (!m->base0 || !m->base1) {
19862306a36Sopenharmony_ci		ret = -ENOMEM;
19962306a36Sopenharmony_ci		goto err_ioremap;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	platform_set_drvdata(dev, mcp);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	/*
20562306a36Sopenharmony_ci	 * Initialise device.  Note that we initially
20662306a36Sopenharmony_ci	 * set the sampling rate to minimum.
20762306a36Sopenharmony_ci	 */
20862306a36Sopenharmony_ci	writel_relaxed(-1, MCSR(m));
20962306a36Sopenharmony_ci	writel_relaxed(m->mccr1, MCCR1(m));
21062306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/*
21362306a36Sopenharmony_ci	 * Calculate the read/write timeout (us) from the bit clock
21462306a36Sopenharmony_ci	 * rate.  This is the period for 3 64-bit frames.  Always
21562306a36Sopenharmony_ci	 * round this time up.
21662306a36Sopenharmony_ci	 */
21762306a36Sopenharmony_ci	mcp->rw_timeout = DIV_ROUND_UP(64 * 3 * 1000000, mcp->sclk_rate);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	ret = mcp_host_add(mcp, data->codec_pdata);
22062306a36Sopenharmony_ci	if (ret == 0)
22162306a36Sopenharmony_ci		return 0;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci err_ioremap:
22462306a36Sopenharmony_ci	iounmap(m->base1);
22562306a36Sopenharmony_ci	iounmap(m->base0);
22662306a36Sopenharmony_ci	mcp_host_free(mcp);
22762306a36Sopenharmony_ci err_alloc:
22862306a36Sopenharmony_ci	release_mem_region(mem1->start, resource_size(mem1));
22962306a36Sopenharmony_ci err_mem1:
23062306a36Sopenharmony_ci	release_mem_region(mem0->start, resource_size(mem0));
23162306a36Sopenharmony_ci err_mem0:
23262306a36Sopenharmony_ci	return ret;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic int mcp_sa11x0_remove(struct platform_device *dev)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	struct mcp *mcp = platform_get_drvdata(dev);
23862306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(mcp);
23962306a36Sopenharmony_ci	struct resource *mem0, *mem1;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (m->mccr0 & MCCR0_MCE)
24262306a36Sopenharmony_ci		dev_warn(&dev->dev,
24362306a36Sopenharmony_ci			 "device left active (missing disable call?)\n");
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0);
24662306a36Sopenharmony_ci	mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	mcp_host_del(mcp);
24962306a36Sopenharmony_ci	iounmap(m->base1);
25062306a36Sopenharmony_ci	iounmap(m->base0);
25162306a36Sopenharmony_ci	mcp_host_free(mcp);
25262306a36Sopenharmony_ci	release_mem_region(mem1->start, resource_size(mem1));
25362306a36Sopenharmony_ci	release_mem_region(mem0->start, resource_size(mem0));
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	return 0;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic int mcp_sa11x0_suspend(struct device *dev)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev));
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (m->mccr0 & MCCR0_MCE)
26362306a36Sopenharmony_ci		dev_warn(dev, "device left active (missing disable call?)\n");
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	writel(m->mccr0 & ~MCCR0_MCE, MCCR0(m));
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic int mcp_sa11x0_resume(struct device *dev)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev));
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	writel_relaxed(m->mccr1, MCCR1(m));
27562306a36Sopenharmony_ci	writel_relaxed(m->mccr0, MCCR0(m));
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	return 0;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic const struct dev_pm_ops mcp_sa11x0_pm_ops = {
28162306a36Sopenharmony_ci	.suspend = mcp_sa11x0_suspend,
28262306a36Sopenharmony_ci	.freeze = mcp_sa11x0_suspend,
28362306a36Sopenharmony_ci	.poweroff = mcp_sa11x0_suspend,
28462306a36Sopenharmony_ci	.resume_noirq = mcp_sa11x0_resume,
28562306a36Sopenharmony_ci	.thaw_noirq = mcp_sa11x0_resume,
28662306a36Sopenharmony_ci	.restore_noirq = mcp_sa11x0_resume,
28762306a36Sopenharmony_ci};
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic struct platform_driver mcp_sa11x0_driver = {
29062306a36Sopenharmony_ci	.probe		= mcp_sa11x0_probe,
29162306a36Sopenharmony_ci	.remove		= mcp_sa11x0_remove,
29262306a36Sopenharmony_ci	.driver		= {
29362306a36Sopenharmony_ci		.name	= DRIVER_NAME,
29462306a36Sopenharmony_ci		.pm	= pm_sleep_ptr(&mcp_sa11x0_pm_ops),
29562306a36Sopenharmony_ci	},
29662306a36Sopenharmony_ci};
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci/*
29962306a36Sopenharmony_ci * This needs re-working
30062306a36Sopenharmony_ci */
30162306a36Sopenharmony_cimodule_platform_driver(mcp_sa11x0_driver);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME);
30462306a36Sopenharmony_ciMODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
30562306a36Sopenharmony_ciMODULE_DESCRIPTION("SA11x0 multimedia communications port driver");
30662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
307