2017-11-10 09:52:24 +01:00
|
|
|
/*
|
2018-04-04 11:36:18 +02:00
|
|
|
* SEEED voice card
|
|
|
|
*
|
|
|
|
* (C) Copyright 2017-2018
|
|
|
|
* Seeed Technology Co., Ltd. <www.seeedstudio.com>
|
|
|
|
*
|
|
|
|
* base on ASoC simple sound card support
|
2017-11-10 09:52:24 +01:00
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Renesas Solutions Corp.
|
|
|
|
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-03-26 10:24:12 +02:00
|
|
|
/* #undef DEBUG */
|
2018-03-30 04:37:57 +02:00
|
|
|
#include <linux/version.h>
|
2017-11-10 09:52:24 +01:00
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <sound/soc.h>
|
2018-04-04 11:36:18 +02:00
|
|
|
#include <sound/soc-dai.h>
|
|
|
|
#include <sound/simple_card_utils.h>
|
2018-11-27 10:47:44 +01:00
|
|
|
#include "ac10x.h"
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2020-04-24 03:27:58 +02:00
|
|
|
#define LINUX_VERSION_IS_GEQ(x1,x2,x3) (LINUX_VERSION_CODE >= KERNEL_VERSION(x1,x2,x3))
|
|
|
|
|
2021-10-09 01:45:09 +02:00
|
|
|
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,13,0)
|
|
|
|
#define asoc_simple_parse_clk_cpu(dev, node, dai_link, simple_dai) \
|
|
|
|
asoc_simple_parse_clk(dev, node, simple_dai, dai_link->cpus)
|
|
|
|
#define asoc_simple_parse_clk_codec(dev, node, dai_link, simple_dai) \
|
|
|
|
asoc_simple_parse_clk(dev, node, simple_dai, dai_link->codecs)
|
|
|
|
#define asoc_simple_parse_cpu(node, dai_link, is_single_link) \
|
|
|
|
asoc_simple_parse_dai(node, dai_link->cpus, is_single_link)
|
|
|
|
#define asoc_simple_parse_codec(node, dai_link) \
|
|
|
|
asoc_simple_parse_dai(node, dai_link->codecs, NULL)
|
|
|
|
#define asoc_simple_parse_platform(node, dai_link) \
|
|
|
|
asoc_simple_parse_dai(node, dai_link->platforms, NULL)
|
|
|
|
#endif
|
|
|
|
|
2018-02-07 07:02:45 +01:00
|
|
|
/*
|
|
|
|
* single codec:
|
|
|
|
* 0 - allow multi codec
|
|
|
|
* 1 - yes
|
|
|
|
*/
|
|
|
|
#define _SINGLE_CODEC 1
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data {
|
2017-11-10 09:52:24 +01:00
|
|
|
struct snd_soc_card snd_card;
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_dai_props {
|
2017-11-10 09:52:24 +01:00
|
|
|
struct asoc_simple_dai cpu_dai;
|
|
|
|
struct asoc_simple_dai codec_dai;
|
2020-08-05 03:30:53 +02:00
|
|
|
struct snd_soc_dai_link_component cpus; /* single cpu */
|
2020-08-05 02:42:57 +02:00
|
|
|
struct snd_soc_dai_link_component codecs; /* single codec */
|
2020-08-05 03:18:23 +02:00
|
|
|
struct snd_soc_dai_link_component platforms;
|
2017-11-10 09:52:24 +01:00
|
|
|
unsigned int mclk_fs;
|
|
|
|
} *dai_props;
|
|
|
|
unsigned int mclk_fs;
|
2017-11-22 08:33:59 +01:00
|
|
|
unsigned channels_playback_default;
|
|
|
|
unsigned channels_playback_override;
|
|
|
|
unsigned channels_capture_default;
|
|
|
|
unsigned channels_capture_override;
|
2017-11-10 09:52:24 +01:00
|
|
|
struct snd_soc_dai_link *dai_link;
|
2018-11-27 10:47:44 +01:00
|
|
|
#if CONFIG_AC10X_TRIG_LOCK
|
2018-04-02 09:59:17 +02:00
|
|
|
spinlock_t lock;
|
2018-11-27 10:47:44 +01:00
|
|
|
#endif
|
2018-11-08 05:33:55 +01:00
|
|
|
struct work_struct work_codec_clk;
|
|
|
|
#define TRY_STOP_MAX 3
|
|
|
|
int try_stop;
|
2017-11-10 09:52:24 +01:00
|
|
|
};
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_info {
|
|
|
|
const char *name;
|
|
|
|
const char *card;
|
|
|
|
const char *codec;
|
|
|
|
const char *platform;
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
unsigned int daifmt;
|
|
|
|
struct asoc_simple_dai cpu_dai;
|
|
|
|
struct asoc_simple_dai codec_dai;
|
|
|
|
};
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2021-04-13 01:30:58 +02:00
|
|
|
#define seeed_priv_to_card(priv) (&(priv)->snd_card)
|
2018-04-04 11:36:18 +02:00
|
|
|
#define seeed_priv_to_dev(priv) ((priv)->snd_card.dev)
|
|
|
|
#define seeed_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i))
|
|
|
|
#define seeed_priv_to_props(priv, i) ((priv)->dai_props + (i))
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
#define DAI "sound-dai"
|
|
|
|
#define CELL "#sound-dai-cells"
|
|
|
|
#define PREFIX "seeed-voice-card,"
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_startup(struct snd_pcm_substream *substream)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
|
|
|
|
struct seeed_dai_props *dai_props =
|
|
|
|
seeed_priv_to_props(priv, rtd->num);
|
2017-11-10 09:52:24 +01:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = clk_prepare_enable(dai_props->cpu_dai.clk);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = clk_prepare_enable(dai_props->codec_dai.clk);
|
|
|
|
if (ret)
|
|
|
|
clk_disable_unprepare(dai_props->cpu_dai.clk);
|
|
|
|
|
2020-08-30 20:13:59 +02:00
|
|
|
if (asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min) {
|
|
|
|
priv->channels_playback_default = asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min;
|
2017-11-22 08:33:59 +01:00
|
|
|
}
|
2020-08-30 20:13:59 +02:00
|
|
|
if (asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min) {
|
|
|
|
priv->channels_capture_default = asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min;
|
2017-11-22 08:33:59 +01:00
|
|
|
}
|
2020-08-30 20:13:59 +02:00
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_override;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_override;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_override;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_override;
|
2017-11-10 10:00:23 +01:00
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static void seeed_voice_card_shutdown(struct snd_pcm_substream *substream)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
|
|
|
|
struct seeed_dai_props *dai_props =
|
|
|
|
seeed_priv_to_props(priv, rtd->num);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2020-08-30 20:13:59 +02:00
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_default;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_default;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_default;
|
|
|
|
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_default;
|
2017-11-10 10:00:23 +01:00
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
clk_disable_unprepare(dai_props->cpu_dai.clk);
|
|
|
|
|
|
|
|
clk_disable_unprepare(dai_props->codec_dai.clk);
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_hw_params(struct snd_pcm_substream *substream,
|
2017-11-10 09:52:24 +01:00
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2020-08-30 20:13:59 +02:00
|
|
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
|
|
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
|
|
|
|
struct seeed_dai_props *dai_props =
|
|
|
|
seeed_priv_to_props(priv, rtd->num);
|
2017-11-10 09:52:24 +01:00
|
|
|
unsigned int mclk, mclk_fs = 0;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (priv->mclk_fs)
|
|
|
|
mclk_fs = priv->mclk_fs;
|
|
|
|
else if (dai_props->mclk_fs)
|
|
|
|
mclk_fs = dai_props->mclk_fs;
|
|
|
|
|
|
|
|
if (mclk_fs) {
|
|
|
|
mclk = params_rate(params) * mclk_fs;
|
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
|
|
|
|
SND_SOC_CLOCK_IN);
|
|
|
|
if (ret && ret != -ENOTSUPP)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
|
|
|
|
SND_SOC_CLOCK_OUT);
|
|
|
|
if (ret && ret != -ENOTSUPP)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-02 09:59:17 +02:00
|
|
|
#define _SET_CLOCK_CNT 2
|
2021-04-27 02:35:13 +02:00
|
|
|
static int (* _set_clock[_SET_CLOCK_CNT])(int y_start_n_stop, struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai);
|
2018-02-05 03:45:55 +01:00
|
|
|
|
2021-04-27 02:35:13 +02:00
|
|
|
int seeed_voice_card_register_set_clock(int stream, int (*set_clock)(int, struct snd_pcm_substream *, int, struct snd_soc_dai *)) {
|
2018-04-02 09:59:17 +02:00
|
|
|
if (! _set_clock[stream]) {
|
|
|
|
_set_clock[stream] = set_clock;
|
|
|
|
}
|
2018-02-05 03:45:55 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2018-04-04 11:36:18 +02:00
|
|
|
EXPORT_SYMBOL(seeed_voice_card_register_set_clock);
|
2018-01-30 12:12:47 +01:00
|
|
|
|
2018-11-08 05:33:55 +01:00
|
|
|
/*
|
|
|
|
* work_cb_codec_clk: clear audio codec inner clock.
|
|
|
|
*/
|
|
|
|
static void work_cb_codec_clk(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct seeed_card_data *priv = container_of(work, struct seeed_card_data, work_codec_clk);
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) {
|
2021-04-27 02:35:13 +02:00
|
|
|
r = r || _set_clock[SNDRV_PCM_STREAM_CAPTURE](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */
|
2018-11-08 05:33:55 +01:00
|
|
|
}
|
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) {
|
2021-04-27 02:35:13 +02:00
|
|
|
r = r || _set_clock[SNDRV_PCM_STREAM_PLAYBACK](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */
|
2018-11-08 05:33:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (r && priv->try_stop++ < TRY_STOP_MAX) {
|
|
|
|
if (0 != schedule_work(&priv->work_codec_clk)) {}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd)
|
2018-01-30 12:12:47 +01:00
|
|
|
{
|
2018-02-07 07:02:45 +01:00
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2020-08-30 20:13:59 +02:00
|
|
|
struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
|
2018-11-27 10:47:44 +01:00
|
|
|
#if CONFIG_AC10X_TRIG_LOCK
|
2018-04-02 09:59:17 +02:00
|
|
|
unsigned long flags;
|
2018-11-27 10:47:44 +01:00
|
|
|
#endif
|
2018-01-30 12:12:47 +01:00
|
|
|
int ret = 0;
|
|
|
|
|
2018-03-24 11:01:33 +01:00
|
|
|
dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d\n",
|
|
|
|
__FUNCTION__, snd_pcm_stream_str(substream), cmd,
|
2023-06-17 00:49:29 +02:00
|
|
|
dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active, dai->stream[SNDRV_PCM_STREAM_CAPTURE].active);
|
2018-01-30 12:12:47 +01:00
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
2018-11-08 05:33:55 +01:00
|
|
|
if (cancel_work_sync(&priv->work_codec_clk) != 0) {}
|
2018-11-27 10:47:44 +01:00
|
|
|
#if CONFIG_AC10X_TRIG_LOCK
|
2018-11-08 05:33:55 +01:00
|
|
|
/* I know it will degrades performance, but I have no choice */
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
2018-11-27 10:47:44 +01:00
|
|
|
#endif
|
2021-04-27 02:35:13 +02:00
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) _set_clock[SNDRV_PCM_STREAM_CAPTURE](1, substream, cmd, dai);
|
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) _set_clock[SNDRV_PCM_STREAM_PLAYBACK](1, substream, cmd, dai);
|
2018-11-27 10:47:44 +01:00
|
|
|
#if CONFIG_AC10X_TRIG_LOCK
|
2018-11-08 05:33:55 +01:00
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
2018-11-27 10:47:44 +01:00
|
|
|
#endif
|
2018-01-30 12:12:47 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
2018-03-24 11:01:33 +01:00
|
|
|
/* capture channel resync, if overrun */
|
2023-06-17 00:49:29 +02:00
|
|
|
if (dai->stream[SNDRV_PCM_STREAM_CAPTURE].active && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
2018-03-24 11:01:33 +01:00
|
|
|
break;
|
|
|
|
}
|
2018-11-08 05:33:55 +01:00
|
|
|
|
|
|
|
/* interrupt environment */
|
|
|
|
if (in_irq() || in_nmi() || in_serving_softirq()) {
|
|
|
|
priv->try_stop = 0;
|
|
|
|
if (0 != schedule_work(&priv->work_codec_clk)) {
|
|
|
|
}
|
|
|
|
} else {
|
2021-04-27 02:35:13 +02:00
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) _set_clock[SNDRV_PCM_STREAM_CAPTURE](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */
|
|
|
|
if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) _set_clock[SNDRV_PCM_STREAM_PLAYBACK](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */
|
2018-11-08 05:33:55 +01:00
|
|
|
}
|
2018-01-30 12:12:47 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
|
2021-04-23 22:03:22 +02:00
|
|
|
dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d;finished %d\n",
|
|
|
|
__FUNCTION__, snd_pcm_stream_str(substream), cmd,
|
2023-06-17 00:49:29 +02:00
|
|
|
dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active, dai->stream[SNDRV_PCM_STREAM_CAPTURE].active, ret);
|
2021-04-23 22:03:22 +02:00
|
|
|
|
2018-03-26 10:24:12 +02:00
|
|
|
return ret;
|
2018-01-30 12:12:47 +01:00
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static struct snd_soc_ops seeed_voice_card_ops = {
|
|
|
|
.startup = seeed_voice_card_startup,
|
|
|
|
.shutdown = seeed_voice_card_shutdown,
|
|
|
|
.hw_params = seeed_voice_card_hw_params,
|
|
|
|
.trigger = seeed_voice_card_trigger,
|
2017-11-10 09:52:24 +01:00
|
|
|
};
|
|
|
|
|
2020-04-26 23:48:50 +02:00
|
|
|
static int asoc_simple_parse_dai(struct device_node *node,
|
|
|
|
struct snd_soc_dai_link_component *dlc,
|
|
|
|
int *is_single_link)
|
|
|
|
{
|
|
|
|
struct of_phandle_args args;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get node via "sound-dai = <&phandle port>"
|
|
|
|
* it will be used as xxx_of_node on soc_bind_dai_link()
|
|
|
|
*/
|
|
|
|
ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FIXME
|
|
|
|
*
|
|
|
|
* Here, dlc->dai_name is pointer to CPU/Codec DAI name.
|
|
|
|
* If user unbinded CPU or Codec driver, but not for Sound Card,
|
|
|
|
* dlc->dai_name is keeping unbinded CPU or Codec
|
|
|
|
* driver's pointer.
|
|
|
|
*
|
|
|
|
* If user re-bind CPU or Codec driver again, ALSA SoC will try
|
|
|
|
* to rebind Card via snd_soc_try_rebind_card(), but because of
|
|
|
|
* above reason, it might can't bind Sound Card.
|
|
|
|
* Because Sound Card is pointing to released dai_name pointer.
|
|
|
|
*
|
|
|
|
* To avoid this rebind Card issue,
|
|
|
|
* 1) It needs to alloc memory to keep dai_name eventhough
|
|
|
|
* CPU or Codec driver was unbinded, or
|
|
|
|
* 2) user need to rebind Sound Card everytime
|
|
|
|
* if he unbinded CPU or Codec.
|
|
|
|
*/
|
|
|
|
ret = snd_soc_of_get_dai_name(node, &dlc->dai_name);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
dlc->of_node = args.np;
|
|
|
|
|
|
|
|
if (is_single_link)
|
|
|
|
*is_single_link = !args.args_count;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-08-05 05:42:41 +02:00
|
|
|
static int asoc_simple_init_dai(struct snd_soc_dai *dai,
|
|
|
|
struct asoc_simple_dai *simple_dai)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!simple_dai)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (simple_dai->sysclk) {
|
|
|
|
ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk,
|
|
|
|
simple_dai->clk_direction);
|
|
|
|
if (ret && ret != -ENOTSUPP) {
|
|
|
|
dev_err(dai->dev, "simple-card: set_sysclk error\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (simple_dai->slots) {
|
2020-09-12 02:39:19 +02:00
|
|
|
ret = snd_soc_dai_set_bclk_ratio(dai,
|
|
|
|
simple_dai->slots *
|
2020-08-05 05:42:41 +02:00
|
|
|
simple_dai->slot_width);
|
|
|
|
if (ret && ret != -ENOTSUPP) {
|
|
|
|
dev_err(dai->dev, "simple-card: set_tdm_slot error\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-04-19 13:46:26 +02:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
|
v6.0: ASoC: simple-card-utils: Move snd_soc_component_is_codec to be local
commit 28086d05ada6d03daa886aad0e469854b811311c
Author: Charles Keepax <ckeepax@opensource.cirrus.com>
Date: Thu May 19 16:43:18 2022 +0100
ASoC: simple-card-utils: Move snd_soc_component_is_codec to be local
The helper function snd_soc_component_is_codec is based off the
presence of the non_legacy_dai_naming flag. This isn't super robust
as CPU side components may also specify this flag, and indeed the
kernel already contains a couple that do. After componentisation there
isn't really a totally robust solution to identifying what is a CODEC
driver, without introducing a flag specifically for that purpose, and
really the desirable direction to move in is that the distinction
doesn't matter.
This patch does two things to try to mitigate these problems. Firstly,
now that all the other users of the helper function have been removed,
it makes the helper function local to the driver rather, than being
part of the core. This should help to discourage any new code from
being created that depends on the CODEC driver distinction. Secondly,
it updates the helper function itself to use the endianness flag
rather than the non_legacy_dai_naming flag. The endianness flag is
definitely invalid on a CPU side component, so it a more reliable
indicator that the device is definitely a CODEC. The vast majority of
buses require the CODEC to set the endianness flag, so the number of
corner cases should be fairly minimal. It is worth noting that CODECs
sending audio over SPI, or built into the CPU CODECs are potential
corner cases, however the hope is that in most cases those types of
devices do not consitute a simple audio card.
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220519154318.2153729-57-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2022-10-30 23:45:19 +01:00
|
|
|
static inline int asoc_simple_component_is_codec(struct snd_soc_component *component)
|
|
|
|
{
|
|
|
|
return component->driver->endianness;
|
|
|
|
}
|
|
|
|
|
2021-04-13 03:53:46 +02:00
|
|
|
static int asoc_simple_init_dai_link_params(struct snd_soc_pcm_runtime *rtd)
|
2021-04-13 03:50:59 +02:00
|
|
|
{
|
|
|
|
struct snd_soc_dai_link *dai_link = rtd->dai_link;
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
struct snd_soc_pcm_stream *params;
|
|
|
|
struct snd_pcm_hardware hw;
|
|
|
|
int i, ret, stream;
|
|
|
|
|
2022-10-30 23:25:19 +01:00
|
|
|
/* Only Codecs */
|
2021-04-13 03:50:59 +02:00
|
|
|
for_each_rtd_components(rtd, i, component) {
|
v6.0: ASoC: simple-card-utils: Move snd_soc_component_is_codec to be local
commit 28086d05ada6d03daa886aad0e469854b811311c
Author: Charles Keepax <ckeepax@opensource.cirrus.com>
Date: Thu May 19 16:43:18 2022 +0100
ASoC: simple-card-utils: Move snd_soc_component_is_codec to be local
The helper function snd_soc_component_is_codec is based off the
presence of the non_legacy_dai_naming flag. This isn't super robust
as CPU side components may also specify this flag, and indeed the
kernel already contains a couple that do. After componentisation there
isn't really a totally robust solution to identifying what is a CODEC
driver, without introducing a flag specifically for that purpose, and
really the desirable direction to move in is that the distinction
doesn't matter.
This patch does two things to try to mitigate these problems. Firstly,
now that all the other users of the helper function have been removed,
it makes the helper function local to the driver rather, than being
part of the core. This should help to discourage any new code from
being created that depends on the CODEC driver distinction. Secondly,
it updates the helper function itself to use the endianness flag
rather than the non_legacy_dai_naming flag. The endianness flag is
definitely invalid on a CPU side component, so it a more reliable
indicator that the device is definitely a CODEC. The vast majority of
buses require the CODEC to set the endianness flag, so the number of
corner cases should be fairly minimal. It is worth noting that CODECs
sending audio over SPI, or built into the CPU CODECs are potential
corner cases, however the hope is that in most cases those types of
devices do not consitute a simple audio card.
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220519154318.2153729-57-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2022-10-30 23:45:19 +01:00
|
|
|
if (!asoc_simple_component_is_codec(component))
|
2021-04-13 03:50:59 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Assumes the capabilities are the same for all supported streams */
|
|
|
|
for (stream = 0; stream < 2; stream++) {
|
|
|
|
ret = snd_soc_runtime_calc_hw(rtd, &hw, stream);
|
|
|
|
if (ret == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(rtd->dev, "simple-card: no valid dai_link params\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
params = devm_kzalloc(rtd->dev, sizeof(*params), GFP_KERNEL);
|
|
|
|
if (!params)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
params->formats = hw.formats;
|
|
|
|
params->rates = hw.rates;
|
|
|
|
params->rate_min = hw.rate_min;
|
|
|
|
params->rate_max = hw.rate_max;
|
|
|
|
params->channels_min = hw.channels_min;
|
|
|
|
params->channels_max = hw.channels_max;
|
|
|
|
|
2023-07-27 03:08:40 +02:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,4,0)
|
2023-07-27 02:52:33 +02:00
|
|
|
dai_link->c2c_params = params;
|
|
|
|
dai_link->num_c2c_params = 1;
|
2023-07-27 03:08:40 +02:00
|
|
|
#else
|
|
|
|
/* apparently this goes back to 5.6.x */
|
|
|
|
dai_link->params = params;
|
|
|
|
dai_link->num_params = 1;
|
|
|
|
#endif
|
2021-04-13 03:50:59 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2021-04-19 13:46:26 +02:00
|
|
|
#endif
|
2021-04-13 03:50:59 +02:00
|
|
|
|
2020-08-05 05:28:16 +02:00
|
|
|
static int seeed_voice_card_dai_init(struct snd_soc_pcm_runtime *rtd)
|
|
|
|
{
|
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
|
2020-08-30 20:13:59 +02:00
|
|
|
struct snd_soc_dai *codec = asoc_rtd_to_codec(rtd, 0);
|
|
|
|
struct snd_soc_dai *cpu = asoc_rtd_to_cpu(rtd, 0);
|
2020-08-05 05:28:16 +02:00
|
|
|
struct seeed_dai_props *dai_props =
|
|
|
|
seeed_priv_to_props(priv, rtd->num);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = asoc_simple_init_dai(codec, &dai_props->codec_dai);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = asoc_simple_init_dai(cpu, &dai_props->cpu_dai);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2021-04-19 13:46:26 +02:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
|
2021-04-13 03:53:46 +02:00
|
|
|
ret = asoc_simple_init_dai_link_params(rtd);
|
2021-04-13 03:50:59 +02:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2021-04-19 13:46:26 +02:00
|
|
|
#endif
|
2021-04-13 03:50:59 +02:00
|
|
|
|
2021-04-13 02:18:51 +02:00
|
|
|
dev_dbg(rtd->card->dev, "codec \"%s\" mapping to cpu \"%s\"\n", codec->name, cpu->name);
|
2020-08-05 05:28:16 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_dai_link_of(struct device_node *node,
|
|
|
|
struct seeed_card_data *priv,
|
2017-11-10 09:52:24 +01:00
|
|
|
int idx,
|
|
|
|
bool is_top_level_node)
|
|
|
|
{
|
2018-04-04 11:36:18 +02:00
|
|
|
struct device *dev = seeed_priv_to_dev(priv);
|
|
|
|
struct snd_soc_dai_link *dai_link = seeed_priv_to_link(priv, idx);
|
|
|
|
struct seeed_dai_props *dai_props = seeed_priv_to_props(priv, idx);
|
2017-11-10 09:52:24 +01:00
|
|
|
struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai;
|
|
|
|
struct asoc_simple_dai *codec_dai = &dai_props->codec_dai;
|
|
|
|
struct device_node *cpu = NULL;
|
|
|
|
struct device_node *plat = NULL;
|
|
|
|
struct device_node *codec = NULL;
|
|
|
|
char prop[128];
|
|
|
|
char *prefix = "";
|
|
|
|
int ret, single_cpu;
|
|
|
|
|
|
|
|
/* For single DAI link & old style of DT node */
|
|
|
|
if (is_top_level_node)
|
|
|
|
prefix = PREFIX;
|
|
|
|
|
|
|
|
snprintf(prop, sizeof(prop), "%scpu", prefix);
|
|
|
|
cpu = of_get_child_by_name(node, prop);
|
|
|
|
|
2019-05-21 06:18:25 +02:00
|
|
|
if (!cpu) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
|
|
|
|
goto dai_link_of_err;
|
|
|
|
}
|
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
snprintf(prop, sizeof(prop), "%splat", prefix);
|
|
|
|
plat = of_get_child_by_name(node, prop);
|
|
|
|
|
|
|
|
snprintf(prop, sizeof(prop), "%scodec", prefix);
|
|
|
|
codec = of_get_child_by_name(node, prop);
|
|
|
|
|
2019-05-21 06:18:25 +02:00
|
|
|
if (!codec) {
|
2017-11-10 09:52:24 +01:00
|
|
|
ret = -EINVAL;
|
|
|
|
dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
|
|
|
|
goto dai_link_of_err;
|
|
|
|
}
|
|
|
|
|
2020-04-24 04:35:45 +02:00
|
|
|
ret = asoc_simple_parse_daifmt(dev, node, codec,
|
2017-11-10 09:52:24 +01:00
|
|
|
prefix, &dai_link->dai_fmt);
|
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
|
|
|
of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs);
|
|
|
|
|
2020-04-24 05:03:19 +02:00
|
|
|
ret = asoc_simple_parse_cpu(cpu, dai_link, &single_cpu);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
2018-02-07 07:02:45 +01:00
|
|
|
#if _SINGLE_CODEC
|
2020-04-24 05:03:19 +02:00
|
|
|
ret = asoc_simple_parse_codec(codec, dai_link);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
2018-02-07 07:02:45 +01:00
|
|
|
#else
|
|
|
|
ret = snd_soc_of_get_dai_link_codecs(dev, codec, dai_link);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "parse codec info error %d\n", ret);
|
|
|
|
goto dai_link_of_err;
|
|
|
|
}
|
|
|
|
dev_dbg(dev, "dai_link num_codecs = %d\n", dai_link->num_codecs);
|
|
|
|
#endif
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2020-04-24 05:03:19 +02:00
|
|
|
ret = asoc_simple_parse_platform(plat, dai_link);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
|
|
|
ret = snd_soc_of_parse_tdm_slot(cpu, &cpu_dai->tx_slot_mask,
|
|
|
|
&cpu_dai->rx_slot_mask,
|
|
|
|
&cpu_dai->slots,
|
|
|
|
&cpu_dai->slot_width);
|
2017-11-10 10:00:23 +01:00
|
|
|
dev_dbg(dev, "cpu_dai : slot,width,tx,rx = %d,%d,%d,%d\n",
|
|
|
|
cpu_dai->slots, cpu_dai->slot_width,
|
|
|
|
cpu_dai->tx_slot_mask, cpu_dai->rx_slot_mask
|
|
|
|
);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
|
|
|
ret = snd_soc_of_parse_tdm_slot(codec, &codec_dai->tx_slot_mask,
|
|
|
|
&codec_dai->rx_slot_mask,
|
|
|
|
&codec_dai->slots,
|
|
|
|
&codec_dai->slot_width);
|
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
2018-03-30 04:37:57 +02:00
|
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0)
|
2020-08-05 06:26:01 +02:00
|
|
|
ret = asoc_simple_card_parse_clk_cpu(cpu, dai_link, cpu_dai);
|
2018-03-30 04:37:57 +02:00
|
|
|
#else
|
2020-04-24 04:35:45 +02:00
|
|
|
ret = asoc_simple_parse_clk_cpu(dev, cpu, dai_link, cpu_dai);
|
2018-03-30 04:37:57 +02:00
|
|
|
#endif
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
2018-03-30 04:37:57 +02:00
|
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0)
|
2020-08-05 06:26:01 +02:00
|
|
|
ret = asoc_simple_card_parse_clk_codec(codec, dai_link, codec_dai);
|
2018-03-30 04:37:57 +02:00
|
|
|
#else
|
2020-04-24 04:35:45 +02:00
|
|
|
ret = asoc_simple_parse_clk_codec(dev, codec, dai_link, codec_dai);
|
2018-03-30 04:37:57 +02:00
|
|
|
#endif
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
2020-04-24 04:35:45 +02:00
|
|
|
ret = asoc_simple_set_dailink_name(dev, dai_link,
|
2017-11-10 09:52:24 +01:00
|
|
|
"%s-%s",
|
2020-04-24 05:58:58 +02:00
|
|
|
dai_link->cpus->dai_name,
|
2018-02-07 07:02:45 +01:00
|
|
|
#if _SINGLE_CODEC
|
2020-04-24 05:58:58 +02:00
|
|
|
dai_link->codecs->dai_name
|
2018-02-07 07:02:45 +01:00
|
|
|
#else
|
|
|
|
dai_link->codecs[0].dai_name
|
|
|
|
#endif
|
|
|
|
);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto dai_link_of_err;
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
dai_link->ops = &seeed_voice_card_ops;
|
2020-08-05 05:28:16 +02:00
|
|
|
dai_link->init = seeed_voice_card_dai_init;
|
2017-11-10 09:52:24 +01:00
|
|
|
|
|
|
|
dev_dbg(dev, "\tname : %s\n", dai_link->stream_name);
|
|
|
|
dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt);
|
|
|
|
dev_dbg(dev, "\tcpu : %s / %d\n",
|
2020-04-24 05:58:58 +02:00
|
|
|
dai_link->cpus->dai_name,
|
2017-11-10 09:52:24 +01:00
|
|
|
dai_props->cpu_dai.sysclk);
|
|
|
|
dev_dbg(dev, "\tcodec : %s / %d\n",
|
2018-02-07 07:02:45 +01:00
|
|
|
#if _SINGLE_CODEC
|
2020-04-24 05:58:58 +02:00
|
|
|
dai_link->codecs->dai_name,
|
2018-02-07 07:02:45 +01:00
|
|
|
#else
|
|
|
|
dai_link->codecs[0].dai_name,
|
|
|
|
#endif
|
2017-11-10 09:52:24 +01:00
|
|
|
dai_props->codec_dai.sysclk);
|
|
|
|
|
2021-10-09 01:55:55 +02:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,13,0)
|
|
|
|
asoc_simple_canonicalize_cpu(dai_link->cpus, single_cpu);
|
|
|
|
#if _SINGLE_CODEC
|
|
|
|
asoc_simple_canonicalize_platform(dai_link->platforms, dai_link->cpus);
|
|
|
|
#endif
|
|
|
|
#else
|
2020-04-24 04:35:45 +02:00
|
|
|
asoc_simple_canonicalize_cpu(dai_link, single_cpu);
|
2021-04-16 19:14:01 +02:00
|
|
|
#if _SINGLE_CODEC
|
|
|
|
asoc_simple_canonicalize_platform(dai_link);
|
|
|
|
#endif
|
2021-10-09 01:55:55 +02:00
|
|
|
#endif
|
2017-11-10 09:52:24 +01:00
|
|
|
|
|
|
|
dai_link_of_err:
|
|
|
|
of_node_put(cpu);
|
|
|
|
of_node_put(codec);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_parse_aux_devs(struct device_node *node,
|
|
|
|
struct seeed_card_data *priv)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
2018-04-04 11:36:18 +02:00
|
|
|
struct device *dev = seeed_priv_to_dev(priv);
|
2017-11-10 09:52:24 +01:00
|
|
|
struct device_node *aux_node;
|
|
|
|
int i, n, len;
|
|
|
|
|
|
|
|
if (!of_find_property(node, PREFIX "aux-devs", &len))
|
|
|
|
return 0; /* Ok to have no aux-devs */
|
|
|
|
|
|
|
|
n = len / sizeof(__be32);
|
|
|
|
if (n <= 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
priv->snd_card.aux_dev = devm_kzalloc(dev,
|
|
|
|
n * sizeof(*priv->snd_card.aux_dev), GFP_KERNEL);
|
|
|
|
if (!priv->snd_card.aux_dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
aux_node = of_parse_phandle(node, PREFIX "aux-devs", i);
|
|
|
|
if (!aux_node)
|
|
|
|
return -EINVAL;
|
2020-04-24 06:13:41 +02:00
|
|
|
priv->snd_card.aux_dev[i].dlc.of_node = aux_node;
|
2017-11-10 09:52:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
priv->snd_card.num_aux_devs = n;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_parse_of(struct device_node *node,
|
|
|
|
struct seeed_card_data *priv)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
2018-04-04 11:36:18 +02:00
|
|
|
struct device *dev = seeed_priv_to_dev(priv);
|
2017-11-10 09:52:24 +01:00
|
|
|
struct device_node *dai_link;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
dai_link = of_get_child_by_name(node, PREFIX "dai-link");
|
|
|
|
|
|
|
|
/* The off-codec widgets */
|
|
|
|
if (of_property_read_bool(node, PREFIX "widgets")) {
|
|
|
|
ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card,
|
|
|
|
PREFIX "widgets");
|
|
|
|
if (ret)
|
|
|
|
goto card_parse_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DAPM routes */
|
|
|
|
if (of_property_read_bool(node, PREFIX "routing")) {
|
|
|
|
ret = snd_soc_of_parse_audio_routing(&priv->snd_card,
|
|
|
|
PREFIX "routing");
|
|
|
|
if (ret)
|
|
|
|
goto card_parse_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Factor to mclk, used in hw_params() */
|
|
|
|
of_property_read_u32(node, PREFIX "mclk-fs", &priv->mclk_fs);
|
|
|
|
|
|
|
|
/* Single/Muti DAI link(s) & New style of DT node */
|
|
|
|
if (dai_link) {
|
|
|
|
struct device_node *np = NULL;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for_each_child_of_node(node, np) {
|
|
|
|
dev_dbg(dev, "\tlink %d:\n", i);
|
2018-04-04 11:36:18 +02:00
|
|
|
ret = seeed_voice_card_dai_link_of(np, priv,
|
2017-11-10 09:52:24 +01:00
|
|
|
i, false);
|
|
|
|
if (ret < 0) {
|
|
|
|
of_node_put(np);
|
|
|
|
goto card_parse_end;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* For single DAI link & old style of DT node */
|
2018-04-04 11:36:18 +02:00
|
|
|
ret = seeed_voice_card_dai_link_of(node, priv, 0, true);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto card_parse_end;
|
|
|
|
}
|
|
|
|
|
2020-04-24 04:35:45 +02:00
|
|
|
ret = asoc_simple_parse_card_name(&priv->snd_card, PREFIX);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto card_parse_end;
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
ret = seeed_voice_card_parse_aux_devs(node, priv);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2017-11-22 08:33:59 +01:00
|
|
|
priv->channels_playback_default = 0;
|
|
|
|
priv->channels_playback_override = 2;
|
|
|
|
priv->channels_capture_default = 0;
|
|
|
|
priv->channels_capture_override = 2;
|
|
|
|
of_property_read_u32(node, PREFIX "channels-playback-default",
|
|
|
|
&priv->channels_playback_default);
|
|
|
|
of_property_read_u32(node, PREFIX "channels-playback-override",
|
|
|
|
&priv->channels_playback_override);
|
|
|
|
of_property_read_u32(node, PREFIX "channels-capture-default",
|
|
|
|
&priv->channels_capture_default);
|
|
|
|
of_property_read_u32(node, PREFIX "channels-capture-override",
|
|
|
|
&priv->channels_capture_override);
|
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
card_parse_end:
|
|
|
|
of_node_put(dai_link);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-13 00:12:42 +02:00
|
|
|
#ifdef DEBUG
|
2021-04-13 01:30:58 +02:00
|
|
|
inline void seeed_debug_dai(struct seeed_card_data *priv,
|
2021-04-13 00:12:42 +02:00
|
|
|
char *name,
|
|
|
|
struct asoc_simple_dai *dai)
|
|
|
|
{
|
2021-04-13 01:30:58 +02:00
|
|
|
struct device *dev = seeed_priv_to_dev(priv);
|
2021-04-13 00:12:42 +02:00
|
|
|
|
|
|
|
if (dai->name)
|
|
|
|
dev_dbg(dev, "%s dai name = %s\n",
|
|
|
|
name, dai->name);
|
|
|
|
if (dai->sysclk)
|
|
|
|
dev_dbg(dev, "%s sysclk = %d\n",
|
|
|
|
name, dai->sysclk);
|
|
|
|
|
|
|
|
dev_dbg(dev, "%s direction = %s\n",
|
|
|
|
name, dai->clk_direction ? "OUT" : "IN");
|
|
|
|
|
|
|
|
if (dai->slots)
|
|
|
|
dev_dbg(dev, "%s slots = %d\n", name, dai->slots);
|
|
|
|
if (dai->slot_width)
|
|
|
|
dev_dbg(dev, "%s slot width = %d\n", name, dai->slot_width);
|
|
|
|
if (dai->tx_slot_mask)
|
|
|
|
dev_dbg(dev, "%s tx slot mask = %d\n", name, dai->tx_slot_mask);
|
|
|
|
if (dai->rx_slot_mask)
|
|
|
|
dev_dbg(dev, "%s rx slot mask = %d\n", name, dai->rx_slot_mask);
|
|
|
|
if (dai->clk)
|
|
|
|
dev_dbg(dev, "%s clk %luHz\n", name, clk_get_rate(dai->clk));
|
|
|
|
}
|
|
|
|
|
2021-04-13 01:30:58 +02:00
|
|
|
inline void seeed_debug_info(struct seeed_card_data *priv)
|
2021-04-13 00:12:42 +02:00
|
|
|
{
|
2021-04-27 16:48:33 +02:00
|
|
|
struct snd_soc_card *card = seeed_priv_to_card(priv);
|
2021-04-13 01:30:58 +02:00
|
|
|
struct device *dev = seeed_priv_to_dev(priv);
|
2021-04-13 00:12:42 +02:00
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (card->name)
|
|
|
|
dev_dbg(dev, "Card Name: %s\n", card->name);
|
|
|
|
|
|
|
|
for (i = 0; i < card->num_links; i++) {
|
2021-04-13 01:30:58 +02:00
|
|
|
struct seeed_dai_props *props = seeed_priv_to_props(priv, i);
|
|
|
|
struct snd_soc_dai_link *link = seeed_priv_to_link(priv, i);
|
2021-04-13 00:12:42 +02:00
|
|
|
|
|
|
|
dev_dbg(dev, "DAI%d\n", i);
|
|
|
|
|
2021-04-13 01:30:58 +02:00
|
|
|
seeed_debug_dai(priv, "cpu", &props->cpu_dai);
|
|
|
|
seeed_debug_dai(priv, "codec", &props->codec_dai);
|
2021-04-13 00:12:42 +02:00
|
|
|
|
|
|
|
if (link->name)
|
|
|
|
dev_dbg(dev, "dai name = %s\n", link->name);
|
|
|
|
|
|
|
|
dev_dbg(dev, "dai format = %04x\n", link->dai_fmt);
|
|
|
|
|
2021-04-13 01:30:58 +02:00
|
|
|
/*
|
2021-04-13 00:12:42 +02:00
|
|
|
if (props->adata.convert_rate)
|
|
|
|
dev_dbg(dev, "convert_rate = %d\n",
|
|
|
|
props->adata.convert_rate);
|
|
|
|
if (props->adata.convert_channels)
|
|
|
|
dev_dbg(dev, "convert_channels = %d\n",
|
|
|
|
props->adata.convert_channels);
|
|
|
|
if (props->codec_conf && props->codec_conf->name_prefix)
|
|
|
|
dev_dbg(dev, "name prefix = %s\n",
|
|
|
|
props->codec_conf->name_prefix);
|
2021-04-13 01:30:58 +02:00
|
|
|
*/
|
2021-04-13 00:12:42 +02:00
|
|
|
if (props->mclk_fs)
|
|
|
|
dev_dbg(dev, "mclk-fs = %d\n",
|
|
|
|
props->mclk_fs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2021-04-13 01:30:58 +02:00
|
|
|
#define seeed_debug_info(priv)
|
2021-04-13 00:12:42 +02:00
|
|
|
#endif /* DEBUG */
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_probe(struct platform_device *pdev)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_data *priv;
|
2017-11-10 09:52:24 +01:00
|
|
|
struct snd_soc_dai_link *dai_link;
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_dai_props *dai_props;
|
2017-11-10 09:52:24 +01:00
|
|
|
struct device_node *np = pdev->dev.of_node;
|
|
|
|
struct device *dev = &pdev->dev;
|
2020-08-05 02:42:57 +02:00
|
|
|
int num, ret, i;
|
2017-11-10 09:52:24 +01:00
|
|
|
|
|
|
|
/* Get the number of DAI links */
|
|
|
|
if (np && of_get_child_by_name(np, PREFIX "dai-link"))
|
|
|
|
num = of_get_child_count(np);
|
|
|
|
else
|
|
|
|
num = 1;
|
|
|
|
|
|
|
|
/* Allocate the private data and the DAI link array */
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
|
|
if (!priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL);
|
|
|
|
dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL);
|
|
|
|
if (!dai_props || !dai_link)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2020-08-05 02:42:57 +02:00
|
|
|
/*
|
|
|
|
* Use snd_soc_dai_link_component instead of legacy style
|
|
|
|
* It is codec only. but cpu/platform will be supported in the future.
|
|
|
|
* see
|
|
|
|
* soc-core.c :: snd_soc_init_multicodec()
|
2020-08-05 04:26:15 +02:00
|
|
|
*
|
|
|
|
* "platform" might be removed
|
|
|
|
* see
|
|
|
|
* simple-card-utils.c :: asoc_simple_canonicalize_platform()
|
2020-08-05 02:42:57 +02:00
|
|
|
*/
|
|
|
|
for (i = 0; i < num; i++) {
|
2020-07-29 18:57:37 +02:00
|
|
|
dai_link[i].cpus = &dai_props[i].cpus;
|
|
|
|
dai_link[i].num_cpus = 1;
|
|
|
|
dai_link[i].codecs = &dai_props[i].codecs;
|
|
|
|
dai_link[i].num_codecs = 1;
|
|
|
|
dai_link[i].platforms = &dai_props[i].platforms;
|
|
|
|
dai_link[i].num_platforms = 1;
|
2020-08-05 02:42:57 +02:00
|
|
|
}
|
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
priv->dai_props = dai_props;
|
|
|
|
priv->dai_link = dai_link;
|
|
|
|
|
|
|
|
/* Init snd_soc_card */
|
|
|
|
priv->snd_card.owner = THIS_MODULE;
|
|
|
|
priv->snd_card.dev = dev;
|
|
|
|
priv->snd_card.dai_link = priv->dai_link;
|
|
|
|
priv->snd_card.num_links = num;
|
|
|
|
|
|
|
|
if (np && of_device_is_available(np)) {
|
2018-04-04 11:36:18 +02:00
|
|
|
ret = seeed_voice_card_parse_of(np, priv);
|
2017-11-10 09:52:24 +01:00
|
|
|
if (ret < 0) {
|
|
|
|
if (ret != -EPROBE_DEFER)
|
|
|
|
dev_err(dev, "parse error %d\n", ret);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
} else {
|
2018-04-04 11:36:18 +02:00
|
|
|
struct seeed_card_info *cinfo;
|
2020-08-05 03:30:53 +02:00
|
|
|
struct snd_soc_dai_link_component *cpus;
|
2020-08-05 02:42:57 +02:00
|
|
|
struct snd_soc_dai_link_component *codecs;
|
2020-08-05 03:07:32 +02:00
|
|
|
struct snd_soc_dai_link_component *platform;
|
2017-11-10 09:52:24 +01:00
|
|
|
|
|
|
|
cinfo = dev->platform_data;
|
|
|
|
if (!cinfo) {
|
2018-04-04 11:36:18 +02:00
|
|
|
dev_err(dev, "no info for seeed-voice-card\n");
|
2017-11-10 09:52:24 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cinfo->name ||
|
|
|
|
!cinfo->codec_dai.name ||
|
|
|
|
!cinfo->codec ||
|
|
|
|
!cinfo->platform ||
|
|
|
|
!cinfo->cpu_dai.name) {
|
2018-04-04 11:36:18 +02:00
|
|
|
dev_err(dev, "insufficient seeed_voice_card_info settings\n");
|
2017-11-10 09:52:24 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2020-08-05 03:30:53 +02:00
|
|
|
cpus = dai_link->cpus;
|
|
|
|
cpus->dai_name = cinfo->cpu_dai.name;
|
|
|
|
|
2020-08-05 02:42:57 +02:00
|
|
|
codecs = dai_link->codecs;
|
|
|
|
codecs->name = cinfo->codec;
|
|
|
|
codecs->dai_name = cinfo->codec_dai.name;
|
|
|
|
|
2020-08-05 03:07:32 +02:00
|
|
|
platform = dai_link->platforms;
|
|
|
|
platform->name = cinfo->platform;
|
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
priv->snd_card.name = (cinfo->card) ? cinfo->card : cinfo->name;
|
|
|
|
dai_link->name = cinfo->name;
|
|
|
|
dai_link->stream_name = cinfo->name;
|
|
|
|
dai_link->dai_fmt = cinfo->daifmt;
|
2020-08-05 05:28:16 +02:00
|
|
|
dai_link->init = seeed_voice_card_dai_init;
|
2017-11-10 09:52:24 +01:00
|
|
|
memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai,
|
|
|
|
sizeof(priv->dai_props->cpu_dai));
|
|
|
|
memcpy(&priv->dai_props->codec_dai, &cinfo->codec_dai,
|
|
|
|
sizeof(priv->dai_props->codec_dai));
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_soc_card_set_drvdata(&priv->snd_card, priv);
|
|
|
|
|
2018-11-27 10:47:44 +01:00
|
|
|
#if CONFIG_AC10X_TRIG_LOCK
|
2018-04-02 09:59:17 +02:00
|
|
|
spin_lock_init(&priv->lock);
|
2018-11-27 10:47:44 +01:00
|
|
|
#endif
|
2018-04-02 09:59:17 +02:00
|
|
|
|
2018-11-08 05:33:55 +01:00
|
|
|
INIT_WORK(&priv->work_codec_clk, work_cb_codec_clk);
|
|
|
|
|
2021-04-13 01:30:58 +02:00
|
|
|
seeed_debug_info(priv);
|
2021-04-13 00:12:42 +02:00
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
|
|
|
|
if (ret >= 0)
|
|
|
|
return ret;
|
2018-04-02 09:59:17 +02:00
|
|
|
|
2017-11-10 09:52:24 +01:00
|
|
|
err:
|
2020-04-24 04:35:45 +02:00
|
|
|
asoc_simple_clean_reference(&priv->snd_card);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static int seeed_voice_card_remove(struct platform_device *pdev)
|
2017-11-10 09:52:24 +01:00
|
|
|
{
|
|
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
2018-11-08 05:33:55 +01:00
|
|
|
struct seeed_card_data *priv = snd_soc_card_get_drvdata(card);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-11-08 05:33:55 +01:00
|
|
|
if (cancel_work_sync(&priv->work_codec_clk) != 0) {
|
|
|
|
}
|
2022-10-30 23:37:13 +01:00
|
|
|
asoc_simple_clean_reference(card);
|
|
|
|
|
|
|
|
return 0;
|
2017-11-10 09:52:24 +01:00
|
|
|
}
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static const struct of_device_id seeed_voice_of_match[] = {
|
|
|
|
{ .compatible = "seeed-voicecard", },
|
2017-11-10 09:52:24 +01:00
|
|
|
{},
|
|
|
|
};
|
2018-04-04 11:36:18 +02:00
|
|
|
MODULE_DEVICE_TABLE(of, seeed_voice_of_match);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
static struct platform_driver seeed_voice_card = {
|
2017-11-10 09:52:24 +01:00
|
|
|
.driver = {
|
2018-04-04 11:36:18 +02:00
|
|
|
.name = "seeed-voicecard",
|
2017-11-10 09:52:24 +01:00
|
|
|
.pm = &snd_soc_pm_ops,
|
2018-04-04 11:36:18 +02:00
|
|
|
.of_match_table = seeed_voice_of_match,
|
2017-11-10 09:52:24 +01:00
|
|
|
},
|
2018-04-04 11:36:18 +02:00
|
|
|
.probe = seeed_voice_card_probe,
|
|
|
|
.remove = seeed_voice_card_remove,
|
2017-11-10 09:52:24 +01:00
|
|
|
};
|
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
module_platform_driver(seeed_voice_card);
|
2017-11-10 09:52:24 +01:00
|
|
|
|
2018-04-04 11:36:18 +02:00
|
|
|
MODULE_ALIAS("platform:seeed-voice-card");
|
2017-11-10 09:52:24 +01:00
|
|
|
MODULE_LICENSE("GPL v2");
|
2018-04-04 11:36:18 +02:00
|
|
|
MODULE_DESCRIPTION("ASoC SEEED Voice Card");
|
|
|
|
MODULE_AUTHOR("PeterYang<linsheng.yang@seeed.cc>");
|