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