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 37 static 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 48 static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg) 49 { 50 return reg >= 0x10 && reg <= 0x17; 51 } 52 53 static 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 62 struct pcm179x_private { 63 struct regmap *regmap; 64 unsigned int format; 65 unsigned int rate; 66 }; 67 68 static 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 79 static int pcm179x_mute(struct snd_soc_dai *dai, int mute, int direction) 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 93 static 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 145 static const struct snd_soc_dai_ops pcm179x_dai_ops = { 146 .set_fmt = pcm179x_set_dai_fmt, 147 .hw_params = pcm179x_hw_params, 148 .mute_stream = pcm179x_mute, 149 .no_capture_mute = 1, 150 }; 151 152 static const DECLARE_TLV_DB_SCALE(pcm179x_dac_tlv, -12000, 50, 1); 153 154 static const struct snd_kcontrol_new pcm179x_controls[] = { 155 SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM179X_DAC_VOL_LEFT, 156 PCM179X_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, 157 pcm179x_dac_tlv), 158 SOC_SINGLE("DAC Invert Output Switch", PCM179X_MODE_CONTROL, 7, 1, 0), 159 SOC_SINGLE("DAC Rolloff Filter Switch", PCM179X_MODE_CONTROL, 1, 1, 0), 160 }; 161 162 static const struct snd_soc_dapm_widget pcm179x_dapm_widgets[] = { 163 SND_SOC_DAPM_OUTPUT("IOUTL+"), 164 SND_SOC_DAPM_OUTPUT("IOUTL-"), 165 SND_SOC_DAPM_OUTPUT("IOUTR+"), 166 SND_SOC_DAPM_OUTPUT("IOUTR-"), 167 }; 168 169 static const struct snd_soc_dapm_route pcm179x_dapm_routes[] = { 170 { "IOUTL+", NULL, "Playback" }, 171 { "IOUTL-", NULL, "Playback" }, 172 { "IOUTR+", NULL, "Playback" }, 173 { "IOUTR-", NULL, "Playback" }, 174 }; 175 176 static struct snd_soc_dai_driver pcm179x_dai = { 177 .name = "pcm179x-hifi", 178 .playback = { 179 .stream_name = "Playback", 180 .channels_min = 2, 181 .channels_max = 2, 182 .rates = SNDRV_PCM_RATE_CONTINUOUS, 183 .rate_min = 10000, 184 .rate_max = 200000, 185 .formats = PCM1792A_FORMATS, }, 186 .ops = &pcm179x_dai_ops, 187 }; 188 189 const struct regmap_config pcm179x_regmap_config = { 190 .reg_bits = 8, 191 .val_bits = 8, 192 .max_register = 23, 193 .reg_defaults = pcm179x_reg_defaults, 194 .num_reg_defaults = ARRAY_SIZE(pcm179x_reg_defaults), 195 .writeable_reg = pcm179x_writeable_reg, 196 .readable_reg = pcm179x_accessible_reg, 197 }; 198 EXPORT_SYMBOL_GPL(pcm179x_regmap_config); 199 200 static const struct snd_soc_component_driver soc_component_dev_pcm179x = { 201 .controls = pcm179x_controls, 202 .num_controls = ARRAY_SIZE(pcm179x_controls), 203 .dapm_widgets = pcm179x_dapm_widgets, 204 .num_dapm_widgets = ARRAY_SIZE(pcm179x_dapm_widgets), 205 .dapm_routes = pcm179x_dapm_routes, 206 .num_dapm_routes = ARRAY_SIZE(pcm179x_dapm_routes), 207 .idle_bias_on = 1, 208 .use_pmdown_time = 1, 209 .endianness = 1, 210 }; 211 212 int 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 } 227 EXPORT_SYMBOL_GPL(pcm179x_common_init); 228 229 MODULE_DESCRIPTION("ASoC PCM179X driver"); 230 MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); 231 MODULE_LICENSE("GPL"); 232