18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * TXx9 ACLC AC97 driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Atsushi Nemoto
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Based on RBTX49xx patch from CELF patch archive.
88c2ecf20Sopenharmony_ci * (C) Copyright TOSHIBA CORPORATION 2004-2006
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci#include <linux/gfp.h>
178c2ecf20Sopenharmony_ci#include <asm/mach-tx39xx/ioremap.h> /* for TXX9_DIRECTMAP_BASE */
188c2ecf20Sopenharmony_ci#include <sound/core.h>
198c2ecf20Sopenharmony_ci#include <sound/pcm.h>
208c2ecf20Sopenharmony_ci#include <sound/soc.h>
218c2ecf20Sopenharmony_ci#include "txx9aclc.h"
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define AC97_DIR	\
248c2ecf20Sopenharmony_ci	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define AC97_RATES	\
278c2ecf20Sopenharmony_ci	SNDRV_PCM_RATE_8000_48000
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#ifdef __BIG_ENDIAN
308c2ecf20Sopenharmony_ci#define AC97_FMTS	SNDRV_PCM_FMTBIT_S16_BE
318c2ecf20Sopenharmony_ci#else
328c2ecf20Sopenharmony_ci#define AC97_FMTS	SNDRV_PCM_FMTBIT_S16_LE
338c2ecf20Sopenharmony_ci#endif
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(ac97_waitq);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */
388c2ecf20Sopenharmony_cistatic struct txx9aclc_plat_drvdata *txx9aclc_drvdata;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int txx9aclc_regready(struct txx9aclc_plat_drvdata *drvdata)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* AC97 controller reads codec register */
468c2ecf20Sopenharmony_cistatic unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97,
478c2ecf20Sopenharmony_ci					 unsigned short reg)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
508c2ecf20Sopenharmony_ci	void __iomem *base = drvdata->base;
518c2ecf20Sopenharmony_ci	u32 dat;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num)))
548c2ecf20Sopenharmony_ci		return 0xffff;
558c2ecf20Sopenharmony_ci	reg |= ac97->num << 7;
568c2ecf20Sopenharmony_ci	dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ;
578c2ecf20Sopenharmony_ci	__raw_writel(dat, base + ACREGACC);
588c2ecf20Sopenharmony_ci	__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
598c2ecf20Sopenharmony_ci	if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
608c2ecf20Sopenharmony_ci		__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
618c2ecf20Sopenharmony_ci		printk(KERN_ERR "ac97 read timeout (reg %#x)\n", reg);
628c2ecf20Sopenharmony_ci		dat = 0xffff;
638c2ecf20Sopenharmony_ci		goto done;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci	dat = __raw_readl(base + ACREGACC);
668c2ecf20Sopenharmony_ci	if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) {
678c2ecf20Sopenharmony_ci		printk(KERN_ERR "reg mismatch %x with %x\n",
688c2ecf20Sopenharmony_ci			dat, reg);
698c2ecf20Sopenharmony_ci		dat = 0xffff;
708c2ecf20Sopenharmony_ci		goto done;
718c2ecf20Sopenharmony_ci	}
728c2ecf20Sopenharmony_ci	dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff;
738c2ecf20Sopenharmony_cidone:
748c2ecf20Sopenharmony_ci	__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
758c2ecf20Sopenharmony_ci	return dat;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/* AC97 controller writes to codec register */
798c2ecf20Sopenharmony_cistatic void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
808c2ecf20Sopenharmony_ci				unsigned short val)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
838c2ecf20Sopenharmony_ci	void __iomem *base = drvdata->base;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	__raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) |
868c2ecf20Sopenharmony_ci		     (val << ACREGACC_DAT_SHIFT),
878c2ecf20Sopenharmony_ci		     base + ACREGACC);
888c2ecf20Sopenharmony_ci	__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
898c2ecf20Sopenharmony_ci	if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
908c2ecf20Sopenharmony_ci		printk(KERN_ERR
918c2ecf20Sopenharmony_ci			"ac97 write timeout (reg %#x)\n", reg);
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci	__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
998c2ecf20Sopenharmony_ci	void __iomem *base = drvdata->base;
1008c2ecf20Sopenharmony_ci	u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	__raw_writel(ACCTL_ENLINK, base + ACCTLDIS);
1038c2ecf20Sopenharmony_ci	udelay(1);
1048c2ecf20Sopenharmony_ci	__raw_writel(ACCTL_ENLINK, base + ACCTLEN);
1058c2ecf20Sopenharmony_ci	/* wait for primary codec ready status */
1068c2ecf20Sopenharmony_ci	__raw_writel(ready, base + ACINTEN);
1078c2ecf20Sopenharmony_ci	if (!wait_event_timeout(ac97_waitq,
1088c2ecf20Sopenharmony_ci				(__raw_readl(base + ACINTSTS) & ready) == ready,
1098c2ecf20Sopenharmony_ci				HZ)) {
1108c2ecf20Sopenharmony_ci		dev_err(&ac97->dev, "primary codec is not ready "
1118c2ecf20Sopenharmony_ci			"(status %#x)\n",
1128c2ecf20Sopenharmony_ci			__raw_readl(base + ACINTSTS));
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci	__raw_writel(ACINT_REGACCRDY, base + ACINTSTS);
1158c2ecf20Sopenharmony_ci	__raw_writel(ready, base + ACINTDIS);
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/* AC97 controller operations */
1198c2ecf20Sopenharmony_cistatic struct snd_ac97_bus_ops txx9aclc_ac97_ops = {
1208c2ecf20Sopenharmony_ci	.read		= txx9aclc_ac97_read,
1218c2ecf20Sopenharmony_ci	.write		= txx9aclc_ac97_write,
1228c2ecf20Sopenharmony_ci	.reset		= txx9aclc_ac97_cold_reset,
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata = dev_id;
1288c2ecf20Sopenharmony_ci	void __iomem *base = drvdata->base;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	__raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS);
1318c2ecf20Sopenharmony_ci	wake_up(&ac97_waitq);
1328c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic int txx9aclc_ac97_probe(struct snd_soc_dai *dai)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	txx9aclc_drvdata = snd_soc_dai_get_drvdata(dai);
1388c2ecf20Sopenharmony_ci	return 0;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic int txx9aclc_ac97_remove(struct snd_soc_dai *dai)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata = snd_soc_dai_get_drvdata(dai);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	/* disable AC-link */
1468c2ecf20Sopenharmony_ci	__raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS);
1478c2ecf20Sopenharmony_ci	txx9aclc_drvdata = NULL;
1488c2ecf20Sopenharmony_ci	return 0;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver txx9aclc_ac97_dai = {
1528c2ecf20Sopenharmony_ci	.probe			= txx9aclc_ac97_probe,
1538c2ecf20Sopenharmony_ci	.remove			= txx9aclc_ac97_remove,
1548c2ecf20Sopenharmony_ci	.playback = {
1558c2ecf20Sopenharmony_ci		.rates		= AC97_RATES,
1568c2ecf20Sopenharmony_ci		.formats	= AC97_FMTS,
1578c2ecf20Sopenharmony_ci		.channels_min	= 2,
1588c2ecf20Sopenharmony_ci		.channels_max	= 2,
1598c2ecf20Sopenharmony_ci	},
1608c2ecf20Sopenharmony_ci	.capture = {
1618c2ecf20Sopenharmony_ci		.rates		= AC97_RATES,
1628c2ecf20Sopenharmony_ci		.formats	= AC97_FMTS,
1638c2ecf20Sopenharmony_ci		.channels_min	= 2,
1648c2ecf20Sopenharmony_ci		.channels_max	= 2,
1658c2ecf20Sopenharmony_ci	},
1668c2ecf20Sopenharmony_ci};
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver txx9aclc_ac97_component = {
1698c2ecf20Sopenharmony_ci	.name		= "txx9aclc-ac97",
1708c2ecf20Sopenharmony_ci};
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic int txx9aclc_ac97_dev_probe(struct platform_device *pdev)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	struct txx9aclc_plat_drvdata *drvdata;
1758c2ecf20Sopenharmony_ci	struct resource *r;
1768c2ecf20Sopenharmony_ci	int err;
1778c2ecf20Sopenharmony_ci	int irq;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
1808c2ecf20Sopenharmony_ci	if (irq < 0)
1818c2ecf20Sopenharmony_ci		return irq;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
1848c2ecf20Sopenharmony_ci	if (!drvdata)
1858c2ecf20Sopenharmony_ci		return -ENOMEM;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1888c2ecf20Sopenharmony_ci	drvdata->base = devm_ioremap_resource(&pdev->dev, r);
1898c2ecf20Sopenharmony_ci	if (IS_ERR(drvdata->base))
1908c2ecf20Sopenharmony_ci		return PTR_ERR(drvdata->base);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, drvdata);
1938c2ecf20Sopenharmony_ci	drvdata->physbase = r->start;
1948c2ecf20Sopenharmony_ci	if (sizeof(drvdata->physbase) > sizeof(r->start) &&
1958c2ecf20Sopenharmony_ci	    r->start >= TXX9_DIRECTMAP_BASE &&
1968c2ecf20Sopenharmony_ci	    r->start < TXX9_DIRECTMAP_BASE + 0x400000)
1978c2ecf20Sopenharmony_ci		drvdata->physbase |= 0xf00000000ull;
1988c2ecf20Sopenharmony_ci	err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq,
1998c2ecf20Sopenharmony_ci			       0, dev_name(&pdev->dev), drvdata);
2008c2ecf20Sopenharmony_ci	if (err < 0)
2018c2ecf20Sopenharmony_ci		return err;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	err = snd_soc_set_ac97_ops(&txx9aclc_ac97_ops);
2048c2ecf20Sopenharmony_ci	if (err < 0)
2058c2ecf20Sopenharmony_ci		return err;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	return devm_snd_soc_register_component(&pdev->dev, &txx9aclc_ac97_component,
2088c2ecf20Sopenharmony_ci					  &txx9aclc_ac97_dai, 1);
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_cistatic int txx9aclc_ac97_dev_remove(struct platform_device *pdev)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	snd_soc_set_ac97_ops(NULL);
2148c2ecf20Sopenharmony_ci	return 0;
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic struct platform_driver txx9aclc_ac97_driver = {
2188c2ecf20Sopenharmony_ci	.probe		= txx9aclc_ac97_dev_probe,
2198c2ecf20Sopenharmony_ci	.remove		= txx9aclc_ac97_dev_remove,
2208c2ecf20Sopenharmony_ci	.driver		= {
2218c2ecf20Sopenharmony_ci		.name	= "txx9aclc-ac97",
2228c2ecf20Sopenharmony_ci	},
2238c2ecf20Sopenharmony_ci};
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cimodule_platform_driver(txx9aclc_ac97_driver);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
2288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TXx9 ACLC AC97 driver");
2298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2308c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:txx9aclc-ac97");
231