1 // SPDX-License-Identifier: GPL-2.0
3 * ASoC Driver for HiFiBerry DAC+ HD
5 * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * version 2 as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/kernel.h>
21 #include <linux/delay.h>
22 #include <linux/module.h>
24 #include <linux/delay.h>
25 #include <linux/gpio.h>
26 #include <linux/gpio/consumer.h>
27 #include <sound/core.h>
28 #include <sound/pcm.h>
29 #include <sound/pcm_params.h>
30 #include <sound/soc.h>
31 #include <linux/i2c.h>
32 #include <linux/clk.h>
34 #include "../codecs/pcm179x.h"
36 #define DEFAULT_RATE 44100
39 struct regmap *regmap;
43 static struct brd_drv_data drvdata;
44 static struct gpio_desc *reset_gpio;
45 static const unsigned int hb_dacplushd_rates[] = {
46 192000, 96000, 48000, 176400, 88200, 44100,
49 static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = {
50 .list = hb_dacplushd_rates,
51 .count = ARRAY_SIZE(hb_dacplushd_rates),
54 static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream)
56 /* constraints for standard sample rates */
57 snd_pcm_hw_constraint_list(substream->runtime, 0,
58 SNDRV_PCM_HW_PARAM_RATE,
59 &hb_dacplushd_constraints);
63 static void snd_rpi_hifiberry_dacplushd_set_sclk(
64 struct snd_soc_component *component,
67 if (!IS_ERR(drvdata.sclk))
68 clk_set_rate(drvdata.sclk, sample_rate);
71 static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd)
73 struct snd_soc_dai_link *dai = rtd->dai_link;
74 struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
76 dai->name = "HiFiBerry DAC+ HD";
77 dai->stream_name = "HiFiBerry DAC+ HD HiFi";
78 dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
79 | SND_SOC_DAIFMT_CBM_CFM;
81 /* allow only fixed 32 clock counts per channel */
82 snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
87 static int snd_rpi_hifiberry_dacplushd_hw_params(
88 struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
91 struct snd_soc_pcm_runtime *rtd = substream->private_data;
93 struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
95 snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params));
99 /* machine stream operations */
100 static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = {
101 .startup = snd_rpi_hb_dacplushd_startup,
102 .hw_params = snd_rpi_hifiberry_dacplushd_hw_params,
105 SND_SOC_DAILINK_DEFS(hifi,
106 DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
107 DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")),
108 DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
111 static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = {
113 .name = "HiFiBerry DAC+ HD",
114 .stream_name = "HiFiBerry DAC+ HD HiFi",
115 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
116 SND_SOC_DAIFMT_CBS_CFS,
117 .ops = &snd_rpi_hifiberry_dacplushd_ops,
118 .init = snd_rpi_hifiberry_dacplushd_init,
119 SND_SOC_DAILINK_REG(hifi),
123 /* audio machine driver */
124 static struct snd_soc_card snd_rpi_hifiberry_dacplushd = {
125 .name = "snd_rpi_hifiberry_dacplushd",
126 .driver_name = "HifiberryDacplusHD",
127 .owner = THIS_MODULE,
128 .dai_link = snd_rpi_hifiberry_dacplushd_dai,
129 .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai),
132 static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev)
135 static int dac_reset_done;
136 struct device *dev = &pdev->dev;
137 struct device_node *dev_node = dev->of_node;
139 snd_rpi_hifiberry_dacplushd.dev = &pdev->dev;
141 /* get GPIO and release DAC from RESET */
142 if (!dac_reset_done) {
143 reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
144 if (IS_ERR(reset_gpio)) {
145 dev_err(&pdev->dev, "gpiod_get() failed\n");
150 if (!IS_ERR(reset_gpio))
151 gpiod_set_value(reset_gpio, 0);
153 if (!IS_ERR(reset_gpio))
154 gpiod_set_value(reset_gpio, 1);
156 if (!IS_ERR(reset_gpio))
157 gpiod_set_value(reset_gpio, 0);
159 if (pdev->dev.of_node) {
160 struct device_node *i2s_node;
161 struct snd_soc_dai_link *dai;
163 dai = &snd_rpi_hifiberry_dacplushd_dai[0];
164 i2s_node = of_parse_phandle(pdev->dev.of_node,
165 "i2s-controller", 0);
168 dai->cpus->of_node = i2s_node;
169 dai->platforms->of_node = i2s_node;
170 dai->cpus->dai_name = NULL;
171 dai->platforms->name = NULL;
173 return -EPROBE_DEFER;
178 ret = devm_snd_soc_register_card(&pdev->dev,
179 &snd_rpi_hifiberry_dacplushd);
180 if (ret && ret != -EPROBE_DEFER) {
182 "snd_soc_register_card() failed: %d\n", ret);
185 if (ret == -EPROBE_DEFER)
188 dev_set_drvdata(dev, &drvdata);
189 if (dev_node == NULL) {
190 dev_err(&pdev->dev, "Device tree node not found\n");
194 drvdata.sclk = devm_clk_get(dev, NULL);
195 if (IS_ERR(drvdata.sclk)) {
196 drvdata.sclk = ERR_PTR(-ENOENT);
200 clk_set_rate(drvdata.sclk, DEFAULT_RATE);
205 static int snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
207 if (IS_ERR(reset_gpio))
210 /* put DAC into RESET and release GPIO */
211 gpiod_set_value(reset_gpio, 0);
212 gpiod_put(reset_gpio);
217 static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = {
218 { .compatible = "hifiberry,hifiberry-dacplushd", },
222 MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match);
224 static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = {
226 .name = "snd-rpi-hifiberry-dacplushd",
227 .owner = THIS_MODULE,
228 .of_match_table = snd_rpi_hifiberry_dacplushd_of_match,
230 .probe = snd_rpi_hifiberry_dacplushd_probe,
231 .remove = snd_rpi_hifiberry_dacplushd_remove,
234 module_platform_driver(snd_rpi_hifiberry_dacplushd_driver);
236 MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
237 MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD");
238 MODULE_LICENSE("GPL v2");