// SPDX-License-Identifier: GPL-2.0-only // // tegra210_ope.c - Tegra210 OPE driver // // Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra210_mbdrc.h" #include "tegra210_ope.h" #include "tegra210_peq.h" #include "tegra_cif.h" static const struct reg_default tegra210_ope_reg_defaults[] = { { TEGRA210_OPE_RX_INT_MASK, 0x00000001}, { TEGRA210_OPE_RX_CIF_CTRL, 0x00007700}, { TEGRA210_OPE_TX_INT_MASK, 0x00000001}, { TEGRA210_OPE_TX_CIF_CTRL, 0x00007700}, { TEGRA210_OPE_CG, 0x1}, }; static int tegra210_ope_set_audio_cif(struct tegra210_ope *ope, struct snd_pcm_hw_params *params, unsigned int reg) { int channels, audio_bits; struct tegra_cif_conf cif_conf; memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); channels = params_channels(params); if (channels < 2) return -EINVAL; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: audio_bits = TEGRA_ACIF_BITS_16; break; case SNDRV_PCM_FORMAT_S32_LE: audio_bits = TEGRA_ACIF_BITS_32; break; default: return -EINVAL; } cif_conf.audio_ch = channels; cif_conf.client_ch = channels; cif_conf.audio_bits = audio_bits; cif_conf.client_bits = audio_bits; tegra_set_cif(ope->regmap, reg, &cif_conf); return 0; } static int tegra210_ope_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct device *dev = dai->dev; struct tegra210_ope *ope = snd_soc_dai_get_drvdata(dai); int err; /* Set RX and TX CIF */ err = tegra210_ope_set_audio_cif(ope, params, TEGRA210_OPE_RX_CIF_CTRL); if (err) { dev_err(dev, "Can't set OPE RX CIF: %d\n", err); return err; } err = tegra210_ope_set_audio_cif(ope, params, TEGRA210_OPE_TX_CIF_CTRL); if (err) { dev_err(dev, "Can't set OPE TX CIF: %d\n", err); return err; } tegra210_mbdrc_hw_params(dai->component); return err; } static int tegra210_ope_component_probe(struct snd_soc_component *cmpnt) { struct tegra210_ope *ope = dev_get_drvdata(cmpnt->dev); tegra210_peq_component_init(cmpnt); tegra210_mbdrc_component_init(cmpnt); /* * The OPE, PEQ and MBDRC functionalities are combined under one * device registered by OPE driver. In fact OPE HW block includes * sub blocks PEQ and MBDRC. However driver registers separate * regmap interfaces for each of these. ASoC core depends on * dev_get_regmap() to populate the regmap field for a given ASoC * component. A component can have one regmap reference and since * the DAPM routes depend on OPE regmap only, below explicit * assignment is done to highlight this. This is needed for ASoC * core to access correct regmap during DAPM path setup. */ snd_soc_component_init_regmap(cmpnt, ope->regmap); return 0; } static const struct snd_soc_dai_ops tegra210_ope_dai_ops = { .hw_params = tegra210_ope_hw_params, }; static struct snd_soc_dai_driver tegra210_ope_dais[] = { { .name = "OPE-RX-CIF", .playback = { .stream_name = "RX-CIF-Playback", .channels_min = 1, .channels_max = 8, .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "RX-CIF-Capture", .channels_min = 1, .channels_max = 8, .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, }, }, { .name = "OPE-TX-CIF", .playback = { .stream_name = "TX-CIF-Playback", .channels_min = 1, .channels_max = 8, .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "TX-CIF-Capture", .channels_min = 1, .channels_max = 8, .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &tegra210_ope_dai_ops, } }; static const struct snd_soc_dapm_widget tegra210_ope_widgets[] = { SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_OPE_ENABLE, TEGRA210_OPE_EN_SHIFT, 0), }; #define OPE_ROUTES(sname) \ { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ { "RX", NULL, "RX-CIF-" sname }, \ { "TX-CIF-" sname, NULL, "TX" }, \ { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ { "XBAR-RX", NULL, "TX XBAR-" sname } static const struct snd_soc_dapm_route tegra210_ope_routes[] = { { "TX", NULL, "RX" }, OPE_ROUTES("Playback"), OPE_ROUTES("Capture"), }; static const char * const tegra210_ope_data_dir_text[] = { "MBDRC to PEQ", "PEQ to MBDRC" }; static const struct soc_enum tegra210_ope_data_dir_enum = SOC_ENUM_SINGLE(TEGRA210_OPE_DIR, TEGRA210_OPE_DIR_SHIFT, 2, tegra210_ope_data_dir_text); static int tegra210_ope_get_data_dir(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); ucontrol->value.enumerated.item[0] = ope->data_dir; return 0; } static int tegra210_ope_put_data_dir(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); unsigned int value = ucontrol->value.enumerated.item[0]; if (value == ope->data_dir) return 0; ope->data_dir = value; return 1; } static const struct snd_kcontrol_new tegra210_ope_controls[] = { SOC_ENUM_EXT("Data Flow Direction", tegra210_ope_data_dir_enum, tegra210_ope_get_data_dir, tegra210_ope_put_data_dir), }; static const struct snd_soc_component_driver tegra210_ope_cmpnt = { .probe = tegra210_ope_component_probe, .dapm_widgets = tegra210_ope_widgets, .num_dapm_widgets = ARRAY_SIZE(tegra210_ope_widgets), .dapm_routes = tegra210_ope_routes, .num_dapm_routes = ARRAY_SIZE(tegra210_ope_routes), .controls = tegra210_ope_controls, .num_controls = ARRAY_SIZE(tegra210_ope_controls), }; static bool tegra210_ope_wr_reg(struct device *dev, unsigned int reg) { switch (reg) { case TEGRA210_OPE_RX_INT_MASK ... TEGRA210_OPE_RX_CIF_CTRL: case TEGRA210_OPE_TX_INT_MASK ... TEGRA210_OPE_TX_CIF_CTRL: case TEGRA210_OPE_ENABLE ... TEGRA210_OPE_CG: case TEGRA210_OPE_DIR: return true; default: return false; } } static bool tegra210_ope_rd_reg(struct device *dev, unsigned int reg) { if (tegra210_ope_wr_reg(dev, reg)) return true; switch (reg) { case TEGRA210_OPE_RX_STATUS: case TEGRA210_OPE_RX_INT_STATUS: case TEGRA210_OPE_TX_STATUS: case TEGRA210_OPE_TX_INT_STATUS: case TEGRA210_OPE_STATUS: case TEGRA210_OPE_INT_STATUS: return true; default: return false; } } static bool tegra210_ope_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case TEGRA210_OPE_RX_STATUS: case TEGRA210_OPE_RX_INT_STATUS: case TEGRA210_OPE_TX_STATUS: case TEGRA210_OPE_TX_INT_STATUS: case TEGRA210_OPE_SOFT_RESET: case TEGRA210_OPE_STATUS: case TEGRA210_OPE_INT_STATUS: return true; default: return false; } } static const struct regmap_config tegra210_ope_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = TEGRA210_OPE_DIR, .writeable_reg = tegra210_ope_wr_reg, .readable_reg = tegra210_ope_rd_reg, .volatile_reg = tegra210_ope_volatile_reg, .reg_defaults = tegra210_ope_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tegra210_ope_reg_defaults), .cache_type = REGCACHE_FLAT, }; static int tegra210_ope_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct tegra210_ope *ope; void __iomem *regs; int err; ope = devm_kzalloc(dev, sizeof(*ope), GFP_KERNEL); if (!ope) return -ENOMEM; regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) return PTR_ERR(regs); ope->regmap = devm_regmap_init_mmio(dev, regs, &tegra210_ope_regmap_config); if (IS_ERR(ope->regmap)) { dev_err(dev, "regmap init failed\n"); return PTR_ERR(ope->regmap); } regcache_cache_only(ope->regmap, true); dev_set_drvdata(dev, ope); err = tegra210_peq_regmap_init(pdev); if (err < 0) { dev_err(dev, "PEQ init failed\n"); return err; } err = tegra210_mbdrc_regmap_init(pdev); if (err < 0) { dev_err(dev, "MBDRC init failed\n"); return err; } err = devm_snd_soc_register_component(dev, &tegra210_ope_cmpnt, tegra210_ope_dais, ARRAY_SIZE(tegra210_ope_dais)); if (err) { dev_err(dev, "can't register OPE component, err: %d\n", err); return err; } pm_runtime_enable(dev); return 0; } static void tegra210_ope_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); } static int __maybe_unused tegra210_ope_runtime_suspend(struct device *dev) { struct tegra210_ope *ope = dev_get_drvdata(dev); tegra210_peq_save(ope->peq_regmap, ope->peq_biquad_gains, ope->peq_biquad_shifts); regcache_cache_only(ope->mbdrc_regmap, true); regcache_cache_only(ope->peq_regmap, true); regcache_cache_only(ope->regmap, true); regcache_mark_dirty(ope->regmap); regcache_mark_dirty(ope->peq_regmap); regcache_mark_dirty(ope->mbdrc_regmap); return 0; } static int __maybe_unused tegra210_ope_runtime_resume(struct device *dev) { struct tegra210_ope *ope = dev_get_drvdata(dev); regcache_cache_only(ope->regmap, false); regcache_cache_only(ope->peq_regmap, false); regcache_cache_only(ope->mbdrc_regmap, false); regcache_sync(ope->regmap); regcache_sync(ope->peq_regmap); regcache_sync(ope->mbdrc_regmap); tegra210_peq_restore(ope->peq_regmap, ope->peq_biquad_gains, ope->peq_biquad_shifts); return 0; } static const struct dev_pm_ops tegra210_ope_pm_ops = { SET_RUNTIME_PM_OPS(tegra210_ope_runtime_suspend, tegra210_ope_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static const struct of_device_id tegra210_ope_of_match[] = { { .compatible = "nvidia,tegra210-ope" }, {}, }; MODULE_DEVICE_TABLE(of, tegra210_ope_of_match); static struct platform_driver tegra210_ope_driver = { .driver = { .name = "tegra210-ope", .of_match_table = tegra210_ope_of_match, .pm = &tegra210_ope_pm_ops, }, .probe = tegra210_ope_probe, .remove_new = tegra210_ope_remove, }; module_platform_driver(tegra210_ope_driver) MODULE_AUTHOR("Sumit Bhattacharya "); MODULE_DESCRIPTION("Tegra210 OPE ASoC driver"); MODULE_LICENSE("GPL");