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");