linux/sound/soc/meson/axg-frddr.c
<<
>>
Prefs
   1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
   2//
   3// Copyright (c) 2018 BayLibre, SAS.
   4// Author: Jerome Brunet <jbrunet@baylibre.com>
   5
   6/*
   7 * This driver implements the frontend playback DAI of AXG and G12A based SoCs
   8 */
   9
  10#include <linux/clk.h>
  11#include <linux/regmap.h>
  12#include <linux/module.h>
  13#include <linux/of_platform.h>
  14#include <sound/soc.h>
  15#include <sound/soc-dai.h>
  16
  17#include "axg-fifo.h"
  18
  19#define CTRL0_FRDDR_PP_MODE             BIT(30)
  20#define CTRL0_SEL1_EN_SHIFT             3
  21#define CTRL0_SEL2_SHIFT                4
  22#define CTRL0_SEL2_EN_SHIFT             7
  23#define CTRL0_SEL3_SHIFT                8
  24#define CTRL0_SEL3_EN_SHIFT             11
  25#define CTRL1_FRDDR_FORCE_FINISH        BIT(12)
  26
  27static int g12a_frddr_dai_prepare(struct snd_pcm_substream *substream,
  28                                  struct snd_soc_dai *dai)
  29{
  30        struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
  31
  32        /* Reset the read pointer to the FIFO_INIT_ADDR */
  33        regmap_update_bits(fifo->map, FIFO_CTRL1,
  34                           CTRL1_FRDDR_FORCE_FINISH, 0);
  35        regmap_update_bits(fifo->map, FIFO_CTRL1,
  36                           CTRL1_FRDDR_FORCE_FINISH, CTRL1_FRDDR_FORCE_FINISH);
  37        regmap_update_bits(fifo->map, FIFO_CTRL1,
  38                           CTRL1_FRDDR_FORCE_FINISH, 0);
  39
  40        return 0;
  41}
  42
  43static int axg_frddr_dai_startup(struct snd_pcm_substream *substream,
  44                                 struct snd_soc_dai *dai)
  45{
  46        struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
  47        unsigned int fifo_depth, fifo_threshold;
  48        int ret;
  49
  50        /* Enable pclk to access registers and clock the fifo ip */
  51        ret = clk_prepare_enable(fifo->pclk);
  52        if (ret)
  53                return ret;
  54
  55        /* Apply single buffer mode to the interface */
  56        regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0);
  57
  58        /*
  59         * TODO: We could adapt the fifo depth and the fifo threshold
  60         * depending on the expected memory throughput and lantencies
  61         * For now, we'll just use the same values as the vendor kernel
  62         * Depth and threshold are zero based.
  63         */
  64        fifo_depth = AXG_FIFO_MIN_CNT - 1;
  65        fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1;
  66        regmap_update_bits(fifo->map, FIFO_CTRL1,
  67                           CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK,
  68                           CTRL1_FRDDR_DEPTH(fifo_depth) |
  69                           CTRL1_THRESHOLD(fifo_threshold));
  70
  71        return 0;
  72}
  73
  74static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream,
  75                                   struct snd_soc_dai *dai)
  76{
  77        struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
  78
  79        clk_disable_unprepare(fifo->pclk);
  80}
  81
  82static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
  83                             struct snd_soc_dai *dai)
  84{
  85        return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK);
  86}
  87
  88static const struct snd_soc_dai_ops axg_frddr_ops = {
  89        .startup        = axg_frddr_dai_startup,
  90        .shutdown       = axg_frddr_dai_shutdown,
  91};
  92
  93static struct snd_soc_dai_driver axg_frddr_dai_drv = {
  94        .name = "FRDDR",
  95        .playback = {
  96                .stream_name    = "Playback",
  97                .channels_min   = 1,
  98                .channels_max   = AXG_FIFO_CH_MAX,
  99                .rates          = AXG_FIFO_RATES,
 100                .formats        = AXG_FIFO_FORMATS,
 101        },
 102        .ops            = &axg_frddr_ops,
 103        .pcm_new        = axg_frddr_pcm_new,
 104};
 105
 106static const char * const axg_frddr_sel_texts[] = {
 107        "OUT 0", "OUT 1", "OUT 2", "OUT 3"
 108};
 109
 110static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
 111                            axg_frddr_sel_texts);
 112
 113static const struct snd_kcontrol_new axg_frddr_out_demux =
 114        SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum);
 115
 116static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = {
 117        SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0,
 118                           &axg_frddr_out_demux),
 119        SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
 120        SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
 121        SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
 122        SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
 123};
 124
 125static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = {
 126        { "SINK SEL", NULL, "Playback" },
 127        { "OUT 0", "OUT 0",  "SINK SEL" },
 128        { "OUT 1", "OUT 1",  "SINK SEL" },
 129        { "OUT 2", "OUT 2",  "SINK SEL" },
 130        { "OUT 3", "OUT 3",  "SINK SEL" },
 131};
 132
 133static const struct snd_soc_component_driver axg_frddr_component_drv = {
 134        .dapm_widgets           = axg_frddr_dapm_widgets,
 135        .num_dapm_widgets       = ARRAY_SIZE(axg_frddr_dapm_widgets),
 136        .dapm_routes            = axg_frddr_dapm_routes,
 137        .num_dapm_routes        = ARRAY_SIZE(axg_frddr_dapm_routes),
 138        .ops                    = &axg_fifo_pcm_ops
 139};
 140
 141static const struct axg_fifo_match_data axg_frddr_match_data = {
 142        .component_drv  = &axg_frddr_component_drv,
 143        .dai_drv        = &axg_frddr_dai_drv
 144};
 145
 146static const struct snd_soc_dai_ops g12a_frddr_ops = {
 147        .prepare        = g12a_frddr_dai_prepare,
 148        .startup        = axg_frddr_dai_startup,
 149        .shutdown       = axg_frddr_dai_shutdown,
 150};
 151
 152static struct snd_soc_dai_driver g12a_frddr_dai_drv = {
 153        .name = "FRDDR",
 154        .playback = {
 155                .stream_name    = "Playback",
 156                .channels_min   = 1,
 157                .channels_max   = AXG_FIFO_CH_MAX,
 158                .rates          = AXG_FIFO_RATES,
 159                .formats        = AXG_FIFO_FORMATS,
 160        },
 161        .ops            = &g12a_frddr_ops,
 162        .pcm_new        = axg_frddr_pcm_new,
 163};
 164
 165static const char * const g12a_frddr_sel_texts[] = {
 166        "OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4",
 167};
 168
 169static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
 170                            g12a_frddr_sel_texts);
 171static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT,
 172                            g12a_frddr_sel_texts);
 173static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT,
 174                            g12a_frddr_sel_texts);
 175
 176static const struct snd_kcontrol_new g12a_frddr_out1_demux =
 177        SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum);
 178static const struct snd_kcontrol_new g12a_frddr_out2_demux =
 179        SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum);
 180static const struct snd_kcontrol_new g12a_frddr_out3_demux =
 181        SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum);
 182
 183static const struct snd_kcontrol_new g12a_frddr_out1_enable =
 184        SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
 185                                    CTRL0_SEL1_EN_SHIFT, 1, 0);
 186static const struct snd_kcontrol_new g12a_frddr_out2_enable =
 187        SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
 188                                    CTRL0_SEL2_EN_SHIFT, 1, 0);
 189static const struct snd_kcontrol_new g12a_frddr_out3_enable =
 190        SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
 191                                    CTRL0_SEL3_EN_SHIFT, 1, 0);
 192
 193static const struct snd_soc_dapm_widget g12a_frddr_dapm_widgets[] = {
 194        SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0),
 195        SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0),
 196        SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0),
 197        SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0,
 198                            &g12a_frddr_out1_enable),
 199        SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0,
 200                            &g12a_frddr_out2_enable),
 201        SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0,
 202                            &g12a_frddr_out3_enable),
 203        SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0,
 204                           &g12a_frddr_out1_demux),
 205        SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0,
 206                           &g12a_frddr_out2_demux),
 207        SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0,
 208                           &g12a_frddr_out3_demux),
 209        SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
 210        SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
 211        SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
 212        SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
 213        SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
 214};
 215
 216static const struct snd_soc_dapm_route g12a_frddr_dapm_routes[] = {
 217        { "SRC 1", NULL, "Playback" },
 218        { "SRC 2", NULL, "Playback" },
 219        { "SRC 3", NULL, "Playback" },
 220        { "SRC 1 EN", "Switch", "SRC 1" },
 221        { "SRC 2 EN", "Switch", "SRC 2" },
 222        { "SRC 3 EN", "Switch", "SRC 3" },
 223        { "SINK 1 SEL", NULL, "SRC 1 EN" },
 224        { "SINK 2 SEL", NULL, "SRC 2 EN" },
 225        { "SINK 3 SEL", NULL, "SRC 3 EN" },
 226        { "OUT 0", "OUT 0", "SINK 1 SEL" },
 227        { "OUT 1", "OUT 1", "SINK 1 SEL" },
 228        { "OUT 2", "OUT 2", "SINK 1 SEL" },
 229        { "OUT 3", "OUT 3", "SINK 1 SEL" },
 230        { "OUT 4", "OUT 4", "SINK 1 SEL" },
 231        { "OUT 0", "OUT 0", "SINK 2 SEL" },
 232        { "OUT 1", "OUT 1", "SINK 2 SEL" },
 233        { "OUT 2", "OUT 2", "SINK 2 SEL" },
 234        { "OUT 3", "OUT 3", "SINK 2 SEL" },
 235        { "OUT 4", "OUT 4", "SINK 2 SEL" },
 236        { "OUT 0", "OUT 0", "SINK 3 SEL" },
 237        { "OUT 1", "OUT 1", "SINK 3 SEL" },
 238        { "OUT 2", "OUT 2", "SINK 3 SEL" },
 239        { "OUT 3", "OUT 3", "SINK 3 SEL" },
 240        { "OUT 4", "OUT 4", "SINK 3 SEL" },
 241};
 242
 243static const struct snd_soc_component_driver g12a_frddr_component_drv = {
 244        .dapm_widgets           = g12a_frddr_dapm_widgets,
 245        .num_dapm_widgets       = ARRAY_SIZE(g12a_frddr_dapm_widgets),
 246        .dapm_routes            = g12a_frddr_dapm_routes,
 247        .num_dapm_routes        = ARRAY_SIZE(g12a_frddr_dapm_routes),
 248        .ops                    = &g12a_fifo_pcm_ops
 249};
 250
 251static const struct axg_fifo_match_data g12a_frddr_match_data = {
 252        .component_drv  = &g12a_frddr_component_drv,
 253        .dai_drv        = &g12a_frddr_dai_drv
 254};
 255
 256static const struct of_device_id axg_frddr_of_match[] = {
 257        {
 258                .compatible = "amlogic,axg-frddr",
 259                .data = &axg_frddr_match_data,
 260        }, {
 261                .compatible = "amlogic,g12a-frddr",
 262                .data = &g12a_frddr_match_data,
 263        }, {}
 264};
 265MODULE_DEVICE_TABLE(of, axg_frddr_of_match);
 266
 267static struct platform_driver axg_frddr_pdrv = {
 268        .probe = axg_fifo_probe,
 269        .driver = {
 270                .name = "axg-frddr",
 271                .of_match_table = axg_frddr_of_match,
 272        },
 273};
 274module_platform_driver(axg_frddr_pdrv);
 275
 276MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver");
 277MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 278MODULE_LICENSE("GPL v2");
 279