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 #define CTRL2_SEL1_SHIFT 0 27 #define CTRL2_SEL1_EN_SHIFT 4 28 #define CTRL2_SEL2_SHIFT 8 29 #define CTRL2_SEL2_EN_SHIFT 12 30 #define CTRL2_SEL3_SHIFT 16 31 #define CTRL2_SEL3_EN_SHIFT 20 32 33 static int g12a_frddr_dai_prepare(struct snd_pcm_substream *substream, 34 struct snd_soc_dai *dai) 35 { 36 struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); 37 38 /* Reset the read pointer to the FIFO_INIT_ADDR */ 39 regmap_update_bits(fifo->map, FIFO_CTRL1, 40 CTRL1_FRDDR_FORCE_FINISH, 0); 41 regmap_update_bits(fifo->map, FIFO_CTRL1, 42 CTRL1_FRDDR_FORCE_FINISH, CTRL1_FRDDR_FORCE_FINISH); 43 regmap_update_bits(fifo->map, FIFO_CTRL1, 44 CTRL1_FRDDR_FORCE_FINISH, 0); 45 46 return 0; 47 } 48 49 static int axg_frddr_dai_startup(struct snd_pcm_substream *substream, 50 struct snd_soc_dai *dai) 51 { 52 struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); 53 unsigned int fifo_depth, fifo_threshold; 54 int ret; 55 56 /* Enable pclk to access registers and clock the fifo ip */ 57 ret = clk_prepare_enable(fifo->pclk); 58 if (ret) 59 return ret; 60 61 /* Apply single buffer mode to the interface */ 62 regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0); 63 64 /* 65 * TODO: We could adapt the fifo depth and the fifo threshold 66 * depending on the expected memory throughput and lantencies 67 * For now, we'll just use the same values as the vendor kernel 68 * Depth and threshold are zero based. 69 */ 70 fifo_depth = AXG_FIFO_MIN_CNT - 1; 71 fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1; 72 regmap_update_bits(fifo->map, FIFO_CTRL1, 73 CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK, 74 CTRL1_FRDDR_DEPTH(fifo_depth) | 75 CTRL1_THRESHOLD(fifo_threshold)); 76 77 return 0; 78 } 79 80 static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream, 81 struct snd_soc_dai *dai) 82 { 83 struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); 84 85 clk_disable_unprepare(fifo->pclk); 86 } 87 88 static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd, 89 struct snd_soc_dai *dai) 90 { 91 return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK); 92 } 93 94 static const struct snd_soc_dai_ops axg_frddr_ops = { 95 .startup = axg_frddr_dai_startup, 96 .shutdown = axg_frddr_dai_shutdown, 97 }; 98 99 static struct snd_soc_dai_driver axg_frddr_dai_drv = { 100 .name = "FRDDR", 101 .playback = { 102 .stream_name = "Playback", 103 .channels_min = 1, 104 .channels_max = AXG_FIFO_CH_MAX, 105 .rates = AXG_FIFO_RATES, 106 .formats = AXG_FIFO_FORMATS, 107 }, 108 .ops = &axg_frddr_ops, 109 .pcm_new = axg_frddr_pcm_new, 110 }; 111 112 static const char * const axg_frddr_sel_texts[] = { 113 "OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", "OUT 5", "OUT 6", "OUT 7", 114 }; 115 116 static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, 117 axg_frddr_sel_texts); 118 119 static const struct snd_kcontrol_new axg_frddr_out_demux = 120 SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum); 121 122 static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = { 123 SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0, 124 &axg_frddr_out_demux), 125 SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0), 126 SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0), 127 SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0), 128 SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0), 129 SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0), 130 SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0), 131 SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0), 132 SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0), 133 }; 134 135 static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = { 136 { "SINK SEL", NULL, "Playback" }, 137 { "OUT 0", "OUT 0", "SINK SEL" }, 138 { "OUT 1", "OUT 1", "SINK SEL" }, 139 { "OUT 2", "OUT 2", "SINK SEL" }, 140 { "OUT 3", "OUT 3", "SINK SEL" }, 141 { "OUT 4", "OUT 4", "SINK SEL" }, 142 { "OUT 5", "OUT 5", "SINK SEL" }, 143 { "OUT 6", "OUT 6", "SINK SEL" }, 144 { "OUT 7", "OUT 7", "SINK SEL" }, 145 }; 146 147 static const struct snd_soc_component_driver axg_frddr_component_drv = { 148 .dapm_widgets = axg_frddr_dapm_widgets, 149 .num_dapm_widgets = ARRAY_SIZE(axg_frddr_dapm_widgets), 150 .dapm_routes = axg_frddr_dapm_routes, 151 .num_dapm_routes = ARRAY_SIZE(axg_frddr_dapm_routes), 152 .open = axg_fifo_pcm_open, 153 .close = axg_fifo_pcm_close, 154 .ioctl = snd_soc_pcm_lib_ioctl, 155 .hw_params = axg_fifo_pcm_hw_params, 156 .hw_free = axg_fifo_pcm_hw_free, 157 .pointer = axg_fifo_pcm_pointer, 158 .trigger = axg_fifo_pcm_trigger, 159 }; 160 161 static const struct axg_fifo_match_data axg_frddr_match_data = { 162 .component_drv = &axg_frddr_component_drv, 163 .dai_drv = &axg_frddr_dai_drv 164 }; 165 166 static const struct snd_soc_dai_ops g12a_frddr_ops = { 167 .prepare = g12a_frddr_dai_prepare, 168 .startup = axg_frddr_dai_startup, 169 .shutdown = axg_frddr_dai_shutdown, 170 }; 171 172 static struct snd_soc_dai_driver g12a_frddr_dai_drv = { 173 .name = "FRDDR", 174 .playback = { 175 .stream_name = "Playback", 176 .channels_min = 1, 177 .channels_max = AXG_FIFO_CH_MAX, 178 .rates = AXG_FIFO_RATES, 179 .formats = AXG_FIFO_FORMATS, 180 }, 181 .ops = &g12a_frddr_ops, 182 .pcm_new = axg_frddr_pcm_new, 183 }; 184 185 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, 186 axg_frddr_sel_texts); 187 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT, 188 axg_frddr_sel_texts); 189 static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT, 190 axg_frddr_sel_texts); 191 192 static const struct snd_kcontrol_new g12a_frddr_out1_demux = 193 SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum); 194 static const struct snd_kcontrol_new g12a_frddr_out2_demux = 195 SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum); 196 static const struct snd_kcontrol_new g12a_frddr_out3_demux = 197 SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum); 198 199 static const struct snd_kcontrol_new g12a_frddr_out1_enable = 200 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, 201 CTRL0_SEL1_EN_SHIFT, 1, 0); 202 static const struct snd_kcontrol_new g12a_frddr_out2_enable = 203 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, 204 CTRL0_SEL2_EN_SHIFT, 1, 0); 205 static const struct snd_kcontrol_new g12a_frddr_out3_enable = 206 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, 207 CTRL0_SEL3_EN_SHIFT, 1, 0); 208 209 static const struct snd_soc_dapm_widget g12a_frddr_dapm_widgets[] = { 210 SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0), 211 SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0), 212 SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0), 213 SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0, 214 &g12a_frddr_out1_enable), 215 SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0, 216 &g12a_frddr_out2_enable), 217 SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0, 218 &g12a_frddr_out3_enable), 219 SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0, 220 &g12a_frddr_out1_demux), 221 SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0, 222 &g12a_frddr_out2_demux), 223 SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0, 224 &g12a_frddr_out3_demux), 225 SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0), 226 SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0), 227 SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0), 228 SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0), 229 SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0), 230 SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0), 231 SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0), 232 SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0), 233 }; 234 235 static const struct snd_soc_dapm_route g12a_frddr_dapm_routes[] = { 236 { "SRC 1", NULL, "Playback" }, 237 { "SRC 2", NULL, "Playback" }, 238 { "SRC 3", NULL, "Playback" }, 239 { "SRC 1 EN", "Switch", "SRC 1" }, 240 { "SRC 2 EN", "Switch", "SRC 2" }, 241 { "SRC 3 EN", "Switch", "SRC 3" }, 242 { "SINK 1 SEL", NULL, "SRC 1 EN" }, 243 { "SINK 2 SEL", NULL, "SRC 2 EN" }, 244 { "SINK 3 SEL", NULL, "SRC 3 EN" }, 245 { "OUT 0", "OUT 0", "SINK 1 SEL" }, 246 { "OUT 1", "OUT 1", "SINK 1 SEL" }, 247 { "OUT 2", "OUT 2", "SINK 1 SEL" }, 248 { "OUT 3", "OUT 3", "SINK 1 SEL" }, 249 { "OUT 4", "OUT 4", "SINK 1 SEL" }, 250 { "OUT 5", "OUT 5", "SINK 1 SEL" }, 251 { "OUT 6", "OUT 6", "SINK 1 SEL" }, 252 { "OUT 7", "OUT 7", "SINK 1 SEL" }, 253 { "OUT 0", "OUT 0", "SINK 2 SEL" }, 254 { "OUT 1", "OUT 1", "SINK 2 SEL" }, 255 { "OUT 2", "OUT 2", "SINK 2 SEL" }, 256 { "OUT 3", "OUT 3", "SINK 2 SEL" }, 257 { "OUT 4", "OUT 4", "SINK 2 SEL" }, 258 { "OUT 5", "OUT 5", "SINK 2 SEL" }, 259 { "OUT 6", "OUT 6", "SINK 2 SEL" }, 260 { "OUT 7", "OUT 7", "SINK 2 SEL" }, 261 { "OUT 0", "OUT 0", "SINK 3 SEL" }, 262 { "OUT 1", "OUT 1", "SINK 3 SEL" }, 263 { "OUT 2", "OUT 2", "SINK 3 SEL" }, 264 { "OUT 3", "OUT 3", "SINK 3 SEL" }, 265 { "OUT 4", "OUT 4", "SINK 3 SEL" }, 266 { "OUT 5", "OUT 5", "SINK 3 SEL" }, 267 { "OUT 6", "OUT 6", "SINK 3 SEL" }, 268 { "OUT 7", "OUT 7", "SINK 3 SEL" }, 269 }; 270 271 static const struct snd_soc_component_driver g12a_frddr_component_drv = { 272 .dapm_widgets = g12a_frddr_dapm_widgets, 273 .num_dapm_widgets = ARRAY_SIZE(g12a_frddr_dapm_widgets), 274 .dapm_routes = g12a_frddr_dapm_routes, 275 .num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes), 276 .open = axg_fifo_pcm_open, 277 .close = axg_fifo_pcm_close, 278 .ioctl = snd_soc_pcm_lib_ioctl, 279 .hw_params = g12a_fifo_pcm_hw_params, 280 .hw_free = axg_fifo_pcm_hw_free, 281 .pointer = axg_fifo_pcm_pointer, 282 .trigger = axg_fifo_pcm_trigger, 283 }; 284 285 static const struct axg_fifo_match_data g12a_frddr_match_data = { 286 .component_drv = &g12a_frddr_component_drv, 287 .dai_drv = &g12a_frddr_dai_drv 288 }; 289 290 /* On SM1, the output selection in on CTRL2 */ 291 static const struct snd_kcontrol_new sm1_frddr_out1_enable = 292 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2, 293 CTRL2_SEL1_EN_SHIFT, 1, 0); 294 static const struct snd_kcontrol_new sm1_frddr_out2_enable = 295 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2, 296 CTRL2_SEL2_EN_SHIFT, 1, 0); 297 static const struct snd_kcontrol_new sm1_frddr_out3_enable = 298 SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2, 299 CTRL2_SEL3_EN_SHIFT, 1, 0); 300 301 static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel1_enum, FIFO_CTRL2, CTRL2_SEL1_SHIFT, 302 axg_frddr_sel_texts); 303 static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel2_enum, FIFO_CTRL2, CTRL2_SEL2_SHIFT, 304 axg_frddr_sel_texts); 305 static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel3_enum, FIFO_CTRL2, CTRL2_SEL3_SHIFT, 306 axg_frddr_sel_texts); 307 308 static const struct snd_kcontrol_new sm1_frddr_out1_demux = 309 SOC_DAPM_ENUM("Output Src 1", sm1_frddr_sel1_enum); 310 static const struct snd_kcontrol_new sm1_frddr_out2_demux = 311 SOC_DAPM_ENUM("Output Src 2", sm1_frddr_sel2_enum); 312 static const struct snd_kcontrol_new sm1_frddr_out3_demux = 313 SOC_DAPM_ENUM("Output Src 3", sm1_frddr_sel3_enum); 314 315 static const struct snd_soc_dapm_widget sm1_frddr_dapm_widgets[] = { 316 SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0), 317 SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0), 318 SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0), 319 SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0, 320 &sm1_frddr_out1_enable), 321 SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0, 322 &sm1_frddr_out2_enable), 323 SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0, 324 &sm1_frddr_out3_enable), 325 SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0, 326 &sm1_frddr_out1_demux), 327 SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0, 328 &sm1_frddr_out2_demux), 329 SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0, 330 &sm1_frddr_out3_demux), 331 SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0), 332 SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0), 333 SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0), 334 SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0), 335 SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0), 336 SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0), 337 SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0), 338 SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0), 339 }; 340 341 static const struct snd_soc_component_driver sm1_frddr_component_drv = { 342 .dapm_widgets = sm1_frddr_dapm_widgets, 343 .num_dapm_widgets = ARRAY_SIZE(sm1_frddr_dapm_widgets), 344 .dapm_routes = g12a_frddr_dapm_routes, 345 .num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes), 346 .open = axg_fifo_pcm_open, 347 .close = axg_fifo_pcm_close, 348 .ioctl = snd_soc_pcm_lib_ioctl, 349 .hw_params = g12a_fifo_pcm_hw_params, 350 .hw_free = axg_fifo_pcm_hw_free, 351 .pointer = axg_fifo_pcm_pointer, 352 .trigger = axg_fifo_pcm_trigger, 353 }; 354 355 static const struct axg_fifo_match_data sm1_frddr_match_data = { 356 .component_drv = &sm1_frddr_component_drv, 357 .dai_drv = &g12a_frddr_dai_drv 358 }; 359 360 static const struct of_device_id axg_frddr_of_match[] = { 361 { 362 .compatible = "amlogic,axg-frddr", 363 .data = &axg_frddr_match_data, 364 }, { 365 .compatible = "amlogic,g12a-frddr", 366 .data = &g12a_frddr_match_data, 367 }, { 368 .compatible = "amlogic,sm1-frddr", 369 .data = &sm1_frddr_match_data, 370 }, {} 371 }; 372 MODULE_DEVICE_TABLE(of, axg_frddr_of_match); 373 374 static struct platform_driver axg_frddr_pdrv = { 375 .probe = axg_fifo_probe, 376 .driver = { 377 .name = "axg-frddr", 378 .of_match_table = axg_frddr_of_match, 379 }, 380 }; 381 module_platform_driver(axg_frddr_pdrv); 382 383 MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver"); 384 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 385 MODULE_LICENSE("GPL v2"); 386