linux/sound/soc/codecs/pcm179x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * PCM179X ASoC codec driver
   4 *
   5 * Copyright (c) Amarula Solutions B.V. 2013
   6 *
   7 *     Michael Trimarchi <michael@amarulasolutions.com>
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/slab.h>
  12#include <linux/kernel.h>
  13#include <linux/device.h>
  14
  15#include <sound/core.h>
  16#include <sound/pcm.h>
  17#include <sound/pcm_params.h>
  18#include <sound/initval.h>
  19#include <sound/soc.h>
  20#include <sound/tlv.h>
  21#include <linux/of.h>
  22
  23#include "pcm179x.h"
  24
  25#define PCM179X_DAC_VOL_LEFT    0x10
  26#define PCM179X_DAC_VOL_RIGHT   0x11
  27#define PCM179X_FMT_CONTROL     0x12
  28#define PCM179X_MODE_CONTROL    0x13
  29#define PCM179X_SOFT_MUTE       PCM179X_FMT_CONTROL
  30
  31#define PCM179X_FMT_MASK        0x70
  32#define PCM179X_FMT_SHIFT       4
  33#define PCM179X_MUTE_MASK       0x01
  34#define PCM179X_MUTE_SHIFT      0
  35#define PCM179X_ATLD_ENABLE     (1 << 7)
  36
  37static const struct reg_default pcm179x_reg_defaults[] = {
  38        { 0x10, 0xff },
  39        { 0x11, 0xff },
  40        { 0x12, 0x50 },
  41        { 0x13, 0x00 },
  42        { 0x14, 0x00 },
  43        { 0x15, 0x01 },
  44        { 0x16, 0x00 },
  45        { 0x17, 0x00 },
  46};
  47
  48static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg)
  49{
  50        return reg >= 0x10 && reg <= 0x17;
  51}
  52
  53static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg)
  54{
  55        bool accessible;
  56
  57        accessible = pcm179x_accessible_reg(dev, reg);
  58
  59        return accessible && reg != 0x16 && reg != 0x17;
  60}
  61
  62struct pcm179x_private {
  63        struct regmap *regmap;
  64        unsigned int format;
  65        unsigned int rate;
  66};
  67
  68static int pcm179x_set_dai_fmt(struct snd_soc_dai *codec_dai,
  69                             unsigned int format)
  70{
  71        struct snd_soc_component *component = codec_dai->component;
  72        struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);
  73
  74        priv->format = format;
  75
  76        return 0;
  77}
  78
  79static int pcm179x_digital_mute(struct snd_soc_dai *dai, int mute)
  80{
  81        struct snd_soc_component *component = dai->component;
  82        struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);
  83        int ret;
  84
  85        ret = regmap_update_bits(priv->regmap, PCM179X_SOFT_MUTE,
  86                                 PCM179X_MUTE_MASK, !!mute);
  87        if (ret < 0)
  88                return ret;
  89
  90        return 0;
  91}
  92
  93static int pcm179x_hw_params(struct snd_pcm_substream *substream,
  94                             struct snd_pcm_hw_params *params,
  95                             struct snd_soc_dai *dai)
  96{
  97        struct snd_soc_component *component = dai->component;
  98        struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);
  99        int val = 0, ret;
 100
 101        priv->rate = params_rate(params);
 102
 103        switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
 104        case SND_SOC_DAIFMT_RIGHT_J:
 105                switch (params_width(params)) {
 106                case 24:
 107                case 32:
 108                        val = 2;
 109                        break;
 110                case 16:
 111                        val = 0;
 112                        break;
 113                default:
 114                        return -EINVAL;
 115                }
 116                break;
 117        case SND_SOC_DAIFMT_I2S:
 118                switch (params_width(params)) {
 119                case 24:
 120                case 32:
 121                        val = 5;
 122                        break;
 123                case 16:
 124                        val = 4;
 125                        break;
 126                default:
 127                        return -EINVAL;
 128                }
 129                break;
 130        default:
 131                dev_err(component->dev, "Invalid DAI format\n");
 132                return -EINVAL;
 133        }
 134
 135        val = val << PCM179X_FMT_SHIFT | PCM179X_ATLD_ENABLE;
 136
 137        ret = regmap_update_bits(priv->regmap, PCM179X_FMT_CONTROL,
 138                                 PCM179X_FMT_MASK | PCM179X_ATLD_ENABLE, val);
 139        if (ret < 0)
 140                return ret;
 141
 142        return 0;
 143}
 144
 145static const struct snd_soc_dai_ops pcm179x_dai_ops = {
 146        .set_fmt        = pcm179x_set_dai_fmt,
 147        .hw_params      = pcm179x_hw_params,
 148        .digital_mute   = pcm179x_digital_mute,
 149};
 150
 151static const DECLARE_TLV_DB_SCALE(pcm179x_dac_tlv, -12000, 50, 1);
 152
 153static const struct snd_kcontrol_new pcm179x_controls[] = {
 154        SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM179X_DAC_VOL_LEFT,
 155                         PCM179X_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0,
 156                         pcm179x_dac_tlv),
 157        SOC_SINGLE("DAC Invert Output Switch", PCM179X_MODE_CONTROL, 7, 1, 0),
 158        SOC_SINGLE("DAC Rolloff Filter Switch", PCM179X_MODE_CONTROL, 1, 1, 0),
 159};
 160
 161static const struct snd_soc_dapm_widget pcm179x_dapm_widgets[] = {
 162SND_SOC_DAPM_OUTPUT("IOUTL+"),
 163SND_SOC_DAPM_OUTPUT("IOUTL-"),
 164SND_SOC_DAPM_OUTPUT("IOUTR+"),
 165SND_SOC_DAPM_OUTPUT("IOUTR-"),
 166};
 167
 168static const struct snd_soc_dapm_route pcm179x_dapm_routes[] = {
 169        { "IOUTL+", NULL, "Playback" },
 170        { "IOUTL-", NULL, "Playback" },
 171        { "IOUTR+", NULL, "Playback" },
 172        { "IOUTR-", NULL, "Playback" },
 173};
 174
 175static struct snd_soc_dai_driver pcm179x_dai = {
 176        .name = "pcm179x-hifi",
 177        .playback = {
 178                .stream_name = "Playback",
 179                .channels_min = 2,
 180                .channels_max = 2,
 181                .rates = SNDRV_PCM_RATE_CONTINUOUS,
 182                .rate_min = 10000,
 183                .rate_max = 200000,
 184                .formats = PCM1792A_FORMATS, },
 185        .ops = &pcm179x_dai_ops,
 186};
 187
 188const struct regmap_config pcm179x_regmap_config = {
 189        .reg_bits               = 8,
 190        .val_bits               = 8,
 191        .max_register           = 23,
 192        .reg_defaults           = pcm179x_reg_defaults,
 193        .num_reg_defaults       = ARRAY_SIZE(pcm179x_reg_defaults),
 194        .writeable_reg          = pcm179x_writeable_reg,
 195        .readable_reg           = pcm179x_accessible_reg,
 196};
 197EXPORT_SYMBOL_GPL(pcm179x_regmap_config);
 198
 199static const struct snd_soc_component_driver soc_component_dev_pcm179x = {
 200        .controls               = pcm179x_controls,
 201        .num_controls           = ARRAY_SIZE(pcm179x_controls),
 202        .dapm_widgets           = pcm179x_dapm_widgets,
 203        .num_dapm_widgets       = ARRAY_SIZE(pcm179x_dapm_widgets),
 204        .dapm_routes            = pcm179x_dapm_routes,
 205        .num_dapm_routes        = ARRAY_SIZE(pcm179x_dapm_routes),
 206        .idle_bias_on           = 1,
 207        .use_pmdown_time        = 1,
 208        .endianness             = 1,
 209        .non_legacy_dai_naming  = 1,
 210};
 211
 212int pcm179x_common_init(struct device *dev, struct regmap *regmap)
 213{
 214        struct pcm179x_private *pcm179x;
 215
 216        pcm179x = devm_kzalloc(dev, sizeof(struct pcm179x_private),
 217                                GFP_KERNEL);
 218        if (!pcm179x)
 219                return -ENOMEM;
 220
 221        pcm179x->regmap = regmap;
 222        dev_set_drvdata(dev, pcm179x);
 223
 224        return devm_snd_soc_register_component(dev,
 225                        &soc_component_dev_pcm179x, &pcm179x_dai, 1);
 226}
 227EXPORT_SYMBOL_GPL(pcm179x_common_init);
 228
 229MODULE_DESCRIPTION("ASoC PCM179X driver");
 230MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
 231MODULE_LICENSE("GPL");
 232