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 27 static 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 43 static 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 74 static 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 82 static 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 88 static const struct snd_soc_dai_ops axg_frddr_ops = { 89 .startup = axg_frddr_dai_startup, 90 .shutdown = axg_frddr_dai_shutdown, 91 }; 92 93 static 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 106 static const char * const axg_frddr_sel_texts[] = { 107 "OUT 0", "OUT 1", "OUT 2", "OUT 3" 108 }; 109 110 static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, 111 axg_frddr_sel_texts); 112 113 static const struct snd_kcontrol_new axg_frddr_out_demux = 114 SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum); 115 116 static 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 125 static 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 133 static 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 141 static 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 146 static 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 152 static 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 165 static const char * const g12a_frddr_sel_texts[] = { 166 "OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", 167 }; 168 169 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, 170 g12a_frddr_sel_texts); 171 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT, 172 g12a_frddr_sel_texts); 173 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT, 174 g12a_frddr_sel_texts); 175 176 static const struct snd_kcontrol_new g12a_frddr_out1_demux = 177 SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum); 178 static const struct snd_kcontrol_new g12a_frddr_out2_demux = 179 SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum); 180 static const struct snd_kcontrol_new g12a_frddr_out3_demux = 181 SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum); 182 183 static const struct snd_kcontrol_new g12a_frddr_out1_enable = 184 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, 185 CTRL0_SEL1_EN_SHIFT, 1, 0); 186 static const struct snd_kcontrol_new g12a_frddr_out2_enable = 187 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, 188 CTRL0_SEL2_EN_SHIFT, 1, 0); 189 static 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 193 static 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 216 static 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 243 static 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 251 static 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 256 static 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 }; 265 MODULE_DEVICE_TABLE(of, axg_frddr_of_match); 266 267 static 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 }; 274 module_platform_driver(axg_frddr_pdrv); 275 276 MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver"); 277 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 278 MODULE_LICENSE("GPL v2"); 279