1 From 0fa0f5932fff8c960862176fd61a7bccd2f28b90 Mon Sep 17 00:00:00 2001
2 From: David Knell <david.knell@gmail.com>
3 Date: Wed, 28 Oct 2020 14:21:37 +0000
4 Subject: [PATCH] PiFi-40 driver, Makefile and Kconfig
6 Signed-off-by: David Knell <david.knell@gmail.com>
8 sound/soc/bcm/Kconfig | 8 ++
9 sound/soc/bcm/Makefile | 3 +
10 sound/soc/bcm/pifi-40.c | 283 ++++++++++++++++++++++++++++++++++++++++
11 3 files changed, 294 insertions(+)
12 create mode 100644 sound/soc/bcm/pifi-40.c
14 --- a/sound/soc/bcm/Kconfig
15 +++ b/sound/soc/bcm/Kconfig
16 @@ -100,6 +100,14 @@ config SND_BCM2708_SOC_HIFIBERRY_AMP
18 Say Y or M if you want to add support for the HifiBerry Amp amplifier board.
20 + config SND_BCM2708_SOC_PIFI_40
21 + tristate "Support for the PiFi-40 amp"
22 + depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
23 + select SND_SOC_TAS571X
26 + Say Y or M if you want to add support for the PiFi40 amp board
28 config SND_BCM2708_SOC_RPI_CIRRUS
29 tristate "Support for Cirrus Logic Audio Card"
30 depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
31 --- a/sound/soc/bcm/Makefile
32 +++ b/sound/soc/bcm/Makefile
33 @@ -45,6 +45,7 @@ snd-soc-pisound-objs := pisound.o
34 snd-soc-fe-pi-audio-objs := fe-pi-audio.o
35 snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o
36 snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o
37 +snd-soc-pifi-40-objs := pifi-40.o
39 obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.o
40 obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o
41 @@ -74,3 +75,5 @@ obj-$(CONFIG_SND_PISOUND) += snd-soc-pis
42 obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o
43 obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o
44 obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o
45 +obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o
48 +++ b/sound/soc/bcm/pifi-40.c
50 +// SPDX-License-Identifier: GPL-2.0-only
52 + * ALSA ASoC Machine Driver for PiFi-40
54 + * Author: David Knell <david.knell@gmail.com)
55 + * based on code by Daniel Matuschek <info@crazy-audio.com>
56 + * based on code by Florian Meier <florian.meier@koalo.de>
57 + * Copyright (C) 2020
59 + * This program is free software; you can redistribute it and/or
60 + * modify it under the terms of the GNU General Public License
61 + * version 2 as published by the Free Software Foundation.
63 + * This program is distributed in the hope that it will be useful, but
64 + * WITHOUT ANY WARRANTY; without even the implied warranty of
65 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
66 + * General Public License for more details.
69 +#include <linux/module.h>
70 +#include <linux/platform_device.h>
71 +#include <linux/gpio/consumer.h>
72 +#include <sound/core.h>
73 +#include <sound/pcm.h>
74 +#include <sound/pcm_params.h>
75 +#include <sound/soc.h>
76 +#include <linux/firmware.h>
77 +#include <linux/delay.h>
78 +#include <sound/tlv.h>
80 +static struct gpio_desc *pdn_gpio;
81 +static int vol = 0x30;
84 +static int pifi_40_vol_get(struct snd_kcontrol *kcontrol,
85 + struct snd_ctl_elem_value *ucontrol)
87 + ucontrol->value.integer.value[0] = vol;
88 + ucontrol->value.integer.value[1] = vol;
92 +static int pifi_40_vol_set(struct snd_kcontrol *kcontrol,
93 + struct snd_ctl_elem_value *ucontrol)
95 + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
96 + struct snd_soc_pcm_runtime *rtd;
97 + unsigned int v = ucontrol->value.integer.value[0];
98 + struct snd_soc_component *dac[2];
100 + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
101 + dac[0] = asoc_rtd_to_codec(rtd, 0)->component;
102 + dac[1] = asoc_rtd_to_codec(rtd, 1)->component;
104 + snd_soc_component_write(dac[0], 0x07, 255 - v);
105 + snd_soc_component_write(dac[1], 0x07, 255 - v);
111 +static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
112 +static const struct snd_kcontrol_new pifi_40_controls[] = {
113 + SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01,
117 + pifi_40_vol_get, pifi_40_vol_set,
118 + digital_tlv_master)
121 +static const char * const codec_ctl_pfx[] = { "Left", "Right" };
123 +static const char * const codec_ctl_name[] = { "Master Volume",
125 + "Speaker Switch" };
127 +static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd)
129 + struct snd_soc_card *card = rtd->card;
130 + struct snd_soc_component *dac[2];
131 + struct snd_kcontrol *kctl;
134 + dac[0] = asoc_rtd_to_codec(rtd, 0)->component;
135 + dac[1] = asoc_rtd_to_codec(rtd, 1)->component;
138 + // Set up cards - pulse power down first
139 + gpiod_set_value_cansleep(pdn_gpio, 1);
140 + usleep_range(1000, 10000);
141 + gpiod_set_value_cansleep(pdn_gpio, 0);
142 + usleep_range(20000, 30000);
145 + snd_soc_component_write(dac[0], 0x1b, 0);
146 + snd_soc_component_write(dac[1], 0x1b, 0);
147 + usleep_range(60000, 80000);
150 + for (i = 0; i < 2; i++) {
151 + // MCLK at 64fs, sample rate 44.1 or 48kHz
152 + snd_soc_component_write(dac[i], 0x00, 0x60);
155 + snd_soc_component_write(dac[i], 0x19, 0x3A);
156 + snd_soc_component_write(dac[i], 0x25, 0x01103245);
158 + // Master vol to -10db
159 + snd_soc_component_write(dac[i], 0x07, 0x44);
161 + // Inputs set to L and R respectively
162 + snd_soc_component_write(dac[0], 0x20, 0x00017772);
163 + snd_soc_component_write(dac[1], 0x20, 0x00107772);
165 + // Remove codec controls
166 + for (i = 0; i < 2; i++) {
167 + for (j = 0; j < 3; j++) {
170 + sprintf(cname, "%s %s", codec_ctl_pfx[i],
171 + codec_ctl_name[j]);
172 + kctl = snd_soc_card_get_kcontrol(card, cname);
174 + pr_info("Control %s not found\n",
177 + kctl->vd[0].access =
178 + SNDRV_CTL_ELEM_ACCESS_READWRITE;
179 + snd_ctl_remove(card->snd_card, kctl);
187 +static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream,
188 + struct snd_pcm_hw_params *params)
190 + struct snd_soc_pcm_runtime *rtd = substream->private_data;
191 + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
192 + unsigned int sample_bits;
194 + sample_bits = snd_pcm_format_physical_width(params_format(params));
195 + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
198 +static struct snd_soc_ops snd_pifi_40_ops = { .hw_params =
199 + snd_pifi_40_hw_params };
201 +static struct snd_soc_dai_link_component pifi_40_codecs[] = {
203 + .dai_name = "tas571x-hifi",
206 + .dai_name = "tas571x-hifi",
210 +SND_SOC_DAILINK_DEFS(
211 + pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()),
212 + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"),
213 + COMP_CODEC("tas571x.1-001b", "tas571x-hifi")),
214 + DAILINK_COMP_ARRAY(COMP_EMPTY()));
216 +static struct snd_soc_dai_link snd_pifi_40_dai[] = {
219 + .stream_name = "PiFi40",
220 + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
221 + SND_SOC_DAIFMT_CBS_CFS,
222 + .ops = &snd_pifi_40_ops,
223 + .init = snd_pifi_40_init,
224 + SND_SOC_DAILINK_REG(pifi_40_dai),
229 +static struct snd_soc_card snd_pifi_40 = {
231 + .owner = THIS_MODULE,
232 + .dai_link = snd_pifi_40_dai,
233 + .num_links = ARRAY_SIZE(snd_pifi_40_dai),
234 + .controls = pifi_40_controls,
235 + .num_controls = ARRAY_SIZE(pifi_40_controls)
238 +static void snd_pifi_40_pdn(struct snd_soc_card *card, int on)
241 + gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1);
244 +static int snd_pifi_40_probe(struct platform_device *pdev)
246 + struct snd_soc_card *card = &snd_pifi_40;
247 + int ret = 0, i = 0;
249 + card->dev = &pdev->dev;
250 + platform_set_drvdata(pdev, &snd_pifi_40);
252 + if (pdev->dev.of_node) {
253 + struct device_node *i2s_node;
254 + struct snd_soc_dai_link *dai;
256 + dai = &snd_pifi_40_dai[0];
257 + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller",
260 + for (i = 0; i < card->num_links; i++) {
261 + dai->cpus->dai_name = NULL;
262 + dai->cpus->of_node = i2s_node;
263 + dai->platforms->name = NULL;
264 + dai->platforms->of_node = i2s_node;
268 + pifi_40_codecs[0].of_node =
269 + of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
270 + pifi_40_codecs[1].of_node =
271 + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
272 + if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) {
273 + dev_err(&pdev->dev,
274 + "Property 'audio-codec' missing or invalid\n");
278 + pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn",
280 + if (IS_ERR(pdn_gpio)) {
281 + ret = PTR_ERR(pdn_gpio);
282 + dev_err(&pdev->dev, "failed to get pdn gpio: %d\n",
287 + ret = snd_soc_register_card(&snd_pifi_40);
289 + dev_err(&pdev->dev,
290 + "snd_soc_register_card() failed: %d\n", ret);
300 +static int snd_pifi_40_remove(struct platform_device *pdev)
302 + struct snd_soc_card *card = platform_get_drvdata(pdev);
304 + kfree(&card->drvdata);
305 + snd_pifi_40_pdn(&snd_pifi_40, 0);
306 + return snd_soc_unregister_card(&snd_pifi_40);
309 +static const struct of_device_id snd_pifi_40_of_match[] = {
311 + .compatible = "pifi,pifi-40",
313 + { /* sentinel */ },
316 +MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match);
318 +static struct platform_driver snd_pifi_40_driver = {
320 + .name = "snd-pifi-40",
321 + .owner = THIS_MODULE,
322 + .of_match_table = snd_pifi_40_of_match,
324 + .probe = snd_pifi_40_probe,
325 + .remove = snd_pifi_40_remove,
328 +module_platform_driver(snd_pifi_40_driver);
330 +MODULE_AUTHOR("David Knell <david.knell@gmail.com>");
331 +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40");
332 +MODULE_LICENSE("GPL v2");