| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * ZynqMP DisplayPort Subsystem Driver - Audio support
- *
- * Copyright (C) 2015 - 2024 Xilinx, Inc.
- *
- * Authors:
- * - Hyun Woo Kwon <hyun.kwon@xilinx.com>
- * - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
- */
- #include <linux/clk.h>
- #include <linux/device.h>
- #include <linux/mutex.h>
- #include <linux/pm_runtime.h>
- #include <sound/asoundef.h>
- #include <sound/core.h>
- #include <sound/dmaengine_pcm.h>
- #include <sound/initval.h>
- #include <sound/pcm.h>
- #include <sound/soc.h>
- #include <sound/tlv.h>
- #include "zynqmp_disp_regs.h"
- #include "zynqmp_dp.h"
- #include "zynqmp_dpsub.h"
- #define ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK 512
- #define ZYNQMP_NUM_PCMS 2
- struct zynqmp_dpsub_audio {
- void __iomem *base;
- struct snd_soc_card card;
- const char *dai_name;
- const char *link_names[ZYNQMP_NUM_PCMS];
- const char *pcm_names[ZYNQMP_NUM_PCMS];
- struct snd_soc_dai_driver dai_driver;
- struct snd_dmaengine_pcm_config pcm_configs[2];
- struct snd_soc_dai_link links[ZYNQMP_NUM_PCMS];
- struct {
- struct snd_soc_dai_link_component cpu;
- struct snd_soc_dai_link_component platform;
- } components[ZYNQMP_NUM_PCMS];
- /*
- * Protects:
- * - enabled_streams
- * - volumes
- * - current_rate
- */
- struct mutex enable_lock;
- u32 enabled_streams;
- u32 current_rate;
- u16 volumes[2];
- };
- static const struct snd_pcm_hardware zynqmp_dp_pcm_hw = {
- .info = SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_RESUME |
- SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
- .buffer_bytes_max = 128 * 1024,
- .period_bytes_min = 256,
- .period_bytes_max = 1024 * 1024,
- .periods_min = 2,
- .periods_max = 256,
- };
- static int zynqmp_dp_startup(struct snd_pcm_substream *substream)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- 256);
- return 0;
- }
- static const struct snd_soc_ops zynqmp_dp_ops = {
- .startup = zynqmp_dp_startup,
- };
- static void zynqmp_dp_audio_write(struct zynqmp_dpsub_audio *audio, int reg,
- u32 val)
- {
- writel(val, audio->base + reg);
- }
- static int dp_dai_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *socdai)
- {
- struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
- struct zynqmp_dpsub *dpsub =
- snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
- struct zynqmp_dpsub_audio *audio = dpsub->audio;
- int ret;
- u32 sample_rate;
- struct snd_aes_iec958 iec = { 0 };
- unsigned long rate;
- sample_rate = params_rate(params);
- if (sample_rate != 48000 && sample_rate != 44100)
- return -EINVAL;
- guard(mutex)(&audio->enable_lock);
- if (audio->enabled_streams && audio->current_rate != sample_rate) {
- dev_err(dpsub->dev,
- "Can't change rate while playback enabled\n");
- return -EINVAL;
- }
- if (audio->enabled_streams > 0) {
- /* Nothing to do */
- audio->enabled_streams++;
- return 0;
- }
- audio->current_rate = sample_rate;
- /* Note: clock rate can only be changed if the clock is disabled */
- ret = clk_set_rate(dpsub->aud_clk,
- sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK);
- if (ret) {
- dev_err(dpsub->dev, "can't set aud_clk to %u err:%d\n",
- sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK, ret);
- return ret;
- }
- clk_prepare_enable(dpsub->aud_clk);
- rate = clk_get_rate(dpsub->aud_clk);
- /* Ignore some offset +- 10 */
- if (abs(sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK - rate) > 10) {
- dev_err(dpsub->dev, "aud_clk offset is higher: %ld\n",
- sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK - rate);
- clk_disable_unprepare(dpsub->aud_clk);
- return -EINVAL;
- }
- pm_runtime_get_sync(dpsub->dev);
- zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_MIXER_VOLUME,
- audio->volumes[0] | (audio->volumes[1] << 16));
- /* Clear the audio soft reset register as it's an non-reset flop. */
- zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_SOFT_RESET, 0);
- /* Only 2 channel audio is supported now */
- zynqmp_dp_audio_set_channels(dpsub->dp, 2);
- zynqmp_dp_audio_write_n_m(dpsub->dp);
- /* Channel status */
- if (sample_rate == 48000)
- iec.status[3] = IEC958_AES3_CON_FS_48000;
- else
- iec.status[3] = IEC958_AES3_CON_FS_44100;
- for (unsigned int i = 0; i < AES_IEC958_STATUS_SIZE / 4; ++i) {
- u32 v;
- v = (iec.status[(i * 4) + 0] << 0) |
- (iec.status[(i * 4) + 1] << 8) |
- (iec.status[(i * 4) + 2] << 16) |
- (iec.status[(i * 4) + 3] << 24);
- zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_CH_STATUS(i), v);
- }
- zynqmp_dp_audio_enable(dpsub->dp);
- audio->enabled_streams++;
- return 0;
- }
- static int dp_dai_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *socdai)
- {
- struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
- struct zynqmp_dpsub *dpsub =
- snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
- struct zynqmp_dpsub_audio *audio = dpsub->audio;
- guard(mutex)(&audio->enable_lock);
- /* Nothing to do */
- if (audio->enabled_streams > 1) {
- audio->enabled_streams--;
- return 0;
- }
- pm_runtime_put(dpsub->dev);
- zynqmp_dp_audio_disable(dpsub->dp);
- /*
- * Reset doesn't work. If we assert reset between audio stop and start,
- * the audio won't start anymore. Probably we are missing writing
- * some audio related registers. A/B buf?
- */
- /*
- zynqmp_disp_audio_write(audio, ZYNQMP_DISP_AUD_SOFT_RESET,
- ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
- */
- clk_disable_unprepare(dpsub->aud_clk);
- audio->current_rate = 0;
- audio->enabled_streams--;
- return 0;
- }
- static const struct snd_soc_dai_ops zynqmp_dp_dai_ops = {
- .hw_params = dp_dai_hw_params,
- .hw_free = dp_dai_hw_free,
- };
- /*
- * Min = 10 * log10(0x1 / 0x2000) = -39.13
- * Max = 10 * log10(0xffffff / 0x2000) = 9.03
- */
- static const DECLARE_TLV_DB_RANGE(zynqmp_dp_tlv,
- 0x0, 0x0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, -3913, 1),
- 0x1, 0x2000, TLV_DB_LINEAR_ITEM(-3913, 0),
- 0x2000, 0xffff, TLV_DB_LINEAR_ITEM(0, 903),
- );
- static const struct snd_kcontrol_new zynqmp_dp_snd_controls[] = {
- SOC_SINGLE_TLV("Input0 Playback Volume", 0,
- 0, 0xffff, 0, zynqmp_dp_tlv),
- SOC_SINGLE_TLV("Input1 Playback Volume", 1,
- 0, 0xffff, 0, zynqmp_dp_tlv),
- };
- /*
- * Note: these read & write functions only support two "registers", 0 and 1,
- * for volume 0 and 1. In other words, these are not real register read/write
- * functions.
- *
- * This is done to support caching the volume value for the case where the
- * hardware is not enabled, and also to support locking as volumes 0 and 1
- * are in the same register.
- */
- static unsigned int zynqmp_dp_dai_read(struct snd_soc_component *component,
- unsigned int reg)
- {
- struct zynqmp_dpsub *dpsub = dev_get_drvdata(component->dev);
- struct zynqmp_dpsub_audio *audio = dpsub->audio;
- return audio->volumes[reg];
- }
- static int zynqmp_dp_dai_write(struct snd_soc_component *component,
- unsigned int reg, unsigned int val)
- {
- struct zynqmp_dpsub *dpsub = dev_get_drvdata(component->dev);
- struct zynqmp_dpsub_audio *audio = dpsub->audio;
- guard(mutex)(&audio->enable_lock);
- audio->volumes[reg] = val;
- if (audio->enabled_streams)
- zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_MIXER_VOLUME,
- audio->volumes[0] |
- (audio->volumes[1] << 16));
- return 0;
- }
- static const struct snd_soc_component_driver zynqmp_dp_component_driver = {
- .idle_bias_on = 1,
- .use_pmdown_time = 1,
- .endianness = 1,
- .controls = zynqmp_dp_snd_controls,
- .num_controls = ARRAY_SIZE(zynqmp_dp_snd_controls),
- .read = zynqmp_dp_dai_read,
- .write = zynqmp_dp_dai_write,
- };
- int zynqmp_audio_init(struct zynqmp_dpsub *dpsub)
- {
- struct platform_device *pdev = to_platform_device(dpsub->dev);
- struct device *dev = dpsub->dev;
- struct zynqmp_dpsub_audio *audio;
- struct snd_soc_card *card;
- void *dev_data;
- int ret;
- if (!dpsub->aud_clk)
- return 0;
- audio = devm_kzalloc(dev, sizeof(*audio), GFP_KERNEL);
- if (!audio)
- return -ENOMEM;
- dpsub->audio = audio;
- mutex_init(&audio->enable_lock);
- /* 0x2000 is the zero level, no change */
- audio->volumes[0] = 0x2000;
- audio->volumes[1] = 0x2000;
- audio->dai_name = devm_kasprintf(dev, GFP_KERNEL,
- "%s-dai", dev_name(dev));
- if (!audio->dai_name)
- return -ENOMEM;
- for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
- audio->link_names[i] = devm_kasprintf(dev, GFP_KERNEL,
- "%s-dp-%u", dev_name(dev), i);
- audio->pcm_names[i] = devm_kasprintf(dev, GFP_KERNEL,
- "%s-pcm-%u", dev_name(dev), i);
- if (!audio->link_names[i] || !audio->pcm_names[i])
- return -ENOMEM;
- }
- audio->base = devm_platform_ioremap_resource_byname(pdev, "aud");
- if (IS_ERR(audio->base))
- return PTR_ERR(audio->base);
- /* Create CPU DAI */
- audio->dai_driver = (struct snd_soc_dai_driver) {
- .name = audio->dai_name,
- .ops = &zynqmp_dp_dai_ops,
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- };
- ret = devm_snd_soc_register_component(dev, &zynqmp_dp_component_driver,
- &audio->dai_driver, 1);
- if (ret) {
- dev_err(dev, "Failed to register CPU DAI\n");
- return ret;
- }
- /* Create PCMs */
- for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
- struct snd_dmaengine_pcm_config *pcm_config =
- &audio->pcm_configs[i];
- *pcm_config = (struct snd_dmaengine_pcm_config){
- .name = audio->pcm_names[i],
- .pcm_hardware = &zynqmp_dp_pcm_hw,
- .prealloc_buffer_size = 64 * 1024,
- .chan_names[SNDRV_PCM_STREAM_PLAYBACK] =
- i == 0 ? "aud0" : "aud1",
- };
- ret = devm_snd_dmaengine_pcm_register(dev, pcm_config, 0);
- if (ret) {
- dev_err(dev, "Failed to register PCM %u\n", i);
- return ret;
- }
- }
- /* Create card */
- card = &audio->card;
- card->name = "DisplayPort";
- card->long_name = "DisplayPort Monitor";
- card->driver_name = "zynqmp_dpsub";
- card->dev = dev;
- card->owner = THIS_MODULE;
- card->num_links = ZYNQMP_NUM_PCMS;
- card->dai_link = audio->links;
- for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
- struct snd_soc_dai_link *link = &card->dai_link[i];
- link->ops = &zynqmp_dp_ops;
- link->name = audio->link_names[i];
- link->stream_name = audio->link_names[i];
- link->cpus = &audio->components[i].cpu;
- link->num_cpus = 1;
- link->cpus[0].dai_name = audio->dai_name;
- link->codecs = &snd_soc_dummy_dlc;
- link->num_codecs = 1;
- link->platforms = &audio->components[i].platform;
- link->num_platforms = 1;
- link->platforms[0].name = audio->pcm_names[i];
- }
- /*
- * HACK: devm_snd_soc_register_card() overwrites current drvdata
- * so we need to hack it back.
- */
- dev_data = dev_get_drvdata(dev);
- ret = devm_snd_soc_register_card(dev, card);
- dev_set_drvdata(dev, dev_data);
- if (ret) {
- /*
- * As older dtbs may not have the audio channel dmas defined,
- * instead of returning an error here we'll continue and just
- * mark the audio as disabled.
- */
- dev_err(dev, "Failed to register sound card, disabling audio support\n");
- devm_kfree(dev, audio);
- dpsub->audio = NULL;
- return 0;
- }
- return 0;
- }
- void zynqmp_audio_uninit(struct zynqmp_dpsub *dpsub)
- {
- struct zynqmp_dpsub_audio *audio = dpsub->audio;
- if (!audio)
- return;
- if (!dpsub->aud_clk)
- return;
- mutex_destroy(&audio->enable_lock);
- }
|