Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Paul Handrigan 4275 70.00% 4 22.22%
Simon Trimmer 1768 28.95% 4 22.22%
Vitaly Rodionov 52 0.85% 6 33.33%
Kuninori Morimoto 9 0.15% 3 16.67%
Peter Zijlstra 3 0.05% 1 5.56%
Total 6107 18


// SPDX-License-Identifier: GPL-2.0
//
// CS530x CODEC driver
//
// Copyright (C) 2024-2025 Cirrus Logic, Inc. and
//                         Cirrus Logic International Semiconductor Ltd.

#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>

#include "cs530x.h"

static const char *cs530x_supply_names[CS530X_NUM_SUPPLIES] = {
	"vdd-a",
	"vdd-io",
};

static const struct reg_default cs530x_reg_defaults[] = {
	{ CS530X_CLK_CFG_0, 0x30 },
	{ CS530X_CLK_CFG_1, 0x0001 },
	{ CS530X_CHIP_ENABLE, 0 },
	{ CS530X_ASP_CFG, 0 },
	{ CS530X_SIGNAL_PATH_CFG, 0 },
	{ CS530X_IN_ENABLES, 0 },
	{ CS530X_IN_RAMP_SUM, 0x0022 },
	{ CS530X_IN_FILTER, 0 },
	{ CS530X_IN_HIZ, 0 },
	{ CS530X_IN_INV, 0 },
	{ CS530X_IN_VOL_CTRL1_0, 0x8000 },
	{ CS530X_IN_VOL_CTRL1_1, 0x8000 },
	{ CS530X_IN_VOL_CTRL2_0, 0x8000 },
	{ CS530X_IN_VOL_CTRL2_1, 0x8000 },
	{ CS530X_IN_VOL_CTRL3_0, 0x8000 },
	{ CS530X_IN_VOL_CTRL3_1, 0x8000 },
	{ CS530X_IN_VOL_CTRL4_0, 0x8000 },
	{ CS530X_IN_VOL_CTRL4_1, 0x8000 },
	{ CS530X_OUT_ENABLES, 0 },
	{ CS530X_OUT_RAMP_SUM, 0x0022 },
	{ CS530X_OUT_FILTER, 0 },
	{ CS530X_OUT_INV, 0 },
	{ CS530X_OUT_VOL_CTRL1_0, 0x8000 },
	{ CS530X_OUT_VOL_CTRL1_1, 0x8000 },
	{ CS530X_OUT_VOL_CTRL2_0, 0x8000 },
	{ CS530X_OUT_VOL_CTRL2_1, 0x8000 },
	{ CS530X_OUT_VOL_CTRL3_0, 0x8000 },
	{ CS530X_OUT_VOL_CTRL3_1, 0x8000 },
	{ CS530X_OUT_VOL_CTRL4_0, 0x8000 },
	{ CS530X_OUT_VOL_CTRL4_1, 0x8000 },
	{ CS530X_PAD_FN, 0 },
	{ CS530X_PAD_LVL, 0 },
};

static bool cs530x_read_and_write_regs(unsigned int reg)
{
	switch (reg) {
	case CS530X_CLK_CFG_0:
	case CS530X_CLK_CFG_1:
	case CS530X_CHIP_ENABLE:
	case CS530X_ASP_CFG:
	case CS530X_SIGNAL_PATH_CFG:
	case CS530X_IN_ENABLES:
	case CS530X_IN_RAMP_SUM:
	case CS530X_IN_FILTER:
	case CS530X_IN_HIZ:
	case CS530X_IN_INV:
	case CS530X_IN_VOL_CTRL1_0:
	case CS530X_IN_VOL_CTRL1_1:
	case CS530X_IN_VOL_CTRL2_0:
	case CS530X_IN_VOL_CTRL2_1:
	case CS530X_IN_VOL_CTRL3_0:
	case CS530X_IN_VOL_CTRL3_1:
	case CS530X_IN_VOL_CTRL4_0:
	case CS530X_IN_VOL_CTRL4_1:
	case CS530X_OUT_ENABLES:
	case CS530X_OUT_RAMP_SUM:
	case CS530X_OUT_DEEMPH:
	case CS530X_OUT_FILTER:
	case CS530X_OUT_INV:
	case CS530X_OUT_VOL_CTRL1_0:
	case CS530X_OUT_VOL_CTRL1_1:
	case CS530X_OUT_VOL_CTRL2_0:
	case CS530X_OUT_VOL_CTRL2_1:
	case CS530X_OUT_VOL_CTRL3_0:
	case CS530X_OUT_VOL_CTRL3_1:
	case CS530X_OUT_VOL_CTRL4_0:
	case CS530X_OUT_VOL_CTRL4_1:
	case CS530X_PAD_FN:
	case CS530X_PAD_LVL:
		return true;
	default:
		return false;
	}
}

static bool cs530x_readable_register(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case CS530X_DEVID:
	case CS530X_REVID:
		return true;
	default:
		return cs530x_read_and_write_regs(reg);
	}
}

static bool cs530x_writeable_register(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case CS530X_SW_RESET:
	case CS530X_IN_VOL_CTRL5:
	case CS530X_OUT_VOL_CTRL5:
		return true;
	default:
		return cs530x_read_and_write_regs(reg);
	}
}

static int cs530x_put_volsw_vu(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;
	int ret;

	snd_soc_dapm_mutex_lock(dapm);

	ret = snd_soc_put_volsw(kcontrol, ucontrol);
	if (ret)
		goto volsw_err;

	/* Write INOUT_VU bit for the volume change to take effect */
	regmap_write(regmap, CS530X_IN_VOL_CTRL5, CS530X_INOUT_VU);

volsw_err:
	snd_soc_dapm_mutex_unlock(dapm);

	return ret;
}

static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1270, 50, 0);

static const char * const cs530x_inout_filter_text[] = {
	"Min Phase Slow Roll-off",
	"Min Phase Fast Roll-off",
	"Linear Phase Slow Roll-off",
	"Linear Phase Fast Roll-off",
};

static SOC_ENUM_SINGLE_DECL(cs530x_in_filter_enum, CS530X_IN_FILTER,
			    CS530X_INOUT_FILTER_SHIFT,
			    cs530x_inout_filter_text);

static SOC_ENUM_SINGLE_DECL(cs530x_out_filter_enum, CS530X_OUT_FILTER,
			    CS530X_INOUT_FILTER_SHIFT,
			    cs530x_inout_filter_text);

static const char * const cs530x_4ch_sum_text[] = {
	"None",
	"Groups of 2",
	"Groups of 4",
};

static SOC_ENUM_SINGLE_DECL(cs530x_in_sum_ch4_enum, CS530X_IN_RAMP_SUM,
			    CS530X_INOUT_SUM_MODE_SHIFT,
			    cs530x_4ch_sum_text);

static const struct snd_kcontrol_new cs530x_in_sum_4ch_controls[] = {
SOC_ENUM("IN Sum Select", cs530x_in_sum_ch4_enum),
};

static SOC_ENUM_SINGLE_DECL(cs530x_out_sum_ch4_enum, CS530X_OUT_RAMP_SUM,
			    CS530X_INOUT_SUM_MODE_SHIFT,
			    cs530x_4ch_sum_text);

static const struct snd_kcontrol_new cs530x_out_sum_4ch_controls[] = {
SOC_ENUM("OUT Sum Select", cs530x_out_sum_ch4_enum),
};

static const char * const cs530x_8ch_sum_text[] = {
	"None",
	"Groups of 2",
	"Groups of 4",
	"Groups of 8",
};

static SOC_ENUM_SINGLE_DECL(cs530x_in_sum_ch8_enum, CS530X_IN_RAMP_SUM,
			    CS530X_INOUT_SUM_MODE_SHIFT,
			    cs530x_8ch_sum_text);

static const struct snd_kcontrol_new cs530x_in_sum_8ch_controls[] = {
SOC_ENUM("IN Sum Select", cs530x_in_sum_ch8_enum),
};

static SOC_ENUM_SINGLE_DECL(cs530x_out_sum_ch8_enum, CS530X_OUT_RAMP_SUM,
			    CS530X_INOUT_SUM_MODE_SHIFT,
			    cs530x_8ch_sum_text);

static const struct snd_kcontrol_new cs530x_out_sum_8ch_controls[] = {
SOC_ENUM("OUT Sum Select", cs530x_out_sum_ch8_enum),
};

static const char * const cs530x_vol_ramp_text[] = {
	"0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB",
	"15ms/6dB", "30ms/6dB",
};

static SOC_ENUM_SINGLE_DECL(cs530x_ramp_inc_enum, CS530X_IN_RAMP_SUM,
			    CS530X_RAMP_RATE_INC_SHIFT,
			    cs530x_vol_ramp_text);

static SOC_ENUM_SINGLE_DECL(cs530x_ramp_dec_enum, CS530X_IN_RAMP_SUM,
			    CS530X_RAMP_RATE_DEC_SHIFT,
			    cs530x_vol_ramp_text);

static const struct snd_kcontrol_new cs530x_in_1_to_2_controls[] = {
SOC_SINGLE_EXT_TLV("IN1 Volume", CS530X_IN_VOL_CTRL1_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("IN2 Volume", CS530X_IN_VOL_CTRL1_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_ENUM("IN DEC Filter Select", cs530x_in_filter_enum),
SOC_ENUM("Input Ramp Up", cs530x_ramp_inc_enum),
SOC_ENUM("Input Ramp Down", cs530x_ramp_dec_enum),

SOC_SINGLE("ADC1 Invert Switch", CS530X_IN_INV, CS530X_INOUT1_INV_SHIFT, 1, 0),
SOC_SINGLE("ADC2 Invert Switch", CS530X_IN_INV, CS530X_INOUT2_INV_SHIFT, 1, 0),
};

static const struct snd_kcontrol_new cs530x_in_3_to_4_controls[] = {
SOC_SINGLE_EXT_TLV("IN3 Volume", CS530X_IN_VOL_CTRL2_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("IN4 Volume", CS530X_IN_VOL_CTRL2_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_SINGLE("ADC3 Invert Switch", CS530X_IN_INV, CS530X_INOUT3_INV_SHIFT, 1, 0),
SOC_SINGLE("ADC4 Invert Switch", CS530X_IN_INV, CS530X_INOUT4_INV_SHIFT, 1, 0),
};

static const struct snd_kcontrol_new cs530x_in_5_to_8_controls[] = {
SOC_SINGLE_EXT_TLV("IN5 Volume", CS530X_IN_VOL_CTRL3_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("IN6 Volume", CS530X_IN_VOL_CTRL3_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("IN7 Volume", CS530X_IN_VOL_CTRL4_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("IN8 Volume", CS530X_IN_VOL_CTRL4_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_SINGLE("ADC5 Invert Switch", CS530X_IN_INV, CS530X_INOUT5_INV_SHIFT, 1, 0),
SOC_SINGLE("ADC6 Invert Switch", CS530X_IN_INV, CS530X_INOUT6_INV_SHIFT, 1, 0),
SOC_SINGLE("ADC7 Invert Switch", CS530X_IN_INV, CS530X_INOUT7_INV_SHIFT, 1, 0),
SOC_SINGLE("ADC8 Invert Switch", CS530X_IN_INV, CS530X_INOUT8_INV_SHIFT, 1, 0),
};

static int cs530x_adc_event(struct snd_soc_dapm_widget *w,
			    struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;

	switch (event) {
	case SND_SOC_DAPM_PRE_PMU:
		cs530x->adc_pairs_count++;
		break;
	case SND_SOC_DAPM_POST_PMU:
		regmap_clear_bits(regmap, CS530X_IN_VOL_CTRL1_0 +
				  (w->shift * 2), CS530X_INOUT_MUTE);
		regmap_clear_bits(regmap, CS530X_IN_VOL_CTRL1_0 +
				  ((w->shift + 1) * 2), CS530X_INOUT_MUTE);

		cs530x->adc_pairs_count--;
		if (!cs530x->adc_pairs_count) {
			usleep_range(1000, 1100);
			return regmap_write(regmap, CS530X_IN_VOL_CTRL5,
					    CS530X_INOUT_VU);
		}
		break;
	case SND_SOC_DAPM_PRE_PMD:
		regmap_set_bits(regmap, CS530X_IN_VOL_CTRL1_0 +
				(w->shift * 2), CS530X_INOUT_MUTE);
		regmap_set_bits(regmap, CS530X_IN_VOL_CTRL1_0 +
				((w->shift + 1) * 2), CS530X_INOUT_MUTE);
		return regmap_write(regmap, CS530X_IN_VOL_CTRL5,
				    CS530X_INOUT_VU);
	default:
		return -EINVAL;
	}

	return 0;
}

static SOC_ENUM_SINGLE_DECL(cs530x_ramp_out_inc_enum, CS530X_OUT_RAMP_SUM,
			    CS530X_RAMP_RATE_INC_SHIFT,
			    cs530x_vol_ramp_text);

static SOC_ENUM_SINGLE_DECL(cs530x_ramp_out_dec_enum, CS530X_OUT_RAMP_SUM,
			    CS530X_RAMP_RATE_DEC_SHIFT,
			    cs530x_vol_ramp_text);

static const struct snd_kcontrol_new cs530x_out_1_to_2_controls[] = {
SOC_SINGLE_EXT_TLV("OUT1 Volume", CS530X_OUT_VOL_CTRL1_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("OUT2 Volume", CS530X_OUT_VOL_CTRL1_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_ENUM("OUT DEC Filter Select", cs530x_out_filter_enum),
SOC_ENUM("Output Ramp Up", cs530x_ramp_out_inc_enum),
SOC_ENUM("Output Ramp Down", cs530x_ramp_out_dec_enum),

SOC_SINGLE("DAC1 Invert Switch", CS530X_OUT_INV, CS530X_INOUT1_INV_SHIFT, 1, 0),
SOC_SINGLE("DAC2 Invert Switch", CS530X_OUT_INV, CS530X_INOUT2_INV_SHIFT, 1, 0),
};

static const struct snd_kcontrol_new cs530x_out_3_to_4_controls[] = {
SOC_SINGLE_EXT_TLV("OUT3 Volume", CS530X_OUT_VOL_CTRL2_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("OUT4 Volume", CS530X_OUT_VOL_CTRL2_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_SINGLE("DAC3 Invert Switch", CS530X_OUT_INV, CS530X_INOUT3_INV_SHIFT, 1, 0),
SOC_SINGLE("DAC4 Invert Switch", CS530X_OUT_INV, CS530X_INOUT4_INV_SHIFT, 1, 0),
};

static const struct snd_kcontrol_new cs530x_out_5_to_8_controls[] = {
SOC_SINGLE_EXT_TLV("OUT5 Volume", CS530X_OUT_VOL_CTRL3_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("OUT6 Volume", CS530X_OUT_VOL_CTRL3_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("OUT7 Volume", CS530X_OUT_VOL_CTRL4_0, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),
SOC_SINGLE_EXT_TLV("OUT8 Volume", CS530X_OUT_VOL_CTRL4_1, 0, 255, 1,
		    snd_soc_get_volsw, cs530x_put_volsw_vu, in_vol_tlv),

SOC_SINGLE("DAC5 Invert Switch", CS530X_OUT_INV, CS530X_INOUT5_INV_SHIFT, 1, 0),
SOC_SINGLE("DAC6 Invert Switch", CS530X_OUT_INV, CS530X_INOUT6_INV_SHIFT, 1, 0),
SOC_SINGLE("DAC7 Invert Switch", CS530X_OUT_INV, CS530X_INOUT7_INV_SHIFT, 1, 0),
SOC_SINGLE("DAC8 Invert Switch", CS530X_OUT_INV, CS530X_INOUT8_INV_SHIFT, 1, 0),
};

static int cs530x_dac_event(struct snd_soc_dapm_widget *w,
			    struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;

	switch (event) {
	case SND_SOC_DAPM_PRE_PMU:
		cs530x->dac_pairs_count++;
		break;
	case SND_SOC_DAPM_POST_PMU:
		regmap_clear_bits(regmap, CS530X_OUT_VOL_CTRL1_0 +
				 (w->shift * 2), CS530X_INOUT_MUTE);
		regmap_clear_bits(regmap, CS530X_OUT_VOL_CTRL1_0 +
				 ((w->shift + 1) * 2), CS530X_INOUT_MUTE);

		cs530x->dac_pairs_count--;
		if (!cs530x->dac_pairs_count) {
			usleep_range(1000, 1100);
			return regmap_write(regmap, CS530X_OUT_VOL_CTRL5,
					    CS530X_INOUT_VU);
		}
		break;
	case SND_SOC_DAPM_PRE_PMD:
		regmap_set_bits(regmap, CS530X_OUT_VOL_CTRL1_0 +
			       (w->shift * 2), CS530X_INOUT_MUTE);
		regmap_set_bits(regmap, CS530X_OUT_VOL_CTRL1_0 +
			       ((w->shift + 1) * 2), CS530X_INOUT_MUTE);
		return regmap_write(regmap, CS530X_OUT_VOL_CTRL5,
				    CS530X_INOUT_VU);
	default:
		return -EINVAL;
	}

	return 0;
}

static const struct snd_kcontrol_new adc12_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new adc34_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new adc56_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new adc78_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new dac12_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new dac34_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new dac56_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new dac78_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new in_hpf_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);
static const struct snd_kcontrol_new out_hpf_ctrl =
	SOC_DAPM_SINGLE_VIRT("Switch", 1);

/* General DAPM widgets for all devices */
static const struct snd_soc_dapm_widget cs530x_gen_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("Global Enable", CS530X_CHIP_ENABLE, 0, 0, NULL, 0),
};

/* ADC's Channels 1 and 2 plus generic ADC DAPM events */
static const struct snd_soc_dapm_widget cs530x_adc_ch12_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN1"),
SND_SOC_DAPM_INPUT("IN2"),
SND_SOC_DAPM_ADC_E("ADC1", NULL, CS530X_IN_ENABLES, 0, 0,
		   cs530x_adc_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_ADC("ADC2", NULL, CS530X_IN_ENABLES, 1, 0),
SND_SOC_DAPM_SWITCH("ADC12 Enable", SND_SOC_NOPM, 0, 0, &adc12_ctrl),
SND_SOC_DAPM_SWITCH("IN HPF", CS530X_IN_FILTER, CS530X_INOUT_HPF_EN_SHIFT,
		    0, &in_hpf_ctrl),
};

/* ADC's Channels 3 and 4 */
static const struct snd_soc_dapm_widget cs530x_adc_ch34_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN3"),
SND_SOC_DAPM_INPUT("IN4"),
SND_SOC_DAPM_ADC_E("ADC3", NULL, CS530X_IN_ENABLES, 2, 0,
		   cs530x_adc_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_ADC("ADC4", NULL, CS530X_IN_ENABLES, 3, 0),
SND_SOC_DAPM_SWITCH("ADC34 Enable", SND_SOC_NOPM, 0, 0, &adc34_ctrl),
};

/* ADC's Channels 5 to 8 */
static const struct snd_soc_dapm_widget cs530x_adc_ch58_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("IN5"),
SND_SOC_DAPM_INPUT("IN6"),
SND_SOC_DAPM_INPUT("IN7"),
SND_SOC_DAPM_INPUT("IN8"),
SND_SOC_DAPM_ADC_E("ADC5", NULL, CS530X_IN_ENABLES, 4, 0,
		   cs530x_adc_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_ADC("ADC6", NULL, CS530X_IN_ENABLES, 5, 0),
SND_SOC_DAPM_SWITCH("ADC56 Enable", SND_SOC_NOPM, 0, 0, &adc56_ctrl),
SND_SOC_DAPM_ADC_E("ADC7", NULL, CS530X_IN_ENABLES, 6, 0,
		   cs530x_adc_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_ADC("ADC8", NULL, CS530X_IN_ENABLES, 7, 0),
SND_SOC_DAPM_SWITCH("ADC78 Enable", SND_SOC_NOPM, 0, 0, &adc78_ctrl),
};

static const struct snd_soc_dapm_route adc_ch1_2_routes[] = {
	{ "ADC1", NULL, "Global Enable" },
	{ "ADC2", NULL, "Global Enable" },

	{ "ADC12 Enable", "Switch", "IN1" },
	{ "ADC12 Enable", "Switch", "IN2" },
	{ "ADC1", NULL, "ADC12 Enable" },
	{ "ADC2", NULL, "ADC12 Enable" },
	{ "IN HPF", "Switch", "ADC1" },
	{ "IN HPF", "Switch", "ADC2" },

	{ "AIF Capture", NULL, "IN HPF" },
	{ "AIF Capture", NULL, "ADC1" },
	{ "AIF Capture", NULL, "ADC2" },
};

static const struct snd_soc_dapm_route adc_ch3_4_routes[] = {
	{ "ADC3", NULL, "Global Enable" },
	{ "ADC4", NULL, "Global Enable" },

	{ "ADC34 Enable", "Switch", "IN3" },
	{ "ADC34 Enable", "Switch", "IN4" },
	{ "ADC3", NULL, "ADC34 Enable" },
	{ "ADC4", NULL, "ADC34 Enable" },
	{ "IN HPF", "Switch", "ADC3" },
	{ "IN HPF", "Switch", "ADC4" },

	{ "AIF Capture", NULL, "ADC3" },
	{ "AIF Capture", NULL, "ADC4" },
};

static const struct snd_soc_dapm_route adc_ch5_8_routes[] = {
	{ "ADC5", NULL, "Global Enable" },
	{ "ADC6", NULL, "Global Enable" },
	{ "ADC7", NULL, "Global Enable" },
	{ "ADC8", NULL, "Global Enable" },

	{ "ADC56 Enable", "Switch", "IN5" },
	{ "ADC56 Enable", "Switch", "IN6" },
	{ "ADC5", NULL, "ADC56 Enable" },
	{ "ADC6", NULL, "ADC56 Enable" },
	{ "IN HPF", "Switch", "ADC5" },
	{ "IN HPF", "Switch", "ADC6" },

	{ "AIF Capture", NULL, "ADC5" },
	{ "AIF Capture", NULL, "ADC6" },

	{ "ADC78 Enable", "Switch", "IN7" },
	{ "ADC78 Enable", "Switch", "IN8" },
	{ "ADC7", NULL, "ADC78 Enable" },
	{ "ADC8", NULL, "ADC78 Enable" },
	{ "IN HPF", "Switch", "ADC7" },
	{ "IN HPF", "Switch", "ADC8" },

	{ "AIF Capture", NULL, "ADC7" },
	{ "AIF Capture", NULL, "ADC8" },
};

static void cs530x_add_12_adc_widgets(struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);

	snd_soc_add_component_controls(component,
				       cs530x_in_1_to_2_controls,
				       ARRAY_SIZE(cs530x_in_1_to_2_controls));

	snd_soc_dapm_new_controls(dapm, cs530x_adc_ch12_dapm_widgets,
				  ARRAY_SIZE(cs530x_adc_ch12_dapm_widgets));

	snd_soc_dapm_add_routes(dapm, adc_ch1_2_routes,
				ARRAY_SIZE(adc_ch1_2_routes));
}

static void cs530x_add_34_adc_widgets(struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);

	snd_soc_add_component_controls(component,
				       cs530x_in_3_to_4_controls,
				       ARRAY_SIZE(cs530x_in_3_to_4_controls));

	snd_soc_dapm_new_controls(dapm, cs530x_adc_ch34_dapm_widgets,
				  ARRAY_SIZE(cs530x_adc_ch34_dapm_widgets));

	snd_soc_dapm_add_routes(dapm, adc_ch3_4_routes,
				ARRAY_SIZE(adc_ch3_4_routes));
}

/* DAC's Channels 1 and 2 plus generic DAC DAPM events */
static const struct snd_soc_dapm_widget cs530x_dac_ch12_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("OUT1"),
SND_SOC_DAPM_OUTPUT("OUT2"),
SND_SOC_DAPM_DAC_E("DAC1", NULL, CS530X_OUT_ENABLES, 0, 0,
		   cs530x_dac_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_DAC("DAC2", NULL, CS530X_OUT_ENABLES, 1, 0),
SND_SOC_DAPM_SWITCH("DAC12 Enable", SND_SOC_NOPM, 0, 0, &dac12_ctrl),
SND_SOC_DAPM_SWITCH("OUT HPF", CS530X_OUT_FILTER, CS530X_INOUT_HPF_EN_SHIFT,
		    0, &out_hpf_ctrl),
};

/* DAC's Channels 3 and 4 */
static const struct snd_soc_dapm_widget cs530x_dac_ch34_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("OUT3"),
SND_SOC_DAPM_OUTPUT("OUT4"),
SND_SOC_DAPM_DAC_E("DAC3", NULL, CS530X_OUT_ENABLES, 2, 0,
		   cs530x_dac_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_DAC("DAC4", NULL, CS530X_OUT_ENABLES, 3, 0),
SND_SOC_DAPM_SWITCH("DAC34 Enable", SND_SOC_NOPM, 0, 0, &dac34_ctrl),
};

/* DAC's Channels 5 to 8 */
static const struct snd_soc_dapm_widget cs530x_dac_ch58_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("OUT5"),
SND_SOC_DAPM_OUTPUT("OUT6"),
SND_SOC_DAPM_OUTPUT("OUT7"),
SND_SOC_DAPM_OUTPUT("OUT8"),
SND_SOC_DAPM_DAC_E("DAC5", NULL, CS530X_OUT_ENABLES, 4, 0,
		   cs530x_dac_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_DAC("DAC6", NULL, CS530X_OUT_ENABLES, 5, 0),
SND_SOC_DAPM_SWITCH("DAC56 Enable", SND_SOC_NOPM, 0, 0, &dac56_ctrl),
SND_SOC_DAPM_DAC_E("DAC7", NULL, CS530X_OUT_ENABLES, 6, 0,
		   cs530x_dac_event,
		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU |
		   SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_DAC("DAC8", NULL, CS530X_OUT_ENABLES, 7, 0),
SND_SOC_DAPM_SWITCH("DAC78 Enable", SND_SOC_NOPM, 0, 0, &dac78_ctrl),
};

static const struct snd_soc_dapm_route dac_ch1_2_routes[] = {
	{ "DAC1", NULL, "Global Enable" },
	{ "DAC2", NULL, "Global Enable" },

	{ "DAC12 Enable", "Switch", "OUT1" },
	{ "DAC12 Enable", "Switch", "OUT2" },
	{ "DAC1", NULL, "DAC12 Enable" },
	{ "DAC2", NULL, "DAC12 Enable" },
	{ "OUT HPF", "Switch", "DAC1" },
	{ "OUT HPF", "Switch", "DAC2" },

	{ "OUT HPF", NULL, "AIF Playback" },
	{ "DAC1", NULL, "AIF Playback" },
	{ "DAC2", NULL, "AIF Playback" },

	{ "OUT1", NULL, "DAC1" },
	{ "OUT2", NULL, "DAC2" },
};

static const struct snd_soc_dapm_route dac_ch3_4_routes[] = {
	{ "DAC3", NULL, "Global Enable" },
	{ "DAC4", NULL, "Global Enable" },

	{ "DAC34 Enable", "Switch", "OUT3" },
	{ "DAC34 Enable", "Switch", "OUT4" },
	{ "DAC3", NULL, "DAC34 Enable" },
	{ "DAC4", NULL, "DAC34 Enable" },
	{ "OUT HPF", "Switch", "DAC3" },
	{ "OUT HPF", "Switch", "DAC4" },

	{ "DAC3", NULL, "AIF Playback" },
	{ "DAC4", NULL, "AIF Playback" },

	{ "OUT3", NULL, "DAC3" },
	{ "OUT4", NULL, "DAC4" },
};

static const struct snd_soc_dapm_route dac_ch5_8_routes[] = {
	{ "DAC5", NULL, "Global Enable" },
	{ "DAC6", NULL, "Global Enable" },

	{ "DAC56 Enable", "Switch", "OUT5" },
	{ "DAC56 Enable", "Switch", "OUT6" },
	{ "DAC5", NULL, "DAC56 Enable" },
	{ "DAC6", NULL, "DAC56 Enable" },
	{ "OUT HPF", "Switch", "DAC5" },
	{ "OUT HPF", "Switch", "DAC6" },

	{ "DAC5", NULL, "AIF Playback" },
	{ "DAC6", NULL, "AIF Playback" },

	{ "OUT5", NULL, "DAC5" },
	{ "OUT6", NULL, "DAC6" },

	{ "DAC7", NULL, "Global Enable" },
	{ "DAC8", NULL, "Global Enable" },

	{ "DAC78 Enable", "Switch", "OUT7" },
	{ "DAC78 Enable", "Switch", "OUT8" },
	{ "DAC7", NULL, "DAC78 Enable" },
	{ "DAC8", NULL, "DAC78 Enable" },
	{ "OUT HPF", "Switch", "DAC7" },
	{ "OUT HPF", "Switch", "DAC8" },

	{ "DAC7", NULL, "AIF Playback" },
	{ "DAC8", NULL, "AIF Playback" },

	{ "OUT7", NULL, "DAC7" },
	{ "OUT8", NULL, "DAC8" },
};

static void cs530x_add_12_dac_widgets(struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);

	snd_soc_add_component_controls(component,
				       cs530x_out_1_to_2_controls,
				       ARRAY_SIZE(cs530x_out_1_to_2_controls));

	snd_soc_dapm_new_controls(dapm, cs530x_dac_ch12_dapm_widgets,
				  ARRAY_SIZE(cs530x_dac_ch12_dapm_widgets));

	snd_soc_dapm_add_routes(dapm, dac_ch1_2_routes,
				ARRAY_SIZE(dac_ch1_2_routes));
}

static void cs530x_add_34_dac_widgets(struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);

	snd_soc_add_component_controls(component,
				       cs530x_out_3_to_4_controls,
				       ARRAY_SIZE(cs530x_out_3_to_4_controls));

	snd_soc_dapm_new_controls(dapm, cs530x_dac_ch34_dapm_widgets,
				  ARRAY_SIZE(cs530x_dac_ch34_dapm_widgets));

	snd_soc_dapm_add_routes(dapm, dac_ch3_4_routes,
				ARRAY_SIZE(dac_ch3_4_routes));
}

static int cs530x_set_bclk(struct snd_soc_component *component, const int freq)
{
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;
	unsigned int bclk_val;

	switch (freq) {
	case 2822400:
	case 3072000:
		bclk_val = CS530X_BCLK_2P822_3P072;
		break;
	case 5644800:
	case 6144000:
		bclk_val = CS530X_BCLK_5P6448_6P144;
		break;
	case 11289600:
	case 12288000:
		bclk_val = CS530X_BCLK_11P2896_12P288;
		break;
	case 22579200:
	case 24576000:
		bclk_val = CS530X_BCLK_24P5792_24P576;
		break;
	default:
		dev_err(component->dev, "Invalid BCLK frequency %d\n", freq);
		return -EINVAL;
	}

	dev_dbg(component->dev, "BCLK frequency is %d\n", freq);

	return regmap_update_bits(regmap, CS530X_ASP_CFG,
				  CS530X_ASP_BCLK_FREQ_MASK, bclk_val);
}

static int cs530x_set_pll_refclk(struct snd_soc_component *component,
				 const unsigned int freq)
{
	struct cs530x_priv *priv = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = priv->regmap;
	unsigned int refclk;

	switch (freq) {
	case 2822400:
	case 3072000:
		refclk = CS530X_REFCLK_2P822_3P072;
		break;
	case 5644800:
	case 6144000:
		refclk = CS530X_REFCLK_5P6448_6P144;
		break;
	case 11289600:
	case 12288000:
		refclk = CS530X_REFCLK_11P2896_12P288;
		break;
	case 22579200:
	case 24576000:
		refclk = CS530X_REFCLK_24P5792_24P576;
		break;
	default:
		dev_err(component->dev, "Invalid PLL refclk %d\n", freq);
		return -EINVAL;
	}

	return regmap_update_bits(regmap, CS530X_CLK_CFG_0,
				  CS530X_PLL_REFCLK_FREQ_MASK, refclk);
}

static int cs530x_hw_params(struct snd_pcm_substream *substream,
			    struct snd_pcm_hw_params *params,
			    struct snd_soc_dai *dai)
{
	struct snd_soc_component *component = dai->component;
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;
	int ret = 0, fs = params_rate(params), bclk;
	unsigned int fs_val;

	switch (fs) {
	case 32000:
		fs_val = CS530X_FS_32K;
		break;
	case 44100:
	case 48000:
		fs_val = CS530X_FS_44P1K_48K;
		break;
	case 88200:
	case 96000:
		fs_val = CS530X_FS_88P2K_96K;
		break;
	case 176400:
	case 192000:
		fs_val = CS530X_FS_176P4K_192K;
		break;
	case 356800:
	case 384000:
		fs_val = CS530X_FS_356P8K_384K;
		break;
	case 705600:
	case 768000:
		fs_val = CS530X_FS_705P6K_768K;
		break;
	default:
		dev_err(component->dev, "Invalid sample rate %d\n", fs);
		return -EINVAL;
	}

	regmap_update_bits(regmap, CS530X_CLK_CFG_1,
			   CS530X_SAMPLE_RATE_MASK, fs_val);


	if (regmap_test_bits(regmap, CS530X_SIGNAL_PATH_CFG,
			     CS530X_TDM_EN_MASK)) {
		dev_dbg(component->dev, "Configuring for %d %d bit TDM slots\n",
			cs530x->tdm_slots, cs530x->tdm_width);
		bclk = snd_soc_tdm_params_to_bclk(params,
						  cs530x->tdm_width,
						  cs530x->tdm_slots,
						  1);
	} else {
		bclk = snd_soc_params_to_bclk(params);
	}

	if (!regmap_test_bits(regmap, CS530X_CLK_CFG_0,
			      CS530X_PLL_REFCLK_SRC_MASK)) {
		ret = cs530x_set_pll_refclk(component, bclk);
		if (ret)
			return ret;
	}

	return cs530x_set_bclk(component, bclk);
}

static int cs530x_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
	struct snd_soc_component *component = dai->component;
	struct cs530x_priv *priv = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = priv->regmap;
	unsigned int asp_fmt, asp_cfg = 0;

	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBC_CFC:
		break;
	case SND_SOC_DAIFMT_CBP_CFP:
		asp_cfg = CS530X_ASP_PRIMARY;
		break;
	default:
		return -EINVAL;
	}

	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_DSP_A:
		asp_fmt = CS530X_ASP_FMT_DSP_A;
		break;
	case SND_SOC_DAIFMT_I2S:
		asp_fmt = CS530X_ASP_FMT_I2S;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		asp_fmt = CS530X_ASP_FMT_LJ;
		break;
	default:
		return -EINVAL;
	}

	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
	case SND_SOC_DAIFMT_NB_NF:
		break;
	case SND_SOC_DAIFMT_IB_IF:
		asp_cfg |= CS530X_ASP_BCLK_INV;
		break;
	default:
		return -EINVAL;
	}

	regmap_update_bits(regmap, CS530X_ASP_CFG,
			   CS530X_ASP_PRIMARY | CS530X_ASP_BCLK_INV,
			   asp_cfg);

	return regmap_update_bits(regmap, CS530X_SIGNAL_PATH_CFG,
				  CS530X_ASP_FMT_MASK, asp_fmt);
}

static bool cs530x_check_mclk_freq(struct snd_soc_component *component,
				   const unsigned int freq)
{
	switch (freq) {
	case 24576000:
	case 22579200:
	case 12288000:
	case 11289600:
		return true;
	default:
		dev_err(component->dev, "Invalid MCLK %d\n", freq);
		return false;
	}
}

static int cs530x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
			       unsigned int rx_mask, int slots, int slot_width)
{
	struct snd_soc_component *component = dai->component;
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;
	unsigned int val;

	switch (tx_mask) {
	case CS530X_0_1_TDM_SLOT_MASK:
	case CS530X_0_3_TDM_SLOT_MASK:
	case CS530X_0_7_TDM_SLOT_MASK:
		val = CS530X_0_7_TDM_SLOT_VAL;
		break;
	case CS530X_2_3_TDM_SLOT_MASK:
		val = CS530X_2_3_TDM_SLOT_VAL;
		break;
	case CS530X_4_5_TDM_SLOT_MASK:
	case CS530X_4_7_TDM_SLOT_MASK:
		val = CS530X_4_7_TDM_SLOT_VAL;
		break;
	case CS530X_6_7_TDM_SLOT_MASK:
		val = CS530X_6_7_TDM_SLOT_VAL;
		break;
	case CS530X_8_9_TDM_SLOT_MASK:
	case CS530X_8_11_TDM_SLOT_MASK:
	case CS530X_8_15_TDM_SLOT_MASK:
		val = CS530X_8_15_TDM_SLOT_VAL;
		break;
	case CS530X_10_11_TDM_SLOT_MASK:
		val = CS530X_10_11_TDM_SLOT_VAL;
		break;
	case CS530X_12_13_TDM_SLOT_MASK:
	case CS530X_12_15_TDM_SLOT_MASK:
		val = CS530X_12_15_TDM_SLOT_VAL;
		break;
	case CS530X_14_15_TDM_SLOT_MASK:
		val = CS530X_14_15_TDM_SLOT_VAL;
		break;
	default:
		dev_err(component->dev, "Invalid TX slot(s) 0x%x\n", tx_mask);
		return -EINVAL;
	}

	cs530x->tdm_width = slot_width;
	cs530x->tdm_slots = slots;

	return regmap_update_bits(regmap, CS530X_SIGNAL_PATH_CFG,
				  CS530X_ASP_TDM_SLOT_MASK,
				  val << CS530X_ASP_TDM_SLOT_SHIFT);
}

static const struct snd_soc_dai_ops cs530x_dai_ops = {
	.set_fmt = cs530x_set_fmt,
	.hw_params = cs530x_hw_params,
	.set_tdm_slot = cs530x_set_tdm_slot,
};

static const struct snd_soc_dai_driver cs530x_dai = {
	.name = "cs530x-dai",
	.capture = {
		.stream_name = "AIF Capture",
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = SNDRV_PCM_FMTBIT_S32_LE,
	},
	.playback = {
		.stream_name = "AIF Playback",
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = SNDRV_PCM_FMTBIT_S32_LE,
	},
	.ops = &cs530x_dai_ops,
	.symmetric_rate = 1,
	.symmetric_sample_bits = 1,
};

static int cs530x_set_pll(struct snd_soc_component *component, int pll_id,
			  int source, unsigned int freq_in,
			  unsigned int freq_out)
{
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;
	unsigned int sysclk_src;
	int ret;

	regmap_read(regmap, CS530X_CLK_CFG_0, &sysclk_src);

	/* Check if the source is the PLL  */
	if ((sysclk_src & CS530X_SYSCLK_SRC_MASK) == 0)
		return 0;

	switch (source) {
	case CS530X_PLL_SRC_MCLK:
		if (!cs530x_check_mclk_freq(component, freq_in))
			return -EINVAL;

		ret = cs530x_set_pll_refclk(component, freq_in);
		if (ret)
			return ret;

		break;
	case CS530X_PLL_SRC_BCLK:
		break;
	default:
		dev_err(component->dev, "Invalid PLL source %d\n", source);
		return -EINVAL;
	}

	return regmap_update_bits(regmap, CS530X_CLK_CFG_0,
				  CS530X_PLL_REFCLK_SRC_MASK, source);
}

static int cs530x_component_probe(struct snd_soc_component *component)
{
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
	int num_widgets;

	snd_soc_dapm_new_controls(dapm, cs530x_gen_dapm_widgets,
				  ARRAY_SIZE(cs530x_gen_dapm_widgets));

	switch (cs530x->devtype) {
	case CS4282:
		cs530x_add_12_adc_widgets(component);
		cs530x_add_12_dac_widgets(component);
		break;
	case CS4302:
		cs530x_add_12_dac_widgets(component);
		break;
	case CS4304:
		cs530x_add_12_dac_widgets(component);
		cs530x_add_34_dac_widgets(component);

		num_widgets = ARRAY_SIZE(cs530x_out_sum_4ch_controls);
		snd_soc_add_component_controls(component,
					       cs530x_out_sum_4ch_controls,
					       num_widgets);
		break;
	case CS4308:
		cs530x_add_12_dac_widgets(component);
		cs530x_add_34_dac_widgets(component);

		num_widgets = ARRAY_SIZE(cs530x_out_5_to_8_controls);
		snd_soc_add_component_controls(component,
					       cs530x_out_5_to_8_controls,
					       num_widgets);

		num_widgets = ARRAY_SIZE(cs530x_out_sum_8ch_controls);
		snd_soc_add_component_controls(component,
					       cs530x_out_sum_8ch_controls,
					       num_widgets);

		num_widgets = ARRAY_SIZE(cs530x_dac_ch58_dapm_widgets);
		snd_soc_dapm_new_controls(dapm, cs530x_dac_ch58_dapm_widgets,
					  num_widgets);

		snd_soc_dapm_add_routes(dapm, dac_ch5_8_routes,
					ARRAY_SIZE(dac_ch5_8_routes));
		break;
	case CS5302:
		cs530x_add_12_adc_widgets(component);
		break;
	case CS5304:
		cs530x_add_12_adc_widgets(component);
		cs530x_add_34_adc_widgets(component);

		num_widgets = ARRAY_SIZE(cs530x_in_sum_4ch_controls);
		snd_soc_add_component_controls(component,
					       cs530x_in_sum_4ch_controls,
					       num_widgets);
		break;
	case CS5308:
		cs530x_add_12_adc_widgets(component);
		cs530x_add_34_adc_widgets(component);

		num_widgets = ARRAY_SIZE(cs530x_in_5_to_8_controls);
		snd_soc_add_component_controls(component,
					       cs530x_in_5_to_8_controls,
					       num_widgets);

		num_widgets = ARRAY_SIZE(cs530x_in_sum_8ch_controls);
		snd_soc_add_component_controls(component,
					       cs530x_in_sum_8ch_controls,
					       num_widgets);

		num_widgets = ARRAY_SIZE(cs530x_adc_ch58_dapm_widgets);
		snd_soc_dapm_new_controls(dapm, cs530x_adc_ch58_dapm_widgets,
					  num_widgets);

		snd_soc_dapm_add_routes(dapm, adc_ch5_8_routes,
					ARRAY_SIZE(adc_ch5_8_routes));
		break;
	default:
		dev_err(component->dev, "Invalid device type %d\n",
			cs530x->devtype);
		return -EINVAL;
	}

	return 0;
}

static int cs530x_set_sysclk(struct snd_soc_component *component, int clk_id,
			     int source, unsigned int freq, int dir)
{
	struct cs530x_priv *cs530x = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cs530x->regmap;

	switch (source) {
	case CS530X_SYSCLK_SRC_MCLK:
		switch (freq) {
		case CS530X_SYSCLK_REF_45_1MHZ:
		case CS530X_SYSCLK_REF_49_1MHZ:
			break;
		default:
			dev_err(component->dev, "Invalid MCLK source rate %d\n", freq);
			return -EINVAL;
		}
		break;
	case CS530X_SYSCLK_SRC_PLL:
		break;
	default:
		dev_err(component->dev, "Invalid sysclk source: %d\n", source);
		return -EINVAL;
	}

	return regmap_update_bits(regmap, CS530X_CLK_CFG_0,
				  CS530X_SYSCLK_SRC_MASK,
				  source << CS530X_SYSCLK_SRC_SHIFT);
}

static const struct snd_soc_component_driver soc_component_dev_cs530x = {
	.probe			= cs530x_component_probe,
	.set_sysclk		= cs530x_set_sysclk,
	.set_pll		= cs530x_set_pll,
	.endianness		= 1,
};

const struct regmap_config cs530x_regmap_i2c = {
	.reg_bits = 16,
	.val_bits = 16,

	.max_register = CS530X_MAX_REGISTER,
	.readable_reg = cs530x_readable_register,
	.writeable_reg = cs530x_writeable_register,

	.cache_type = REGCACHE_MAPLE,
	.reg_defaults = cs530x_reg_defaults,
	.num_reg_defaults = ARRAY_SIZE(cs530x_reg_defaults),
};
EXPORT_SYMBOL_NS_GPL(cs530x_regmap_i2c, "SND_SOC_CS530X");

const struct regmap_config cs530x_regmap_spi = {
	.reg_bits = 16,
	.pad_bits = 16,
	.val_bits = 16,

	.reg_stride = 2,

	.reg_format_endian = REGMAP_ENDIAN_BIG,
	.val_format_endian = REGMAP_ENDIAN_BIG,

	.max_register = CS530X_MAX_REGISTER,
	.writeable_reg = cs530x_writeable_register,
	.readable_reg = cs530x_readable_register,

	.cache_type = REGCACHE_MAPLE,
	.reg_defaults = cs530x_reg_defaults,
	.num_reg_defaults = ARRAY_SIZE(cs530x_reg_defaults),
};
EXPORT_SYMBOL_NS_GPL(cs530x_regmap_spi, "SND_SOC_CS530X");

static int cs530x_check_device_id(struct cs530x_priv *cs530x)
{
	struct device *dev = cs530x->dev;
	unsigned int dev_id, rev;
	int ret;

	ret = regmap_read(cs530x->regmap, CS530X_DEVID, &dev_id);
	if (ret)
		return dev_err_probe(dev, ret, "Can't read device ID\n");

	ret = regmap_read(cs530x->regmap, CS530X_REVID, &rev);
	if (ret)
		return dev_err_probe(dev, ret, "Can't read REV ID\n");

	switch (dev_id) {
	case CS530X_2CH_CODEC_DEV_ID:
		cs530x->num_dacs = 2;
		cs530x->num_adcs = 2;
		break;
	case CS530X_2CH_DAC_DEV_ID:
		cs530x->num_dacs = 2;
		break;
	case CS530X_4CH_DAC_DEV_ID:
		cs530x->num_dacs = 4;
		break;
	case CS530X_8CH_DAC_DEV_ID:
		cs530x->num_dacs = 8;
		break;
	case CS530X_2CH_ADC_DEV_ID:
		cs530x->num_adcs = 2;
		break;
	case CS530X_4CH_ADC_DEV_ID:
		cs530x->num_adcs = 4;
		break;
	case CS530X_8CH_ADC_DEV_ID:
		cs530x->num_adcs = 8;
		break;
	default:
		return dev_err_probe(dev, -EINVAL, "Invalid device ID 0x%x\n",
				     dev_id);
	}

	if (cs530x->devtype != dev_id) {
		dev_err(dev, "Read device ID 0x%x is not the expected devtype 0x%x\n",
			dev_id, cs530x->devtype);
		return -EINVAL;
	}

	dev_dbg(dev, "Device ID 0x%x Rev ID 0x%x (%d in %d out)\n", dev_id, rev,
		cs530x->num_adcs, cs530x->num_dacs);

	return 0;
}

static int cs530x_parse_device_properties(struct cs530x_priv *cs530x)
{
	struct regmap *regmap = cs530x->regmap;
	struct device *dev = cs530x->dev;
	unsigned int val = 0;

	switch (cs530x->num_adcs) {
	case 8:
		if (device_property_read_bool(dev, "cirrus,in-hiz-pin78"))
			val = CS530X_IN78_HIZ;

		if (device_property_read_bool(dev, "cirrus,in-hiz-pin56"))
			val |= CS530X_IN56_HIZ;

		fallthrough;
	case 4:
		if (device_property_read_bool(dev, "cirrus,in-hiz-pin34"))
			val |= CS530X_IN34_HIZ;

		fallthrough;
	case 2:
		if (device_property_read_bool(dev, "cirrus,in-hiz-pin12"))
			val |= CS530X_IN12_HIZ;

		return regmap_set_bits(regmap, CS530X_IN_HIZ, val);
	case 0:
		/* No ADCs */
		return 0;
	default:
		return dev_err_probe(dev, -EINVAL,
				     "Invalid number of adcs %d\n",
				     cs530x->num_adcs);
	}
}

int cs530x_probe(struct cs530x_priv *cs530x)
{
	struct device *dev = cs530x->dev;
	int ret, i;

	cs530x->dev_dai = devm_kmemdup(dev, &cs530x_dai,
				       sizeof(*(cs530x->dev_dai)),
				       GFP_KERNEL);
	if (!cs530x->dev_dai)
		return -ENOMEM;

	for (i = 0; i < ARRAY_SIZE(cs530x->supplies); i++)
		cs530x->supplies[i].supply = cs530x_supply_names[i];

	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs530x->supplies),
				      cs530x->supplies);
	if (ret != 0)
		return dev_err_probe(dev, ret, "Failed to request supplies");

	ret = regulator_bulk_enable(ARRAY_SIZE(cs530x->supplies),
				    cs530x->supplies);
	if (ret != 0)
		return dev_err_probe(dev, ret, "Failed to enable supplies");

	cs530x->reset_gpio = devm_gpiod_get_optional(dev, "reset",
						     GPIOD_OUT_HIGH);
	if (IS_ERR(cs530x->reset_gpio)) {
		ret = dev_err_probe(dev, PTR_ERR(cs530x->reset_gpio),
				    "Reset gpio not available\n");
		goto err_regulator;
	}

	if (cs530x->reset_gpio) {
		usleep_range(2000, 2100);
		gpiod_set_value_cansleep(cs530x->reset_gpio, 0);
	}

	usleep_range(5000, 5100);
	ret = cs530x_check_device_id(cs530x);
	if (ret)
		goto err_reset;

	if (!cs530x->reset_gpio) {
		ret = regmap_write(cs530x->regmap, CS530X_SW_RESET,
				   CS530X_SW_RST_VAL);
		if (ret) {
			dev_err_probe(dev, ret, "Soft Reset Failed\n");
			goto err_reset;
		}
	}

	ret = cs530x_parse_device_properties(cs530x);
	if (ret)
		goto err_reset;

	if (cs530x->num_adcs) {
		cs530x->dev_dai->capture.channels_min = 2;
		cs530x->dev_dai->capture.channels_max = cs530x->num_adcs;
	}

	if (cs530x->num_dacs) {
		cs530x->dev_dai->playback.channels_min = 2;
		cs530x->dev_dai->playback.channels_max = cs530x->num_dacs;
	}

	ret = devm_snd_soc_register_component(dev,
					      &soc_component_dev_cs530x,
					      cs530x->dev_dai, 1);
	if (ret) {
		dev_err_probe(dev, ret, "Can't register cs530x component\n");
		goto err_reset;
	}

	return 0;

err_reset:
	gpiod_set_value_cansleep(cs530x->reset_gpio, 1);

err_regulator:
	regulator_bulk_disable(ARRAY_SIZE(cs530x->supplies),
			       cs530x->supplies);

	return ret;
}
EXPORT_SYMBOL_NS_GPL(cs530x_probe, "SND_SOC_CS530X");

MODULE_DESCRIPTION("CS530X CODEC Driver");
MODULE_AUTHOR("Paul Handrigan <paulha@opensource.cirrus.com>");
MODULE_LICENSE("GPL");