0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 #include <linux/clk.h>
0012 #include <linux/module.h>
0013 #include <sound/soc.h>
0014
0015 #include <linux/mfd/lochnagar.h>
0016 #include <linux/mfd/lochnagar1_regs.h>
0017 #include <linux/mfd/lochnagar2_regs.h>
0018
0019 struct lochnagar_sc_priv {
0020 struct clk *mclk;
0021 };
0022
0023 static const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = {
0024 SND_SOC_DAPM_LINE("Line Jack", NULL),
0025 SND_SOC_DAPM_LINE("USB Audio", NULL),
0026 };
0027
0028 static const struct snd_soc_dapm_route lochnagar_sc_routes[] = {
0029 { "Line Jack", NULL, "AIF1 Playback" },
0030 { "AIF1 Capture", NULL, "Line Jack" },
0031
0032 { "USB Audio", NULL, "USB1 Playback" },
0033 { "USB Audio", NULL, "USB2 Playback" },
0034 { "USB1 Capture", NULL, "USB Audio" },
0035 { "USB2 Capture", NULL, "USB Audio" },
0036 };
0037
0038 static const unsigned int lochnagar_sc_chan_vals[] = {
0039 4, 8,
0040 };
0041
0042 static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = {
0043 .count = ARRAY_SIZE(lochnagar_sc_chan_vals),
0044 .list = lochnagar_sc_chan_vals,
0045 };
0046
0047 static const unsigned int lochnagar_sc_rate_vals[] = {
0048 8000, 16000, 24000, 32000, 48000, 96000, 192000,
0049 22050, 44100, 88200, 176400,
0050 };
0051
0052 static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = {
0053 .count = ARRAY_SIZE(lochnagar_sc_rate_vals),
0054 .list = lochnagar_sc_rate_vals,
0055 };
0056
0057 static int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params,
0058 struct snd_pcm_hw_rule *rule)
0059 {
0060 struct snd_interval range = {
0061 .min = 8000,
0062 .max = 24576000 / hw_param_interval(params, rule->deps[0])->max,
0063 };
0064
0065 return snd_interval_refine(hw_param_interval(params, rule->var),
0066 &range);
0067 }
0068
0069 static int lochnagar_sc_startup(struct snd_pcm_substream *substream,
0070 struct snd_soc_dai *dai)
0071 {
0072 struct snd_soc_component *comp = dai->component;
0073 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
0074 int ret;
0075
0076 ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
0077 SNDRV_PCM_HW_PARAM_RATE,
0078 &lochnagar_sc_rate_constraint);
0079 if (ret)
0080 return ret;
0081
0082 return snd_pcm_hw_rule_add(substream->runtime, 0,
0083 SNDRV_PCM_HW_PARAM_RATE,
0084 lochnagar_sc_hw_rule_rate, priv,
0085 SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
0086 }
0087
0088 static int lochnagar_sc_line_startup(struct snd_pcm_substream *substream,
0089 struct snd_soc_dai *dai)
0090 {
0091 struct snd_soc_component *comp = dai->component;
0092 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
0093 int ret;
0094
0095 ret = clk_prepare_enable(priv->mclk);
0096 if (ret < 0) {
0097 dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret);
0098 return ret;
0099 }
0100
0101 ret = lochnagar_sc_startup(substream, dai);
0102 if (ret)
0103 return ret;
0104
0105 return snd_pcm_hw_constraint_list(substream->runtime, 0,
0106 SNDRV_PCM_HW_PARAM_CHANNELS,
0107 &lochnagar_sc_chan_constraint);
0108 }
0109
0110 static void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream,
0111 struct snd_soc_dai *dai)
0112 {
0113 struct snd_soc_component *comp = dai->component;
0114 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
0115
0116 clk_disable_unprepare(priv->mclk);
0117 }
0118
0119 static int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt,
0120 unsigned int tar)
0121 {
0122 tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
0123
0124 if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar)
0125 return -EINVAL;
0126
0127 return 0;
0128 }
0129
0130 static int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt)
0131 {
0132 return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS);
0133 }
0134
0135 static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt)
0136 {
0137 return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM);
0138 }
0139
0140 static const struct snd_soc_dai_ops lochnagar_sc_line_ops = {
0141 .startup = lochnagar_sc_line_startup,
0142 .shutdown = lochnagar_sc_line_shutdown,
0143 .set_fmt = lochnagar_sc_set_line_fmt,
0144 };
0145
0146 static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = {
0147 .startup = lochnagar_sc_startup,
0148 .set_fmt = lochnagar_sc_set_usb_fmt,
0149 };
0150
0151 static struct snd_soc_dai_driver lochnagar_sc_dai[] = {
0152 {
0153 .name = "lochnagar-line",
0154 .playback = {
0155 .stream_name = "AIF1 Playback",
0156 .channels_min = 4,
0157 .channels_max = 8,
0158 .rates = SNDRV_PCM_RATE_KNOT,
0159 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0160 },
0161 .capture = {
0162 .stream_name = "AIF1 Capture",
0163 .channels_min = 4,
0164 .channels_max = 8,
0165 .rates = SNDRV_PCM_RATE_KNOT,
0166 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0167 },
0168 .ops = &lochnagar_sc_line_ops,
0169 .symmetric_rate = true,
0170 .symmetric_sample_bits = true,
0171 },
0172 {
0173 .name = "lochnagar-usb1",
0174 .playback = {
0175 .stream_name = "USB1 Playback",
0176 .channels_min = 1,
0177 .channels_max = 8,
0178 .rates = SNDRV_PCM_RATE_KNOT,
0179 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0180 },
0181 .capture = {
0182 .stream_name = "USB1 Capture",
0183 .channels_min = 1,
0184 .channels_max = 8,
0185 .rates = SNDRV_PCM_RATE_KNOT,
0186 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0187 },
0188 .ops = &lochnagar_sc_usb_ops,
0189 .symmetric_rate = true,
0190 .symmetric_sample_bits = true,
0191 },
0192 {
0193 .name = "lochnagar-usb2",
0194 .playback = {
0195 .stream_name = "USB2 Playback",
0196 .channels_min = 1,
0197 .channels_max = 8,
0198 .rates = SNDRV_PCM_RATE_KNOT,
0199 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0200 },
0201 .capture = {
0202 .stream_name = "USB2 Capture",
0203 .channels_min = 1,
0204 .channels_max = 8,
0205 .rates = SNDRV_PCM_RATE_KNOT,
0206 .formats = SNDRV_PCM_FMTBIT_S32_LE,
0207 },
0208 .ops = &lochnagar_sc_usb_ops,
0209 .symmetric_rate = true,
0210 .symmetric_sample_bits = true,
0211 },
0212 };
0213
0214 static const struct snd_soc_component_driver lochnagar_sc_driver = {
0215 .dapm_widgets = lochnagar_sc_widgets,
0216 .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets),
0217 .dapm_routes = lochnagar_sc_routes,
0218 .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes),
0219
0220 .endianness = 1,
0221 };
0222
0223 static int lochnagar_sc_probe(struct platform_device *pdev)
0224 {
0225 struct lochnagar_sc_priv *priv;
0226 int ret;
0227
0228 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
0229 if (!priv)
0230 return -ENOMEM;
0231
0232 priv->mclk = devm_clk_get(&pdev->dev, "mclk");
0233 if (IS_ERR(priv->mclk)) {
0234 ret = PTR_ERR(priv->mclk);
0235 dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret);
0236 return ret;
0237 }
0238
0239 platform_set_drvdata(pdev, priv);
0240
0241 return devm_snd_soc_register_component(&pdev->dev,
0242 &lochnagar_sc_driver,
0243 lochnagar_sc_dai,
0244 ARRAY_SIZE(lochnagar_sc_dai));
0245 }
0246
0247 static const struct of_device_id lochnagar_of_match[] = {
0248 { .compatible = "cirrus,lochnagar2-soundcard" },
0249 {}
0250 };
0251 MODULE_DEVICE_TABLE(of, lochnagar_of_match);
0252
0253 static struct platform_driver lochnagar_sc_codec_driver = {
0254 .driver = {
0255 .name = "lochnagar-soundcard",
0256 .of_match_table = of_match_ptr(lochnagar_of_match),
0257 },
0258
0259 .probe = lochnagar_sc_probe,
0260 };
0261 module_platform_driver(lochnagar_sc_codec_driver);
0262
0263 MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver");
0264 MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>");
0265 MODULE_LICENSE("GPL v2");
0266 MODULE_ALIAS("platform:lochnagar-soundcard");