465 lines
12 KiB
C
465 lines
12 KiB
C
/*
|
|
* sound\soc\sunxi\snd_sunxi_mach.c
|
|
* (C) Copyright 2021-2025
|
|
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* Dby <dby@allwinnertech.com>
|
|
*
|
|
* based on ${LINUX}/sound/soc/generic/simple-card.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 <sound/soc.h>
|
|
|
|
#include "snd_sunxi_log.h"
|
|
#include "snd_sunxi_mach.h"
|
|
|
|
#define HLOG "MACH"
|
|
#define DAI "sound-dai"
|
|
#define CELL "#sound-dai-cells"
|
|
#define PREFIX "soundcard-mach,"
|
|
|
|
#define DRV_NAME "sunxi-snd-mach"
|
|
|
|
static void asoc_simple_shutdown(struct snd_pcm_substream *substream)
|
|
{
|
|
}
|
|
|
|
static int asoc_simple_startup(struct snd_pcm_substream *substream)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int asoc_simple_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 *codec_dai = rtd->codec_dai;
|
|
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
|
struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, rtd->num);
|
|
struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num);
|
|
struct asoc_simple_dai *dais = priv->dais;
|
|
unsigned int mclk;
|
|
unsigned int cpu_pll_clk, codec_pll_clk;
|
|
unsigned int cpu_bclk_ratio, codec_bclk_ratio;
|
|
unsigned int freq_point;
|
|
int cpu_clk_div, codec_clk_div;
|
|
int ret = 0;
|
|
|
|
switch (params_rate(params)) {
|
|
case 8000:
|
|
case 12000:
|
|
case 16000:
|
|
case 24000:
|
|
case 32000:
|
|
case 48000:
|
|
case 64000:
|
|
case 96000:
|
|
case 192000:
|
|
freq_point = 24576000;
|
|
break;
|
|
case 11025:
|
|
case 22050:
|
|
case 44100:
|
|
case 88200:
|
|
case 176400:
|
|
freq_point = 22579200;
|
|
break;
|
|
default:
|
|
SND_LOG_ERR(HLOG, "Invalid rate %d\n", params_rate(params));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* for cpudai pll clk */
|
|
cpu_pll_clk = freq_point * dai_props->cpu_pll_fs;
|
|
codec_pll_clk = freq_point * dai_props->codec_pll_fs;
|
|
cpu_clk_div = cpu_pll_clk / params_rate(params);
|
|
codec_clk_div = codec_pll_clk / params_rate(params);
|
|
SND_LOG_DEBUG(HLOG, "freq point : %u\n", freq_point);
|
|
SND_LOG_DEBUG(HLOG, "cpu pllclk : %u\n", cpu_pll_clk);
|
|
SND_LOG_DEBUG(HLOG, "codec pllclk : %u\n", codec_pll_clk);
|
|
SND_LOG_DEBUG(HLOG, "cpu clk_div : %u\n", cpu_clk_div);
|
|
SND_LOG_DEBUG(HLOG, "codec clk_div: %u\n", codec_clk_div);
|
|
|
|
if (cpu_dai->driver->ops->set_pll) {
|
|
ret = snd_soc_dai_set_pll(cpu_dai, substream->stream, 0,
|
|
cpu_pll_clk, cpu_pll_clk);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu_dai set pllclk failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_pll) {
|
|
ret = snd_soc_dai_set_pll(codec_dai, substream->stream, 0,
|
|
codec_pll_clk, codec_pll_clk);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "codec_dai set pllclk failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (cpu_dai->driver->ops->set_clkdiv) {
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, cpu_clk_div);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu_dai set clk_div failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_clkdiv) {
|
|
ret = snd_soc_dai_set_clkdiv(codec_dai, 0, codec_clk_div);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cadec_dai set clk_div failed.\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* use for tdm only */
|
|
if (!(dais->slots && dais->slot_width))
|
|
return 0;
|
|
|
|
/* for cpudai & codecdai mclk */
|
|
if (dai_props->mclk_fp)
|
|
mclk = (freq_point >> 1) * dai_props->mclk_fs;
|
|
else
|
|
mclk = params_rate(params) * dai_props->mclk_fs;
|
|
cpu_bclk_ratio = cpu_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
|
|
codec_bclk_ratio = codec_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
|
|
SND_LOG_DEBUG(HLOG, "mclk : %u\n", mclk);
|
|
SND_LOG_DEBUG(HLOG, "cpu_bclk_ratio : %u\n", cpu_bclk_ratio);
|
|
SND_LOG_DEBUG(HLOG, "codec_bclk_ratio: %u\n", codec_bclk_ratio);
|
|
|
|
if (cpu_dai->driver->ops->set_sysclk) {
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu_dai set sysclk(mclk) failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_sysclk) {
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cadec_dai set sysclk(mclk) failed.\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (cpu_dai->driver->ops->set_bclk_ratio) {
|
|
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, cpu_bclk_ratio);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu_dai set bclk failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_bclk_ratio) {
|
|
ret = snd_soc_dai_set_bclk_ratio(codec_dai, codec_bclk_ratio);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "codec_dai set bclk failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (cpu_dai->driver->ops->set_fmt) {
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, dai_link->dai_fmt);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu dai set fmt failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_fmt) {
|
|
ret = snd_soc_dai_set_fmt(codec_dai, dai_link->dai_fmt);
|
|
if (ret) {
|
|
SND_LOG_WARN(HLOG, "codec dai set fmt failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (cpu_dai->driver->ops->set_tdm_slot) {
|
|
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, dais->slots, dais->slot_width);
|
|
if (ret) {
|
|
SND_LOG_ERR(HLOG, "cpu dai set tdm slot failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
if (codec_dai->driver->ops->set_tdm_slot) {
|
|
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, dais->slots, dais->slot_width);
|
|
if (ret) {
|
|
SND_LOG_WARN(HLOG, "codec dai set tdm slot failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_ops simple_ops = {
|
|
.startup = asoc_simple_startup,
|
|
.shutdown = asoc_simple_shutdown,
|
|
.hw_params = asoc_simple_hw_params,
|
|
};
|
|
|
|
static int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
int i;
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
struct snd_soc_dapm_context *dapm = &codec->component.dapm;
|
|
struct snd_soc_card *card = rtd->card;
|
|
const struct snd_kcontrol_new *controls = card->controls;
|
|
|
|
for (i = 0; i < card->num_controls; i++)
|
|
if (controls[i].info == snd_soc_dapm_info_pin_switch)
|
|
snd_soc_dapm_disable_pin(dapm,
|
|
(const char *)controls[i].private_value);
|
|
|
|
if (card->num_controls)
|
|
snd_soc_dapm_sync(dapm);
|
|
|
|
/* snd_soc_dai_set_sysclk(); */
|
|
/* snd_soc_dai_set_tdm_slot(); */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int simple_dai_link_of(struct device_node *node,
|
|
struct asoc_simple_priv *priv)
|
|
{
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, 0);
|
|
struct simple_dai_props *dai_props = simple_priv_to_props(priv, 0);
|
|
struct device_node *top_np = NULL;
|
|
struct device_node *cpu = NULL;
|
|
struct device_node *plat = NULL;
|
|
struct device_node *codec = NULL;
|
|
char prop[128];
|
|
char *prefix = "";
|
|
int ret, single_cpu;
|
|
|
|
SND_LOG_DEBUG(HLOG, "\n");
|
|
|
|
prefix = PREFIX;
|
|
top_np = node;
|
|
|
|
snprintf(prop, sizeof(prop), "%scpu", prefix);
|
|
cpu = of_get_child_by_name(top_np, prop);
|
|
if (!cpu) {
|
|
ret = -EINVAL;
|
|
SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
|
|
goto dai_link_of_err;
|
|
}
|
|
|
|
snprintf(prop, sizeof(prop), "%splat", prefix);
|
|
plat = of_get_child_by_name(top_np, prop);
|
|
|
|
snprintf(prop, sizeof(prop), "%scodec", prefix);
|
|
codec = of_get_child_by_name(top_np, prop);
|
|
if (!codec) {
|
|
ret = -EINVAL;
|
|
SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
|
|
goto dai_link_of_err;
|
|
}
|
|
|
|
ret = asoc_simple_parse_daifmt(top_np, codec, prefix, &dai_link->dai_fmt);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
/* sunxi: parse stream direction
|
|
* ex1)
|
|
* top_node {
|
|
* PREFIXplayback-only;
|
|
* }
|
|
* ex2)
|
|
* top_node {
|
|
* PREFIXcapture-only;
|
|
* }
|
|
*/
|
|
ret = asoc_simple_parse_daistream(top_np, prefix, dai_link);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
/* sunxi: parse slot-num & slot-width
|
|
* ex)
|
|
* top_node {
|
|
* PREFIXplayslot-num = <x>;
|
|
* PREFIXplayslot-width = <x>;
|
|
* }
|
|
*/
|
|
ret = asoc_simple_parse_tdm_slot(top_np, prefix, priv->dais);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
ret = asoc_simple_parse_cpu(cpu, dai_link, DAI, CELL, &single_cpu);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
ret = asoc_simple_parse_codec(codec, dai_link, DAI, CELL);
|
|
if (ret < 0) {
|
|
if (ret == -EPROBE_DEFER)
|
|
goto dai_link_of_err;
|
|
dai_link->codec_name = "snd-soc-dummy";
|
|
dai_link->codec_dai_name = "snd-soc-dummy-dai";
|
|
/* dai_link->codec_name = "sunxi-dummy-codec"; */
|
|
/* dai_link->codec_dai_name = "sunxi-dummy-codec-dai"; */
|
|
SND_LOG_DEBUG(HLOG, "use dummy codec for simple card.\n");
|
|
}
|
|
|
|
ret = asoc_simple_parse_platform(plat, dai_link, DAI, CELL);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
/* sunxi: parse pll-fs & mclk-fs
|
|
* ex)
|
|
* top_node {
|
|
* PREFIXcpu {
|
|
* PREFIXpll-fs = <x>;
|
|
* PREFIXmclk-fs = <x>;
|
|
* }
|
|
* }
|
|
*/
|
|
ret = asoc_simple_parse_tdm_clk(cpu, codec, prefix, dai_props);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
ret = asoc_simple_canonicalize_platform(dai_link);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
ret = asoc_simple_set_dailink_name(dev, dai_link,
|
|
"%s-%s",
|
|
dai_link->cpu_dai_name,
|
|
dai_link->codec_dai_name);
|
|
if (ret < 0)
|
|
goto dai_link_of_err;
|
|
|
|
dai_link->ops = &simple_ops;
|
|
dai_link->init = asoc_simple_dai_init;
|
|
asoc_simple_canonicalize_cpu(dai_link, single_cpu);
|
|
|
|
SND_LOG_DEBUG(HLOG, "name : %s\n", dai_link->stream_name);
|
|
SND_LOG_DEBUG(HLOG, "format : %x\n", dai_link->dai_fmt);
|
|
SND_LOG_DEBUG(HLOG, "cpu : %s\n", dai_link->cpu_dai_name);
|
|
SND_LOG_DEBUG(HLOG, "codec : %s\n", dai_link->codec_dai_name);
|
|
|
|
dai_link_of_err:
|
|
of_node_put(cpu);
|
|
of_node_put(plat);
|
|
of_node_put(codec);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int simple_parse_of(struct asoc_simple_priv *priv)
|
|
{
|
|
int ret;
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct snd_soc_card *card = simple_priv_to_card(priv);
|
|
struct device_node *top_np = dev->of_node;
|
|
|
|
SND_LOG_DEBUG(HLOG, "\n");
|
|
|
|
if (!top_np)
|
|
return -EINVAL;
|
|
|
|
/* DAPM widgets */
|
|
ret = asoc_simple_parse_widgets(card, PREFIX);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* DAPM routes */
|
|
ret = asoc_simple_parse_routing(card, PREFIX);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* DAPM pin_switches */
|
|
ret = asoc_simple_parse_pin_switches(card, PREFIX);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* For single DAI link & old style of DT node */
|
|
ret = simple_dai_link_of(top_np, priv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = asoc_simple_parse_card_name(card, PREFIX);
|
|
return ret;
|
|
}
|
|
|
|
static int simple_soc_probe(struct snd_soc_card *card)
|
|
{
|
|
SND_LOG_DEBUG(HLOG, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int asoc_simple_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *top_np = dev->of_node;
|
|
struct asoc_simple_priv *priv;
|
|
struct snd_soc_card *card;
|
|
int ret;
|
|
|
|
/* Allocate the private data and the DAI link array */
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
card = simple_priv_to_card(priv);
|
|
card->owner = THIS_MODULE;
|
|
card->dev = dev;
|
|
card->probe = simple_soc_probe;
|
|
|
|
ret = asoc_simple_init_priv(priv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (top_np && of_device_is_available(top_np)) {
|
|
ret = simple_parse_of(priv);
|
|
if (ret < 0) {
|
|
if (ret != -EPROBE_DEFER)
|
|
SND_LOG_ERR(HLOG, "parse error %d\n", ret);
|
|
goto err;
|
|
}
|
|
} else {
|
|
SND_LOG_ERR(HLOG, "simple card dts available\n");
|
|
}
|
|
|
|
snd_soc_card_set_drvdata(card, priv);
|
|
|
|
/* asoc_simple_debug_info(priv); */
|
|
ret = devm_snd_soc_register_card(dev, card);
|
|
if (ret >= 0)
|
|
return ret;
|
|
err:
|
|
asoc_simple_clean_reference(card);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int asoc_simple_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
|
|
return asoc_simple_clean_reference(card);
|
|
}
|
|
|
|
static const struct of_device_id snd_soc_sunxi_of_match[] = {
|
|
{ .compatible = "allwinner," DRV_NAME, },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, snd_soc_sunxi_of_match);
|
|
|
|
static struct platform_driver sunxi_soundcard_machine_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.pm = &snd_soc_pm_ops,
|
|
.of_match_table = snd_soc_sunxi_of_match,
|
|
},
|
|
.probe = asoc_simple_probe,
|
|
.remove = asoc_simple_remove,
|
|
};
|
|
|
|
module_platform_driver(sunxi_soundcard_machine_driver);
|
|
|
|
MODULE_AUTHOR("Dby@allwinnertech.com");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("sunxi soundcard machine");
|