forked from Minki/linux
e6b5be2be4
Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlSOD20ACgkQMUfUDdst+ylLPACg2QrW1oHhdTMT9WI8jihlHVRM 53kAoLeteByQ3iVwWurwwseRPiWa8+MI =OVRS -----END PGP SIGNATURE----- Merge tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core update from Greg KH: "Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while" * tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (324 commits) Revert "ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries" fs: debugfs: add forward declaration for struct device type firmware class: Deletion of an unnecessary check before the function call "vunmap" firmware loader: fix hung task warning dump devcoredump: provide a one-way disable function device: Add dev_<level>_once variants ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries ath: use seq_file api for ath9k debugfs files debugfs: add helper function to create device related seq_file drivers/base: cacheinfo: remove noisy error boot message Revert "core: platform: add warning if driver has no owner" drivers: base: support cpu cache information interface to userspace via sysfs drivers: base: add cpu_device_create to support per-cpu devices topology: replace custom attribute macros with standard DEVICE_ATTR* cpumask: factor out show_cpumap into separate helper function driver core: Fix unbalanced device reference in drivers_probe driver core: fix race with userland in device_add() sysfs/kernfs: make read requests on pre-alloc files use the buffer. sysfs/kernfs: allow attributes to request write buffer be pre-allocated. fs: sysfs: return EGBIG on write if offset is larger than file size ...
454 lines
11 KiB
C
454 lines
11 KiB
C
/* sound/soc/samsung/ac97.c
|
|
*
|
|
* ALSA SoC Audio Layer - S3C AC97 Controller driver
|
|
* Evolved from s3c2443-ac97.c
|
|
*
|
|
* Copyright (c) 2010 Samsung Electronics Co. Ltd
|
|
* Author: Jaswinder Singh <jassisinghbrar@gmail.com>
|
|
* Credits: Graeme Gregory, Sean Choi
|
|
*
|
|
* 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/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include "regs-ac97.h"
|
|
#include <linux/platform_data/asoc-s3c.h>
|
|
|
|
#include "dma.h"
|
|
|
|
#define AC_CMD_ADDR(x) (x << 16)
|
|
#define AC_CMD_DATA(x) (x & 0xffff)
|
|
|
|
#define S3C_AC97_DAI_PCM 0
|
|
#define S3C_AC97_DAI_MIC 1
|
|
|
|
struct s3c_ac97_info {
|
|
struct clk *ac97_clk;
|
|
void __iomem *regs;
|
|
struct mutex lock;
|
|
struct completion done;
|
|
};
|
|
static struct s3c_ac97_info s3c_ac97;
|
|
|
|
static struct s3c_dma_params s3c_ac97_pcm_out = {
|
|
.dma_size = 4,
|
|
};
|
|
|
|
static struct s3c_dma_params s3c_ac97_pcm_in = {
|
|
.dma_size = 4,
|
|
};
|
|
|
|
static struct s3c_dma_params s3c_ac97_mic_in = {
|
|
.dma_size = 4,
|
|
};
|
|
|
|
static void s3c_ac97_activate(struct snd_ac97 *ac97)
|
|
{
|
|
u32 ac_glbctrl, stat;
|
|
|
|
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
|
|
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
|
|
return; /* Return if already active */
|
|
|
|
reinit_completion(&s3c_ac97.done);
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
|
pr_err("AC97: Unable to activate!");
|
|
}
|
|
|
|
static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
|
|
unsigned short reg)
|
|
{
|
|
u32 ac_glbctrl, ac_codec_cmd;
|
|
u32 stat, addr, data;
|
|
|
|
mutex_lock(&s3c_ac97.lock);
|
|
|
|
s3c_ac97_activate(ac97);
|
|
|
|
reinit_completion(&s3c_ac97.done);
|
|
|
|
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
|
|
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
|
|
udelay(50);
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
|
pr_err("AC97: Unable to read!");
|
|
|
|
stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
|
|
addr = (stat >> 16) & 0x7f;
|
|
data = (stat & 0xffff);
|
|
|
|
if (addr != reg)
|
|
pr_err("ac97: req addr = %02x, rep addr = %02x\n",
|
|
reg, addr);
|
|
|
|
mutex_unlock(&s3c_ac97.lock);
|
|
|
|
return (unsigned short)data;
|
|
}
|
|
|
|
static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
|
unsigned short val)
|
|
{
|
|
u32 ac_glbctrl, ac_codec_cmd;
|
|
|
|
mutex_lock(&s3c_ac97.lock);
|
|
|
|
s3c_ac97_activate(ac97);
|
|
|
|
reinit_completion(&s3c_ac97.done);
|
|
|
|
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
|
|
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
|
|
udelay(50);
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
|
|
pr_err("AC97: Unable to write!");
|
|
|
|
ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
|
|
writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
|
|
|
|
mutex_unlock(&s3c_ac97.lock);
|
|
}
|
|
|
|
static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
|
|
{
|
|
pr_debug("AC97: Cold reset\n");
|
|
writel(S3C_AC97_GLBCTRL_COLDRESET,
|
|
s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
|
|
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
}
|
|
|
|
static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
|
|
{
|
|
u32 stat;
|
|
|
|
stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
|
|
if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
|
|
return; /* Return if already active */
|
|
|
|
pr_debug("AC97: Warm reset\n");
|
|
|
|
writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
|
|
writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
msleep(1);
|
|
|
|
s3c_ac97_activate(ac97);
|
|
}
|
|
|
|
static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
|
|
{
|
|
u32 ac_glbctrl, ac_glbstat;
|
|
|
|
ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
|
|
|
|
if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
complete(&s3c_ac97.done);
|
|
}
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl |= (1<<30); /* Clear interrupt */
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct snd_ac97_bus_ops s3c_ac97_ops = {
|
|
.read = s3c_ac97_read,
|
|
.write = s3c_ac97_write,
|
|
.warm_reset = s3c_ac97_warm_reset,
|
|
.reset = s3c_ac97_cold_reset,
|
|
};
|
|
|
|
static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
u32 ac_glbctrl;
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
|
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
|
|
else
|
|
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
|
|
else
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
break;
|
|
}
|
|
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
u32 ac_glbctrl;
|
|
|
|
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
break;
|
|
}
|
|
|
|
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops s3c_ac97_dai_ops = {
|
|
.trigger = s3c_ac97_trigger,
|
|
};
|
|
|
|
static const struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
|
|
.trigger = s3c_ac97_mic_trigger,
|
|
};
|
|
|
|
static int s3c_ac97_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
samsung_asoc_init_dma_data(dai, &s3c_ac97_pcm_out, &s3c_ac97_pcm_in);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int s3c_ac97_mic_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
samsung_asoc_init_dma_data(dai, NULL, &s3c_ac97_mic_in);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_dai_driver s3c_ac97_dai[] = {
|
|
[S3C_AC97_DAI_PCM] = {
|
|
.name = "samsung-ac97",
|
|
.bus_control = true,
|
|
.playback = {
|
|
.stream_name = "AC97 Playback",
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_8000_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
|
.capture = {
|
|
.stream_name = "AC97 Capture",
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_8000_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
|
.probe = s3c_ac97_dai_probe,
|
|
.ops = &s3c_ac97_dai_ops,
|
|
},
|
|
[S3C_AC97_DAI_MIC] = {
|
|
.name = "samsung-ac97-mic",
|
|
.bus_control = true,
|
|
.capture = {
|
|
.stream_name = "AC97 Mic Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 1,
|
|
.rates = SNDRV_PCM_RATE_8000_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
|
.probe = s3c_ac97_mic_dai_probe,
|
|
.ops = &s3c_ac97_mic_dai_ops,
|
|
},
|
|
};
|
|
|
|
static const struct snd_soc_component_driver s3c_ac97_component = {
|
|
.name = "s3c-ac97",
|
|
};
|
|
|
|
static int s3c_ac97_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
|
|
struct s3c_audio_pdata *ac97_pdata;
|
|
int ret;
|
|
|
|
ac97_pdata = pdev->dev.platform_data;
|
|
if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
|
|
dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check for availability of necessary resource */
|
|
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
|
if (!dmatx_res) {
|
|
dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
|
if (!dmarx_res) {
|
|
dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
|
|
if (!dmamic_res) {
|
|
dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!irq_res) {
|
|
dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
s3c_ac97.regs = devm_ioremap_resource(&pdev->dev, mem_res);
|
|
if (IS_ERR(s3c_ac97.regs))
|
|
return PTR_ERR(s3c_ac97.regs);
|
|
|
|
s3c_ac97_pcm_out.channel = dmatx_res->start;
|
|
s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
|
|
s3c_ac97_pcm_in.channel = dmarx_res->start;
|
|
s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
|
|
s3c_ac97_mic_in.channel = dmamic_res->start;
|
|
s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
|
|
|
|
init_completion(&s3c_ac97.done);
|
|
mutex_init(&s3c_ac97.lock);
|
|
|
|
s3c_ac97.ac97_clk = devm_clk_get(&pdev->dev, "ac97");
|
|
if (IS_ERR(s3c_ac97.ac97_clk)) {
|
|
dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n");
|
|
ret = -ENODEV;
|
|
goto err2;
|
|
}
|
|
clk_prepare_enable(s3c_ac97.ac97_clk);
|
|
|
|
if (ac97_pdata->cfg_gpio(pdev)) {
|
|
dev_err(&pdev->dev, "Unable to configure gpio\n");
|
|
ret = -EINVAL;
|
|
goto err3;
|
|
}
|
|
|
|
ret = request_irq(irq_res->start, s3c_ac97_irq,
|
|
0, "AC97", NULL);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "ac97: interrupt request failed.\n");
|
|
goto err4;
|
|
}
|
|
|
|
ret = snd_soc_set_ac97_ops(&s3c_ac97_ops);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
|
|
goto err4;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev, &s3c_ac97_component,
|
|
s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
|
|
if (ret)
|
|
goto err5;
|
|
|
|
ret = samsung_asoc_dma_platform_register(&pdev->dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
|
|
goto err5;
|
|
}
|
|
|
|
return 0;
|
|
err5:
|
|
free_irq(irq_res->start, NULL);
|
|
err4:
|
|
err3:
|
|
clk_disable_unprepare(s3c_ac97.ac97_clk);
|
|
err2:
|
|
snd_soc_set_ac97_ops(NULL);
|
|
return ret;
|
|
}
|
|
|
|
static int s3c_ac97_remove(struct platform_device *pdev)
|
|
{
|
|
struct resource *irq_res;
|
|
|
|
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (irq_res)
|
|
free_irq(irq_res->start, NULL);
|
|
|
|
clk_disable_unprepare(s3c_ac97.ac97_clk);
|
|
snd_soc_set_ac97_ops(NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver s3c_ac97_driver = {
|
|
.probe = s3c_ac97_probe,
|
|
.remove = s3c_ac97_remove,
|
|
.driver = {
|
|
.name = "samsung-ac97",
|
|
},
|
|
};
|
|
|
|
module_platform_driver(s3c_ac97_driver);
|
|
|
|
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
|
|
MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:samsung-ac97");
|