diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
index 9989754951a355..92ff31f6406be6 100644
--- a/arch/arm/boot/dts/overlays/Makefile
+++ b/arch/arm/boot/dts/overlays/Makefile
@@ -33,6 +33,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
audioinjector-wm8731-audio.dtbo \
audiosense-pi.dtbo \
audremap.dtbo \
+ audremap-pi5.dtbo \
balena-fin.dtbo \
bcm2712d0.dtbo \
camera-mux-2port.dtbo \
diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README
index 82cde99520c4fd..2736e1fac3f3fa 100644
--- a/arch/arm/boot/dts/overlays/README
+++ b/arch/arm/boot/dts/overlays/README
@@ -871,6 +871,17 @@ Params: swap_lr Reverse the channel allocation, which will also
pins are on different controllers)
+Name: audremap-pi5
+Info: On Raspberry Pi 5 / 500 /CM5, enable digital audio output
+ and route it to GPIOs 12 & 13 on the 40-pin header
+Load: dtoverlay=audremap-pi5,=
+Params: swap_lr Reverse the channel allocation (default off)
+ pins_12_13 Select GPIOs 12 & 13 (default)
+ pins_18_19 Not available; this will not enable audio out
+ pins_40_41 Not available; this will not enable audio out
+ pins_40_45 Not available; this will not enable audio out
+
+
Name: balena-fin
Info: Overlay that enables WLAN, Bluetooth and the GPIO expander on the
balenaFin carrier board for the Raspberry Pi Compute Module 3/3+ Lite.
diff --git a/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts
new file mode 100644
index 00000000000000..aa74ecd26f15ff
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts
@@ -0,0 +1,65 @@
+/dts-v1/;
+/plugin/;
+
+/*
+ * Raspberry Pi 5 has a different Audio Out hardware from earlier Raspberry Pis.
+ * It can output only to GPIOs 12 and 13. We therefore enable it *only* when
+ * that particular GPIO mapping is requested here. To make it work with ASOC
+ * we must also define a "dummy" codec and a generic audio card.
+ */
+
+/ {
+ compatible = "brcm,bcm2712";
+
+ fragment@0 {
+ target = <&rp1_audio_out>;
+ frag0: __overlay__ {
+ pinctrl-0 = <&rp1_audio_out_12_13>;
+ pinctrl-names = "default";
+ status = "ok";
+ };
+ };
+
+ fragment@1 {
+ target-path = "/";
+ __overlay__ {
+
+ rp1_audio_out_codec: rp1_audio_out_codec@0 {
+ compatible = "linux,spdif-dit";
+ #sound-dai-cells = <0>;
+ status = "ok";
+ };
+
+ rp1_audio_out_simple_card@0 {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "RP1-Audio-Out";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "ok";
+
+ simple-audio-card,dai-link@0 {
+ reg = <0>;
+ format = "left_j";
+ bitclock-master = <&sndcpu0>;
+ frame-master = <&sndcpu0>;
+
+ sndcpu0: cpu {
+ sound-dai = <&rp1_audio_out>;
+ };
+
+ codec {
+ sound-dai = <&rp1_audio_out_codec>;
+ };
+ };
+ };
+ };
+ };
+
+ __overrides__ {
+ swap_lr = <&frag0>, "swap_lr?";
+ pins_12_13 = <0>, "+0+1"; /* this is the default */
+ pins_18_19 = <0>, "-0-1"; /* sorry not supported */
+ pins_40_41 = <0>, "-0-1"; /* sorry not supported */
+ pins_40_45 = <0>, "-0-1"; /* sorry not supported */
+ };
+};
diff --git a/arch/arm/boot/dts/overlays/overlay_map.dts b/arch/arm/boot/dts/overlays/overlay_map.dts
index 53256828de0ab5..2639384e8b701e 100644
--- a/arch/arm/boot/dts/overlays/overlay_map.dts
+++ b/arch/arm/boot/dts/overlays/overlay_map.dts
@@ -4,6 +4,11 @@
audremap {
bcm2835;
bcm2711;
+ bcm2712 = "audremap-pi5";
+ };
+
+ audremap-pi5 {
+ bcm2712;
};
balena-fin {
diff --git a/arch/arm64/boot/dts/broadcom/rp1.dtsi b/arch/arm64/boot/dts/broadcom/rp1.dtsi
index 15e770a63c5546..3a798aa31dde92 100644
--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
@@ -49,7 +49,7 @@
<125000000>, // RP1_PLL_SYS_SEC
<125000000>, // RP1_CLK_ETH
<61440000>, // RP1_PLL_AUDIO
- <192000000>, // RP1_PLL_AUDIO_SEC
+ <153600000>, // RP1_PLL_AUDIO_SEC
<200000000>, // RP1_CLK_SYS
<100000000>, // RP1_PLL_SYS_PRI_PH
// Must match the XOSC frequency
@@ -380,6 +380,20 @@
status = "disabled";
};
+ rp1_audio_out: audio_out@94000 {
+ compatible = "raspberrypi,rp1-audio-out";
+ reg = <0xc0 0x40094000 0x0 0x4000>;
+ clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>;
+ assigned-clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>;
+ assigned-clock-rates = <153600000>;
+ assigned-clock-parents = <&rp1_clocks RP1_PLL_AUDIO_SEC>;
+ dmas = <&rp1_dma RP1_DMA_AUDIO_OUT>;
+ dma-maxburst = <4>;
+ dma-names = "tx";
+ #sound-dai-cells = <0>;
+ status = "disabled";
+ };
+
rp1_pwm0: pwm@98000 {
compatible = "raspberrypi,rp1-pwm";
reg = <0xc0 0x40098000 0x0 0x100>;
@@ -974,6 +988,12 @@
pins = "gpio52", "gpio53";
bias-pull-up;
};
+
+ rp1_audio_out_12_13: rp1_audio_out_12_13 {
+ function = "aaud";
+ pins = "gpio12", "gpio13";
+ bias-disable;
+ };
};
rp1_eth: ethernet@100000 {
diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig
index 9eb5aace19ed2e..47a23748a4c16c 100644
--- a/arch/arm64/configs/bcm2712_defconfig
+++ b/arch/arm64/configs/bcm2712_defconfig
@@ -1155,6 +1155,7 @@ CONFIG_SND_PISOUND=m
CONFIG_SND_DACBERRY400=m
CONFIG_SND_DESIGNWARE_I2S=m
CONFIG_SND_DESIGNWARE_PCM=y
+CONFIG_SND_RP1_AUDIO_OUT=m
CONFIG_SND_SOC_AD193X_SPI=m
CONFIG_SND_SOC_AD193X_I2C=m
CONFIG_SND_SOC_ADAU1701=m
diff --git a/drivers/clk/clk-rp1.c b/drivers/clk/clk-rp1.c
index 3ae2f51d4771dd..a04d218dd3846a 100644
--- a/drivers/clk/clk-rp1.c
+++ b/drivers/clk/clk-rp1.c
@@ -1326,9 +1326,11 @@ static void rp1_clock_choose_div_and_prate(struct clk_hw *hw,
/*
* Prevent overclocks - if all parent choices result in
* a downstream clock in excess of the maximum, then the
- * call to set the clock will fail.
+ * call to set the clock will fail. But due to round-to-
+ * nearest in the PLL core (which has 24 fractional bits),
+ * it's expedient to tolerate a tiny error (1Hz/33MHz).
*/
- if (tmp > clock->data->max_freq)
+ if (tmp > clock->data->max_freq + (clock->data->max_freq >> 25))
*calc_rate = 0;
else
*calc_rate = tmp;
@@ -2006,7 +2008,7 @@ static const struct rp1_clk_desc clk_desc_array[] = {
[RP1_CLK_AUDIO_OUT] = REGISTER_CLK(
.name = "clk_audio_out",
.parents = {"", //"pll_audio",
- "", //"pll_audio_sec",
+ "pll_audio_sec",
"pll_video_sec",
"xosc",
"clksrc_gp0",
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index e87bd15a8b4393..60a98ef7dcc511 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -106,6 +106,7 @@ source "sound/soc/meson/Kconfig"
source "sound/soc/mxs/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/qcom/Kconfig"
+source "sound/soc/raspberrypi/Kconfig"
source "sound/soc/rockchip/Kconfig"
source "sound/soc/samsung/Kconfig"
source "sound/soc/sh/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 775bb38c2ed447..94d7dfa17e256d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_SND_SOC) += mxs/
obj-$(CONFIG_SND_SOC) += kirkwood/
obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += qcom/
+obj-$(CONFIG_SND_SOC) += raspberrypi/
obj-$(CONFIG_SND_SOC) += rockchip/
obj-$(CONFIG_SND_SOC) += samsung/
obj-$(CONFIG_SND_SOC) += sh/
diff --git a/sound/soc/raspberrypi/Kconfig b/sound/soc/raspberrypi/Kconfig
new file mode 100644
index 00000000000000..389bb82e3651e9
--- /dev/null
+++ b/sound/soc/raspberrypi/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_RP1_AUDIO_OUT
+ tristate "PWM Audio Out from RP1"
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SOC_SPDIF
+ help
+ Say Y or M if you want to add support for PWM digital
+ audio output from a Raspberry Pi 5, 500 or CM5.
+
+ Output is from RP1 GPIOs pins 12 and 13 only, and additional
+ components will be needed. It may be useful when HDMI, I2S
+ or USB audio devices are unavailable, or for compatibility.
diff --git a/sound/soc/raspberrypi/Makefile b/sound/soc/raspberrypi/Makefile
new file mode 100644
index 00000000000000..80ff460c785a3c
--- /dev/null
+++ b/sound/soc/raspberrypi/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o
diff --git a/sound/soc/raspberrypi/rp1_aout.c b/sound/soc/raspberrypi/rp1_aout.c
new file mode 100644
index 00000000000000..ffcc7b815d5edd
--- /dev/null
+++ b/sound/soc/raspberrypi/rp1_aout.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Raspberry Pi Ltd.
+ *
+ * rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block.
+ * This is a modified 2-channel PWM with hardware 40 times oversampling,
+ * 2nd order noise shaping and dither. Only output (playback) is supported.
+ *
+ * For ASOC, this is implemented as a "DAI" and will need to be linked to a
+ * dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card".
+ *
+ * Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c"
+ * and other example drivers.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define AUDIO_OUT_CTRL 0x0000
+#define AUDIO_OUT_CTRL_PERIPH_EN BIT(31)
+#define AUDIO_OUT_CTRL_CIC_RATE GENMASK(11, 8)
+#define AUDIO_OUT_CTRL_CHANNEL_SWAP BIT(2)
+#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE BIT(1)
+#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE BIT(0)
+
+#define AUDIO_OUT_SDMCTL_LEFT 0x0004
+#define AUDIO_OUT_SDMCTL_RIGHT 0x0008
+#define AUDIO_OUT_SDMCTL_LR_BIAS GENMASK(31, 16)
+#define AUDIO_OUT_SDMCTL_LR_BYPASS BIT(7)
+#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER BIT(6)
+#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN BIT(5)
+#define AUDIO_OUT_SDMCTL_LR_DITHER_EN BIT(4)
+#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0)
+
+#define AUDIO_OUT_QCLAMP_LEFT 0x000c
+#define AUDIO_OUT_QCLAMP_RIGHT 0x0010
+#define AUDIO_OUT_QCLAMP_LR_MAX GENMASK(31, 16)
+#define AUDIO_OUT_QCLAMP_LR_MIN GENMASK(15, 0)
+
+#define AUDIO_OUT_MUTE_CTRL_LEFT 0x0014
+#define AUDIO_OUT_MUTE_CTRL_RIGHT 0x0018
+#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS BIT(31)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD GENMASK(23, 16)
+#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE GENMASK(15, 8)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE BIT(5)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE BIT(4)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM GENMASK(3, 0)
+
+#define AUDIO_OUT_PWMCTL_LEFT 0x001c
+#define AUDIO_OUT_PWMRANGE_LEFT 0x0020
+#define AUDIO_OUT_PWMCTL_RIGHT 0x0028
+#define AUDIO_OUT_PWMRANGE_RIGHT 0x002c
+
+#define AUDIO_OUT_FIFO_CONTROL 0x0034
+#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN BIT(31)
+#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE BIT(25)
+#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH BIT(24)
+#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME GENMASK(20, 16)
+#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD GENMASK(5, 0)
+
+#define AUDIO_OUT_SAMPLE_FIFO 0x0038
+
+struct rp1_aout {
+ void __iomem *regs;
+ phys_addr_t physaddr;
+ struct clk *clk;
+ struct device *dev;
+ struct snd_dmaengine_dai_dma_data play_dma_data;
+ bool initted;
+ bool swap_lr;
+};
+
+static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val)
+{
+ void __iomem *addr = ao->regs + offset;
+
+ writel(val, addr);
+}
+
+static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset)
+{
+ void __iomem *addr = ao->regs + offset;
+
+ return readl(addr);
+}
+
+static void audio_init(struct rp1_aout *aout)
+{
+ u32 val;
+
+ /*
+ * The hardware was tuned to play at 48 kHz with 40 times oversampling and
+ * 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz.
+ * Changing these settings is not recommended. At those rates, the filter
+ * leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD.
+ */
+
+ /* Clamp to +/- (32767 * 40 / 64) before quantization */
+ val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) |
+ FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479));
+ aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val);
+ aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val);
+ aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0);
+ aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0);
+
+ /* Range = 39 */
+ aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27);
+ aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27);
+
+ /* bias = 20 (half FSD). Quantize to 5+1 bits */
+ val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) |
+ AUDIO_OUT_SDMCTL_LR_CLAMP_EN |
+ AUDIO_OUT_SDMCTL_LR_DITHER_EN |
+ FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5);
+ aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val);
+ aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val);
+
+ /* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */
+ val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+ FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+ /* Configure DMA flow control with threshold at half FIFO depth */
+ val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) |
+ FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) |
+ AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN;
+ aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val);
+}
+
+static void audio_startup(struct rp1_aout *aout)
+{
+ u32 val;
+
+ /* CIC rate 10, for an overall upsampling ratio of 40 */
+ val = FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) |
+ AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE;
+ if (aout->swap_lr)
+ val |= AUDIO_OUT_CTRL_CHANNEL_SWAP;
+ aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+ aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */
+
+ /* Press the "go" button */
+ val |= AUDIO_OUT_CTRL_PERIPH_EN;
+ aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+ aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */
+
+ /* Poke zeroes in to avoid undefined values on underrun */
+ aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+}
+
+static void audio_muting(struct rp1_aout *aout, u32 flag)
+{
+ u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+ FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+ val |= flag;
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+ aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+ aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */
+}
+
+static void audio_mute_sync(struct rp1_aout *aout)
+{
+ static const u32 mask = 0x1 | 0x4; /* transitional states */
+ unsigned int count;
+
+ for (count = 0; count < 500; count++) {
+ if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0)
+ break;
+ usleep_range(1000, 5000);
+ }
+ for (; count < 500; count++) {
+ if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0)
+ break;
+ usleep_range(1000, 5000);
+ }
+}
+
+/* Device DAI interface */
+
+static int rp1_aout_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+ dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+
+ if (!aout->initted) {
+ dev_info(dai->dev, "RP1 Audio Out start\n");
+ audio_init(aout);
+ audio_startup(aout);
+ audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
+ aout->initted = true;
+ }
+
+ return 0;
+}
+
+static int rp1_aout_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+ /* We only support this one configuration */
+ if (params_rate(params) != 48000 || params_channels(params) != 2 ||
+ params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+ dev_err(dai->dev, "Invalid HW params\n");
+ return -EINVAL;
+ }
+
+ audio_mute_sync(aout);
+
+ return 0;
+}
+
+static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai);
+
+ if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+ cmd == SNDRV_PCM_TRIGGER_SUSPEND ||
+ cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+ /* Push a zero sample (assuming DMA has stopped already) */
+ aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+ }
+
+ return 0;
+}
+
+static int rp1_aout_dai_probe(struct snd_soc_dai *dai)
+{
+ struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+ snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL);
+ snd_soc_dai_set_drvdata(dai, aout);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops rp1_aout_dai_ops = {
+ .probe = rp1_aout_dai_probe,
+ .startup = rp1_aout_startup,
+ .hw_params = rp1_aout_hw_params,
+ .trigger = rp1_aout_trigger,
+};
+
+static const struct snd_soc_component_driver rp1_aout_component = {
+ .name = "rp1-aout",
+};
+
+static struct snd_soc_dai_driver rp1_aout_dai = {
+ .name = "rp1-aout",
+ .id = 0,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &rp1_aout_dai_ops,
+};
+
+static int rp1_aout_platform_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct rp1_aout *aout;
+ struct resource *ioresource;
+
+ dev_info(&pdev->dev, "rp1_aout_platform_probe");
+ aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL);
+ if (!aout)
+ return -ENOMEM;
+
+ aout->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(aout->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk),
+ "could not get clk\n");
+
+ aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
+ if (IS_ERR(aout->regs))
+ return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs),
+ "could not map registers\n");
+ aout->physaddr = ioresource->start;
+ aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr");
+
+ /* Initialize playback DMA parameters */
+ aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO;
+ aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */
+ aout->play_dma_data.maxburst = 4;
+
+ clk_prepare_enable(aout->clk);
+ aout->dev = &pdev->dev;
+ platform_set_drvdata(pdev, aout);
+
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
+
+ /* Finally, register the component so simple-audio-card can detect it */
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &rp1_aout_component,
+ &rp1_aout_dai, 1);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
+
+ return 0;
+}
+
+static void rp1_aout_platform_remove(struct platform_device *pdev)
+{
+ struct rp1_aout *aout = platform_get_drvdata(pdev);
+
+ if (aout) {
+ /*
+ * We leave the PWM carrier/bias running between playbacks,
+ * but mute it just before shutting down, to avoid a click
+ * (devm should clear up everything else).
+ */
+ if (aout->initted) {
+ audio_mute_sync(aout);
+ audio_muting(aout,
+ AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
+ audio_mute_sync(aout);
+ aout->initted = false;
+ }
+ clk_disable_unprepare(aout->clk);
+ }
+}
+
+static const struct of_device_id rp1_aout_of_match[] = {
+ { .compatible = "raspberrypi,rp1-audio-out", },
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1_aout_of_match);
+
+static struct platform_driver rp1_audio_out_driver = {
+ .probe = rp1_aout_platform_probe,
+ .remove = rp1_aout_platform_remove,
+ .shutdown = rp1_aout_platform_remove,
+ .driver = {
+ .name = "rp1-audio-out",
+ .of_match_table = rp1_aout_of_match,
+ },
+};
+
+module_platform_driver(rp1_audio_out_driver);
+
+MODULE_DESCRIPTION("RP1 Audio out");
+MODULE_AUTHOR("Nick Hollinghurst ");
+MODULE_AUTHOR("Jonathan Bell ");
+MODULE_LICENSE("GPL");