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 29 static void 30 aiu_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 38 static 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 46 static int 47 aiu_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 70 static 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 95 static 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, "Unsupported 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 155 static 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 165 static 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 195 static 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 203 const 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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.