summaryrefslogtreecommitdiff
path: root/drivers/sound
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sound')
-rw-r--r--drivers/sound/Kconfig9
-rw-r--r--drivers/sound/Makefile1
-rw-r--r--drivers/sound/rockchip_i2s.c149
-rw-r--r--drivers/sound/rockchip_sound.c132
4 files changed, 291 insertions, 0 deletions
diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig
index c0d97cca33..22e379681d 100644
--- a/drivers/sound/Kconfig
+++ b/drivers/sound/Kconfig
@@ -21,6 +21,15 @@ config I2S
I2S. It calls either of the two supported codecs (no use is made
of driver model at present).
+config I2S_ROCKCHIP
+ bool "Enable I2S support for Rockchip SoCs"
+ depends on I2S
+ help
+ Rockchip SoCs support an I2S interface for sending audio data to an
+ audio codec. This option enables support for this, using one of the
+ available audio codec drivers. This driver does not make use of
+ DMA, but writes each word directly to the hardware.
+
config I2S_SAMSUNG
bool "Enable I2C support for Samsung SoCs"
depends on SOUND
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index 1de4346ec7..d0bf51bab6 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_SOUND) += i2s-uclass.o
obj-$(CONFIG_SOUND) += sound-uclass.o
obj-$(CONFIG_I2S_SAMSUNG) += samsung-i2s.o
obj-$(CONFIG_SOUND_SANDBOX) += sandbox.o
+obj-$(CONFIG_I2S_ROCKCHIP) += rockchip_i2s.o rockchip_sound.o
obj-$(CONFIG_I2S_SAMSUNG) += samsung_sound.o
obj-$(CONFIG_SOUND_WM8994) += wm8994.o
obj-$(CONFIG_SOUND_MAX98090) += max98090.o maxim_codec.o
diff --git a/drivers/sound/rockchip_i2s.c b/drivers/sound/rockchip_i2s.c
new file mode 100644
index 0000000000..e5df8ca0d2
--- /dev/null
+++ b/drivers/sound/rockchip_i2s.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Google LLC
+ * Copyright 2014 Rockchip Electronics Co., Ltd.
+ * Taken from dc i2s/rockchip.c
+ */
+
+#define LOG_CATEGORY UCLASS_I2S
+
+#include <common.h>
+#include <dm.h>
+#include <i2s.h>
+#include <sound.h>
+#include <asm/io.h>
+
+struct rk_i2s_regs {
+ u32 txcr; /* I2S_TXCR, 0x00 */
+ u32 rxcr; /* I2S_RXCR, 0x04 */
+ u32 ckr; /* I2S_CKR, 0x08 */
+ u32 fifolr; /* I2S_FIFOLR, 0x0C */
+ u32 dmacr; /* I2S_DMACR, 0x10 */
+ u32 intcr; /* I2S_INTCR, 0x14 */
+ u32 intsr; /* I2S_INTSR, 0x18 */
+ u32 xfer; /* I2S_XFER, 0x1C */
+ u32 clr; /* I2S_CLR, 0x20 */
+ u32 txdr; /* I2S_TXDR, 0x24 */
+ u32 rxdr; /* I2S_RXDR, 0x28 */
+};
+
+enum {
+ /* I2S_XFER */
+ I2S_RX_TRAN_BIT = BIT(1),
+ I2S_TX_TRAN_BIT = BIT(0),
+ I2S_TRAN_MASK = 3 << 0,
+
+ /* I2S_TXCKR */
+ I2S_MCLK_DIV_SHIFT = 16,
+ I2S_MCLK_DIV_MASK = (0xff << I2S_MCLK_DIV_SHIFT),
+
+ I2S_RX_SCLK_DIV_SHIFT = 8,
+ I2S_RX_SCLK_DIV_MASK = 0xff << I2S_RX_SCLK_DIV_SHIFT,
+ I2S_TX_SCLK_DIV_SHIFT = 0,
+ I2S_TX_SCLK_DIV_MASK = 0xff << I2S_TX_SCLK_DIV_SHIFT,
+
+ I2S_DATA_WIDTH_SHIFT = 0,
+ I2S_DATA_WIDTH_MASK = 0x1f << I2S_DATA_WIDTH_SHIFT,
+};
+
+static int rockchip_i2s_init(struct i2s_uc_priv *priv)
+{
+ struct rk_i2s_regs *regs = (struct rk_i2s_regs *)priv->base_address;
+ u32 bps = priv->bitspersample;
+ u32 lrf = priv->rfs;
+ u32 chn = priv->channels;
+ u32 mode = 0;
+
+ clrbits_le32(&regs->xfer, I2S_TX_TRAN_BIT);
+ mode = readl(&regs->txcr) & ~0x1f;
+ switch (priv->bitspersample) {
+ case 16:
+ case 24:
+ mode |= (priv->bitspersample - 1) << I2S_DATA_WIDTH_SHIFT;
+ break;
+ default:
+ log_err("Invalid sample size input %d\n", priv->bitspersample);
+ return -EINVAL;
+ }
+ writel(mode, &regs->txcr);
+
+ mode = readl(&regs->ckr) & ~I2S_MCLK_DIV_MASK;
+ mode |= (lrf / (bps * chn) - 1) << I2S_MCLK_DIV_SHIFT;
+
+ mode &= ~I2S_TX_SCLK_DIV_MASK;
+ mode |= (priv->bitspersample * priv->channels - 1) <<
+ I2S_TX_SCLK_DIV_SHIFT;
+ writel(mode, &regs->ckr);
+
+ return 0;
+}
+
+static int i2s_send_data(struct rk_i2s_regs *regs, u32 *data, uint length)
+{
+ for (int i = 0; i < min(32u, length); i++)
+ writel(*data++, &regs->txdr);
+
+ length -= min(32u, length);
+
+ /* enable both tx and rx */
+ setbits_le32(&regs->xfer, I2S_TRAN_MASK);
+ while (length) {
+ if ((readl(&regs->fifolr) & 0x3f) < 0x20) {
+ writel(*data++, &regs->txdr);
+ length--;
+ }
+ }
+ while (readl(&regs->fifolr) & 0x3f)
+ /* wait until FIFO empty */;
+ clrbits_le32(&regs->xfer, I2S_TRAN_MASK);
+ writel(0, &regs->clr);
+
+ return 0;
+}
+
+static int rockchip_i2s_tx_data(struct udevice *dev, void *data, uint data_size)
+{
+ struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
+ struct rk_i2s_regs *regs = (struct rk_i2s_regs *)priv->base_address;
+
+ return i2s_send_data(regs, data, data_size / sizeof(u32));
+}
+
+static int rockchip_i2s_probe(struct udevice *dev)
+{
+ struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
+ ulong base;
+
+ base = dev_read_addr(dev);
+ if (base == FDT_ADDR_T_NONE) {
+ log_debug("Missing i2s base\n");
+ return -EINVAL;
+ }
+ priv->base_address = base;
+ priv->id = 1;
+ priv->audio_pll_clk = 4800000;
+ priv->samplingrate = 48000;
+ priv->bitspersample = 16;
+ priv->channels = 2;
+ priv->rfs = 256;
+ priv->bfs = 32;
+
+ return rockchip_i2s_init(priv);
+}
+
+static const struct i2s_ops rockchip_i2s_ops = {
+ .tx_data = rockchip_i2s_tx_data,
+};
+
+static const struct udevice_id rockchip_i2s_ids[] = {
+ { .compatible = "rockchip,rk3288-i2s" },
+ { }
+};
+
+U_BOOT_DRIVER(rockchip_i2s) = {
+ .name = "rockchip_i2s",
+ .id = UCLASS_I2S,
+ .of_match = rockchip_i2s_ids,
+ .probe = rockchip_i2s_probe,
+ .ops = &rockchip_i2s_ops,
+};
diff --git a/drivers/sound/rockchip_sound.c b/drivers/sound/rockchip_sound.c
new file mode 100644
index 0000000000..e7fb9fb164
--- /dev/null
+++ b/drivers/sound/rockchip_sound.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Google, LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_SOUND
+
+#include <common.h>
+#include <audio_codec.h>
+#include <clk.h>
+#include <dm.h>
+#include <i2s.h>
+#include <misc.h>
+#include <sound.h>
+#include <asm/arch/periph.h>
+#include <dm/pinctrl.h>
+
+static int rockchip_sound_setup(struct udevice *dev)
+{
+ struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct i2s_uc_priv *i2c_priv = dev_get_uclass_priv(uc_priv->i2s);
+ int ret;
+
+ if (uc_priv->setup_done)
+ return -EALREADY;
+ ret = audio_codec_set_params(uc_priv->codec, i2c_priv->id,
+ i2c_priv->samplingrate,
+ i2c_priv->samplingrate * i2c_priv->rfs,
+ i2c_priv->bitspersample,
+ i2c_priv->channels);
+ if (ret)
+ return ret;
+ uc_priv->setup_done = true;
+
+ return 0;
+}
+
+static int rockchip_sound_play(struct udevice *dev, void *data, uint data_size)
+{
+ struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+
+ return i2s_tx_data(uc_priv->i2s, data, data_size);
+}
+
+static int rockchip_sound_probe(struct udevice *dev)
+{
+ struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct ofnode_phandle_args args;
+ struct udevice *pinctrl;
+ struct clk clk;
+ ofnode node;
+ int ret;
+
+ node = ofnode_find_subnode(dev_ofnode(dev), "cpu");
+ if (!ofnode_valid(node)) {
+ log_debug("Failed to find /cpu subnode\n");
+ return -EINVAL;
+ }
+ ret = ofnode_parse_phandle_with_args(node, "sound-dai",
+ "#sound-dai-cells", 0, 0, &args);
+ if (ret) {
+ log_debug("Cannot find i2s phandle: %d\n", ret);
+ return ret;
+ }
+ ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s);
+ if (ret) {
+ log_debug("Cannot find i2s: %d\n", ret);
+ return ret;
+ }
+
+ node = ofnode_find_subnode(dev_ofnode(dev), "codec");
+ if (!ofnode_valid(node)) {
+ log_debug("Failed to find /codec subnode\n");
+ return -EINVAL;
+ }
+ ret = ofnode_parse_phandle_with_args(node, "sound-dai",
+ "#sound-dai-cells", 0, 0, &args);
+ if (ret) {
+ log_debug("Cannot find codec phandle: %d\n", ret);
+ return ret;
+ }
+ ret = uclass_get_device_by_ofnode(UCLASS_AUDIO_CODEC, args.node,
+ &uc_priv->codec);
+ if (ret) {
+ log_debug("Cannot find audio codec: %d\n", ret);
+ return ret;
+ }
+ ret = clk_get_by_index(uc_priv->i2s, 1, &clk);
+ if (ret) {
+ log_debug("Cannot find clock: %d\n", ret);
+ return ret;
+ }
+ ret = clk_set_rate(&clk, 12288000);
+ if (ret < 0) {
+ log_debug("Cannot find clock: %d\n", ret);
+ return ret;
+ }
+ ret = uclass_get_device(UCLASS_PINCTRL, 0, &pinctrl);
+ if (ret) {
+ debug("%s: Cannot find pinctrl device\n", __func__);
+ return ret;
+ }
+ ret = pinctrl_request(pinctrl, PERIPH_ID_I2S, 0);
+ if (ret) {
+ debug("%s: Cannot select I2C pinctrl\n", __func__);
+ return ret;
+ }
+
+ log_debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name,
+ uc_priv->codec->name, uc_priv->i2s->name);
+
+ return 0;
+}
+
+static const struct sound_ops rockchip_sound_ops = {
+ .setup = rockchip_sound_setup,
+ .play = rockchip_sound_play,
+};
+
+static const struct udevice_id rockchip_sound_ids[] = {
+ { .compatible = "rockchip,audio-max98090-jerry" },
+ { }
+};
+
+U_BOOT_DRIVER(rockchip_sound) = {
+ .name = "rockchip_sound",
+ .id = UCLASS_SOUND,
+ .of_match = rockchip_sound_ids,
+ .probe = rockchip_sound_probe,
+ .ops = &rockchip_sound_ops,
+};