aiu-encoder-spdif.c (5857B)
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, "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 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};