root/sound/soc/codecs/adau7118.c
// SPDX-License-Identifier: GPL-2.0
//
// Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter driver
//
// Copyright 2019 Analog Devices Inc.

#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

#include "adau7118.h"

#define ADAU7118_DEC_RATIO_MASK         GENMASK(1, 0)
#define ADAU7118_DEC_RATIO(x)           FIELD_PREP(ADAU7118_DEC_RATIO_MASK, x)
#define ADAU7118_CLK_MAP_MASK           GENMASK(7, 4)
#define ADAU7118_SLOT_WIDTH_MASK        GENMASK(5, 4)
#define ADAU7118_SLOT_WIDTH(x)          FIELD_PREP(ADAU7118_SLOT_WIDTH_MASK, x)
#define ADAU7118_TRISTATE_MASK          BIT(6)
#define ADAU7118_TRISTATE(x)            FIELD_PREP(ADAU7118_TRISTATE_MASK, x)
#define ADAU7118_DATA_FMT_MASK          GENMASK(3, 1)
#define ADAU7118_DATA_FMT(x)            FIELD_PREP(ADAU7118_DATA_FMT_MASK, x)
#define ADAU7118_SAI_MODE_MASK          BIT(0)
#define ADAU7118_SAI_MODE(x)            FIELD_PREP(ADAU7118_SAI_MODE_MASK, x)
#define ADAU7118_LRCLK_BCLK_POL_MASK    GENMASK(1, 0)
#define ADAU7118_LRCLK_BCLK_POL(x) \
                                FIELD_PREP(ADAU7118_LRCLK_BCLK_POL_MASK, x)
#define ADAU7118_SPT_SLOT_MASK          GENMASK(7, 4)
#define ADAU7118_SPT_SLOT(x)            FIELD_PREP(ADAU7118_SPT_SLOT_MASK, x)
#define ADAU7118_FULL_SOFT_R_MASK       BIT(1)
#define ADAU7118_FULL_SOFT_R(x)         FIELD_PREP(ADAU7118_FULL_SOFT_R_MASK, x)

struct adau7118_data {
        struct regmap *map;
        struct device *dev;
        struct regulator *iovdd;
        struct regulator *dvdd;
        u32 slot_width;
        u32 slots;
        bool hw_mode;
        bool right_j;
};

/* Input Enable */
static const struct snd_kcontrol_new adau7118_dapm_pdm_control[4] = {
        SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 0, 1, 0),
        SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 1, 1, 0),
        SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 2, 1, 0),
        SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 3, 1, 0),
};

static const struct snd_soc_dapm_widget adau7118_widgets_sw[] = {
        /* Input Enable Switches */
        SND_SOC_DAPM_SWITCH("PDM0", SND_SOC_NOPM, 0, 0,
                            &adau7118_dapm_pdm_control[0]),
        SND_SOC_DAPM_SWITCH("PDM1", SND_SOC_NOPM, 0, 0,
                            &adau7118_dapm_pdm_control[1]),
        SND_SOC_DAPM_SWITCH("PDM2", SND_SOC_NOPM, 0, 0,
                            &adau7118_dapm_pdm_control[2]),
        SND_SOC_DAPM_SWITCH("PDM3", SND_SOC_NOPM, 0, 0,
                            &adau7118_dapm_pdm_control[3]),

        /* PDM Clocks */
        SND_SOC_DAPM_SUPPLY("PDM_CLK0", ADAU7118_REG_ENABLES, 4, 0, NULL, 0),
        SND_SOC_DAPM_SUPPLY("PDM_CLK1", ADAU7118_REG_ENABLES, 5, 0, NULL, 0),

        /* Output channels */
        SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, ADAU7118_REG_SPT_CX(0),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX2", "Capture", 0, ADAU7118_REG_SPT_CX(1),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX3", "Capture", 0, ADAU7118_REG_SPT_CX(2),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX4", "Capture", 0, ADAU7118_REG_SPT_CX(3),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX5", "Capture", 0, ADAU7118_REG_SPT_CX(4),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX6", "Capture", 0, ADAU7118_REG_SPT_CX(5),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX7", "Capture", 0, ADAU7118_REG_SPT_CX(6),
                             0, 0),
        SND_SOC_DAPM_AIF_OUT("AIF1TX8", "Capture", 0, ADAU7118_REG_SPT_CX(7),
                             0, 0),
};

