Change the regulators id in accordance with b670e44fc3bd. Signed-off-by: Nuno Sá <nuno.sa@analog.com> Link: https://lore.kernel.org/r/20191021140816.262401-3-nuno.sa@analog.com Signed-off-by: Mark Brown <broonie@kernel.org>
		
			
				
	
	
		
			587 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			587 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| //
 | |
| // Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter driver
 | |
| //
 | |
| // Copyright 2019 Analog Devices Inc.
 | |
| 
 | |
| #include <linux/bitfield.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/regmap.h>
 | |
| #include <linux/regulator/consumer.h>
 | |
| #include <sound/pcm_params.h>
 | |
| #include <sound/soc.h>
 | |
| 
 | |
| #include "adau7118.h"
 | |
| 
 | |
| #define ADAU7118_DEC_RATIO_MASK		GENMASK(1, 0)
 | |
| #define ADAU7118_DEC_RATIO(x)		FIELD_PREP(ADAU7118_DEC_RATIO_MASK, x)
 | |
| #define ADAU7118_CLK_MAP_MASK		GENMASK(7, 4)
 | |
| #define ADAU7118_SLOT_WIDTH_MASK	GENMASK(5, 4)
 | |
| #define ADAU7118_SLOT_WIDTH(x)		FIELD_PREP(ADAU7118_SLOT_WIDTH_MASK, x)
 | |
| #define ADAU7118_TRISTATE_MASK		BIT(6)
 | |
| #define ADAU7118_TRISTATE(x)		FIELD_PREP(ADAU7118_TRISTATE_MASK, x)
 | |
| #define ADAU7118_DATA_FMT_MASK		GENMASK(3, 1)
 | |
| #define ADAU7118_DATA_FMT(x)		FIELD_PREP(ADAU7118_DATA_FMT_MASK, x)
 | |
| #define ADAU7118_SAI_MODE_MASK		BIT(0)
 | |
| #define ADAU7118_SAI_MODE(x)		FIELD_PREP(ADAU7118_SAI_MODE_MASK, x)
 | |
| #define ADAU7118_LRCLK_BCLK_POL_MASK	GENMASK(1, 0)
 | |
| #define ADAU7118_LRCLK_BCLK_POL(x) \
 | |
| 				FIELD_PREP(ADAU7118_LRCLK_BCLK_POL_MASK, x)
 | |
| #define ADAU7118_SPT_SLOT_MASK		GENMASK(7, 4)
 | |
| #define ADAU7118_SPT_SLOT(x)		FIELD_PREP(ADAU7118_SPT_SLOT_MASK, x)
 | |
| #define ADAU7118_FULL_SOFT_R_MASK	BIT(1)
 | |
| #define ADAU7118_FULL_SOFT_R(x)		FIELD_PREP(ADAU7118_FULL_SOFT_R_MASK, x)
 | |
| 
 | |
| struct adau7118_data {
 | |
| 	struct regmap *map;
 | |
| 	struct device *dev;
 | |
| 	struct regulator *iovdd;
 | |
| 	struct regulator *dvdd;
 | |
| 	u32 slot_width;
 | |
| 	u32 slots;
 | |
| 	bool hw_mode;
 | |
| 	bool right_j;
 | |
| };
 | |
| 
 | |
| /* Input Enable */
 | |
| static const struct snd_kcontrol_new adau7118_dapm_pdm_control[4] = {
 | |
| 	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 0, 1, 0),
 | |
| 	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 1, 1, 0),
 | |
| 	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 2, 1, 0),
 | |
