brintos

brintos / linux-shallow public Read only

0
0
Text · 5.7 KiB · 97da60d Raw
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