static const struct snd_soc_dapm_route adau7118_routes_sw[] = {
        { "PDM0", "Capture Switch", "PDM_DAT0" },
        { "PDM1", "Capture Switch", "PDM_DAT1" },
        { "PDM2", "Capture Switch", "PDM_DAT2" },
        { "PDM3", "Capture Switch", "PDM_DAT3" },
        { "AIF1TX1", NULL, "PDM0" },
        { "AIF1TX2", NULL, "PDM0" },
        { "AIF1TX3", NULL, "PDM1" },
        { "AIF1TX4", NULL, "PDM1" },
        { "AIF1TX5", NULL, "PDM2" },
        { "AIF1TX6", NULL, "PDM2" },
        { "AIF1TX7", NULL, "PDM3" },
        { "AIF1TX8", NULL, "PDM3" },
        { "Capture", NULL, "PDM_CLK0" },
        { "Capture", NULL, "PDM_CLK1" },
};

static const struct snd_soc_dapm_widget adau7118_widgets_hw[] = {
        SND_SOC_DAPM_AIF_OUT("AIF1TX", "Capture", 0, SND_SOC_NOPM, 0, 0),
};

static const struct snd_soc_dapm_route adau7118_routes_hw[] = {
        { "AIF1TX", NULL, "PDM_DAT0" },
        { "AIF1TX", NULL, "PDM_DAT1" },
        { "AIF1TX", NULL, "PDM_DAT2" },
        { "AIF1TX", NULL, "PDM_DAT3" },
};

static const struct snd_soc_dapm_widget adau7118_widgets[] = {
        SND_SOC_DAPM_INPUT("PDM_DAT0"),
        SND_SOC_DAPM_INPUT("PDM_DAT1"),
        SND_SOC_DAPM_INPUT("PDM_DAT2"),
        SND_SOC_DAPM_INPUT("PDM_DAT3"),
};

static int adau7118_set_channel_map(struct snd_soc_dai *dai,
                                    unsigned int tx_num,
                                    const unsigned int *tx_slot,
                                    unsigned int rx_num,
                                    const unsigned int *rx_slot)
{
        struct adau7118_data *st =
                snd_soc_component_get_drvdata(dai->component);
        int chan, ret;

        dev_dbg(st->dev, "Set channel map, %d", tx_num);

        for (chan = 0; chan < tx_num; chan++) {
                ret = snd_soc_component_update_bits(dai->component,
                                        ADAU7118_REG_SPT_CX(chan),
                                        ADAU7118_SPT_SLOT_MASK,
                                        ADAU7118_SPT_SLOT(tx_slot[chan]));
                if (ret < 0)
                        return ret;
        }

        return 0;
}