| 	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 3, 1, 0),
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_dapm_widget adau7118_widgets_sw[] = {
 | |
| 	/* Input Enable Switches */
 | |
| 	SND_SOC_DAPM_SWITCH("PDM0", SND_SOC_NOPM, 0, 0,
 | |
| 			    &adau7118_dapm_pdm_control[0]),
 | |
| 	SND_SOC_DAPM_SWITCH("PDM1", SND_SOC_NOPM, 0, 0,
 | |
| 			    &adau7118_dapm_pdm_control[1]),
 | |
| 	SND_SOC_DAPM_SWITCH("PDM2", SND_SOC_NOPM, 0, 0,
 | |
| 			    &adau7118_dapm_pdm_control[2]),
 | |
| 	SND_SOC_DAPM_SWITCH("PDM3", SND_SOC_NOPM, 0, 0,
 | |
| 			    &adau7118_dapm_pdm_control[3]),
 | |
| 
 | |
| 	/* PDM Clocks */
 | |
| 	SND_SOC_DAPM_SUPPLY("PDM_CLK0", ADAU7118_REG_ENABLES, 4, 0, NULL, 0),
 | |
| 	SND_SOC_DAPM_SUPPLY("PDM_CLK1", ADAU7118_REG_ENABLES, 5, 0, NULL, 0),
 | |
| 
 | |
| 	/* Output channels */
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, ADAU7118_REG_SPT_CX(0),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX2", "Capture", 0, ADAU7118_REG_SPT_CX(1),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX3", "Capture", 0, ADAU7118_REG_SPT_CX(2),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX4", "Capture", 0, ADAU7118_REG_SPT_CX(3),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX5", "Capture", 0, ADAU7118_REG_SPT_CX(4),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX6", "Capture", 0, ADAU7118_REG_SPT_CX(5),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX7", "Capture", 0, ADAU7118_REG_SPT_CX(6),
 | |
| 			     0, 0),
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX8", "Capture", 0, ADAU7118_REG_SPT_CX(7),
 | |
