forked from Minki/linux
ecb400cabb
In order to support old DTs we check the codec device node if it contains "clocks" property and only if it doesn't (which indicates an old DT) we proceed with enabling the CDCLK clock by means of the set_sysclk() callback. For new DTs which use the common clock bindings for CDCLK that clock is supposed to be handled outside the sound machine driver. Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Signed-off-by: Mark Brown <broonie@kernel.org>
181 lines
4.7 KiB
C
181 lines
4.7 KiB
C
/*
|
|
* Copyright (C) 2014 Samsung Electronics Co., Ltd.
|
|
*
|
|
* 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/of.h>
|
|
#include <linux/module.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/pcm_params.h>
|
|
#include "i2s.h"
|
|
|
|
struct odroidx2_drv_data {
|
|
const struct snd_soc_dapm_widget *dapm_widgets;
|
|
unsigned int num_dapm_widgets;
|
|
};
|
|
|
|
/* The I2S CDCLK output clock frequency for the MAX98090 codec */
|
|
#define MAX98090_MCLK 19200000
|
|
|
|
static struct snd_soc_dai_link odroidx2_dai[];
|
|
|
|
static int odroidx2_late_probe(struct snd_soc_card *card)
|
|
{
|
|
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
|
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
|
|
int ret;
|
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, MAX98090_MCLK,
|
|
SND_SOC_CLOCK_IN);
|
|
|
|
if (ret < 0 || of_find_property(odroidx2_dai[0].codec_of_node,
|
|
"clocks", NULL))
|
|
return ret;
|
|
|
|
/* Set the cpu DAI configuration in order to use CDCLK */
|
|
return snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
|
|
0, SND_SOC_CLOCK_OUT);
|
|
}
|
|
|
|
static const struct snd_soc_dapm_widget odroidx2_dapm_widgets[] = {
|
|
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
|
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
|
SND_SOC_DAPM_MIC("DMIC", NULL),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget odroidu3_dapm_widgets[] = {
|
|
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
|
SND_SOC_DAPM_SPK("Speakers", NULL),
|
|
};
|
|
|
|
static struct snd_soc_dai_link odroidx2_dai[] = {
|
|
{
|
|
.name = "MAX98090",
|
|
.stream_name = "MAX98090 PCM",
|
|
.codec_dai_name = "HiFi",
|
|
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
|
SND_SOC_DAIFMT_CBM_CFM,
|
|
}
|
|
};
|
|
|
|
static struct snd_soc_card odroidx2 = {
|
|
.owner = THIS_MODULE,
|
|
.dai_link = odroidx2_dai,
|
|
.num_links = ARRAY_SIZE(odroidx2_dai),
|
|
.fully_routed = true,
|
|
.late_probe = odroidx2_late_probe,
|
|
};
|
|
|
|
static const struct odroidx2_drv_data odroidx2_drvdata = {
|
|
.dapm_widgets = odroidx2_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(odroidx2_dapm_widgets),
|
|
};
|
|
|
|
static const struct odroidx2_drv_data odroidu3_drvdata = {
|
|
.dapm_widgets = odroidu3_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(odroidu3_dapm_widgets),
|
|
};
|
|
|
|
static const struct of_device_id odroidx2_audio_of_match[] = {
|
|
{
|
|
.compatible = "samsung,odroidx2-audio",
|
|
.data = &odroidx2_drvdata,
|
|
}, {
|
|
.compatible = "samsung,odroidu3-audio",
|
|
.data = &odroidu3_drvdata,
|
|
},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, odroidx2_audio_of_match);
|
|
|
|
static int odroidx2_audio_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *snd_node = pdev->dev.of_node;
|
|
struct snd_soc_card *card = &odroidx2;
|
|
struct device_node *i2s_node, *codec_node;
|
|
struct odroidx2_drv_data *dd;
|
|
const struct of_device_id *of_id;
|
|
int ret;
|
|
|
|
of_id = of_match_node(odroidx2_audio_of_match, snd_node);
|
|
dd = (struct odroidx2_drv_data *)of_id->data;
|
|
|
|
card->num_dapm_widgets = dd->num_dapm_widgets;
|
|
card->dapm_widgets = dd->dapm_widgets;
|
|
|
|
card->dev = &pdev->dev;
|
|
|
|
ret = snd_soc_of_parse_card_name(card, "samsung,model");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
codec_node = of_parse_phandle(snd_node, "samsung,audio-codec", 0);
|
|
if (!codec_node) {
|
|
dev_err(&pdev->dev,
|
|
"Failed parsing samsung,i2s-codec property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2s_node = of_parse_phandle(snd_node, "samsung,i2s-controller", 0);
|
|
if (!i2s_node) {
|
|
dev_err(&pdev->dev,
|
|
"Failed parsing samsung,i2s-controller property\n");
|
|
ret = -EINVAL;
|
|
goto err_put_codec_n;
|
|
}
|
|
|
|
odroidx2_dai[0].codec_of_node = codec_node;
|
|
odroidx2_dai[0].cpu_of_node = i2s_node;
|
|
odroidx2_dai[0].platform_of_node = i2s_node;
|
|
|
|
ret = snd_soc_register_card(card);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
|
ret);
|
|
goto err_put_i2s_n;
|
|
}
|
|
return 0;
|
|
|
|
err_put_i2s_n:
|
|
of_node_put(i2s_node);
|
|
err_put_codec_n:
|
|
of_node_put(codec_node);
|
|
return ret;
|
|
}
|
|
|
|
static int odroidx2_audio_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
|
|
snd_soc_unregister_card(card);
|
|
|
|
of_node_put(odroidx2_dai[0].cpu_of_node);
|
|
of_node_put(odroidx2_dai[0].codec_of_node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver odroidx2_audio_driver = {
|
|
.driver = {
|
|
.name = "odroidx2-audio",
|
|
.of_match_table = odroidx2_audio_of_match,
|
|
.pm = &snd_soc_pm_ops,
|
|
},
|
|
.probe = odroidx2_audio_probe,
|
|
.remove = odroidx2_audio_remove,
|
|
};
|
|
module_platform_driver(odroidx2_audio_driver);
|
|
|
|
MODULE_AUTHOR("Chen Zhen <zhen1.chen@samsung.com>");
|
|
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
|
|
MODULE_DESCRIPTION("ALSA SoC Odroid X2/U3 Audio Support");
|
|
MODULE_LICENSE("GPL v2");
|