Skip to content

Commit 0ab48b8

Browse files
sound: soc: raspberrypi: Convert RP1 AOut driver to ASOC DAI. TO SQUASH.
Rewrite the driver to be an ASOC DAI, now using standard DMA support. It now requires some additional OF gubbins to connect it to a "dummy" codec and generic sound card. Signed-off-by: Nick Hollinghurst <[email protected]>
1 parent 31c26e8 commit 0ab48b8

File tree

8 files changed

+359
-1374
lines changed

8 files changed

+359
-1374
lines changed

sound/soc/Kconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ source "sound/soc/meson/Kconfig"
106106
source "sound/soc/mxs/Kconfig"
107107
source "sound/soc/pxa/Kconfig"
108108
source "sound/soc/qcom/Kconfig"
109+
source "sound/soc/raspberrypi/Kconfig"
109110
source "sound/soc/rockchip/Kconfig"
110-
source "sound/soc/rp1/Kconfig"
111111
source "sound/soc/samsung/Kconfig"
112112
source "sound/soc/sh/Kconfig"
113113
source "sound/soc/sof/Kconfig"

sound/soc/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ obj-$(CONFIG_SND_SOC) += mxs/
5959
obj-$(CONFIG_SND_SOC) += kirkwood/
6060
obj-$(CONFIG_SND_SOC) += pxa/
6161
obj-$(CONFIG_SND_SOC) += qcom/
62+
obj-$(CONFIG_SND_SOC) += raspberrypi/
6263
obj-$(CONFIG_SND_SOC) += rockchip/
63-
obj-$(CONFIG_SND_SOC) += rp1/
6464
obj-$(CONFIG_SND_SOC) += samsung/
6565
obj-$(CONFIG_SND_SOC) += sh/
6666
obj-$(CONFIG_SND_SOC) += sof/
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
config SND_RP1_AUDIO_OUT
33
tristate "PWM Audio Out from RP1"
44
help
5-
Say Y or M if you want to add support for digital audio
6-
output from Raspberry Pi 5.
7-
5+
Say Y or M if you want to add support for PWM
6+
digital audio output from Raspberry Pi 5, 500 or CM5.

sound/soc/raspberrypi/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o

