root/sound/soc/img/pistachio-internal-dac.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Pistachio internal dac driver
 *
 * Copyright (C) 2015 Imagination Technologies Ltd.
 *
 * Author: Damien Horsley <Damien.Horsley@imgtec.com>
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>

#include <sound/pcm_params.h>
#include <sound/soc.h>

#define PISTACHIO_INTERNAL_DAC_CTRL                     0x40
#define PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK        0x2
#define PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK          0x1

#define PISTACHIO_INTERNAL_DAC_SRST                     0x44
#define PISTACHIO_INTERNAL_DAC_SRST_MASK                0x1

#define PISTACHIO_INTERNAL_DAC_GTI_CTRL                 0x48
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT      0
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK       0xFFF
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK         0x1000
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT     13
#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK      0x1FE000

#define PISTACHIO_INTERNAL_DAC_PWR                      0x1
#define PISTACHIO_INTERNAL_DAC_PWR_MASK                 0x1

#define PISTACHIO_INTERNAL_DAC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE |  \
                                        SNDRV_PCM_FMTBIT_S32_LE)

/* codec private data */
struct pistachio_internal_dac {
        struct regmap *regmap;
        struct regulator *supply;
        bool mute;
};

static const struct snd_kcontrol_new pistachio_internal_dac_snd_controls[] = {
        SOC_SINGLE("Playback Switch", PISTACHIO_INTERNAL_DAC_CTRL, 2, 1, 1)
};

static const struct snd_soc_dapm_widget pistachio_internal_dac_widgets[] = {
        SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_OUTPUT("AOUTL"),
        SND_SOC_DAPM_OUTPUT("AOUTR"),
};

static const struct snd_soc_dapm_route pistachio_internal_dac_routes[] = {
        { "AOUTL", NULL, "DAC" },
        { "AOUTR", NULL, "DAC" },
};

static void pistachio_internal_dac_reg_writel(struct regmap *top_regs,
                                                u32 val, u32 reg)
{
        regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
                        PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK,
                        reg << PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT);

        regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
                        PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK,
                        val << PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT);

        regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
                        PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK,
                        PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK);

        regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL,
                        PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, 0);
}

static void pistachio_internal_dac_pwr_off(struct pistachio_internal_dac *dac)
{
        regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
                PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK,
                PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK);

        pistachio_internal_dac_reg_writel(dac->regmap, 0,
                                        PISTACHIO_INTERNAL_DAC_PWR);
}

static void pistachio_internal_dac_pwr_on(struct pistachio_internal_dac *dac)
{
        regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST,
                        PISTACHIO_INTERNAL_DAC_SRST_MASK,
                        PISTACHIO_INTERNAL_DAC_SRST_MASK);

        regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST,
                        PISTACHIO_INTERNAL_DAC_SRST_MASK, 0);

        pistachio_internal_dac_reg_writel(dac->regmap,
                                        PISTACHIO_INTERNAL_DAC_PWR_MASK,
                                        PISTACHIO_INTERNAL_DAC_PWR);

        regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
                        PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, 0);
}

static struct snd_soc_dai_driver pistachio_internal_dac_dais[] = {
        {
                .name = "pistachio_internal_dac",
                .playback = {
                        .stream_name = "Playback",
                        .channels_min = 2,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_8000_48000,
                        .formats = PISTACHIO_INTERNAL_DAC_FORMATS,
                }
        },
};

static int pistachio_internal_dac_codec_probe(struct snd_soc_component *component)
{
        struct pistachio_internal_dac *dac = snd_soc_component_get_drvdata(component);

        snd_soc_component_init_regmap(component, dac->regmap);

        return 0;
}

static const struct snd_soc_component_driver pistachio_internal_dac_driver = {
        .probe                  = pistachio_internal_dac_codec_probe,
        .controls               = pistachio_internal_dac_snd_controls,
        .num_controls           = ARRAY_SIZE(pistachio_internal_dac_snd_controls),
        .dapm_widgets           = pistachio_internal_dac_widgets,
        .num_dapm_widgets       = ARRAY_SIZE(pistachio_internal_dac_widgets),
        .dapm_routes            = pistachio_internal_dac_routes,
        .num_dapm_routes        = ARRAY_SIZE(pistachio_internal_dac_routes),
        .use_pmdown_time        = 1,
        .endianness             = 1,
};

