[PATCH 2/4] sound: bcm2835-analog-audio: Add driver for bcm2835(Raspberry PI) headphone jack.

Michael Zoran mzoran at crowfest.net
Sat Mar 11 22:38:18 PST 2017


Add driver for the bcm2835 analog/headphone jack which
uses the PWM hardware to generate audio.

Signed-off-by: Michael Zoran <mzoran at crowfest.net>
---
 sound/arm/bcm2835-analog-audio.c | 678 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 678 insertions(+)
 create mode 100644 sound/arm/bcm2835-analog-audio.c

diff --git a/sound/arm/bcm2835-analog-audio.c b/sound/arm/bcm2835-analog-audio.c
new file mode 100644
index 000000000000..c95456e16506
--- /dev/null
+++ b/sound/arm/bcm2835-analog-audio.c
@@ -0,0 +1,678 @@
+/*
+ * Michael Zoran <mzoran at crowfest.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2.
+ *
+ * bcm2835-analog-audio provides support for very simple analog
+ * audio using the PWM hardware of the bcm2835.  It is assumed
+ * that additional analog hardware is connected to the GPIO pins
+ * to amplify the audio and provide basic analog filtering.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <linux/of_address.h>
+#include <linux/dma-mapping.h>
+
+/*
+ *  PWM Register Offsets
+ */
+#define PWM_REG_CTR		0x00
+#define PWM_REG_STA		0x04
+#define PWM_REG_DMAC		0x08
+#define PWM_REG_RNG1		0x10
+#define PWM_REG_DAT1		0x14
+#define PWM_REG_FIFO		0x18
+#define PWM_REG_RNG2		0x20
+#define PWM_REG_DAT2		0x24
+
+#define PWM_CLOCK_FREQUENCY	100000000
+#define PWM_SAMPLE_RATE		48000
+#define PWM_SYMBOLS		(PWM_CLOCK_FREQUENCY / PWM_SAMPLE_RATE)
+#define PWM_DC_OFFSET		(PWM_SYMBOLS / 2)
+
+/*
+ * The channel order that needs to be passed to the PWM FIFO is opposite the
+ * order that is passed by the application.  So the order needs to be flipped
+ * in software.
+ */
+
+
+struct bcm2835_hardware_frame {
+	u32 right;
+	u32 left;
+};
+
+struct bcm2835_software_frame {
+	s16 left;
+	s16 right;
+};
+
+#define HARDWARE_BUFFER_FRAMES_PER_PERIOD 720
+#define HARDWARE_BUFFER_PERIODS_PER_BUFFER 2
+#define HARDWARE_BUFFER_FRAMES_PER_BUFFER (HARDWARE_BUFFER_FRAMES_PER_PERIOD * \
+					   HARDWARE_BUFFER_PERIODS_PER_BUFFER)
+#define HARDWARE_BUFFER_PERIOD_BYTES (sizeof(struct bcm2835_hardware_frame) * \
+				      HARDWARE_BUFFER_FRAMES_PER_PERIOD)
+#define HARDWARE_BUFFER_BYTES (HARDWARE_BUFFER_PERIOD_BYTES * \
+			       HARDWARE_BUFFER_PERIODS_PER_BUFFER)
+
+struct bcm2835_chip;
+
+struct bcm2835_chip_runtime {
+	struct bcm2835_chip *chip;
+	struct snd_pcm_substream *substream;
+	spinlock_t spinlock;
+	struct dma_slave_config dma_slave_config;
+	struct dma_async_tx_descriptor *dma_desc;
+	dma_cookie_t dma_cookie;
+	struct bcm2835_hardware_frame *hardware_buffer;
+	dma_addr_t hardware_buffer_dma;
+	int hardware_period_number;
+	bool is_playing;
+	struct bcm2835_software_frame *playback_src_buffer;
+	snd_pcm_uframes_t playback_src_pos;
+	snd_pcm_uframes_t playback_src_frames_this_period;
+};
+
+struct bcm2835_chip {
+	struct platform_device *pdev;
+	struct device *dev;
+	struct mutex lock;
+	u32 dma_addr;
+	void __iomem *base;
+	struct clk *clk;
+	int opencount;
+	struct dma_chan *dma_channel;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct bcm2835_chip_runtime *runtime;
+};
+
+static u32 convert_audio_data(s16 input)
+{
+	s32 output;
+
+	output = ((s32) input * (s32)(PWM_SYMBOLS / 2)) / (s32)32762;
+	return (u32)(output + PWM_DC_OFFSET);
+}
+
+static void convert_dma_buffer(struct bcm2835_chip_runtime *chip_runtime)
+{
+	snd_pcm_uframes_t i;
+	snd_pcm_uframes_t hardware_start_pos =
+		chip_runtime->hardware_period_number *
+		HARDWARE_BUFFER_FRAMES_PER_PERIOD;
+	snd_pcm_uframes_t buffer_size =
+		chip_runtime->substream->runtime->buffer_size;
+	struct bcm2835_hardware_frame *hard_frame =
+		chip_runtime->hardware_buffer + hardware_start_pos;
+
+	for (i = 0; i < HARDWARE_BUFFER_FRAMES_PER_PERIOD; i++) {
+		struct bcm2835_software_frame *soft_frame =
+			chip_runtime->playback_src_buffer +
+			chip_runtime->playback_src_pos;
+
+		hard_frame->left = convert_audio_data(soft_frame->left);
+		hard_frame->right = convert_audio_data(soft_frame->right);
+		hard_frame++;
+
+		if (chip_runtime->playback_src_pos >= buffer_size - 1)
+			chip_runtime->playback_src_pos = 0;
+		else
+			chip_runtime->playback_src_pos++;
+	}
+}
+
+static void fill_silence(struct bcm2835_chip_runtime *chip_runtime)
+{
+	snd_pcm_uframes_t i;
+	snd_pcm_uframes_t hardware_start_pos =
+		chip_runtime->hardware_period_number *
+		HARDWARE_BUFFER_FRAMES_PER_PERIOD;
+	struct bcm2835_hardware_frame *hard_frame =
+		chip_runtime->hardware_buffer + hardware_start_pos;
+
+	for (i = 0; i < HARDWARE_BUFFER_FRAMES_PER_PERIOD; i++) {
+		hard_frame->left = PWM_DC_OFFSET;
+		hard_frame->right = PWM_DC_OFFSET;
+		hard_frame++;
+	}
+}
+
+static void dma_complete(void *arg)
+{
+	struct bcm2835_chip_runtime *chip_runtime = arg;
+	unsigned long flags;
+	bool period_elapsed = false;
+
+	spin_lock_irqsave(&chip_runtime->spinlock, flags);
+
+	chip_runtime->hardware_period_number++;
+	if (chip_runtime->hardware_period_number >=
+		HARDWARE_BUFFER_PERIODS_PER_BUFFER)
+		chip_runtime->hardware_period_number = 0;
+
+	if (!chip_runtime->is_playing)
+		fill_silence(chip_runtime);
+	else {
+		chip_runtime->playback_src_frames_this_period +=
+			HARDWARE_BUFFER_FRAMES_PER_PERIOD;
+
+		if (chip_runtime->playback_src_frames_this_period >=
+			chip_runtime->substream->runtime->period_size) {
+			chip_runtime->playback_src_frames_this_period = 0;
+			period_elapsed = true;
+		}
+
+		convert_dma_buffer(chip_runtime);
+	}
+
+	spin_unlock_irqrestore(&chip_runtime->spinlock, flags);
+
+	if (period_elapsed)
+		snd_pcm_period_elapsed(chip_runtime->substream);
+}
+
+static void snd_bcm2835_cleanup_runtime(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime = chip->runtime;
+
+	if (!chip_runtime)
+		return;
+
+	if (chip_runtime->dma_cookie)
+		dmaengine_terminate_sync(chip->dma_channel);
+
+	writel(0x00, chip->base + PWM_REG_CTR);
+	writel(0x00, chip->base + PWM_REG_DMAC);
+
+	if (chip_runtime->dma_desc)
+		dmaengine_desc_free(chip_runtime->dma_desc);
+
+	if (chip_runtime->hardware_buffer)
+		dma_free_coherent(chip->dma_channel->device->dev,
+			HARDWARE_BUFFER_BYTES,
+			chip_runtime->hardware_buffer,
+			chip_runtime->hardware_buffer_dma);
+
+	chip->runtime = NULL;
+	kfree(chip_runtime);
+}
+
+static int snd_bcm2835_init_runtime(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime;
+	int err;
+	int i;
+
+	if (chip->runtime)
+		return 0;
+
+	chip_runtime = kzalloc(sizeof(*chip_runtime), GFP_KERNEL);
+
+	if (!chip_runtime)
+		return -ENOMEM;
+
+	chip_runtime->chip = chip;
+	chip_runtime->substream = substream;
+	spin_lock_init(&chip_runtime->spinlock);
+	chip->runtime = chip_runtime;
+
+	chip_runtime->hardware_buffer =
+		dma_alloc_coherent(chip->dma_channel->device->dev,
+				   HARDWARE_BUFFER_BYTES,
+				   &chip_runtime->hardware_buffer_dma,
+				   GFP_KERNEL);
+
+	if (!chip_runtime->hardware_buffer) {
+		snd_bcm2835_cleanup_runtime(substream);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < HARDWARE_BUFFER_FRAMES_PER_BUFFER; i++) {
+		chip_runtime->hardware_buffer[i].left = PWM_DC_OFFSET;
+		chip_runtime->hardware_buffer[i].right = PWM_DC_OFFSET;
+	}
+
+	chip_runtime->hardware_period_number =
+		(HARDWARE_BUFFER_PERIODS_PER_BUFFER - 1);
+
+	chip_runtime->dma_slave_config.direction	= DMA_MEM_TO_DEV;
+	chip_runtime->dma_slave_config.dst_addr		= chip->dma_addr;
+	chip_runtime->dma_slave_config.dst_maxburst	= 2;
+	chip_runtime->dma_slave_config.dst_addr_width	= 4;
+	chip_runtime->dma_slave_config.src_addr		=
+		chip_runtime->hardware_buffer_dma;
+	chip_runtime->dma_slave_config.src_maxburst	= 2;
+	chip_runtime->dma_slave_config.src_addr_width	= 4;
+
+	err = dmaengine_slave_config(chip->dma_channel,
+				     &chip_runtime->dma_slave_config);
+
+	if (err < 0) {
+		snd_bcm2835_cleanup_runtime(substream);
+		return err;
+	}
+
+	chip_runtime->dma_desc =
+		dmaengine_prep_dma_cyclic(chip->dma_channel,
+					  chip_runtime->hardware_buffer_dma,
+					  HARDWARE_BUFFER_BYTES,
+					  HARDWARE_BUFFER_PERIOD_BYTES,
+					  DMA_MEM_TO_DEV,
+					  DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+
+	if (!chip_runtime->dma_desc) {
+		snd_bcm2835_cleanup_runtime(substream);
+		return -ENOMEM;
+	}
+
+	chip_runtime->dma_desc->callback = dma_complete;
+	chip_runtime->dma_desc->callback_param = chip_runtime;
+
+	writel(PWM_SYMBOLS,	chip->base + PWM_REG_RNG1);
+	writel(PWM_SYMBOLS,	chip->base + PWM_REG_RNG2);
+	writel(0xa1e1,		chip->base + PWM_REG_CTR);
+	writel(0x80000E0E,	chip->base + PWM_REG_DMAC);
+
+	chip_runtime->dma_cookie = dmaengine_submit(chip_runtime->dma_desc);
+	dma_async_issue_pending(chip->dma_channel);
+
+	return 0;
+
+}
+static struct snd_pcm_hardware snd_bcm2835_playback_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 128 * 1024,
+	.period_bytes_min = 4 * 1024,
+	.period_bytes_max = 128 * 1024,
+	.periods_min = 1,
+	.periods_max = 128 / 4,
+	.fifo_size = 0,
+};
+
+static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
+{
+	runtime->private_data = NULL;
+}
+
+static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if (mutex_lock_interruptible(&chip->lock))
+		return -EINTR;
+
+	if (chip->opencount) {
+		chip->opencount++;
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	clk_set_rate(chip->clk, PWM_CLOCK_FREQUENCY);
+	err = clk_prepare_enable(chip->clk);
+	if (err)
+		return err;
+
+	err = snd_bcm2835_init_runtime(substream);
+	if (err) {
+		clk_disable_unprepare(chip->clk);
+		mutex_unlock(&chip->lock);
+		return err;
+	}
+
+	chip->opencount++;
+
+	runtime->hw = snd_bcm2835_playback_hw;
+	runtime->private_data = chip->runtime;
+	runtime->private_free = snd_bcm2835_playback_free;
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+}
+
+static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (mutex_lock_interruptible(&chip->lock))
+		return -EINTR;
+
+	if (!chip->opencount) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	chip->opencount--;
+	if (chip->opencount) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	snd_bcm2835_cleanup_runtime(substream);
+	clk_disable_unprepare(chip->clk);
+
+	runtime->private_data = NULL;
+	runtime->private_free = NULL;
+
+	mutex_unlock(&chip->lock);
+	return 0;
+}
+
+static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime = chip->runtime;
+	snd_pcm_uframes_t playback_src_buffer_frames;
+	snd_pcm_uframes_t playback_src_period_frames;
+	int err = 0;
+
+	if (mutex_lock_interruptible(&chip->lock))
+		return -EINTR;
+
+	snd_bcm2835_pcm_hw_free(substream);
+
+	playback_src_buffer_frames = params_buffer_bytes(params) / 4;
+	playback_src_period_frames = params_period_bytes(params) / 4;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (err < 0) {
+		snd_bcm2835_pcm_hw_free(substream);
+		mutex_unlock(&chip->lock);
+		return err;
+	}
+
+	chip_runtime->playback_src_buffer =
+		(struct bcm2835_software_frame *)(substream->runtime->dma_area);
+	chip_runtime->playback_src_pos = 0;
+	chip_runtime->playback_src_frames_this_period = 0;
+
+	mutex_unlock(&chip->lock);
+	return 0;
+}
+
+static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime = chip->runtime;
+
+	if (mutex_lock_interruptible(&chip->lock))
+		return -EINTR;
+
+	chip_runtime->playback_src_buffer =
+		(struct bcm2835_software_frame *)(substream->runtime->dma_area);
+	chip_runtime->playback_src_pos = 0;
+	chip_runtime->playback_src_frames_this_period = 0;
+
+	memset(chip_runtime->playback_src_buffer, 0,
+		substream->runtime->buffer_size *
+		sizeof(*(chip_runtime->playback_src_buffer)));
+
+	mutex_unlock(&chip->lock);
+	return 0;
+}
+
+static int snd_bcm2835_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime = chip->runtime;
+	int ret = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip_runtime->spinlock, flags);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip_runtime->is_playing = true;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		chip_runtime->is_playing = false;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&chip_runtime->spinlock, flags);
+
+	return ret;
+}
+
+static snd_pcm_uframes_t
+snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+	struct bcm2835_chip_runtime *chip_runtime = chip->runtime;
+	snd_pcm_uframes_t ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip_runtime->spinlock, flags);
+	ret = chip_runtime->playback_src_pos;
+	spin_unlock_irqrestore(&chip_runtime->spinlock, flags);
+
+	return ret;
+}
+
+static struct snd_pcm_ops snd_bcm2835_playback_ops = {
+	.open = snd_bcm2835_playback_open,
+	.close = snd_bcm2835_playback_close,
+	.ioctl =  snd_pcm_lib_ioctl,
+	.hw_params = snd_bcm2835_pcm_hw_params,
+	.hw_free = snd_bcm2835_pcm_hw_free,
+	.prepare = snd_bcm2835_pcm_prepare,
+	.trigger = snd_bcm2835_pcm_trigger,
+	.pointer = snd_bcm2835_pcm_pointer,
+};
+
+static int snd_bcm2835_new_pcm(struct bcm2835_chip *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, "BCM2835 Analog", 0, 1, 0, &pcm);
+
+	if (err < 0)
+		return err;
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "BCM2835 Analog");
+	chip->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+		&snd_bcm2835_playback_ops);
+
+	/* pre-allocation of buffers */
+	/* NOTE: this may fail */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL),
+		snd_bcm2835_playback_hw.buffer_bytes_max,
+		snd_bcm2835_playback_hw.buffer_bytes_max);
+
+	return 0;
+}
+
+static int snd_bcm2835_free(struct bcm2835_chip *chip)
+{
+	if (chip->dma_channel)
+		dma_release_channel(chip->dma_channel);
+
+	return 0;
+}
+
+static int snd_bcm2835_dev_free(struct snd_device *device)
+{
+	return snd_bcm2835_free(device->device_data);
+}
+
+static struct snd_device_ops snd_bcm2835_dev_ops = {
+	.dev_free = snd_bcm2835_dev_free,
+};
+
+static int snd_bcm2835_create(struct snd_card *card,
+	struct platform_device *pdev,
+	struct bcm2835_chip **rchip)
+{
+	struct bcm2835_chip *chip;
+	struct resource *res;
+	int err;
+	const __be32 *addr;
+
+	*rchip = NULL;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->pdev = pdev;
+	chip->dev = &pdev->dev;
+	chip->card = card;
+
+	mutex_init(&chip->lock);
+
+	/*
+	 * Get the physical address of the PWM FIFO. We need to retrieve
+	 * the bus address specified in the DT, because the physical address
+	 * (the one returned by platform_get_resource()) is not appropriate
+	 * for DMA transfers.
+	 */
+	addr = of_get_address(chip->dev->of_node, 0, NULL, NULL);
+	chip->dma_addr = be32_to_cpup(addr) + PWM_REG_FIFO;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	chip->base = devm_ioremap_resource(chip->dev, res);
+	if (IS_ERR(chip->base))
+		return PTR_ERR(chip->base);
+
+	chip->clk = devm_clk_get(chip->dev, NULL);
+	if (IS_ERR(chip->clk)) {
+		dev_err(&pdev->dev, "clock not found: %ld\n",
+			PTR_ERR(chip->clk));
+		return PTR_ERR(chip->clk);
+	}
+
+	chip->dma_channel = dma_request_slave_channel(chip->dev, "tx");
+
+	if (!chip->dma_channel)
+		return -ENOMEM;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip,
+		&snd_bcm2835_dev_ops);
+
+	if (err < 0) {
+		snd_bcm2835_free(chip);
+		return err;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static int bcm2835_analog_audio_probe(struct platform_device *pdev)
+{
+	struct snd_card *card;
+	int ret;
+	struct device *dev = &pdev->dev;
+	struct bcm2835_chip *chip;
+
+	ret = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
+	if (ret) {
+		dev_err(dev, "Failed to create sound card structure\n");
+		return ret;
+	}
+
+	ret = snd_bcm2835_create(card, pdev, &chip);
+	if (ret < 0) {
+		dev_err(dev, "Failed to create bcm2835 chip\n");
+		return ret;
+	}
+
+	snd_card_set_dev(card, dev);
+	strcpy(card->driver, "BCM2835 Analog");
+	strcpy(card->shortname, "BCM2835 Analog");
+	sprintf(card->longname, "%s", card->shortname);
+
+	ret = snd_bcm2835_new_pcm(chip);
+	if (ret < 0) {
+		snd_card_free(card);
+		return ret;
+	}
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		snd_card_free(card);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, card);
+
+	dev_notice(dev, "BCM2835 Analog Audio Initialized\n");
+
+	return 0;
+}
+
+static int bcm2835_analog_audio_remove(struct platform_device *pdev)
+{
+	struct snd_card *card;
+
+	card = platform_get_drvdata(pdev);
+
+	if (card)
+		snd_card_free(card);
+
+	return 0;
+}
+
+static const struct of_device_id bcm2835_analog_audio_of_match[] = {
+	{ .compatible = "brcm,bcm2835-analog-audio",},
+	{ /* sentinel */}
+};
+MODULE_DEVICE_TABLE(of, bcm2835_analog_audio_of_match);
+
+static struct platform_driver bcm2835_analog_audio_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "bcm2835-analog-audio",
+		.of_match_table = bcm2835_analog_audio_of_match,
+	},
+	.probe = bcm2835_analog_audio_probe,
+	.remove = bcm2835_analog_audio_remove,
+};
+module_platform_driver(bcm2835_analog_audio_driver);
+
+MODULE_AUTHOR("Michael Zoran");
+MODULE_DESCRIPTION("Audio driver for analog output on the BCM2835 chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:brcm,bcm2835-analog-audio");
-- 
2.11.0




More information about the linux-rpi-kernel mailing list