static int adau7118_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
        struct adau7118_data *st =
                snd_soc_component_get_drvdata(dai->component);
        int ret = 0;
        u32 regval;

        dev_dbg(st->dev, "Set format, fmt:%d\n", fmt);

        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_I2S:
                ret = snd_soc_component_update_bits(dai->component,
                                                    ADAU7118_REG_SPT_CTRL1,
                                                    ADAU7118_DATA_FMT_MASK,
                                                    ADAU7118_DATA_FMT(0));
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                ret = snd_soc_component_update_bits(dai->component,
                                                    ADAU7118_REG_SPT_CTRL1,
                                                    ADAU7118_DATA_FMT_MASK,
                                                    ADAU7118_DATA_FMT(1));
                break;
        case SND_SOC_DAIFMT_RIGHT_J:
                st->right_j = true;
                break;
        case SND_SOC_DAIFMT_DSP_A:
                ret = snd_soc_component_update_bits(dai->component,
                                                    ADAU7118_REG_SPT_CTRL1,
                                                    ADAU7118_DATA_FMT_MASK,
                                                    ADAU7118_DATA_FMT(1));
                break;
        default:
                dev_err(st->dev, "Invalid format %d",
                        fmt & SND_SOC_DAIFMT_FORMAT_MASK);
                return -EINVAL;
        }

        if (ret < 0)
                return ret;

        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
        case SND_SOC_DAIFMT_NB_NF:
                regval = ADAU7118_LRCLK_BCLK_POL(0);
                break;
        case SND_SOC_DAIFMT_NB_IF:
                regval = ADAU7118_LRCLK_BCLK_POL(2);
                break;
        case SND_SOC_DAIFMT_IB_NF:
                regval = ADAU7118_LRCLK_BCLK_POL(1);
                break;
        case SND_SOC_DAIFMT_IB_IF:
                regval = ADAU7118_LRCLK_BCLK_POL(3);
                break;
        default:
                dev_err(st->dev, "Invalid Inv mask %d",
                        fmt & SND_SOC_DAIFMT_INV_MASK);
                return -EINVAL;
        }

        ret = snd_soc_component_update_bits(dai->component,
                                            ADAU7118_REG_SPT_CTRL2,
                                            ADAU7118_LRCLK_BCLK_POL_MASK,
                                            regval);
        if (ret < 0)
                return ret;

        return 0;
}

static int adau7118_set_tristate(struct snd_soc_dai *dai, int tristate)
{
        struct adau7118_data *st =
                snd_soc_component_get_drvdata(dai->component);
        int ret;

        dev_dbg(st->dev, "Set tristate, %d\n", tristate);

        ret = snd_soc_component_update_bits(dai->component,
                                            ADAU7118_REG_SPT_CTRL1,
                                            ADAU7118_TRISTATE_MASK,
                                            ADAU7118_TRISTATE(tristate));
        if (ret < 0)
                return ret;

        return 0;
}

static int adau7118_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
                                 unsigned int rx_mask, int slots,
                                 int slot_width)
{
        struct adau7118_data *st =
                snd_soc_component_get_drvdata(dai->component);
        int ret = 0;
        u32 regval;

        dev_dbg(st->dev, "Set tdm, slots:%d width:%d\n", slots, slot_width);

        switch (slot_width) {
        case 32:
                regval = ADAU7118_SLOT_WIDTH(0);
                break;
        case 24:
                regval = ADAU7118_SLOT_WIDTH(2);
                break;
        case 16:
                regval = ADAU7118_SLOT_WIDTH(1);
                break;
        default:
                dev_err(st->dev, "Invalid slot width:%d\n", slot_width);
                return -EINVAL;
        }

        ret = snd_soc_component_update_bits(dai->component,
                                            ADAU7118_REG_SPT_CTRL1,
                                            ADAU7118_SLOT_WIDTH_MASK, regval);
        if (ret < 0)
                return ret;

        st->slot_width = slot_width;
        st->slots = slots;

        return 0;
}

static int adau7118_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params,
                              struct snd_soc_dai *dai)
{
        struct adau7118_data *st =
                snd_soc_component_get_drvdata(dai->component);
        u32 data_width = params_width(params), slots_width;
        int ret;
        u32 regval;

        if (!st->slots) {
                /* set stereo mode */
                ret = snd_soc_component_update_bits(dai->component,
                                                    ADAU7118_REG_SPT_CTRL1,
                                                    ADAU7118_SAI_MODE_MASK,
                                                    ADAU7118_SAI_MODE(0));
                if (ret < 0)
                        return ret;

                slots_width = 32;
        } else {
                slots_width = st->slot_width;
        }

        if (data_width > slots_width) {
                dev_err(st->dev, "Invalid data_width:%d, slots_width:%d",
                        data_width, slots_width);
                return -EINVAL;
        }