static int pistachio_internal_dac_probe(struct platform_device *pdev)
{
        struct pistachio_internal_dac *dac;
        int ret, voltage;
        struct device *dev = &pdev->dev;
        u32 reg;

        dac = devm_kzalloc(dev, sizeof(*dac), GFP_KERNEL);

        if (!dac)
                return -ENOMEM;

        platform_set_drvdata(pdev, dac);

        dac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
                                                            "img,cr-top");
        if (IS_ERR(dac->regmap))
                return PTR_ERR(dac->regmap);

        dac->supply = devm_regulator_get(dev, "VDD");
        if (IS_ERR(dac->supply))
                return dev_err_probe(dev, PTR_ERR(dac->supply),
                                     "failed to acquire supply 'VDD-supply'\n");

        ret = regulator_enable(dac->supply);
        if (ret) {
                dev_err(dev, "failed to enable supply: %d\n", ret);
                return ret;
        }

        voltage = regulator_get_voltage(dac->supply);

        switch (voltage) {
        case 1800000:
                reg = 0;
                break;
        case 3300000:
                reg = PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK;
                break;
        default:
                dev_err(dev, "invalid voltage: %d\n", voltage);
                ret = -EINVAL;
                goto err_regulator;
        }

        regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL,
                        PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK, reg);

        pistachio_internal_dac_pwr_off(dac);
        pistachio_internal_dac_pwr_on(dac);

        pm_runtime_set_active(dev);
        pm_runtime_enable(dev);
        pm_runtime_idle(dev);

        ret = devm_snd_soc_register_component(dev,
                        &pistachio_internal_dac_driver,
                        pistachio_internal_dac_dais,
                        ARRAY_SIZE(pistachio_internal_dac_dais));
        if (ret) {
                dev_err(dev, "failed to register component: %d\n", ret);
                goto err_pwr;
        }

        return 0;

err_pwr:
        pm_runtime_disable(&pdev->dev);
        pistachio_internal_dac_pwr_off(dac);
err_regulator:
        regulator_disable(dac->supply);

        return ret;
}

static void pistachio_internal_dac_remove(struct platform_device *pdev)
{
        struct pistachio_internal_dac *dac = dev_get_drvdata(&pdev->dev);

        pm_runtime_disable(&pdev->dev);
        pistachio_internal_dac_pwr_off(dac);
        regulator_disable(dac->supply);
}

static int pistachio_internal_dac_rt_resume(struct device *dev)
{
        struct pistachio_internal_dac *dac = dev_get_drvdata(dev);
        int ret;

        ret = regulator_enable(dac->supply);
        if (ret) {
                dev_err(dev, "failed to enable supply: %d\n", ret);
                return ret;
        }

        pistachio_internal_dac_pwr_on(dac);

        return 0;
}

static int pistachio_internal_dac_rt_suspend(struct device *dev)
{
        struct pistachio_internal_dac *dac = dev_get_drvdata(dev);

        pistachio_internal_dac_pwr_off(dac);

        regulator_disable(dac->supply);

        return 0;
}

static const struct dev_pm_ops pistachio_internal_dac_pm_ops = {
        RUNTIME_PM_OPS(pistachio_internal_dac_rt_suspend,
                       pistachio_internal_dac_rt_resume, NULL)
};

static const struct of_device_id pistachio_internal_dac_of_match[] = {
        { .compatible = "img,pistachio-internal-dac" },
        {}
};
MODULE_DEVICE_TABLE(of, pistachio_internal_dac_of_match);

static struct platform_driver pistachio_internal_dac_plat_driver = {
        .driver = {
                .name = "img-pistachio-internal-dac",
                .of_match_table = pistachio_internal_dac_of_match,
                .pm = pm_ptr(&pistachio_internal_dac_pm_ops)
        },
        .probe = pistachio_internal_dac_probe,
        .remove = pistachio_internal_dac_remove
};
module_platform_driver(pistachio_internal_dac_plat_driver);

MODULE_DESCRIPTION("Pistachio Internal DAC driver");
MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
MODULE_LICENSE("GPL v2");