423 lines
9.3 KiB
C
423 lines
9.3 KiB
C
/*
|
|
* espresso7420_dummy.c
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <sound/soc.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/initval.h>
|
|
|
|
/*#include <mach/regs-pmu.h>*/
|
|
|
|
#include "i2s.h"
|
|
#include "i2s-regs.h"
|
|
|
|
/* ESPRESSO use CLKOUT from AP */
|
|
#define ESPRESSO_MCLK_FREQ 24000000
|
|
#define ESPRESSO_AUD_PLL_FREQ 491520000
|
|
|
|
static bool clkout_enabled;
|
|
static struct snd_soc_card espresso;
|
|
|
|
static void espresso_enable_mclk(bool on)
|
|
{
|
|
pr_debug("%s: %s\n", __func__, on ? "on" : "off");
|
|
|
|
clkout_enabled = on;
|
|
/*
|
|
writel(on ? 0x1000 : 0x1001, EXYNOS_PMU_PMU_DEBUG);
|
|
*/
|
|
}
|
|
#if 0 /* later */
|
|
static int set_aud_pll_rate(unsigned long rate)
|
|
{
|
|
struct clk *fout_aud_pll;
|
|
|
|
fout_aud_pll = clk_get(espresso.dev, "aud_pll");
|
|
if (IS_ERR(fout_aud_pll)) {
|
|
printk(KERN_ERR "%s: failed to get fout_aud_pll\n", __func__);
|
|
return PTR_ERR(fout_aud_pll);
|
|
}
|
|
|
|
if (rate == clk_get_rate(fout_aud_pll))
|
|
goto out;
|
|
|
|
rate += 20; /* margin */
|
|
clk_set_rate(fout_aud_pll, rate);
|
|
pr_debug("%s: aud_pll rate = %ld\n",
|
|
__func__, clk_get_rate(fout_aud_pll));
|
|
out:
|
|
clk_put(fout_aud_pll);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
/*
|
|
* ESPRESSO I2S DAI operations. (AP master)
|
|
*/
|
|
static int espresso_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
int pll, div, sclk, bfs, psr, rfs, ret;
|
|
unsigned long rclk;
|
|
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_U24:
|
|
case SNDRV_PCM_FORMAT_S24:
|
|
bfs = 48;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_U16_LE:
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
bfs = 32;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (params_rate(params)) {
|
|
case 16000:
|
|
case 22050:
|
|
case 24000:
|
|
case 32000:
|
|
case 44100:
|
|
case 48000:
|
|
case 88200:
|
|
case 96000:
|
|
if (bfs == 48)
|
|
rfs = 384;
|
|
else
|
|
rfs = 256;
|
|
break;
|
|
case 64000:
|
|
rfs = 384;
|
|
break;
|
|
case 8000:
|
|
case 11025:
|
|
case 12000:
|
|
if (bfs == 48)
|
|
rfs = 768;
|
|
else
|
|
rfs = 512;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
rclk = params_rate(params) * rfs;
|
|
|
|
switch (rclk) {
|
|
case 4096000:
|
|
case 5644800:
|
|
case 6144000:
|
|
case 8467200:
|
|
case 9216000:
|
|
psr = 8;
|
|
break;
|
|
case 8192000:
|
|
case 11289600:
|
|
case 12288000:
|
|
case 16934400:
|
|
case 18432000:
|
|
psr = 4;
|
|
break;
|
|
case 22579200:
|
|
case 24576000:
|
|
case 33868800:
|
|
case 36864000:
|
|
psr = 2;
|
|
break;
|
|
case 67737600:
|
|
case 73728000:
|
|
psr = 1;
|
|
break;
|
|
default:
|
|
printk("Not yet supported!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set AUD_PLL frequency */
|
|
sclk = rclk * psr;
|
|
for (div = 2; div <= 16; div++) {
|
|
if (sclk * div > ESPRESSO_AUD_PLL_FREQ)
|
|
break;
|
|
}
|
|
pll = sclk * (div - 1);
|
|
// later set_aud_pll_rate(pll);
|
|
|
|
/* CLKOUT(XXTI) for Codec MCLK */
|
|
espresso_enable_mclk(true);
|
|
|
|
/* Set CPU DAI configuration */
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
|
|
| SND_SOC_DAIFMT_NB_NF
|
|
| SND_SOC_DAIFMT_CBS_CFS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
|
|
0, SND_SOC_CLOCK_OUT);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
|
|
0, MOD_OPCLK_PCLK);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_1, 0, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_ops espresso_ops = {
|
|
.hw_params = espresso_hw_params,
|
|
};
|
|
static const struct snd_soc_component_driver dummy_cmpnt = {
|
|
.name = "espresso-audio",
|
|
};
|
|
static struct snd_soc_dai_driver dummy_ext_dai[] = {
|
|
{
|
|
.name = "dummy-ext voice call",
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rate_min = 8000,
|
|
.rate_max = 48000,
|
|
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
|
SNDRV_PCM_RATE_48000),
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rate_min = 8000,
|
|
.rate_max = 48000,
|
|
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
|
SNDRV_PCM_RATE_48000),
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
},
|
|
},
|
|
{
|
|
.name = "dummy-ext bluetooth sco",
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rate_min = 8000,
|
|
.rate_max = 16000,
|
|
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rate_min = 8000,
|
|
.rate_max = 16000,
|
|
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
},
|
|
},
|
|
};
|
|
|
|
|
|
static struct snd_soc_dai_link espresso_dai[] = {
|
|
{ /* Primary DAI i/f */
|
|
.name = "playback-pri",
|
|
.stream_name = "i2s0-pri",
|
|
.codec_dai_name = "dummy-aif3",
|
|
.ops = &espresso_ops,
|
|
},
|
|
{ /* Secondary DAI i/f */
|
|
.name = "playback-sec",
|
|
.stream_name = "i2s0-sec",
|
|
.cpu_dai_name = "samsung-i2s-sec",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif3",
|
|
.ops = &espresso_ops,
|
|
},
|
|
{ /* voice call */
|
|
.name = "baseband",
|
|
.stream_name = "dummy-ext voice call",
|
|
.cpu_dai_name = "dummy-ext voice call",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif2",
|
|
.ops = &espresso_ops,
|
|
.ignore_suspend = 1,
|
|
},
|
|
{ /* eax0 playback */
|
|
.name = "playback-eax0",
|
|
.stream_name = "eax0",
|
|
.cpu_dai_name = "samsung-eax.0",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif1",
|
|
.ops = &espresso_ops,
|
|
},
|
|
{ /* eax1 playback */
|
|
.name = "playback-eax1",
|
|
.stream_name = "eax1",
|
|
.cpu_dai_name = "samsung-eax.1",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif1",
|
|
.ops = &espresso_ops,
|
|
},
|
|
{ /* eax2 playback */
|
|
.name = "playback-eax2",
|
|
.stream_name = "eax2",
|
|
.cpu_dai_name = "samsung-eax.2",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif1",
|
|
.ops = &espresso_ops,
|
|
},
|
|
{ /* eax3 playback */
|
|
.name = "playback-eax3",
|
|
.stream_name = "eax3",
|
|
.cpu_dai_name = "samsung-eax.3",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif1",
|
|
.ops = &espresso_ops,
|
|
},
|
|
#if defined(CONFIG_SND_SAMSUNG_COMPR)
|
|
{ /* compress playback */
|
|
.name = "playback offload",
|
|
.stream_name = "i2s0-compr",
|
|
.cpu_dai_name = "samsung-i2s-compr",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif1",
|
|
.ops = &espresso_ops,
|
|
},
|
|
#endif
|
|
{ /* compress capture for visualizer */
|
|
.name = "compress capture",
|
|
.stream_name = "compr-cap",
|
|
.codec_dai_name = "dummy-aif1",
|
|
},
|
|
{ /* bluetooth sco */
|
|
.name = "bluetooth sco",
|
|
.stream_name = "dummy-ext bluetooth sco",
|
|
.cpu_dai_name = "dummy-ext bluetooth sco",
|
|
.platform_name = "snd-soc-dummy",
|
|
.codec_dai_name = "dummy-aif3",
|
|
.ops = &espresso_ops,
|
|
.ignore_suspend = 1,
|
|
},
|
|
};
|
|
|
|
static int espresso_suspend_post(struct snd_soc_card *card)
|
|
{
|
|
espresso_enable_mclk(false);
|
|
return 0;
|
|
}
|
|
|
|
static int espresso_resume_pre(struct snd_soc_card *card)
|
|
{
|
|
espresso_enable_mclk(true);
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_card espresso = {
|
|
.name = "ESPRESSO-I2S",
|
|
.owner = THIS_MODULE,
|
|
.suspend_post = espresso_suspend_post,
|
|
.resume_pre = espresso_resume_pre,
|
|
.dai_link = espresso_dai,
|
|
.num_links = ARRAY_SIZE(espresso_dai),
|
|
};
|
|
|
|
static int espresso_audio_probe(struct platform_device *pdev)
|
|
{
|
|
int n, ret;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct snd_soc_card *card = &espresso;
|
|
|
|
card->dev = &pdev->dev;
|
|
|
|
ret = snd_soc_register_component(card->dev, &dummy_cmpnt,
|
|
dummy_ext_dai, ARRAY_SIZE(dummy_ext_dai));
|
|
if (ret != 0)
|
|
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
|
|
|
|
for (n = 0; np && n < ARRAY_SIZE(espresso_dai); n++) {
|
|
if (!espresso_dai[n].cpu_dai_name) {
|
|
espresso_dai[n].cpu_of_node = of_parse_phandle(np,
|
|
"samsung,audio-cpu", n);
|
|
|
|
if (!espresso_dai[n].cpu_of_node) {
|
|
dev_err(&pdev->dev, "Property "
|
|
"'samsung,audio-cpu' missing or invalid\n");
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (!espresso_dai[n].platform_name)
|
|
espresso_dai[n].platform_of_node = espresso_dai[n].cpu_of_node;
|
|
|
|
espresso_dai[n].codec_name = NULL;
|
|
espresso_dai[n].codec_of_node = of_parse_phandle(np,
|
|
"samsung,audio-codec", n);
|
|
if (!espresso_dai[0].codec_of_node) {
|
|
dev_err(&pdev->dev,
|
|
"Property 'samsung,audio-codec' missing or invalid\n");
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
|
|
ret = snd_soc_register_card(card);
|
|
|
|
if (ret)
|
|
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int espresso_audio_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
|
|
snd_soc_unregister_card(card);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id samsung_dummy_of_match[] = {
|
|
{ .compatible = "samsung,universal-dummy", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, samsung_dummy_of_match);
|
|
#endif /* CONFIG_OF */
|
|
|
|
static struct platform_driver espresso_audio_driver = {
|
|
.driver = {
|
|
.name = "espresso-audio",
|
|
.owner = THIS_MODULE,
|
|
.pm = &snd_soc_pm_ops,
|
|
.of_match_table = of_match_ptr(samsung_dummy_of_match),
|
|
},
|
|
.probe = espresso_audio_probe,
|
|
.remove = espresso_audio_remove,
|
|
};
|
|
|
|
module_platform_driver(espresso_audio_driver);
|
|
|
|
MODULE_DESCRIPTION("ALSA SoC UNIVERSAL8890 DUMMY");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:espresso-audio");
|