123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- // SPDX-License-Identifier: GPL-2.0
- // Audio driver for PCM1789
- // Copyright (C) 2018 Bootlin
- // Mylène Josserand <[email protected]>
- #include <linux/gpio/consumer.h>
- #include <linux/module.h>
- #include <linux/workqueue.h>
- #include <sound/pcm_params.h>
- #include <sound/soc.h>
- #include <sound/tlv.h>
- #include "pcm1789.h"
- #define PCM1789_MUTE_CONTROL 0x10
- #define PCM1789_FMT_CONTROL 0x11
- #define PCM1789_SOFT_MUTE 0x14
- #define PCM1789_DAC_VOL_LEFT 0x18
- #define PCM1789_DAC_VOL_RIGHT 0x19
- #define PCM1789_FMT_MASK 0x07
- #define PCM1789_MUTE_MASK 0x03
- #define PCM1789_MUTE_SRET 0x06
- struct pcm1789_private {
- struct regmap *regmap;
- unsigned int format;
- unsigned int rate;
- struct gpio_desc *reset;
- struct work_struct work;
- struct device *dev;
- };
- static const struct reg_default pcm1789_reg_defaults[] = {
- { PCM1789_FMT_CONTROL, 0x00 },
- { PCM1789_SOFT_MUTE, 0x00 },
- { PCM1789_DAC_VOL_LEFT, 0xff },
- { PCM1789_DAC_VOL_RIGHT, 0xff },
- };
- static bool pcm1789_accessible_reg(struct device *dev, unsigned int reg)
- {
- return reg >= PCM1789_MUTE_CONTROL && reg <= PCM1789_DAC_VOL_RIGHT;
- }
- static bool pcm1789_writeable_reg(struct device *dev, unsigned int reg)
- {
- return pcm1789_accessible_reg(dev, reg);
- }
- static int pcm1789_set_dai_fmt(struct snd_soc_dai *codec_dai,
- unsigned int format)
- {
- struct snd_soc_component *component = codec_dai->component;
- struct pcm1789_private *priv = snd_soc_component_get_drvdata(component);
- priv->format = format;
- return 0;
- }
- static int pcm1789_mute(struct snd_soc_dai *codec_dai, int mute, int direction)
- {
- struct snd_soc_component *component = codec_dai->component;
- struct pcm1789_private *priv = snd_soc_component_get_drvdata(component);
- return regmap_update_bits(priv->regmap, PCM1789_SOFT_MUTE,
- PCM1789_MUTE_MASK,
- mute ? 0 : PCM1789_MUTE_MASK);
- }
- static int pcm1789_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *codec_dai)
- {
- struct snd_soc_component *component = codec_dai->component;
- struct pcm1789_private *priv = snd_soc_component_get_drvdata(component);
- int val = 0, ret;
- priv->rate = params_rate(params);
- switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_RIGHT_J:
- switch (params_width(params)) {
- case 24:
- val = 2;
- break;
- case 16:
- val = 3;
- break;
- default:
- return -EINVAL;
- }
- break;
- case SND_SOC_DAIFMT_I2S:
- switch (params_width(params)) {
- case 16:
- case 24:
- case 32:
- val = 0;
- break;
- default:
- return -EINVAL;
- }
- break;
- case SND_SOC_DAIFMT_LEFT_J:
- switch (params_width(params)) {
- case 16:
- case 24:
- case 32:
- val = 1;
- break;
- default:
- return -EINVAL;
- }
- break;
- default:
- dev_err(component->dev, "Invalid DAI format\n");
- return -EINVAL;
- }
- ret = regmap_update_bits(priv->regmap, PCM1789_FMT_CONTROL,
- PCM1789_FMT_MASK, val);
- if (ret < 0)
- return ret;
- return 0;
- }
- static void pcm1789_work_queue(struct work_struct *work)
- {
- struct pcm1789_private *priv = container_of(work,
- struct pcm1789_private,
- work);
- /* Perform a software reset to remove codec from desynchronized state */
- if (regmap_update_bits(priv->regmap, PCM1789_MUTE_CONTROL,
- 0x3 << PCM1789_MUTE_SRET, 0) < 0)
- dev_err(priv->dev, "Error while setting SRET");
- }
- static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
- {
- struct snd_soc_component *component = dai->component;
- struct pcm1789_private *priv = snd_soc_component_get_drvdata(component);
- int ret = 0;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- schedule_work(&priv->work);
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- default:
- ret = -EINVAL;
- }
- return ret;
- }
- static const struct snd_soc_dai_ops pcm1789_dai_ops = {
- .set_fmt = pcm1789_set_dai_fmt,
- .hw_params = pcm1789_hw_params,
- .mute_stream = pcm1789_mute,
- .trigger = pcm1789_trigger,
- .no_capture_mute = 1,
- };
- static const DECLARE_TLV_DB_SCALE(pcm1789_dac_tlv, -12000, 50, 1);
- static const struct snd_kcontrol_new pcm1789_controls[] = {
- SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM1789_DAC_VOL_LEFT,
- PCM1789_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0,
- pcm1789_dac_tlv),
- };
- static const struct snd_soc_dapm_widget pcm1789_dapm_widgets[] = {
- SND_SOC_DAPM_OUTPUT("IOUTL+"),
- SND_SOC_DAPM_OUTPUT("IOUTL-"),
- SND_SOC_DAPM_OUTPUT("IOUTR+"),
- SND_SOC_DAPM_OUTPUT("IOUTR-"),
- };
- static const struct snd_soc_dapm_route pcm1789_dapm_routes[] = {
- { "IOUTL+", NULL, "Playback" },
- { "IOUTL-", NULL, "Playback" },
- { "IOUTR+", NULL, "Playback" },
- { "IOUTR-", NULL, "Playback" },
- };
- static struct snd_soc_dai_driver pcm1789_dai = {
- .name = "pcm1789-hifi",
- .playback = {
- .stream_name = "Playback",
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_CONTINUOUS,
- .rate_min = 10000,
- .rate_max = 200000,
- .formats = PCM1789_FORMATS,
- },
- .ops = &pcm1789_dai_ops,
- };
- const struct regmap_config pcm1789_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
- .max_register = PCM1789_DAC_VOL_RIGHT,
- .reg_defaults = pcm1789_reg_defaults,
- .num_reg_defaults = ARRAY_SIZE(pcm1789_reg_defaults),
- .writeable_reg = pcm1789_writeable_reg,
- .readable_reg = pcm1789_accessible_reg,
- };
- EXPORT_SYMBOL_GPL(pcm1789_regmap_config);
- static const struct snd_soc_component_driver soc_component_dev_pcm1789 = {
- .controls = pcm1789_controls,
- .num_controls = ARRAY_SIZE(pcm1789_controls),
- .dapm_widgets = pcm1789_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(pcm1789_dapm_widgets),
- .dapm_routes = pcm1789_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(pcm1789_dapm_routes),
- .idle_bias_on = 1,
- .use_pmdown_time = 1,
- .endianness = 1,
- };
- int pcm1789_common_init(struct device *dev, struct regmap *regmap)
- {
- struct pcm1789_private *pcm1789;
- pcm1789 = devm_kzalloc(dev, sizeof(struct pcm1789_private),
- GFP_KERNEL);
- if (!pcm1789)
- return -ENOMEM;
- pcm1789->regmap = regmap;
- pcm1789->dev = dev;
- dev_set_drvdata(dev, pcm1789);
- pcm1789->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
- if (IS_ERR(pcm1789->reset))
- return PTR_ERR(pcm1789->reset);
- gpiod_set_value_cansleep(pcm1789->reset, 0);
- msleep(300);
- INIT_WORK(&pcm1789->work, pcm1789_work_queue);
- return devm_snd_soc_register_component(dev, &soc_component_dev_pcm1789,
- &pcm1789_dai, 1);
- }
- EXPORT_SYMBOL_GPL(pcm1789_common_init);
- void pcm1789_common_exit(struct device *dev)
- {
- struct pcm1789_private *priv = dev_get_drvdata(dev);
- flush_work(&priv->work);
- }
- EXPORT_SYMBOL_GPL(pcm1789_common_exit);
- MODULE_DESCRIPTION("ASoC PCM1789 driver");
- MODULE_AUTHOR("Mylène Josserand <[email protected]>");
- MODULE_LICENSE("GPL");
|