210 lines · c
1// SPDX-License-Identifier: GPL-2.02//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 427#define AIU_958_INTERNAL_DIV 228 29static void30aiu_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 int47aiu_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 own175 * clock. We should (in theory) change the source depending on the176 * 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 are180 * playing from the same FIFO. The clock should be in sync so, it181 * 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