| // 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); |
| } |
| |
| #ifdef CONFIG_PM |
| 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; |
| } |
| #endif |
| |
| static const struct dev_pm_ops pistachio_internal_dac_pm_ops = { |
| SET_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 = &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"); |