brintos

brintos / linux-shallow public Read only

0
0
Text · 8.5 KiB · a0dd914 Raw
332 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/soc.h>10#include <sound/soc-dai.h>11 12#include "aiu.h"13 14#define AIU_I2S_SOURCE_DESC_MODE_8CH	BIT(0)15#define AIU_I2S_SOURCE_DESC_MODE_24BIT	BIT(5)16#define AIU_I2S_SOURCE_DESC_MODE_32BIT	BIT(9)17#define AIU_I2S_SOURCE_DESC_MODE_SPLIT	BIT(11)18#define AIU_RST_SOFT_I2S_FAST		BIT(0)19 20#define AIU_I2S_DAC_CFG_MSB_FIRST	BIT(2)21#define AIU_CLK_CTRL_I2S_DIV_EN		BIT(0)22#define AIU_CLK_CTRL_I2S_DIV		GENMASK(3, 2)23#define AIU_CLK_CTRL_AOCLK_INVERT	BIT(6)24#define AIU_CLK_CTRL_LRCLK_INVERT	BIT(7)25#define AIU_CLK_CTRL_LRCLK_SKEW		GENMASK(9, 8)26#define AIU_CLK_CTRL_MORE_HDMI_AMCLK	BIT(6)27#define AIU_CLK_CTRL_MORE_I2S_DIV	GENMASK(5, 0)28#define AIU_CODEC_DAC_LRCLK_CTRL_DIV	GENMASK(11, 0)29 30static void aiu_encoder_i2s_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_I2S_DIV_EN,35				      enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);36}37 38static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,39				      struct snd_pcm_hw_params *params)40{41	/* Always operate in split (classic interleaved) mode */42	unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;43 44	/* Reset required to update the pipeline */45	snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);46	snd_soc_component_read(component, AIU_I2S_SYNC);47 48	switch (params_physical_width(params)) {49	case 16: /* Nothing to do */50		break;51 52	case 32:53		desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |54			 AIU_I2S_SOURCE_DESC_MODE_32BIT);55		break;56 57	default:58		return -EINVAL;59	}60 61	switch (params_channels(params)) {62	case 2: /* Nothing to do */63		break;64	case 8:65		desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;66		break;67	default:68		return -EINVAL;69	}70 71	snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,72				      AIU_I2S_SOURCE_DESC_MODE_8CH |73				      AIU_I2S_SOURCE_DESC_MODE_24BIT |74				      AIU_I2S_SOURCE_DESC_MODE_32BIT |75				      AIU_I2S_SOURCE_DESC_MODE_SPLIT,76				      desc);77 78	return 0;79}80 81static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,82					  struct snd_pcm_hw_params *params,83					  unsigned int bs)84{85	switch (bs) {86	case 1:87	case 2:88	case 4:89	case 8:90		/* These are the only valid legacy dividers */91		break;92 93	default:94		dev_err(component->dev, "Unsupported i2s divider: %u\n", bs);95		return -EINVAL;96	}97 98	snd_soc_component_update_bits(component, AIU_CLK_CTRL,99				      AIU_CLK_CTRL_I2S_DIV,100				      FIELD_PREP(AIU_CLK_CTRL_I2S_DIV,101						 __ffs(bs)));102 103	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,104				      AIU_CLK_CTRL_MORE_I2S_DIV,105				      FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,106						 0));107 108	return 0;109}110 111static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,112					struct snd_pcm_hw_params *params,113					unsigned int bs)114{115	/*116	 * NOTE: this HW is odd.117	 * In most configuration, the i2s divider is 'mclk / blck'.118	 * However, in 16 bits - 8ch mode, this factor needs to be119	 * increased by 50% to get the correct output rate.120	 * No idea why !121	 */122	if (params_width(params) == 16 && params_channels(params) == 8) {123		if (bs % 2) {124			dev_err(component->dev,125				"Cannot increase i2s divider by 50%%\n");126			return -EINVAL;127		}128		bs += bs / 2;129	}130 131	/* Use CLK_MORE for mclk to bclk divider */132	snd_soc_component_update_bits(component, AIU_CLK_CTRL,133				      AIU_CLK_CTRL_I2S_DIV,134				      FIELD_PREP(AIU_CLK_CTRL_I2S_DIV, 0));135 136	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,137				      AIU_CLK_CTRL_MORE_I2S_DIV,138				      FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,139						 bs - 1));140 141	return 0;142}143 144static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,145				      struct snd_pcm_hw_params *params)146{147	struct aiu *aiu = snd_soc_component_get_drvdata(component);148	unsigned int srate = params_rate(params);149	unsigned int fs, bs;150	int ret;151 152	/* Get the oversampling factor */153	fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);154 155	if (fs % 64)156		return -EINVAL;157 158	/* Send data MSB first */159	snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,160				      AIU_I2S_DAC_CFG_MSB_FIRST,161				      AIU_I2S_DAC_CFG_MSB_FIRST);162 163	/* Set bclk to lrlck ratio */164	snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,165				      AIU_CODEC_DAC_LRCLK_CTRL_DIV,166				      FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,167						 64 - 1));168 169	bs = fs / 64;170 171	if (aiu->platform->has_clk_ctrl_more_i2s_div)172		ret = aiu_encoder_i2s_set_more_div(component, params, bs);173	else174		ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);175 176	if (ret)177		return ret;178 179	/* Make sure amclk is used for HDMI i2s as well */180	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,181				      AIU_CLK_CTRL_MORE_HDMI_AMCLK,182				      AIU_CLK_CTRL_MORE_HDMI_AMCLK);183 184	return 0;185}186 187static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,188				     struct snd_pcm_hw_params *params,189				     struct snd_soc_dai *dai)190{191	struct snd_soc_component *component = dai->component;192	int ret;193 194	/* Disable the clock while changing the settings */195	aiu_encoder_i2s_divider_enable(component, false);196 197	ret = aiu_encoder_i2s_setup_desc(component, params);198	if (ret) {199		dev_err(dai->dev, "setting i2s desc failed\n");200		return ret;201	}202 203	ret = aiu_encoder_i2s_set_clocks(component, params);204	if (ret) {205		dev_err(dai->dev, "setting i2s clocks failed\n");206		return ret;207	}208 209	aiu_encoder_i2s_divider_enable(component, true);210 211	return 0;212}213 214static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,215				   struct snd_soc_dai *dai)216{217	struct snd_soc_component *component = dai->component;218 219	aiu_encoder_i2s_divider_enable(component, false);220 221	return 0;222}223 224static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)225{226	struct snd_soc_component *component = dai->component;227	unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;228	unsigned int val = 0;229	unsigned int skew;230 231	/* Only CPU Master / Codec Slave supported ATM */232	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP)233		return -EINVAL;234 235	if (inv == SND_SOC_DAIFMT_NB_IF ||236	    inv == SND_SOC_DAIFMT_IB_IF)237		val |= AIU_CLK_CTRL_LRCLK_INVERT;238 239	if (inv == SND_SOC_DAIFMT_IB_NF ||240	    inv == SND_SOC_DAIFMT_IB_IF)241		val |= AIU_CLK_CTRL_AOCLK_INVERT;242 243	/* Signal skew */244	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {245	case SND_SOC_DAIFMT_I2S:246		/* Invert sample clock for i2s */247		val ^= AIU_CLK_CTRL_LRCLK_INVERT;248		skew = 1;249		break;250	case SND_SOC_DAIFMT_LEFT_J:251		skew = 0;252		break;253	default:254		return -EINVAL;255	}256 257	val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);258	snd_soc_component_update_bits(component, AIU_CLK_CTRL,259				      AIU_CLK_CTRL_LRCLK_INVERT |260				      AIU_CLK_CTRL_AOCLK_INVERT |261				      AIU_CLK_CTRL_LRCLK_SKEW,262				      val);263 264	return 0;265}266 267static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,268				      unsigned int freq, int dir)269{270	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);271	int ret;272 273	if (WARN_ON(clk_id != 0))274		return -EINVAL;275 276	if (dir == SND_SOC_CLOCK_IN)277		return 0;278 279	ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);280	if (ret)281		dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);282 283	return ret;284}285 286static const unsigned int hw_channels[] = {2, 8};287static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {288	.list = hw_channels,289	.count = ARRAY_SIZE(hw_channels),290	.mask = 0,291};292 293static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,294				   struct snd_soc_dai *dai)295{296	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);297	int ret;298 299	/* Make sure the encoder gets either 2 or 8 channels */300	ret = snd_pcm_hw_constraint_list(substream->runtime, 0,301					 SNDRV_PCM_HW_PARAM_CHANNELS,302					 &hw_channel_constraints);303	if (ret) {304		dev_err(dai->dev, "adding channels constraints failed\n");305		return ret;306	}307 308	ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);309	if (ret)310		dev_err(dai->dev, "failed to enable i2s clocks\n");311 312	return ret;313}314 315static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,316				     struct snd_soc_dai *dai)317{318	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);319 320	clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);321}322 323const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {324	.hw_params	= aiu_encoder_i2s_hw_params,325	.hw_free	= aiu_encoder_i2s_hw_free,326	.set_fmt	= aiu_encoder_i2s_set_fmt,327	.set_sysclk	= aiu_encoder_i2s_set_sysclk,328	.startup	= aiu_encoder_i2s_startup,329	.shutdown	= aiu_encoder_i2s_shutdown,330};331 332