Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Amlogic"
depends on ARCH_MESON || (COMPILE_TEST && COMMON_CLK)
config SND_MESON_AIU
tristate "Amlogic AIU"
select SND_MESON_CODEC_GLUE
select SND_PCM_IEC958
imply SND_SOC_MESON_T9015
imply SND_SOC_HDMI_CODEC if DRM_MESON_DW_HDMI
help
Select Y or M to add support for the Audio output subsystem found
in the Amlogic Meson8, Meson8b and GX SoC families
config SND_MESON_AXG_FIFO
tristate
select REGMAP_MMIO
imply COMMON_CLK_AXG_AUDIO
imply RESET_MESON_AUDIO_ARB
config SND_MESON_AXG_FRDDR
tristate "Amlogic AXG Playback FIFO support"
select SND_MESON_AXG_FIFO
help
Select Y or M to add support for the frontend playback interfaces
embedded in the Amlogic AXG SoC family
config SND_MESON_AXG_TODDR
tristate "Amlogic AXG Capture FIFO support"
select SND_MESON_AXG_FIFO
help
Select Y or M to add support for the frontend capture interfaces
embedded in the Amlogic AXG SoC family
config SND_MESON_AXG_TDM_FORMATTER
tristate
select REGMAP_MMIO
imply COMMON_CLK_AXG_AUDIO
config SND_MESON_AXG_TDM_INTERFACE
tristate
select SND_MESON_AXG_TDM_FORMATTER
config SND_MESON_AXG_TDMIN
tristate "Amlogic AXG TDM Input Support"
select SND_MESON_AXG_TDM_FORMATTER
select SND_MESON_AXG_TDM_INTERFACE
help
Select Y or M to add support for TDM input formatter embedded
in the Amlogic AXG SoC family
config SND_MESON_AXG_TDMOUT
tristate "Amlogic AXG TDM Output Support"
select SND_MESON_AXG_TDM_FORMATTER
select SND_MESON_AXG_TDM_INTERFACE
help
Select Y or M to add support for TDM output formatter embedded
in the Amlogic AXG SoC family
config SND_MESON_AXG_SOUND_CARD
tristate "Amlogic AXG Sound Card Support"
select SND_MESON_AXG_TDM_INTERFACE
select SND_MESON_CARD_UTILS
imply SND_MESON_AXG_FRDDR
imply SND_MESON_AXG_TODDR
imply SND_MESON_AXG_TDMIN
imply SND_MESON_AXG_TDMOUT
imply SND_MESON_AXG_SPDIFOUT
imply SND_MESON_AXG_SPDIFIN
imply SND_MESON_AXG_PDM
imply SND_MESON_G12A_TOACODEC
imply SND_MESON_G12A_TOHDMITX if DRM_MESON_DW_HDMI
help
Select Y or M to add support for the AXG SoC sound card
config SND_MESON_AXG_SPDIFOUT
tristate "Amlogic AXG SPDIF Output Support"
select SND_PCM_IEC958
imply SND_SOC_SPDIF
imply COMMON_CLK_AXG_AUDIO
help
Select Y or M to add support for SPDIF output serializer embedded
in the Amlogic AXG SoC family
config SND_MESON_AXG_SPDIFIN
tristate "Amlogic AXG SPDIF Input Support"
imply SND_SOC_SPDIF
help
Select Y or M to add support for SPDIF input embedded
in the Amlogic AXG SoC family
config SND_MESON_AXG_PDM
tristate "Amlogic AXG PDM Input Support"
imply SND_SOC_DMIC
imply COMMON_CLK_AXG_AUDIO
help
Select Y or M to add support for PDM input embedded
in the Amlogic AXG SoC family
config SND_MESON_CARD_UTILS
tristate
select SND_DYNAMIC_MINORS
config SND_MESON_CODEC_GLUE
tristate
config SND_MESON_GX_SOUND_CARD
tristate "Amlogic GX Sound Card Support"
select SND_MESON_CARD_UTILS
imply SND_MESON_AIU
help
Select Y or M to add support for the GXBB/GXL SoC sound card
config SND_MESON_G12A_TOACODEC
tristate "Amlogic G12A To Internal DAC Control Support"
select SND_MESON_CODEC_GLUE
select REGMAP_MMIO
imply SND_SOC_MESON_T9015
help
Select Y or M to add support for the internal audio DAC on the
g12a SoC family
config SND_MESON_G12A_TOHDMITX
tristate "Amlogic G12A To HDMI TX Control Support"
select REGMAP_MMIO
select SND_MESON_CODEC_GLUE
imply SND_SOC_HDMI_CODEC
help
Select Y or M to add support for HDMI audio on the g12a SoC
family
config SND_SOC_MESON_T9015
tristate "Amlogic T9015 DAC"
select REGMAP_MMIO
help
Say Y or M if you want to add support for the internal DAC found
on GXL, G12 and SM1 SoC family.
endmenu
+46
View File
@@ -0,0 +1,46 @@
# SPDX-License-Identifier: (GPL-2.0 OR MIT)
snd-soc-meson-aiu-y := aiu.o
snd-soc-meson-aiu-y += aiu-acodec-ctrl.o
snd-soc-meson-aiu-y += aiu-codec-ctrl.o
snd-soc-meson-aiu-y += aiu-encoder-i2s.o
snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
snd-soc-meson-aiu-y += aiu-fifo-spdif.o
snd-soc-meson-axg-fifo-y := axg-fifo.o
snd-soc-meson-axg-frddr-y := axg-frddr.o
snd-soc-meson-axg-toddr-y := axg-toddr.o
snd-soc-meson-axg-tdm-formatter-y := axg-tdm-formatter.o
snd-soc-meson-axg-tdm-interface-y := axg-tdm-interface.o
snd-soc-meson-axg-tdmin-y := axg-tdmin.o
snd-soc-meson-axg-tdmout-y := axg-tdmout.o
snd-soc-meson-axg-sound-card-y := axg-card.o
snd-soc-meson-axg-spdifin-y := axg-spdifin.o
snd-soc-meson-axg-spdifout-y := axg-spdifout.o
snd-soc-meson-axg-pdm-y := axg-pdm.o
snd-soc-meson-card-utils-y := meson-card-utils.o
snd-soc-meson-codec-glue-y := meson-codec-glue.o
snd-soc-meson-gx-sound-card-y := gx-card.o
snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
snd-soc-meson-t9015-y := t9015.o
obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.o
obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o
obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o
obj-$(CONFIG_SND_MESON_AXG_SPDIFIN) += snd-soc-meson-axg-spdifin.o
obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
obj-$(CONFIG_SND_MESON_AXG_PDM) += snd-soc-meson-axg-pdm.o
obj-$(CONFIG_SND_MESON_CARD_UTILS) += snd-soc-meson-card-utils.o
obj-$(CONFIG_SND_MESON_CODEC_GLUE) += snd-soc-meson-codec-glue.o
obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += snd-soc-meson-gx-sound-card.o
obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += snd-soc-meson-g12a-toacodec.o
obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += snd-soc-meson-g12a-tohdmitx.o
obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <dt-bindings/sound/meson-aiu.h>
#include "aiu.h"
#include "meson-codec-glue.h"
#define CTRL_DIN_EN 15
#define CTRL_CLK_INV BIT(14)
#define CTRL_LRCLK_INV BIT(13)
#define CTRL_I2S_IN_BCLK_SRC BIT(11)
#define CTRL_DIN_LRCLK_SRC_SHIFT 6
#define CTRL_DIN_LRCLK_SRC (0x3 << CTRL_DIN_LRCLK_SRC_SHIFT)
#define CTRL_BCLK_MCLK_SRC GENMASK(5, 4)
#define CTRL_DIN_SKEW GENMASK(3, 2)
#define CTRL_I2S_OUT_LANE_SRC 0
#define AIU_ACODEC_OUT_CHMAX 2
static const char * const aiu_acodec_ctrl_mux_texts[] = {
"DISABLED", "I2S", "PCM",
};
static int aiu_acodec_ctrl_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_to_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int mux, changed;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
changed = snd_soc_component_test_bits(component, e->reg,
CTRL_DIN_LRCLK_SRC,
FIELD_PREP(CTRL_DIN_LRCLK_SRC,
mux));
if (!changed)
return 0;
/* Force disconnect of the mux while updating */
snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
snd_soc_component_update_bits(component, e->reg,
CTRL_DIN_LRCLK_SRC |
CTRL_BCLK_MCLK_SRC,
FIELD_PREP(CTRL_DIN_LRCLK_SRC, mux) |
FIELD_PREP(CTRL_BCLK_MCLK_SRC, mux));
snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
return 1;
}
static SOC_ENUM_SINGLE_DECL(aiu_acodec_ctrl_mux_enum, AIU_ACODEC_CTRL,
CTRL_DIN_LRCLK_SRC_SHIFT,
aiu_acodec_ctrl_mux_texts);
static const struct snd_kcontrol_new aiu_acodec_ctrl_mux =
SOC_DAPM_ENUM_EXT("ACodec Source", aiu_acodec_ctrl_mux_enum,
snd_soc_dapm_get_enum_double,
aiu_acodec_ctrl_mux_put_enum);
static const struct snd_kcontrol_new aiu_acodec_ctrl_out_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", AIU_ACODEC_CTRL,
CTRL_DIN_EN, 1, 0);
static const struct snd_soc_dapm_widget aiu_acodec_ctrl_widgets[] = {
SND_SOC_DAPM_MUX("ACODEC SRC", SND_SOC_NOPM, 0, 0,
&aiu_acodec_ctrl_mux),
SND_SOC_DAPM_SWITCH("ACODEC OUT EN", SND_SOC_NOPM, 0, 0,
&aiu_acodec_ctrl_out_enable),
};
static int aiu_acodec_ctrl_input_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct meson_codec_glue_input *data;
int ret;
ret = meson_codec_glue_input_hw_params(substream, params, dai);
if (ret)
return ret;
/* The glue will provide 1 lane out of the 4 to the output */
data = meson_codec_glue_input_get_data(dai);
data->params.channels_min = min_t(unsigned int, AIU_ACODEC_OUT_CHMAX,
data->params.channels_min);
data->params.channels_max = min_t(unsigned int, AIU_ACODEC_OUT_CHMAX,
data->params.channels_max);
return 0;
}
static const struct snd_soc_dai_ops aiu_acodec_ctrl_input_ops = {
.probe = meson_codec_glue_input_dai_probe,
.remove = meson_codec_glue_input_dai_remove,
.hw_params = aiu_acodec_ctrl_input_hw_params,
.set_fmt = meson_codec_glue_input_set_fmt,
};
static const struct snd_soc_dai_ops aiu_acodec_ctrl_output_ops = {
.startup = meson_codec_glue_output_startup,
};
#define AIU_ACODEC_CTRL_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
#define AIU_ACODEC_STREAM(xname, xsuffix, xchmax) \
{ \
.stream_name = xname " " xsuffix, \
.channels_min = 1, \
.channels_max = (xchmax), \
.rate_min = 5512, \
.rate_max = 192000, \
.formats = AIU_ACODEC_CTRL_FORMATS, \
}
#define AIU_ACODEC_INPUT(xname) { \
.name = "ACODEC CTRL " xname, \
.playback = AIU_ACODEC_STREAM(xname, "Playback", 8), \
.ops = &aiu_acodec_ctrl_input_ops, \
}
#define AIU_ACODEC_OUTPUT(xname) { \
.name = "ACODEC CTRL " xname, \
.capture = AIU_ACODEC_STREAM(xname, "Capture", AIU_ACODEC_OUT_CHMAX), \
.ops = &aiu_acodec_ctrl_output_ops, \
}
static struct snd_soc_dai_driver aiu_acodec_ctrl_dai_drv[] = {
[CTRL_I2S] = AIU_ACODEC_INPUT("ACODEC I2S IN"),
[CTRL_PCM] = AIU_ACODEC_INPUT("ACODEC PCM IN"),
[CTRL_OUT] = AIU_ACODEC_OUTPUT("ACODEC OUT"),
};
static const struct snd_soc_dapm_route aiu_acodec_ctrl_routes[] = {
{ "ACODEC SRC", "I2S", "ACODEC I2S IN Playback" },
{ "ACODEC SRC", "PCM", "ACODEC PCM IN Playback" },
{ "ACODEC OUT EN", "Switch", "ACODEC SRC" },
{ "ACODEC OUT Capture", NULL, "ACODEC OUT EN" },
};
static const struct snd_kcontrol_new aiu_acodec_ctrl_controls[] = {
SOC_SINGLE("ACODEC I2S Lane Select", AIU_ACODEC_CTRL,
CTRL_I2S_OUT_LANE_SRC, 3, 0),
};
static int aiu_acodec_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name)
{
return aiu_of_xlate_dai_name(component, args, dai_name, AIU_ACODEC);
}
static int aiu_acodec_ctrl_component_probe(struct snd_soc_component *component)
{
/*
* NOTE: Din Skew setting
* According to the documentation, the following update adds one delay
* to the din line. Without this, the output saturates. This happens
* regardless of the link format (i2s or left_j) so it is not clear what
* it actually does but it seems to be required
*/
snd_soc_component_update_bits(component, AIU_ACODEC_CTRL,
CTRL_DIN_SKEW,
FIELD_PREP(CTRL_DIN_SKEW, 2));
return 0;
}
static const struct snd_soc_component_driver aiu_acodec_ctrl_component = {
.name = "AIU Internal DAC Codec Control",
.probe = aiu_acodec_ctrl_component_probe,
.controls = aiu_acodec_ctrl_controls,
.num_controls = ARRAY_SIZE(aiu_acodec_ctrl_controls),
.dapm_widgets = aiu_acodec_ctrl_widgets,
.num_dapm_widgets = ARRAY_SIZE(aiu_acodec_ctrl_widgets),
.dapm_routes = aiu_acodec_ctrl_routes,
.num_dapm_routes = ARRAY_SIZE(aiu_acodec_ctrl_routes),
.of_xlate_dai_name = aiu_acodec_of_xlate_dai_name,
.endianness = 1,
#ifdef CONFIG_DEBUG_FS
.debugfs_prefix = "acodec",
#endif
};
int aiu_acodec_ctrl_register_component(struct device *dev)
{
return snd_soc_register_component(dev, &aiu_acodec_ctrl_component,
aiu_acodec_ctrl_dai_drv,
ARRAY_SIZE(aiu_acodec_ctrl_dai_drv));
}
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <dt-bindings/sound/meson-aiu.h>
#include "aiu.h"
#include "meson-codec-glue.h"
#define CTRL_CLK_SEL GENMASK(1, 0)
#define CTRL_DATA_SEL_SHIFT 4
#define CTRL_DATA_SEL (0x3 << CTRL_DATA_SEL_SHIFT)
static const char * const aiu_codec_ctrl_mux_texts[] = {
"DISABLED", "PCM", "I2S",
};
static int aiu_codec_ctrl_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_to_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int mux, changed;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
changed = snd_soc_component_test_bits(component, e->reg,
CTRL_DATA_SEL,
FIELD_PREP(CTRL_DATA_SEL, mux));
if (!changed)
return 0;
/* Force disconnect of the mux while updating */
snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
/* Reset the source first */
snd_soc_component_update_bits(component, e->reg,
CTRL_CLK_SEL |
CTRL_DATA_SEL,
FIELD_PREP(CTRL_CLK_SEL, 0) |
FIELD_PREP(CTRL_DATA_SEL, 0));
/* Set the appropriate source */
snd_soc_component_update_bits(component, e->reg,
CTRL_CLK_SEL |
CTRL_DATA_SEL,
FIELD_PREP(CTRL_CLK_SEL, mux) |
FIELD_PREP(CTRL_DATA_SEL, mux));
snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
return 1;
}
static SOC_ENUM_SINGLE_DECL(aiu_hdmi_ctrl_mux_enum, AIU_HDMI_CLK_DATA_CTRL,
CTRL_DATA_SEL_SHIFT,
aiu_codec_ctrl_mux_texts);
static const struct snd_kcontrol_new aiu_hdmi_ctrl_mux =
SOC_DAPM_ENUM_EXT("HDMI Source", aiu_hdmi_ctrl_mux_enum,
snd_soc_dapm_get_enum_double,
aiu_codec_ctrl_mux_put_enum);
static const struct snd_soc_dapm_widget aiu_hdmi_ctrl_widgets[] = {
SND_SOC_DAPM_MUX("HDMI CTRL SRC", SND_SOC_NOPM, 0, 0,
&aiu_hdmi_ctrl_mux),
};
static const struct snd_soc_dai_ops aiu_codec_ctrl_input_ops = {
.probe = meson_codec_glue_input_dai_probe,
.remove = meson_codec_glue_input_dai_remove,
.hw_params = meson_codec_glue_input_hw_params,
.set_fmt = meson_codec_glue_input_set_fmt,
};
static const struct snd_soc_dai_ops aiu_codec_ctrl_output_ops = {
.startup = meson_codec_glue_output_startup,
};
#define AIU_CODEC_CTRL_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
#define AIU_CODEC_CTRL_STREAM(xname, xsuffix) \
{ \
.stream_name = xname " " xsuffix, \
.channels_min = 1, \
.channels_max = 8, \
.rate_min = 5512, \
.rate_max = 192000, \
.formats = AIU_CODEC_CTRL_FORMATS, \
}
#define AIU_CODEC_CTRL_INPUT(xname) { \
.name = "CODEC CTRL " xname, \
.playback = AIU_CODEC_CTRL_STREAM(xname, "Playback"), \
.ops = &aiu_codec_ctrl_input_ops, \
}
#define AIU_CODEC_CTRL_OUTPUT(xname) { \
.name = "CODEC CTRL " xname, \
.capture = AIU_CODEC_CTRL_STREAM(xname, "Capture"), \
.ops = &aiu_codec_ctrl_output_ops, \
}
static struct snd_soc_dai_driver aiu_hdmi_ctrl_dai_drv[] = {
[CTRL_I2S] = AIU_CODEC_CTRL_INPUT("HDMI I2S IN"),
[CTRL_PCM] = AIU_CODEC_CTRL_INPUT("HDMI PCM IN"),
[CTRL_OUT] = AIU_CODEC_CTRL_OUTPUT("HDMI OUT"),
};
static const struct snd_soc_dapm_route aiu_hdmi_ctrl_routes[] = {
{ "HDMI CTRL SRC", "I2S", "HDMI I2S IN Playback" },
{ "HDMI CTRL SRC", "PCM", "HDMI PCM IN Playback" },
{ "HDMI OUT Capture", NULL, "HDMI CTRL SRC" },
};
static int aiu_hdmi_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name)
{
return aiu_of_xlate_dai_name(component, args, dai_name, AIU_HDMI);
}
static const struct snd_soc_component_driver aiu_hdmi_ctrl_component = {
.name = "AIU HDMI Codec Control",
.dapm_widgets = aiu_hdmi_ctrl_widgets,
.num_dapm_widgets = ARRAY_SIZE(aiu_hdmi_ctrl_widgets),
.dapm_routes = aiu_hdmi_ctrl_routes,
.num_dapm_routes = ARRAY_SIZE(aiu_hdmi_ctrl_routes),
.of_xlate_dai_name = aiu_hdmi_of_xlate_dai_name,
.endianness = 1,
#ifdef CONFIG_DEBUG_FS
.debugfs_prefix = "hdmi",
#endif
};
int aiu_hdmi_ctrl_register_component(struct device *dev)
{
return snd_soc_register_component(dev, &aiu_hdmi_ctrl_component,
aiu_hdmi_ctrl_dai_drv,
ARRAY_SIZE(aiu_hdmi_ctrl_dai_drv));
}
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "aiu.h"
#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
#define AIU_RST_SOFT_I2S_FAST BIT(0)
#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0)
#define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2)
#define AIU_CLK_CTRL_AOCLK_INVERT BIT(6)
#define AIU_CLK_CTRL_LRCLK_INVERT BIT(7)
#define AIU_CLK_CTRL_LRCLK_SKEW GENMASK(9, 8)
#define AIU_CLK_CTRL_MORE_HDMI_AMCLK BIT(6)
#define AIU_CLK_CTRL_MORE_I2S_DIV GENMASK(5, 0)
#define AIU_CODEC_DAC_LRCLK_CTRL_DIV GENMASK(11, 0)
static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
bool enable)
{
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_I2S_DIV_EN,
enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
}
static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
struct snd_pcm_hw_params *params)
{
/* Always operate in split (classic interleaved) mode */
unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
/* Reset required to update the pipeline */
snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
snd_soc_component_read(component, AIU_I2S_SYNC);
switch (params_physical_width(params)) {
case 16: /* Nothing to do */
break;
case 32:
desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
AIU_I2S_SOURCE_DESC_MODE_32BIT);
break;
default:
return -EINVAL;
}
switch (params_channels(params)) {
case 2: /* Nothing to do */
break;
case 8:
desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,
AIU_I2S_SOURCE_DESC_MODE_8CH |
AIU_I2S_SOURCE_DESC_MODE_24BIT |
AIU_I2S_SOURCE_DESC_MODE_32BIT |
AIU_I2S_SOURCE_DESC_MODE_SPLIT,
desc);
return 0;
}
static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
struct snd_pcm_hw_params *params,
unsigned int bs)
{
switch (bs) {
case 1:
case 2:
case 4:
case 8:
/* These are the only valid legacy dividers */
break;
default:
dev_err(component->dev, "Unsupported i2s divider: %u\n", bs);
return -EINVAL;
}
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_I2S_DIV,
FIELD_PREP(AIU_CLK_CTRL_I2S_DIV,
__ffs(bs)));
snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
AIU_CLK_CTRL_MORE_I2S_DIV,
FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
0));
return 0;
}
static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
struct snd_pcm_hw_params *params,
unsigned int bs)
{
/*
* NOTE: this HW is odd.
* In most configuration, the i2s divider is 'mclk / blck'.
* However, in 16 bits - 8ch mode, this factor needs to be
* increased by 50% to get the correct output rate.
* No idea why !
*/
if (params_width(params) == 16 && params_channels(params) == 8) {
if (bs % 2) {
dev_err(component->dev,
"Cannot increase i2s divider by 50%%\n");
return -EINVAL;
}
bs += bs / 2;
}
/* Use CLK_MORE for mclk to bclk divider */
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_I2S_DIV,
FIELD_PREP(AIU_CLK_CTRL_I2S_DIV, 0));
snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
AIU_CLK_CTRL_MORE_I2S_DIV,
FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
bs - 1));
return 0;
}
static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
struct snd_pcm_hw_params *params)
{
struct aiu *aiu = snd_soc_component_get_drvdata(component);
unsigned int srate = params_rate(params);
unsigned int fs, bs;
int ret;
/* Get the oversampling factor */
fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
if (fs % 64)
return -EINVAL;
/* Send data MSB first */
snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,
AIU_I2S_DAC_CFG_MSB_FIRST,
AIU_I2S_DAC_CFG_MSB_FIRST);
/* Set bclk to lrlck ratio */
snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,
AIU_CODEC_DAC_LRCLK_CTRL_DIV,
FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,
64 - 1));
bs = fs / 64;
if (aiu->platform->has_clk_ctrl_more_i2s_div)
ret = aiu_encoder_i2s_set_more_div(component, params, bs);
else
ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);
if (ret)
return ret;
/* Make sure amclk is used for HDMI i2s as well */
snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
AIU_CLK_CTRL_MORE_HDMI_AMCLK,
AIU_CLK_CTRL_MORE_HDMI_AMCLK);
return 0;
}
static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
int ret;
/* Disable the clock while changing the settings */
aiu_encoder_i2s_divider_enable(component, false);
ret = aiu_encoder_i2s_setup_desc(component, params);
if (ret) {
dev_err(dai->dev, "setting i2s desc failed\n");
return ret;
}
ret = aiu_encoder_i2s_set_clocks(component, params);
if (ret) {
dev_err(dai->dev, "setting i2s clocks failed\n");
return ret;
}
aiu_encoder_i2s_divider_enable(component, true);
return 0;
}
static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
aiu_encoder_i2s_divider_enable(component, false);
return 0;
}
static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
unsigned int val = 0;
unsigned int skew;
/* Only CPU Master / Codec Slave supported ATM */
if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP)
return -EINVAL;
if (inv == SND_SOC_DAIFMT_NB_IF ||
inv == SND_SOC_DAIFMT_IB_IF)
val |= AIU_CLK_CTRL_LRCLK_INVERT;
/*
* The SoC changes data on the rising edge of the bitclock
* so an inversion of the bitclock is required in normal mode
*/
if (inv == SND_SOC_DAIFMT_NB_NF ||
inv == SND_SOC_DAIFMT_NB_IF)
val |= AIU_CLK_CTRL_AOCLK_INVERT;
/* Signal skew */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
/* Invert sample clock for i2s */
val ^= AIU_CLK_CTRL_LRCLK_INVERT;
skew = 1;
break;
case SND_SOC_DAIFMT_LEFT_J:
skew = 0;
break;
default:
return -EINVAL;
}
val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_LRCLK_INVERT |
AIU_CLK_CTRL_AOCLK_INVERT |
AIU_CLK_CTRL_LRCLK_SKEW,
val);
return 0;
}
static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
int ret;
if (WARN_ON(clk_id != 0))
return -EINVAL;
if (dir == SND_SOC_CLOCK_IN)
return 0;
ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);
if (ret)
dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
return ret;
}
static const unsigned int hw_channels[] = {2, 8};
static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {
.list = hw_channels,
.count = ARRAY_SIZE(hw_channels),
.mask = 0,
};
static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
int ret;
/* Make sure the encoder gets either 2 or 8 channels */
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
&hw_channel_constraints);
if (ret) {
dev_err(dai->dev, "adding channels constraints failed\n");
return ret;
}
ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);
if (ret)
dev_err(dai->dev, "failed to enable i2s clocks\n");
return ret;
}
static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
}
const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
.hw_params = aiu_encoder_i2s_hw_params,
.hw_free = aiu_encoder_i2s_hw_free,
.set_fmt = aiu_encoder_i2s_set_fmt,
.set_sysclk = aiu_encoder_i2s_set_sysclk,
.startup = aiu_encoder_i2s_startup,
.shutdown = aiu_encoder_i2s_shutdown,
};
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <sound/pcm_params.h>
#include <sound/pcm_iec958.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "aiu.h"
#define AIU_958_MISC_NON_PCM BIT(0)
#define AIU_958_MISC_MODE_16BITS BIT(1)
#define AIU_958_MISC_16BITS_ALIGN GENMASK(6, 5)
#define AIU_958_MISC_MODE_32BITS BIT(7)
#define AIU_958_MISC_U_FROM_STREAM BIT(12)
#define AIU_958_MISC_FORCE_LR BIT(13)
#define AIU_958_CTRL_HOLD_EN BIT(0)
#define AIU_CLK_CTRL_958_DIV_EN BIT(1)
#define AIU_CLK_CTRL_958_DIV GENMASK(5, 4)
#define AIU_CLK_CTRL_958_DIV_MORE BIT(12)
#define AIU_CS_WORD_LEN 4
#define AIU_958_INTERNAL_DIV 2
static void
aiu_encoder_spdif_divider_enable(struct snd_soc_component *component,
bool enable)
{
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_958_DIV_EN,
enable ? AIU_CLK_CTRL_958_DIV_EN : 0);
}
static void aiu_encoder_spdif_hold(struct snd_soc_component *component,
bool enable)
{
snd_soc_component_update_bits(component, AIU_958_CTRL,
AIU_958_CTRL_HOLD_EN,
enable ? AIU_958_CTRL_HOLD_EN : 0);
}
static int
aiu_encoder_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
aiu_encoder_spdif_hold(component, false);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
aiu_encoder_spdif_hold(component, true);
return 0;
default:
return -EINVAL;
}
}
static int aiu_encoder_spdif_setup_cs_word(struct snd_soc_component *component,
struct snd_pcm_hw_params *params)
{
u8 cs[AIU_CS_WORD_LEN];
unsigned int val;
int ret;
ret = snd_pcm_create_iec958_consumer_hw_params(params, cs,
AIU_CS_WORD_LEN);
if (ret < 0)
return ret;
/* Write the 1st half word */
val = cs[1] | cs[0] << 8;
snd_soc_component_write(component, AIU_958_CHSTAT_L0, val);
snd_soc_component_write(component, AIU_958_CHSTAT_R0, val);
/* Write the 2nd half word */
val = cs[3] | cs[2] << 8;
snd_soc_component_write(component, AIU_958_CHSTAT_L1, val);
snd_soc_component_write(component, AIU_958_CHSTAT_R1, val);
return 0;
}
static int aiu_encoder_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct aiu *aiu = snd_soc_component_get_drvdata(component);
unsigned int val = 0, mrate;
int ret;
/* Disable the clock while changing the settings */
aiu_encoder_spdif_divider_enable(component, false);
switch (params_physical_width(params)) {
case 16:
val |= AIU_958_MISC_MODE_16BITS;
val |= FIELD_PREP(AIU_958_MISC_16BITS_ALIGN, 2);
break;
case 32:
val |= AIU_958_MISC_MODE_32BITS;
break;
default:
dev_err(dai->dev, "Unsupported physical width\n");
return -EINVAL;
}
snd_soc_component_update_bits(component, AIU_958_MISC,
AIU_958_MISC_NON_PCM |
AIU_958_MISC_MODE_16BITS |
AIU_958_MISC_16BITS_ALIGN |
AIU_958_MISC_MODE_32BITS |
AIU_958_MISC_FORCE_LR |
AIU_958_MISC_U_FROM_STREAM,
val);
/* Set the stream channel status word */
ret = aiu_encoder_spdif_setup_cs_word(component, params);
if (ret) {
dev_err(dai->dev, "failed to set channel status word\n");
return ret;
}
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_958_DIV |
AIU_CLK_CTRL_958_DIV_MORE,
FIELD_PREP(AIU_CLK_CTRL_958_DIV,
__ffs(AIU_958_INTERNAL_DIV)));
/* 2 * 32bits per subframe * 2 channels = 128 */
mrate = params_rate(params) * 128 * AIU_958_INTERNAL_DIV;
ret = clk_set_rate(aiu->spdif.clks[MCLK].clk, mrate);
if (ret) {
dev_err(dai->dev, "failed to set mclk rate\n");
return ret;
}
aiu_encoder_spdif_divider_enable(component, true);
return 0;
}
static int aiu_encoder_spdif_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
aiu_encoder_spdif_divider_enable(component, false);
return 0;
}
static int aiu_encoder_spdif_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
int ret;
/*
* NOTE: Make sure the spdif block is on its own divider.
*
* The spdif can be clocked by the i2s master clock or its own
* clock. We should (in theory) change the source depending on the
* origin of the data.
*
* However, considering the clocking scheme used on these platforms,
* the master clocks will pick the same PLL source when they are
* playing from the same FIFO. The clock should be in sync so, it
* should not be necessary to reparent the spdif master clock.
*/
ret = clk_set_parent(aiu->spdif.clks[MCLK].clk,
aiu->spdif_mclk);
if (ret)
return ret;
ret = clk_bulk_prepare_enable(aiu->spdif.clk_num, aiu->spdif.clks);
if (ret)
dev_err(dai->dev, "failed to enable spdif clocks\n");
return ret;
}
static void aiu_encoder_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
clk_bulk_disable_unprepare(aiu->spdif.clk_num, aiu->spdif.clks);
}
const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops = {
.trigger = aiu_encoder_spdif_trigger,
.hw_params = aiu_encoder_spdif_hw_params,
.hw_free = aiu_encoder_spdif_hw_free,
.startup = aiu_encoder_spdif_startup,
.shutdown = aiu_encoder_spdif_shutdown,
};
+173
View File
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "aiu.h"
#include "aiu-fifo.h"
#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
#define AIU_MEM_I2S_MASKS_IRQ_BLOCK GENMASK(31, 16)
#define AIU_MEM_I2S_CONTROL_MODE_16BIT BIT(6)
#define AIU_MEM_I2S_BUF_CNTL_INIT BIT(0)
#define AIU_RST_SOFT_I2S_FAST BIT(0)
#define AIU_I2S_MISC_HOLD_EN BIT(2)
#define AIU_I2S_MISC_FORCE_LEFT_RIGHT BIT(4)
#define AIU_FIFO_I2S_BLOCK 256
static const struct snd_pcm_hardware fifo_i2s_pcm = {
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE),
.formats = AIU_FORMATS,
.rate_min = 5512,
.rate_max = 192000,
.channels_min = 2,
.channels_max = 8,
.period_bytes_min = AIU_FIFO_I2S_BLOCK,
.period_bytes_max = AIU_FIFO_I2S_BLOCK * USHRT_MAX,
.periods_min = 2,
.periods_max = UINT_MAX,
/* No real justification for this */
.buffer_bytes_max = 1 * 1024 * 1024,
};
static int aiu_fifo_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_soc_component_write(component, AIU_RST_SOFT,
AIU_RST_SOFT_I2S_FAST);
snd_soc_component_read(component, AIU_I2S_SYNC);
break;
}
return aiu_fifo_trigger(substream, cmd, dai);
}
static int aiu_fifo_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
int ret;
ret = aiu_fifo_prepare(substream, dai);
if (ret)
return ret;
snd_soc_component_update_bits(component,
AIU_MEM_I2S_BUF_CNTL,
AIU_MEM_I2S_BUF_CNTL_INIT,
AIU_MEM_I2S_BUF_CNTL_INIT);
snd_soc_component_update_bits(component,
AIU_MEM_I2S_BUF_CNTL,
AIU_MEM_I2S_BUF_CNTL_INIT, 0);
return 0;
}
static int aiu_fifo_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
unsigned int val;
int ret;
snd_soc_component_update_bits(component, AIU_I2S_MISC,
AIU_I2S_MISC_HOLD_EN,
AIU_I2S_MISC_HOLD_EN);
ret = aiu_fifo_hw_params(substream, params, dai);
if (ret)
return ret;
switch (params_physical_width(params)) {
case 16:
val = AIU_MEM_I2S_CONTROL_MODE_16BIT;
break;
case 32:
val = 0;
break;
default:
dev_err(dai->dev, "Unsupported physical width %u\n",
params_physical_width(params));
return -EINVAL;
}
snd_soc_component_update_bits(component, AIU_MEM_I2S_CONTROL,
AIU_MEM_I2S_CONTROL_MODE_16BIT,
val);
/* Setup the irq periodicity */
val = params_period_bytes(params) / fifo->fifo_block;
val = FIELD_PREP(AIU_MEM_I2S_MASKS_IRQ_BLOCK, val);
snd_soc_component_update_bits(component, AIU_MEM_I2S_MASKS,
AIU_MEM_I2S_MASKS_IRQ_BLOCK, val);
/*
* Most (all?) supported SoCs have this bit set by default. The vendor
* driver however sets it manually (depending on the version either
* while un-setting AIU_I2S_MISC_HOLD_EN or right before that). Follow
* the same approach for consistency with the vendor driver.
*/
snd_soc_component_update_bits(component, AIU_I2S_MISC,
AIU_I2S_MISC_FORCE_LEFT_RIGHT,
AIU_I2S_MISC_FORCE_LEFT_RIGHT);
snd_soc_component_update_bits(component, AIU_I2S_MISC,
AIU_I2S_MISC_HOLD_EN, 0);
return 0;
}
const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops = {
.pcm_new = aiu_fifo_pcm_new,
.probe = aiu_fifo_i2s_dai_probe,
.remove = aiu_fifo_dai_remove,
.trigger = aiu_fifo_i2s_trigger,
.prepare = aiu_fifo_i2s_prepare,
.hw_params = aiu_fifo_i2s_hw_params,
.startup = aiu_fifo_startup,
.shutdown = aiu_fifo_shutdown,
};
int aiu_fifo_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct aiu *aiu = snd_soc_component_get_drvdata(component);
struct aiu_fifo *fifo;
int ret;
ret = aiu_fifo_dai_probe(dai);
if (ret)
return ret;
fifo = snd_soc_dai_dma_data_get_playback(dai);
fifo->pcm = &fifo_i2s_pcm;
fifo->mem_offset = AIU_MEM_I2S_START;
fifo->fifo_block = AIU_FIFO_I2S_BLOCK;
fifo->pclk = aiu->i2s.clks[PCLK].clk;
fifo->irq = aiu->i2s.irq;
return 0;
}
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "aiu.h"
#include "aiu-fifo.h"
#define AIU_IEC958_DCU_FF_CTRL_EN BIT(0)
#define AIU_IEC958_DCU_FF_CTRL_AUTO_DISABLE BIT(1)
#define AIU_IEC958_DCU_FF_CTRL_IRQ_MODE GENMASK(3, 2)
#define AIU_IEC958_DCU_FF_CTRL_IRQ_OUT_THD BIT(2)
#define AIU_IEC958_DCU_FF_CTRL_IRQ_FRAME_READ BIT(3)
#define AIU_IEC958_DCU_FF_CTRL_SYNC_HEAD_EN BIT(4)
#define AIU_IEC958_DCU_FF_CTRL_BYTE_SEEK BIT(5)
#define AIU_IEC958_DCU_FF_CTRL_CONTINUE BIT(6)
#define AIU_MEM_IEC958_CONTROL_ENDIAN GENMASK(5, 3)
#define AIU_MEM_IEC958_CONTROL_RD_DDR BIT(6)
#define AIU_MEM_IEC958_CONTROL_MODE_16BIT BIT(7)
#define AIU_MEM_IEC958_CONTROL_MODE_LINEAR BIT(8)
#define AIU_MEM_IEC958_BUF_CNTL_INIT BIT(0)
#define AIU_RST_SOFT_958_FAST BIT(2)
#define AIU_FIFO_SPDIF_BLOCK 8
static const struct snd_pcm_hardware fifo_spdif_pcm = {
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE),
.formats = AIU_FORMATS,
.rate_min = 5512,
.rate_max = 192000,
.channels_min = 2,
.channels_max = 2,
.period_bytes_min = AIU_FIFO_SPDIF_BLOCK,
.period_bytes_max = AIU_FIFO_SPDIF_BLOCK * USHRT_MAX,
.periods_min = 2,
.periods_max = UINT_MAX,
/* No real justification for this */
.buffer_bytes_max = 1 * 1024 * 1024,
};
static void fifo_spdif_dcu_enable(struct snd_soc_component *component,
bool enable)
{
snd_soc_component_update_bits(component, AIU_IEC958_DCU_FF_CTRL,
AIU_IEC958_DCU_FF_CTRL_EN,
enable ? AIU_IEC958_DCU_FF_CTRL_EN : 0);
}
static int fifo_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
int ret;
ret = aiu_fifo_trigger(substream, cmd, dai);
if (ret)
return ret;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_soc_component_write(component, AIU_RST_SOFT,
AIU_RST_SOFT_958_FAST);
fifo_spdif_dcu_enable(component, true);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
snd_soc_component_write(component, AIU_RST_SOFT,
AIU_RST_SOFT_958_FAST);
fifo_spdif_dcu_enable(component, false);
break;
default:
return -EINVAL;
}
return 0;
}
static int fifo_spdif_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
int ret;
ret = aiu_fifo_prepare(substream, dai);
if (ret)
return ret;
snd_soc_component_update_bits(component,
AIU_MEM_IEC958_BUF_CNTL,
AIU_MEM_IEC958_BUF_CNTL_INIT,
AIU_MEM_IEC958_BUF_CNTL_INIT);
snd_soc_component_update_bits(component,
AIU_MEM_IEC958_BUF_CNTL,
AIU_MEM_IEC958_BUF_CNTL_INIT, 0);
return 0;
}
static int fifo_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
unsigned int val;
int ret;
ret = aiu_fifo_hw_params(substream, params, dai);
if (ret)
return ret;
val = AIU_MEM_IEC958_CONTROL_RD_DDR |
AIU_MEM_IEC958_CONTROL_MODE_LINEAR;
switch (params_physical_width(params)) {
case 16:
val |= AIU_MEM_IEC958_CONTROL_MODE_16BIT;
break;
case 32:
break;
default:
dev_err(dai->dev, "Unsupported physical width %u\n",
params_physical_width(params));
return -EINVAL;
}
snd_soc_component_update_bits(component, AIU_MEM_IEC958_CONTROL,
AIU_MEM_IEC958_CONTROL_ENDIAN |
AIU_MEM_IEC958_CONTROL_RD_DDR |
AIU_MEM_IEC958_CONTROL_MODE_LINEAR |
AIU_MEM_IEC958_CONTROL_MODE_16BIT,
val);
/* Number bytes read by the FIFO between each IRQ */
snd_soc_component_write(component, AIU_IEC958_BPF,
params_period_bytes(params));
/*
* AUTO_DISABLE and SYNC_HEAD are enabled by default but
* this should be disabled in PCM (uncompressed) mode
*/
snd_soc_component_update_bits(component, AIU_IEC958_DCU_FF_CTRL,
AIU_IEC958_DCU_FF_CTRL_AUTO_DISABLE |
AIU_IEC958_DCU_FF_CTRL_IRQ_MODE |
AIU_IEC958_DCU_FF_CTRL_SYNC_HEAD_EN,
AIU_IEC958_DCU_FF_CTRL_IRQ_FRAME_READ);
return 0;
}
const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops = {
.pcm_new = aiu_fifo_pcm_new,
.probe = aiu_fifo_spdif_dai_probe,
.remove = aiu_fifo_dai_remove,
.trigger = fifo_spdif_trigger,
.prepare = fifo_spdif_prepare,
.hw_params = fifo_spdif_hw_params,
.startup = aiu_fifo_startup,
.shutdown = aiu_fifo_shutdown,
};
int aiu_fifo_spdif_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct aiu *aiu = snd_soc_component_get_drvdata(component);
struct aiu_fifo *fifo;
int ret;
ret = aiu_fifo_dai_probe(dai);
if (ret)
return ret;
fifo = snd_soc_dai_dma_data_get_playback(dai);
fifo->pcm = &fifo_spdif_pcm;
fifo->mem_offset = AIU_MEM_IEC958_START;
fifo->fifo_block = 1;
fifo->pclk = aiu->spdif.clks[PCLK].clk;
fifo->irq = aiu->spdif.irq;
return 0;
}
+215
View File
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "aiu-fifo.h"
#define AIU_MEM_START 0x00
#define AIU_MEM_RD 0x04
#define AIU_MEM_END 0x08
#define AIU_MEM_MASKS 0x0c
#define AIU_MEM_MASK_CH_RD GENMASK(7, 0)
#define AIU_MEM_MASK_CH_MEM GENMASK(15, 8)
#define AIU_MEM_CONTROL 0x10
#define AIU_MEM_CONTROL_INIT BIT(0)
#define AIU_MEM_CONTROL_FILL_EN BIT(1)
#define AIU_MEM_CONTROL_EMPTY_EN BIT(2)
static struct snd_soc_dai *aiu_fifo_dai(struct snd_pcm_substream *ss)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(ss);
return snd_soc_rtd_to_cpu(rtd, 0);
}
snd_pcm_uframes_t aiu_fifo_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_dai *dai = aiu_fifo_dai(substream);
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int addr;
addr = snd_soc_component_read(component, fifo->mem_offset + AIU_MEM_RD);
return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr);
}
static void aiu_fifo_enable(struct snd_soc_dai *dai, bool enable)
{
struct snd_soc_component *component = dai->component;
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
unsigned int en_mask = (AIU_MEM_CONTROL_FILL_EN |
AIU_MEM_CONTROL_EMPTY_EN);
snd_soc_component_update_bits(component,
fifo->mem_offset + AIU_MEM_CONTROL,
en_mask, enable ? en_mask : 0);
}
int aiu_fifo_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
aiu_fifo_enable(dai, true);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
aiu_fifo_enable(dai, false);
break;
default:
return -EINVAL;
}
return 0;
}
int aiu_fifo_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_component *component = dai->component;
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
snd_soc_component_update_bits(component,
fifo->mem_offset + AIU_MEM_CONTROL,
AIU_MEM_CONTROL_INIT,
AIU_MEM_CONTROL_INIT);
snd_soc_component_update_bits(component,
fifo->mem_offset + AIU_MEM_CONTROL,
AIU_MEM_CONTROL_INIT, 0);
return 0;
}
int aiu_fifo_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_component *component = dai->component;
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
dma_addr_t end;
/* Setup the fifo boundaries */
end = runtime->dma_addr + runtime->dma_bytes - fifo->fifo_block;
snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_START,
runtime->dma_addr);
snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_RD,
runtime->dma_addr);
snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_END,
end);
/* Setup the fifo to read all the memory - no skip */
snd_soc_component_update_bits(component,
fifo->mem_offset + AIU_MEM_MASKS,
AIU_MEM_MASK_CH_RD | AIU_MEM_MASK_CH_MEM,
FIELD_PREP(AIU_MEM_MASK_CH_RD, 0xff) |
FIELD_PREP(AIU_MEM_MASK_CH_MEM, 0xff));
return 0;
}
static irqreturn_t aiu_fifo_isr(int irq, void *dev_id)
{
struct snd_pcm_substream *playback = dev_id;
snd_pcm_period_elapsed(playback);
return IRQ_HANDLED;
}
int aiu_fifo_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
int ret;
snd_soc_set_runtime_hwparams(substream, fifo->pcm);
/*
* Make sure the buffer and period size are multiple of the fifo burst
* size
*/
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
fifo->fifo_block);
if (ret)
return ret;
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
fifo->fifo_block);
if (ret)
return ret;
ret = clk_prepare_enable(fifo->pclk);
if (ret)
return ret;
ret = request_irq(fifo->irq, aiu_fifo_isr, 0, dev_name(dai->dev),
substream);
if (ret)
clk_disable_unprepare(fifo->pclk);
return ret;
}
void aiu_fifo_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
free_irq(fifo->irq, substream);
clk_disable_unprepare(fifo->pclk);
}
int aiu_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai)
{
struct snd_card *card = rtd->card->snd_card;
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
size_t size = fifo->pcm->buffer_bytes_max;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
card->dev, size, size);
return 0;
}
int aiu_fifo_dai_probe(struct snd_soc_dai *dai)
{
struct aiu_fifo *fifo;
fifo = kzalloc_obj(*fifo);
if (!fifo)
return -ENOMEM;
snd_soc_dai_dma_data_set_playback(dai, fifo);
return 0;
}
int aiu_fifo_dai_remove(struct snd_soc_dai *dai)
{
struct aiu_fifo *fifo = snd_soc_dai_dma_data_get_playback(dai);
kfree(fifo);
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Copyright (c) 2020 BayLibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_AIU_FIFO_H
#define _MESON_AIU_FIFO_H
struct snd_pcm_hardware;
struct snd_soc_component_driver;
struct snd_soc_dai_driver;
struct clk;
struct snd_pcm_ops;
struct snd_pcm_substream;
struct snd_soc_dai;
struct snd_pcm_hw_params;
struct platform_device;
struct aiu_fifo {
const struct snd_pcm_hardware *pcm;
unsigned int mem_offset;
unsigned int fifo_block;
struct clk *pclk;
int irq;
};
int aiu_fifo_dai_probe(struct snd_soc_dai *dai);
int aiu_fifo_dai_remove(struct snd_soc_dai *dai);
snd_pcm_uframes_t aiu_fifo_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
int aiu_fifo_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai);
int aiu_fifo_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
int aiu_fifo_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
int aiu_fifo_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
void aiu_fifo_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
int aiu_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai);
#endif /* _MESON_AIU_FIFO_H */
+358
View File
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <dt-bindings/sound/meson-aiu.h>
#include "aiu.h"
#include "aiu-fifo.h"
#define AIU_I2S_MISC_958_SRC_SHIFT 3
static const char * const aiu_spdif_encode_sel_texts[] = {
"SPDIF", "I2S",
};
static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC,
AIU_I2S_MISC_958_SRC_SHIFT,
aiu_spdif_encode_sel_texts);
static const struct snd_kcontrol_new aiu_spdif_encode_mux =
SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum);
static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
&aiu_spdif_encode_mux),
};
static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = {
{ "I2S Encoder Playback", NULL, "I2S FIFO Playback" },
{ "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" },
{ "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" },
{ "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" },
};
int aiu_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name,
unsigned int component_id)
{
struct snd_soc_dai *dai;
int id;
if (args->args_count != 2)
return -EINVAL;
if (args->args[0] != component_id)
return -EINVAL;
id = args->args[1];
if (id < 0 || id >= component->num_dai)
return -EINVAL;
for_each_component_dais(component, dai) {
if (id == 0)
break;
id--;
}
*dai_name = dai->driver->name;
return 0;
}
static int aiu_cpu_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name)
{
return aiu_of_xlate_dai_name(component, args, dai_name, AIU_CPU);
}
static int aiu_cpu_component_probe(struct snd_soc_component *component)
{
struct aiu *aiu = snd_soc_component_get_drvdata(component);
/* Required for the SPDIF Source control operation */
return clk_prepare_enable(aiu->i2s.clks[PCLK].clk);
}
static void aiu_cpu_component_remove(struct snd_soc_component *component)
{
struct aiu *aiu = snd_soc_component_get_drvdata(component);
clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
}
static const struct snd_soc_component_driver aiu_cpu_component = {
.name = "AIU CPU",
.dapm_widgets = aiu_cpu_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(aiu_cpu_dapm_widgets),
.dapm_routes = aiu_cpu_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(aiu_cpu_dapm_routes),
.of_xlate_dai_name = aiu_cpu_of_xlate_dai_name,
.pointer = aiu_fifo_pointer,
.probe = aiu_cpu_component_probe,
.remove = aiu_cpu_component_remove,
#ifdef CONFIG_DEBUG_FS
.debugfs_prefix = "cpu",
#endif
};
static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = {
[CPU_I2S_FIFO] = {
.name = "I2S FIFO",
.playback = {
.stream_name = "I2S FIFO Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 192000,
.formats = AIU_FORMATS,
},
.ops = &aiu_fifo_i2s_dai_ops,
},
[CPU_SPDIF_FIFO] = {
.name = "SPDIF FIFO",
.playback = {
.stream_name = "SPDIF FIFO Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 192000,
.formats = AIU_FORMATS,
},
.ops = &aiu_fifo_spdif_dai_ops,
},
[CPU_I2S_ENCODER] = {
.name = "I2S Encoder",
.playback = {
.stream_name = "I2S Encoder Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = AIU_FORMATS,
},
.ops = &aiu_encoder_i2s_dai_ops,
},
[CPU_SPDIF_ENCODER] = {
.name = "SPDIF Encoder",
.playback = {
.stream_name = "SPDIF Encoder Playback",
.channels_min = 2,
.channels_max = 2,
.rates = (SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000),
.formats = AIU_FORMATS,
},
.ops = &aiu_encoder_spdif_dai_ops,
}
};
static const struct regmap_config aiu_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0x2ac,
};
static int aiu_clk_bulk_get(struct device *dev,
const char * const *ids,
unsigned int num,
struct aiu_interface *interface)
{
struct clk_bulk_data *clks;
int i, ret;
clks = devm_kcalloc(dev, num, sizeof(*clks), GFP_KERNEL);
if (!clks)
return -ENOMEM;
for (i = 0; i < num; i++)
clks[i].id = ids[i];
ret = devm_clk_bulk_get(dev, num, clks);
if (ret < 0)
return ret;
interface->clks = clks;
interface->clk_num = num;
return 0;
}
static const char * const aiu_i2s_ids[] = {
[PCLK] = "i2s_pclk",
[AOCLK] = "i2s_aoclk",
[MCLK] = "i2s_mclk",
[MIXER] = "i2s_mixer",
};
static const char * const aiu_spdif_ids[] = {
[PCLK] = "spdif_pclk",
[AOCLK] = "spdif_aoclk",
[MCLK] = "spdif_mclk_sel"
};
static int aiu_clk_get(struct device *dev)
{
struct aiu *aiu = dev_get_drvdata(dev);
struct clk *pclk;
int ret;
pclk = devm_clk_get_enabled(dev, "pclk");
if (IS_ERR(pclk))
return dev_err_probe(dev, PTR_ERR(pclk), "Can't get the aiu pclk\n");
aiu->spdif_mclk = devm_clk_get(dev, "spdif_mclk");
if (IS_ERR(aiu->spdif_mclk))
return dev_err_probe(dev, PTR_ERR(aiu->spdif_mclk),
"Can't get the aiu spdif master clock\n");
ret = aiu_clk_bulk_get(dev, aiu_i2s_ids, ARRAY_SIZE(aiu_i2s_ids),
&aiu->i2s);
if (ret)
return dev_err_probe(dev, ret, "Can't get the i2s clocks\n");
ret = aiu_clk_bulk_get(dev, aiu_spdif_ids, ARRAY_SIZE(aiu_spdif_ids),
&aiu->spdif);
if (ret)
return dev_err_probe(dev, ret, "Can't get the spdif clocks\n");
return ret;
}
static int aiu_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
void __iomem *regs;
struct regmap *map;
struct aiu *aiu;
int ret;
aiu = devm_kzalloc(dev, sizeof(*aiu), GFP_KERNEL);
if (!aiu)
return -ENOMEM;
aiu->platform = device_get_match_data(dev);
if (!aiu->platform)
return -ENODEV;
platform_set_drvdata(pdev, aiu);
ret = device_reset(dev);
if (ret)
return dev_err_probe(dev, ret, "Failed to reset device\n");
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
map = devm_regmap_init_mmio(dev, regs, &aiu_regmap_cfg);
if (IS_ERR(map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(map));
return PTR_ERR(map);
}
aiu->i2s.irq = platform_get_irq_byname(pdev, "i2s");
if (aiu->i2s.irq < 0)
return aiu->i2s.irq;
aiu->spdif.irq = platform_get_irq_byname(pdev, "spdif");
if (aiu->spdif.irq < 0)
return aiu->spdif.irq;
ret = aiu_clk_get(dev);
if (ret)
return ret;
/* Register the cpu component of the aiu */
ret = snd_soc_register_component(dev, &aiu_cpu_component,
aiu_cpu_dai_drv,
ARRAY_SIZE(aiu_cpu_dai_drv));
if (ret) {
dev_err(dev, "Failed to register cpu component\n");
return ret;
}
/* Register the hdmi codec control component */
ret = aiu_hdmi_ctrl_register_component(dev);
if (ret) {
dev_err(dev, "Failed to register hdmi control component\n");
goto err;
}
/* Register the internal dac control component on gxl */
if (aiu->platform->has_acodec) {
ret = aiu_acodec_ctrl_register_component(dev);
if (ret) {
dev_err(dev,
"Failed to register acodec control component\n");
goto err;
}
}
return 0;
err:
snd_soc_unregister_component(dev);
return ret;
}
static void aiu_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
}
static const struct aiu_platform_data aiu_gxbb_pdata = {
.has_acodec = false,
.has_clk_ctrl_more_i2s_div = true,
};
static const struct aiu_platform_data aiu_gxl_pdata = {
.has_acodec = true,
.has_clk_ctrl_more_i2s_div = true,
};
static const struct aiu_platform_data aiu_meson8_pdata = {
.has_acodec = false,
.has_clk_ctrl_more_i2s_div = false,
};
static const struct of_device_id aiu_of_match[] = {
{ .compatible = "amlogic,aiu-gxbb", .data = &aiu_gxbb_pdata },
{ .compatible = "amlogic,aiu-gxl", .data = &aiu_gxl_pdata },
{ .compatible = "amlogic,aiu-meson8", .data = &aiu_meson8_pdata },
{ .compatible = "amlogic,aiu-meson8b", .data = &aiu_meson8_pdata },
{}
};
MODULE_DEVICE_TABLE(of, aiu_of_match);
static struct platform_driver aiu_pdrv = {
.probe = aiu_probe,
.remove = aiu_remove,
.driver = {
.name = "meson-aiu",
.of_match_table = aiu_of_match,
},
};
module_platform_driver(aiu_pdrv);
MODULE_DESCRIPTION("Meson AIU Driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+88
View File
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Copyright (c) 2018 BayLibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_AIU_H
#define _MESON_AIU_H
struct clk;
struct clk_bulk_data;
struct device;
struct of_phandle_args;
struct snd_soc_dai;
struct snd_soc_dai_ops;
enum aiu_clk_ids {
PCLK = 0,
AOCLK,
MCLK,
MIXER
};
struct aiu_interface {
struct clk_bulk_data *clks;
unsigned int clk_num;
int irq;
};
struct aiu_platform_data {
bool has_acodec;
bool has_clk_ctrl_more_i2s_div;
};
struct aiu {
struct clk *spdif_mclk;
struct aiu_interface i2s;
struct aiu_interface spdif;
const struct aiu_platform_data *platform;
};
#define AIU_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_LE | \
SNDRV_PCM_FMTBIT_S24_LE)
int aiu_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name,
unsigned int component_id);
int aiu_hdmi_ctrl_register_component(struct device *dev);
int aiu_acodec_ctrl_register_component(struct device *dev);
int aiu_fifo_i2s_dai_probe(struct snd_soc_dai *dai);
int aiu_fifo_spdif_dai_probe(struct snd_soc_dai *dai);
extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops;
#define AIU_IEC958_BPF 0x000
#define AIU_958_MISC 0x010
#define AIU_IEC958_DCU_FF_CTRL 0x01c
#define AIU_958_CHSTAT_L0 0x020
#define AIU_958_CHSTAT_L1 0x024
#define AIU_958_CTRL 0x028
#define AIU_I2S_SOURCE_DESC 0x034
#define AIU_I2S_DAC_CFG 0x040
#define AIU_I2S_SYNC 0x044
#define AIU_I2S_MISC 0x048
#define AIU_RST_SOFT 0x054
#define AIU_CLK_CTRL 0x058
#define AIU_CLK_CTRL_MORE 0x064
#define AIU_CODEC_DAC_LRCLK_CTRL 0x0a0
#define AIU_HDMI_CLK_DATA_CTRL 0x0a8
#define AIU_ACODEC_CTRL 0x0b0
#define AIU_958_CHSTAT_R0 0x0c0
#define AIU_958_CHSTAT_R1 0x0c4
#define AIU_MEM_I2S_START 0x180
#define AIU_MEM_I2S_MASKS 0x18c
#define AIU_MEM_I2S_CONTROL 0x190
#define AIU_MEM_IEC958_START 0x194
#define AIU_MEM_IEC958_CONTROL 0x1a4
#define AIU_MEM_I2S_BUF_CNTL 0x1d8
#define AIU_MEM_IEC958_BUF_CNTL 0x1fc
#endif /* _MESON_AIU_H */
+373
View File
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-tdm.h"
#include "meson-card.h"
struct axg_dai_link_tdm_mask {
u32 tx;
u32 rx;
};
struct axg_dai_link_tdm_data {
unsigned int mclk_fs;
unsigned int slots;
unsigned int slot_width;
u32 *tx_mask;
u32 *rx_mask;
struct axg_dai_link_tdm_mask *codec_masks;
};
/*
* Base params for the codec to codec links
* Those will be over-written by the CPU side of the link
*/
static const struct snd_soc_pcm_stream codec_params = {
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rate_min = 5525,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 8,
};
static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card);
struct axg_dai_link_tdm_data *be =
(struct axg_dai_link_tdm_data *)priv->link_data[rtd->id];
return meson_card_i2s_set_sysclk(substream, params, be->mclk_fs);
}
static const struct snd_soc_ops axg_card_tdm_be_ops = {
.hw_params = axg_card_tdm_be_hw_params,
};
static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd)
{
struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card);
struct axg_dai_link_tdm_data *be =
(struct axg_dai_link_tdm_data *)priv->link_data[rtd->id];
struct snd_soc_dai *codec_dai;
int ret, i;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
ret = snd_soc_dai_set_tdm_slot(codec_dai,
be->codec_masks[i].tx,
be->codec_masks[i].rx,
be->slots, be->slot_width);
if (ret && ret != -ENOTSUPP) {
dev_err(codec_dai->dev,
"setting tdm link slots failed\n");
return ret;
}
}
ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), be->tx_mask, be->rx_mask,
be->slots, be->slot_width);
if (ret) {
dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n");
return ret;
}
return 0;
}
static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd)
{
struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card);
struct axg_dai_link_tdm_data *be =
(struct axg_dai_link_tdm_data *)priv->link_data[rtd->id];
int ret;
/* The loopback rx_mask is the pad tx_mask */
ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), NULL, be->tx_mask,
be->slots, be->slot_width);
if (ret) {
dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n");
return ret;
}
return 0;
}
static int axg_card_add_tdm_loopback(struct snd_soc_card *card,
int *index)
{
struct meson_card *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link *pad;
struct snd_soc_dai_link *lb;
struct snd_soc_dai_link_component *dlc;
int ret;
/* extend links */
ret = meson_card_reallocate_links(card, card->num_links + 1);
if (ret)
return ret;
pad = &card->dai_link[*index];
lb = &card->dai_link[*index + 1];
lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name);
if (!lb->name)
return -ENOMEM;
dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL);
if (!dlc)
return -ENOMEM;
lb->cpus = dlc;
lb->codecs = &snd_soc_dummy_dlc;
lb->num_cpus = 1;
lb->num_codecs = 1;
lb->stream_name = lb->name;
lb->cpus->of_node = pad->cpus->of_node;
lb->cpus->dai_name = "TDM Loopback";
lb->capture_only = 1;
lb->no_pcm = 1;
lb->ops = &axg_card_tdm_be_ops;
lb->init = axg_card_tdm_dai_lb_init;
/* Provide the same link data to the loopback */
priv->link_data[*index + 1] = priv->link_data[*index];
/*
* axg_card_clean_references() will iterate over this link,
* make sure the node count is balanced
*/
of_node_get(lb->cpus->of_node);
/* Let add_links continue where it should */
*index += 1;
return 0;
}
static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node,
struct axg_dai_link_tdm_data *be)
{
char propname[32];
u32 tx, rx;
int i;
be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
sizeof(*be->tx_mask), GFP_KERNEL);
be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
sizeof(*be->rx_mask), GFP_KERNEL);
if (!be->tx_mask || !be->rx_mask)
return -ENOMEM;
for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) {
snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i);
snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]);
tx = max(tx, be->tx_mask[i]);
}
/* Disable playback is the interface has no tx slots */
if (!tx)
link->capture_only = 1;
for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) {
snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i);
snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]);
rx = max(rx, be->rx_mask[i]);
}
/* Disable capture is the interface has no rx slots */
if (!rx)
link->playback_only = 1;
/* ... but the interface should at least have one direction */
if (!tx && !rx) {
dev_err(card->dev, "tdm link has no cpu slots\n");
return -EINVAL;
}
of_property_read_u32(node, "dai-tdm-slot-num", &be->slots);
if (!be->slots) {
/*
* If the slot number is not provided, set it such as it
* accommodates the largest mask
*/
be->slots = fls(max(tx, rx));
} else if (be->slots < fls(max(tx, rx)) || be->slots > 32) {
/*
* Error if the slots can't accommodate the largest mask or
* if it is just too big
*/
dev_err(card->dev, "bad slot number\n");
return -EINVAL;
}
of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width);
return 0;
}
static int axg_card_parse_codecs_masks(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node,
struct axg_dai_link_tdm_data *be)
{
struct axg_dai_link_tdm_mask *codec_mask;
codec_mask = devm_kcalloc(card->dev, link->num_codecs,
sizeof(*codec_mask), GFP_KERNEL);
if (!codec_mask)
return -ENOMEM;
be->codec_masks = codec_mask;
for_each_child_of_node_scoped(node, np) {
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask",
&codec_mask->rx);
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask",
&codec_mask->tx);
codec_mask++;
}
return 0;
}
static int axg_card_parse_tdm(struct snd_soc_card *card,
struct device_node *node,
int *index)
{
struct meson_card *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link *link = &card->dai_link[*index];
struct axg_dai_link_tdm_data *be;
int ret;
/* Allocate tdm link parameters */
be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL);
if (!be)
return -ENOMEM;
priv->link_data[*index] = be;
/* Setup tdm link */
link->ops = &axg_card_tdm_be_ops;
link->init = axg_card_tdm_dai_init;
link->dai_fmt = meson_card_parse_daifmt(node, link->cpus->of_node);
of_property_read_u32(node, "mclk-fs", &be->mclk_fs);
ret = axg_card_parse_cpu_tdm_slots(card, link, node, be);
if (ret) {
dev_err(card->dev, "error parsing tdm link slots\n");
return ret;
}
ret = axg_card_parse_codecs_masks(card, link, node, be);
if (ret)
return ret;
/* Add loopback if the pad dai has playback */
if (!link->capture_only) {
ret = axg_card_add_tdm_loopback(card, index);
if (ret)
return ret;
}
return 0;
}
static int axg_card_cpu_is_capture_fe(struct device_node *np)
{
return of_device_is_compatible(np, DT_PREFIX "axg-toddr");
}
static int axg_card_cpu_is_playback_fe(struct device_node *np)
{
return of_device_is_compatible(np, DT_PREFIX "axg-frddr");
}
static int axg_card_cpu_is_tdm_iface(struct device_node *np)
{
return of_device_is_compatible(np, DT_PREFIX "axg-tdm-iface");
}
static int axg_card_cpu_is_codec(struct device_node *np)
{
return of_device_is_compatible(np, DT_PREFIX "g12a-tohdmitx") ||
of_device_is_compatible(np, DT_PREFIX "g12a-toacodec");
}
static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np,
int *index)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[*index];
struct snd_soc_dai_link_component *cpu;
int ret;
cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL);
if (!cpu)
return -ENOMEM;
dai_link->cpus = cpu;
dai_link->num_cpus = 1;
dai_link->nonatomic = true;
ret = meson_card_parse_dai(card, np, dai_link->cpus);
if (ret)
return ret;
if (axg_card_cpu_is_playback_fe(dai_link->cpus->of_node))
return meson_card_set_fe_link(card, dai_link, np, true);
else if (axg_card_cpu_is_capture_fe(dai_link->cpus->of_node))
return meson_card_set_fe_link(card, dai_link, np, false);
ret = meson_card_set_be_link(card, dai_link, np);
if (ret)
return ret;
if (axg_card_cpu_is_codec(dai_link->cpus->of_node)) {
dai_link->c2c_params = &codec_params;
dai_link->num_c2c_params = 1;
} else {
dai_link->no_pcm = 1;
if (axg_card_cpu_is_tdm_iface(dai_link->cpus->of_node))
ret = axg_card_parse_tdm(card, np, index);
}
return ret;
}
static const struct meson_card_match_data axg_card_match_data = {
.add_link = axg_card_add_link,
};
static const struct of_device_id axg_card_of_match[] = {
{
.compatible = "amlogic,axg-sound-card",
.data = &axg_card_match_data,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_card_of_match);
static struct platform_driver axg_card_pdrv = {
.probe = meson_card_probe,
.remove = meson_card_remove,
.driver = {
.name = "axg-sound-card",
.of_match_table = axg_card_of_match,
},
};
module_platform_driver(axg_card_pdrv);
MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+398
View File
@@ -0,0 +1,398 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-fifo.h"
/*
* This file implements the platform operations common to the playback and
* capture frontend DAI. The logic behind this two types of fifo is very
* similar but some difference exist.
* These differences are handled in the respective DAI drivers
*/
static const struct snd_pcm_hardware axg_fifo_hw = {
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = AXG_FIFO_FORMATS,
.rate_min = 5512,
.rate_max = 768000,
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.period_bytes_min = AXG_FIFO_BURST,
.period_bytes_max = UINT_MAX,
.periods_min = 2,
.periods_max = UINT_MAX,
/* No real justification for this */
.buffer_bytes_max = 1 * 1024 * 1024,
};
static struct snd_soc_dai *axg_fifo_dai(struct snd_pcm_substream *ss)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(ss);
return snd_soc_rtd_to_cpu(rtd, 0);
}
static struct axg_fifo *axg_fifo_data(struct snd_pcm_substream *ss)
{
struct snd_soc_dai *dai = axg_fifo_dai(ss);
return snd_soc_dai_get_drvdata(dai);
}
static struct device *axg_fifo_dev(struct snd_pcm_substream *ss)
{
struct snd_soc_dai *dai = axg_fifo_dai(ss);
return dai->dev;
}
static void __dma_enable(struct axg_fifo *fifo, bool enable)
{
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_DMA_EN,
enable ? CTRL0_DMA_EN : 0);
}
int axg_fifo_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *ss, int cmd)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
__dma_enable(fifo, true);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
__dma_enable(fifo, false);
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_trigger);
snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *ss)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
struct snd_pcm_runtime *runtime = ss->runtime;
unsigned int addr;
regmap_read(fifo->map, FIFO_STATUS2, &addr);
return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr);
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_pointer);
int axg_fifo_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = ss->runtime;
struct axg_fifo *fifo = axg_fifo_data(ss);
unsigned int burst_num, period, threshold, irq_en;
dma_addr_t end_ptr;
period = params_period_bytes(params);
/* Setup dma memory pointers */
end_ptr = runtime->dma_addr + runtime->dma_bytes - AXG_FIFO_BURST;
regmap_write(fifo->map, FIFO_START_ADDR, runtime->dma_addr);
regmap_write(fifo->map, FIFO_FINISH_ADDR, end_ptr);
/* Setup interrupt periodicity */
burst_num = period / AXG_FIFO_BURST;
regmap_write(fifo->map, FIFO_INT_ADDR, burst_num);
/*
* Start the fifo request on the smallest of the following:
* - Half the fifo size
* - Half the period size
*/
threshold = min(period / 2, fifo->depth / 2);
/*
* With the threshold in bytes, register value is:
* V = (threshold / burst) - 1
*/
threshold /= AXG_FIFO_BURST;
regmap_field_write(fifo->field_threshold,
threshold ? threshold - 1 : 0);
/* Enable irq if necessary */
irq_en = runtime->no_period_wakeup ? 0 : FIFO_INT_COUNT_REPEAT;
regmap_update_bits(fifo->map, FIFO_CTRL0,
CTRL0_INT_EN,
FIELD_PREP(CTRL0_INT_EN, irq_en));
return 0;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_hw_params);
int g12a_fifo_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *params)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
struct snd_pcm_runtime *runtime = ss->runtime;
int ret;
ret = axg_fifo_pcm_hw_params(component, ss, params);
if (ret)
return ret;
/* Set the initial memory address of the DMA */
regmap_write(fifo->map, FIFO_INIT_ADDR, runtime->dma_addr);
return 0;
}
EXPORT_SYMBOL_GPL(g12a_fifo_pcm_hw_params);
int axg_fifo_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *ss)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
/* Disable irqs */
regmap_update_bits(fifo->map, FIFO_CTRL0,
CTRL0_INT_EN, 0);
return 0;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_hw_free);
static void axg_fifo_ack_irq(struct axg_fifo *fifo, u8 mask)
{
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_INT_CLR,
FIELD_PREP(CTRL1_INT_CLR, mask));
/* Clear must also be cleared */
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_INT_CLR,
FIELD_PREP(CTRL1_INT_CLR, 0));
}
static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id)
{
struct snd_pcm_substream *ss = dev_id;
struct axg_fifo *fifo = axg_fifo_data(ss);
unsigned int status;
regmap_read(fifo->map, FIFO_STATUS1, &status);
status = FIELD_GET(STATUS1_INT_STS, status);
axg_fifo_ack_irq(fifo, status);
if (status & ~FIFO_INT_COUNT_REPEAT)
dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n",
status);
if (status & FIFO_INT_COUNT_REPEAT) {
snd_pcm_period_elapsed(ss);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
int axg_fifo_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *ss)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
struct device *dev = axg_fifo_dev(ss);
int ret;
snd_soc_set_runtime_hwparams(ss, &axg_fifo_hw);
/*
* Make sure the buffer and period size are multiple of the FIFO
* burst
*/
ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
AXG_FIFO_BURST);
if (ret)
return ret;
ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
AXG_FIFO_BURST);
if (ret)
return ret;
/* Use the threaded irq handler only with non-atomic links */
ret = request_threaded_irq(fifo->irq, NULL,
axg_fifo_pcm_irq_block,
IRQF_ONESHOT, dev_name(dev), ss);
if (ret)
return ret;
/* Enable pclk to access registers and clock the fifo ip */
ret = clk_prepare_enable(fifo->pclk);
if (ret)
goto free_irq;
/* Setup status2 so it reports the memory pointer */
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_STATUS2_SEL,
FIELD_PREP(CTRL1_STATUS2_SEL, STATUS2_SEL_DDR_READ));
/* Make sure the dma is initially disabled */
__dma_enable(fifo, false);
/* Disable irqs until params are ready */
regmap_update_bits(fifo->map, FIFO_CTRL0,
CTRL0_INT_EN, 0);
/* Clear any pending interrupt */
axg_fifo_ack_irq(fifo, FIFO_INT_MASK);
/* Take memory arbitror out of reset */
ret = reset_control_deassert(fifo->arb);
if (ret)
goto free_clk;
return 0;
free_clk:
clk_disable_unprepare(fifo->pclk);
free_irq:
free_irq(fifo->irq, ss);
return ret;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_open);
int axg_fifo_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *ss)
{
struct axg_fifo *fifo = axg_fifo_data(ss);
int ret;
/* Put the memory arbitror back in reset */
ret = reset_control_assert(fifo->arb);
/* Disable fifo ip and register access */
clk_disable_unprepare(fifo->pclk);
/* remove IRQ */
free_irq(fifo->irq, ss);
return ret;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_close);
int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type)
{
struct snd_card *card = rtd->card->snd_card;
size_t size = axg_fifo_hw.buffer_bytes_max;
snd_pcm_set_managed_buffer(rtd->pcm->streams[type].substream,
SNDRV_DMA_TYPE_DEV, card->dev,
size, size);
return 0;
}
EXPORT_SYMBOL_GPL(axg_fifo_pcm_new);
static const struct regmap_config axg_fifo_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = FIFO_CTRL2,
};
int axg_fifo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct axg_fifo_match_data *data;
struct axg_fifo *fifo;
void __iomem *regs;
int ret;
data = of_device_get_match_data(dev);
if (!data) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL);
if (!fifo)
return -ENOMEM;
platform_set_drvdata(pdev, fifo);
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
fifo->map = devm_regmap_init_mmio(dev, regs, &axg_fifo_regmap_cfg);
if (IS_ERR(fifo->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(fifo->map));
return PTR_ERR(fifo->map);
}
fifo->pclk = devm_clk_get(dev, NULL);
if (IS_ERR(fifo->pclk))
return dev_err_probe(dev, PTR_ERR(fifo->pclk), "failed to get pclk\n");
fifo->arb = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(fifo->arb))
return dev_err_probe(dev, PTR_ERR(fifo->arb), "failed to get arb reset\n");
fifo->irq = of_irq_get(dev->of_node, 0);
if (fifo->irq <= 0) {
dev_err(dev, "failed to get irq: %d\n", fifo->irq);
return fifo->irq;
}
fifo->field_threshold =
devm_regmap_field_alloc(dev, fifo->map, data->field_threshold);
if (IS_ERR(fifo->field_threshold))
return PTR_ERR(fifo->field_threshold);
ret = of_property_read_u32(dev->of_node, "amlogic,fifo-depth",
&fifo->depth);
if (ret) {
/* Error out for anything but a missing property */
if (ret != -EINVAL)
return ret;
/*
* If the property is missing, it might be because of an old
* DT. In such case, assume the smallest known fifo depth
*/
fifo->depth = 256;
dev_warn(dev, "fifo depth not found, assume %u bytes\n",
fifo->depth);
}
return devm_snd_soc_register_component(dev, data->component_drv,
data->dai_drv, 1);
}
EXPORT_SYMBOL_GPL(axg_fifo_probe);
MODULE_DESCRIPTION("Amlogic AXG/G12A fifo driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+95
View File
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Copyright (c) 2018 BayLibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_AXG_FIFO_H
#define _MESON_AXG_FIFO_H
struct clk;
struct platform_device;
struct reg_field;
struct regmap;
struct regmap_field;
struct reset_control;
struct snd_soc_component_driver;
struct snd_soc_dai;
struct snd_soc_dai_driver;
struct snd_soc_pcm_runtime;
#define AXG_FIFO_CH_MAX 128
#define AXG_FIFO_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE | \
SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
#define AXG_FIFO_BURST 8
#define FIFO_INT_ADDR_FINISH BIT(0)
#define FIFO_INT_ADDR_INT BIT(1)
#define FIFO_INT_COUNT_REPEAT BIT(2)
#define FIFO_INT_COUNT_ONCE BIT(3)
#define FIFO_INT_FIFO_ZERO BIT(4)
#define FIFO_INT_FIFO_DEPTH BIT(5)
#define FIFO_INT_MASK GENMASK(7, 0)
#define FIFO_CTRL0 0x00
#define CTRL0_DMA_EN BIT(31)
#define CTRL0_INT_EN GENMASK(23, 16)
#define CTRL0_SEL_MASK GENMASK(2, 0)
#define CTRL0_SEL_SHIFT 0
#define FIFO_CTRL1 0x04
#define CTRL1_INT_CLR GENMASK(7, 0)
#define CTRL1_STATUS2_SEL GENMASK(11, 8)
#define STATUS2_SEL_DDR_READ 0
#define CTRL1_FRDDR_DEPTH GENMASK(31, 24)
#define FIFO_START_ADDR 0x08
#define FIFO_FINISH_ADDR 0x0c
#define FIFO_INT_ADDR 0x10
#define FIFO_STATUS1 0x14
#define STATUS1_INT_STS GENMASK(7, 0)
#define FIFO_STATUS2 0x18
#define FIFO_INIT_ADDR 0x24
#define FIFO_CTRL2 0x28
struct axg_fifo {
struct regmap *map;
struct clk *pclk;
struct reset_control *arb;
struct regmap_field *field_threshold;
unsigned int depth;
int irq;
};
struct axg_fifo_match_data {
const struct snd_soc_component_driver *component_drv;
struct snd_soc_dai_driver *dai_drv;
struct reg_field field_threshold;
};
int axg_fifo_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *ss);
int axg_fifo_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *ss);
int axg_fifo_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *params);
int g12a_fifo_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *params);
int axg_fifo_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *ss);
snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *ss);
int axg_fifo_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *ss, int cmd);
int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type);
int axg_fifo_probe(struct platform_device *pdev);
#endif /* _MESON_AXG_FIFO_H */
+400
View File
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
/*
* This driver implements the frontend playback DAI of AXG and G12A based SoCs
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-fifo.h"
#define CTRL0_FRDDR_PP_MODE BIT(30)
#define CTRL0_SEL1_EN_SHIFT 3
#define CTRL0_SEL2_SHIFT 4
#define CTRL0_SEL2_EN_SHIFT 7
#define CTRL0_SEL3_SHIFT 8
#define CTRL0_SEL3_EN_SHIFT 11
#define CTRL1_FRDDR_FORCE_FINISH BIT(12)
#define CTRL2_SEL1_SHIFT 0
#define CTRL2_SEL1_EN_SHIFT 4
#define CTRL2_SEL2_SHIFT 8
#define CTRL2_SEL2_EN_SHIFT 12
#define CTRL2_SEL3_SHIFT 16
#define CTRL2_SEL3_EN_SHIFT 20
static int g12a_frddr_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
/* Reset the read pointer to the FIFO_INIT_ADDR */
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_FRDDR_FORCE_FINISH, 0);
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_FRDDR_FORCE_FINISH, CTRL1_FRDDR_FORCE_FINISH);
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_FRDDR_FORCE_FINISH, 0);
return 0;
}
static int axg_frddr_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
unsigned int period, depth, val;
period = params_period_bytes(params);
/* Trim the FIFO depth if the period is small to improve latency */
depth = min(period, fifo->depth);
val = (depth / AXG_FIFO_BURST) - 1;
regmap_update_bits(fifo->map, FIFO_CTRL1, CTRL1_FRDDR_DEPTH,
FIELD_PREP(CTRL1_FRDDR_DEPTH, val));
return 0;
}
static int axg_frddr_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
int ret;
/* Enable pclk to access registers and clock the fifo ip */
ret = clk_prepare_enable(fifo->pclk);
if (ret)
return ret;
/* Apply single buffer mode to the interface */
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0);
return 0;
}
static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(fifo->pclk);
}
static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai)
{
return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK);
}
static const struct snd_soc_dai_ops axg_frddr_ops = {
.hw_params = axg_frddr_dai_hw_params,
.startup = axg_frddr_dai_startup,
.shutdown = axg_frddr_dai_shutdown,
.pcm_new = axg_frddr_pcm_new,
};
static struct snd_soc_dai_driver axg_frddr_dai_drv = {
.name = "FRDDR",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5515,
.rate_max = 768000,
.formats = AXG_FIFO_FORMATS,
},
.ops = &axg_frddr_ops,
};
static const char * const axg_frddr_sel_texts[] = {
"OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", "OUT 5", "OUT 6", "OUT 7",
};
static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
axg_frddr_sel_texts);
static const struct snd_kcontrol_new axg_frddr_out_demux =
SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum);
static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = {
SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0,
&axg_frddr_out_demux),
SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = {
{ "SINK SEL", NULL, "Playback" },
{ "OUT 0", "OUT 0", "SINK SEL" },
{ "OUT 1", "OUT 1", "SINK SEL" },
{ "OUT 2", "OUT 2", "SINK SEL" },
{ "OUT 3", "OUT 3", "SINK SEL" },
{ "OUT 4", "OUT 4", "SINK SEL" },
{ "OUT 5", "OUT 5", "SINK SEL" },
{ "OUT 6", "OUT 6", "SINK SEL" },
{ "OUT 7", "OUT 7", "SINK SEL" },
};
static const struct snd_soc_component_driver axg_frddr_component_drv = {
.dapm_widgets = axg_frddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_frddr_dapm_widgets),
.dapm_routes = axg_frddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_frddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = axg_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data axg_frddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23),
.component_drv = &axg_frddr_component_drv,
.dai_drv = &axg_frddr_dai_drv
};
static const struct snd_soc_dai_ops g12a_frddr_ops = {
.prepare = g12a_frddr_dai_prepare,
.hw_params = axg_frddr_dai_hw_params,
.startup = axg_frddr_dai_startup,
.shutdown = axg_frddr_dai_shutdown,
.pcm_new = axg_frddr_pcm_new,
};
static struct snd_soc_dai_driver g12a_frddr_dai_drv = {
.name = "FRDDR",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5515,
.rate_max = 768000,
.formats = AXG_FIFO_FORMATS,
},
.ops = &g12a_frddr_ops,
};
static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
axg_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT,
axg_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT,
axg_frddr_sel_texts);
static const struct snd_kcontrol_new g12a_frddr_out1_demux =
SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum);
static const struct snd_kcontrol_new g12a_frddr_out2_demux =
SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum);
static const struct snd_kcontrol_new g12a_frddr_out3_demux =
SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum);
static const struct snd_kcontrol_new g12a_frddr_out1_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
CTRL0_SEL1_EN_SHIFT, 1, 0);
static const struct snd_kcontrol_new g12a_frddr_out2_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
CTRL0_SEL2_EN_SHIFT, 1, 0);
static const struct snd_kcontrol_new g12a_frddr_out3_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
CTRL0_SEL3_EN_SHIFT, 1, 0);
static const struct snd_soc_dapm_widget g12a_frddr_dapm_widgets[] = {
SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out1_enable),
SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out2_enable),
SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out3_enable),
SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out1_demux),
SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out2_demux),
SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0,
&g12a_frddr_out3_demux),
SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route g12a_frddr_dapm_routes[] = {
{ "SRC 1", NULL, "Playback" },
{ "SRC 2", NULL, "Playback" },
{ "SRC 3", NULL, "Playback" },
{ "SRC 1 EN", "Switch", "SRC 1" },
{ "SRC 2 EN", "Switch", "SRC 2" },
{ "SRC 3 EN", "Switch", "SRC 3" },
{ "SINK 1 SEL", NULL, "SRC 1 EN" },
{ "SINK 2 SEL", NULL, "SRC 2 EN" },
{ "SINK 3 SEL", NULL, "SRC 3 EN" },
{ "OUT 0", "OUT 0", "SINK 1 SEL" },
{ "OUT 1", "OUT 1", "SINK 1 SEL" },
{ "OUT 2", "OUT 2", "SINK 1 SEL" },
{ "OUT 3", "OUT 3", "SINK 1 SEL" },
{ "OUT 4", "OUT 4", "SINK 1 SEL" },
{ "OUT 5", "OUT 5", "SINK 1 SEL" },
{ "OUT 6", "OUT 6", "SINK 1 SEL" },
{ "OUT 7", "OUT 7", "SINK 1 SEL" },
{ "OUT 0", "OUT 0", "SINK 2 SEL" },
{ "OUT 1", "OUT 1", "SINK 2 SEL" },
{ "OUT 2", "OUT 2", "SINK 2 SEL" },
{ "OUT 3", "OUT 3", "SINK 2 SEL" },
{ "OUT 4", "OUT 4", "SINK 2 SEL" },
{ "OUT 5", "OUT 5", "SINK 2 SEL" },
{ "OUT 6", "OUT 6", "SINK 2 SEL" },
{ "OUT 7", "OUT 7", "SINK 2 SEL" },
{ "OUT 0", "OUT 0", "SINK 3 SEL" },
{ "OUT 1", "OUT 1", "SINK 3 SEL" },
{ "OUT 2", "OUT 2", "SINK 3 SEL" },
{ "OUT 3", "OUT 3", "SINK 3 SEL" },
{ "OUT 4", "OUT 4", "SINK 3 SEL" },
{ "OUT 5", "OUT 5", "SINK 3 SEL" },
{ "OUT 6", "OUT 6", "SINK 3 SEL" },
{ "OUT 7", "OUT 7", "SINK 3 SEL" },
};
static const struct snd_soc_component_driver g12a_frddr_component_drv = {
.dapm_widgets = g12a_frddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(g12a_frddr_dapm_widgets),
.dapm_routes = g12a_frddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = g12a_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data g12a_frddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23),
.component_drv = &g12a_frddr_component_drv,
.dai_drv = &g12a_frddr_dai_drv
};
/* On SM1, the output selection in on CTRL2 */
static const struct snd_kcontrol_new sm1_frddr_out1_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
CTRL2_SEL1_EN_SHIFT, 1, 0);
static const struct snd_kcontrol_new sm1_frddr_out2_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
CTRL2_SEL2_EN_SHIFT, 1, 0);
static const struct snd_kcontrol_new sm1_frddr_out3_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
CTRL2_SEL3_EN_SHIFT, 1, 0);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel1_enum, FIFO_CTRL2, CTRL2_SEL1_SHIFT,
axg_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel2_enum, FIFO_CTRL2, CTRL2_SEL2_SHIFT,
axg_frddr_sel_texts);
static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel3_enum, FIFO_CTRL2, CTRL2_SEL3_SHIFT,
axg_frddr_sel_texts);
static const struct snd_kcontrol_new sm1_frddr_out1_demux =
SOC_DAPM_ENUM("Output Src 1", sm1_frddr_sel1_enum);
static const struct snd_kcontrol_new sm1_frddr_out2_demux =
SOC_DAPM_ENUM("Output Src 2", sm1_frddr_sel2_enum);
static const struct snd_kcontrol_new sm1_frddr_out3_demux =
SOC_DAPM_ENUM("Output Src 3", sm1_frddr_sel3_enum);
static const struct snd_soc_dapm_widget sm1_frddr_dapm_widgets[] = {
SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out1_enable),
SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out2_enable),
SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out3_enable),
SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out1_demux),
SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out2_demux),
SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0,
&sm1_frddr_out3_demux),
SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_component_driver sm1_frddr_component_drv = {
.dapm_widgets = sm1_frddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sm1_frddr_dapm_widgets),
.dapm_routes = g12a_frddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = g12a_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data sm1_frddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23),
.component_drv = &sm1_frddr_component_drv,
.dai_drv = &g12a_frddr_dai_drv
};
static const struct of_device_id axg_frddr_of_match[] = {
{
.compatible = "amlogic,axg-frddr",
.data = &axg_frddr_match_data,
}, {
.compatible = "amlogic,g12a-frddr",
.data = &g12a_frddr_match_data,
}, {
.compatible = "amlogic,sm1-frddr",
.data = &sm1_frddr_match_data,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_frddr_of_match);
static struct platform_driver axg_frddr_pdrv = {
.probe = axg_fifo_probe,
.driver = {
.name = "axg-frddr",
.of_match_table = axg_frddr_of_match,
},
};
module_platform_driver(axg_frddr_pdrv);
MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+641
View File
@@ -0,0 +1,641 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/pcm_params.h>
#define PDM_CTRL 0x00
#define PDM_CTRL_EN BIT(31)
#define PDM_CTRL_OUT_MODE BIT(29)
#define PDM_CTRL_BYPASS_MODE BIT(28)
#define PDM_CTRL_RST_FIFO BIT(16)
#define PDM_CTRL_CHAN_RSTN_MASK GENMASK(15, 8)
#define PDM_CTRL_CHAN_RSTN(x) ((x) << 8)
#define PDM_CTRL_CHAN_EN_MASK GENMASK(7, 0)
#define PDM_CTRL_CHAN_EN(x) ((x) << 0)
#define PDM_HCIC_CTRL1 0x04
#define PDM_FILTER_EN BIT(31)
#define PDM_HCIC_CTRL1_GAIN_SFT_MASK GENMASK(29, 24)
#define PDM_HCIC_CTRL1_GAIN_SFT(x) ((x) << 24)
#define PDM_HCIC_CTRL1_GAIN_MULT_MASK GENMASK(23, 16)
#define PDM_HCIC_CTRL1_GAIN_MULT(x) ((x) << 16)
#define PDM_HCIC_CTRL1_DSR_MASK GENMASK(8, 4)
#define PDM_HCIC_CTRL1_DSR(x) ((x) << 4)
#define PDM_HCIC_CTRL1_STAGE_NUM_MASK GENMASK(3, 0)
#define PDM_HCIC_CTRL1_STAGE_NUM(x) ((x) << 0)
#define PDM_HCIC_CTRL2 0x08
#define PDM_F1_CTRL 0x0c
#define PDM_LPF_ROUND_MODE_MASK GENMASK(17, 16)
#define PDM_LPF_ROUND_MODE(x) ((x) << 16)
#define PDM_LPF_DSR_MASK GENMASK(15, 12)
#define PDM_LPF_DSR(x) ((x) << 12)
#define PDM_LPF_STAGE_NUM_MASK GENMASK(8, 0)
#define PDM_LPF_STAGE_NUM(x) ((x) << 0)
#define PDM_LPF_MAX_STAGE 336
#define PDM_LPF_NUM 3
#define PDM_F2_CTRL 0x10
#define PDM_F3_CTRL 0x14
#define PDM_HPF_CTRL 0x18
#define PDM_HPF_SFT_STEPS_MASK GENMASK(20, 16)
#define PDM_HPF_SFT_STEPS(x) ((x) << 16)
#define PDM_HPF_OUT_FACTOR_MASK GENMASK(15, 0)
#define PDM_HPF_OUT_FACTOR(x) ((x) << 0)
#define PDM_CHAN_CTRL 0x1c
#define PDM_CHAN_CTRL_POINTER_WIDTH 8
#define PDM_CHAN_CTRL_POINTER_MAX ((1 << PDM_CHAN_CTRL_POINTER_WIDTH) - 1)
#define PDM_CHAN_CTRL_NUM 4
#define PDM_CHAN_CTRL1 0x20
#define PDM_COEFF_ADDR 0x24
#define PDM_COEFF_DATA 0x28
#define PDM_CLKG_CTRL 0x2c
#define PDM_STS 0x30
struct axg_pdm_lpf {
unsigned int ds;
unsigned int round_mode;
const unsigned int *tap;
unsigned int tap_num;
};
struct axg_pdm_hcic {
unsigned int shift;
unsigned int mult;
unsigned int steps;
unsigned int ds;
};
struct axg_pdm_hpf {
unsigned int out_factor;
unsigned int steps;
};
struct axg_pdm_filters {
struct axg_pdm_hcic hcic;
struct axg_pdm_hpf hpf;
struct axg_pdm_lpf lpf[PDM_LPF_NUM];
};
struct axg_pdm_cfg {
const struct axg_pdm_filters *filters;
unsigned int sys_rate;
};
struct axg_pdm {
const struct axg_pdm_cfg *cfg;
struct regmap *map;
struct clk *dclk;
struct clk *sysclk;
struct clk *pclk;
};
static void axg_pdm_enable(struct regmap *map)
{
/* Reset AFIFO */
regmap_update_bits(map, PDM_CTRL, PDM_CTRL_RST_FIFO, PDM_CTRL_RST_FIFO);
regmap_update_bits(map, PDM_CTRL, PDM_CTRL_RST_FIFO, 0);
/* Enable PDM */
regmap_update_bits(map, PDM_CTRL, PDM_CTRL_EN, PDM_CTRL_EN);
}
static void axg_pdm_disable(struct regmap *map)
{
regmap_update_bits(map, PDM_CTRL, PDM_CTRL_EN, 0);
}
static void axg_pdm_filters_enable(struct regmap *map, bool enable)
{
unsigned int val = enable ? PDM_FILTER_EN : 0;
regmap_update_bits(map, PDM_HCIC_CTRL1, PDM_FILTER_EN, val);
regmap_update_bits(map, PDM_F1_CTRL, PDM_FILTER_EN, val);
regmap_update_bits(map, PDM_F2_CTRL, PDM_FILTER_EN, val);
regmap_update_bits(map, PDM_F3_CTRL, PDM_FILTER_EN, val);
regmap_update_bits(map, PDM_HPF_CTRL, PDM_FILTER_EN, val);
}
static int axg_pdm_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
axg_pdm_enable(priv->map);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
axg_pdm_disable(priv->map);
return 0;
default:
return -EINVAL;
}
}
static unsigned int axg_pdm_get_os(struct axg_pdm *priv)
{
const struct axg_pdm_filters *filters = priv->cfg->filters;
unsigned int os = filters->hcic.ds;
int i;
/*
* The global oversampling factor is defined by the down sampling
* factor applied by each filter (HCIC and LPFs)
*/
for (i = 0; i < PDM_LPF_NUM; i++)
os *= filters->lpf[i].ds;
return os;
}
static int axg_pdm_set_sysclk(struct axg_pdm *priv, unsigned int os,
unsigned int rate)
{
unsigned int sys_rate = os * 2 * rate * PDM_CHAN_CTRL_POINTER_MAX;
/*
* Set the default system clock rate unless it is too fast for
* the requested sample rate. In this case, the sample pointer
* counter could overflow so set a lower system clock rate
*/
if (sys_rate < priv->cfg->sys_rate)
return clk_set_rate(priv->sysclk, sys_rate);
return clk_set_rate(priv->sysclk, priv->cfg->sys_rate);
}
static int axg_pdm_set_sample_pointer(struct axg_pdm *priv)
{
unsigned int spmax, sp, val;
int i;
/* Max sample counter value per half period of dclk */
spmax = DIV_ROUND_UP_ULL((u64)clk_get_rate(priv->sysclk),
clk_get_rate(priv->dclk) * 2);
/* Check if sysclk is not too fast - should not happen */
if (WARN_ON(spmax > PDM_CHAN_CTRL_POINTER_MAX))
return -EINVAL;
/* Capture the data when we are at 75% of the half period */
sp = spmax * 3 / 4;
for (i = 0, val = 0; i < PDM_CHAN_CTRL_NUM; i++)
val |= sp << (PDM_CHAN_CTRL_POINTER_WIDTH * i);
regmap_write(priv->map, PDM_CHAN_CTRL, val);
regmap_write(priv->map, PDM_CHAN_CTRL1, val);
return 0;
}
static void axg_pdm_set_channel_mask(struct axg_pdm *priv,
unsigned int channels)
{
unsigned int mask = GENMASK(channels - 1, 0);
/* Put all channel in reset */
regmap_update_bits(priv->map, PDM_CTRL,
PDM_CTRL_CHAN_RSTN_MASK, 0);
/* Take the necessary channels out of reset and enable them */
regmap_update_bits(priv->map, PDM_CTRL,
PDM_CTRL_CHAN_RSTN_MASK |
PDM_CTRL_CHAN_EN_MASK,
PDM_CTRL_CHAN_RSTN(mask) |
PDM_CTRL_CHAN_EN(mask));
}
static int axg_pdm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
unsigned int os = axg_pdm_get_os(priv);
unsigned int rate = params_rate(params);
unsigned int val;
int ret;
switch (params_width(params)) {
case 24:
val = PDM_CTRL_OUT_MODE;
break;
case 32:
val = 0;
break;
default:
dev_err(dai->dev, "unsupported sample width\n");
return -EINVAL;
}
regmap_update_bits(priv->map, PDM_CTRL, PDM_CTRL_OUT_MODE, val);
ret = axg_pdm_set_sysclk(priv, os, rate);
if (ret) {
dev_err(dai->dev, "failed to set system clock\n");
return ret;
}
ret = clk_set_rate(priv->dclk, rate * os);
if (ret) {
dev_err(dai->dev, "failed to set dclk\n");
return ret;
}
ret = axg_pdm_set_sample_pointer(priv);
if (ret) {
dev_err(dai->dev, "invalid clock setting\n");
return ret;
}
axg_pdm_set_channel_mask(priv, params_channels(params));
return 0;
}
static int axg_pdm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
int ret;
ret = clk_prepare_enable(priv->dclk);
if (ret) {
dev_err(dai->dev, "enabling dclk failed\n");
return ret;
}
/* Enable the filters */
axg_pdm_filters_enable(priv->map, true);
return ret;
}
static void axg_pdm_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
axg_pdm_filters_enable(priv->map, false);
clk_disable_unprepare(priv->dclk);
}
static void axg_pdm_set_hcic_ctrl(struct axg_pdm *priv)
{
const struct axg_pdm_hcic *hcic = &priv->cfg->filters->hcic;
unsigned int val;
val = PDM_HCIC_CTRL1_STAGE_NUM(hcic->steps);
val |= PDM_HCIC_CTRL1_DSR(hcic->ds);
val |= PDM_HCIC_CTRL1_GAIN_MULT(hcic->mult);
val |= PDM_HCIC_CTRL1_GAIN_SFT(hcic->shift);
regmap_update_bits(priv->map, PDM_HCIC_CTRL1,
PDM_HCIC_CTRL1_STAGE_NUM_MASK |
PDM_HCIC_CTRL1_DSR_MASK |
PDM_HCIC_CTRL1_GAIN_MULT_MASK |
PDM_HCIC_CTRL1_GAIN_SFT_MASK,
val);
}
static void axg_pdm_set_lpf_ctrl(struct axg_pdm *priv, unsigned int index)
{
const struct axg_pdm_lpf *lpf = &priv->cfg->filters->lpf[index];
unsigned int offset = index * regmap_get_reg_stride(priv->map)
+ PDM_F1_CTRL;
unsigned int val;
val = PDM_LPF_STAGE_NUM(lpf->tap_num);
val |= PDM_LPF_DSR(lpf->ds);
val |= PDM_LPF_ROUND_MODE(lpf->round_mode);
regmap_update_bits(priv->map, offset,
PDM_LPF_STAGE_NUM_MASK |
PDM_LPF_DSR_MASK |
PDM_LPF_ROUND_MODE_MASK,
val);
}
static void axg_pdm_set_hpf_ctrl(struct axg_pdm *priv)
{
const struct axg_pdm_hpf *hpf = &priv->cfg->filters->hpf;
unsigned int val;
val = PDM_HPF_OUT_FACTOR(hpf->out_factor);
val |= PDM_HPF_SFT_STEPS(hpf->steps);
regmap_update_bits(priv->map, PDM_HPF_CTRL,
PDM_HPF_OUT_FACTOR_MASK |
PDM_HPF_SFT_STEPS_MASK,
val);
}
static int axg_pdm_set_lpf_filters(struct axg_pdm *priv)
{
const struct axg_pdm_lpf *lpf = priv->cfg->filters->lpf;
unsigned int count = 0;
int i, j;
for (i = 0; i < PDM_LPF_NUM; i++)
count += lpf[i].tap_num;
/* Make sure the coeffs fit in the memory */
if (count >= PDM_LPF_MAX_STAGE)
return -EINVAL;
/* Set the initial APB bus register address */
regmap_write(priv->map, PDM_COEFF_ADDR, 0);
/* Set the tap filter values of all 3 filters */
for (i = 0; i < PDM_LPF_NUM; i++) {
axg_pdm_set_lpf_ctrl(priv, i);
for (j = 0; j < lpf[i].tap_num; j++)
regmap_write(priv->map, PDM_COEFF_DATA, lpf[i].tap[j]);
}
return 0;
}
static int axg_pdm_dai_probe(struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
int ret;
ret = clk_prepare_enable(priv->pclk);
if (ret) {
dev_err(dai->dev, "enabling pclk failed\n");
return ret;
}
/*
* sysclk must be set and enabled as well to access the pdm registers
* Accessing the register w/o it will give a bus error.
*/
ret = clk_set_rate(priv->sysclk, priv->cfg->sys_rate);
if (ret) {
dev_err(dai->dev, "setting sysclk failed\n");
goto err_pclk;
}
ret = clk_prepare_enable(priv->sysclk);
if (ret) {
dev_err(dai->dev, "enabling sysclk failed\n");
goto err_pclk;
}
/* Make sure the device is initially disabled */
axg_pdm_disable(priv->map);
/* Make sure filter bypass is disabled */
regmap_update_bits(priv->map, PDM_CTRL, PDM_CTRL_BYPASS_MODE, 0);
/* Load filter settings */
axg_pdm_set_hcic_ctrl(priv);
axg_pdm_set_hpf_ctrl(priv);
ret = axg_pdm_set_lpf_filters(priv);
if (ret) {
dev_err(dai->dev, "invalid filter configuration\n");
goto err_sysclk;
}
return 0;
err_sysclk:
clk_disable_unprepare(priv->sysclk);
err_pclk:
clk_disable_unprepare(priv->pclk);
return ret;
}
static int axg_pdm_dai_remove(struct snd_soc_dai *dai)
{
struct axg_pdm *priv = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(priv->sysclk);
clk_disable_unprepare(priv->pclk);
return 0;
}
static const struct snd_soc_dai_ops axg_pdm_dai_ops = {
.probe = axg_pdm_dai_probe,
.remove = axg_pdm_dai_remove,
.trigger = axg_pdm_trigger,
.hw_params = axg_pdm_hw_params,
.startup = axg_pdm_startup,
.shutdown = axg_pdm_shutdown,
};
static struct snd_soc_dai_driver axg_pdm_dai_drv = {
.name = "PDM",
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 48000,
.formats = (SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE),
},
.ops = &axg_pdm_dai_ops,
};
static const struct snd_soc_component_driver axg_pdm_component_drv = {
.legacy_dai_naming = 1,
};
static const struct regmap_config axg_pdm_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = PDM_STS,
};
static const unsigned int lpf1_default_tap[] = {
0x000014, 0xffffb2, 0xfffed9, 0xfffdce, 0xfffd45,
0xfffe32, 0x000147, 0x000645, 0x000b86, 0x000e21,
0x000ae3, 0x000000, 0xffeece, 0xffdca8, 0xffd212,
0xffd7d1, 0xfff2a7, 0x001f4c, 0x0050c2, 0x0072aa,
0x006ff1, 0x003c32, 0xffdc4e, 0xff6a18, 0xff0fef,
0xfefbaf, 0xff4c40, 0x000000, 0x00ebc8, 0x01c077,
0x02209e, 0x01c1a4, 0x008e60, 0xfebe52, 0xfcd690,
0xfb8fa5, 0xfba498, 0xfd9812, 0x0181ce, 0x06f5f3,
0x0d112f, 0x12a958, 0x169686, 0x18000e, 0x169686,
0x12a958, 0x0d112f, 0x06f5f3, 0x0181ce, 0xfd9812,
0xfba498, 0xfb8fa5, 0xfcd690, 0xfebe52, 0x008e60,
0x01c1a4, 0x02209e, 0x01c077, 0x00ebc8, 0x000000,
0xff4c40, 0xfefbaf, 0xff0fef, 0xff6a18, 0xffdc4e,
0x003c32, 0x006ff1, 0x0072aa, 0x0050c2, 0x001f4c,
0xfff2a7, 0xffd7d1, 0xffd212, 0xffdca8, 0xffeece,
0x000000, 0x000ae3, 0x000e21, 0x000b86, 0x000645,
0x000147, 0xfffe32, 0xfffd45, 0xfffdce, 0xfffed9,
0xffffb2, 0x000014,
};
static const unsigned int lpf2_default_tap[] = {
0x00050a, 0xfff004, 0x0002c1, 0x003c12, 0xffa818,
0xffc87d, 0x010aef, 0xff5223, 0xfebd93, 0x028f41,
0xff5c0e, 0xfc63f8, 0x055f81, 0x000000, 0xf478a0,
0x11c5e3, 0x2ea74d, 0x11c5e3, 0xf478a0, 0x000000,
0x055f81, 0xfc63f8, 0xff5c0e, 0x028f41, 0xfebd93,
0xff5223, 0x010aef, 0xffc87d, 0xffa818, 0x003c12,
0x0002c1, 0xfff004, 0x00050a,
};
static const unsigned int lpf3_default_tap[] = {
0x000000, 0x000081, 0x000000, 0xfffedb, 0x000000,
0x00022d, 0x000000, 0xfffc46, 0x000000, 0x0005f7,
0x000000, 0xfff6eb, 0x000000, 0x000d4e, 0x000000,
0xffed1e, 0x000000, 0x001a1c, 0x000000, 0xffdcb0,
0x000000, 0x002ede, 0x000000, 0xffc2d1, 0x000000,
0x004ebe, 0x000000, 0xff9beb, 0x000000, 0x007dd7,
0x000000, 0xff633a, 0x000000, 0x00c1d2, 0x000000,
0xff11d5, 0x000000, 0x012368, 0x000000, 0xfe9c45,
0x000000, 0x01b252, 0x000000, 0xfdebf6, 0x000000,
0x0290b8, 0x000000, 0xfcca0d, 0x000000, 0x041d7c,
0x000000, 0xfa8152, 0x000000, 0x07e9c6, 0x000000,
0xf28fb5, 0x000000, 0x28b216, 0x3fffde, 0x28b216,
0x000000, 0xf28fb5, 0x000000, 0x07e9c6, 0x000000,
0xfa8152, 0x000000, 0x041d7c, 0x000000, 0xfcca0d,
0x000000, 0x0290b8, 0x000000, 0xfdebf6, 0x000000,
0x01b252, 0x000000, 0xfe9c45, 0x000000, 0x012368,
0x000000, 0xff11d5, 0x000000, 0x00c1d2, 0x000000,
0xff633a, 0x000000, 0x007dd7, 0x000000, 0xff9beb,
0x000000, 0x004ebe, 0x000000, 0xffc2d1, 0x000000,
0x002ede, 0x000000, 0xffdcb0, 0x000000, 0x001a1c,
0x000000, 0xffed1e, 0x000000, 0x000d4e, 0x000000,
0xfff6eb, 0x000000, 0x0005f7, 0x000000, 0xfffc46,
0x000000, 0x00022d, 0x000000, 0xfffedb, 0x000000,
0x000081, 0x000000,
};
/*
* These values are sane defaults for the axg platform:
* - OS = 64
* - Latency = 38700 (?)
*
* TODO: There is a lot of different HCIC, LPFs and HPF configurations possible.
* the configuration may depend on the dmic used by the platform, the
* expected tradeoff between latency and quality, etc ... If/When other
* settings are required, we should add a fw interface to this driver to
* load new filter settings.
*/
static const struct axg_pdm_filters axg_default_filters = {
.hcic = {
.shift = 0x15,
.mult = 0x80,
.steps = 7,
.ds = 8,
},
.hpf = {
.out_factor = 0x8000,
.steps = 13,
},
.lpf = {
[0] = {
.ds = 2,
.round_mode = 1,
.tap = lpf1_default_tap,
.tap_num = ARRAY_SIZE(lpf1_default_tap),
},
[1] = {
.ds = 2,
.round_mode = 0,
.tap = lpf2_default_tap,
.tap_num = ARRAY_SIZE(lpf2_default_tap),
},
[2] = {
.ds = 2,
.round_mode = 1,
.tap = lpf3_default_tap,
.tap_num = ARRAY_SIZE(lpf3_default_tap)
},
},
};
static const struct axg_pdm_cfg axg_pdm_config = {
.filters = &axg_default_filters,
.sys_rate = 250000000,
};
static const struct of_device_id axg_pdm_of_match[] = {
{
.compatible = "amlogic,axg-pdm",
.data = &axg_pdm_config,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_pdm_of_match);
static int axg_pdm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axg_pdm *priv;
void __iomem *regs;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->cfg = of_device_get_match_data(dev);
if (!priv->cfg) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
priv->map = devm_regmap_init_mmio(dev, regs, &axg_pdm_regmap_cfg);
if (IS_ERR(priv->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(priv->map));
return PTR_ERR(priv->map);
}
priv->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(priv->pclk))
return dev_err_probe(dev, PTR_ERR(priv->pclk), "failed to get pclk\n");
priv->dclk = devm_clk_get(dev, "dclk");
if (IS_ERR(priv->dclk))
return dev_err_probe(dev, PTR_ERR(priv->dclk), "failed to get dclk\n");
priv->sysclk = devm_clk_get(dev, "sysclk");
if (IS_ERR(priv->sysclk))
return dev_err_probe(dev, PTR_ERR(priv->sysclk), "failed to get dclk\n");
return devm_snd_soc_register_component(dev, &axg_pdm_component_drv,
&axg_pdm_dai_drv, 1);
}
static struct platform_driver axg_pdm_pdrv = {
.probe = axg_pdm_probe,
.driver = {
.name = "axg-pdm",
.of_match_table = axg_pdm_of_match,
},
};
module_platform_driver(axg_pdm_pdrv);
MODULE_DESCRIPTION("Amlogic AXG PDM Input driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+496
View File
@@ -0,0 +1,496 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/pcm_params.h>
#define SPDIFIN_CTRL0 0x00
#define SPDIFIN_CTRL0_EN BIT(31)
#define SPDIFIN_CTRL0_RST_OUT BIT(29)
#define SPDIFIN_CTRL0_RST_IN BIT(28)
#define SPDIFIN_CTRL0_WIDTH_SEL BIT(24)
#define SPDIFIN_CTRL0_STATUS_CH_SHIFT 11
#define SPDIFIN_CTRL0_STATUS_SEL GENMASK(10, 8)
#define SPDIFIN_CTRL0_SRC_SEL GENMASK(5, 4)
#define SPDIFIN_CTRL0_CHK_VALID BIT(3)
#define SPDIFIN_CTRL1 0x04
#define SPDIFIN_CTRL1_BASE_TIMER GENMASK(19, 0)
#define SPDIFIN_CTRL1_IRQ_MASK GENMASK(27, 20)
#define SPDIFIN_CTRL2 0x08
#define SPDIFIN_THRES_PER_REG 3
#define SPDIFIN_THRES_WIDTH 10
#define SPDIFIN_CTRL3 0x0c
#define SPDIFIN_CTRL4 0x10
#define SPDIFIN_TIMER_PER_REG 4
#define SPDIFIN_TIMER_WIDTH 8
#define SPDIFIN_CTRL5 0x14
#define SPDIFIN_CTRL6 0x18
#define SPDIFIN_STAT0 0x1c
#define SPDIFIN_STAT0_MODE GENMASK(30, 28)
#define SPDIFIN_STAT0_MAXW GENMASK(17, 8)
#define SPDIFIN_STAT0_IRQ GENMASK(7, 0)
#define SPDIFIN_IRQ_MODE_CHANGED BIT(2)
#define SPDIFIN_STAT1 0x20
#define SPDIFIN_STAT2 0x24
#define SPDIFIN_MUTE_VAL 0x28
#define SPDIFIN_MODE_NUM 7
struct axg_spdifin_cfg {
const unsigned int *mode_rates;
unsigned int ref_rate;
};
struct axg_spdifin {
const struct axg_spdifin_cfg *conf;
struct regmap *map;
struct clk *refclk;
struct clk *pclk;
};
/*
* TODO:
* It would have been nice to check the actual rate against the sample rate
* requested in hw_params(). Unfortunately, I was not able to make the mode
* detection and IRQ work reliably:
*
* 1. IRQs are generated on mode change only, so there is no notification
* on transition between no signal and mode 0 (32kHz).
* 2. Mode detection very often has glitches, and may detects the
* lowest or the highest mode before zeroing in on the actual mode.
*
* This makes calling snd_pcm_stop() difficult to get right. Even notifying
* the kcontrol would be very unreliable at this point.
* Let's keep things simple until the magic spell that makes this work is
* found.
*/
static unsigned int axg_spdifin_get_rate(struct axg_spdifin *priv)
{
unsigned int stat, mode, rate = 0;
regmap_read(priv->map, SPDIFIN_STAT0, &stat);
mode = FIELD_GET(SPDIFIN_STAT0_MODE, stat);
/*
* If max width is zero, we are not capturing anything.
* Also Sometimes, when the capture is on but there is no data,
* mode is SPDIFIN_MODE_NUM, but not always ...
*/
if (FIELD_GET(SPDIFIN_STAT0_MAXW, stat) &&
mode < SPDIFIN_MODE_NUM)
rate = priv->conf->mode_rates[mode];
return rate;
}
static int axg_spdifin_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_spdifin *priv = snd_soc_dai_get_drvdata(dai);
/* Apply both reset */
regmap_update_bits(priv->map, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_RST_OUT |
SPDIFIN_CTRL0_RST_IN,
0);
/* Clear out reset before in reset */
regmap_update_bits(priv->map, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_RST_OUT, SPDIFIN_CTRL0_RST_OUT);
regmap_update_bits(priv->map, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_RST_IN, SPDIFIN_CTRL0_RST_IN);
return 0;
}
static void axg_spdifin_write_mode_param(struct regmap *map, int mode,
unsigned int val,
unsigned int num_per_reg,
unsigned int base_reg,
unsigned int width)
{
uint64_t offset = mode;
unsigned int reg, shift, rem;
rem = do_div(offset, num_per_reg);
reg = offset * regmap_get_reg_stride(map) + base_reg;
shift = width * (num_per_reg - 1 - rem);
regmap_update_bits(map, reg, GENMASK(width - 1, 0) << shift,
val << shift);
}
static void axg_spdifin_write_timer(struct regmap *map, int mode,
unsigned int val)
{
axg_spdifin_write_mode_param(map, mode, val, SPDIFIN_TIMER_PER_REG,
SPDIFIN_CTRL4, SPDIFIN_TIMER_WIDTH);
}
static void axg_spdifin_write_threshold(struct regmap *map, int mode,
unsigned int val)
{
axg_spdifin_write_mode_param(map, mode, val, SPDIFIN_THRES_PER_REG,
SPDIFIN_CTRL2, SPDIFIN_THRES_WIDTH);
}
static unsigned int axg_spdifin_mode_timer(struct axg_spdifin *priv,
int mode,
unsigned int rate)
{
/*
* Number of period of the reference clock during a period of the
* input signal reference clock
*/
return rate / (128 * priv->conf->mode_rates[mode]);
}
static int axg_spdifin_sample_mode_config(struct snd_soc_dai *dai,
struct axg_spdifin *priv)
{
unsigned int rate, t_next;
int ret, i = SPDIFIN_MODE_NUM - 1;
/* Set spdif input reference clock */
ret = clk_set_rate(priv->refclk, priv->conf->ref_rate);
if (ret) {
dev_err(dai->dev, "reference clock rate set failed\n");
return ret;
}
/*
* The rate actually set might be slightly different, get
* the actual rate for the following mode calculation
*/
rate = clk_get_rate(priv->refclk);
/* HW will update mode every 1ms */
regmap_update_bits(priv->map, SPDIFIN_CTRL1,
SPDIFIN_CTRL1_BASE_TIMER,
FIELD_PREP(SPDIFIN_CTRL1_BASE_TIMER, rate / 1000));
/* Threshold based on the maximum width between two edges */
regmap_update_bits(priv->map, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_WIDTH_SEL, 0);
/* Calculate the last timer which has no threshold */
t_next = axg_spdifin_mode_timer(priv, i, rate);
axg_spdifin_write_timer(priv->map, i, t_next);
do {
unsigned int t;
i -= 1;
/* Calculate the timer */
t = axg_spdifin_mode_timer(priv, i, rate);
/* Set the timer value */
axg_spdifin_write_timer(priv->map, i, t);
/* Set the threshold value */
axg_spdifin_write_threshold(priv->map, i, 3 * (t + t_next));
/* Save the current timer for the next threshold calculation */
t_next = t;
} while (i > 0);
return 0;
}
static int axg_spdifin_dai_probe(struct snd_soc_dai *dai)
{
struct axg_spdifin *priv = snd_soc_dai_get_drvdata(dai);
int ret;
ret = clk_prepare_enable(priv->pclk);
if (ret) {
dev_err(dai->dev, "failed to enable pclk\n");
return ret;
}
ret = axg_spdifin_sample_mode_config(dai, priv);
if (ret) {
dev_err(dai->dev, "mode configuration failed\n");
goto pclk_err;
}
ret = clk_prepare_enable(priv->refclk);
if (ret) {
dev_err(dai->dev,
"failed to enable spdifin reference clock\n");
goto pclk_err;
}
regmap_update_bits(priv->map, SPDIFIN_CTRL0, SPDIFIN_CTRL0_EN,
SPDIFIN_CTRL0_EN);
return 0;
pclk_err:
clk_disable_unprepare(priv->pclk);
return ret;
}
static int axg_spdifin_dai_remove(struct snd_soc_dai *dai)
{
struct axg_spdifin *priv = snd_soc_dai_get_drvdata(dai);
regmap_update_bits(priv->map, SPDIFIN_CTRL0, SPDIFIN_CTRL0_EN, 0);
clk_disable_unprepare(priv->refclk);
clk_disable_unprepare(priv->pclk);
return 0;
}
static const struct snd_soc_dai_ops axg_spdifin_ops = {
.probe = axg_spdifin_dai_probe,
.remove = axg_spdifin_dai_remove,
.prepare = axg_spdifin_prepare,
};
static int axg_spdifin_iec958_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int axg_spdifin_get_status_mask(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
for (i = 0; i < 24; i++)
ucontrol->value.iec958.status[i] = 0xff;
return 0;
}
static int axg_spdifin_get_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
struct axg_spdifin *priv = snd_soc_component_get_drvdata(c);
int i, j;
for (i = 0; i < 6; i++) {
unsigned int val;
regmap_update_bits(priv->map, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_STATUS_SEL,
FIELD_PREP(SPDIFIN_CTRL0_STATUS_SEL, i));
regmap_read(priv->map, SPDIFIN_STAT1, &val);
for (j = 0; j < 4; j++) {
unsigned int offset = i * 4 + j;
ucontrol->value.iec958.status[offset] =
(val >> (j * 8)) & 0xff;
}
}
return 0;
}
#define AXG_SPDIFIN_IEC958_MASK \
{ \
.access = SNDRV_CTL_ELEM_ACCESS_READ, \
.iface = SNDRV_CTL_ELEM_IFACE_PCM, \
.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), \
.info = axg_spdifin_iec958_info, \
.get = axg_spdifin_get_status_mask, \
}
#define AXG_SPDIFIN_IEC958_STATUS \
{ \
.access = (SNDRV_CTL_ELEM_ACCESS_READ | \
SNDRV_CTL_ELEM_ACCESS_VOLATILE), \
.iface = SNDRV_CTL_ELEM_IFACE_PCM, \
.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE), \
.info = axg_spdifin_iec958_info, \
.get = axg_spdifin_get_status, \
}
static const char * const spdifin_chsts_src_texts[] = {
"A", "B",
};
static SOC_ENUM_SINGLE_DECL(axg_spdifin_chsts_src_enum, SPDIFIN_CTRL0,
SPDIFIN_CTRL0_STATUS_CH_SHIFT,
spdifin_chsts_src_texts);
static int axg_spdifin_rate_lock_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 192000;
return 0;
}
static int axg_spdifin_rate_lock_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *c = snd_kcontrol_chip(kcontrol);
struct axg_spdifin *priv = snd_soc_component_get_drvdata(c);
ucontrol->value.integer.value[0] = axg_spdifin_get_rate(priv);
return 0;
}
#define AXG_SPDIFIN_LOCK_RATE(xname) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_PCM, \
.access = (SNDRV_CTL_ELEM_ACCESS_READ | \
SNDRV_CTL_ELEM_ACCESS_VOLATILE), \
.get = axg_spdifin_rate_lock_get, \
.info = axg_spdifin_rate_lock_info, \
.name = xname, \
}
static const struct snd_kcontrol_new axg_spdifin_controls[] = {
AXG_SPDIFIN_LOCK_RATE("Capture Rate Lock"),
SOC_DOUBLE("Capture Switch", SPDIFIN_CTRL0, 7, 6, 1, 1),
SOC_ENUM(SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Src",
axg_spdifin_chsts_src_enum),
AXG_SPDIFIN_IEC958_MASK,
AXG_SPDIFIN_IEC958_STATUS,
};
static const struct snd_soc_component_driver axg_spdifin_component_drv = {
.controls = axg_spdifin_controls,
.num_controls = ARRAY_SIZE(axg_spdifin_controls),
.legacy_dai_naming = 1,
};
static const struct regmap_config axg_spdifin_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = SPDIFIN_MUTE_VAL,
};
static const unsigned int axg_spdifin_mode_rates[SPDIFIN_MODE_NUM] = {
32000, 44100, 48000, 88200, 96000, 176400, 192000,
};
static const struct axg_spdifin_cfg axg_cfg = {
.mode_rates = axg_spdifin_mode_rates,
.ref_rate = 333333333,
};
static const struct of_device_id axg_spdifin_of_match[] = {
{
.compatible = "amlogic,axg-spdifin",
.data = &axg_cfg,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_spdifin_of_match);
static struct snd_soc_dai_driver *
axg_spdifin_get_dai_drv(struct device *dev, struct axg_spdifin *priv)
{
struct snd_soc_dai_driver *drv;
int i;
drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return ERR_PTR(-ENOMEM);
drv->name = "SPDIF Input";
drv->ops = &axg_spdifin_ops;
drv->capture.stream_name = "Capture";
drv->capture.channels_min = 1;
drv->capture.channels_max = 2;
drv->capture.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
for (i = 0; i < SPDIFIN_MODE_NUM; i++) {
unsigned int rb =
snd_pcm_rate_to_rate_bit(priv->conf->mode_rates[i]);
if (rb == SNDRV_PCM_RATE_KNOT)
return ERR_PTR(-EINVAL);
drv->capture.rates |= rb;
}
return drv;
}
static int axg_spdifin_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axg_spdifin *priv;
struct snd_soc_dai_driver *dai_drv;
void __iomem *regs;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->conf = of_device_get_match_data(dev);
if (!priv->conf) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifin_regmap_cfg);
if (IS_ERR(priv->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(priv->map));
return PTR_ERR(priv->map);
}
priv->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(priv->pclk))
return dev_err_probe(dev, PTR_ERR(priv->pclk), "failed to get pclk\n");
priv->refclk = devm_clk_get(dev, "refclk");
if (IS_ERR(priv->refclk))
return dev_err_probe(dev, PTR_ERR(priv->refclk), "failed to get mclk\n");
dai_drv = axg_spdifin_get_dai_drv(dev, priv);
if (IS_ERR(dai_drv)) {
dev_err(dev, "failed to get dai driver: %ld\n",
PTR_ERR(dai_drv));
return PTR_ERR(dai_drv);
}
return devm_snd_soc_register_component(dev, &axg_spdifin_component_drv,
dai_drv, 1);
}
static struct platform_driver axg_spdifin_pdrv = {
.probe = axg_spdifin_probe,
.driver = {
.name = "axg-spdifin",
.of_match_table = axg_spdifin_of_match,
},
};
module_platform_driver(axg_spdifin_pdrv);
MODULE_DESCRIPTION("Amlogic AXG SPDIF Input driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+447
View File
@@ -0,0 +1,447 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/pcm_params.h>
#include <sound/pcm_iec958.h>
/*
* NOTE:
* The meaning of bits SPDIFOUT_CTRL0_XXX_SEL is actually the opposite
* of what the documentation says. Manual control on V, U and C bits is
* applied when the related sel bits are cleared
*/
#define SPDIFOUT_STAT 0x00
#define SPDIFOUT_GAIN0 0x04
#define SPDIFOUT_GAIN1 0x08
#define SPDIFOUT_CTRL0 0x0c
#define SPDIFOUT_CTRL0_EN BIT(31)
#define SPDIFOUT_CTRL0_RST_OUT BIT(29)
#define SPDIFOUT_CTRL0_RST_IN BIT(28)
#define SPDIFOUT_CTRL0_USEL BIT(26)
#define SPDIFOUT_CTRL0_USET BIT(25)
#define SPDIFOUT_CTRL0_CHSTS_SEL BIT(24)
#define SPDIFOUT_CTRL0_DATA_SEL BIT(20)
#define SPDIFOUT_CTRL0_MSB_FIRST BIT(19)
#define SPDIFOUT_CTRL0_VSEL BIT(18)
#define SPDIFOUT_CTRL0_VSET BIT(17)
#define SPDIFOUT_CTRL0_MASK_MASK GENMASK(11, 4)
#define SPDIFOUT_CTRL0_MASK(x) ((x) << 4)
#define SPDIFOUT_CTRL1 0x10
#define SPDIFOUT_CTRL1_MSB_POS_MASK GENMASK(12, 8)
#define SPDIFOUT_CTRL1_MSB_POS(x) ((x) << 8)
#define SPDIFOUT_CTRL1_TYPE_MASK GENMASK(6, 4)
#define SPDIFOUT_CTRL1_TYPE(x) ((x) << 4)
#define SPDIFOUT_PREAMB 0x14
#define SPDIFOUT_SWAP 0x18
#define SPDIFOUT_CHSTS0 0x1c
#define SPDIFOUT_CHSTS1 0x20
#define SPDIFOUT_CHSTS2 0x24
#define SPDIFOUT_CHSTS3 0x28
#define SPDIFOUT_CHSTS4 0x2c
#define SPDIFOUT_CHSTS5 0x30
#define SPDIFOUT_CHSTS6 0x34
#define SPDIFOUT_CHSTS7 0x38
#define SPDIFOUT_CHSTS8 0x3c
#define SPDIFOUT_CHSTS9 0x40
#define SPDIFOUT_CHSTSA 0x44
#define SPDIFOUT_CHSTSB 0x48
#define SPDIFOUT_MUTE_VAL 0x4c
struct axg_spdifout {
struct regmap *map;
struct clk *mclk;
struct clk *pclk;
};
static void axg_spdifout_enable(struct regmap *map)
{
/* Apply both reset */
regmap_update_bits(map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_RST_OUT | SPDIFOUT_CTRL0_RST_IN,
0);
/* Clear out reset before in reset */
regmap_update_bits(map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_RST_OUT, SPDIFOUT_CTRL0_RST_OUT);
regmap_update_bits(map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_RST_IN, SPDIFOUT_CTRL0_RST_IN);
/* Enable spdifout */
regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN,
SPDIFOUT_CTRL0_EN);
}
static void axg_spdifout_disable(struct regmap *map)
{
regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, 0);
}
static int axg_spdifout_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
axg_spdifout_enable(priv->map);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
axg_spdifout_disable(priv->map);
return 0;
default:
return -EINVAL;
}
}
static int axg_spdifout_mute(struct snd_soc_dai *dai, int mute, int direction)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
/* Use spdif valid bit to perform digital mute */
regmap_update_bits(priv->map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_VSET,
mute ? SPDIFOUT_CTRL0_VSET : 0);
return 0;
}
static int axg_spdifout_sample_fmt(struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
unsigned int val;
/* Set the samples spdifout will pull from the FIFO */
switch (params_channels(params)) {
case 1:
val = SPDIFOUT_CTRL0_MASK(0x1);
break;
case 2:
val = SPDIFOUT_CTRL0_MASK(0x3);
break;
default:
dev_err(dai->dev, "too many channels for spdif dai: %u\n",
params_channels(params));
return -EINVAL;
}
regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_MASK_MASK, val);
/* FIFO data are arranged in chunks of 64bits */
switch (params_physical_width(params)) {
case 8:
/* 8 samples of 8 bits */
val = SPDIFOUT_CTRL1_TYPE(0);
break;
case 16:
/* 4 samples of 16 bits - right justified */
val = SPDIFOUT_CTRL1_TYPE(2);
break;
case 32:
/* 2 samples of 32 bits - right justified */
val = SPDIFOUT_CTRL1_TYPE(4);
break;
default:
dev_err(dai->dev, "Unsupported physical width: %u\n",
params_physical_width(params));
return -EINVAL;
}
/* Position of the MSB in FIFO samples */
val |= SPDIFOUT_CTRL1_MSB_POS(params_width(params) - 1);
regmap_update_bits(priv->map, SPDIFOUT_CTRL1,
SPDIFOUT_CTRL1_MSB_POS_MASK |
SPDIFOUT_CTRL1_TYPE_MASK, val);
regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
0);
return 0;
}
static int axg_spdifout_set_chsts(struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
unsigned int offset;
int ret;
u8 cs[4];
u32 val;
ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, 4);
if (ret < 0) {
dev_err(dai->dev, "Creating IEC958 channel status failed %d\n",
ret);
return ret;
}
val = cs[0] | cs[1] << 8 | cs[2] << 16 | cs[3] << 24;
/* Setup channel status A bits [31 - 0]*/
regmap_write(priv->map, SPDIFOUT_CHSTS0, val);
/* Clear channel status A bits [191 - 32] */
for (offset = SPDIFOUT_CHSTS1; offset <= SPDIFOUT_CHSTS5;
offset += regmap_get_reg_stride(priv->map))
regmap_write(priv->map, offset, 0);
/* Setup channel status B bits [31 - 0]*/
regmap_write(priv->map, SPDIFOUT_CHSTS6, val);
/* Clear channel status B bits [191 - 32] */
for (offset = SPDIFOUT_CHSTS7; offset <= SPDIFOUT_CHSTSB;
offset += regmap_get_reg_stride(priv->map))
regmap_write(priv->map, offset, 0);
return 0;
}
static int axg_spdifout_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
unsigned int rate = params_rate(params);
int ret;
/* 2 * 32bits per subframe * 2 channels = 128 */
ret = clk_set_rate(priv->mclk, rate * 128);
if (ret) {
dev_err(dai->dev, "failed to set spdif clock\n");
return ret;
}
ret = axg_spdifout_sample_fmt(params, dai);
if (ret) {
dev_err(dai->dev, "failed to setup sample format\n");
return ret;
}
ret = axg_spdifout_set_chsts(params, dai);
if (ret) {
dev_err(dai->dev, "failed to setup channel status words\n");
return ret;
}
return 0;
}
static int axg_spdifout_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
int ret;
/* Clock the spdif output block */
ret = clk_prepare_enable(priv->pclk);
if (ret) {
dev_err(dai->dev, "failed to enable pclk\n");
return ret;
}
/* Make sure the block is initially stopped */
axg_spdifout_disable(priv->map);
/* Insert data from bit 27 lsb first */
regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
0);
/* Manual control of V, C and U, U = 0 */
regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
SPDIFOUT_CTRL0_CHSTS_SEL | SPDIFOUT_CTRL0_VSEL |
SPDIFOUT_CTRL0_USEL | SPDIFOUT_CTRL0_USET,
0);
/* Static SWAP configuration ATM */
regmap_write(priv->map, SPDIFOUT_SWAP, 0x10);
return 0;
}
static void axg_spdifout_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(priv->pclk);
}
static const struct snd_soc_dai_ops axg_spdifout_ops = {
.trigger = axg_spdifout_trigger,
.mute_stream = axg_spdifout_mute,
.hw_params = axg_spdifout_hw_params,
.startup = axg_spdifout_startup,
.shutdown = axg_spdifout_shutdown,
.no_capture_mute = 1,
};
static struct snd_soc_dai_driver axg_spdifout_dai_drv[] = {
{
.name = "SPDIF Output",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = (SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000),
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_LE |
SNDRV_PCM_FMTBIT_S24_LE),
},
.ops = &axg_spdifout_ops,
},
};
static const char * const spdifout_sel_texts[] = {
"IN 0", "IN 1", "IN 2",
};
static SOC_ENUM_SINGLE_DECL(axg_spdifout_sel_enum, SPDIFOUT_CTRL1, 24,
spdifout_sel_texts);
static const struct snd_kcontrol_new axg_spdifout_in_mux =
SOC_DAPM_ENUM("Input Source", axg_spdifout_sel_enum);
static const struct snd_soc_dapm_widget axg_spdifout_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_spdifout_in_mux),
};
static const struct snd_soc_dapm_route axg_spdifout_dapm_routes[] = {
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "Playback", NULL, "SRC SEL" },
};
static const struct snd_kcontrol_new axg_spdifout_controls[] = {
SOC_DOUBLE("Playback Volume", SPDIFOUT_GAIN0, 0, 8, 255, 0),
SOC_DOUBLE("Playback Switch", SPDIFOUT_CTRL0, 22, 21, 1, 1),
SOC_SINGLE("Playback Gain Enable Switch",
SPDIFOUT_CTRL1, 26, 1, 0),
SOC_SINGLE("Playback Channels Mix Switch",
SPDIFOUT_CTRL0, 23, 1, 0),
};
static int axg_spdifout_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct axg_spdifout *priv = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
enum snd_soc_bias_level now = snd_soc_dapm_get_bias_level(dapm);
int ret = 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (now == SND_SOC_BIAS_STANDBY)
ret = clk_prepare_enable(priv->mclk);
break;
case SND_SOC_BIAS_STANDBY:
if (now == SND_SOC_BIAS_PREPARE)
clk_disable_unprepare(priv->mclk);
break;
case SND_SOC_BIAS_OFF:
case SND_SOC_BIAS_ON:
break;
}
return ret;
}
static const struct snd_soc_component_driver axg_spdifout_component_drv = {
.controls = axg_spdifout_controls,
.num_controls = ARRAY_SIZE(axg_spdifout_controls),
.dapm_widgets = axg_spdifout_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_spdifout_dapm_widgets),
.dapm_routes = axg_spdifout_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_spdifout_dapm_routes),
.set_bias_level = axg_spdifout_set_bias_level,
.legacy_dai_naming = 1,
};
static const struct regmap_config axg_spdifout_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = SPDIFOUT_MUTE_VAL,
};
static const struct of_device_id axg_spdifout_of_match[] = {
{ .compatible = "amlogic,axg-spdifout", },
{}
};
MODULE_DEVICE_TABLE(of, axg_spdifout_of_match);
static int axg_spdifout_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axg_spdifout *priv;
void __iomem *regs;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifout_regmap_cfg);
if (IS_ERR(priv->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(priv->map));
return PTR_ERR(priv->map);
}
priv->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(priv->pclk))
return dev_err_probe(dev, PTR_ERR(priv->pclk), "failed to get pclk\n");
priv->mclk = devm_clk_get(dev, "mclk");
if (IS_ERR(priv->mclk))
return dev_err_probe(dev, PTR_ERR(priv->mclk), "failed to get mclk\n");
return devm_snd_soc_register_component(dev, &axg_spdifout_component_drv,
axg_spdifout_dai_drv, ARRAY_SIZE(axg_spdifout_dai_drv));
}
static struct platform_driver axg_spdifout_pdrv = {
.probe = axg_spdifout_probe,
.driver = {
.name = "axg-spdifout",
.of_match_table = axg_spdifout_of_match,
},
};
module_platform_driver(axg_spdifout_pdrv);
MODULE_DESCRIPTION("Amlogic AXG SPDIF Output driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,431 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <sound/soc.h>
#include "axg-tdm-formatter.h"
struct axg_tdm_formatter {
struct list_head list;
struct axg_tdm_stream *stream;
const struct axg_tdm_formatter_driver *drv;
struct clk *pclk;
struct clk *sclk;
struct clk *lrclk;
struct clk *sclk_sel;
struct clk *lrclk_sel;
struct reset_control *reset;
bool enabled;
struct regmap *map;
};
int axg_tdm_formatter_set_channel_masks(struct regmap *map,
struct axg_tdm_stream *ts,
unsigned int offset)
{
unsigned int ch = ts->channels;
u32 val[AXG_TDM_NUM_LANES];
int i, j, k;
/*
* We need to mimick the slot distribution used by the HW to keep the
* channel placement consistent regardless of the number of channel
* in the stream. This is why the odd algorithm below is used.
*/
memset(val, 0, sizeof(*val) * AXG_TDM_NUM_LANES);
/*
* Distribute the channels of the stream over the available slots
* of each TDM lane. We need to go over the 32 slots ...
*/
for (i = 0; (i < 32) && ch; i += 2) {
/* ... of all the lanes ... */
for (j = 0; j < AXG_TDM_NUM_LANES; j++) {
/* ... then distribute the channels in pairs */
for (k = 0; k < 2; k++) {
if ((BIT(i + k) & ts->mask[j]) && ch) {
val[j] |= BIT(i + k);
ch -= 1;
}
}
}
}
/*
* If we still have channel left at the end of the process, it means
* the stream has more channels than we can accommodate and we should
* have caught this earlier.
*/
if (WARN_ON(ch != 0)) {
pr_err("channel mask error\n");
return -EINVAL;
}
for (i = 0; i < AXG_TDM_NUM_LANES; i++) {
regmap_write(map, offset, val[i]);
offset += regmap_get_reg_stride(map);
}
return 0;
}
EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks);
static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter)
{
struct axg_tdm_stream *ts = formatter->stream;
bool invert;
int ret;
/* Do nothing if the formatter is already enabled */
if (formatter->enabled)
return 0;
/*
* On the g12a (and possibly other SoCs), when a stream using
* multiple lanes is restarted, it will sometimes not start
* from the first lane, but randomly from another used one.
* The result is an unexpected and random channel shift.
*
* The hypothesis is that an HW counter is not properly reset
* and the formatter simply starts on the lane it stopped
* before. Unfortunately, there does not seems to be a way to
* reset this through the registers of the block.
*
* However, the g12a has indenpendent reset lines for each audio
* devices. Using this reset before each start solves the issue.
*/
ret = reset_control_reset(formatter->reset);
if (ret)
return ret;
/*
* If sclk is inverted, it means the bit should latched on the
* rising edge which is what our HW expects. If not, we need to
* invert it before the formatter.
*/
invert = axg_tdm_sclk_invert(ts->iface->fmt);
ret = clk_set_phase(formatter->sclk, invert ? 0 : 180);
if (ret)
return ret;
/* Setup the stream parameter in the formatter */
ret = formatter->drv->ops->prepare(formatter->map,
formatter->drv->quirks,
formatter->stream);
if (ret)
return ret;
/* Enable the signal clocks feeding the formatter */
ret = clk_prepare_enable(formatter->sclk);
if (ret)
return ret;
ret = clk_prepare_enable(formatter->lrclk);
if (ret) {
clk_disable_unprepare(formatter->sclk);
return ret;
}
/* Finally, actually enable the formatter */
formatter->drv->ops->enable(formatter->map);
formatter->enabled = true;
return 0;
}
static void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter)
{
/* Do nothing if the formatter is already disabled */
if (!formatter->enabled)
return;
formatter->drv->ops->disable(formatter->map);
clk_disable_unprepare(formatter->lrclk);
clk_disable_unprepare(formatter->sclk);
formatter->enabled = false;
}
static int axg_tdm_formatter_attach(struct axg_tdm_formatter *formatter)
{
struct axg_tdm_stream *ts = formatter->stream;
int ret = 0;
guard(mutex)(&ts->lock);
/* Catch up if the stream is already running when we attach */
if (ts->ready) {
ret = axg_tdm_formatter_enable(formatter);
if (ret) {
pr_err("failed to enable formatter\n");
return ret;
}
}
list_add_tail(&formatter->list, &ts->formatter_list);
return ret;
}
static void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter)
{
struct axg_tdm_stream *ts = formatter->stream;
scoped_guard(mutex, &ts->lock)
list_del(&formatter->list);
axg_tdm_formatter_disable(formatter);
}
static int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter,
struct snd_soc_dapm_widget *w)
{
struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w);
int ret;
/*
* If we don't get a stream at this stage, it would mean that the
* widget is powering up but is not attached to any backend DAI.
* It should not happen, ever !
*/
if (WARN_ON(!ts))
return -ENODEV;
/* Clock our device */
ret = clk_prepare_enable(formatter->pclk);
if (ret)
return ret;
/* Reparent the bit clock to the TDM interface */
ret = clk_set_parent(formatter->sclk_sel, ts->iface->sclk);
if (ret)
goto disable_pclk;
/* Reparent the sample clock to the TDM interface */
ret = clk_set_parent(formatter->lrclk_sel, ts->iface->lrclk);
if (ret)
goto disable_pclk;
formatter->stream = ts;
ret = axg_tdm_formatter_attach(formatter);
if (ret)
goto disable_pclk;
return 0;
disable_pclk:
clk_disable_unprepare(formatter->pclk);
return ret;
}
static void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter)
{
axg_tdm_formatter_dettach(formatter);
clk_disable_unprepare(formatter->pclk);
formatter->stream = NULL;
}
int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *control,
int event)
{
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c);
int ret = 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = axg_tdm_formatter_power_up(formatter, w);
break;
case SND_SOC_DAPM_PRE_PMD:
axg_tdm_formatter_power_down(formatter);
break;
default:
dev_err(c->dev, "Unexpected event %d\n", event);
return -EINVAL;
}
return ret;
}
EXPORT_SYMBOL_GPL(axg_tdm_formatter_event);
int axg_tdm_formatter_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct axg_tdm_formatter_driver *drv;
struct axg_tdm_formatter *formatter;
void __iomem *regs;
drv = of_device_get_match_data(dev);
if (!drv) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
if (!formatter)
return -ENOMEM;
platform_set_drvdata(pdev, formatter);
formatter->drv = drv;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg);
if (IS_ERR(formatter->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(formatter->map));
return PTR_ERR(formatter->map);
}
/* Peripharal clock */
formatter->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(formatter->pclk))
return dev_err_probe(dev, PTR_ERR(formatter->pclk), "failed to get pclk\n");
/* Formatter bit clock */
formatter->sclk = devm_clk_get(dev, "sclk");
if (IS_ERR(formatter->sclk))
return dev_err_probe(dev, PTR_ERR(formatter->sclk), "failed to get sclk\n");
/* Formatter sample clock */
formatter->lrclk = devm_clk_get(dev, "lrclk");
if (IS_ERR(formatter->lrclk))
return dev_err_probe(dev, PTR_ERR(formatter->lrclk), "failed to get lrclk\n");
/* Formatter bit clock input multiplexer */
formatter->sclk_sel = devm_clk_get(dev, "sclk_sel");
if (IS_ERR(formatter->sclk_sel))
return dev_err_probe(dev, PTR_ERR(formatter->sclk_sel), "failed to get sclk_sel\n");
/* Formatter sample clock input multiplexer */
formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel");
if (IS_ERR(formatter->lrclk_sel))
return dev_err_probe(dev, PTR_ERR(formatter->lrclk_sel),
"failed to get lrclk_sel\n");
/* Formatter dedicated reset line */
formatter->reset = devm_reset_control_get_optional_exclusive(dev, NULL);
if (IS_ERR(formatter->reset))
return dev_err_probe(dev, PTR_ERR(formatter->reset), "failed to get reset\n");
return devm_snd_soc_register_component(dev, drv->component_drv,
NULL, 0);
}
EXPORT_SYMBOL_GPL(axg_tdm_formatter_probe);
int axg_tdm_stream_start(struct axg_tdm_stream *ts)
{
struct axg_tdm_formatter *formatter;
int ret = 0;
guard(mutex)(&ts->lock);
ts->ready = true;
/* Start all the formatters attached to the stream */
list_for_each_entry(formatter, &ts->formatter_list, list) {
ret = axg_tdm_formatter_enable(formatter);
if (ret) {
pr_err("failed to start tdm stream\n");
return ret;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_start);
void axg_tdm_stream_stop(struct axg_tdm_stream *ts)
{
struct axg_tdm_formatter *formatter;
guard(mutex)(&ts->lock);
ts->ready = false;
/* Stop all the formatters attached to the stream */
list_for_each_entry(formatter, &ts->formatter_list, list) {
axg_tdm_formatter_disable(formatter);
}
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_stop);
struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface)
{
struct axg_tdm_stream *ts;
ts = kzalloc_obj(*ts);
if (ts) {
INIT_LIST_HEAD(&ts->formatter_list);
mutex_init(&ts->lock);
ts->iface = iface;
}
return ts;
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_alloc);
void axg_tdm_stream_free(struct axg_tdm_stream *ts)
{
/*
* If the list is not empty, it would mean that one of the formatter
* widget is still powered and attached to the interface while we
* are removing the TDM DAI. It should not be possible
*/
WARN_ON(!list_empty(&ts->formatter_list));
mutex_destroy(&ts->lock);
kfree(ts);
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_free);
int axg_tdm_stream_set_cont_clocks(struct axg_tdm_stream *ts,
unsigned int fmt)
{
int ret = 0;
if (fmt & SND_SOC_DAIFMT_CONT) {
/* Clock are already enabled - skipping */
if (ts->clk_enabled)
return 0;
ret = clk_prepare_enable(ts->iface->mclk);
if (ret)
return ret;
ret = clk_prepare_enable(ts->iface->sclk);
if (ret)
goto err_sclk;
ret = clk_prepare_enable(ts->iface->lrclk);
if (ret)
goto err_lrclk;
ts->clk_enabled = true;
return 0;
}
/* Clocks are already disabled - skipping */
if (!ts->clk_enabled)
return 0;
clk_disable_unprepare(ts->iface->lrclk);
err_lrclk:
clk_disable_unprepare(ts->iface->sclk);
err_sclk:
clk_disable_unprepare(ts->iface->mclk);
ts->clk_enabled = false;
return ret;
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_set_cont_clocks);
MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
*
* Copyright (c) 2018 Baylibre SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_AXG_TDM_FORMATTER_H
#define _MESON_AXG_TDM_FORMATTER_H
#include "axg-tdm.h"
struct platform_device;
struct regmap;
struct snd_soc_dapm_widget;
struct snd_kcontrol;
struct axg_tdm_formatter_hw {
unsigned int skew_offset;
};
struct axg_tdm_formatter_ops {
struct axg_tdm_stream *(*get_stream)(struct snd_soc_dapm_widget *w);
void (*enable)(struct regmap *map);
void (*disable)(struct regmap *map);
int (*prepare)(struct regmap *map,
const struct axg_tdm_formatter_hw *quirks,
struct axg_tdm_stream *ts);
};
struct axg_tdm_formatter_driver {
const struct snd_soc_component_driver *component_drv;
const struct regmap_config *regmap_cfg;
const struct axg_tdm_formatter_ops *ops;
const struct axg_tdm_formatter_hw *quirks;
};
int axg_tdm_formatter_set_channel_masks(struct regmap *map,
struct axg_tdm_stream *ts,
unsigned int offset);
int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *control,
int event);
int axg_tdm_formatter_probe(struct platform_device *pdev);
#endif /* _MESON_AXG_TDM_FORMATTER_H */
@@ -0,0 +1,584 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-tdm.h"
/* Maximum bit clock frequency according the datasheets */
#define MAX_SCLK 100000000 /* Hz */
enum {
TDM_IFACE_PAD,
TDM_IFACE_LOOPBACK,
};
static unsigned int axg_tdm_slots_total(u32 *mask)
{
unsigned int slots = 0;
int i;
if (!mask)
return 0;
/* Count the total number of slots provided by all 4 lanes */
for (i = 0; i < AXG_TDM_NUM_LANES; i++)
slots += hweight32(mask[i]);
return slots;
}
int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
u32 *rx_mask, unsigned int slots,
unsigned int slot_width)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
struct axg_tdm_stream *tx = snd_soc_dai_dma_data_get_playback(dai);
struct axg_tdm_stream *rx = snd_soc_dai_dma_data_get_capture(dai);
unsigned int tx_slots, rx_slots;
unsigned int fmt = 0;
tx_slots = axg_tdm_slots_total(tx_mask);
rx_slots = axg_tdm_slots_total(rx_mask);
/* We should at least have a slot for a valid interface */
if (!tx_slots && !rx_slots) {
dev_err(dai->dev, "interface has no slot\n");
return -EINVAL;
}
iface->slots = slots;
switch (slot_width) {
case 0:
slot_width = 32;
fallthrough;
case 32:
fmt |= SNDRV_PCM_FMTBIT_S32_LE;
fallthrough;
case 24:
fmt |= SNDRV_PCM_FMTBIT_S24_LE;
fmt |= SNDRV_PCM_FMTBIT_S20_LE;
fallthrough;
case 16:
fmt |= SNDRV_PCM_FMTBIT_S16_LE;
fallthrough;
case 8:
fmt |= SNDRV_PCM_FMTBIT_S8;
break;
default:
dev_err(dai->dev, "unsupported slot width: %d\n", slot_width);
return -EINVAL;
}
iface->slot_width = slot_width;
/* Amend the dai driver and let dpcm merge do its job */
if (tx) {
tx->mask = tx_mask;
dai->driver->playback.channels_max = tx_slots;
dai->driver->playback.formats = fmt;
}
if (rx) {
rx->mask = rx_mask;
dai->driver->capture.channels_max = rx_slots;
dai->driver->capture.formats = fmt;
}
return 0;
}
EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots);
static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
int ret = -ENOTSUPP;
if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) {
if (!iface->mclk) {
dev_warn(dai->dev, "master clock not provided\n");
} else {
ret = clk_set_rate(iface->mclk, freq);
if (!ret)
iface->mclk_rate = freq;
}
}
return ret;
}
static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
if (!iface->mclk) {
dev_err(dai->dev, "cpu clock master: mclk missing\n");
return -ENODEV;
}
break;
case SND_SOC_DAIFMT_BC_FC:
break;
case SND_SOC_DAIFMT_BP_FC:
case SND_SOC_DAIFMT_BC_FP:
dev_err(dai->dev, "only BP_FP and BC_FC are supported\n");
fallthrough;
default:
return -EINVAL;
}
iface->fmt = fmt;
return 0;
}
static int axg_tdm_iface_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
struct axg_tdm_stream *ts =
snd_soc_dai_get_dma_data(dai, substream);
int ret;
if (!axg_tdm_slots_total(ts->mask)) {
dev_err(dai->dev, "interface has not slots\n");
return -EINVAL;
}
if (snd_soc_component_active(dai->component)) {
/* Apply component wide rate symmetry */
ret = snd_pcm_hw_constraint_single(substream->runtime,
SNDRV_PCM_HW_PARAM_RATE,
iface->rate);
} else {
/* Limit rate according to the slot number and width */
unsigned int max_rate =
MAX_SCLK / (iface->slots * iface->slot_width);
ret = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_RATE,
0, max_rate);
}
if (ret < 0)
dev_err(dai->dev, "can't set iface rate constraint\n");
else
ret = 0;
return ret;
}
static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
unsigned int channels = params_channels(params);
unsigned int width = params_width(params);
/* Save rate and sample_bits for component symmetry */
iface->rate = params_rate(params);
/* Make sure this interface can cope with the stream */
if (axg_tdm_slots_total(ts->mask) < channels) {
dev_err(dai->dev, "not enough slots for channels\n");
return -EINVAL;
}
if (iface->slot_width < width) {
dev_err(dai->dev, "incompatible slots width for stream\n");
return -EINVAL;
}
/* Save the parameter for tdmout/tdmin widgets */
ts->physical_width = params_physical_width(params);
ts->width = params_width(params);
ts->channels = params_channels(params);
return 0;
}
static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
unsigned int ratio_num;
int ret;
ret = clk_set_rate(iface->lrclk, params_rate(params));
if (ret) {
dev_err(dai->dev, "setting sample clock failed: %d\n", ret);
return ret;
}
switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
/* 50% duty cycle ratio */
ratio_num = 1;
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
/*
* A zero duty cycle ratio will result in setting the mininum
* ratio possible which, for this clock, is 1 cycle of the
* parent bclk clock high and the rest low, This is exactly
* what we want here.
*/
ratio_num = 0;
break;
default:
return -EINVAL;
}
ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2);
if (ret) {
dev_err(dai->dev,
"setting sample clock duty cycle failed: %d\n", ret);
return ret;
}
/* Set sample clock inversion */
ret = clk_set_phase(iface->lrclk,
axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0);
if (ret) {
dev_err(dai->dev,
"setting sample clock phase failed: %d\n", ret);
return ret;
}
return 0;
}
static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
unsigned long srate;
int ret;
srate = iface->slots * iface->slot_width * params_rate(params);
if (!iface->mclk_rate) {
/* If no specific mclk is requested, default to bit clock * 2 */
clk_set_rate(iface->mclk, 2 * srate);
} else {
/* Check if we can actually get the bit clock from mclk */
if (iface->mclk_rate % srate) {
dev_err(dai->dev,
"can't derive sclk %lu from mclk %lu\n",
srate, iface->mclk_rate);
return -EINVAL;
}
}
ret = clk_set_rate(iface->sclk, srate);
if (ret) {
dev_err(dai->dev, "setting bit clock failed: %d\n", ret);
return ret;
}
/* Set the bit clock inversion */
ret = clk_set_phase(iface->sclk,
axg_tdm_sclk_invert(iface->fmt) ? 0 : 180);
if (ret) {
dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret);
return ret;
}
return ret;
}
static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
int ret;
switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
if (iface->slots > 2) {
dev_err(dai->dev, "bad slot number for format: %d\n",
iface->slots);
return -EINVAL;
}
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
break;
default:
dev_err(dai->dev, "unsupported dai format\n");
return -EINVAL;
}
ret = axg_tdm_iface_set_stream(substream, params, dai);
if (ret)
return ret;
if ((iface->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) ==
SND_SOC_DAIFMT_BP_FP) {
ret = axg_tdm_iface_set_sclk(dai, params);
if (ret)
return ret;
ret = axg_tdm_iface_set_lrclk(dai, params);
if (ret)
return ret;
}
ret = axg_tdm_stream_set_cont_clocks(ts, iface->fmt);
if (ret)
dev_err(dai->dev, "failed to apply continuous clock setting\n");
return ret;
}
static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
return axg_tdm_stream_set_cont_clocks(ts, 0);
}
static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream,
int cmd,
struct snd_soc_dai *dai)
{
struct axg_tdm_stream *ts =
snd_soc_dai_get_dma_data(dai, substream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
axg_tdm_stream_start(ts);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
axg_tdm_stream_stop(ts);
break;
default:
return -EINVAL;
}
return 0;
}
static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai)
{
int stream;
for_each_pcm_streams(stream) {
struct axg_tdm_stream *ts = snd_soc_dai_dma_data_get(dai, stream);
if (ts)
axg_tdm_stream_free(ts);
}
return 0;
}
static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai)
{
struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
int stream;
for_each_pcm_streams(stream) {
struct axg_tdm_stream *ts;
if (!snd_soc_dai_get_widget(dai, stream))
continue;
ts = axg_tdm_stream_alloc(iface);
if (!ts) {
axg_tdm_iface_remove_dai(dai);
return -ENOMEM;
}
snd_soc_dai_dma_data_set(dai, stream, ts);
}
return 0;
}
static const struct snd_soc_dai_ops axg_tdm_iface_ops = {
.probe = axg_tdm_iface_probe_dai,
.remove = axg_tdm_iface_remove_dai,
.set_sysclk = axg_tdm_iface_set_sysclk,
.set_fmt = axg_tdm_iface_set_fmt,
.startup = axg_tdm_iface_startup,
.hw_params = axg_tdm_iface_hw_params,
.hw_free = axg_tdm_iface_hw_free,
.trigger = axg_tdm_iface_trigger,
};
/* TDM Backend DAIs */
static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = {
[TDM_IFACE_PAD] = {
.name = "TDM Pad",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = AXG_TDM_CHANNEL_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 768000,
.formats = AXG_TDM_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = AXG_TDM_CHANNEL_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 768000,
.formats = AXG_TDM_FORMATS,
},
.id = TDM_IFACE_PAD,
.ops = &axg_tdm_iface_ops,
},
[TDM_IFACE_LOOPBACK] = {
.name = "TDM Loopback",
.capture = {
.stream_name = "Loopback",
.channels_min = 1,
.channels_max = AXG_TDM_CHANNEL_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5512,
.rate_max = 768000,
.formats = AXG_TDM_FORMATS,
},
.id = TDM_IFACE_LOOPBACK,
.ops = &axg_tdm_iface_ops,
},
};
static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
enum snd_soc_bias_level now = snd_soc_dapm_get_bias_level(dapm);
int ret = 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (now == SND_SOC_BIAS_STANDBY)
ret = clk_prepare_enable(iface->mclk);
break;
case SND_SOC_BIAS_STANDBY:
if (now == SND_SOC_BIAS_PREPARE)
clk_disable_unprepare(iface->mclk);
break;
case SND_SOC_BIAS_OFF:
case SND_SOC_BIAS_ON:
break;
}
return ret;
}
static const struct snd_soc_dapm_widget axg_tdm_iface_dapm_widgets[] = {
SND_SOC_DAPM_SIGGEN("Playback Signal"),
};
static const struct snd_soc_dapm_route axg_tdm_iface_dapm_routes[] = {
{ "Loopback", NULL, "Playback Signal" },
};
static const struct snd_soc_component_driver axg_tdm_iface_component_drv = {
.dapm_widgets = axg_tdm_iface_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_tdm_iface_dapm_widgets),
.dapm_routes = axg_tdm_iface_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_tdm_iface_dapm_routes),
.set_bias_level = axg_tdm_iface_set_bias_level,
};
static const struct of_device_id axg_tdm_iface_of_match[] = {
{ .compatible = "amlogic,axg-tdm-iface", },
{}
};
MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match);
static int axg_tdm_iface_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct snd_soc_dai_driver *dai_drv;
struct axg_tdm_iface *iface;
iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL);
if (!iface)
return -ENOMEM;
platform_set_drvdata(pdev, iface);
/*
* Duplicate dai driver: depending on the slot masks configuration
* We'll change the number of channel provided by DAI stream, so dpcm
* channel merge can be done properly
*/
dai_drv = devm_kmemdup_array(dev, axg_tdm_iface_dai_drv, ARRAY_SIZE(axg_tdm_iface_dai_drv),
sizeof(axg_tdm_iface_dai_drv[0]), GFP_KERNEL);
if (!dai_drv)
return -ENOMEM;
/* Bit clock provided on the pad */
iface->sclk = devm_clk_get(dev, "sclk");
if (IS_ERR(iface->sclk))
return dev_err_probe(dev, PTR_ERR(iface->sclk), "failed to get sclk\n");
/* Sample clock provided on the pad */
iface->lrclk = devm_clk_get(dev, "lrclk");
if (IS_ERR(iface->lrclk))
return dev_err_probe(dev, PTR_ERR(iface->lrclk), "failed to get lrclk\n");
/*
* mclk maybe be missing when the cpu dai is in slave mode and
* the codec does not require it to provide a master clock.
* At this point, ignore the error if mclk is missing. We'll
* throw an error if the cpu dai is master and mclk is missing
*/
iface->mclk = devm_clk_get_optional(dev, "mclk");
if (IS_ERR(iface->mclk))
return dev_err_probe(dev, PTR_ERR(iface->mclk), "failed to get mclk\n");
return devm_snd_soc_register_component(dev,
&axg_tdm_iface_component_drv, dai_drv,
ARRAY_SIZE(axg_tdm_iface_dai_drv));
}
static struct platform_driver axg_tdm_iface_pdrv = {
.probe = axg_tdm_iface_probe,
.driver = {
.name = "axg-tdm-iface",
.of_match_table = axg_tdm_iface_of_match,
},
};
module_platform_driver(axg_tdm_iface_pdrv);
MODULE_DESCRIPTION("Amlogic AXG TDM interface driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+81
View File
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
*
* Copyright (c) 2018 Baylibre SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_AXG_TDM_H
#define _MESON_AXG_TDM_H
#include <linux/clk.h>
#include <linux/regmap.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#define AXG_TDM_NUM_LANES 4
#define AXG_TDM_CHANNEL_MAX 128
#define AXG_TDM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
struct axg_tdm_iface {
struct clk *sclk;
struct clk *lrclk;
struct clk *mclk;
unsigned long mclk_rate;
/* format is common to all the DAIs of the iface */
unsigned int fmt;
unsigned int slots;
unsigned int slot_width;
/* For component wide symmetry */
int rate;
};
static inline bool axg_tdm_lrclk_invert(unsigned int fmt)
{
return ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) ^
!!(fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_NB_IF));
}
static inline bool axg_tdm_sclk_invert(unsigned int fmt)
{
return fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_IB_NF);
}
struct axg_tdm_stream {
struct axg_tdm_iface *iface;
struct list_head formatter_list;
struct mutex lock;
unsigned int channels;
unsigned int width;
unsigned int physical_width;
u32 *mask;
bool ready;
/* For continuous clock tracking */
bool clk_enabled;
};
struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface);
void axg_tdm_stream_free(struct axg_tdm_stream *ts);
int axg_tdm_stream_start(struct axg_tdm_stream *ts);
void axg_tdm_stream_stop(struct axg_tdm_stream *ts);
int axg_tdm_stream_set_cont_clocks(struct axg_tdm_stream *ts,
unsigned int fmt);
static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts)
{
axg_tdm_stream_stop(ts);
return axg_tdm_stream_start(ts);
}
int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
u32 *rx_mask, unsigned int slots,
unsigned int slot_width);
#endif /* _MESON_AXG_TDM_H */
+260
View File
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-tdm-formatter.h"
#define TDMIN_CTRL 0x00
#define TDMIN_CTRL_ENABLE BIT(31)
#define TDMIN_CTRL_I2S_MODE BIT(30)
#define TDMIN_CTRL_RST_OUT BIT(29)
#define TDMIN_CTRL_RST_IN BIT(28)
#define TDMIN_CTRL_WS_INV BIT(25)
#define TDMIN_CTRL_SEL_SHIFT 20
#define TDMIN_CTRL_IN_BIT_SKEW_MASK GENMASK(18, 16)
#define TDMIN_CTRL_IN_BIT_SKEW(x) ((x) << 16)
#define TDMIN_CTRL_LSB_FIRST BIT(5)
#define TDMIN_CTRL_BITNUM_MASK GENMASK(4, 0)
#define TDMIN_CTRL_BITNUM(x) ((x) << 0)
#define TDMIN_SWAP 0x04
#define TDMIN_MASK0 0x08
#define TDMIN_MASK1 0x0c
#define TDMIN_MASK2 0x10
#define TDMIN_MASK3 0x14
#define TDMIN_STAT 0x18
#define TDMIN_MUTE_VAL 0x1c
#define TDMIN_MUTE0 0x20
#define TDMIN_MUTE1 0x24
#define TDMIN_MUTE2 0x28
#define TDMIN_MUTE3 0x2c
static const struct regmap_config axg_tdmin_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = TDMIN_MUTE3,
};
static const char * const axg_tdmin_sel_texts[] = {
"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7",
"IN 8", "IN 9", "IN 10", "IN 11", "IN 12", "IN 13", "IN 14", "IN 15",
};
/* Change to special mux control to reset dapm */
static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
static const struct snd_kcontrol_new axg_tdmin_in_mux =
SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
static struct snd_soc_dai *
axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_path *p;
struct snd_soc_dai *be;
snd_soc_dapm_widget_for_each_source_path(w, p) {
if (!p->connect)
continue;
if (p->source->id == snd_soc_dapm_dai_out)
return (struct snd_soc_dai *)p->source->priv;
be = axg_tdmin_get_be(p->source);
if (be)
return be;
}
return NULL;
}
static struct axg_tdm_stream *
axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dai *be = axg_tdmin_get_be(w);
if (!be)
return NULL;
return snd_soc_dai_dma_data_get_capture(be);
}
static void axg_tdmin_enable(struct regmap *map)
{
/* Apply both reset */
regmap_update_bits(map, TDMIN_CTRL,
TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0);
/* Clear out reset before in reset */
regmap_update_bits(map, TDMIN_CTRL,
TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT);
regmap_update_bits(map, TDMIN_CTRL,
TDMIN_CTRL_RST_IN, TDMIN_CTRL_RST_IN);
/* Actually enable tdmin */
regmap_update_bits(map, TDMIN_CTRL,
TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE);
}
static void axg_tdmin_disable(struct regmap *map)
{
regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
}
static int axg_tdmin_prepare(struct regmap *map,
const struct axg_tdm_formatter_hw *quirks,
struct axg_tdm_stream *ts)
{
unsigned int val, skew = quirks->skew_offset;
/* Set stream skew */
switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_DSP_A:
skew += 1;
break;
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_DSP_B:
break;
default:
pr_err("Unsupported format: %u\n",
ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
return -EINVAL;
}
val = TDMIN_CTRL_IN_BIT_SKEW(skew);
/* Set stream format mode */
switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
val |= TDMIN_CTRL_I2S_MODE;
break;
}
/* If the sample clock is inverted, invert it back for the formatter */
if (axg_tdm_lrclk_invert(ts->iface->fmt))
val |= TDMIN_CTRL_WS_INV;
/* Set the slot width */
val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1);
/*
* The following also reset LSB_FIRST which result in the formatter
* placing the first bit received at bit 31
*/
regmap_update_bits(map, TDMIN_CTRL,
(TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV |
TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST |
TDMIN_CTRL_BITNUM_MASK), val);
/* Set static swap mask configuration */
regmap_write(map, TDMIN_SWAP, 0x76543210);
return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
}
static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 8", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 9", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 10", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 11", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 12", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 13", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 14", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 15", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux),
SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
axg_tdm_formatter_event,
(SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "SRC SEL", "IN 3", "IN 3" },
{ "SRC SEL", "IN 4", "IN 4" },
{ "SRC SEL", "IN 5", "IN 5" },
{ "SRC SEL", "IN 6", "IN 6" },
{ "SRC SEL", "IN 7", "IN 7" },
{ "SRC SEL", "IN 8", "IN 8" },
{ "SRC SEL", "IN 9", "IN 9" },
{ "SRC SEL", "IN 10", "IN 10" },
{ "SRC SEL", "IN 11", "IN 11" },
{ "SRC SEL", "IN 12", "IN 12" },
{ "SRC SEL", "IN 13", "IN 13" },
{ "SRC SEL", "IN 14", "IN 14" },
{ "SRC SEL", "IN 15", "IN 15" },
{ "DEC", NULL, "SRC SEL" },
{ "OUT", NULL, "DEC" },
};
static const struct snd_soc_component_driver axg_tdmin_component_drv = {
.dapm_widgets = axg_tdmin_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_tdmin_dapm_widgets),
.dapm_routes = axg_tdmin_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_tdmin_dapm_routes),
};
static const struct axg_tdm_formatter_ops axg_tdmin_ops = {
.get_stream = axg_tdmin_get_tdm_stream,
.prepare = axg_tdmin_prepare,
.enable = axg_tdmin_enable,
.disable = axg_tdmin_disable,
};
static const struct axg_tdm_formatter_driver axg_tdmin_drv = {
.component_drv = &axg_tdmin_component_drv,
.regmap_cfg = &axg_tdmin_regmap_cfg,
.ops = &axg_tdmin_ops,
.quirks = &(const struct axg_tdm_formatter_hw) {
.skew_offset = 3,
},
};
static const struct of_device_id axg_tdmin_of_match[] = {
{
.compatible = "amlogic,axg-tdmin",
.data = &axg_tdmin_drv,
}, {
.compatible = "amlogic,g12a-tdmin",
.data = &axg_tdmin_drv,
}, {
.compatible = "amlogic,sm1-tdmin",
.data = &axg_tdmin_drv,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
static struct platform_driver axg_tdmin_pdrv = {
.probe = axg_tdm_formatter_probe,
.driver = {
.name = "axg-tdmin",
.of_match_table = axg_tdmin_of_match,
},
};
module_platform_driver(axg_tdmin_pdrv);
MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+339
View File
@@ -0,0 +1,339 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-tdm-formatter.h"
#define TDMOUT_CTRL0 0x00
#define TDMOUT_CTRL0_BITNUM_MASK GENMASK(4, 0)
#define TDMOUT_CTRL0_BITNUM(x) ((x) << 0)
#define TDMOUT_CTRL0_SLOTNUM_MASK GENMASK(9, 5)
#define TDMOUT_CTRL0_SLOTNUM(x) ((x) << 5)
#define TDMOUT_CTRL0_INIT_BITNUM_MASK GENMASK(19, 15)
#define TDMOUT_CTRL0_INIT_BITNUM(x) ((x) << 15)
#define TDMOUT_CTRL0_ENABLE BIT(31)
#define TDMOUT_CTRL0_RST_OUT BIT(29)
#define TDMOUT_CTRL0_RST_IN BIT(28)
#define TDMOUT_CTRL1 0x04
#define TDMOUT_CTRL1_TYPE_MASK GENMASK(6, 4)
#define TDMOUT_CTRL1_TYPE(x) ((x) << 4)
#define SM1_TDMOUT_CTRL1_GAIN_EN 7
#define TDMOUT_CTRL1_MSB_POS_MASK GENMASK(12, 8)
#define TDMOUT_CTRL1_MSB_POS(x) ((x) << 8)
#define TDMOUT_CTRL1_SEL_SHIFT 24
#define TDMOUT_CTRL1_GAIN_EN 26
#define TDMOUT_CTRL1_WS_INV BIT(28)
#define TDMOUT_SWAP 0x08
#define TDMOUT_MASK0 0x0c
#define TDMOUT_MASK1 0x10
#define TDMOUT_MASK2 0x14
#define TDMOUT_MASK3 0x18
#define TDMOUT_STAT 0x1c
#define TDMOUT_GAIN0 0x20
#define TDMOUT_GAIN1 0x24
#define TDMOUT_MUTE_VAL 0x28
#define TDMOUT_MUTE0 0x2c
#define TDMOUT_MUTE1 0x30
#define TDMOUT_MUTE2 0x34
#define TDMOUT_MUTE3 0x38
#define TDMOUT_MASK_VAL 0x3c
static const struct regmap_config axg_tdmout_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = TDMOUT_MASK_VAL,
};
static struct snd_soc_dai *
axg_tdmout_get_be(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_path *p;
struct snd_soc_dai *be;
snd_soc_dapm_widget_for_each_sink_path(w, p) {
if (!p->connect)
continue;
if (p->sink->id == snd_soc_dapm_dai_in)
return (struct snd_soc_dai *)p->sink->priv;
be = axg_tdmout_get_be(p->sink);
if (be)
return be;
}
return NULL;
}
static struct axg_tdm_stream *
axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dai *be = axg_tdmout_get_be(w);
if (!be)
return NULL;
return snd_soc_dai_dma_data_get_playback(be);
}
static void axg_tdmout_enable(struct regmap *map)
{
/* Apply both reset */
regmap_update_bits(map, TDMOUT_CTRL0,
TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);
/* Clear out reset before in reset */
regmap_update_bits(map, TDMOUT_CTRL0,
TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT);
regmap_update_bits(map, TDMOUT_CTRL0,
TDMOUT_CTRL0_RST_IN, TDMOUT_CTRL0_RST_IN);
/* Actually enable tdmout */
regmap_update_bits(map, TDMOUT_CTRL0,
TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE);
}
static void axg_tdmout_disable(struct regmap *map)
{
regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0);
}
static int axg_tdmout_prepare(struct regmap *map,
const struct axg_tdm_formatter_hw *quirks,
struct axg_tdm_stream *ts)
{
unsigned int val, skew = quirks->skew_offset;
/* Set the stream skew */
switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_DSP_A:
break;
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_DSP_B:
skew += 1;
break;
default:
pr_err("Unsupported format: %u\n",
ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
return -EINVAL;
}
val = TDMOUT_CTRL0_INIT_BITNUM(skew);
/* Set the slot width */
val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);
/* Set the slot number */
val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1);
regmap_update_bits(map, TDMOUT_CTRL0,
TDMOUT_CTRL0_INIT_BITNUM_MASK |
TDMOUT_CTRL0_BITNUM_MASK |
TDMOUT_CTRL0_SLOTNUM_MASK, val);
/* Set the sample width */
val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);
/* FIFO data are arranged in chunks of 64bits */
switch (ts->physical_width) {
case 8:
/* 8 samples of 8 bits */
val |= TDMOUT_CTRL1_TYPE(0);
break;
case 16:
/* 4 samples of 16 bits - right justified */
val |= TDMOUT_CTRL1_TYPE(2);
break;
case 32:
/* 2 samples of 32 bits - right justified */
val |= TDMOUT_CTRL1_TYPE(4);
break;
default:
pr_err("Unsupported physical width: %u\n",
ts->physical_width);
return -EINVAL;
}
/* If the sample clock is inverted, invert it back for the formatter */
if (axg_tdm_lrclk_invert(ts->iface->fmt))
val |= TDMOUT_CTRL1_WS_INV;
regmap_update_bits(map, TDMOUT_CTRL1,
(TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK |
TDMOUT_CTRL1_WS_INV), val);
/* Set static swap mask configuration */
regmap_write(map, TDMOUT_SWAP, 0x76543210);
return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0);
}
static const struct snd_kcontrol_new axg_tdmout_controls[] = {
SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0, 0, 8, 255, 0),
SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1, 0, 8, 255, 0),
SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
TDMOUT_CTRL1_GAIN_EN, 1, 0),
};
static const char * const axg_tdmout_sel_texts[] = {
"IN 0", "IN 1", "IN 2",
};
static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1,
TDMOUT_CTRL1_SEL_SHIFT, axg_tdmout_sel_texts);
static const struct snd_kcontrol_new axg_tdmout_in_mux =
SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum);
static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux),
SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
axg_tdm_formatter_event,
(SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = {
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "ENC", NULL, "SRC SEL" },
{ "OUT", NULL, "ENC" },
};
static const struct snd_soc_component_driver axg_tdmout_component_drv = {
.controls = axg_tdmout_controls,
.num_controls = ARRAY_SIZE(axg_tdmout_controls),
.dapm_widgets = axg_tdmout_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_tdmout_dapm_widgets),
.dapm_routes = axg_tdmout_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_tdmout_dapm_routes),
};
static const struct axg_tdm_formatter_ops axg_tdmout_ops = {
.get_stream = axg_tdmout_get_tdm_stream,
.prepare = axg_tdmout_prepare,
.enable = axg_tdmout_enable,
.disable = axg_tdmout_disable,
};
static const struct axg_tdm_formatter_driver axg_tdmout_drv = {
.component_drv = &axg_tdmout_component_drv,
.regmap_cfg = &axg_tdmout_regmap_cfg,
.ops = &axg_tdmout_ops,
.quirks = &(const struct axg_tdm_formatter_hw) {
.skew_offset = 1,
},
};
static const struct axg_tdm_formatter_driver g12a_tdmout_drv = {
.component_drv = &axg_tdmout_component_drv,
.regmap_cfg = &axg_tdmout_regmap_cfg,
.ops = &axg_tdmout_ops,
.quirks = &(const struct axg_tdm_formatter_hw) {
.skew_offset = 2,
},
};
static const struct snd_kcontrol_new sm1_tdmout_controls[] = {
SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0, 0, 8, 255, 0),
SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1, 0, 8, 255, 0),
SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
SM1_TDMOUT_CTRL1_GAIN_EN, 1, 0),
};
static const char * const sm1_tdmout_sel_texts[] = {
"IN 0", "IN 1", "IN 2", "IN 3", "IN 4",
};
static SOC_ENUM_SINGLE_DECL(sm1_tdmout_sel_enum, TDMOUT_CTRL1,
TDMOUT_CTRL1_SEL_SHIFT, sm1_tdmout_sel_texts);
static const struct snd_kcontrol_new sm1_tdmout_in_mux =
SOC_DAPM_ENUM("Input Source", sm1_tdmout_sel_enum);
static const struct snd_soc_dapm_widget sm1_tdmout_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_tdmout_in_mux),
SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
axg_tdm_formatter_event,
(SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route sm1_tdmout_dapm_routes[] = {
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "SRC SEL", "IN 3", "IN 3" },
{ "SRC SEL", "IN 4", "IN 4" },
{ "ENC", NULL, "SRC SEL" },
{ "OUT", NULL, "ENC" },
};
static const struct snd_soc_component_driver sm1_tdmout_component_drv = {
.controls = sm1_tdmout_controls,
.num_controls = ARRAY_SIZE(sm1_tdmout_controls),
.dapm_widgets = sm1_tdmout_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sm1_tdmout_dapm_widgets),
.dapm_routes = sm1_tdmout_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(sm1_tdmout_dapm_routes),
};
static const struct axg_tdm_formatter_driver sm1_tdmout_drv = {
.component_drv = &sm1_tdmout_component_drv,
.regmap_cfg = &axg_tdmout_regmap_cfg,
.ops = &axg_tdmout_ops,
.quirks = &(const struct axg_tdm_formatter_hw) {
.skew_offset = 2,
},
};
static const struct of_device_id axg_tdmout_of_match[] = {
{
.compatible = "amlogic,axg-tdmout",
.data = &axg_tdmout_drv,
}, {
.compatible = "amlogic,g12a-tdmout",
.data = &g12a_tdmout_drv,
}, {
.compatible = "amlogic,sm1-tdmout",
.data = &sm1_tdmout_drv,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_tdmout_of_match);
static struct platform_driver axg_tdmout_pdrv = {
.probe = axg_tdm_formatter_probe,
.driver = {
.name = "axg-tdmout",
.of_match_table = axg_tdmout_of_match,
},
};
module_platform_driver(axg_tdmout_pdrv);
MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+353
View File
@@ -0,0 +1,353 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
/* This driver implements the frontend capture DAI of AXG based SoCs */
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "axg-fifo.h"
#define CTRL0_TODDR_SEL_RESAMPLE BIT(30)
#define CTRL0_TODDR_EXT_SIGNED BIT(29)
#define CTRL0_TODDR_PP_MODE BIT(28)
#define CTRL0_TODDR_SYNC_CH BIT(27)
#define CTRL0_TODDR_TYPE GENMASK(15, 13)
#define CTRL0_TODDR_MSB_POS GENMASK(12, 8)
#define CTRL0_TODDR_LSB_POS GENMASK(7, 3)
#define CTRL1_TODDR_FORCE_FINISH BIT(25)
#define CTRL1_SEL_SHIFT 28
#define TODDR_MSB_POS 31
static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *dai)
{
return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE);
}
static int g12a_toddr_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
/* Reset the write pointer to the FIFO_INIT_ADDR */
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_TODDR_FORCE_FINISH, 0);
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_TODDR_FORCE_FINISH, CTRL1_TODDR_FORCE_FINISH);
regmap_update_bits(fifo->map, FIFO_CTRL1,
CTRL1_TODDR_FORCE_FINISH, 0);
return 0;
}
static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
unsigned int type, width;
switch (params_physical_width(params)) {
case 8:
type = 0; /* 8 samples of 8 bits */
break;
case 16:
type = 2; /* 4 samples of 16 bits - right justified */
break;
case 32:
type = 4; /* 2 samples of 32 bits - right justified */
break;
default:
return -EINVAL;
}
width = params_width(params);
regmap_update_bits(fifo->map, FIFO_CTRL0,
CTRL0_TODDR_TYPE |
CTRL0_TODDR_MSB_POS |
CTRL0_TODDR_LSB_POS,
FIELD_PREP(CTRL0_TODDR_TYPE, type) |
FIELD_PREP(CTRL0_TODDR_MSB_POS, TODDR_MSB_POS) |
FIELD_PREP(CTRL0_TODDR_LSB_POS, TODDR_MSB_POS - (width - 1)));
return 0;
}
static int axg_toddr_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
int ret;
/* Enable pclk to access registers and clock the fifo ip */
ret = clk_prepare_enable(fifo->pclk);
if (ret)
return ret;
/* Select orginal data - resampling not supported ATM */
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, 0);
/* Only signed format are supported ATM */
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED,
CTRL0_TODDR_EXT_SIGNED);
/* Apply single buffer mode to the interface */
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, 0);
return 0;
}
static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(fifo->pclk);
}
static const struct snd_soc_dai_ops axg_toddr_ops = {
.hw_params = axg_toddr_dai_hw_params,
.startup = axg_toddr_dai_startup,
.shutdown = axg_toddr_dai_shutdown,
.pcm_new = axg_toddr_pcm_new,
};
static struct snd_soc_dai_driver axg_toddr_dai_drv = {
.name = "TODDR",
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5515,
.rate_max = 768000,
.formats = AXG_FIFO_FORMATS,
},
.ops = &axg_toddr_ops,
};
static const char * const axg_toddr_sel_texts[] = {
"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7"
};
static SOC_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
axg_toddr_sel_texts);
static const struct snd_kcontrol_new axg_toddr_in_mux =
SOC_DAPM_ENUM("Input Source", axg_toddr_sel_enum);
static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = {
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux),
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = {
{ "Capture", NULL, "SRC SEL" },
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "SRC SEL", "IN 3", "IN 3" },
{ "SRC SEL", "IN 4", "IN 4" },
{ "SRC SEL", "IN 5", "IN 5" },
{ "SRC SEL", "IN 6", "IN 6" },
{ "SRC SEL", "IN 7", "IN 7" },
};
static const struct snd_soc_component_driver axg_toddr_component_drv = {
.dapm_widgets = axg_toddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets),
.dapm_routes = axg_toddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = axg_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data axg_toddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23),
.component_drv = &axg_toddr_component_drv,
.dai_drv = &axg_toddr_dai_drv
};
static int g12a_toddr_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
int ret;
ret = axg_toddr_dai_startup(substream, dai);
if (ret)
return ret;
/*
* Make sure the first channel ends up in the at beginning of the output
* As weird as it looks, without this the first channel may be misplaced
* in memory, with a random shift of 2 channels.
*/
regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SYNC_CH,
CTRL0_TODDR_SYNC_CH);
return 0;
}
static const struct snd_soc_dai_ops g12a_toddr_ops = {
.prepare = g12a_toddr_dai_prepare,
.hw_params = axg_toddr_dai_hw_params,
.startup = g12a_toddr_dai_startup,
.shutdown = axg_toddr_dai_shutdown,
.pcm_new = axg_toddr_pcm_new,
};
static struct snd_soc_dai_driver g12a_toddr_dai_drv = {
.name = "TODDR",
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 5515,
.rate_max = 768000,
.formats = AXG_FIFO_FORMATS,
},
.ops = &g12a_toddr_ops,
};
static const struct snd_soc_component_driver g12a_toddr_component_drv = {
.dapm_widgets = axg_toddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets),
.dapm_routes = axg_toddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = g12a_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data g12a_toddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23),
.component_drv = &g12a_toddr_component_drv,
.dai_drv = &g12a_toddr_dai_drv
};
static const char * const sm1_toddr_sel_texts[] = {
"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7",
"IN 8", "IN 9", "IN 10", "IN 11", "IN 12", "IN 13", "IN 14", "IN 15"
};
static SOC_ENUM_SINGLE_DECL(sm1_toddr_sel_enum, FIFO_CTRL1, CTRL1_SEL_SHIFT,
sm1_toddr_sel_texts);
static const struct snd_kcontrol_new sm1_toddr_in_mux =
SOC_DAPM_ENUM("Input Source", sm1_toddr_sel_enum);
static const struct snd_soc_dapm_widget sm1_toddr_dapm_widgets[] = {
SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_toddr_in_mux),
SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 8", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 9", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 10", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 11", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 12", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 13", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 14", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("IN 15", NULL, 0, SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route sm1_toddr_dapm_routes[] = {
{ "Capture", NULL, "SRC SEL" },
{ "SRC SEL", "IN 0", "IN 0" },
{ "SRC SEL", "IN 1", "IN 1" },
{ "SRC SEL", "IN 2", "IN 2" },
{ "SRC SEL", "IN 3", "IN 3" },
{ "SRC SEL", "IN 4", "IN 4" },
{ "SRC SEL", "IN 5", "IN 5" },
{ "SRC SEL", "IN 6", "IN 6" },
{ "SRC SEL", "IN 7", "IN 7" },
{ "SRC SEL", "IN 8", "IN 8" },
{ "SRC SEL", "IN 9", "IN 9" },
{ "SRC SEL", "IN 10", "IN 10" },
{ "SRC SEL", "IN 11", "IN 11" },
{ "SRC SEL", "IN 12", "IN 12" },
{ "SRC SEL", "IN 13", "IN 13" },
{ "SRC SEL", "IN 14", "IN 14" },
{ "SRC SEL", "IN 15", "IN 15" },
};
static const struct snd_soc_component_driver sm1_toddr_component_drv = {
.dapm_widgets = sm1_toddr_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sm1_toddr_dapm_widgets),
.dapm_routes = sm1_toddr_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(sm1_toddr_dapm_routes),
.open = axg_fifo_pcm_open,
.close = axg_fifo_pcm_close,
.hw_params = g12a_fifo_pcm_hw_params,
.hw_free = axg_fifo_pcm_hw_free,
.pointer = axg_fifo_pcm_pointer,
.trigger = axg_fifo_pcm_trigger,
.legacy_dai_naming = 1,
};
static const struct axg_fifo_match_data sm1_toddr_match_data = {
.field_threshold = REG_FIELD(FIFO_CTRL1, 12, 23),
.component_drv = &sm1_toddr_component_drv,
.dai_drv = &g12a_toddr_dai_drv
};
static const struct of_device_id axg_toddr_of_match[] = {
{
.compatible = "amlogic,axg-toddr",
.data = &axg_toddr_match_data,
}, {
.compatible = "amlogic,g12a-toddr",
.data = &g12a_toddr_match_data,
}, {
.compatible = "amlogic,sm1-toddr",
.data = &sm1_toddr_match_data,
}, {}
};
MODULE_DEVICE_TABLE(of, axg_toddr_of_match);
static struct platform_driver axg_toddr_pdrv = {
.probe = axg_fifo_probe,
.driver = {
.name = "axg-toddr",
.of_match_table = axg_toddr_of_match,
},
};
module_platform_driver(axg_toddr_pdrv);
MODULE_DESCRIPTION("Amlogic AXG capture fifo driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+356
View File
@@ -0,0 +1,356 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/pcm_params.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <dt-bindings/sound/meson-g12a-toacodec.h>
#include "axg-tdm.h"
#include "meson-codec-glue.h"
#define G12A_TOACODEC_DRV_NAME "g12a-toacodec"
#define TOACODEC_CTRL0 0x0
#define CTRL0_ENABLE_SHIFT 31
#define CTRL0_DAT_SEL_SM1_MSB 19
#define CTRL0_DAT_SEL_SM1_LSB 18
#define CTRL0_DAT_SEL_MSB 15
#define CTRL0_DAT_SEL_LSB 14
#define CTRL0_LANE_SEL_SM1 16
#define CTRL0_LANE_SEL 12
#define CTRL0_LRCLK_SEL_SM1_MSB 14
#define CTRL0_LRCLK_SEL_SM1_LSB 12
#define CTRL0_LRCLK_SEL_MSB 9
#define CTRL0_LRCLK_SEL_LSB 8
#define CTRL0_LRCLK_INV_SM1 BIT(10)
#define CTRL0_BLK_CAP_INV_SM1 BIT(9)
#define CTRL0_BLK_CAP_INV BIT(7)
#define CTRL0_BCLK_O_INV_SM1 BIT(8)
#define CTRL0_BCLK_O_INV BIT(6)
#define CTRL0_BCLK_SEL_SM1_MSB 6
#define CTRL0_BCLK_SEL_MSB 5
#define CTRL0_BCLK_SEL_LSB 4
#define CTRL0_MCLK_SEL GENMASK(2, 0)
#define TOACODEC_OUT_CHMAX 2
struct g12a_toacodec {
struct regmap_field *field_dat_sel;
struct regmap_field *field_lrclk_sel;
struct regmap_field *field_bclk_sel;
};
struct g12a_toacodec_match_data {
const struct snd_soc_component_driver *component_drv;
struct reg_field field_dat_sel;
struct reg_field field_lrclk_sel;
struct reg_field field_bclk_sel;
};
static const char * const g12a_toacodec_mux_texts[] = {
"I2S A", "I2S B", "I2S C",
};
static int g12a_toacodec_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_to_component(kcontrol);
struct g12a_toacodec *priv = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int mux, reg;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
regmap_field_read(priv->field_dat_sel, &reg);
if (mux == reg)
return 0;
/* Force disconnect of the mux while updating */
snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
regmap_field_write(priv->field_dat_sel, mux);
regmap_field_write(priv->field_lrclk_sel, mux);
regmap_field_write(priv->field_bclk_sel, mux);
/*
* FIXME:
* On this soc, the glue gets the MCLK directly from the clock
* controller instead of going the through the TDM interface.
*
* Here we assume interface A uses clock A, etc ... While it is
* true for now, it could be different. Instead the glue should
* find out the clock used by the interface and select the same
* source. For that, we will need regmap backed clock mux which
* is a work in progress
*/
snd_soc_component_update_bits(component, e->reg,
CTRL0_MCLK_SEL,
FIELD_PREP(CTRL0_MCLK_SEL, mux));
snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
return 1;
}
static SOC_ENUM_SINGLE_DECL(g12a_toacodec_mux_enum, TOACODEC_CTRL0,
CTRL0_DAT_SEL_LSB,
g12a_toacodec_mux_texts);
static SOC_ENUM_SINGLE_DECL(sm1_toacodec_mux_enum, TOACODEC_CTRL0,
CTRL0_DAT_SEL_SM1_LSB,
g12a_toacodec_mux_texts);
static const struct snd_kcontrol_new g12a_toacodec_mux =
SOC_DAPM_ENUM_EXT("Source", g12a_toacodec_mux_enum,
snd_soc_dapm_get_enum_double,
g12a_toacodec_mux_put_enum);
static const struct snd_kcontrol_new sm1_toacodec_mux =
SOC_DAPM_ENUM_EXT("Source", sm1_toacodec_mux_enum,
snd_soc_dapm_get_enum_double,
g12a_toacodec_mux_put_enum);
static const struct snd_kcontrol_new g12a_toacodec_out_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
CTRL0_ENABLE_SHIFT, 1, 0);
static const struct snd_soc_dapm_widget g12a_toacodec_widgets[] = {
SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM, 0, 0,
&g12a_toacodec_mux),
SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM, 0, 0,
&g12a_toacodec_out_enable),
};
static const struct snd_soc_dapm_widget sm1_toacodec_widgets[] = {
SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM, 0, 0,
&sm1_toacodec_mux),
SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM, 0, 0,
&g12a_toacodec_out_enable),
};
static int g12a_toacodec_input_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct meson_codec_glue_input *data;
int ret;
ret = meson_codec_glue_input_hw_params(substream, params, dai);
if (ret)
return ret;
/* The glue will provide 1 lane out of the 4 to the output */
data = meson_codec_glue_input_get_data(dai);
data->params.channels_min = min_t(unsigned int, TOACODEC_OUT_CHMAX,
data->params.channels_min);
data->params.channels_max = min_t(unsigned int, TOACODEC_OUT_CHMAX,
data->params.channels_max);
return 0;
}
static const struct snd_soc_dai_ops g12a_toacodec_input_ops = {
.probe = meson_codec_glue_input_dai_probe,
.remove = meson_codec_glue_input_dai_remove,
.hw_params = g12a_toacodec_input_hw_params,
.set_fmt = meson_codec_glue_input_set_fmt,
};
static const struct snd_soc_dai_ops g12a_toacodec_output_ops = {
.startup = meson_codec_glue_output_startup,
};
#define TOACODEC_STREAM(xname, xsuffix, xchmax) \
{ \
.stream_name = xname " " xsuffix, \
.channels_min = 1, \
.channels_max = (xchmax), \
.rate_min = 5512, \
.rate_max = 192000, \
.formats = AXG_TDM_FORMATS, \
}
#define TOACODEC_INPUT(xname, xid) { \
.name = xname, \
.id = (xid), \
.playback = TOACODEC_STREAM(xname, "Playback", 8), \
.ops = &g12a_toacodec_input_ops, \
}
#define TOACODEC_OUTPUT(xname, xid) { \
.name = xname, \
.id = (xid), \
.capture = TOACODEC_STREAM(xname, "Capture", TOACODEC_OUT_CHMAX), \
.ops = &g12a_toacodec_output_ops, \
}
static struct snd_soc_dai_driver g12a_toacodec_dai_drv[] = {
TOACODEC_INPUT("IN A", TOACODEC_IN_A),
TOACODEC_INPUT("IN B", TOACODEC_IN_B),
TOACODEC_INPUT("IN C", TOACODEC_IN_C),
TOACODEC_OUTPUT("OUT", TOACODEC_OUT),
};
static int g12a_toacodec_component_probe(struct snd_soc_component *c)
{
/* Initialize the static clock parameters */
return snd_soc_component_write(c, TOACODEC_CTRL0,
CTRL0_BLK_CAP_INV);
}
static int sm1_toacodec_component_probe(struct snd_soc_component *c)
{
/* Initialize the static clock parameters */
return snd_soc_component_write(c, TOACODEC_CTRL0,
CTRL0_BLK_CAP_INV_SM1);
}
static const struct snd_soc_dapm_route g12a_toacodec_routes[] = {
{ "SRC", "I2S A", "IN A Playback" },
{ "SRC", "I2S B", "IN B Playback" },
{ "SRC", "I2S C", "IN C Playback" },
{ "OUT EN", "Switch", "SRC" },
{ "OUT Capture", NULL, "OUT EN" },
};
static const struct snd_kcontrol_new g12a_toacodec_controls[] = {
SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL, 3, 0),
};
static const struct snd_kcontrol_new sm1_toacodec_controls[] = {
SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_SM1, 3, 0),
};
static const struct snd_soc_component_driver g12a_toacodec_component_drv = {
.probe = g12a_toacodec_component_probe,
.controls = g12a_toacodec_controls,
.num_controls = ARRAY_SIZE(g12a_toacodec_controls),
.dapm_widgets = g12a_toacodec_widgets,
.num_dapm_widgets = ARRAY_SIZE(g12a_toacodec_widgets),
.dapm_routes = g12a_toacodec_routes,
.num_dapm_routes = ARRAY_SIZE(g12a_toacodec_routes),
.endianness = 1,
};
static const struct snd_soc_component_driver sm1_toacodec_component_drv = {
.probe = sm1_toacodec_component_probe,
.controls = sm1_toacodec_controls,
.num_controls = ARRAY_SIZE(sm1_toacodec_controls),
.dapm_widgets = sm1_toacodec_widgets,
.num_dapm_widgets = ARRAY_SIZE(sm1_toacodec_widgets),
.dapm_routes = g12a_toacodec_routes,
.num_dapm_routes = ARRAY_SIZE(g12a_toacodec_routes),
.endianness = 1,
};
static const struct regmap_config g12a_toacodec_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static const struct g12a_toacodec_match_data g12a_toacodec_match_data = {
.component_drv = &g12a_toacodec_component_drv,
.field_dat_sel = REG_FIELD(TOACODEC_CTRL0, 14, 15),
.field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 8, 9),
.field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 5),
};
static const struct g12a_toacodec_match_data sm1_toacodec_match_data = {
.component_drv = &sm1_toacodec_component_drv,
.field_dat_sel = REG_FIELD(TOACODEC_CTRL0, 18, 19),
.field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
.field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
};
static const struct of_device_id g12a_toacodec_of_match[] = {
{
.compatible = "amlogic,g12a-toacodec",
.data = &g12a_toacodec_match_data,
},
{
.compatible = "amlogic,sm1-toacodec",
.data = &sm1_toacodec_match_data,
},
{}
};
MODULE_DEVICE_TABLE(of, g12a_toacodec_of_match);
static int g12a_toacodec_probe(struct platform_device *pdev)
{
const struct g12a_toacodec_match_data *data;
struct device *dev = &pdev->dev;
struct g12a_toacodec *priv;
void __iomem *regs;
struct regmap *map;
int ret;
data = device_get_match_data(dev);
if (!data) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
ret = device_reset(dev);
if (ret)
return ret;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
map = devm_regmap_init_mmio(dev, regs, &g12a_toacodec_regmap_cfg);
if (IS_ERR(map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(map));
return PTR_ERR(map);
}
priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
if (IS_ERR(priv->field_dat_sel))
return PTR_ERR(priv->field_dat_sel);
priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
if (IS_ERR(priv->field_lrclk_sel))
return PTR_ERR(priv->field_lrclk_sel);
priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
if (IS_ERR(priv->field_bclk_sel))
return PTR_ERR(priv->field_bclk_sel);
return devm_snd_soc_register_component(dev,
data->component_drv, g12a_toacodec_dai_drv,
ARRAY_SIZE(g12a_toacodec_dai_drv));
}
static struct platform_driver g12a_toacodec_pdrv = {
.driver = {
.name = G12A_TOACODEC_DRV_NAME,
.of_match_table = g12a_toacodec_of_match,
},
.probe = g12a_toacodec_probe,
};
module_platform_driver(g12a_toacodec_pdrv);
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_DESCRIPTION("Amlogic G12a To Internal DAC Codec Driver");
MODULE_LICENSE("GPL v2");
+283
View File
@@ -0,0 +1,283 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2019 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/pcm_params.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <dt-bindings/sound/meson-g12a-tohdmitx.h>
#include "meson-codec-glue.h"
#define G12A_TOHDMITX_DRV_NAME "g12a-tohdmitx"
#define TOHDMITX_CTRL0 0x0
#define CTRL0_ENABLE_SHIFT 31
#define CTRL0_I2S_DAT_SEL_SHIFT 12
#define CTRL0_I2S_DAT_SEL (0x3 << CTRL0_I2S_DAT_SEL_SHIFT)
#define CTRL0_I2S_LRCLK_SEL GENMASK(9, 8)
#define CTRL0_I2S_BLK_CAP_INV BIT(7)
#define CTRL0_I2S_BCLK_O_INV BIT(6)
#define CTRL0_I2S_BCLK_SEL GENMASK(5, 4)
#define CTRL0_SPDIF_CLK_CAP_INV BIT(3)
#define CTRL0_SPDIF_CLK_O_INV BIT(2)
#define CTRL0_SPDIF_SEL_SHIFT 1
#define CTRL0_SPDIF_SEL (0x1 << CTRL0_SPDIF_SEL_SHIFT)
#define CTRL0_SPDIF_CLK_SEL BIT(0)
static const char * const g12a_tohdmitx_i2s_mux_texts[] = {
"I2S A", "I2S B", "I2S C",
};
static int g12a_tohdmitx_i2s_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_to_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int mux, changed;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
changed = snd_soc_component_test_bits(component, e->reg,
CTRL0_I2S_DAT_SEL,
FIELD_PREP(CTRL0_I2S_DAT_SEL,
mux));
if (!changed)
return 0;
/* Force disconnect of the mux while updating */
snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
snd_soc_component_update_bits(component, e->reg,
CTRL0_I2S_DAT_SEL |
CTRL0_I2S_LRCLK_SEL |
CTRL0_I2S_BCLK_SEL,
FIELD_PREP(CTRL0_I2S_DAT_SEL, mux) |
FIELD_PREP(CTRL0_I2S_LRCLK_SEL, mux) |
FIELD_PREP(CTRL0_I2S_BCLK_SEL, mux));
snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
return 1;
}
static SOC_ENUM_SINGLE_DECL(g12a_tohdmitx_i2s_mux_enum, TOHDMITX_CTRL0,
CTRL0_I2S_DAT_SEL_SHIFT,
g12a_tohdmitx_i2s_mux_texts);
static const struct snd_kcontrol_new g12a_tohdmitx_i2s_mux =
SOC_DAPM_ENUM_EXT("I2S Source", g12a_tohdmitx_i2s_mux_enum,
snd_soc_dapm_get_enum_double,
g12a_tohdmitx_i2s_mux_put_enum);
static const char * const g12a_tohdmitx_spdif_mux_texts[] = {
"SPDIF A", "SPDIF B",
};
static int g12a_tohdmitx_spdif_mux_put_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_to_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int mux, changed;
if (ucontrol->value.enumerated.item[0] >= e->items)
return -EINVAL;
mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
changed = snd_soc_component_test_bits(component, TOHDMITX_CTRL0,
CTRL0_SPDIF_SEL,
FIELD_PREP(CTRL0_SPDIF_SEL, mux));
if (!changed)
return 0;
/* Force disconnect of the mux while updating */
snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
snd_soc_component_update_bits(component, TOHDMITX_CTRL0,
CTRL0_SPDIF_SEL |
CTRL0_SPDIF_CLK_SEL,
FIELD_PREP(CTRL0_SPDIF_SEL, mux) |
FIELD_PREP(CTRL0_SPDIF_CLK_SEL, mux));
snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
return 1;
}
static SOC_ENUM_SINGLE_DECL(g12a_tohdmitx_spdif_mux_enum, TOHDMITX_CTRL0,
CTRL0_SPDIF_SEL_SHIFT,
g12a_tohdmitx_spdif_mux_texts);
static const struct snd_kcontrol_new g12a_tohdmitx_spdif_mux =
SOC_DAPM_ENUM_EXT("SPDIF Source", g12a_tohdmitx_spdif_mux_enum,
snd_soc_dapm_get_enum_double,
g12a_tohdmitx_spdif_mux_put_enum);
static const struct snd_kcontrol_new g12a_tohdmitx_out_enable =
SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOHDMITX_CTRL0,
CTRL0_ENABLE_SHIFT, 1, 0);
static const struct snd_soc_dapm_widget g12a_tohdmitx_widgets[] = {
SND_SOC_DAPM_MUX("I2S SRC", SND_SOC_NOPM, 0, 0,
&g12a_tohdmitx_i2s_mux),
SND_SOC_DAPM_SWITCH("I2S OUT EN", SND_SOC_NOPM, 0, 0,
&g12a_tohdmitx_out_enable),
SND_SOC_DAPM_MUX("SPDIF SRC", SND_SOC_NOPM, 0, 0,
&g12a_tohdmitx_spdif_mux),
SND_SOC_DAPM_SWITCH("SPDIF OUT EN", SND_SOC_NOPM, 0, 0,
&g12a_tohdmitx_out_enable),
};
static const struct snd_soc_dai_ops g12a_tohdmitx_input_ops = {
.probe = meson_codec_glue_input_dai_probe,
.remove = meson_codec_glue_input_dai_remove,
.hw_params = meson_codec_glue_input_hw_params,
.set_fmt = meson_codec_glue_input_set_fmt,
};
static const struct snd_soc_dai_ops g12a_tohdmitx_output_ops = {
.startup = meson_codec_glue_output_startup,
};
#define TOHDMITX_SPDIF_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
#define TOHDMITX_I2S_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
#define TOHDMITX_STREAM(xname, xsuffix, xfmt, xchmax) \
{ \
.stream_name = xname " " xsuffix, \
.channels_min = 1, \
.channels_max = (xchmax), \
.rate_min = 8000, \
.rate_max = 192000, \
.formats = (xfmt), \
}
#define TOHDMITX_IN(xname, xid, xfmt, xchmax) { \
.name = xname, \
.id = (xid), \
.playback = TOHDMITX_STREAM(xname, "Playback", xfmt, xchmax), \
.ops = &g12a_tohdmitx_input_ops, \
}
#define TOHDMITX_OUT(xname, xid, xfmt, xchmax) { \
.name = xname, \
.id = (xid), \
.capture = TOHDMITX_STREAM(xname, "Capture", xfmt, xchmax), \
.ops = &g12a_tohdmitx_output_ops, \
}
static struct snd_soc_dai_driver g12a_tohdmitx_dai_drv[] = {
TOHDMITX_IN("I2S IN A", TOHDMITX_I2S_IN_A,
TOHDMITX_I2S_FORMATS, 8),
TOHDMITX_IN("I2S IN B", TOHDMITX_I2S_IN_B,
TOHDMITX_I2S_FORMATS, 8),
TOHDMITX_IN("I2S IN C", TOHDMITX_I2S_IN_C,
TOHDMITX_I2S_FORMATS, 8),
TOHDMITX_OUT("I2S OUT", TOHDMITX_I2S_OUT,
TOHDMITX_I2S_FORMATS, 8),
TOHDMITX_IN("SPDIF IN A", TOHDMITX_SPDIF_IN_A,
TOHDMITX_SPDIF_FORMATS, 2),
TOHDMITX_IN("SPDIF IN B", TOHDMITX_SPDIF_IN_B,
TOHDMITX_SPDIF_FORMATS, 2),
TOHDMITX_OUT("SPDIF OUT", TOHDMITX_SPDIF_OUT,
TOHDMITX_SPDIF_FORMATS, 2),
};
static int g12a_tohdmi_component_probe(struct snd_soc_component *c)
{
/* Initialize the static clock parameters */
return snd_soc_component_write(c, TOHDMITX_CTRL0,
CTRL0_I2S_BLK_CAP_INV | CTRL0_SPDIF_CLK_CAP_INV);
}
static const struct snd_soc_dapm_route g12a_tohdmitx_routes[] = {
{ "I2S SRC", "I2S A", "I2S IN A Playback" },
{ "I2S SRC", "I2S B", "I2S IN B Playback" },
{ "I2S SRC", "I2S C", "I2S IN C Playback" },
{ "I2S OUT EN", "Switch", "I2S SRC" },
{ "I2S OUT Capture", NULL, "I2S OUT EN" },
{ "SPDIF SRC", "SPDIF A", "SPDIF IN A Playback" },
{ "SPDIF SRC", "SPDIF B", "SPDIF IN B Playback" },
{ "SPDIF OUT EN", "Switch", "SPDIF SRC" },
{ "SPDIF OUT Capture", NULL, "SPDIF OUT EN" },
};
static const struct snd_soc_component_driver g12a_tohdmitx_component_drv = {
.probe = g12a_tohdmi_component_probe,
.dapm_widgets = g12a_tohdmitx_widgets,
.num_dapm_widgets = ARRAY_SIZE(g12a_tohdmitx_widgets),
.dapm_routes = g12a_tohdmitx_routes,
.num_dapm_routes = ARRAY_SIZE(g12a_tohdmitx_routes),
.endianness = 1,
};
static const struct regmap_config g12a_tohdmitx_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static const struct of_device_id g12a_tohdmitx_of_match[] = {
{ .compatible = "amlogic,g12a-tohdmitx", },
{}
};
MODULE_DEVICE_TABLE(of, g12a_tohdmitx_of_match);
static int g12a_tohdmitx_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
void __iomem *regs;
struct regmap *map;
int ret;
ret = device_reset(dev);
if (ret)
return ret;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
map = devm_regmap_init_mmio(dev, regs, &g12a_tohdmitx_regmap_cfg);
if (IS_ERR(map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(map));
return PTR_ERR(map);
}
return devm_snd_soc_register_component(dev,
&g12a_tohdmitx_component_drv, g12a_tohdmitx_dai_drv,
ARRAY_SIZE(g12a_tohdmitx_dai_drv));
}
static struct platform_driver g12a_tohdmitx_pdrv = {
.driver = {
.name = G12A_TOHDMITX_DRV_NAME,
.of_match_table = g12a_tohdmitx_of_match,
},
.probe = g12a_tohdmitx_probe,
};
module_platform_driver(g12a_tohdmitx_pdrv);
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_DESCRIPTION("Amlogic G12a To HDMI Tx Control Codec Driver");
MODULE_LICENSE("GPL v2");
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "meson-card.h"
struct gx_dai_link_i2s_data {
unsigned int mclk_fs;
};
/*
* Base params for the codec to codec links
* Those will be over-written by the CPU side of the link
*/
static const struct snd_soc_pcm_stream codec_params = {
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rate_min = 5525,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 8,
};
static int gx_card_i2s_be_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card);
struct gx_dai_link_i2s_data *be =
(struct gx_dai_link_i2s_data *)priv->link_data[rtd->id];
return meson_card_i2s_set_sysclk(substream, params, be->mclk_fs);
}
static const struct snd_soc_ops gx_card_i2s_be_ops = {
.hw_params = gx_card_i2s_be_hw_params,
};
static int gx_card_parse_i2s(struct snd_soc_card *card,
struct device_node *node,
int *index)
{
struct meson_card *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link *link = &card->dai_link[*index];
struct gx_dai_link_i2s_data *be;
/* Allocate i2s link parameters */
be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL);
if (!be)
return -ENOMEM;
priv->link_data[*index] = be;
/* Setup i2s link */
link->ops = &gx_card_i2s_be_ops;
link->dai_fmt = meson_card_parse_daifmt(node, link->cpus->of_node);
of_property_read_u32(node, "mclk-fs", &be->mclk_fs);
return 0;
}
static int gx_card_cpu_identify(struct snd_soc_dai_link_component *c,
char *match)
{
if (of_device_is_compatible(c->of_node, DT_PREFIX "aiu")) {
if (strstr(c->dai_name, match))
return 1;
}
/* dai not matched */
return 0;
}
static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np,
int *index)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[*index];
struct snd_soc_dai_link_component *cpu;
int ret;
cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL);
if (!cpu)
return -ENOMEM;
dai_link->cpus = cpu;
dai_link->num_cpus = 1;
ret = meson_card_parse_dai(card, np, dai_link->cpus);
if (ret)
return ret;
if (gx_card_cpu_identify(dai_link->cpus, "FIFO"))
return meson_card_set_fe_link(card, dai_link, np, true);
ret = meson_card_set_be_link(card, dai_link, np);
if (ret)
return ret;
/* Or apply codec to codec params if necessary */
if (gx_card_cpu_identify(dai_link->cpus, "CODEC CTRL")) {
dai_link->c2c_params = &codec_params;
dai_link->num_c2c_params = 1;
} else {
dai_link->no_pcm = 1;
/* Check if the cpu is the i2s encoder and parse i2s data */
if (gx_card_cpu_identify(dai_link->cpus, "I2S Encoder"))
ret = gx_card_parse_i2s(card, np, index);
}
return ret;
}
static const struct meson_card_match_data gx_card_match_data = {
.add_link = gx_card_add_link,
};
static const struct of_device_id gx_card_of_match[] = {
{
.compatible = "amlogic,gx-sound-card",
.data = &gx_card_match_data,
}, {}
};
MODULE_DEVICE_TABLE(of, gx_card_of_match);
static struct platform_driver gx_card_pdrv = {
.probe = meson_card_probe,
.remove = meson_card_remove,
.driver = {
.name = "gx-sound-card",
.of_match_table = gx_card_of_match,
},
};
module_platform_driver(gx_card_pdrv);
MODULE_DESCRIPTION("Amlogic GX ALSA machine driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include "meson-card.h"
int meson_card_i2s_set_sysclk(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
unsigned int mclk_fs)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai;
unsigned int mclk;
int ret, i;
if (!mclk_fs)
return 0;
mclk = params_rate(params) * mclk_fs;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
SND_SOC_CLOCK_IN);
if (ret && ret != -ENOTSUPP)
return ret;
}
ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), 0, mclk,
SND_SOC_CLOCK_OUT);
if (ret && ret != -ENOTSUPP)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(meson_card_i2s_set_sysclk);
int meson_card_reallocate_links(struct snd_soc_card *card,
unsigned int num_links)
{
struct meson_card *priv = snd_soc_card_get_drvdata(card);
struct snd_soc_dai_link *links;
void **ldata;
links = krealloc(priv->card.dai_link,
num_links * sizeof(*priv->card.dai_link),
GFP_KERNEL | __GFP_ZERO);
if (!links)
goto err_links;
ldata = krealloc(priv->link_data,
num_links * sizeof(*priv->link_data),
GFP_KERNEL | __GFP_ZERO);
if (!ldata)
goto err_ldata;
priv->card.dai_link = links;
priv->link_data = ldata;
priv->card.num_links = num_links;
return 0;
err_ldata:
kfree(links);
err_links:
dev_err(priv->card.dev, "failed to allocate links\n");
return -ENOMEM;
}
EXPORT_SYMBOL_GPL(meson_card_reallocate_links);
int meson_card_parse_dai(struct snd_soc_card *card,
struct device_node *node,
struct snd_soc_dai_link_component *dlc)
{
int ret;
if (!dlc || !node)
return -EINVAL;
ret = snd_soc_of_get_dlc(node, NULL, dlc, 0);
if (ret)
return dev_err_probe(card->dev, ret, "can't parse dai\n");
return ret;
}
EXPORT_SYMBOL_GPL(meson_card_parse_dai);
static int meson_card_set_link_name(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node,
const char *prefix)
{
char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s",
prefix, node->full_name);
if (!name)
return -ENOMEM;
link->name = name;
link->stream_name = name;
return 0;
}
unsigned int meson_card_parse_daifmt(struct device_node *node,
struct device_node *cpu_node)
{
struct device_node *bitclkmaster = NULL;
struct device_node *framemaster = NULL;
unsigned int daifmt;
daifmt = snd_soc_daifmt_parse_format(node, NULL);
snd_soc_daifmt_parse_clock_provider_as_phandle(node, NULL, &bitclkmaster, &framemaster);
/* If no master is provided, default to cpu master */
if (!bitclkmaster || bitclkmaster == cpu_node) {
daifmt |= (!framemaster || framemaster == cpu_node) ?
SND_SOC_DAIFMT_CBC_CFC : SND_SOC_DAIFMT_CBC_CFP;
} else {
daifmt |= (!framemaster || framemaster == cpu_node) ?
SND_SOC_DAIFMT_CBP_CFC : SND_SOC_DAIFMT_CBP_CFP;
}
of_node_put(bitclkmaster);
of_node_put(framemaster);
return daifmt;
}
EXPORT_SYMBOL_GPL(meson_card_parse_daifmt);
int meson_card_set_be_link(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node)
{
struct snd_soc_dai_link_component *codec;
int ret, num_codecs;
num_codecs = of_get_child_count(node);
if (!num_codecs) {
dev_err(card->dev, "be link %s has no codec\n",
node->full_name);
return -EINVAL;
}
codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
if (!codec)
return -ENOMEM;
link->codecs = codec;
link->num_codecs = num_codecs;
for_each_child_of_node_scoped(node, np) {
ret = meson_card_parse_dai(card, np, codec);
if (ret)
return ret;
codec++;
}
ret = meson_card_set_link_name(card, link, node, "be");
if (ret)
dev_err(card->dev, "error setting %pOFn link name\n", node);
return ret;
}
EXPORT_SYMBOL_GPL(meson_card_set_be_link);
int meson_card_set_fe_link(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node,
bool is_playback)
{
link->codecs = &snd_soc_dummy_dlc;
link->num_codecs = 1;
link->dynamic = 1;
link->dpcm_merged_format = 1;
link->dpcm_merged_chan = 1;
link->dpcm_merged_rate = 1;
if (is_playback)
link->playback_only = 1;
else
link->capture_only = 1;
return meson_card_set_link_name(card, link, node, "fe");
}
EXPORT_SYMBOL_GPL(meson_card_set_fe_link);
static int meson_card_add_links(struct snd_soc_card *card)
{
struct meson_card *priv = snd_soc_card_get_drvdata(card);
struct device_node *node = card->dev->of_node;
int num, i, ret;
num = of_get_child_count(node);
if (!num) {
dev_err(card->dev, "card has no links\n");
return -EINVAL;
}
ret = meson_card_reallocate_links(card, num);
if (ret)
return ret;
i = 0;
for_each_child_of_node_scoped(node, np) {
ret = priv->match_data->add_link(card, np, &i);
if (ret)
return ret;
i++;
}
return 0;
}
static int meson_card_parse_of_optional(struct snd_soc_card *card,
const char *propname,
int (*func)(struct snd_soc_card *c,
const char *p))
{
/* If property is not provided, don't fail ... */
if (!of_property_present(card->dev->of_node, propname))
return 0;
/* ... but do fail if it is provided and the parsing fails */
return func(card, propname);
}
static void meson_card_clean_references(struct meson_card *priv)
{
struct snd_soc_card *card = &priv->card;
struct snd_soc_dai_link *link;
struct snd_soc_dai_link_component *codec;
struct snd_soc_aux_dev *aux;
int i, j;
if (card->dai_link) {
for_each_card_prelinks(card, i, link) {
if (link->cpus)
of_node_put(link->cpus->of_node);
for_each_link_codecs(link, j, codec)
of_node_put(codec->of_node);
}
}
if (card->aux_dev) {
for_each_card_pre_auxs(card, i, aux)
of_node_put(aux->dlc.of_node);
}
kfree(card->dai_link);
kfree(priv->link_data);
}
int meson_card_probe(struct platform_device *pdev)
{
const struct meson_card_match_data *data;
struct device *dev = &pdev->dev;
struct meson_card *priv;
int ret;
data = of_device_get_match_data(dev);
if (!data) {
dev_err(dev, "failed to match device\n");
return -ENODEV;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
snd_soc_card_set_drvdata(&priv->card, priv);
priv->card.owner = THIS_MODULE;
priv->card.dev = dev;
priv->card.driver_name = dev->driver->name;
priv->match_data = data;
ret = snd_soc_of_parse_card_name(&priv->card, "model");
if (ret < 0)
return ret;
ret = meson_card_parse_of_optional(&priv->card, "audio-routing",
snd_soc_of_parse_audio_routing);
if (ret) {
dev_err(dev, "error while parsing routing\n");
return ret;
}
ret = meson_card_parse_of_optional(&priv->card, "audio-widgets",
snd_soc_of_parse_audio_simple_widgets);
if (ret) {
dev_err(dev, "error while parsing widgets\n");
return ret;
}
ret = meson_card_add_links(&priv->card);
if (ret)
goto out_err;
ret = snd_soc_of_parse_aux_devs(&priv->card, "audio-aux-devs");
if (ret)
goto out_err;
ret = devm_snd_soc_register_card(dev, &priv->card);
if (ret)
goto out_err;
return 0;
out_err:
meson_card_clean_references(priv);
return ret;
}
EXPORT_SYMBOL_GPL(meson_card_probe);
void meson_card_remove(struct platform_device *pdev)
{
struct meson_card *priv = platform_get_drvdata(pdev);
meson_card_clean_references(priv);
}
EXPORT_SYMBOL_GPL(meson_card_remove);
MODULE_DESCRIPTION("Amlogic Sound Card Utils");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");
+54
View File
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2020 BayLibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_SND_CARD_H
#define _MESON_SND_CARD_H
struct device_node;
struct platform_device;
struct snd_soc_card;
struct snd_pcm_substream;
struct snd_pcm_hw_params;
#define DT_PREFIX "amlogic,"
struct meson_card_match_data {
int (*add_link)(struct snd_soc_card *card,
struct device_node *node,
int *index);
};
struct meson_card {
const struct meson_card_match_data *match_data;
struct snd_soc_card card;
void **link_data;
};
unsigned int meson_card_parse_daifmt(struct device_node *node,
struct device_node *cpu_node);
int meson_card_i2s_set_sysclk(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
unsigned int mclk_fs);
int meson_card_reallocate_links(struct snd_soc_card *card,
unsigned int num_links);
int meson_card_parse_dai(struct snd_soc_card *card,
struct device_node *node,
struct snd_soc_dai_link_component *dlc);
int meson_card_set_be_link(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node);
int meson_card_set_fe_link(struct snd_soc_card *card,
struct snd_soc_dai_link *link,
struct device_node *node,
bool is_playback);
int meson_card_probe(struct platform_device *pdev);
void meson_card_remove(struct platform_device *pdev);
#endif /* _MESON_SND_CARD_H */
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2019 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/module.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "meson-codec-glue.h"
static struct snd_soc_dapm_widget *
meson_codec_glue_get_input(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_path *p;
struct snd_soc_dapm_widget *in;
snd_soc_dapm_widget_for_each_source_path(w, p) {
if (!p->connect)
continue;
/* Check that we still are in the same component */
if (snd_soc_dapm_to_component(w->dapm) !=
snd_soc_dapm_to_component(p->source->dapm))
continue;
if (p->source->id == snd_soc_dapm_dai_in)
return p->source;
in = meson_codec_glue_get_input(p->source);
if (in)
return in;
}
return NULL;
}
static void meson_codec_glue_input_set_data(struct snd_soc_dai *dai,
struct meson_codec_glue_input *data)
{
snd_soc_dai_dma_data_set_playback(dai, data);
}
struct meson_codec_glue_input *
meson_codec_glue_input_get_data(struct snd_soc_dai *dai)
{
return snd_soc_dai_dma_data_get_playback(dai);
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_get_data);
static struct meson_codec_glue_input *
meson_codec_glue_output_get_input_data(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_widget *in =
meson_codec_glue_get_input(w);
struct snd_soc_dai *dai;
if (WARN_ON(!in))
return NULL;
dai = in->priv;
return meson_codec_glue_input_get_data(dai);
}
int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct meson_codec_glue_input *data =
meson_codec_glue_input_get_data(dai);
data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params));
data->params.rate_min = params_rate(params);
data->params.rate_max = params_rate(params);
data->params.formats = 1ULL << (__force int) params_format(params);
data->params.channels_min = params_channels(params);
data->params.channels_max = params_channels(params);
data->params.sig_bits = dai->driver->playback.sig_bits;
return 0;
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_hw_params);
int meson_codec_glue_input_set_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct meson_codec_glue_input *data =
meson_codec_glue_input_get_data(dai);
/* Save the source stream format for the downstream link */
data->fmt = fmt;
return 0;
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_set_fmt);
int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget_capture(dai);
struct meson_codec_glue_input *in_data = meson_codec_glue_output_get_input_data(w);
if (!in_data)
return -ENODEV;
if (WARN_ON(!rtd->dai_link->c2c_params)) {
dev_warn(dai->dev, "codec2codec link expected\n");
return -EINVAL;
}
/* Replace link params with the input params */
rtd->dai_link->c2c_params = &in_data->params;
rtd->dai_link->num_c2c_params = 1;
return snd_soc_runtime_set_dai_fmt(rtd, in_data->fmt);
}
EXPORT_SYMBOL_GPL(meson_codec_glue_output_startup);
int meson_codec_glue_input_dai_probe(struct snd_soc_dai *dai)
{
struct meson_codec_glue_input *data;
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
meson_codec_glue_input_set_data(dai, data);
return 0;
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_dai_probe);
int meson_codec_glue_input_dai_remove(struct snd_soc_dai *dai)
{
struct meson_codec_glue_input *data =
meson_codec_glue_input_get_data(dai);
kfree(data);
return 0;
}
EXPORT_SYMBOL_GPL(meson_codec_glue_input_dai_remove);
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_DESCRIPTION("Amlogic Codec Glue Helpers");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (c) 2018 Baylibre SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
#ifndef _MESON_CODEC_GLUE_H
#define _MESON_CODEC_GLUE_H
#include <sound/soc.h>
struct meson_codec_glue_input {
struct snd_soc_pcm_stream params;
unsigned int fmt;
};
/* Input helpers */
struct meson_codec_glue_input *
meson_codec_glue_input_get_data(struct snd_soc_dai *dai);
int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
int meson_codec_glue_input_set_fmt(struct snd_soc_dai *dai,
unsigned int fmt);
int meson_codec_glue_input_dai_probe(struct snd_soc_dai *dai);
int meson_codec_glue_input_dai_remove(struct snd_soc_dai *dai);
/* Output helpers */
int meson_codec_glue_output_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
#endif /* _MESON_CODEC_GLUE_H */
+314
View File
@@ -0,0 +1,314 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2020 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#define BLOCK_EN 0x00
#define LORN_EN 0
#define LORP_EN 1
#define LOLN_EN 2
#define LOLP_EN 3
#define DACR_EN 4
#define DACL_EN 5
#define DACR_INV 20
#define DACL_INV 21
#define DACR_SRC 22
#define DACL_SRC 23
#define REFP_BUF_EN BIT(12)
#define BIAS_CURRENT_EN BIT(13)
#define VMID_GEN_FAST BIT(14)
#define VMID_GEN_EN BIT(15)
#define I2S_MODE BIT(30)
#define VOL_CTRL0 0x04
#define GAIN_H 31
#define GAIN_L 23
#define VOL_CTRL1 0x08
#define DAC_MONO 8
#define RAMP_RATE 10
#define VC_RAMP_MODE 12
#define MUTE_MODE 13
#define UNMUTE_MODE 14
#define DAC_SOFT_MUTE 15
#define DACR_VC 16
#define DACL_VC 24
#define LINEOUT_CFG 0x0c
#define LORN_POL 0
#define LORP_POL 4
#define LOLN_POL 8
#define LOLP_POL 12
#define POWER_CFG 0x10
struct t9015 {
struct regulator *avdd;
};
static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
unsigned int val;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBP_CFP:
val = I2S_MODE;
break;
case SND_SOC_DAIFMT_CBC_CFC:
val = 0;
break;
default:
return -EINVAL;
}
snd_soc_component_update_bits(component, BLOCK_EN, I2S_MODE, val);
if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) &&
((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_LEFT_J))
return -EINVAL;
return 0;
}
static const struct snd_soc_dai_ops t9015_dai_ops = {
.set_fmt = t9015_dai_set_fmt,
};
static struct snd_soc_dai_driver t9015_dai = {
.name = "t9015-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_LE |
SNDRV_PCM_FMTBIT_S24_LE),
},
.ops = &t9015_dai_ops,
};
static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0);
static const char * const ramp_rate_txt[] = { "Fast", "Slow" };
static SOC_ENUM_SINGLE_DECL(ramp_rate_enum, VOL_CTRL1, RAMP_RATE,
ramp_rate_txt);
static const char * const dacr_in_txt[] = { "Right", "Left" };
static SOC_ENUM_SINGLE_DECL(dacr_in_enum, BLOCK_EN, DACR_SRC, dacr_in_txt);
static const char * const dacl_in_txt[] = { "Left", "Right" };
static SOC_ENUM_SINGLE_DECL(dacl_in_enum, BLOCK_EN, DACL_SRC, dacl_in_txt);
static const char * const mono_txt[] = { "Stereo", "Mono"};
static SOC_ENUM_SINGLE_DECL(mono_enum, VOL_CTRL1, DAC_MONO, mono_txt);
static const struct snd_kcontrol_new t9015_snd_controls[] = {
/* Volume Controls */
SOC_ENUM("Playback Channel Mode", mono_enum),
SOC_SINGLE("Playback Switch", VOL_CTRL1, DAC_SOFT_MUTE, 1, 1),
SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1, DACL_VC, DACR_VC,
0xff, 0, dac_vol_tlv),
/* Ramp Controls */
SOC_ENUM("Ramp Rate", ramp_rate_enum),
SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1, VC_RAMP_MODE, 1, 0),
SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1, MUTE_MODE, 1, 0),
SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
};
static const struct snd_kcontrol_new t9015_right_dac_mux =
SOC_DAPM_ENUM("Right DAC Source", dacr_in_enum);
static const struct snd_kcontrol_new t9015_left_dac_mux =
SOC_DAPM_ENUM("Left DAC Source", dacl_in_enum);
static const struct snd_soc_dapm_widget t9015_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM, 0, 0,
&t9015_right_dac_mux),
SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM, 0, 0,
&t9015_left_dac_mux),
SND_SOC_DAPM_DAC("Right DAC", NULL, BLOCK_EN, DACR_EN, 0),
SND_SOC_DAPM_DAC("Left DAC", NULL, BLOCK_EN, DACL_EN, 0),
SND_SOC_DAPM_OUT_DRV("Right- Driver", BLOCK_EN, LORN_EN, 0,
NULL, 0),
SND_SOC_DAPM_OUT_DRV("Right+ Driver", BLOCK_EN, LORP_EN, 0,
NULL, 0),
SND_SOC_DAPM_OUT_DRV("Left- Driver", BLOCK_EN, LOLN_EN, 0,
NULL, 0),
SND_SOC_DAPM_OUT_DRV("Left+ Driver", BLOCK_EN, LOLP_EN, 0,
NULL, 0),
SND_SOC_DAPM_OUTPUT("LORN"),
SND_SOC_DAPM_OUTPUT("LORP"),
SND_SOC_DAPM_OUTPUT("LOLN"),
SND_SOC_DAPM_OUTPUT("LOLP"),
};
static const struct snd_soc_dapm_route t9015_dapm_routes[] = {
{ "Right IN", NULL, "Playback" },
{ "Left IN", NULL, "Playback" },
{ "Right DAC Sel", "Right", "Right IN" },
{ "Right DAC Sel", "Left", "Left IN" },
{ "Left DAC Sel", "Right", "Right IN" },
{ "Left DAC Sel", "Left", "Left IN" },
{ "Right DAC", NULL, "Right DAC Sel" },
{ "Left DAC", NULL, "Left DAC Sel" },
{ "Right- Driver", NULL, "Right DAC" },
{ "Right+ Driver", NULL, "Right DAC" },
{ "Left- Driver", NULL, "Left DAC" },
{ "Left+ Driver", NULL, "Left DAC" },
{ "LORN", NULL, "Right- Driver", },
{ "LORP", NULL, "Right+ Driver", },
{ "LOLN", NULL, "Left- Driver", },
{ "LOLP", NULL, "Left+ Driver", },
};
static int t9015_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct t9015 *priv = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
enum snd_soc_bias_level now = snd_soc_dapm_get_bias_level(dapm);
int ret;
switch (level) {
case SND_SOC_BIAS_ON:
snd_soc_component_update_bits(component, BLOCK_EN,
BIAS_CURRENT_EN,
BIAS_CURRENT_EN);
break;
case SND_SOC_BIAS_PREPARE:
snd_soc_component_update_bits(component, BLOCK_EN,
BIAS_CURRENT_EN,
0);
break;
case SND_SOC_BIAS_STANDBY:
ret = regulator_enable(priv->avdd);
if (ret) {
dev_err(component->dev, "AVDD enable failed\n");
return ret;
}
if (now == SND_SOC_BIAS_OFF) {
snd_soc_component_update_bits(component, BLOCK_EN,
VMID_GEN_EN | VMID_GEN_FAST | REFP_BUF_EN,
VMID_GEN_EN | VMID_GEN_FAST | REFP_BUF_EN);
mdelay(200);
snd_soc_component_update_bits(component, BLOCK_EN,
VMID_GEN_FAST,
0);
}
break;
case SND_SOC_BIAS_OFF:
snd_soc_component_update_bits(component, BLOCK_EN,
VMID_GEN_EN | VMID_GEN_FAST | REFP_BUF_EN,
0);
regulator_disable(priv->avdd);
break;
}
return 0;
}
static const struct snd_soc_component_driver t9015_codec_driver = {
.set_bias_level = t9015_set_bias_level,
.controls = t9015_snd_controls,
.num_controls = ARRAY_SIZE(t9015_snd_controls),
.dapm_widgets = t9015_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(t9015_dapm_widgets),
.dapm_routes = t9015_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(t9015_dapm_routes),
.suspend_bias_off = 1,
.endianness = 1,
};
static const struct regmap_config t9015_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = POWER_CFG,
};
static int t9015_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct t9015 *priv;
void __iomem *regs;
struct regmap *regmap;
struct clk *pclk;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
pclk = devm_clk_get_enabled(dev, "pclk");
if (IS_ERR(pclk))
return dev_err_probe(dev, PTR_ERR(pclk), "failed to get core clock\n");
priv->avdd = devm_regulator_get(dev, "AVDD");
if (IS_ERR(priv->avdd))
return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n");
ret = device_reset(dev);
if (ret) {
dev_err(dev, "reset failed\n");
return ret;
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs)) {
dev_err(dev, "register map failed\n");
return PTR_ERR(regs);
}
regmap = devm_regmap_init_mmio(dev, regs, &t9015_regmap_config);
if (IS_ERR(regmap)) {
dev_err(dev, "regmap init failed\n");
return PTR_ERR(regmap);
}
/*
* Initialize output polarity:
* ATM the output polarity is fixed but in the future it might useful
* to add DT property to set this depending on the platform needs
*/
regmap_write(regmap, LINEOUT_CFG, 0x1111);
return devm_snd_soc_register_component(dev, &t9015_codec_driver,
&t9015_dai, 1);
}
static const struct of_device_id t9015_ids[] __maybe_unused = {
{ .compatible = "amlogic,t9015", },
{ }
};
MODULE_DEVICE_TABLE(of, t9015_ids);
static struct platform_driver t9015_driver = {
.driver = {
.name = "t9015-codec",
.of_match_table = of_match_ptr(t9015_ids),
},
.probe = t9015_probe,
};
module_platform_driver(t9015_driver);
MODULE_DESCRIPTION("ASoC Amlogic T9015 codec driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL");