root/sound/soc/codecs/cs43130.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * cs43130.c  --  CS43130 ALSA Soc Audio driver
 *
 * Copyright 2017 Cirrus Logic, Inc.
 *
 * Authors: Li Xu <li.xu@cirrus.com>
 */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <sound/jack.h>

#include "cs43130.h"
#include "cirrus_legacy.h"

static const struct reg_default cs43130_reg_defaults[] = {
        {CS43130_SYS_CLK_CTL_1, 0x06},
        {CS43130_SP_SRATE, 0x01},
        {CS43130_SP_BITSIZE, 0x05},
        {CS43130_PAD_INT_CFG, 0x03},
        {CS43130_PWDN_CTL, 0xFE},
        {CS43130_CRYSTAL_SET, 0x04},
        {CS43130_PLL_SET_1, 0x00},
        {CS43130_PLL_SET_2, 0x00},
        {CS43130_PLL_SET_3, 0x00},
        {CS43130_PLL_SET_4, 0x00},
        {CS43130_PLL_SET_5, 0x40},
        {CS43130_PLL_SET_6, 0x10},
        {CS43130_PLL_SET_7, 0x80},
        {CS43130_PLL_SET_8, 0x03},
        {CS43130_PLL_SET_9, 0x02},
        {CS43130_PLL_SET_10, 0x02},
        {CS43130_CLKOUT_CTL, 0x00},
        {CS43130_ASP_NUM_1, 0x01},
        {CS43130_ASP_NUM_2, 0x00},
        {CS43130_ASP_DEN_1, 0x08},
        {CS43130_ASP_DEN_2, 0x00},
        {CS43130_ASP_LRCK_HI_TIME_1, 0x1F},
        {CS43130_ASP_LRCK_HI_TIME_2, 0x00},
        {CS43130_ASP_LRCK_PERIOD_1, 0x3F},
        {CS43130_ASP_LRCK_PERIOD_2, 0x00},
        {CS43130_ASP_CLOCK_CONF, 0x0C},
        {CS43130_ASP_FRAME_CONF, 0x0A},
        {CS43130_XSP_NUM_1, 0x01},
        {CS43130_XSP_NUM_2, 0x00},
        {CS43130_XSP_DEN_1, 0x02},
        {CS43130_XSP_DEN_2, 0x00},
        {CS43130_XSP_LRCK_HI_TIME_1, 0x1F},
        {CS43130_XSP_LRCK_HI_TIME_2, 0x00},
        {CS43130_XSP_LRCK_PERIOD_1, 0x3F},
        {CS43130_XSP_LRCK_PERIOD_2, 0x00},
        {CS43130_XSP_CLOCK_CONF, 0x0C},
        {CS43130_XSP_FRAME_CONF, 0x0A},
        {CS43130_ASP_CH_1_LOC, 0x00},
        {CS43130_ASP_CH_2_LOC, 0x00},
        {CS43130_ASP_CH_1_SZ_EN, 0x06},
        {CS43130_ASP_CH_2_SZ_EN, 0x0E},
        {CS43130_XSP_CH_1_LOC, 0x00},
        {CS43130_XSP_CH_2_LOC, 0x00},
        {CS43130_XSP_CH_1_SZ_EN, 0x06},
        {CS43130_XSP_CH_2_SZ_EN, 0x0E},
        {CS43130_DSD_VOL_B, 0x78},
        {CS43130_DSD_VOL_A, 0x78},
        {CS43130_DSD_PATH_CTL_1, 0xA8},
        {CS43130_DSD_INT_CFG, 0x00},
        {CS43130_DSD_PATH_CTL_2, 0x02},
        {CS43130_DSD_PCM_MIX_CTL, 0x00},
        {CS43130_DSD_PATH_CTL_3, 0x40},
        {CS43130_HP_OUT_CTL_1, 0x30},
        {CS43130_PCM_FILT_OPT, 0x02},
        {CS43130_PCM_VOL_B, 0x78},
        {CS43130_PCM_VOL_A, 0x78},
        {CS43130_PCM_PATH_CTL_1, 0xA8},
        {CS43130_PCM_PATH_CTL_2, 0x00},
        {CS43130_CLASS_H_CTL, 0x1E},
        {CS43130_HP_DETECT, 0x04},
        {CS43130_HP_LOAD_1, 0x00},
        {CS43130_HP_MEAS_LOAD_1, 0x00},
        {CS43130_HP_MEAS_LOAD_2, 0x00},
        {CS43130_INT_MASK_1, 0xFF},
        {CS43130_INT_MASK_2, 0xFF},
        {CS43130_INT_MASK_3, 0xFF},
        {CS43130_INT_MASK_4, 0xFF},
        {CS43130_INT_MASK_5, 0xFF},
};

static bool cs43130_volatile_register(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
        case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2:
        case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2:
                return true;
        default:
                return false;
        }
}

static bool cs43130_readable_register(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1:
        case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG:
        case CS43130_PWDN_CTL:
        case CS43130_CRYSTAL_SET:
        case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5:
        case CS43130_PLL_SET_6:
        case CS43130_PLL_SET_7:
        case CS43130_PLL_SET_8:
        case CS43130_PLL_SET_9:
        case CS43130_PLL_SET_10:
        case CS43130_CLKOUT_CTL:
        case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF:
        case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF:
        case CS43130_ASP_CH_1_LOC:
        case CS43130_ASP_CH_2_LOC:
        case CS43130_ASP_CH_1_SZ_EN:
        case CS43130_ASP_CH_2_SZ_EN:
        case CS43130_XSP_CH_1_LOC:
        case CS43130_XSP_CH_2_LOC:
        case CS43130_XSP_CH_1_SZ_EN:
        case CS43130_XSP_CH_2_SZ_EN:
        case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3:
        case CS43130_HP_OUT_CTL_1:
        case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2:
        case CS43130_CLASS_H_CTL:
        case CS43130_HP_DETECT:
        case CS43130_HP_STATUS:
        case CS43130_HP_LOAD_1:
        case CS43130_HP_MEAS_LOAD_1:
        case CS43130_HP_MEAS_LOAD_2:
        case CS43130_HP_DC_STAT_1:
        case CS43130_HP_DC_STAT_2:
        case CS43130_HP_AC_STAT_1:
        case CS43130_HP_AC_STAT_2:
        case CS43130_HP_LOAD_STAT:
        case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
        case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5:
                return true;
        default:
                return false;
        }
}

static bool cs43130_precious_register(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
                return true;
        default:
                return false;
        }
}

struct cs43130_pll_params {
        unsigned int pll_in;
        u8 sclk_prediv;
        u8 pll_div_int;
        u32 pll_div_frac;
        u8 pll_mode;
        u8 pll_divout;
        unsigned int pll_out;
        u8 pll_cal_ratio;
};

static const struct cs43130_pll_params pll_ratio_table[] = {
        {9600000, 0x02, 0x49, 0x800000, 0x00, 0x08, 22579200, 151},
        {9600000, 0x02, 0x50, 0x000000, 0x00, 0x08, 24576000, 164},

        {11289600, 0x02, 0X40, 0, 0x01, 0x08, 22579200, 128},
        {11289600, 0x02, 0x44, 0x06F700, 0x0, 0x08, 24576000, 139},

        {12000000, 0x02, 0x49, 0x800000, 0x00, 0x0A, 22579200, 120},
        {12000000, 0x02, 0x40, 0x000000, 0x00, 0x08, 24576000, 131},

        {12288000, 0x02, 0x49, 0x800000, 0x01, 0x0A, 22579200, 118},
        {12288000, 0x02, 0x40, 0x000000, 0x01, 0x08, 24576000, 128},

        {13000000, 0x02, 0x45, 0x797680, 0x01, 0x0A, 22579200, 111},
        {13000000, 0x02, 0x3C, 0x7EA940, 0x01, 0x08, 24576000, 121},

        {19200000, 0x03, 0x49, 0x800000, 0x00, 0x08, 22579200, 151},
        {19200000, 0x03, 0x50, 0x000000, 0x00, 0x08, 24576000, 164},

        {22579200, 0, 0, 0, 0, 0, 22579200, 0},
        {22579200, 0x03, 0x44, 0x06F700, 0x00, 0x08, 24576000, 139},

        {24000000, 0x03, 0x49, 0x800000, 0x00, 0x0A, 22579200, 120},
        {24000000, 0x03, 0x40, 0x000000, 0x00, 0x08, 24576000, 131},

        {24576000, 0x03, 0x49, 0x800000, 0x01, 0x0A, 22579200, 118},
        {24576000, 0, 0, 0, 0, 0, 24576000, 0},

        {26000000, 0x03, 0x45, 0x797680, 0x01, 0x0A, 22579200, 111},
        {26000000, 0x03, 0x3C, 0x7EA940, 0x01, 0x08, 24576000, 121},
};

static const struct cs43130_pll_params *cs43130_get_pll_table(
                unsigned int freq_in, unsigned int freq_out)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {
                if (pll_ratio_table[i].pll_in == freq_in &&
                    pll_ratio_table[i].pll_out == freq_out)
                        return &pll_ratio_table[i];
        }

        return NULL;
}

static int cs43130_pll_config(struct snd_soc_component *component)
{
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        const struct cs43130_pll_params *pll_entry;

        dev_dbg(cs43130->dev, "cs43130->mclk = %u, cs43130->mclk_int = %u\n",
                cs43130->mclk, cs43130->mclk_int);

        pll_entry = cs43130_get_pll_table(cs43130->mclk, cs43130->mclk_int);
        if (!pll_entry)
                return -EINVAL;

        if (pll_entry->pll_cal_ratio == 0) {
                regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_1,
                                   CS43130_PLL_START_MASK, 0);

                cs43130->pll_bypass = true;
                return 0;
        }

        cs43130->pll_bypass = false;

        regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_2,
                           CS43130_PLL_DIV_DATA_MASK,
                           pll_entry->pll_div_frac >>
                           CS43130_PLL_DIV_FRAC_0_DATA_SHIFT);
        regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_3,
                           CS43130_PLL_DIV_DATA_MASK,
                           pll_entry->pll_div_frac >>
                           CS43130_PLL_DIV_FRAC_1_DATA_SHIFT);
        regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_4,
                           CS43130_PLL_DIV_DATA_MASK,
                           pll_entry->pll_div_frac >>
                           CS43130_PLL_DIV_FRAC_2_DATA_SHIFT);
        regmap_write(cs43130->regmap, CS43130_PLL_SET_5,
                     pll_entry->pll_div_int);
        regmap_write(cs43130->regmap, CS43130_PLL_SET_6, pll_entry->pll_divout);
        regmap_write(cs43130->regmap, CS43130_PLL_SET_7,
                     pll_entry->pll_cal_ratio);
        regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_8,
                           CS43130_PLL_MODE_MASK,
                           pll_entry->pll_mode << CS43130_PLL_MODE_SHIFT);
        regmap_write(cs43130->regmap, CS43130_PLL_SET_9,
                     pll_entry->sclk_prediv);
        regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_1,
                           CS43130_PLL_START_MASK, 1);

        return 0;
}

static int cs43130_set_pll(struct snd_soc_component *component, int pll_id, int source,
                           unsigned int freq_in, unsigned int freq_out)
{
        int ret = 0;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (freq_in) {
        case 9600000:
        case 11289600:
        case 12000000:
        case 12288000:
        case 13000000:
        case 19200000:
        case 22579200:
        case 24000000:
        case 24576000:
        case 26000000:
                cs43130->mclk = freq_in;
                break;
        default:
                dev_err(cs43130->dev,
                        "unsupported pll input reference clock:%d\n", freq_in);
                return -EINVAL;
        }

        switch (freq_out) {
        case 22579200:
                cs43130->mclk_int = freq_out;
                break;
        case 24576000:
                cs43130->mclk_int = freq_out;
                break;
        default:
                dev_err(cs43130->dev,
                        "unsupported pll output ref clock: %u\n", freq_out);
                return -EINVAL;
        }

        ret = cs43130_pll_config(component);
        dev_dbg(cs43130->dev, "cs43130->pll_bypass = %d", cs43130->pll_bypass);
        return ret;
}

static int cs43130_wait_for_completion(struct cs43130_private *cs43130, struct completion *to_poll,
                                        int time)
{
        int stickies, offset, flag, ret;

        if (cs43130->has_irq_line) {
                ret = wait_for_completion_timeout(to_poll, msecs_to_jiffies(time));
                if (ret == 0)
                        return -ETIMEDOUT;
                else
                        return 0; // Discard number of jiffies left till timeout and return success
        }

        if (to_poll == &cs43130->xtal_rdy) {
                offset = 0;
                flag = CS43130_XTAL_RDY_INT;
        } else if (to_poll == &cs43130->pll_rdy) {
                offset = 0;
                flag = CS43130_PLL_RDY_INT;
        } else {
                return -EINVAL;
        }

        return regmap_read_poll_timeout(cs43130->regmap, CS43130_INT_STATUS_1 + offset,
                                        stickies, (stickies & flag),
                                        1000, time * 1000);
}

static int cs43130_change_clksrc(struct snd_soc_component *component,
                                 enum cs43130_mclk_src_sel src)
{
        int ret;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        int mclk_int_decoded;

        if (src == cs43130->mclk_int_src) {
                /* clk source has not changed */
                return 0;
        }

        switch (cs43130->mclk_int) {
        case CS43130_MCLK_22M:
                mclk_int_decoded = CS43130_MCLK_22P5;
                break;
        case CS43130_MCLK_24M:
                mclk_int_decoded = CS43130_MCLK_24P5;
                break;
        default:
                dev_err(cs43130->dev, "Invalid MCLK INT freq: %u\n", cs43130->mclk_int);
                return -EINVAL;
        }

        switch (src) {
        case CS43130_MCLK_SRC_EXT:
                cs43130->pll_bypass = true;
                cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT;
                if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) {
                        regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                           CS43130_PDN_XTAL_MASK,
                                           1 << CS43130_PDN_XTAL_SHIFT);
                } else {
                        reinit_completion(&cs43130->xtal_rdy);
                        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                           CS43130_XTAL_RDY_INT_MASK, 0);
                        regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                           CS43130_PDN_XTAL_MASK, 0);
                        ret = cs43130_wait_for_completion(cs43130, &cs43130->xtal_rdy, 100);
                        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                           CS43130_XTAL_RDY_INT_MASK,
                                           1 << CS43130_XTAL_RDY_INT_SHIFT);
                        if (ret) {
                                dev_err(cs43130->dev, "Error waiting for XTAL_READY interrupt: %d\n", ret);
                                return ret;
                        }
                }

                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_SRC_SEL_MASK,
                                   src << CS43130_MCLK_SRC_SEL_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_INT_MASK,
                                   mclk_int_decoded << CS43130_MCLK_INT_SHIFT);
                usleep_range(150, 200);

                regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                   CS43130_PDN_PLL_MASK,
                                   1 << CS43130_PDN_PLL_SHIFT);
                break;
        case CS43130_MCLK_SRC_PLL:
                cs43130->pll_bypass = false;
                cs43130->mclk_int_src = CS43130_MCLK_SRC_PLL;
                if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) {
                        regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                           CS43130_PDN_XTAL_MASK,
                                           1 << CS43130_PDN_XTAL_SHIFT);
                } else {
                        reinit_completion(&cs43130->xtal_rdy);
                        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                           CS43130_XTAL_RDY_INT_MASK, 0);
                        regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                           CS43130_PDN_XTAL_MASK, 0);
                        ret = cs43130_wait_for_completion(cs43130, &cs43130->xtal_rdy, 100);
                        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                           CS43130_XTAL_RDY_INT_MASK,
                                           1 << CS43130_XTAL_RDY_INT_SHIFT);
                        if (ret) {
                                dev_err(cs43130->dev, "Error waiting for XTAL_READY interrupt: %d\n", ret);
                                return ret;
                        }
                }

                reinit_completion(&cs43130->pll_rdy);
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_PLL_RDY_INT_MASK, 0);
                regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                   CS43130_PDN_PLL_MASK, 0);
                ret = cs43130_wait_for_completion(cs43130, &cs43130->pll_rdy, 100);
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_PLL_RDY_INT_MASK,
                                   1 << CS43130_PLL_RDY_INT_SHIFT);
                if (ret) {
                        dev_err(cs43130->dev, "Error waiting for PLL_READY interrupt: %d\n", ret);
                        return ret;
                }

                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_SRC_SEL_MASK,
                                   src << CS43130_MCLK_SRC_SEL_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_INT_MASK,
                                   mclk_int_decoded << CS43130_MCLK_INT_SHIFT);
                usleep_range(150, 200);
                break;
        case CS43130_MCLK_SRC_RCO:
                cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;

                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_SRC_SEL_MASK,
                                   src << CS43130_MCLK_SRC_SEL_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
                                   CS43130_MCLK_INT_MASK,
                                   CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT);
                usleep_range(150, 200);

                regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                   CS43130_PDN_XTAL_MASK,
                                   1 << CS43130_PDN_XTAL_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
                                   CS43130_PDN_PLL_MASK,
                                   1 << CS43130_PDN_PLL_SHIFT);
                break;
        default:
                dev_err(cs43130->dev, "Invalid MCLK source value\n");
                return -EINVAL;
        }

        return 0;
}

static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = {
        {8,     CS43130_SP_BIT_SIZE_8,  CS43130_CH_BIT_SIZE_8},
        {16,    CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16},
        {24,    CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24},
        {32,    CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32},
};

static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table(
                                unsigned int bitwidth)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) {
                if (cs43130_bitwidth_table[i].bitwidth == bitwidth)
                        return &cs43130_bitwidth_table[i];
        }

        return NULL;
}

static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai,
                          struct regmap *regmap)
{
        const struct cs43130_bitwidth_map *bw_map;

        bw_map = cs43130_get_bitwidth_table(bitwidth_dai);
        if (!bw_map)
                return -EINVAL;

        switch (dai_id) {
        case CS43130_ASP_PCM_DAI:
        case CS43130_ASP_DOP_DAI:
                regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN,
                                   CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
                regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN,
                                   CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
                regmap_update_bits(regmap, CS43130_SP_BITSIZE,
                                   CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit);
                break;
        case CS43130_XSP_DOP_DAI:
                regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN,
                                   CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
                regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN,
                                   CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
                regmap_update_bits(regmap, CS43130_SP_BITSIZE,
                                   CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit <<
                                   CS43130_XSP_BITSIZE_SHIFT);
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static const struct cs43130_rate_map cs43130_rate_table[] = {
        {32000,         CS43130_ASP_SPRATE_32K},
        {44100,         CS43130_ASP_SPRATE_44_1K},
        {48000,         CS43130_ASP_SPRATE_48K},
        {88200,         CS43130_ASP_SPRATE_88_2K},
        {96000,         CS43130_ASP_SPRATE_96K},
        {176400,        CS43130_ASP_SPRATE_176_4K},
        {192000,        CS43130_ASP_SPRATE_192K},
        {352800,        CS43130_ASP_SPRATE_352_8K},
        {384000,        CS43130_ASP_SPRATE_384K},
};

static const struct cs43130_rate_map *cs43130_get_rate_table(int fs)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) {
                if (cs43130_rate_table[i].fs == fs)
                        return &cs43130_rate_table[i];
        }

        return NULL;
}

static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs,
                const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table)
{
        int i;

        for (i = 0; i < len_clk_gen_table; i++) {
                if (clk_gen_table[i].mclk_int == mclk_int &&
                    clk_gen_table[i].fs == fs)
                        return &clk_gen_table[i];
        }

        return NULL;
}

static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk,
                              struct snd_pcm_hw_params *params,
                              struct cs43130_private *cs43130)
{
        u16 frm_size;
        u16 hi_size;
        u8 frm_delay;
        u8 frm_phase;
        u8 frm_data;
        u8 sclk_edge;
        u8 lrck_edge;
        u8 clk_data;
        u8 loc_ch1;
        u8 loc_ch2;
        u8 dai_mode_val;
        const struct cs43130_clk_gen *clk_gen;

        switch (cs43130->dais[dai_id].dai_format) {
        case SND_SOC_DAIFMT_I2S:
                hi_size = bitwidth_sclk;
                frm_delay = 2;
                frm_phase = 0;
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                hi_size = bitwidth_sclk;
                frm_delay = 0;
                frm_phase = 1;
                break;
        case SND_SOC_DAIFMT_DSP_A:
                hi_size = 1;
                frm_delay = 2;
                frm_phase = 1;
                break;
        case SND_SOC_DAIFMT_DSP_B:
                hi_size = 1;
                frm_delay = 0;
                frm_phase = 1;
                break;
        default:
                return -EINVAL;
        }

        switch (cs43130->dais[dai_id].dai_invert) {
        case SND_SOC_DAIFMT_NB_NF:
                sclk_edge = 1;
                lrck_edge = 0;
                break;
        case SND_SOC_DAIFMT_IB_NF:
                sclk_edge = 0;
                lrck_edge = 0;
                break;
        case SND_SOC_DAIFMT_NB_IF:
                sclk_edge = 1;
                lrck_edge = 1;
                break;
        case SND_SOC_DAIFMT_IB_IF:
                sclk_edge = 0;
                lrck_edge = 1;
                break;
        default:
                return -EINVAL;
        }

        switch (cs43130->dais[dai_id].dai_mode) {
        case SND_SOC_DAIFMT_CBC_CFC:
                dai_mode_val = 0;
                break;
        case SND_SOC_DAIFMT_CBP_CFP:
                dai_mode_val = 1;
                break;
        default:
                return -EINVAL;
        }

        frm_size = bitwidth_sclk * params_channels(params);
        loc_ch1 = 0;
        loc_ch2 = bitwidth_sclk * (params_channels(params) - 1);

        frm_data = frm_delay & CS43130_SP_FSD_MASK;
        frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK;

        clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK;
        clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) &
                    CS43130_SP_LCPOL_OUT_MASK;
        clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) &
                    CS43130_SP_SCPOL_IN_MASK;
        clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) &
                    CS43130_SP_SCPOL_OUT_MASK;
        clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) &
                    CS43130_SP_MODE_MASK;

        switch (dai_id) {
        case CS43130_ASP_PCM_DAI:
        case CS43130_ASP_DOP_DAI:
                regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1,
                        CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
                        CS43130_SP_LCPR_LSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2,
                        CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
                        CS43130_SP_LCPR_MSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1,
                        CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
                        CS43130_SP_LCHI_LSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2,
                        CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
                        CS43130_SP_LCHI_MSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data);
                regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1);
                regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2);
                regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN,
                        CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN,
                        CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
                regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data);
                break;
        case CS43130_XSP_DOP_DAI:
                regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1,
                        CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
                        CS43130_SP_LCPR_LSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2,
                        CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
                        CS43130_SP_LCPR_MSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1,
                        CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
                        CS43130_SP_LCHI_LSB_DATA_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2,
                        CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
                        CS43130_SP_LCHI_MSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data);
                regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1);
                regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2);
                regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN,
                        CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN,
                        CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
                regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data);
                break;
        default:
                return -EINVAL;
        }

        switch (frm_size) {
        case 16:
                clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
                                              params_rate(params),
                                              cs43130_16_clk_gen,
                                              ARRAY_SIZE(cs43130_16_clk_gen));
                break;
        case 32:
                clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
                                              params_rate(params),
                                              cs43130_32_clk_gen,
                                              ARRAY_SIZE(cs43130_32_clk_gen));
                break;
        case 48:
                clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
                                              params_rate(params),
                                              cs43130_48_clk_gen,
                                              ARRAY_SIZE(cs43130_48_clk_gen));
                break;
        case 64:
                clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
                                              params_rate(params),
                                              cs43130_64_clk_gen,
                                              ARRAY_SIZE(cs43130_64_clk_gen));
                break;
        default:
                return -EINVAL;
        }

        if (!clk_gen)
                return -EINVAL;

        switch (dai_id) {
        case CS43130_ASP_PCM_DAI:
        case CS43130_ASP_DOP_DAI:
                regmap_write(cs43130->regmap, CS43130_ASP_DEN_1,
                             (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
                             CS43130_SP_M_LSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_ASP_DEN_2,
                             (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
                             CS43130_SP_M_MSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_ASP_NUM_1,
                             (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
                             CS43130_SP_N_LSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_ASP_NUM_2,
                             (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
                             CS43130_SP_N_MSB_DATA_SHIFT);
                break;
        case CS43130_XSP_DOP_DAI:
                regmap_write(cs43130->regmap, CS43130_XSP_DEN_1,
                             (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
                             CS43130_SP_M_LSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_XSP_DEN_2,
                             (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
                             CS43130_SP_M_MSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_XSP_NUM_1,
                             (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
                             CS43130_SP_N_LSB_DATA_SHIFT);
                regmap_write(cs43130->regmap, CS43130_XSP_NUM_2,
                             (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
                             CS43130_SP_N_MSB_DATA_SHIFT);
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int cs43130_pcm_dsd_mix(bool en, struct regmap *regmap)
{
        if (en) {
                regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL,
                                   CS43130_MIX_PCM_PREP_MASK,
                                   1 << CS43130_MIX_PCM_PREP_SHIFT);
                usleep_range(6000, 6050);
                regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL,
                                   CS43130_MIX_PCM_DSD_MASK,
                                   1 << CS43130_MIX_PCM_DSD_SHIFT);
        } else {
                regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL,
                                   CS43130_MIX_PCM_DSD_MASK,
                                   0 << CS43130_MIX_PCM_DSD_SHIFT);
                usleep_range(1600, 1650);
                regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL,
                                   CS43130_MIX_PCM_PREP_MASK,
                                   0 << CS43130_MIX_PCM_PREP_SHIFT);
        }

        return 0;
}

static int cs43130_dsd_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        unsigned int required_clk;
        u8 dsd_speed;

        mutex_lock(&cs43130->clk_mutex);
        if (!cs43130->clk_req) {
                /* no DAI is currently using clk */
                if (!(CS43130_MCLK_22M % params_rate(params)))
                        required_clk = CS43130_MCLK_22M;
                else
                        required_clk = CS43130_MCLK_24M;

                cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk);
                if (cs43130->pll_bypass)
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT);
                else
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL);
        }

        cs43130->clk_req++;
        if (cs43130->clk_req == 2)
                cs43130_pcm_dsd_mix(true, cs43130->regmap);
        mutex_unlock(&cs43130->clk_mutex);

        switch (params_rate(params)) {
        case 176400:
                dsd_speed = 0;
                break;
        case 352800:
                dsd_speed = 1;
                break;
        default:
                dev_err(cs43130->dev, "Rate(%u) not supported\n",
                        params_rate(params));
                return -EINVAL;
        }

        if (cs43130->dais[dai->id].dai_mode == SND_SOC_DAIFMT_CBP_CFP)
                regmap_update_bits(cs43130->regmap, CS43130_DSD_INT_CFG,
                                   CS43130_DSD_MASTER, CS43130_DSD_MASTER);
        else
                regmap_update_bits(cs43130->regmap, CS43130_DSD_INT_CFG,
                                   CS43130_DSD_MASTER, 0);

        regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
                           CS43130_DSD_SPEED_MASK,
                           dsd_speed << CS43130_DSD_SPEED_SHIFT);
        regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
                           CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_DSD <<
                           CS43130_DSD_SRC_SHIFT);

        return 0;
}

static int cs43130_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        const struct cs43130_rate_map *rate_map;
        unsigned int sclk = cs43130->dais[dai->id].sclk;
        unsigned int bitwidth_sclk;
        unsigned int bitwidth_dai = (unsigned int)(params_width(params));
        unsigned int required_clk;
        u8 dsd_speed;

        mutex_lock(&cs43130->clk_mutex);
        if (!cs43130->clk_req) {
                /* no DAI is currently using clk */
                if (!(CS43130_MCLK_22M % params_rate(params)))
                        required_clk = CS43130_MCLK_22M;
                else
                        required_clk = CS43130_MCLK_24M;

                cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk);
                if (cs43130->pll_bypass)
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT);
                else
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL);
        }

        cs43130->clk_req++;
        if (cs43130->clk_req == 2)
                cs43130_pcm_dsd_mix(true, cs43130->regmap);
        mutex_unlock(&cs43130->clk_mutex);

        switch (dai->id) {
        case CS43130_ASP_DOP_DAI:
        case CS43130_XSP_DOP_DAI:
                /* DoP bitwidth is always 24-bit */
                bitwidth_dai = 24;
                sclk = params_rate(params) * bitwidth_dai *
                       params_channels(params);

                switch (params_rate(params)) {
                case 176400:
                        dsd_speed = 0;
                        break;
                case 352800:
                        dsd_speed = 1;
                        break;
                default:
                        dev_err(cs43130->dev, "Rate(%u) not supported\n",
                                params_rate(params));
                        return -EINVAL;
                }

                regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
                                   CS43130_DSD_SPEED_MASK,
                                   dsd_speed << CS43130_DSD_SPEED_SHIFT);
                break;
        case CS43130_ASP_PCM_DAI:
                rate_map = cs43130_get_rate_table(params_rate(params));
                if (!rate_map)
                        return -EINVAL;

                regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val);
                break;
        default:
                dev_err(cs43130->dev, "Invalid DAI (%d)\n", dai->id);
                return -EINVAL;
        }

        switch (dai->id) {
        case CS43130_ASP_DOP_DAI:
                regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
                                   CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP <<
                                   CS43130_DSD_SRC_SHIFT);
                break;
        case CS43130_XSP_DOP_DAI:
                regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
                                   CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP <<
                                   CS43130_DSD_SRC_SHIFT);
                break;
        }

        if (!sclk && cs43130->dais[dai->id].dai_mode == SND_SOC_DAIFMT_CBP_CFP)
                /* Calculate SCLK in master mode if unassigned */
                sclk = params_rate(params) * bitwidth_dai *
                       params_channels(params);

        if (!sclk) {
                /* at this point, SCLK must be set */
                dev_err(cs43130->dev, "SCLK freq is not set\n");
                return -EINVAL;
        }

        bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params);
        if (bitwidth_sclk < bitwidth_dai) {
                dev_err(cs43130->dev, "Format not supported: SCLK freq is too low\n");
                return -EINVAL;
        }

        dev_dbg(cs43130->dev,
                "sclk = %u, fs = %d, bitwidth_dai = %u\n",
                sclk, params_rate(params), bitwidth_dai);

        dev_dbg(cs43130->dev,
                "bitwidth_sclk = %u, num_ch = %u\n",
                bitwidth_sclk, params_channels(params));

        cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap);
        cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130);

        return 0;
}

static int cs43130_hw_free(struct snd_pcm_substream *substream,
                           struct snd_soc_dai *dai)
{
        struct snd_soc_component *component = dai->component;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        mutex_lock(&cs43130->clk_mutex);
        cs43130->clk_req--;
        if (!cs43130->clk_req) {
                /* no DAI is currently using clk */
                cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO);
                cs43130_pcm_dsd_mix(false, cs43130->regmap);
        }
        mutex_unlock(&cs43130->clk_mutex);

        return 0;
}

static const DECLARE_TLV_DB_SCALE(pcm_vol_tlv, -12750, 50, 1);

static const char * const pcm_ch_text[] = {
        "Left-Right Ch",
        "Left-Left Ch",
        "Right-Left Ch",
        "Right-Right Ch",
};

static const struct reg_sequence pcm_ch_en_seq[] = {
        {CS43130_DXD1, 0x99},
        {0x180005, 0x8C},
        {0x180007, 0xAB},
        {0x180015, 0x31},
        {0x180017, 0xB2},
        {0x180025, 0x30},
        {0x180027, 0x84},
        {0x180035, 0x9C},
        {0x180037, 0xAE},
        {0x18000D, 0x24},
        {0x18000F, 0xA3},
        {0x18001D, 0x05},
        {0x18001F, 0xD4},
        {0x18002D, 0x0B},
        {0x18002F, 0xC7},
        {0x18003D, 0x71},
        {0x18003F, 0xE7},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence pcm_ch_dis_seq[] = {
        {CS43130_DXD1, 0x99},
        {0x180005, 0x24},
        {0x180007, 0xA3},
        {0x180015, 0x05},
        {0x180017, 0xD4},
        {0x180025, 0x0B},
        {0x180027, 0xC7},
        {0x180035, 0x71},
        {0x180037, 0xE7},
        {0x18000D, 0x8C},
        {0x18000F, 0xAB},
        {0x18001D, 0x31},
        {0x18001F, 0xB2},
        {0x18002D, 0x30},
        {0x18002F, 0x84},
        {0x18003D, 0x9C},
        {0x18003F, 0xAE},
        {CS43130_DXD1, 0},
};

static int cs43130_pcm_ch_get(struct snd_kcontrol *kcontrol,
                              struct snd_ctl_elem_value *ucontrol)
{
        return snd_soc_get_enum_double(kcontrol, ucontrol);
}

static int cs43130_pcm_ch_put(struct snd_kcontrol *kcontrol,
                              struct snd_ctl_elem_value *ucontrol)
{
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
        unsigned int *item = ucontrol->value.enumerated.item;
        struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        unsigned int val;

        if (item[0] >= e->items)
                return -EINVAL;
        val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l;

        switch (cs43130->dev_id) {
        case CS43131_CHIP_ID:
        case CS43198_CHIP_ID:
                if (val >= 2)
                        regmap_multi_reg_write(cs43130->regmap, pcm_ch_en_seq,
                                               ARRAY_SIZE(pcm_ch_en_seq));
                else
                        regmap_multi_reg_write(cs43130->regmap, pcm_ch_dis_seq,
                                               ARRAY_SIZE(pcm_ch_dis_seq));
                break;
        }

        return snd_soc_put_enum_double(kcontrol, ucontrol);
}

static SOC_ENUM_SINGLE_DECL(pcm_ch_enum, CS43130_PCM_PATH_CTL_2, 0,
                            pcm_ch_text);

static const char * const pcm_spd_texts[] = {
        "Fast",
        "Slow",
};

static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7,
                            pcm_spd_texts);

static const char * const dsd_texts[] = {
        "Off",
        "BCKA Mode",
        "BCKD Mode",
};

static const unsigned int dsd_values[] = {
        CS43130_DSD_SRC_DSD,
        CS43130_DSD_SRC_ASP,
        CS43130_DSD_SRC_XSP,
};

static SOC_VALUE_ENUM_SINGLE_DECL(dsd_enum, CS43130_DSD_INT_CFG, 0, 0x03,
                                  dsd_texts, dsd_values);

static const struct snd_kcontrol_new cs43130_snd_controls[] = {
        SOC_DOUBLE_R_TLV("Master Playback Volume",
                         CS43130_PCM_VOL_A, CS43130_PCM_VOL_B, 0, 0xFF, 1,
                         pcm_vol_tlv),
        SOC_DOUBLE_R_TLV("Master DSD Playback Volume",
                         CS43130_DSD_VOL_A, CS43130_DSD_VOL_B, 0, 0xFF, 1,
                         pcm_vol_tlv),
        SOC_ENUM_EXT("PCM Ch Select", pcm_ch_enum, cs43130_pcm_ch_get,
                     cs43130_pcm_ch_put),
        SOC_ENUM("PCM Filter Speed", pcm_spd_enum),
        SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0),
        SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0),
        SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0),
        SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0),
        SOC_ENUM("DSD Phase Modulation", dsd_enum),
};

static const struct reg_sequence pcm_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD7, 0x01},
        {CS43130_DXD8, 0},
        {CS43130_DXD9, 0x01},
        {CS43130_DXD3, 0x12},
        {CS43130_DXD4, 0},
        {CS43130_DXD10, 0x28},
        {CS43130_DXD11, 0x28},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence dsd_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD7, 0x01},
        {CS43130_DXD8, 0},
        {CS43130_DXD9, 0x01},
        {CS43130_DXD3, 0x12},
        {CS43130_DXD4, 0},
        {CS43130_DXD10, 0x1E},
        {CS43130_DXD11, 0x20},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence pop_free_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD12, 0x0A},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence pop_free_seq2[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD13, 0x20},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence mute_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD3, 0x12},
        {CS43130_DXD5, 0x02},
        {CS43130_DXD4, 0x12},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence unmute_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD3, 0x10},
        {CS43130_DXD5, 0},
        {CS43130_DXD4, 0x16},
        {CS43130_DXD1, 0},
};

static int cs43130_dsd_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (event) {
        case SND_SOC_DAPM_PRE_PMU:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, dsd_seq,
                                               ARRAY_SIZE(dsd_seq));
                        break;
                }
                break;
        case SND_SOC_DAPM_POST_PMU:
                regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_1,
                                   CS43130_MUTE_MASK, 0);
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, unmute_seq,
                                               ARRAY_SIZE(unmute_seq));
                        break;
                }
                break;
        case SND_SOC_DAPM_PRE_PMD:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, mute_seq,
                                               ARRAY_SIZE(mute_seq));
                        regmap_update_bits(cs43130->regmap,
                                           CS43130_DSD_PATH_CTL_1,
                                           CS43130_MUTE_MASK, CS43130_MUTE_EN);
                        /*
                         * DSD Power Down Sequence
                         * According to Design, 130ms is preferred.
                         */
                        msleep(130);
                        break;
                case CS43131_CHIP_ID:
                case CS43198_CHIP_ID:
                        regmap_update_bits(cs43130->regmap,
                                           CS43130_DSD_PATH_CTL_1,
                                           CS43130_MUTE_MASK, CS43130_MUTE_EN);
                        break;
                }
                break;
        default:
                dev_err(cs43130->dev, "Invalid event = 0x%x\n", event);
                return -EINVAL;
        }
        return 0;
}

static int cs43130_pcm_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (event) {
        case SND_SOC_DAPM_PRE_PMU:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, pcm_seq,
                                               ARRAY_SIZE(pcm_seq));
                        break;
                }
                break;
        case SND_SOC_DAPM_POST_PMU:
                regmap_update_bits(cs43130->regmap, CS43130_PCM_PATH_CTL_1,
                                   CS43130_MUTE_MASK, 0);
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, unmute_seq,
                                               ARRAY_SIZE(unmute_seq));
                        break;
                }
                break;
        case SND_SOC_DAPM_PRE_PMD:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, mute_seq,
                                               ARRAY_SIZE(mute_seq));
                        regmap_update_bits(cs43130->regmap,
                                           CS43130_PCM_PATH_CTL_1,
                                           CS43130_MUTE_MASK, CS43130_MUTE_EN);
                        /*
                         * PCM Power Down Sequence
                         * According to Design, 130ms is preferred.
                         */
                        msleep(130);
                        break;
                case CS43131_CHIP_ID:
                case CS43198_CHIP_ID:
                        regmap_update_bits(cs43130->regmap,
                                           CS43130_PCM_PATH_CTL_1,
                                           CS43130_MUTE_MASK, CS43130_MUTE_EN);
                        break;
                }
                break;
        default:
                dev_err(cs43130->dev, "Invalid event = 0x%x\n", event);
                return -EINVAL;
        }
        return 0;
}

static const struct reg_sequence dac_postpmu_seq[] = {
        {CS43130_DXD9, 0x0C},
        {CS43130_DXD3, 0x10},
        {CS43130_DXD4, 0x20},
};

static const struct reg_sequence dac_postpmd_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD6, 0x01},
        {CS43130_DXD1, 0},
};

static int cs43130_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (event) {
        case SND_SOC_DAPM_PRE_PMU:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, pop_free_seq,
                                               ARRAY_SIZE(pop_free_seq));
                        break;
                case CS43131_CHIP_ID:
                case CS43198_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, pop_free_seq2,
                                               ARRAY_SIZE(pop_free_seq2));
                        break;
                }
                break;
        case SND_SOC_DAPM_POST_PMU:
                usleep_range(10000, 10050);

                regmap_write(cs43130->regmap, CS43130_DXD1, 0x99);

                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, dac_postpmu_seq,
                                               ARRAY_SIZE(dac_postpmu_seq));
                        /*
                         * Per datasheet, Sec. PCM Power-Up Sequence.
                         * According to Design, CS43130_DXD12 must be 0 to meet
                         * THDN and Dynamic Range spec.
                         */
                        msleep(1000);
                        regmap_write(cs43130->regmap, CS43130_DXD12, 0);
                        break;
                case CS43131_CHIP_ID:
                case CS43198_CHIP_ID:
                        usleep_range(12000, 12010);
                        regmap_write(cs43130->regmap, CS43130_DXD13, 0);
                        break;
                }

                regmap_write(cs43130->regmap, CS43130_DXD1, 0);
                break;
        case SND_SOC_DAPM_POST_PMD:
                switch (cs43130->dev_id) {
                case CS43130_CHIP_ID:
                case CS4399_CHIP_ID:
                        regmap_multi_reg_write(cs43130->regmap, dac_postpmd_seq,
                                               ARRAY_SIZE(dac_postpmd_seq));
                        break;
                }
                break;
        default:
                dev_err(cs43130->dev, "Invalid DAC event = 0x%x\n", event);
                return -EINVAL;
        }
        return 0;
}

static const struct reg_sequence hpin_prepmd_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD15, 0x64},
        {CS43130_DXD14, 0},
        {CS43130_DXD2, 0},
        {CS43130_DXD1, 0},
};

static const struct reg_sequence hpin_postpmu_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD2, 1},
        {CS43130_DXD14, 0xDC},
        {CS43130_DXD15, 0xE4},
        {CS43130_DXD1, 0},
};

static int cs43130_hpin_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 cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (event) {
        case SND_SOC_DAPM_POST_PMD:
                regmap_multi_reg_write(cs43130->regmap, hpin_prepmd_seq,
                                       ARRAY_SIZE(hpin_prepmd_seq));
                break;
        case SND_SOC_DAPM_PRE_PMU:
                regmap_multi_reg_write(cs43130->regmap, hpin_postpmu_seq,
                                       ARRAY_SIZE(hpin_postpmu_seq));
                break;
        default:
                dev_err(cs43130->dev, "Invalid HPIN event = 0x%x\n", event);
                return -EINVAL;
        }
        return 0;
}

static const char * const bypass_mux_text[] = {
        "Internal",
        "Alternative",
};
static SOC_ENUM_SINGLE_DECL(bypass_enum, SND_SOC_NOPM, 0, bypass_mux_text);
static const struct snd_kcontrol_new bypass_ctrl = SOC_DAPM_ENUM("Switch", bypass_enum);

static const struct snd_soc_dapm_widget hp_widgets[] = {
        SND_SOC_DAPM_MUX("Bypass Switch", SND_SOC_NOPM, 0, 0, &bypass_ctrl),
        SND_SOC_DAPM_OUTPUT("HPOUTA"),
        SND_SOC_DAPM_OUTPUT("HPOUTB"),

        SND_SOC_DAPM_AIF_IN_E("ASPIN PCM", NULL, 0, CS43130_PWDN_CTL,
                              CS43130_PDN_ASP_SHIFT, 1, cs43130_pcm_event,
                              (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                               SND_SOC_DAPM_PRE_PMD)),

        SND_SOC_DAPM_AIF_IN_E("ASPIN DoP", NULL, 0, CS43130_PWDN_CTL,
                              CS43130_PDN_ASP_SHIFT, 1, cs43130_dsd_event,
                              (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                               SND_SOC_DAPM_PRE_PMD)),

        SND_SOC_DAPM_AIF_IN_E("XSPIN DoP", NULL, 0, CS43130_PWDN_CTL,
                              CS43130_PDN_XSP_SHIFT, 1, cs43130_dsd_event,
                              (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                               SND_SOC_DAPM_PRE_PMD)),

        SND_SOC_DAPM_AIF_IN_E("XSPIN DSD", NULL, 0, CS43130_PWDN_CTL,
                              CS43130_PDN_DSDIF_SHIFT, 1, cs43130_dsd_event,
                              (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                               SND_SOC_DAPM_PRE_PMD)),

        SND_SOC_DAPM_DAC("DSD", NULL, CS43130_DSD_PATH_CTL_2,
                         CS43130_DSD_EN_SHIFT, 0),

        SND_SOC_DAPM_DAC_E("HiFi DAC", NULL, CS43130_PWDN_CTL,
                           CS43130_PDN_HP_SHIFT, 1, cs43130_dac_event,
                           (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                            SND_SOC_DAPM_POST_PMD)),

/* Some devices have some extra analog widgets */
#define NUM_ANALOG_WIDGETS      1

        SND_SOC_DAPM_DAC_E("Analog Playback", NULL, CS43130_HP_OUT_CTL_1,
                           CS43130_HP_IN_EN_SHIFT, 0, cs43130_hpin_event,
                           (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)),
};

static const struct snd_soc_dapm_route hp_routes[] = {
        {"ASPIN PCM", NULL, "ASP PCM Playback"},
        {"ASPIN DoP", NULL, "ASP DoP Playback"},
        {"XSPIN DoP", NULL, "XSP DoP Playback"},
        {"XSPIN DSD", NULL, "XSP DSD Playback"},
        {"DSD", NULL, "ASPIN DoP"},
        {"DSD", NULL, "XSPIN DoP"},
        {"DSD", NULL, "XSPIN DSD"},
        {"HiFi DAC", NULL, "ASPIN PCM"},
        {"HiFi DAC", NULL, "DSD"},
        {"Bypass Switch", "Internal", "HiFi DAC"},
        {"HPOUTA", NULL, "Bypass Switch"},
        {"HPOUTB", NULL, "Bypass Switch"},

/* Some devices have some extra analog routes */
#define NUM_ANALOG_ROUTES       1
        {"Bypass Switch", "Alternative", "Analog Playback"},
};


static const unsigned int cs43130_asp_src_rates[] = {
        32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000
};

static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = {
        .count  = ARRAY_SIZE(cs43130_asp_src_rates),
        .list   = cs43130_asp_src_rates,
};

static int cs43130_pcm_startup(struct snd_pcm_substream *substream,
                               struct snd_soc_dai *dai)
{
        return snd_pcm_hw_constraint_list(substream->runtime, 0,
                                          SNDRV_PCM_HW_PARAM_RATE,
                                          &cs43130_asp_constraints);
}

static const unsigned int cs43130_dop_src_rates[] = {
        176400, 352800,
};

static const struct snd_pcm_hw_constraint_list cs43130_dop_constraints = {
        .count  = ARRAY_SIZE(cs43130_dop_src_rates),
        .list   = cs43130_dop_src_rates,
};

static int cs43130_dop_startup(struct snd_pcm_substream *substream,
                               struct snd_soc_dai *dai)
{
        return snd_pcm_hw_constraint_list(substream->runtime, 0,
                                          SNDRV_PCM_HW_PARAM_RATE,
                                          &cs43130_dop_constraints);
}

static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
        struct snd_soc_component *component = codec_dai->component;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBC_CFC:
                cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBC_CFC;
                break;
        case SND_SOC_DAIFMT_CBP_CFP:
                cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBP_CFP;
                break;
        default:
                dev_err(cs43130->dev, "unsupported mode\n");
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
        case SND_SOC_DAIFMT_NB_NF:
                cs43130->dais[codec_dai->id].dai_invert = SND_SOC_DAIFMT_NB_NF;
                break;
        case SND_SOC_DAIFMT_IB_NF:
                cs43130->dais[codec_dai->id].dai_invert = SND_SOC_DAIFMT_IB_NF;
                break;
        case SND_SOC_DAIFMT_NB_IF:
                cs43130->dais[codec_dai->id].dai_invert = SND_SOC_DAIFMT_NB_IF;
                break;
        case SND_SOC_DAIFMT_IB_IF:
                cs43130->dais[codec_dai->id].dai_invert = SND_SOC_DAIFMT_IB_IF;
                break;
        default:
                dev_err(cs43130->dev, "Unsupported invert mode 0x%x\n",
                        fmt & SND_SOC_DAIFMT_INV_MASK);
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_I2S:
                cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S;
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J;
                break;
        case SND_SOC_DAIFMT_DSP_A:
                cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_DSP_A;
                break;
        case SND_SOC_DAIFMT_DSP_B:
                cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_DSP_B;
                break;
        default:
                dev_err(cs43130->dev,
                        "unsupported audio format\n");
                return -EINVAL;
        }

        dev_dbg(cs43130->dev, "dai_id = %d,  dai_mode = %u, dai_format = %u\n",
                codec_dai->id,
                cs43130->dais[codec_dai->id].dai_mode,
                cs43130->dais[codec_dai->id].dai_format);

        return 0;
}

static int cs43130_dsd_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
        struct snd_soc_component *component = codec_dai->component;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBC_CFC:
                cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBC_CFC;
                break;
        case SND_SOC_DAIFMT_CBP_CFP:
                cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBP_CFP;
                break;
        default:
                dev_err(cs43130->dev, "Unsupported DAI format.\n");
                return -EINVAL;
        }

        dev_dbg(cs43130->dev, "dai_mode = 0x%x\n",
                cs43130->dais[codec_dai->id].dai_mode);

        return 0;
}

static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai,
                                  int clk_id, unsigned int freq, int dir)
{
        struct snd_soc_component *component = codec_dai->component;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        cs43130->dais[codec_dai->id].sclk = freq;
        dev_dbg(cs43130->dev, "dai_id = %d,  sclk = %u\n", codec_dai->id,
                cs43130->dais[codec_dai->id].sclk);

        return 0;
}

static const struct snd_soc_dai_ops cs43130_pcm_ops = {
        .startup        = cs43130_pcm_startup,
        .hw_params      = cs43130_hw_params,
        .hw_free        = cs43130_hw_free,
        .set_sysclk     = cs43130_set_sysclk,
        .set_fmt        = cs43130_pcm_set_fmt,
};

static const struct snd_soc_dai_ops cs43130_dop_ops = {
        .startup        = cs43130_dop_startup,
        .hw_params      = cs43130_hw_params,
        .hw_free        = cs43130_hw_free,
        .set_sysclk     = cs43130_set_sysclk,
        .set_fmt        = cs43130_pcm_set_fmt,
};

static const struct snd_soc_dai_ops cs43130_dsd_ops = {
        .startup        = cs43130_dop_startup,
        .hw_params      = cs43130_dsd_hw_params,
        .hw_free        = cs43130_hw_free,
        .set_fmt        = cs43130_dsd_set_fmt,
};

static struct snd_soc_dai_driver cs43130_dai[] = {
        {
                .name = "cs43130-asp-pcm",
                .id = CS43130_ASP_PCM_DAI,
                .playback = {
                        .stream_name = "ASP PCM Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_KNOT,
                        .formats = CS43130_PCM_FORMATS,
                },
                .ops = &cs43130_pcm_ops,
                .symmetric_rate = 1,
        },
        {
                .name = "cs43130-asp-dop",
                .id = CS43130_ASP_DOP_DAI,
                .playback = {
                        .stream_name = "ASP DoP Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_KNOT,
                        .formats = CS43130_DOP_FORMATS,
                },
                .ops = &cs43130_dop_ops,
                .symmetric_rate = 1,
        },
        {
                .name = "cs43130-xsp-dop",
                .id = CS43130_XSP_DOP_DAI,
                .playback = {
                        .stream_name = "XSP DoP Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_KNOT,
                        .formats = CS43130_DOP_FORMATS,
                },
                .ops = &cs43130_dop_ops,
                .symmetric_rate = 1,
        },
        {
                .name = "cs43130-xsp-dsd",
                .id = CS43130_XSP_DSD_DAI,
                .playback = {
                        .stream_name = "XSP DSD Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_KNOT,
                        .formats = CS43130_DOP_FORMATS,
                },
                .ops = &cs43130_dsd_ops,
        },

};

static int cs43130_component_set_sysclk(struct snd_soc_component *component,
                                    int clk_id, int source, unsigned int freq,
                                    int dir)
{
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);

        dev_dbg(cs43130->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n",
                clk_id, source, freq, dir);

        switch (freq) {
        case CS43130_MCLK_22M:
        case CS43130_MCLK_24M:
                cs43130->mclk = freq;
                break;
        default:
                dev_err(cs43130->dev, "Invalid MCLK INT freq: %u\n", freq);
                return -EINVAL;
        }

        if (source == CS43130_MCLK_SRC_EXT) {
                cs43130->pll_bypass = true;
        } else {
                dev_err(cs43130->dev, "Invalid MCLK source\n");
                return -EINVAL;
        }

        return 0;
}

static inline u16 cs43130_get_ac_reg_val(u16 ac_freq)
{
        /* AC freq is counted in 5.94Hz step. */
        return ac_freq / 6;
}

static int cs43130_show_dc(struct device *dev, char *buf, u8 ch)
{
        struct i2c_client *client = to_i2c_client(dev);
        struct cs43130_private *cs43130 = i2c_get_clientdata(client);

        if (!cs43130->hpload_done)
                return sysfs_emit(buf, "NO_HPLOAD\n");
        else
                return sysfs_emit(buf, "%u\n", cs43130->hpload_dc[ch]);
}

static ssize_t hpload_dc_l_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
{
        return cs43130_show_dc(dev, buf, HP_LEFT);
}

static ssize_t hpload_dc_r_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
{
        return cs43130_show_dc(dev, buf, HP_RIGHT);
}

static const u16 cs43130_ac_freq[CS43130_AC_FREQ] = {
        24,
        43,
        93,
        200,
        431,
        928,
        2000,
        4309,
        9283,
        20000,
};

static int cs43130_show_ac(struct device *dev, char *buf, u8 ch)
{
        int i, j = 0, tmp;
        struct i2c_client *client = to_i2c_client(dev);
        struct cs43130_private *cs43130 = i2c_get_clientdata(client);

        if (cs43130->hpload_done && cs43130->ac_meas) {
                for (i = 0; i < ARRAY_SIZE(cs43130_ac_freq); i++) {
                        tmp = sysfs_emit_at(buf, j, "%u\n",
                                            cs43130->hpload_ac[i][ch]);
                        if (!tmp)
                                break;

                        j += tmp;
                }

                return j;
        } else {
                return sysfs_emit(buf, "NO_HPLOAD\n");
        }
}

static ssize_t hpload_ac_l_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
{
        return cs43130_show_ac(dev, buf, HP_LEFT);
}

static ssize_t hpload_ac_r_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
{
        return cs43130_show_ac(dev, buf, HP_RIGHT);
}

static DEVICE_ATTR_RO(hpload_dc_l);
static DEVICE_ATTR_RO(hpload_dc_r);
static DEVICE_ATTR_RO(hpload_ac_l);
static DEVICE_ATTR_RO(hpload_ac_r);

static struct attribute *hpload_attrs[] = {
        &dev_attr_hpload_dc_l.attr,
        &dev_attr_hpload_dc_r.attr,
        &dev_attr_hpload_ac_l.attr,
        &dev_attr_hpload_ac_r.attr,
};
ATTRIBUTE_GROUPS(hpload);

static const struct reg_sequence hp_en_cal_seq[] = {
        {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL},
        {CS43130_HP_MEAS_LOAD_1, 0},
        {CS43130_HP_MEAS_LOAD_2, 0},
        {CS43130_INT_MASK_4, 0},
        {CS43130_DXD1, 0x99},
        {CS43130_DXD16, 0xBB},
        {CS43130_DXD12, 0x01},
        {CS43130_DXD19, 0xCB},
        {CS43130_DXD17, 0x95},
        {CS43130_DXD18, 0x0B},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0x80},
};

static const struct reg_sequence hp_en_cal_seq2[] = {
        {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL},
        {CS43130_HP_MEAS_LOAD_1, 0},
        {CS43130_HP_MEAS_LOAD_2, 0},
        {CS43130_INT_MASK_4, 0},
        {CS43130_HP_LOAD_1, 0x80},
};

static const struct reg_sequence hp_dis_cal_seq[] = {
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_DXD1, 0x99},
        {CS43130_DXD12, 0},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0},
};

static const struct reg_sequence hp_dis_cal_seq2[] = {
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_HP_LOAD_1, 0},
};

static const struct reg_sequence hp_dc_ch_l_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD19, 0x0A},
        {CS43130_DXD17, 0x93},
        {CS43130_DXD18, 0x0A},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_HP_LOAD_1, 0x81},
};

static const struct reg_sequence hp_dc_ch_l_seq2[] = {
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_HP_LOAD_1, 0x81},
};

static const struct reg_sequence hp_dc_ch_r_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD19, 0x8A},
        {CS43130_DXD17, 0x15},
        {CS43130_DXD18, 0x06},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0x90},
        {CS43130_HP_LOAD_1, 0x91},
};

static const struct reg_sequence hp_dc_ch_r_seq2[] = {
        {CS43130_HP_LOAD_1, 0x90},
        {CS43130_HP_LOAD_1, 0x91},
};

static const struct reg_sequence hp_ac_ch_l_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD19, 0x0A},
        {CS43130_DXD17, 0x93},
        {CS43130_DXD18, 0x0A},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_HP_LOAD_1, 0x82},
};

static const struct reg_sequence hp_ac_ch_l_seq2[] = {
        {CS43130_HP_LOAD_1, 0x80},
        {CS43130_HP_LOAD_1, 0x82},
};

static const struct reg_sequence hp_ac_ch_r_seq[] = {
        {CS43130_DXD1, 0x99},
        {CS43130_DXD19, 0x8A},
        {CS43130_DXD17, 0x15},
        {CS43130_DXD18, 0x06},
        {CS43130_DXD1, 0},
        {CS43130_HP_LOAD_1, 0x90},
        {CS43130_HP_LOAD_1, 0x92},
};

static const struct reg_sequence hp_ac_ch_r_seq2[] = {
        {CS43130_HP_LOAD_1, 0x90},
        {CS43130_HP_LOAD_1, 0x92},
};

static const struct reg_sequence hp_cln_seq[] = {
        {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL},
        {CS43130_HP_MEAS_LOAD_1, 0},
        {CS43130_HP_MEAS_LOAD_2, 0},
};

struct reg_sequences {
        const struct reg_sequence       *seq;
        int                             size;
        unsigned int                    msk;
};

static const struct reg_sequences hpload_seq1[] = {
        {
                .seq    = hp_en_cal_seq,
                .size   = ARRAY_SIZE(hp_en_cal_seq),
                .msk    = CS43130_HPLOAD_ON_INT,
        },
        {
                .seq    = hp_dc_ch_l_seq,
                .size   = ARRAY_SIZE(hp_dc_ch_l_seq),
                .msk    = CS43130_HPLOAD_DC_INT,
        },
        {
                .seq    = hp_ac_ch_l_seq,
                .size   = ARRAY_SIZE(hp_ac_ch_l_seq),
                .msk    = CS43130_HPLOAD_AC_INT,
        },
        {
                .seq    = hp_dis_cal_seq,
                .size   = ARRAY_SIZE(hp_dis_cal_seq),
                .msk    = CS43130_HPLOAD_OFF_INT,
        },
        {
                .seq    = hp_en_cal_seq,
                .size   = ARRAY_SIZE(hp_en_cal_seq),
                .msk    = CS43130_HPLOAD_ON_INT,
        },
        {
                .seq    = hp_dc_ch_r_seq,
                .size   = ARRAY_SIZE(hp_dc_ch_r_seq),
                .msk    = CS43130_HPLOAD_DC_INT,
        },
        {
                .seq    = hp_ac_ch_r_seq,
                .size   = ARRAY_SIZE(hp_ac_ch_r_seq),
                .msk    = CS43130_HPLOAD_AC_INT,
        },
};

static const struct reg_sequences hpload_seq2[] = {
        {
                .seq    = hp_en_cal_seq2,
                .size   = ARRAY_SIZE(hp_en_cal_seq2),
                .msk    = CS43130_HPLOAD_ON_INT,
        },
        {
                .seq    = hp_dc_ch_l_seq2,
                .size   = ARRAY_SIZE(hp_dc_ch_l_seq2),
                .msk    = CS43130_HPLOAD_DC_INT,
        },
        {
                .seq    = hp_ac_ch_l_seq2,
                .size   = ARRAY_SIZE(hp_ac_ch_l_seq2),
                .msk    = CS43130_HPLOAD_AC_INT,
        },
        {
                .seq    = hp_dis_cal_seq2,
                .size   = ARRAY_SIZE(hp_dis_cal_seq2),
                .msk    = CS43130_HPLOAD_OFF_INT,
        },
        {
                .seq    = hp_en_cal_seq2,
                .size   = ARRAY_SIZE(hp_en_cal_seq2),
                .msk    = CS43130_HPLOAD_ON_INT,
        },
        {
                .seq    = hp_dc_ch_r_seq2,
                .size   = ARRAY_SIZE(hp_dc_ch_r_seq2),
                .msk    = CS43130_HPLOAD_DC_INT,
        },
        {
                .seq    = hp_ac_ch_r_seq2,
                .size   = ARRAY_SIZE(hp_ac_ch_r_seq2),
                .msk    = CS43130_HPLOAD_AC_INT,
        },
};

static int cs43130_update_hpload(unsigned int msk, int ac_idx,
                                 struct cs43130_private *cs43130)
{
        bool left_ch = true;
        unsigned int reg;
        u32 addr;
        u16 impedance;

        switch (msk) {
        case CS43130_HPLOAD_DC_INT:
        case CS43130_HPLOAD_AC_INT:
                break;
        default:
                return 0;
        }

        regmap_read(cs43130->regmap, CS43130_HP_LOAD_1, &reg);
        if (reg & CS43130_HPLOAD_CHN_SEL)
                left_ch = false;

        if (msk == CS43130_HPLOAD_DC_INT)
                addr = CS43130_HP_DC_STAT_1;
        else
                addr = CS43130_HP_AC_STAT_1;

        regmap_read(cs43130->regmap, addr, &reg);
        impedance = reg >> 3;
        regmap_read(cs43130->regmap, addr + 1, &reg);
        impedance |= reg << 5;

        if (msk == CS43130_HPLOAD_DC_INT) {
                if (left_ch)
                        cs43130->hpload_dc[HP_LEFT] = impedance;
                else
                        cs43130->hpload_dc[HP_RIGHT] = impedance;

                dev_dbg(cs43130->dev, "HP DC impedance (Ch %u): %u\n", !left_ch,
                        impedance);
        } else {
                if (left_ch)
                        cs43130->hpload_ac[ac_idx][HP_LEFT] = impedance;
                else
                        cs43130->hpload_ac[ac_idx][HP_RIGHT] = impedance;

                dev_dbg(cs43130->dev, "HP AC (%u Hz) impedance (Ch %u): %u\n",
                        cs43130->ac_freq[ac_idx], !left_ch, impedance);
        }

        return 0;
}

static int cs43130_hpload_proc(struct cs43130_private *cs43130,
                               const struct reg_sequence *seq, int seq_size,
                               unsigned int rslt_msk, int ac_idx)
{
        int ret;
        unsigned int msk;
        u16 ac_reg_val;

        reinit_completion(&cs43130->hpload_evt);

        if (rslt_msk == CS43130_HPLOAD_AC_INT) {
                ac_reg_val = cs43130_get_ac_reg_val(cs43130->ac_freq[ac_idx]);
                regmap_update_bits(cs43130->regmap, CS43130_HP_LOAD_1,
                                   CS43130_HPLOAD_AC_START, 0);
                regmap_update_bits(cs43130->regmap, CS43130_HP_MEAS_LOAD_1,
                                   CS43130_HP_MEAS_LOAD_MASK,
                                   ac_reg_val >> CS43130_HP_MEAS_LOAD_1_SHIFT);
                regmap_update_bits(cs43130->regmap, CS43130_HP_MEAS_LOAD_2,
                                   CS43130_HP_MEAS_LOAD_MASK,
                                   ac_reg_val >> CS43130_HP_MEAS_LOAD_2_SHIFT);
        }

        regmap_multi_reg_write(cs43130->regmap, seq,
                               seq_size);

        ret = wait_for_completion_timeout(&cs43130->hpload_evt,
                                          msecs_to_jiffies(1000));
        regmap_read(cs43130->regmap, CS43130_INT_MASK_4, &msk);
        if (!ret) {
                dev_err(cs43130->dev, "Timeout waiting for HPLOAD interrupt\n");
                return -ETIMEDOUT;
        }

        dev_dbg(cs43130->dev, "HP load stat: %x, INT_MASK_4: %x\n",
                cs43130->hpload_stat, msk);
        if ((cs43130->hpload_stat & (CS43130_HPLOAD_NO_DC_INT |
                                     CS43130_HPLOAD_UNPLUG_INT |
                                     CS43130_HPLOAD_OOR_INT)) ||
            !(cs43130->hpload_stat & rslt_msk)) {
                dev_dbg(cs43130->dev, "HP load measure failed\n");
                return -1;
        }

        return 0;
}

static const struct reg_sequence hv_seq[][2] = {
        {
                {CS43130_CLASS_H_CTL, 0x1C},
                {CS43130_HP_OUT_CTL_1, 0x10},
        },
        {
                {CS43130_CLASS_H_CTL, 0x1E},
                {CS43130_HP_OUT_CTL_1, 0x20},
        },
        {
                {CS43130_CLASS_H_CTL, 0x1E},
                {CS43130_HP_OUT_CTL_1, 0x30},
        },
};

static int cs43130_set_hv(struct regmap *regmap, u16 hpload_dc,
                          const u16 *dc_threshold)
{
        int i;

        for (i = 0; i < CS43130_DC_THRESHOLD; i++) {
                if (hpload_dc <= dc_threshold[i])
                        break;
        }

        regmap_multi_reg_write(regmap, hv_seq[i], ARRAY_SIZE(hv_seq[i]));

        return 0;
}

static void cs43130_imp_meas(struct work_struct *wk)
{
        unsigned int reg, seq_size;
        int i, ret, ac_idx;
        struct cs43130_private *cs43130;
        struct snd_soc_component *component;
        const struct reg_sequences *hpload_seq;

        cs43130 = container_of(wk, struct cs43130_private, work);
        component = cs43130->component;

        if (!cs43130->mclk)
                return;

        cs43130->hpload_done = false;

        mutex_lock(&cs43130->clk_mutex);
        if (!cs43130->clk_req) {
                /* clk not in use */
                cs43130_set_pll(component, 0, 0, cs43130->mclk, CS43130_MCLK_22M);
                if (cs43130->pll_bypass)
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT);
                else
                        cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL);
        }

        cs43130->clk_req++;
        mutex_unlock(&cs43130->clk_mutex);

        regmap_read(cs43130->regmap, CS43130_INT_STATUS_4, &reg);

        switch (cs43130->dev_id) {
        case CS43130_CHIP_ID:
                hpload_seq = hpload_seq1;
                seq_size = ARRAY_SIZE(hpload_seq1);
                break;
        case CS43131_CHIP_ID:
                hpload_seq = hpload_seq2;
                seq_size = ARRAY_SIZE(hpload_seq2);
                break;
        default:
                WARN(1, "Invalid dev_id for meas: %d", cs43130->dev_id);
                return;
        }

        i = 0;
        ac_idx = 0;
        while (i < seq_size) {
                ret = cs43130_hpload_proc(cs43130, hpload_seq[i].seq,
                                          hpload_seq[i].size,
                                          hpload_seq[i].msk, ac_idx);
                if (ret < 0)
                        goto exit;

                cs43130_update_hpload(hpload_seq[i].msk, ac_idx, cs43130);

                if (cs43130->ac_meas &&
                    hpload_seq[i].msk == CS43130_HPLOAD_AC_INT &&
                    ac_idx < CS43130_AC_FREQ - 1) {
                        ac_idx++;
                } else {
                        ac_idx = 0;
                        i++;
                }
        }
        cs43130->hpload_done = true;

        if (cs43130->hpload_dc[HP_LEFT] >= CS43130_LINEOUT_LOAD)
                snd_soc_jack_report(&cs43130->jack, CS43130_JACK_LINEOUT,
                                    CS43130_JACK_MASK);
        else
                snd_soc_jack_report(&cs43130->jack, CS43130_JACK_HEADPHONE,
                                    CS43130_JACK_MASK);

        dev_dbg(cs43130->dev, "Set HP output control. DC threshold\n");
        for (i = 0; i < CS43130_DC_THRESHOLD; i++)
                dev_dbg(cs43130->dev, "DC threshold[%d]: %u.\n", i,
                        cs43130->dc_threshold[i]);

        cs43130_set_hv(cs43130->regmap, cs43130->hpload_dc[HP_LEFT],
                       cs43130->dc_threshold);

exit:
        switch (cs43130->dev_id) {
        case CS43130_CHIP_ID:
                cs43130_hpload_proc(cs43130, hp_dis_cal_seq,
                                    ARRAY_SIZE(hp_dis_cal_seq),
                                    CS43130_HPLOAD_OFF_INT, ac_idx);
                break;
        case CS43131_CHIP_ID:
                cs43130_hpload_proc(cs43130, hp_dis_cal_seq2,
                                    ARRAY_SIZE(hp_dis_cal_seq2),
                                    CS43130_HPLOAD_OFF_INT, ac_idx);
                break;
        }

        regmap_multi_reg_write(cs43130->regmap, hp_cln_seq,
                               ARRAY_SIZE(hp_cln_seq));

        mutex_lock(&cs43130->clk_mutex);
        cs43130->clk_req--;
        /* clk not in use */
        if (!cs43130->clk_req)
                cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO);
        mutex_unlock(&cs43130->clk_mutex);
}

static irqreturn_t cs43130_irq_thread(int irq, void *data)
{
        struct cs43130_private *cs43130 = (struct cs43130_private *)data;
        unsigned int stickies[CS43130_NUM_INT];
        unsigned int irq_occurrence = 0;
        unsigned int masks[CS43130_NUM_INT];
        int i, j;

        for (i = 0; i < ARRAY_SIZE(stickies); i++) {
                regmap_read(cs43130->regmap, CS43130_INT_STATUS_1 + i,
                            &stickies[i]);
                regmap_read(cs43130->regmap, CS43130_INT_MASK_1 + i,
                            &masks[i]);
        }

        for (i = 0; i < ARRAY_SIZE(stickies); i++) {
                stickies[i] = stickies[i] & (~masks[i]);
                for (j = 0; j < 8; j++)
                        irq_occurrence += (stickies[i] >> j) & 1;
        }

        if (!irq_occurrence)
                return IRQ_NONE;

        if (stickies[0] & CS43130_XTAL_RDY_INT) {
                complete(&cs43130->xtal_rdy);
                return IRQ_HANDLED;
        }

        if (stickies[0] & CS43130_PLL_RDY_INT) {
                complete(&cs43130->pll_rdy);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_NO_DC_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_err(cs43130->dev,
                        "DC load has not completed before AC load (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_UNPLUG_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_err(cs43130->dev, "HP unplugged during measurement (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_OOR_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_err(cs43130->dev, "HP load out of range (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_AC_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_dbg(cs43130->dev, "HP AC load measurement done (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_DC_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_dbg(cs43130->dev, "HP DC load measurement done (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_ON_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_dbg(cs43130->dev, "HP load state machine on done (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[3] & CS43130_HPLOAD_OFF_INT) {
                cs43130->hpload_stat = stickies[3];
                dev_dbg(cs43130->dev, "HP load state machine off done (%x)\n",
                        cs43130->hpload_stat);
                complete(&cs43130->hpload_evt);
                return IRQ_HANDLED;
        }

        if (stickies[0] & CS43130_XTAL_ERR_INT) {
                dev_err(cs43130->dev, "Crystal err: clock is not running\n");
                return IRQ_HANDLED;
        }

        if (stickies[0] & CS43130_HP_UNPLUG_INT) {
                dev_dbg(cs43130->dev, "HP unplugged\n");
                cs43130->hpload_done = false;
                snd_soc_jack_report(&cs43130->jack, 0, CS43130_JACK_MASK);
                return IRQ_HANDLED;
        }

        if (stickies[0] & CS43130_HP_PLUG_INT) {
                if (cs43130->dc_meas && !cs43130->hpload_done &&
                    !work_busy(&cs43130->work)) {
                        dev_dbg(cs43130->dev, "HP load queue work\n");
                        queue_work(cs43130->wq, &cs43130->work);
                }

                snd_soc_jack_report(&cs43130->jack, SND_JACK_MECHANICAL,
                                    CS43130_JACK_MASK);
                return IRQ_HANDLED;
        }

        return IRQ_NONE;
}

static int cs43130_probe(struct snd_soc_component *component)
{
        int ret;
        struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component);
        struct snd_soc_card *card = component->card;
        unsigned int reg;

        cs43130->component = component;

        if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED) {
                regmap_update_bits(cs43130->regmap, CS43130_CRYSTAL_SET,
                                   CS43130_XTAL_IBIAS_MASK,
                                   cs43130->xtal_ibias);
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_XTAL_ERR_INT, 0);
        }

        ret = snd_soc_card_jack_new(card, "Headphone", CS43130_JACK_MASK,
                                    &cs43130->jack);
        if (ret < 0) {
                dev_err(cs43130->dev, "Cannot create jack\n");
                return ret;
        }

        cs43130->hpload_done = false;
        if (cs43130->dc_meas) {
                ret = sysfs_create_groups(&cs43130->dev->kobj, hpload_groups);
                if (ret)
                        return ret;

                cs43130->wq = create_singlethread_workqueue("cs43130_hp");
                if (!cs43130->wq) {
                        sysfs_remove_groups(&cs43130->dev->kobj, hpload_groups);
                        return -ENOMEM;
                }
                INIT_WORK(&cs43130->work, cs43130_imp_meas);
        }

        regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, &reg);
        regmap_read(cs43130->regmap, CS43130_HP_STATUS, &reg);
        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                           CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT, 0);
        regmap_update_bits(cs43130->regmap, CS43130_HP_DETECT,
                           CS43130_HP_DETECT_CTRL_MASK, 0);
        regmap_update_bits(cs43130->regmap, CS43130_HP_DETECT,
                           CS43130_HP_DETECT_CTRL_MASK,
                           CS43130_HP_DETECT_CTRL_MASK);

        return 0;
}

static const struct snd_soc_component_driver soc_component_dev_cs43130_digital = {
        .probe                  = cs43130_probe,
        .controls               = cs43130_snd_controls,
        .num_controls           = ARRAY_SIZE(cs43130_snd_controls),
        .set_sysclk             = cs43130_component_set_sysclk,
        .set_pll                = cs43130_set_pll,
        .idle_bias_on           = 1,
        .use_pmdown_time        = 1,
        .endianness             = 1,
        /* Don't take into account the ending analog widgets and routes */
        .dapm_widgets           = hp_widgets,
        .num_dapm_widgets       = ARRAY_SIZE(hp_widgets) - NUM_ANALOG_WIDGETS,
        .dapm_routes            = hp_routes,
        .num_dapm_routes        = ARRAY_SIZE(hp_routes) - NUM_ANALOG_ROUTES,
};

static const struct snd_soc_component_driver soc_component_dev_cs43130_analog = {
        .probe                  = cs43130_probe,
        .controls               = cs43130_snd_controls,
        .num_controls           = ARRAY_SIZE(cs43130_snd_controls),
        .set_sysclk             = cs43130_component_set_sysclk,
        .set_pll                = cs43130_set_pll,
        .idle_bias_on           = 1,
        .use_pmdown_time        = 1,
        .endianness             = 1,
        .dapm_widgets           = hp_widgets,
        .num_dapm_widgets       = ARRAY_SIZE(hp_widgets),
        .dapm_routes            = hp_routes,
        .num_dapm_routes        = ARRAY_SIZE(hp_routes),
};

static const struct regmap_config cs43130_regmap = {
        .reg_bits               = 24,
        .pad_bits               = 8,
        .val_bits               = 8,

        .max_register           = CS43130_LASTREG,
        .reg_defaults           = cs43130_reg_defaults,
        .num_reg_defaults       = ARRAY_SIZE(cs43130_reg_defaults),
        .readable_reg           = cs43130_readable_register,
        .precious_reg           = cs43130_precious_register,
        .volatile_reg           = cs43130_volatile_register,
        .cache_type             = REGCACHE_MAPLE,
        /* needed for regcache_sync */
        .use_single_read        = true,
        .use_single_write       = true,
};

static const u16 cs43130_dc_threshold[CS43130_DC_THRESHOLD] = {
        50,
        120,
};

static int cs43130_handle_device_data(struct cs43130_private *cs43130)
{
        unsigned int val;
        int i;

        if (device_property_read_u32(cs43130->dev, "cirrus,xtal-ibias", &val) < 0) {
                /* Crystal is unused. System clock is used for external MCLK */
                cs43130->xtal_ibias = CS43130_XTAL_UNUSED;
                return 0;
        }

        switch (val) {
        case 1:
                cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA;
                break;
        case 2:
                cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA;
                break;
        case 3:
                cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA;
                break;
        default:
                dev_err(cs43130->dev,
                        "Invalid cirrus,xtal-ibias value: %d\n", val);
                return -EINVAL;
        }

        cs43130->dc_meas = device_property_read_bool(cs43130->dev, "cirrus,dc-measure");
        cs43130->ac_meas = device_property_read_bool(cs43130->dev, "cirrus,ac-measure");

        if (!device_property_read_u16_array(cs43130->dev, "cirrus,ac-freq", cs43130->ac_freq,
                                        CS43130_AC_FREQ)) {
                for (i = 0; i < CS43130_AC_FREQ; i++)
                        cs43130->ac_freq[i] = cs43130_ac_freq[i];
        }

        if (!device_property_read_u16_array(cs43130->dev, "cirrus,dc-threshold",
                                       cs43130->dc_threshold,
                                       CS43130_DC_THRESHOLD)) {
                for (i = 0; i < CS43130_DC_THRESHOLD; i++)
                        cs43130->dc_threshold[i] = cs43130_dc_threshold[i];
        }

        return 0;
}

static int cs43130_i2c_probe(struct i2c_client *client)
{
        const struct snd_soc_component_driver *component_driver;
        struct cs43130_private *cs43130;
        int ret;
        unsigned int reg;
        int i, devid;

        cs43130 = devm_kzalloc(&client->dev, sizeof(*cs43130), GFP_KERNEL);
        if (!cs43130)
                return -ENOMEM;

        cs43130->dev = &client->dev;

        i2c_set_clientdata(client, cs43130);

        cs43130->regmap = devm_regmap_init_i2c(client, &cs43130_regmap);
        if (IS_ERR(cs43130->regmap)) {
                ret = PTR_ERR(cs43130->regmap);
                return ret;
        }

        if (dev_fwnode(cs43130->dev)) {
                ret = cs43130_handle_device_data(cs43130);
                if (ret != 0)
                        return ret;
        }

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

        ret = devm_regulator_bulk_get(cs43130->dev,
                                      ARRAY_SIZE(cs43130->supplies),
                                      cs43130->supplies);
        if (ret != 0) {
                dev_err(cs43130->dev, "Failed to request supplies: %d\n", ret);
                return ret;
        }
        ret = regulator_bulk_enable(ARRAY_SIZE(cs43130->supplies),
                                    cs43130->supplies);
        if (ret != 0) {
                dev_err(cs43130->dev, "Failed to enable supplies: %d\n", ret);
                return ret;
        }

        cs43130->reset_gpio = devm_gpiod_get_optional(cs43130->dev,
                                                      "reset", GPIOD_OUT_LOW);
        if (IS_ERR(cs43130->reset_gpio)) {
                ret = PTR_ERR(cs43130->reset_gpio);
                goto err_supplies;
        }

        gpiod_set_value_cansleep(cs43130->reset_gpio, 1);

        usleep_range(2000, 2050);

        devid = cirrus_read_device_id(cs43130->regmap, CS43130_DEVID_AB);
        if (devid < 0) {
                ret = devid;
                dev_err(cs43130->dev, "Failed to read device ID: %d\n", ret);
                goto err;
        }

        switch (devid) {
        case CS43130_CHIP_ID:
        case CS4399_CHIP_ID:
        case CS43131_CHIP_ID:
        case CS43198_CHIP_ID:
                break;
        default:
                dev_err(cs43130->dev,
                        "CS43130 Device ID %X. Expected ID %X, %X, %X or %X\n",
                        devid, CS43130_CHIP_ID, CS4399_CHIP_ID,
                        CS43131_CHIP_ID, CS43198_CHIP_ID);
                ret = -ENODEV;
                goto err;
        }

        cs43130->dev_id = devid;
        ret = regmap_read(cs43130->regmap, CS43130_REV_ID, &reg);
        if (ret < 0) {
                dev_err(cs43130->dev, "Get Revision ID failed\n");
                goto err;
        }

        dev_info(cs43130->dev,
                 "Cirrus Logic CS43130 (%x), Revision: %02X\n", devid,
                 reg & 0xFF);

        mutex_init(&cs43130->clk_mutex);

        init_completion(&cs43130->xtal_rdy);
        init_completion(&cs43130->pll_rdy);
        init_completion(&cs43130->hpload_evt);

        if (!client->irq) {
                dev_dbg(cs43130->dev, "IRQ not found, will poll instead\n");
                cs43130->has_irq_line = 0;
        } else {
                ret = devm_request_threaded_irq(cs43130->dev, client->irq,
                                                NULL, cs43130_irq_thread,
                                                IRQF_ONESHOT | IRQF_TRIGGER_LOW,
                                                "cs43130", cs43130);
                if (ret != 0) {
                        dev_err(cs43130->dev, "Failed to request IRQ: %d\n", ret);
                        goto err;
                }
                cs43130->has_irq_line = 1;
        }

        cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;

        pm_runtime_set_autosuspend_delay(cs43130->dev, 100);
        pm_runtime_use_autosuspend(cs43130->dev);
        pm_runtime_set_active(cs43130->dev);
        pm_runtime_enable(cs43130->dev);

        switch (cs43130->dev_id) {
        case CS43130_CHIP_ID:
        case CS43131_CHIP_ID:
                component_driver = &soc_component_dev_cs43130_analog;
                break;
        case CS43198_CHIP_ID:
        case CS4399_CHIP_ID:
                component_driver = &soc_component_dev_cs43130_digital;
                break;
        }

        ret = devm_snd_soc_register_component(cs43130->dev, component_driver,
                                     cs43130_dai, ARRAY_SIZE(cs43130_dai));
        if (ret < 0) {
                dev_err(cs43130->dev,
                        "snd_soc_register_component failed with ret = %d\n", ret);
                goto err;
        }

        regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
                           CS43130_ASP_3ST_MASK, 0);
        regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
                           CS43130_XSP_3ST_MASK, 0);

        return 0;

err:
        gpiod_set_value_cansleep(cs43130->reset_gpio, 0);
err_supplies:
        regulator_bulk_disable(ARRAY_SIZE(cs43130->supplies),
                               cs43130->supplies);

        return ret;
}

static void cs43130_i2c_remove(struct i2c_client *client)
{
        struct cs43130_private *cs43130 = i2c_get_clientdata(client);

        if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED)
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_XTAL_ERR_INT,
                                   1 << CS43130_XTAL_ERR_INT_SHIFT);

        regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                           CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT,
                           CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT);

        if (cs43130->dc_meas) {
                cancel_work_sync(&cs43130->work);
                flush_workqueue(cs43130->wq);

                device_remove_file(cs43130->dev, &dev_attr_hpload_dc_l);
                device_remove_file(cs43130->dev, &dev_attr_hpload_dc_r);
                device_remove_file(cs43130->dev, &dev_attr_hpload_ac_l);
                device_remove_file(cs43130->dev, &dev_attr_hpload_ac_r);
        }

        gpiod_set_value_cansleep(cs43130->reset_gpio, 0);

        pm_runtime_disable(cs43130->dev);
        regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies);
}

static int cs43130_runtime_suspend(struct device *dev)
{
        struct cs43130_private *cs43130 = dev_get_drvdata(dev);

        if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED)
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_XTAL_ERR_INT,
                                   1 << CS43130_XTAL_ERR_INT_SHIFT);

        regcache_cache_only(cs43130->regmap, true);
        regcache_mark_dirty(cs43130->regmap);

        gpiod_set_value_cansleep(cs43130->reset_gpio, 0);

        regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies);

        return 0;
}

static int cs43130_runtime_resume(struct device *dev)
{
        struct cs43130_private *cs43130 = dev_get_drvdata(dev);
        int ret;

        ret = regulator_bulk_enable(CS43130_NUM_SUPPLIES, cs43130->supplies);
        if (ret != 0) {
                dev_err(dev, "Failed to enable supplies: %d\n", ret);
                return ret;
        }

        regcache_cache_only(cs43130->regmap, false);

        gpiod_set_value_cansleep(cs43130->reset_gpio, 1);

        usleep_range(2000, 2050);

        ret = regcache_sync(cs43130->regmap);
        if (ret != 0) {
                dev_err(dev, "Failed to restore register cache\n");
                goto err;
        }

        if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED)
                regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
                                   CS43130_XTAL_ERR_INT, 0);

        return 0;
err:
        regcache_cache_only(cs43130->regmap, true);
        regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies);

        return ret;
}

static const struct dev_pm_ops cs43130_runtime_pm = {
        RUNTIME_PM_OPS(cs43130_runtime_suspend, cs43130_runtime_resume, NULL)
};

#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id cs43130_of_match[] = {
        {.compatible = "cirrus,cs43130",},
        {.compatible = "cirrus,cs4399",},
        {.compatible = "cirrus,cs43131",},
        {.compatible = "cirrus,cs43198",},
        {},
};

MODULE_DEVICE_TABLE(of, cs43130_of_match);
#endif

#if IS_ENABLED(CONFIG_ACPI)
static const struct acpi_device_id cs43130_acpi_match[] = {
        { "CSC4399", 0 },
        {}
};

MODULE_DEVICE_TABLE(acpi, cs43130_acpi_match);
#endif


static const struct i2c_device_id cs43130_i2c_id[] = {
        {"cs43130"},
        {"cs4399"},
        {"cs43131"},
        {"cs43198"},
        {}
};

MODULE_DEVICE_TABLE(i2c, cs43130_i2c_id);

static struct i2c_driver cs43130_i2c_driver = {
        .driver = {
                .name                   = "cs43130",
                .of_match_table         = of_match_ptr(cs43130_of_match),
                .acpi_match_table       = ACPI_PTR(cs43130_acpi_match),
                .pm                     = pm_ptr(&cs43130_runtime_pm),
        },
        .id_table       = cs43130_i2c_id,
        .probe          = cs43130_i2c_probe,
        .remove         = cs43130_i2c_remove,
};

module_i2c_driver(cs43130_i2c_driver);

MODULE_AUTHOR("Li Xu <li.xu@cirrus.com>");
MODULE_DESCRIPTION("Cirrus Logic CS43130 ALSA SoC Codec Driver");
MODULE_LICENSE("GPL");