dm: sound: Create a uclass for i2s

The i2s bus is commonly used with audio codecs. It provides a way to
stream digital data sychronously in both directions. U-Boot only supports
audio output, so this uclass is very simple, with a single tx_data()
method.

Add a uclass and a test for i2s.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index ae5fabe..4aced9d 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -5,6 +5,7 @@
 
 obj-$(CONFIG_SOUND)	+= sound.o
 obj-$(CONFIG_DM_SOUND)	+= codec-uclass.o
+obj-$(CONFIG_DM_SOUND)	+= i2s-uclass.o
 obj-$(CONFIG_I2S)	+= sound-i2s.o
 obj-$(CONFIG_I2S_SAMSUNG)	+= samsung-i2s.o
 obj-$(CONFIG_SOUND_SANDBOX)	+= sandbox.o
diff --git a/drivers/sound/i2s-uclass.c b/drivers/sound/i2s-uclass.c
new file mode 100644
index 0000000..b741e39
--- /dev/null
+++ b/drivers/sound/i2s-uclass.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <i2s.h>
+
+int i2s_tx_data(struct udevice *dev, void *data, uint data_size)
+{
+	struct i2s_ops *ops = i2s_get_ops(dev);
+
+	if (!ops->tx_data)
+		return -ENOSYS;
+
+	return ops->tx_data(dev, data, data_size);
+}
+
+UCLASS_DRIVER(i2s) = {
+	.id		= UCLASS_I2S,
+	.name		= "i2s",
+	.per_device_auto_alloc_size	= sizeof(struct i2s_uc_priv),
+};
diff --git a/drivers/sound/sandbox.c b/drivers/sound/sandbox.c
index d24eb9a..2f7c68b 100644
--- a/drivers/sound/sandbox.c
+++ b/drivers/sound/sandbox.c
@@ -4,8 +4,9 @@
  */
 
 #include <common.h>
-#include <dm.h>
 #include <audio_codec.h>
+#include <dm.h>
+#include <i2s.h>
 #include <asm/sound.h>
 #include <asm/sdl.h>
 
@@ -17,6 +18,10 @@
 	uint channels;
 };
 
+struct sandbox_i2s_priv {
+	int sum;	/* Use to sum the provided audio data */
+};
+
 int sound_play(uint32_t msec, uint32_t frequency)
 {
 	sandbox_sdl_sound_start(frequency);
@@ -44,6 +49,13 @@
 	*channelsp = priv->channels;
 }
 
+int sandbox_get_i2s_sum(struct udevice *dev)
+{
+	struct sandbox_i2s_priv *priv = dev_get_priv(dev);
+
+	return priv->sum;
+}
+
 static int sandbox_codec_set_params(struct udevice *dev, int interface,
 				    int rate, int mclk_freq,
 				    int bits_per_sample, uint channels)
@@ -59,6 +71,34 @@
 	return 0;
 }
 
+static int sandbox_i2s_tx_data(struct udevice *dev, void *data,
+			       uint data_size)
+{
+	struct sandbox_i2s_priv *priv = dev_get_priv(dev);
+	int i;
+
+	for (i = 0; i < data_size; i++)
+		priv->sum += ((uint8_t *)data)[i];
+
+	return 0;
+}
+
+static int sandbox_i2s_probe(struct udevice *dev)
+{
+	struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	/* Use hard-coded values here */
+	uc_priv->rfs = 256;
+	uc_priv->bfs = 32;
+	uc_priv->audio_pll_clk = 192000000;
+	uc_priv->samplingrate = 48000;
+	uc_priv->bitspersample = 16;
+	uc_priv->channels = 2;
+	uc_priv->id = 1;
+
+	return 0;
+}
+
 static const struct audio_codec_ops sandbox_codec_ops = {
 	.set_params	= sandbox_codec_set_params,
 };
@@ -75,3 +115,21 @@
 	.ops		= &sandbox_codec_ops,
 	.priv_auto_alloc_size	= sizeof(struct sandbox_codec_priv),
 };
+
+static const struct i2s_ops sandbox_i2s_ops = {
+	.tx_data	= sandbox_i2s_tx_data,
+};
+
+static const struct udevice_id sandbox_i2s_ids[] = {
+	{ .compatible = "sandbox,i2s" },
+	{ }
+};
+
+U_BOOT_DRIVER(sandbox_i2s) = {
+	.name		= "sandbox_i2s",
+	.id		= UCLASS_I2S,
+	.of_match	= sandbox_i2s_ids,
+	.ops		= &sandbox_i2s_ops,
+	.probe		= sandbox_i2s_probe,
+	.priv_auto_alloc_size	= sizeof(struct sandbox_i2s_priv),
+};