-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ASoC: Add TXx9 AC link controller driver (v3)
This patch adds support for the integrated ACLC of the TXx9 family. Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
- Loading branch information
Atsushi Nemoto
authored and
Mark Brown
committed
May 19, 2009
1 parent
11a7281
commit e24805d
Showing
8 changed files
with
908 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
## | ||
## TXx9 ACLC | ||
## | ||
config SND_SOC_TXX9ACLC | ||
tristate "SoC Audio for TXx9" | ||
depends on HAS_TXX9_ACLC && TXX9_DMAC | ||
help | ||
This option enables support for the AC Link Controllers in TXx9 SoC. | ||
|
||
config HAS_TXX9_ACLC | ||
bool | ||
|
||
config SND_SOC_TXX9ACLC_AC97 | ||
tristate | ||
select AC97_BUS | ||
select SND_AC97_CODEC | ||
select SND_SOC_AC97_BUS | ||
|
||
|
||
## | ||
## Boards | ||
## | ||
config SND_SOC_TXX9ACLC_GENERIC | ||
tristate "Generic TXx9 ACLC sound machine" | ||
depends on SND_SOC_TXX9ACLC | ||
select SND_SOC_TXX9ACLC_AC97 | ||
select SND_SOC_AC97_CODEC | ||
help | ||
This is a generic AC97 sound machine for use in TXx9 based systems. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Platform | ||
snd-soc-txx9aclc-objs := txx9aclc.o | ||
snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o | ||
|
||
obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o | ||
obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o | ||
|
||
# Machine | ||
snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o | ||
|
||
obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
/* | ||
* TXx9 ACLC AC97 driver | ||
* | ||
* Copyright (C) 2009 Atsushi Nemoto | ||
* | ||
* Based on RBTX49xx patch from CELF patch archive. | ||
* (C) Copyright TOSHIBA CORPORATION 2004-2006 | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
*/ | ||
|
||
#include <linux/init.h> | ||
#include <linux/module.h> | ||
#include <linux/delay.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/io.h> | ||
#include <sound/core.h> | ||
#include <sound/pcm.h> | ||
#include <sound/soc.h> | ||
#include "txx9aclc.h" | ||
|
||
#define AC97_DIR \ | ||
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | ||
|
||
#define AC97_RATES \ | ||
SNDRV_PCM_RATE_8000_48000 | ||
|
||
#ifdef __BIG_ENDIAN | ||
#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE | ||
#else | ||
#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE | ||
#endif | ||
|
||
static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq); | ||
|
||
/* REVISIT: How to find txx9aclc_soc_device from snd_ac97? */ | ||
static struct txx9aclc_soc_device *txx9aclc_soc_dev; | ||
|
||
static int txx9aclc_regready(struct txx9aclc_soc_device *dev) | ||
{ | ||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
|
||
return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY; | ||
} | ||
|
||
/* AC97 controller reads codec register */ | ||
static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97, | ||
unsigned short reg) | ||
{ | ||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
void __iomem *base = drvdata->base; | ||
u32 dat; | ||
|
||
if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num))) | ||
return 0xffff; | ||
reg |= ac97->num << 7; | ||
dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ; | ||
__raw_writel(dat, base + ACREGACC); | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTEN); | ||
if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
dev_err(dev->soc_dev.dev, "ac97 read timeout (reg %#x)\n", reg); | ||
dat = 0xffff; | ||
goto done; | ||
} | ||
dat = __raw_readl(base + ACREGACC); | ||
if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) { | ||
dev_err(dev->soc_dev.dev, "reg mismatch %x with %x\n", | ||
dat, reg); | ||
dat = 0xffff; | ||
goto done; | ||
} | ||
dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff; | ||
done: | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
return dat; | ||
} | ||
|
||
/* AC97 controller writes to codec register */ | ||
static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | ||
unsigned short val) | ||
{ | ||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
void __iomem *base = drvdata->base; | ||
|
||
__raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) | | ||
(val << ACREGACC_DAT_SHIFT), | ||
base + ACREGACC); | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTEN); | ||
if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { | ||
dev_err(dev->soc_dev.dev, | ||
"ac97 write timeout (reg %#x)\n", reg); | ||
} | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTDIS); | ||
} | ||
|
||
static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97) | ||
{ | ||
struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; | ||
struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); | ||
void __iomem *base = drvdata->base; | ||
u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY; | ||
|
||
__raw_writel(ACCTL_ENLINK, base + ACCTLDIS); | ||
mmiowb(); | ||
udelay(1); | ||
__raw_writel(ACCTL_ENLINK, base + ACCTLEN); | ||
/* wait for primary codec ready status */ | ||
__raw_writel(ready, base + ACINTEN); | ||
if (!wait_event_timeout(ac97_waitq, | ||
(__raw_readl(base + ACINTSTS) & ready) == ready, | ||
HZ)) { | ||
dev_err(&ac97->dev, "primary codec is not ready " | ||
"(status %#x)\n", | ||
__raw_readl(base + ACINTSTS)); | ||
} | ||
__raw_writel(ACINT_REGACCRDY, base + ACINTSTS); | ||
__raw_writel(ready, base + ACINTDIS); | ||
} | ||
|
||
/* AC97 controller operations */ | ||
struct snd_ac97_bus_ops soc_ac97_ops = { | ||
.read = txx9aclc_ac97_read, | ||
.write = txx9aclc_ac97_write, | ||
.reset = txx9aclc_ac97_cold_reset, | ||
}; | ||
EXPORT_SYMBOL_GPL(soc_ac97_ops); | ||
|
||
static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id) | ||
{ | ||
struct txx9aclc_plat_drvdata *drvdata = dev_id; | ||
void __iomem *base = drvdata->base; | ||
|
||
__raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS); | ||
wake_up(&ac97_waitq); | ||
return IRQ_HANDLED; | ||
} | ||
|
||
static int txx9aclc_ac97_probe(struct platform_device *pdev, | ||
struct snd_soc_dai *dai) | ||
{ | ||
struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
struct txx9aclc_soc_device *dev = | ||
container_of(socdev, struct txx9aclc_soc_device, soc_dev); | ||
|
||
dev->aclc_pdev = to_platform_device(dai->dev); | ||
txx9aclc_soc_dev = dev; | ||
return 0; | ||
} | ||
|
||
static void txx9aclc_ac97_remove(struct platform_device *pdev, | ||
struct snd_soc_dai *dai) | ||
{ | ||
struct platform_device *aclc_pdev = to_platform_device(dai->dev); | ||
struct txx9aclc_plat_drvdata *drvdata = platform_get_drvdata(aclc_pdev); | ||
|
||
/* disable AC-link */ | ||
__raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS); | ||
txx9aclc_soc_dev = NULL; | ||
} | ||
|
||
struct snd_soc_dai txx9aclc_ac97_dai = { | ||
.name = "txx9aclc_ac97", | ||
.ac97_control = 1, | ||
.probe = txx9aclc_ac97_probe, | ||
.remove = txx9aclc_ac97_remove, | ||
.playback = { | ||
.rates = AC97_RATES, | ||
.formats = AC97_FMTS, | ||
.channels_min = 2, | ||
.channels_max = 2, | ||
}, | ||
.capture = { | ||
.rates = AC97_RATES, | ||
.formats = AC97_FMTS, | ||
.channels_min = 2, | ||
.channels_max = 2, | ||
}, | ||
}; | ||
EXPORT_SYMBOL_GPL(txx9aclc_ac97_dai); | ||
|
||
static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev) | ||
{ | ||
struct txx9aclc_plat_drvdata *drvdata; | ||
struct resource *r; | ||
int err; | ||
int irq; | ||
|
||
irq = platform_get_irq(pdev, 0); | ||
if (irq < 0) | ||
return irq; | ||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
if (!r) | ||
return -EBUSY; | ||
|
||
if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), | ||
dev_name(&pdev->dev))) | ||
return -EBUSY; | ||
|
||
drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); | ||
if (!drvdata) | ||
return -ENOMEM; | ||
platform_set_drvdata(pdev, drvdata); | ||
drvdata->physbase = r->start; | ||
if (sizeof(drvdata->physbase) > sizeof(r->start) && | ||
r->start >= TXX9_DIRECTMAP_BASE && | ||
r->start < TXX9_DIRECTMAP_BASE + 0x400000) | ||
drvdata->physbase |= 0xf00000000ull; | ||
drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); | ||
if (!drvdata->base) | ||
return -EBUSY; | ||
err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq, | ||
IRQF_DISABLED, dev_name(&pdev->dev), drvdata); | ||
if (err < 0) | ||
return err; | ||
|
||
txx9aclc_ac97_dai.dev = &pdev->dev; | ||
return snd_soc_register_dai(&txx9aclc_ac97_dai); | ||
} | ||
|
||
static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev) | ||
{ | ||
snd_soc_unregister_dai(&txx9aclc_ac97_dai); | ||
return 0; | ||
} | ||
|
||
static struct platform_driver txx9aclc_ac97_driver = { | ||
.probe = txx9aclc_ac97_dev_probe, | ||
.remove = __devexit_p(txx9aclc_ac97_dev_remove), | ||
.driver = { | ||
.name = "txx9aclc-ac97", | ||
.owner = THIS_MODULE, | ||
}, | ||
}; | ||
|
||
static int __init txx9aclc_ac97_init(void) | ||
{ | ||
return platform_driver_register(&txx9aclc_ac97_driver); | ||
} | ||
|
||
static void __exit txx9aclc_ac97_exit(void) | ||
{ | ||
platform_driver_unregister(&txx9aclc_ac97_driver); | ||
} | ||
|
||
module_init(txx9aclc_ac97_init); | ||
module_exit(txx9aclc_ac97_exit); | ||
|
||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); | ||
MODULE_DESCRIPTION("TXx9 ACLC AC97 driver"); | ||
MODULE_LICENSE("GPL"); |
Oops, something went wrong.