sound/soc/raspberrypi/rp1_aout.c

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (c) 2025 Raspberry Pi Ltd.
4+
*
5+
* rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block.
6+
* This is a modified 2-channel PWM with hardware 40 times oversampling,
7+
* 2nd order noise shaping and dither. Only output (playback) is supported.
8+
*
9+
* For ASOC, this is implemented as a "DAI" and will need to be linked to a
10+
* dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card".
11+
*
12+
* Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c"
13+
* and other example drivers.
14+
*/
15+
16+
#include <linux/clk.h>
17+
#include <linux/device.h>
18+
#include <linux/init.h>
19+
#include <linux/interrupt.h>
20+
#include <linux/io.h>
21+
#include <linux/module.h>
22+
#include <linux/pm_runtime.h>
23+
#include <linux/reset.h>
24+
#include <linux/slab.h>
25+
#include <linux/types.h>
26+
#include <sound/dmaengine_pcm.h>
27+
#include <sound/pcm.h>
28+
#include <sound/pcm_params.h>
29+
#include <sound/soc.h>
30+
31+
#define AUDIO_OUT_REGS_RWTYPE GENMASK(13, 12)
32+
33+
#define AUDIO_OUT_CTRL 0x0000
34+
#define AUDIO_OUT_CTRL_PERIPH_EN BIT(31)
35+
#define AUDIO_OUT_CTRL_CIC_RATE GENMASK(11, 8)
36+
#define AUDIO_OUT_CTRL_CHANNEL_SWAP BIT(2)
37+
#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE BIT(1)
38+
#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE BIT(0)
39+
40+
#define AUDIO_OUT_SDMCTL_LEFT 0x0004
41+
#define AUDIO_OUT_SDMCTL_RIGHT 0x0008
42+
#define AUDIO_OUT_SDMCTL_LR_BIAS GENMASK(31, 16)
43+
#define AUDIO_OUT_SDMCTL_LR_BYPASS BIT(7)
44+
#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER BIT(6)
45+
#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN BIT(5)
46+
#define AUDIO_OUT_SDMCTL_LR_DITHER_EN BIT(4)
47+
#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0)
48+
49+
#define AUDIO_OUT_QCLAMP_LEFT 0x000c
50+
#define AUDIO_OUT_QCLAMP_RIGHT 0x0010
51+
#define AUDIO_OUT_QCLAMP_LR_MAX GENMASK(31, 16)
52+
#define AUDIO_OUT_QCLAMP_LR_MIN GENMASK(15, 0)
53+
54+
#define AUDIO_OUT_MUTE_CTRL_LEFT 0x0014
55+
#define AUDIO_OUT_MUTE_CTRL_RIGHT 0x0018
56+
#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS BIT(31)
57+
#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD GENMASK(23, 16)
58+
#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE GENMASK(15, 8)
59+
#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE BIT(5)
60+
#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE BIT(4)
61+
#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM GENMASK(3, 0)
62+
63+
#define AUDIO_OUT_PWMCTL_LEFT 0x001c
64+
#define AUDIO_OUT_PWMRANGE_LEFT 0x0020
65+
#define AUDIO_OUT_PWMCTL_RIGHT 0x0028
66+
#define AUDIO_OUT_PWMRANGE_RIGHT 0x002c
67+
68+
#define AUDIO_OUT_FIFO_CONTROL 0x0034
69+
#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN BIT(31)
70+
#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE BIT(25)
71+
#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH BIT(24)
72+
#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME GENMASK(20, 16)
73+
#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD GENMASK(5, 0)
74+
75+
#define AUDIO_OUT_SAMPLE_FIFO 0x0038
76+
77+
struct rp1_aout {
78+
void __iomem *regs;
79+
phys_addr_t physaddr;
80+
struct clk *clk;
81+
struct device *dev;
82+
struct snd_dmaengine_dai_dma_data play_dma_data;
83+
bool initted;
84+
};
85+
86+
static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val)
87+
{
88+
void __iomem *addr = ao->regs + offset;
89+
90+
writel(val, addr);
91+
}
92+
93+
static inline void aout_reg_set(struct rp1_aout *ao, unsigned int offset, u32 val)
94+
{
95+
void __iomem *addr = ao->regs + offset +
96+
FIELD_PREP_CONST(AUDIO_OUT_REGS_RWTYPE, 2);
97+
98+
writel(val, addr);
99+
}
100+
101+
static inline void aout_reg_clr(struct rp1_aout *ao, unsigned int offset, u32 val)
102+
{
103+
void __iomem *addr = ao->regs + offset +
104+
FIELD_PREP_CONST(AUDIO_OUT_REGS_RWTYPE, 3);
105+
106+
writel(val, addr);
107+
}
108+
109+
static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset)
110+
{
111+
void __iomem *addr = ao->regs + offset;
112+
113+
return readl(addr);
114+
}
115+
116+
static void audio_init(struct rp1_aout *aout)
117+
{
118+
u32 val;
119+
120+
/*
121+
* The hardware was tuned to play at 48 kHz with 40 times oversampling and
122+
* 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz.
123+
* Changing these settings is not recommended. At those rates, the filter
124+
* leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD.
125+
*/
126+
127+
/* Clamp to +/- (32767 * 40 / 64) before quantization */
128+
val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) |
129+
FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479));
130+
aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val);
131+
aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val);
132+
aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0);
133+
aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0);
134+
135+
/* Range = 39 */
136+
aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27);
137+
aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27);
138+
139+
/* bias = 20 (half FSD). Quantize to 5+1 bits */
140+
val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) |
141+
AUDIO_OUT_SDMCTL_LR_CLAMP_EN |
142+
AUDIO_OUT_SDMCTL_LR_DITHER_EN |
143+
FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5);
144+
aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val);
145+
aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val);
146+
147+
/* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */
148+
val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
149+
FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
150+
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
151+
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
152+
153+
/* Configure DMA flow control with threshold at half FIFO depth */
154+
val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) |
155+
FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) |
156+
AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN;
157+
aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val);
158+
}
159+
160+
static void audio_startup(struct rp1_aout *aout)
161+
{
162+
u32 val;
163+
164+
/* CIC rate 10, for an overall upsampling ratio of 40 */
165+
val = FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) |
166+
AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE;
167+
aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
168+
169+
/* Press the "go" button */
170+
val |= AUDIO_OUT_CTRL_PERIPH_EN;
171+
aout_reg_set(aout, AUDIO_OUT_CTRL, val);
172+
173+
/* Poke zeroes in to avoid undefined values on underrun */
174+
aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
175+
}
176+
177+
static void audio_start_unmute(struct rp1_aout *aout)
178+
{
179+
/* Set the mute engine to start unmuting */
180+
aout_reg_clr(aout, AUDIO_OUT_MUTE_CTRL_LEFT,
181+
AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE |
182+
AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
183+
aout_reg_clr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT,
184+
AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE |
185+
AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
186+
aout_reg_set(aout, AUDIO_OUT_MUTE_CTRL_LEFT,
187+
AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
188+
aout_reg_set(aout, AUDIO_OUT_MUTE_CTRL_RIGHT,
189+
AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
190+
}
191+
192+
static void audio_mute_sync(struct rp1_aout *aout)
193+
{
194+
u32 mask = 0x1 | 0x4; /* stable states of the mute FSM */
195+
196+
while ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) != 0)
197+
usleep_range(1000, 5000);
198+
}
199+
200+
/* Device DAI interface */
201+
202+
static int rp1_aout_startup(struct snd_pcm_substream *substream,
203+
struct snd_soc_dai *dai)
204+
{
205+
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
206+
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
207+
struct snd_soc_dai_link *dai_link = rtd->dai_link;
208+
209+
dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
210+
211+
if (!aout->initted) {
212+
dev_info(dai->dev, "RP1 Audio Out start\n");
213+
audio_init(aout);
214+
audio_startup(aout);
215+
audio_start_unmute(aout);
216+
aout->initted = true;
217+
}
218+
219+
return 0;
220+
}
221+
222+
static int rp1_aout_hw_params(struct snd_pcm_substream *substream,
223+
struct snd_pcm_hw_params *params,
224+
struct snd_soc_dai *dai)
225+
{
226+
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
227+
228+
/* We only support this one configuration */
229+
if (params_rate(params) != 48000 || params_channels(params) != 2 ||
230+
params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
231+
dev_err(dai->dev, "Invalid HW params\n");
232+
return -EINVAL;
233+
}
234+
235+
audio_mute_sync(aout);
236+
237+
return 0;
238+
}
239+
240+
static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd,
241+
struct snd_soc_dai *dai)
242+
{
243+
struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai);
244+
245+
if (cmd == SNDRV_PCM_TRIGGER_STOP ||
246+
cmd == SNDRV_PCM_TRIGGER_SUSPEND ||
247+
cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
248+
/* Push a zero sample (assuming DMA has stopped already?) */
249+
aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
250+
}
251+
252+
return 0;
253+
}
254+
255+
static int rp1_aout_dai_probe(struct snd_soc_dai *dai)
256+
{
257+
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
258+
259+
snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL);
260+
snd_soc_dai_set_drvdata(dai, aout);
261+
262+
return 0;
263+
}
264+
265+
static const struct snd_soc_dai_ops rp1_aout_dai_ops = {
266+
.probe = rp1_aout_dai_probe,
267+
.startup = rp1_aout_startup,
268+
.hw_params = rp1_aout_hw_params,
269+
.trigger = rp1_aout_trigger,
270+
};
271+
272+
static const struct snd_soc_component_driver rp1_aout_component = {
273+
.name = "rp1-aout",
274+
};
275+
276+
static struct snd_soc_dai_driver rp1_aout_dai = {
277+
.name = "rp1-aout",
278+
.id = 0,
279+
.playback = {
280+
.channels_min = 2,
281+
.channels_max = 2,
282+
.rates = SNDRV_PCM_RATE_48000,
283+
.formats = SNDRV_PCM_FMTBIT_S16_LE,
284+
},
285+
.ops = &rp1_aout_dai_ops,
286+
};
287+
288+
static int rp1_aout_platform_probe(struct platform_device *pdev)
289+
{
290+
int ret;
291+
struct rp1_aout *aout;
292+
struct resource *ioresource;
293+
294+
aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL);
295+
if (!aout)
296+
return -ENOMEM;
297+
298+
aout->clk = devm_clk_get(&pdev->dev, NULL);
299+
if (IS_ERR(aout->clk))
300+
return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk),
301+
"could not get clk\n");
302+
303+
aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
304+
if (IS_ERR(aout->regs))
305+
return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs),
306+
"could not map registers\n");
307+
aout->physaddr = ioresource->start;
308+
309+
/* Initialize playback DMA parameters */
310+
aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO;
311+
aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
312+
aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */
313+
aout->play_dma_data.maxburst = 4;
314+
315+
clk_prepare_enable(aout->clk);
316+
aout->dev = &pdev->dev;
317+
dev_set_drvdata(&pdev->dev, aout);
318+
ret = devm_snd_soc_register_component(&pdev->dev,
319+
&rp1_aout_component,
320+
&rp1_aout_dai, 1);
321+
if (ret)
322+
return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
323+
324+
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
325+
if (ret)
326+
return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
327+
328+
/* TODO: More PM stuff? */
329+
pm_runtime_enable(&pdev->dev);
330+
331+
return 0;
332+
}
333+
334+
static const struct of_device_id rp1_aout_of_match[] = {
335+
{ .compatible = "raspberrypi,rp1-audio-out", },
336+
{ /* sentinel */ },
337+
};
338+
339+
MODULE_DEVICE_TABLE(of, rp1_aout_of_match);
340+
341+
static struct platform_driver rp1_audio_out_driver = {
342+
.probe = rp1_aout_platform_probe,
343+
.driver = {
344+
.name = "rp1-audio-out",
345+
.of_match_table = rp1_aout_of_match,
346+
},
347+
};
348+
349+
module_platform_driver(rp1_audio_out_driver);
350+
351+
MODULE_DESCRIPTION("RP1 Audio out");
352+
MODULE_AUTHOR("Nick Hollinghurst <[email protected]>");
353+
MODULE_LICENSE("GPL");

sound/soc/rp1/Makefile

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)