        if (st->right_j) {
                switch (slots_width - data_width) {
                case 8:
                        /* delay bclck by 8 */
                        regval = ADAU7118_DATA_FMT(2);
                        break;
                case 12:
                        /* delay bclck by 12 */
                        regval = ADAU7118_DATA_FMT(3);
                        break;
                case 16:
                        /* delay bclck by 16 */
                        regval = ADAU7118_DATA_FMT(4);
                        break;
                default:
                        dev_err(st->dev,
                                "Cannot set right_j setting, slot_w:%d, data_w:%d\n",
                                        slots_width, data_width);
                        return -EINVAL;
                }

                ret = snd_soc_component_update_bits(dai->component,
                                                    ADAU7118_REG_SPT_CTRL1,
                                                    ADAU7118_DATA_FMT_MASK,
                                                    regval);
                if (ret < 0)
                        return ret;
        }

        return 0;
}

static int adau7118_set_bias_level(struct snd_soc_component *component,
                                   enum snd_soc_bias_level level)
{
        struct adau7118_data *st = snd_soc_component_get_drvdata(component);
        struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
        int ret = 0;

        dev_dbg(st->dev, "Set bias level %d\n", level);

        switch (level) {
        case SND_SOC_BIAS_ON:
        case SND_SOC_BIAS_PREPARE:
                break;

        case SND_SOC_BIAS_STANDBY:
                if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_OFF) {
                        /* power on */
                        ret = regulator_enable(st->iovdd);
                        if (ret)
                                return ret;

                        /* there's no timing constraints before enabling dvdd */
                        ret = regulator_enable(st->dvdd);
                        if (ret) {
                                regulator_disable(st->iovdd);
                                return ret;
                        }

                        if (st->hw_mode)
                                return 0;

                        regcache_cache_only(st->map, false);
                        /* sync cache */
                        ret = snd_soc_component_cache_sync(component);
                }
                break;
        case SND_SOC_BIAS_OFF:
                /* power off */
                ret = regulator_disable(st->dvdd);
                if (ret)
                        return ret;

                ret = regulator_disable(st->iovdd);
                if (ret)
                        return ret;

                if (st->hw_mode)
                        return 0;

                /* cache only */
                regcache_mark_dirty(st->map);
                regcache_cache_only(st->map, true);

                break;
        }

        return ret;
}

static int adau7118_component_probe(struct snd_soc_component *component)
{
        struct adau7118_data *st = snd_soc_component_get_drvdata(component);
        struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
        int ret = 0;

        if (st->hw_mode) {
                ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_hw,
                                        ARRAY_SIZE(adau7118_widgets_hw));
                if (ret)
                        return ret;

                ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_hw,
                                              ARRAY_SIZE(adau7118_routes_hw));
        } else {
                snd_soc_component_init_regmap(component, st->map);
                ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_sw,
                                        ARRAY_SIZE(adau7118_widgets_sw));
                if (ret)
                        return ret;

                ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_sw,
                                              ARRAY_SIZE(adau7118_routes_sw));
        }

        return ret;
}

static const struct snd_soc_dai_ops adau7118_ops = {
        .hw_params = adau7118_hw_params,
        .set_channel_map = adau7118_set_channel_map,
        .set_fmt = adau7118_set_fmt,
        .set_tdm_slot = adau7118_set_tdm_slot,
        .set_tristate = adau7118_set_tristate,
};

static struct snd_soc_dai_driver adau7118_dai = {
        .name = "adau7118-hifi-capture",
        .capture = {
                .stream_name = "Capture",
                .channels_min = 1,
                .channels_max = 8,
                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
                        SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_S24_LE |
                        SNDRV_PCM_FMTBIT_S24_3LE,
                .rates = SNDRV_PCM_RATE_CONTINUOUS,
                .rate_min = 4000,
                .rate_max = 192000,
                .sig_bits = 24,
        },
};

