sound: Add new driver for GPIO based PDM output

This is a new driver for outputting PDM data via two GPIOs,
supporting mono and stereo PDM formats.

The driver does not attempt to anything special with timing,
it just sends the data as fast as the GPIO interface for whatever
system is being used will support.  This may need to be tweaked
in the future but for now the current platform this is used on
the GPIO interface is the bottleneck and there is not need to
try and modulate the output stream.

It has a facility to start the PDM clock before sending data in
case the codec that it is talking to needs the PDM clock active
for its own internal clocking before it can handle data.

BUG=chrome-os-partner:42283
BRANCH=none
TEST=test audio beep on glados

Change-Id: I8b6c149d324153a03c5b56b36e872bddaf06945e
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/301384
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
diff --git a/src/drivers/sound/Kconfig b/src/drivers/sound/Kconfig
index f6c5880..a00b861 100644
--- a/src/drivers/sound/Kconfig
+++ b/src/drivers/sound/Kconfig
@@ -14,6 +14,10 @@
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 
+config DRIVER_SOUND_GPIO_PDM
+	bool "PDM driver based on bit-bang GPIO"
+	default n
+
 config DRIVER_SOUND_HDA
 	bool "HDA codec driver"
 	default n
diff --git a/src/drivers/sound/Makefile.inc b/src/drivers/sound/Makefile.inc
index 85fd3c9..23bc4ed 100644
--- a/src/drivers/sound/Makefile.inc
+++ b/src/drivers/sound/Makefile.inc
@@ -15,6 +15,7 @@
 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 ##
 
+depthcharge-$(CONFIG_DRIVER_SOUND_GPIO_PDM) += gpio_pdm.c
 depthcharge-$(CONFIG_DRIVER_SOUND_HDA) += hda_codec.c
 depthcharge-$(CONFIG_DRIVER_SOUND_I2S) += i2s.c
 depthcharge-$(CONFIG_DRIVER_SOUND_IPQ806X) += ipq806x.c
diff --git a/src/drivers/sound/gpio_pdm.c b/src/drivers/sound/gpio_pdm.c
new file mode 100644
index 0000000..a78633a
--- /dev/null
+++ b/src/drivers/sound/gpio_pdm.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#include <assert.h>
+#include <libpayload.h>
+#include <stdint.h>
+
+#include "base/container_of.h"
+#include "config.h"
+#include "drivers/sound/gpio_pdm.h"
+
+/* Square wave with 1 channel */
+static void pdm_square_wave(uint16_t *data, unsigned samples,
+			    uint32_t freq, uint16_t volume)
+{
+	unsigned period = samples / freq;
+	unsigned half = period / 2;
+
+	while (samples) {
+		for (int i = 0; samples && i < half; samples--, i++)
+			*data++ = volume;
+		for (int i = 0; samples && i < period - half; samples--, i++)
+			*data++ = -volume;
+	}
+}
+
+/* Send PDM data bit, left and right channels */
+static void gpio_pdm_send_bit(GpioPdm *pdm, int bit)
+{
+	switch (pdm->channels) {
+	case 1:
+		/* Mono: send data on both clock edges */
+		pdm->data_gpio->set(pdm->data_gpio, bit);
+
+		/* Toggle Clock */
+		pdm->clock_gpio->set(pdm->clock_gpio,
+			     pdm->clock_gpio->get(pdm->clock_gpio) ^ 1);
+		break;
+
+	case 2:
+		/* Stereo: left channel on rising edge */
+		pdm->data_gpio->set(pdm->data_gpio, bit);
+		pdm->clock_gpio->set(pdm->clock_gpio, 1);
+
+		/* Stereo: right channel on falling edge */
+		pdm->data_gpio->set(pdm->data_gpio, bit);
+		pdm->clock_gpio->set(pdm->clock_gpio, 0);
+		break;
+
+	default:
+		printf("%s: Unsupported channel count %d\n",
+		       __func__, pdm->channels);
+	}
+}
+
+/* Start PDM clock with no data */
+static void gpio_pdm_clock_start(GpioPdm *pdm)
+{
+	for (int i = 0; i < pdm->clock_start; i++)
+		gpio_pdm_send_bit(pdm, 0);
+}
+
+/* Send PDM data */
+static void gpio_pdm_send(GpioPdm *pdm, uint16_t *data, size_t length)
+{
+	for (; length > 0; length--, data++)
+		for (int i = 0; i < 16; i++)
+			gpio_pdm_send_bit(pdm, !!(*data & (1 << i)));
+}
+
+static int gpio_pdm_play(SoundOps *me, uint32_t msec, uint32_t frequency)
+{
+	GpioPdm *pdm = container_of(me, GpioPdm, ops);
+
+	/* Prepare a buffer for 1 second of sound. */
+	unsigned bytes = pdm->sample_rate * sizeof(uint16_t);
+	uint16_t *data = xmalloc(bytes);
+
+	/* Generate a square wave tone with one channel */
+	pdm_square_wave(data, pdm->sample_rate, frequency, pdm->volume);
+
+	/* Start the PDM clock */
+	gpio_pdm_clock_start(pdm);
+
+	/* Send 1 second chunks */
+	while (msec >= 1000) {
+		gpio_pdm_send(pdm, data, bytes / sizeof(uint16_t));
+		msec -= 1000;
+	}
+
+	/* Send remaining portion of a second */
+	if (msec) {
+		size_t len = (bytes * msec) / (sizeof(uint16_t) * 1000);
+		gpio_pdm_send(pdm, data, len);
+	}
+
+	free(data);
+	return 0;
+}
+
+GpioPdm *new_gpio_pdm(GpioOps *clock_gpio, GpioOps *data_gpio,
+		      unsigned clock_start, unsigned sample_rate,
+		      unsigned channels, uint16_t volume)
+{
+	GpioPdm *pdm = xzalloc(sizeof(*pdm));
+
+	pdm->ops.play    = &gpio_pdm_play;
+
+	pdm->clock_gpio  = clock_gpio;
+	pdm->data_gpio   = data_gpio;
+
+	pdm->clock_start = clock_start;
+	pdm->sample_rate = sample_rate;
+	pdm->channels    = channels;
+	pdm->volume      = volume;
+
+	return pdm;
+}
diff --git a/src/drivers/sound/gpio_pdm.h b/src/drivers/sound/gpio_pdm.h
new file mode 100644
index 0000000..cda4dce
--- /dev/null
+++ b/src/drivers/sound/gpio_pdm.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc.
+ */
+
+#ifndef __DRIVERS_SOUND_GPIO_PDM_H__
+#define __DRIVERS_SOUND_GPIO_PDM_H__
+
+#include "drivers/gpio/gpio.h"
+#include "drivers/sound/sound.h"
+
+typedef struct
+{
+	SoundOps ops;
+
+	/* GPIO to use for PDM Clock */
+	GpioOps *clock_gpio;
+	/* GPIO to use for PDM Data */
+	GpioOps *data_gpio;
+
+	/* Number of empty clock cycles to send before data */
+	unsigned clock_start;
+	/* Based on codec and how fast the platform can toggle GPIOs */
+	unsigned sample_rate;
+	/* Supports Mono=1 Stereo=2 */
+	unsigned channels;
+	/* Amplitude for square wave tone */
+	uint16_t volume;
+} GpioPdm;
+
+GpioPdm *new_gpio_pdm(GpioOps *clock_gpio, GpioOps *data_gpio,
+		      unsigned clock_start, unsigned sample_rate,
+		      unsigned channels, uint16_t volume);
+
+#endif /* __DRIVERS_SOUND_GPIO_PDM_H__ */