| 			     0, 0),
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_dapm_route adau7118_routes_sw[] = {
 | |
| 	{ "PDM0", "Capture Switch", "PDM_DAT0" },
 | |
| 	{ "PDM1", "Capture Switch", "PDM_DAT1" },
 | |
| 	{ "PDM2", "Capture Switch", "PDM_DAT2" },
 | |
| 	{ "PDM3", "Capture Switch", "PDM_DAT3" },
 | |
| 	{ "AIF1TX1", NULL, "PDM0" },
 | |
| 	{ "AIF1TX2", NULL, "PDM0" },
 | |
| 	{ "AIF1TX3", NULL, "PDM1" },
 | |
| 	{ "AIF1TX4", NULL, "PDM1" },
 | |
| 	{ "AIF1TX5", NULL, "PDM2" },
 | |
| 	{ "AIF1TX6", NULL, "PDM2" },
 | |
| 	{ "AIF1TX7", NULL, "PDM3" },
 | |
| 	{ "AIF1TX8", NULL, "PDM3" },
 | |
| 	{ "Capture", NULL, "PDM_CLK0" },
 | |
| 	{ "Capture", NULL, "PDM_CLK1" },
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_dapm_widget adau7118_widgets_hw[] = {
 | |
| 	SND_SOC_DAPM_AIF_OUT("AIF1TX", "Capture", 0, SND_SOC_NOPM, 0, 0),
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_dapm_route adau7118_routes_hw[] = {
 | |
| 	{ "AIF1TX", NULL, "PDM_DAT0" },
 | |
| 	{ "AIF1TX", NULL, "PDM_DAT1" },
 | |
| 	{ "AIF1TX", NULL, "PDM_DAT2" },
 | |
| 	{ "AIF1TX", NULL, "PDM_DAT3" },
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_dapm_widget adau7118_widgets[] = {
 | |
| 	SND_SOC_DAPM_INPUT("PDM_DAT0"),
 | |
| 	SND_SOC_DAPM_INPUT("PDM_DAT1"),
 | |
| 	SND_SOC_DAPM_INPUT("PDM_DAT2"),
 | |
| 	SND_SOC_DAPM_INPUT("PDM_DAT3"),
 | |
| };
 | |
| 
 | |
| static int adau7118_set_channel_map(struct snd_soc_dai *dai,
 | |
| 				    unsigned int tx_num, unsigned int *tx_slot,
 | |
| 				    unsigned int rx_num, unsigned int *rx_slot)
 | |
| {
 | |
| 	struct adau7118_data *st =
 | |
| 		snd_soc_component_get_drvdata(dai->component);
 | |
| 	int chan, ret;
 | |
| 
 | |
| 	dev_dbg(st->dev, "Set channel map, %d", tx_num);
 | |
| 
 | |
| 	for (chan = 0; chan < tx_num; chan++) {
 | |
| 		ret = snd_soc_component_update_bits(dai->component,
 | |
| 					ADAU7118_REG_SPT_CX(chan),
 | |
| 					ADAU7118_SPT_SLOT_MASK,
 | |
| 					ADAU7118_SPT_SLOT(tx_slot[chan]));
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int adau7118_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 | |
| {
 | |
| 	struct adau7118_data *st =
 | |
| 		snd_soc_component_get_drvdata(dai->component);
 | |
| 	int ret = 0;
 | |
| 	u32 regval;
 | |
| 
 | |
| 	dev_dbg(st->dev, "Set format, fmt:%d\n", fmt);
 | |
| 
 | |
| 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 | |
| 	case SND_SOC_DAIFMT_I2S:
 | |
| 		ret = snd_soc_component_update_bits(dai->component,
 | |
| 						    ADAU7118_REG_SPT_CTRL1,
 | |
| 						    ADAU7118_DATA_FMT_MASK,
 | |
| 						    ADAU7118_DATA_FMT(0));
 | |
| 		break;
 | |
| 	case SND_SOC_DAIFMT_LEFT_J:
 | |
| 		ret = snd_soc_component_update_bits(dai->component,
 | |
| 						    ADAU7118_REG_SPT_CTRL1,
 | |
| 						    ADAU7118_DATA_FMT_MASK,
 | |
| 						    ADAU7118_DATA_FMT(1));
 | |
| 		break;
 | |
| 	case SND_SOC_DAIFMT_RIGHT_J:
 | |
| 		st->right_j = true;
 | |
| 		break;
 | |
| 	default:
 | |
| 		dev_err(st->dev, "Invalid format %d",
 | |
| 			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 | |
| 	case SND_SOC_DAIFMT_NB_NF:
 | |
| 		regval = ADAU7118_LRCLK_BCLK_POL(0);
 | |
| 		break;
 | |
| 	case SND_SOC_DAIFMT_NB_IF:
 | |
| 		regval = ADAU7118_LRCLK_BCLK_POL(2);
 | |
| 		break;
 | |
| 	case SND_SOC_DAIFMT_IB_NF:
 | |
| 		regval = ADAU7118_LRCLK_BCLK_POL(1);
 | |
| 		break;
 | |
| 	case SND_SOC_DAIFMT_IB_IF:
 | |
| 		regval = ADAU7118_LRCLK_BCLK_POL(3);
 | |
| 		break;
 | |
| 	default:
 | |
| 		dev_err(st->dev, "Invalid Inv mask %d",
 | |
| 			fmt & SND_SOC_DAIFMT_INV_MASK);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	ret = snd_soc_component_update_bits(dai->component,
 | |
| 					    ADAU7118_REG_SPT_CTRL2,
 | |
| 					    ADAU7118_LRCLK_BCLK_POL_MASK,
 | |
| 					    regval);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int adau7118_set_tristate(struct snd_soc_dai *dai, int tristate)
 | |
| {
 | |
| 	struct adau7118_data *st =
 | |
| 		snd_soc_component_get_drvdata(dai->component);
 | |
| 	int ret;
 | |
| 
 | |
| 	dev_dbg(st->dev, "Set tristate, %d\n", tristate);
 | |
| 
 | |
| 	ret = snd_soc_component_update_bits(dai->component,
 | |
| 					    ADAU7118_REG_SPT_CTRL1,
 | |
| 					    ADAU7118_TRISTATE_MASK,
 | |
| 					    ADAU7118_TRISTATE(tristate));
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int adau7118_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
 | |
| 				 unsigned int rx_mask, int slots,
 | |
| 				 int slot_width)
 | |
| {
 | |
| 	struct adau7118_data *st =
 | |
| 		snd_soc_component_get_drvdata(dai->component);
 | |
| 	int ret = 0;
 | |
| 	u32 regval;
 | |
| 
 | |
| 	dev_dbg(st->dev, "Set tdm, slots:%d width:%d\n", slots, slot_width);
 | |
| 
 | |
| 	switch (slot_width) {
 | |
| 	case 32:
 | |
| 		regval = ADAU7118_SLOT_WIDTH(0);
 | |
| 		break;
 | |
| 	case 24:
 | |
| 		regval = ADAU7118_SLOT_WIDTH(2);
 | |
| 		break;
 | |
| 	case 16:
 | |
| 		regval = ADAU7118_SLOT_WIDTH(1);
 | |
| 		break;
 | |
| 	default:
 | |
| 		dev_err(st->dev, "Invalid slot width:%d\n", slot_width);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	ret = snd_soc_component_update_bits(dai->component,
 | |
| 					    ADAU7118_REG_SPT_CTRL1,
 | |
| 					    ADAU7118_SLOT_WIDTH_MASK, regval);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	st->slot_width = slot_width;
 | |
| 	st->slots = slots;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int adau7118_hw_params(struct snd_pcm_substream *substream,
 | |
| 			      struct snd_pcm_hw_params *params,
 | |
| 			      struct snd_soc_dai *dai)
 | |
| {
 | |
| 	struct adau7118_data *st =
 | |
| 		snd_soc_component_get_drvdata(dai->component);
 | |
| 	u32 data_width = params_width(params), slots_width;
 | |
| 	int ret;
 | |
| 	u32 regval;
 | |
| 
 | |
| 	if (!st->slots) {
 | |
| 		/* set stereo mode */
 | |
| 		ret = snd_soc_component_update_bits(dai->component,
 | |
| 						    ADAU7118_REG_SPT_CTRL1,
 | |
| 						    ADAU7118_SAI_MODE_MASK,
 | |
| 						    ADAU7118_SAI_MODE(0));
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		slots_width = 32;
 | |
| 	} else {
 | |
| 		slots_width = st->slot_width;
 | |
| 	}
 | |
| 
 | |
| 	if (data_width > slots_width) {
 | |
| 		dev_err(st->dev, "Invalid data_width:%d, slots_width:%d",
 | |
| 			data_width, slots_width);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (st->right_j) {
 | |
| 		switch (slots_width - data_width) {
 | |
| 		case 8:
 | |
| 			/* delay bclck by 8 */
 | |
| 			regval = ADAU7118_DATA_FMT(2);
 | |
| 			break;
 | |
| 		case 12:
 | |
| 			/* delay bclck by 12 */
 | |
| 			regval = ADAU7118_DATA_FMT(3);
 | |
| 			break;
 | |
| 		case 16:
 | |
| 			/* delay bclck by 16 */
 | |
| 			regval = ADAU7118_DATA_FMT(4);
 | |
| 			break;
 | |
| 		default:
 | |
| 			dev_err(st->dev,
 | |
| 				"Cannot set right_j setting, slot_w:%d, data_w:%d\n",
 | |
| 					slots_width, data_width);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		ret = snd_soc_component_update_bits(dai->component,
 | |
| 						    ADAU7118_REG_SPT_CTRL1,
 | |
| 						    ADAU7118_DATA_FMT_MASK,
 | |
| 						    regval);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int adau7118_set_bias_level(struct snd_soc_component *component,
 | |
| 				   enum snd_soc_bias_level level)
 | |
| {
 | |
| 	struct adau7118_data *st = snd_soc_component_get_drvdata(component);
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	dev_dbg(st->dev, "Set bias level %d\n", level);
 | |
| 
 | |
| 	switch (level) {
 | |
| 	case SND_SOC_BIAS_ON:
 | |
| 	case SND_SOC_BIAS_PREPARE:
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SOC_BIAS_STANDBY:
 | |
| 		if (snd_soc_component_get_bias_level(component) ==
 | |
| 							SND_SOC_BIAS_OFF) {
 | |
| 			/* power on */
 | |
| 			ret = regulator_enable(st->iovdd);
 | |
| 			if (ret)
 | |
| 				return ret;
 | |
| 
 | |
| 			/* there's no timing constraints before enabling dvdd */
 | |
| 			ret = regulator_enable(st->dvdd);
 | |
| 			if (ret) {
 | |
| 				regulator_disable(st->iovdd);
 | |
| 				return ret;
 | |
| 			}
 | |
| 
 | |
| 			if (st->hw_mode)
 | |
| 				return 0;
 | |
| 
 | |
| 			regcache_cache_only(st->map, false);
 | |
| 			/* sync cache */
 | |
| 			ret = snd_soc_component_cache_sync(component);
 | |
| 		}
 | |
| 		break;
 | |
| 	case SND_SOC_BIAS_OFF:
 | |
| 		/* power off */
 | |
| 		ret = regulator_disable(st->dvdd);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		ret = regulator_disable(st->iovdd);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		if (st->hw_mode)
 | |
| 			return 0;
 | |
| 
 | |
| 		/* cache only */
 | |
| 		regcache_mark_dirty(st->map);
 | |
| 		regcache_cache_only(st->map, true);
 | |
| 
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int adau7118_component_probe(struct snd_soc_component *component)
 | |
| {
 | |
| 	struct adau7118_data *st = snd_soc_component_get_drvdata(component);
 | |
| 	struct snd_soc_dapm_context *dapm =
 | |
| 					snd_soc_component_get_dapm(component);
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	if (st->hw_mode) {
 | |
| 		ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_hw,
 | |
| 					ARRAY_SIZE(adau7118_widgets_hw));
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_hw,
 | |
| 					      ARRAY_SIZE(adau7118_routes_hw));
 | |
| 	} else {
 | |
| 		snd_soc_component_init_regmap(component, st->map);
 | |
| 		ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_sw,
 | |
| 					ARRAY_SIZE(adau7118_widgets_sw));
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_sw,
 | |
| 					      ARRAY_SIZE(adau7118_routes_sw));
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct snd_soc_dai_ops adau7118_ops = {
 | |
| 	.hw_params = adau7118_hw_params,
 | |
| 	.set_channel_map = adau7118_set_channel_map,
 | |
| 	.set_fmt = adau7118_set_fmt,
 | |
| 	.set_tdm_slot = adau7118_set_tdm_slot,
 | |
| 	.set_tristate = adau7118_set_tristate,
 | |
| };
 | |
| 
 | |
| static struct snd_soc_dai_driver adau7118_dai = {
 | |
| 	.name = "adau7118-hifi-capture",
 | |
| 	.capture = {
 | |
| 		.stream_name = "Capture",
 | |
| 		.channels_min = 1,
 | |
| 		.channels_max = 8,
 | |
| 		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
 | |
| 			SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_S24_LE |
 | |
| 			SNDRV_PCM_FMTBIT_S24_3LE,
 | |
| 		.rates = SNDRV_PCM_RATE_CONTINUOUS,
 | |
| 		.rate_min = 4000,
 | |
| 		.rate_max = 192000,
 | |
| 		.sig_bits = 24,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static const struct snd_soc_component_driver adau7118_component_driver = {
 | |
| 	.probe			= adau7118_component_probe,
 | |
| 	.set_bias_level		= adau7118_set_bias_level,
 | |
| 	.dapm_widgets		= adau7118_widgets,
 | |
| 	.num_dapm_widgets	= ARRAY_SIZE(adau7118_widgets),
 | |
| 	.use_pmdown_time	= 1,
 | |
| 	.endianness		= 1,
 | |
| 	.non_legacy_dai_naming	= 1,
 | |
| };
 | |
| 
 | |
| static void adau7118_regulator_disable(void *data)
 | |
| {
 | |
| 	struct adau7118_data *st = data;
 | |
| 	int ret;
 | |
| 	/*
 | |
| 	 * If we fail to disable DVDD, don't bother in trying IOVDD. We
 | |
| 	 * actually don't want to be left in the situation where DVDD
 | |
| 	 * is enabled and IOVDD is disabled.
 | |
| 	 */
 | |
| 	ret = regulator_disable(st->dvdd);
 | |
| 	if (ret)
 | |
| 		return;
 | |
| 
 | |
| 	regulator_disable(st->iovdd);
 | |
| }
 | |
| 
 | |
| static int adau7118_regulator_setup(struct adau7118_data *st)
 | |
| {
 | |
| 	st->iovdd = devm_regulator_get(st->dev, "iovdd");
 | |
| 	if (IS_ERR(st->iovdd)) {
 | |
| 		dev_err(st->dev, "Could not get iovdd: %ld\n",
 | |
| 			PTR_ERR(st->iovdd));
 | |
| 		return PTR_ERR(st->iovdd);
 | |
| 	}
 | |
| 
 | |
| 	st->dvdd = devm_regulator_get(st->dev, "dvdd");
 | |
| 	if (IS_ERR(st->dvdd)) {
 | |
| 		dev_err(st->dev, "Could not get dvdd: %ld\n",
 | |
| 			PTR_ERR(st->dvdd));
 | |
| 		return PTR_ERR(st->dvdd);
 | |
| 	}
 | |
| 	/* just assume the device is in reset */
 | |
| 	if (!st->hw_mode) {
 | |
| 		regcache_mark_dirty(st->map);
 | |
| 		regcache_cache_only(st->map, true);
 | |
| 	}
 | |
| 
 | |
| 	return devm_add_action_or_reset(st->dev, adau7118_regulator_disable,
 | |
| 					st);
 | |
| }
 | |
| 
 | |
| static int adau7118_parset_dt(const struct adau7118_data *st)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 dec_ratio = 0;
 | |
| 	/* 4 inputs */
 | |
| 	u32 clk_map[4], regval;
 | |
| 
 | |
| 	if (st->hw_mode)
 | |
| 		return 0;
 | |
| 
 | |
| 	ret = device_property_read_u32(st->dev, "adi,decimation-ratio",
 | |
| 				       &dec_ratio);
 | |
| 	if (!ret) {
 | |
| 		switch (dec_ratio) {
 | |
| 		case 64:
 | |
| 			regval = ADAU7118_DEC_RATIO(0);
 | |
| 			break;
 | |
| 		case 32:
 | |
| 			regval = ADAU7118_DEC_RATIO(1);
 | |
| 			break;
 | |
| 		case 16:
 | |
| 			regval = ADAU7118_DEC_RATIO(2);
 | |
| 			break;
 | |
| 		default:
 | |
| 			dev_err(st->dev, "Invalid dec ratio: %u", dec_ratio);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		ret = regmap_update_bits(st->map,
 | |
| 					 ADAU7118_REG_DEC_RATIO_CLK_MAP,
 | |
| 					 ADAU7118_DEC_RATIO_MASK, regval);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = device_property_read_u32_array(st->dev, "adi,pdm-clk-map",
 | |
| 					     clk_map, ARRAY_SIZE(clk_map));
 | |
| 	if (!ret) {
 | |
| 		int pdm;
 | |
| 		u32 _clk_map = 0;
 | |
| 
 | |
| 		for (pdm = 0; pdm < ARRAY_SIZE(clk_map); pdm++)
 | |
| 			_clk_map |= (clk_map[pdm] << (pdm + 4));
 | |
| 
 | |
| 		ret = regmap_update_bits(st->map,
 | |
| 					 ADAU7118_REG_DEC_RATIO_CLK_MAP,
 | |
| 					 ADAU7118_CLK_MAP_MASK, _clk_map);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int adau7118_probe(struct device *dev, struct regmap *map, bool hw_mode)
 | |
| {
 | |
| 	struct adau7118_data *st;
 | |
| 	int ret;
 | |
| 
 | |
| 	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
 | |
| 	if (!st)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	st->dev = dev;
 | |
| 	st->hw_mode = hw_mode;
 | |
| 	dev_set_drvdata(dev, st);
 | |
| 
 | |
| 	if (!hw_mode) {
 | |
| 		st->map = map;
 | |
| 		adau7118_dai.ops = &adau7118_ops;
 | |
| 		/*
 | |
| 		 * Perform a full soft reset. This will set all register's
 | |
| 		 * with their reset values.
 | |
| 		 */
 | |
| 		ret = regmap_update_bits(map, ADAU7118_REG_RESET,
 | |
| 					 ADAU7118_FULL_SOFT_R_MASK,
 | |
| 					 ADAU7118_FULL_SOFT_R(1));
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = adau7118_parset_dt(st);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = adau7118_regulator_setup(st);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return devm_snd_soc_register_component(dev,
 | |
| 					       &adau7118_component_driver,
 | |
| 					       &adau7118_dai, 1);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(adau7118_probe);
 | |
| 
 | |
| MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
 | |
| MODULE_DESCRIPTION("ADAU7118 8 channel PDM-to-I2S/TDM Converter driver");
 | |
| MODULE_LICENSE("GPL");
 |