static const struct snd_soc_component_driver adau7118_component_driver = {
        .probe                  = adau7118_component_probe,
        .set_bias_level         = adau7118_set_bias_level,
        .dapm_widgets           = adau7118_widgets,
        .num_dapm_widgets       = ARRAY_SIZE(adau7118_widgets),
        .use_pmdown_time        = 1,
        .endianness             = 1,
};

static int adau7118_regulator_setup(struct adau7118_data *st)
{
        st->iovdd = devm_regulator_get(st->dev, "iovdd");
        if (IS_ERR(st->iovdd)) {
                dev_err(st->dev, "Could not get iovdd: %ld\n",
                        PTR_ERR(st->iovdd));
                return PTR_ERR(st->iovdd);
        }

        st->dvdd = devm_regulator_get(st->dev, "dvdd");
        if (IS_ERR(st->dvdd)) {
                dev_err(st->dev, "Could not get dvdd: %ld\n",
                        PTR_ERR(st->dvdd));
                return PTR_ERR(st->dvdd);
        }
        /* just assume the device is in reset */
        if (!st->hw_mode) {
                regcache_mark_dirty(st->map);
                regcache_cache_only(st->map, true);
        }

        return 0;
}

static int adau7118_parset_dt(const struct adau7118_data *st)
{
        int ret;
        u32 dec_ratio = 0;
        /* 4 inputs */
        u32 clk_map[4], regval;

        if (st->hw_mode)
                return 0;

        ret = device_property_read_u32(st->dev, "adi,decimation-ratio",
                                       &dec_ratio);
        if (!ret) {
                switch (dec_ratio) {
                case 64:
                        regval = ADAU7118_DEC_RATIO(0);
                        break;
                case 32:
                        regval = ADAU7118_DEC_RATIO(1);
                        break;
                case 16:
                        regval = ADAU7118_DEC_RATIO(2);
                        break;
                default:
                        dev_err(st->dev, "Invalid dec ratio: %u", dec_ratio);
                        return -EINVAL;
                }

                ret = regmap_update_bits(st->map,
                                         ADAU7118_REG_DEC_RATIO_CLK_MAP,
                                         ADAU7118_DEC_RATIO_MASK, regval);
                if (ret)
                        return ret;
        }

        ret = device_property_read_u32_array(st->dev, "adi,pdm-clk-map",
                                             clk_map, ARRAY_SIZE(clk_map));
        if (!ret) {
                int pdm;
                u32 _clk_map = 0;

                for (pdm = 0; pdm < ARRAY_SIZE(clk_map); pdm++)
                        _clk_map |= (clk_map[pdm] << (pdm + 4));

                ret = regmap_update_bits(st->map,
                                         ADAU7118_REG_DEC_RATIO_CLK_MAP,
                                         ADAU7118_CLK_MAP_MASK, _clk_map);
                if (ret)
                        return ret;
        }

        return 0;
}

int adau7118_probe(struct device *dev, struct regmap *map, bool hw_mode)
{
        struct adau7118_data *st;
        int ret;

        st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
        if (!st)
                return -ENOMEM;

        st->dev = dev;
        st->hw_mode = hw_mode;
        dev_set_drvdata(dev, st);

        if (!hw_mode) {
                st->map = map;
                adau7118_dai.ops = &adau7118_ops;
                /*
                 * Perform a full soft reset. This will set all register's
                 * with their reset values.
                 */
                ret = regmap_update_bits(map, ADAU7118_REG_RESET,
                                         ADAU7118_FULL_SOFT_R_MASK,
                                         ADAU7118_FULL_SOFT_R(1));
                if (ret)
                        return ret;
        }

        ret = adau7118_parset_dt(st);
        if (ret)
                return ret;

        ret = adau7118_regulator_setup(st);
        if (ret)
                return ret;

        return devm_snd_soc_register_component(dev,
                                               &adau7118_component_driver,
                                               &adau7118_dai, 1);
}
EXPORT_SYMBOL_GPL(adau7118_probe);

MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
MODULE_DESCRIPTION("ADAU7118 8 channel PDM-to-I2S/TDM Converter driver");
MODULE_LICENSE("GPL");