cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

aiu-encoder-i2s.c (8723B)


      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/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 be
    119	 * 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	else
    174		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_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
    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