1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Copyright (c) 2019 BayLibre, SAS. 4 // Author: Jerome Brunet <jbrunet@baylibre.com> 5 6 #include <linux/bitfield.h> 7 #include <linux/clk.h> 8 #include <linux/module.h> 9 #include <sound/pcm_params.h> 10 #include <linux/regmap.h> 11 #include <sound/soc.h> 12 #include <sound/soc-dai.h> 13 14 #include <dt-bindings/sound/meson-g12a-tohdmitx.h> 15 16 #define G12A_TOHDMITX_DRV_NAME "g12a-tohdmitx" 17 18 #define TOHDMITX_CTRL0 0x0 19 #define CTRL0_ENABLE_SHIFT 31 20 #define CTRL0_I2S_DAT_SEL GENMASK(13, 12) 21 #define CTRL0_I2S_LRCLK_SEL GENMASK(9, 8) 22 #define CTRL0_I2S_BLK_CAP_INV BIT(7) 23 #define CTRL0_I2S_BCLK_O_INV BIT(6) 24 #define CTRL0_I2S_BCLK_SEL GENMASK(5, 4) 25 #define CTRL0_SPDIF_CLK_CAP_INV BIT(3) 26 #define CTRL0_SPDIF_CLK_O_INV BIT(2) 27 #define CTRL0_SPDIF_SEL BIT(1) 28 #define CTRL0_SPDIF_CLK_SEL BIT(0) 29 30 struct g12a_tohdmitx_input { 31 struct snd_pcm_hw_params params; 32 unsigned int fmt; 33 }; 34 35 static struct snd_soc_dapm_widget * 36 g12a_tohdmitx_get_input(struct snd_soc_dapm_widget *w) 37 { 38 struct snd_soc_dapm_path *p = NULL; 39 struct snd_soc_dapm_widget *in; 40 41 snd_soc_dapm_widget_for_each_source_path(w, p) { 42 if (!p->connect) 43 continue; 44 45 /* Check that we still are in the same component */ 46 if (snd_soc_dapm_to_component(w->dapm) != 47 snd_soc_dapm_to_component(p->source->dapm)) 48 continue; 49 50 if (p->source->id == snd_soc_dapm_dai_in) 51 return p->source; 52 53 in = g12a_tohdmitx_get_input(p->source); 54 if (in) 55 return in; 56 } 57 58 return NULL; 59 } 60 61 static struct g12a_tohdmitx_input * 62 g12a_tohdmitx_get_input_data(struct snd_soc_dapm_widget *w) 63 { 64 struct snd_soc_dapm_widget *in = 65 g12a_tohdmitx_get_input(w); 66 struct snd_soc_dai *dai; 67 68 if (WARN_ON(!in)) 69 return NULL; 70 71 dai = in->priv; 72 73 return dai->playback_dma_data; 74 } 75 76 static const char * const g12a_tohdmitx_i2s_mux_texts[] = { 77 "I2S A", "I2S B", "I2S C", 78 }; 79 80 static SOC_ENUM_SINGLE_EXT_DECL(g12a_tohdmitx_i2s_mux_enum, 81 g12a_tohdmitx_i2s_mux_texts); 82 83 static int g12a_tohdmitx_get_input_val(struct snd_soc_component *component, 84 unsigned int mask) 85 { 86 unsigned int val; 87 88 snd_soc_component_read(component, TOHDMITX_CTRL0, &val); 89 return (val & mask) >> __ffs(mask); 90 } 91 92 static int g12a_tohdmitx_i2s_mux_get_enum(struct snd_kcontrol *kcontrol, 93 struct snd_ctl_elem_value *ucontrol) 94 { 95 struct snd_soc_component *component = 96 snd_soc_dapm_kcontrol_component(kcontrol); 97 98 ucontrol->value.enumerated.item[0] = 99 g12a_tohdmitx_get_input_val(component, CTRL0_I2S_DAT_SEL); 100 101 return 0; 102 } 103 104 static int g12a_tohdmitx_i2s_mux_put_enum(struct snd_kcontrol *kcontrol, 105 struct snd_ctl_elem_value *ucontrol) 106 { 107 struct snd_soc_component *component = 108 snd_soc_dapm_kcontrol_component(kcontrol); 109 struct snd_soc_dapm_context *dapm = 110 snd_soc_dapm_kcontrol_dapm(kcontrol); 111 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 112 unsigned int mux = ucontrol->value.enumerated.item[0]; 113 unsigned int val = g12a_tohdmitx_get_input_val(component, 114 CTRL0_I2S_DAT_SEL); 115 116 /* Force disconnect of the mux while updating */ 117 if (val != mux) 118 snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL); 119 120 snd_soc_component_update_bits(component, TOHDMITX_CTRL0, 121 CTRL0_I2S_DAT_SEL | 122 CTRL0_I2S_LRCLK_SEL | 123 CTRL0_I2S_BCLK_SEL, 124 FIELD_PREP(CTRL0_I2S_DAT_SEL, mux) | 125 FIELD_PREP(CTRL0_I2S_LRCLK_SEL, mux) | 126 FIELD_PREP(CTRL0_I2S_BCLK_SEL, mux)); 127 128 snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL); 129 130 return 0; 131 } 132 133 static const struct snd_kcontrol_new g12a_tohdmitx_i2s_mux = 134 SOC_DAPM_ENUM_EXT("I2S Source", g12a_tohdmitx_i2s_mux_enum, 135 g12a_tohdmitx_i2s_mux_get_enum, 136 g12a_tohdmitx_i2s_mux_put_enum); 137 138 static const char * const g12a_tohdmitx_spdif_mux_texts[] = { 139 "SPDIF A", "SPDIF B", 140 }; 141 142 static SOC_ENUM_SINGLE_EXT_DECL(g12a_tohdmitx_spdif_mux_enum, 143 g12a_tohdmitx_spdif_mux_texts); 144 145 static int g12a_tohdmitx_spdif_mux_get_enum(struct snd_kcontrol *kcontrol, 146 struct snd_ctl_elem_value *ucontrol) 147 { 148 struct snd_soc_component *component = 149 snd_soc_dapm_kcontrol_component(kcontrol); 150 151 ucontrol->value.enumerated.item[0] = 152 g12a_tohdmitx_get_input_val(component, CTRL0_SPDIF_SEL); 153 154 return 0; 155 } 156 157 static int g12a_tohdmitx_spdif_mux_put_enum(struct snd_kcontrol *kcontrol, 158 struct snd_ctl_elem_value *ucontrol) 159 { 160 struct snd_soc_component *component = 161 snd_soc_dapm_kcontrol_component(kcontrol); 162 struct snd_soc_dapm_context *dapm = 163 snd_soc_dapm_kcontrol_dapm(kcontrol); 164 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; 165 unsigned int mux = ucontrol->value.enumerated.item[0]; 166 unsigned int val = g12a_tohdmitx_get_input_val(component, 167 CTRL0_SPDIF_SEL); 168 169 /* Force disconnect of the mux while updating */ 170 if (val != mux) 171 snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL); 172 173 snd_soc_component_update_bits(component, TOHDMITX_CTRL0, 174 CTRL0_SPDIF_SEL | 175 CTRL0_SPDIF_CLK_SEL, 176 FIELD_PREP(CTRL0_SPDIF_SEL, mux) | 177 FIELD_PREP(CTRL0_SPDIF_CLK_SEL, mux)); 178 179 snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL); 180 181 return 0; 182 } 183 184 static const struct snd_kcontrol_new g12a_tohdmitx_spdif_mux = 185 SOC_DAPM_ENUM_EXT("SPDIF Source", g12a_tohdmitx_spdif_mux_enum, 186 g12a_tohdmitx_spdif_mux_get_enum, 187 g12a_tohdmitx_spdif_mux_put_enum); 188 189 static const struct snd_kcontrol_new g12a_tohdmitx_out_enable = 190 SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOHDMITX_CTRL0, 191 CTRL0_ENABLE_SHIFT, 1, 0); 192 193 static const struct snd_soc_dapm_widget g12a_tohdmitx_widgets[] = { 194 SND_SOC_DAPM_MUX("I2S SRC", SND_SOC_NOPM, 0, 0, 195 &g12a_tohdmitx_i2s_mux), 196 SND_SOC_DAPM_SWITCH("I2S OUT EN", SND_SOC_NOPM, 0, 0, 197 &g12a_tohdmitx_out_enable), 198 SND_SOC_DAPM_MUX("SPDIF SRC", SND_SOC_NOPM, 0, 0, 199 &g12a_tohdmitx_spdif_mux), 200 SND_SOC_DAPM_SWITCH("SPDIF OUT EN", SND_SOC_NOPM, 0, 0, 201 &g12a_tohdmitx_out_enable), 202 }; 203 204 static int g12a_tohdmitx_input_probe(struct snd_soc_dai *dai) 205 { 206 struct g12a_tohdmitx_input *data; 207 208 data = kzalloc(sizeof(*data), GFP_KERNEL); 209 if (!data) 210 return -ENOMEM; 211 212 dai->playback_dma_data = data; 213 return 0; 214 } 215 216 static int g12a_tohdmitx_input_remove(struct snd_soc_dai *dai) 217 { 218 kfree(dai->playback_dma_data); 219 return 0; 220 } 221 222 static int g12a_tohdmitx_input_hw_params(struct snd_pcm_substream *substream, 223 struct snd_pcm_hw_params *params, 224 struct snd_soc_dai *dai) 225 { 226 struct g12a_tohdmitx_input *data = dai->playback_dma_data; 227 228 /* Save the stream params for the downstream link */ 229 memcpy(&data->params, params, sizeof(*params)); 230 231 return 0; 232 } 233 234 static int g12a_tohdmitx_output_hw_params(struct snd_pcm_substream *substream, 235 struct snd_pcm_hw_params *params, 236 struct snd_soc_dai *dai) 237 { 238 struct g12a_tohdmitx_input *in_data = 239 g12a_tohdmitx_get_input_data(dai->capture_widget); 240 241 if (!in_data) 242 return -ENODEV; 243 244 memcpy(params, &in_data->params, sizeof(*params)); 245 246 return 0; 247 } 248 249 static int g12a_tohdmitx_input_set_fmt(struct snd_soc_dai *dai, 250 unsigned int fmt) 251 { 252 struct g12a_tohdmitx_input *data = dai->playback_dma_data; 253 254 /* Save the source stream format for the downstream link */ 255 data->fmt = fmt; 256 return 0; 257 } 258 259 static int g12a_tohdmitx_output_startup(struct snd_pcm_substream *substream, 260 struct snd_soc_dai *dai) 261 { 262 struct snd_soc_pcm_runtime *rtd = substream->private_data; 263 struct g12a_tohdmitx_input *in_data = 264 g12a_tohdmitx_get_input_data(dai->capture_widget); 265 266 if (!in_data) 267 return -ENODEV; 268 269 if (!in_data->fmt) 270 return 0; 271 272 return snd_soc_runtime_set_dai_fmt(rtd, in_data->fmt); 273 } 274 275 static const struct snd_soc_dai_ops g12a_tohdmitx_input_ops = { 276 .hw_params = g12a_tohdmitx_input_hw_params, 277 .set_fmt = g12a_tohdmitx_input_set_fmt, 278 }; 279 280 static const struct snd_soc_dai_ops g12a_tohdmitx_output_ops = { 281 .hw_params = g12a_tohdmitx_output_hw_params, 282 .startup = g12a_tohdmitx_output_startup, 283 }; 284 285 #define TOHDMITX_SPDIF_FORMATS \ 286 (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 287 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE) 288 289 #define TOHDMITX_I2S_FORMATS \ 290 (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 291 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \ 292 SNDRV_PCM_FMTBIT_S32_LE) 293 294 #define TOHDMITX_STREAM(xname, xsuffix, xfmt, xchmax) \ 295 { \ 296 .stream_name = xname " " xsuffix, \ 297 .channels_min = 1, \ 298 .channels_max = (xchmax), \ 299 .rate_min = 8000, \ 300 .rate_max = 192000, \ 301 .formats = (xfmt), \ 302 } 303 304 #define TOHDMITX_IN(xname, xid, xfmt, xchmax) { \ 305 .name = xname, \ 306 .id = (xid), \ 307 .playback = TOHDMITX_STREAM(xname, "Playback", xfmt, xchmax), \ 308 .ops = &g12a_tohdmitx_input_ops, \ 309 .probe = g12a_tohdmitx_input_probe, \ 310 .remove = g12a_tohdmitx_input_remove, \ 311 } 312 313 #define TOHDMITX_OUT(xname, xid, xfmt, xchmax) { \ 314 .name = xname, \ 315 .id = (xid), \ 316 .capture = TOHDMITX_STREAM(xname, "Capture", xfmt, xchmax), \ 317 .ops = &g12a_tohdmitx_output_ops, \ 318 } 319 320 static struct snd_soc_dai_driver g12a_tohdmitx_dai_drv[] = { 321 TOHDMITX_IN("I2S IN A", TOHDMITX_I2S_IN_A, 322 TOHDMITX_I2S_FORMATS, 8), 323 TOHDMITX_IN("I2S IN B", TOHDMITX_I2S_IN_B, 324 TOHDMITX_I2S_FORMATS, 8), 325 TOHDMITX_IN("I2S IN C", TOHDMITX_I2S_IN_C, 326 TOHDMITX_I2S_FORMATS, 8), 327 TOHDMITX_OUT("I2S OUT", TOHDMITX_I2S_OUT, 328 TOHDMITX_I2S_FORMATS, 8), 329 TOHDMITX_IN("SPDIF IN A", TOHDMITX_SPDIF_IN_A, 330 TOHDMITX_SPDIF_FORMATS, 2), 331 TOHDMITX_IN("SPDIF IN B", TOHDMITX_SPDIF_IN_B, 332 TOHDMITX_SPDIF_FORMATS, 2), 333 TOHDMITX_OUT("SPDIF OUT", TOHDMITX_SPDIF_OUT, 334 TOHDMITX_SPDIF_FORMATS, 2), 335 }; 336 337 static int g12a_tohdmi_component_probe(struct snd_soc_component *c) 338 { 339 /* Initialize the static clock parameters */ 340 return snd_soc_component_write(c, TOHDMITX_CTRL0, 341 CTRL0_I2S_BLK_CAP_INV | CTRL0_SPDIF_CLK_CAP_INV); 342 } 343 344 static const struct snd_soc_dapm_route g12a_tohdmitx_routes[] = { 345 { "I2S SRC", "I2S A", "I2S IN A Playback" }, 346 { "I2S SRC", "I2S B", "I2S IN B Playback" }, 347 { "I2S SRC", "I2S C", "I2S IN C Playback" }, 348 { "I2S OUT EN", "Switch", "I2S SRC" }, 349 { "I2S OUT Capture", NULL, "I2S OUT EN" }, 350 { "SPDIF SRC", "SPDIF A", "SPDIF IN A Playback" }, 351 { "SPDIF SRC", "SPDIF B", "SPDIF IN B Playback" }, 352 { "SPDIF OUT EN", "Switch", "SPDIF SRC" }, 353 { "SPDIF OUT Capture", NULL, "SPDIF OUT EN" }, 354 }; 355 356 static const struct snd_soc_component_driver g12a_tohdmitx_component_drv = { 357 .probe = g12a_tohdmi_component_probe, 358 .dapm_widgets = g12a_tohdmitx_widgets, 359 .num_dapm_widgets = ARRAY_SIZE(g12a_tohdmitx_widgets), 360 .dapm_routes = g12a_tohdmitx_routes, 361 .num_dapm_routes = ARRAY_SIZE(g12a_tohdmitx_routes), 362 .endianness = 1, 363 .non_legacy_dai_naming = 1, 364 }; 365 366 static const struct regmap_config g12a_tohdmitx_regmap_cfg = { 367 .reg_bits = 32, 368 .val_bits = 32, 369 .reg_stride = 4, 370 }; 371 372 static const struct of_device_id g12a_tohdmitx_of_match[] = { 373 { .compatible = "amlogic,g12a-tohdmitx", }, 374 {} 375 }; 376 MODULE_DEVICE_TABLE(of, g12a_tohdmitx_of_match); 377 378 static int g12a_tohdmitx_probe(struct platform_device *pdev) 379 { 380 struct device *dev = &pdev->dev; 381 struct resource *res; 382 void __iomem *regs; 383 struct regmap *map; 384 385 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 386 regs = devm_ioremap_resource(dev, res); 387 if (IS_ERR(regs)) 388 return PTR_ERR(regs); 389 390 map = devm_regmap_init_mmio(dev, regs, &g12a_tohdmitx_regmap_cfg); 391 if (IS_ERR(map)) { 392 dev_err(dev, "failed to init regmap: %ld\n", 393 PTR_ERR(map)); 394 return PTR_ERR(map); 395 } 396 397 return devm_snd_soc_register_component(dev, 398 &g12a_tohdmitx_component_drv, g12a_tohdmitx_dai_drv, 399 ARRAY_SIZE(g12a_tohdmitx_dai_drv)); 400 } 401 402 static struct platform_driver g12a_tohdmitx_pdrv = { 403 .driver = { 404 .name = G12A_TOHDMITX_DRV_NAME, 405 .of_match_table = g12a_tohdmitx_of_match, 406 }, 407 .probe = g12a_tohdmitx_probe, 408 }; 409 module_platform_driver(g12a_tohdmitx_pdrv); 410 411 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 412 MODULE_DESCRIPTION("Amlogic G12a To HDMI Tx Control Codec Driver"); 413 MODULE_LICENSE("GPL v2"); 414