~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/sound/soc/meson/aiu-encoder-spdif.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php