linux/sound/soc/meson/aiu-encoder-spdif.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2//
   3// Copyright (c) 2020 BayLibre, SAS.
   4// Author: Jerome Brunet <jbrunet@baylibre.com>
   5
   6#include <linux/bitfield.h>
   7#include <linux/clk.h>
   8#include <sound/pcm_params.h>
   9#include <sound/pcm_iec958.h>
  10#include <sound/soc.h>
  11#include <sound/soc-dai.h>
  12
  13#include "aiu.h"
  14
  15#define AIU_958_MISC_NON_PCM            BIT(0)
  16#define AIU_958_MISC_MODE_16BITS        BIT(1)
  17#define AIU_958_MISC_16BITS_ALIGN       GENMASK(6, 5)
  18#define AIU_958_MISC_MODE_32BITS        BIT(7)
  19#define AIU_958_MISC_U_FROM_STREAM      BIT(12)
  20#define AIU_958_MISC_FORCE_LR           BIT(13)
  21#define AIU_958_CTRL_HOLD_EN            BIT(0)
  22#define AIU_CLK_CTRL_958_DIV_EN         BIT(1)
  23#define AIU_CLK_CTRL_958_DIV            GENMASK(5, 4)
  24#define AIU_CLK_CTRL_958_DIV_MORE       BIT(12)
  25
  26#define AIU_CS_WORD_LEN                 4
  27#define AIU_958_INTERNAL_DIV            2
  28
  29static void
  30aiu_encoder_spdif_divider_enable(struct snd_soc_component *component,
  31                                 bool enable)
  32{
  33        snd_soc_component_update_bits(component, AIU_CLK_CTRL,
  34                                      AIU_CLK_CTRL_958_DIV_EN,
  35                                      enable ? AIU_CLK_CTRL_958_DIV_EN : 0);
  36}
  37
  38static void aiu_encoder_spdif_hold(struct snd_soc_component *component,
  39                                   bool enable)
  40{
  41        snd_soc_component_update_bits(component, AIU_958_CTRL,
  42                                      AIU_958_CTRL_HOLD_EN,
  43                                      enable ? AIU_958_CTRL_HOLD_EN : 0);
  44}
  45
  46static int
  47aiu_encoder_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
  48                          struct snd_soc_dai *dai)
  49{
  50        struct snd_soc_component *component = dai->component;
  51
  52        switch (cmd) {
  53        case SNDRV_PCM_TRIGGER_START:
  54        case SNDRV_PCM_TRIGGER_RESUME:
  55        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  56                aiu_encoder_spdif_hold(component, false);
  57                return 0;
  58
  59        case SNDRV_PCM_TRIGGER_STOP:
  60        case SNDRV_PCM_TRIGGER_SUSPEND:
  61        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  62                aiu_encoder_spdif_hold(component, true);
  63                return 0;
  64
  65        default:
  66                return -EINVAL;
  67        }
  68}
  69
  70static int aiu_encoder_spdif_setup_cs_word(struct snd_soc_component *component,
  71                                           struct snd_pcm_hw_params *params)
  72{
  73        u8 cs[AIU_CS_WORD_LEN];
  74        unsigned int val;
  75        int ret;
  76
  77        ret = snd_pcm_create_iec958_consumer_hw_params(params, cs,
  78                                                       AIU_CS_WORD_LEN);
  79        if (ret < 0)
  80                return ret;
  81
  82        /* Write the 1st half word */
  83        val = cs[1] | cs[0] << 8;
  84        snd_soc_component_write(component, AIU_958_CHSTAT_L0, val);
  85        snd_soc_component_write(component, AIU_958_CHSTAT_R0, val);
  86
  87        /* Write the 2nd half word */
  88        val = cs[3] | cs[2] << 8;
  89        snd_soc_component_write(component, AIU_958_CHSTAT_L1, val);
  90        snd_soc_component_write(component, AIU_958_CHSTAT_R1, val);
  91
  92        return 0;
  93}
  94
  95static int aiu_encoder_spdif_hw_params(struct snd_pcm_substream *substream,
  96                                       struct snd_pcm_hw_params *params,
  97                                       struct snd_soc_dai *dai)
  98{
  99        struct snd_soc_component *component = dai->component;
 100        struct aiu *aiu = snd_soc_component_get_drvdata(component);
 101        unsigned int val = 0, mrate;
 102        int ret;
 103
 104        /* Disable the clock while changing the settings */
 105        aiu_encoder_spdif_divider_enable(component, false);
 106
 107        switch (params_physical_width(params)) {
 108        case 16:
 109                val |= AIU_958_MISC_MODE_16BITS;
 110                val |= FIELD_PREP(AIU_958_MISC_16BITS_ALIGN, 2);
 111                break;
 112        case 32:
 113                val |= AIU_958_MISC_MODE_32BITS;
 114                break;
 115        default:
 116                dev_err(dai->dev, "Unsupport physical width\n");
 117                return -EINVAL;
 118        }
 119
 120        snd_soc_component_update_bits(component, AIU_958_MISC,
 121                                      AIU_958_MISC_NON_PCM |
 122                                      AIU_958_MISC_MODE_16BITS |
 123                                      AIU_958_MISC_16BITS_ALIGN |
 124                                      AIU_958_MISC_MODE_32BITS |
 125                                      AIU_958_MISC_FORCE_LR |
 126                                      AIU_958_MISC_U_FROM_STREAM,
 127                                      val);
 128
 129        /* Set the stream channel status word */
 130        ret = aiu_encoder_spdif_setup_cs_word(component, params);
 131        if (ret) {
 132                dev_err(dai->dev, "failed to set channel status word\n");
 133                return ret;
 134        }
 135
 136        snd_soc_component_update_bits(component, AIU_CLK_CTRL,
 137                                      AIU_CLK_CTRL_958_DIV |
 138                                      AIU_CLK_CTRL_958_DIV_MORE,
 139                                      FIELD_PREP(AIU_CLK_CTRL_958_DIV,
 140                                                 __ffs(AIU_958_INTERNAL_DIV)));
 141
 142        /* 2 * 32bits per subframe * 2 channels = 128 */
 143        mrate = params_rate(params) * 128 * AIU_958_INTERNAL_DIV;
 144        ret = clk_set_rate(aiu->spdif.clks[MCLK].clk, mrate);
 145        if (ret) {
 146                dev_err(dai->dev, "failed to set mclk rate\n");
 147                return ret;
 148        }
 149
 150        aiu_encoder_spdif_divider_enable(component, true);
 151
 152        return 0;
 153}
 154
 155static int aiu_encoder_spdif_hw_free(struct snd_pcm_substream *substream,
 156                                     struct snd_soc_dai *dai)
 157{
 158        struct snd_soc_component *component = dai->component;
 159
 160        aiu_encoder_spdif_divider_enable(component, false);
 161
 162        return 0;
 163}
 164
 165static int aiu_encoder_spdif_startup(struct snd_pcm_substream *substream,
 166                                     struct snd_soc_dai *dai)
 167{
 168        struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
 169        int ret;
 170
 171        /*
 172         * NOTE: Make sure the spdif block is on its own divider.
 173         *
 174         * The spdif can be clocked by the i2s master clock or its own
 175         * clock. We should (in theory) change the source depending on the
 176         * origin of the data.
 177         *
 178         * However, considering the clocking scheme used on these platforms,
 179         * the master clocks will pick the same PLL source when they are
 180         * playing from the same FIFO. The clock should be in sync so, it
 181         * should not be necessary to reparent the spdif master clock.
 182         */
 183        ret = clk_set_parent(aiu->spdif.clks[MCLK].clk,
 184                             aiu->spdif_mclk);
 185        if (ret)
 186                return ret;
 187
 188        ret = clk_bulk_prepare_enable(aiu->spdif.clk_num, aiu->spdif.clks);
 189        if (ret)
 190                dev_err(dai->dev, "failed to enable spdif clocks\n");
 191
 192        return ret;
 193}
 194
 195static void aiu_encoder_spdif_shutdown(struct snd_pcm_substream *substream,
 196                                       struct snd_soc_dai *dai)
 197{
 198        struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
 199
 200        clk_bulk_disable_unprepare(aiu->spdif.clk_num, aiu->spdif.clks);
 201}
 202
 203const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops = {
 204        .trigger        = aiu_encoder_spdif_trigger,
 205        .hw_params      = aiu_encoder_spdif_hw_params,
 206        .hw_free        = aiu_encoder_spdif_hw_free,
 207        .startup        = aiu_encoder_spdif_startup,
 208        .shutdown       = aiu_encoder_spdif_shutdown,
 209};
 210