aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2019-05-29 07:28:40 -0400
committerTom Rini <trini@konsulko.com>2019-05-29 07:28:40 -0400
commite2822ccc2c7c0ba8d7d959a1fa1e6221ed135423 (patch)
tree9c8f78e7531db52f7fe3d02130cf93001f3f16cd /drivers
parent93294caaeb67c5b087ea080cb644eefea52da560 (diff)
parent430cfc861be2e5aada26cd96d97f3b9911782b16 (diff)
downloadu-boot-e2822ccc2c7c0ba8d7d959a1fa1e6221ed135423.zip
u-boot-e2822ccc2c7c0ba8d7d959a1fa1e6221ed135423.tar.gz
u-boot-e2822ccc2c7c0ba8d7d959a1fa1e6221ed135423.tar.bz2
Merge branch 'master' of git://git.denx.de/u-boot-tegra
- Audio support
Diffstat (limited to 'drivers')
-rw-r--r--drivers/sound/Kconfig9
-rw-r--r--drivers/sound/Makefile1
-rw-r--r--drivers/sound/tegra_ahub.c256
-rw-r--r--drivers/sound/tegra_i2s.c123
-rw-r--r--drivers/sound/tegra_i2s_priv.h29
-rw-r--r--drivers/sound/tegra_sound.c100
6 files changed, 518 insertions, 0 deletions
diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig
index 6e9dcef..4ebc719 100644
--- a/drivers/sound/Kconfig
+++ b/drivers/sound/Kconfig
@@ -71,6 +71,15 @@ config SOUND_IVYBRIDGE
sometimes called Azalia. The audio codec is detected using a
semi-automatic mechanism.
+config I2S_TEGRA
+ bool "Enable I2S support for Nvidia Tegra SoCs"
+ depends on I2S
+ select TEGRA124_DMA
+ help
+ Nvidia Tegra SoCs support several I2S interfaces for sending audio
+ data to an audio codec. This option enables support for this,
+ using one of the available audio codec drivers.
+
config SOUND_MAX98088
bool "Support Maxim max98088 audio codec"
depends on I2S
diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile
index e155041..73ed7fe 100644
--- a/drivers/sound/Makefile
+++ b/drivers/sound/Makefile
@@ -11,6 +11,7 @@ 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_I2S_TEGRA) += tegra_ahub.o tegra_i2s.o tegra_sound.o
obj-$(CONFIG_SOUND_WM8994) += wm8994.o
obj-$(CONFIG_SOUND_MAX98088) += max98088.o maxim_codec.o
obj-$(CONFIG_SOUND_MAX98090) += max98090.o maxim_codec.o
diff --git a/drivers/sound/tegra_ahub.c b/drivers/sound/tegra_ahub.c
new file mode 100644
index 0000000..c71fce9
--- /dev/null
+++ b/drivers/sound/tegra_ahub.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0+159
+/*
+ * Take from dc tegra_ahub.c
+ *
+ * Copyright 2018 Google LLC
+ */
+
+#define LOG_CATEGORY UCLASS_MISC
+
+#include <common.h>
+#include <dm.h>
+#include <i2s.h>
+#include <misc.h>
+#include <asm/io.h>
+#include <asm/arch-tegra/tegra_ahub.h>
+#include <asm/arch-tegra/tegra_i2s.h>
+#include "tegra_i2s_priv.h"
+
+struct tegra_ahub_priv {
+ struct apbif_regs *apbif_regs;
+ struct xbar_regs *xbar_regs;
+ u32 full_mask;
+ int capacity_words; /* FIFO capacity in words */
+
+ /*
+ * This is unset intially, but is set by tegra_ahub_ioctl() called
+ * from the misc_ioctl() in tegra_sound_probe()
+ */
+ struct udevice *i2s;
+ struct udevice *dma;
+};
+
+static int tegra_ahub_xbar_enable_i2s(struct xbar_regs *regs, int i2s_id)
+{
+ /*
+ * Enables I2S as the receiver of APBIF by writing APBIF_TX0 (0x01) to
+ * the rx0 register
+ */
+ switch (i2s_id) {
+ case 0:
+ writel(1, &regs->i2s0_rx0);
+ break;
+ case 1:
+ writel(1, &regs->i2s1_rx0);
+ break;
+ case 2:
+ writel(1, &regs->i2s2_rx0);
+ break;
+ case 3:
+ writel(1, &regs->i2s3_rx0);
+ break;
+ case 4:
+ writel(1, &regs->i2s4_rx0);
+ break;
+ default:
+ log_err("Invalid I2S component id: %d\n", i2s_id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int tegra_ahub_apbif_is_full(struct udevice *dev)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+
+ return readl(&priv->apbif_regs->apbdma_live_stat) & priv->full_mask;
+}
+
+/**
+ * tegra_ahub_wait_for_space() - Wait for space in the FIFO
+ *
+ * @return 0 if OK, -ETIMEDOUT if no space was available in time
+ */
+static int tegra_ahub_wait_for_space(struct udevice *dev)
+{
+ int i = 100000;
+ ulong start;
+
+ /* Busy-wait initially, since this should take almost no time */
+ while (i--) {
+ if (!tegra_ahub_apbif_is_full(dev))
+ return 0;
+ }
+
+ /* Failed, so do a slower loop for 100ms */
+ start = get_timer(0);
+ while (tegra_ahub_apbif_is_full(dev)) {
+ if (get_timer(start) > 100)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int tegra_ahub_apbif_send(struct udevice *dev, int offset,
+ const void *buf, int len)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+ const u32 *data = (const u32 *)buf;
+ ssize_t written = 0;
+
+ if (len % sizeof(*data)) {
+ log_err("Data size (%zd) must be aligned to %zd.\n", len,
+ sizeof(*data));
+ return -EFAULT;
+ }
+ while (written < len) {
+ int ret = tegra_ahub_wait_for_space(dev);
+
+ if (ret)
+ return ret;
+
+ writel(*data++, &priv->apbif_regs->channel0_txfifo);
+ written += sizeof(*data);
+ }
+
+ return written;
+}
+
+static void tegra_ahub_apbif_set_cif(struct udevice *dev, u32 value)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+
+ writel(value, &priv->apbif_regs->channel0_cif_tx0_ctrl);
+}
+
+static void tegra_ahub_apbif_enable_channel0(struct udevice *dev,
+ int fifo_threshold)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+
+ u32 ctrl = TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN |
+ TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16 |
+ TEGRA_AHUB_CHANNEL_CTRL_TX_EN;
+
+ fifo_threshold--; /* fifo_threshold starts from 1 */
+ ctrl |= (fifo_threshold << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT);
+ writel(ctrl, &priv->apbif_regs->channel0_ctrl);
+}
+
+static u32 tegra_ahub_get_cif(bool is_receive, uint channels,
+ uint bits_per_sample, uint fifo_threshold)
+{
+ uint audio_bits = (bits_per_sample >> 2) - 1;
+ u32 val;
+
+ channels--; /* Channels in CIF starts from 1 */
+ fifo_threshold--; /* FIFO threshold starts from 1 */
+ /* Assume input and output are always using same channel / bits */
+ val = channels << TEGRA_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT |
+ channels << TEGRA_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT |
+ audio_bits << TEGRA_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT |
+ audio_bits << TEGRA_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT |
+ fifo_threshold << TEGRA_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT |
+ (is_receive ? TEGRA_AUDIOCIF_DIRECTION_RX <<
+ TEGRA_AUDIOCIF_CTRL_DIRECTION_SHIFT : 0);
+
+ return val;
+}
+
+static int tegra_ahub_enable(struct udevice *dev)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+ struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(priv->i2s);
+ u32 cif_ctrl = 0;
+ int ret;
+
+ /* We use APBIF channel0 as a sender */
+ priv->full_mask = TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL;
+ priv->capacity_words = 8;
+
+ /*
+ * FIFO is inactive until (fifo_threshold) of words are sent. For
+ * better performance, we want to set it to half of capacity.
+ */
+ u32 fifo_threshold = priv->capacity_words / 2;
+
+ /*
+ * Setup audio client interface (ACIF): APBIF (channel0) as sender and
+ * I2S as receiver
+ */
+ cif_ctrl = tegra_ahub_get_cif(true, uc_priv->channels,
+ uc_priv->bitspersample, fifo_threshold);
+ tegra_i2s_set_cif_tx_ctrl(priv->i2s, cif_ctrl);
+
+ cif_ctrl = tegra_ahub_get_cif(false, uc_priv->channels,
+ uc_priv->bitspersample, fifo_threshold);
+ tegra_ahub_apbif_set_cif(dev, cif_ctrl);
+ tegra_ahub_apbif_enable_channel0(dev, fifo_threshold);
+
+ ret = tegra_ahub_xbar_enable_i2s(priv->xbar_regs, uc_priv->id);
+ if (ret)
+ return ret;
+ log_debug("ahub: channels=%d, bitspersample=%d, cif_ctrl=%x, fifo_threshold=%d, id=%d\n",
+ uc_priv->channels, uc_priv->bitspersample, cif_ctrl,
+ fifo_threshold, uc_priv->id);
+
+ return 0;
+}
+
+static int tegra_ahub_ioctl(struct udevice *dev, unsigned long request,
+ void *buf)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+
+ if (request != AHUB_MISCOP_SET_I2S)
+ return -ENOSYS;
+
+ priv->i2s = *(struct udevice **)buf;
+ log_debug("i2s set to '%s'\n", priv->i2s->name);
+
+ return tegra_ahub_enable(dev);
+}
+
+static int tegra_ahub_probe(struct udevice *dev)
+{
+ struct tegra_ahub_priv *priv = dev_get_priv(dev);
+ ulong addr;
+
+ addr = dev_read_addr_index(dev, 0);
+ if (addr == FDT_ADDR_T_NONE) {
+ log_debug("Invalid apbif address\n");
+ return -EINVAL;
+ }
+ priv->apbif_regs = (struct apbif_regs *)addr;
+
+ addr = dev_read_addr_index(dev, 1);
+ if (addr == FDT_ADDR_T_NONE) {
+ log_debug("Invalid xbar address\n");
+ return -EINVAL;
+ }
+ priv->xbar_regs = (struct xbar_regs *)addr;
+ log_debug("ahub apbif_regs=%p, xbar_regs=%p\n", priv->apbif_regs,
+ priv->xbar_regs);
+
+ return 0;
+}
+
+static struct misc_ops tegra_ahub_ops = {
+ .write = tegra_ahub_apbif_send,
+ .ioctl = tegra_ahub_ioctl,
+};
+
+static const struct udevice_id tegra_ahub_ids[] = {
+ { .compatible = "nvidia,tegra124-ahub" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_ahub) = {
+ .name = "tegra_ahub",
+ .id = UCLASS_MISC,
+ .of_match = tegra_ahub_ids,
+ .ops = &tegra_ahub_ops,
+ .probe = tegra_ahub_probe,
+ .priv_auto_alloc_size = sizeof(struct tegra_ahub_priv),
+};
diff --git a/drivers/sound/tegra_i2s.c b/drivers/sound/tegra_i2s.c
new file mode 100644
index 0000000..8022dbb
--- /dev/null
+++ b/drivers/sound/tegra_i2s.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+#define LOG_CATEGORY UCLASS_I2S
+#define LOG_DEBUG
+
+#include <common.h>
+#include <dm.h>
+#include <i2s.h>
+#include <misc.h>
+#include <sound.h>
+#include <asm/io.h>
+#include <asm/arch-tegra/tegra_i2s.h>
+#include "tegra_i2s_priv.h"
+
+int tegra_i2s_set_cif_tx_ctrl(struct udevice *dev, u32 value)
+{
+ struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
+ struct i2s_ctlr *regs = (struct i2s_ctlr *)priv->base_address;
+
+ writel(value, &regs->cif_tx_ctrl);
+
+ return 0;
+}
+
+static void tegra_i2s_transmit_enable(struct i2s_ctlr *regs, int on)
+{
+ clrsetbits_le32(&regs->ctrl, I2S_CTRL_XFER_EN_TX,
+ on ? I2S_CTRL_XFER_EN_TX : 0);
+}
+
+static int i2s_tx_init(struct i2s_uc_priv *pi2s_tx)
+{
+ struct i2s_ctlr *regs = (struct i2s_ctlr *)pi2s_tx->base_address;
+ u32 audio_bits = (pi2s_tx->bitspersample >> 2) - 1;
+ u32 ctrl = readl(&regs->ctrl);
+
+ /* Set format to LRCK / Left Low */
+ ctrl &= ~(I2S_CTRL_FRAME_FORMAT_MASK | I2S_CTRL_LRCK_MASK);
+ ctrl |= I2S_CTRL_FRAME_FORMAT_LRCK;
+ ctrl |= I2S_CTRL_LRCK_L_LOW;
+
+ /* Disable all transmission until we are ready to transfer */
+ ctrl &= ~(I2S_CTRL_XFER_EN_TX | I2S_CTRL_XFER_EN_RX);
+
+ /* Serve as master */
+ ctrl |= I2S_CTRL_MASTER_ENABLE;
+
+ /* Configure audio bits size */
+ ctrl &= ~I2S_CTRL_BIT_SIZE_MASK;
+ ctrl |= audio_bits << I2S_CTRL_BIT_SIZE_SHIFT;
+ writel(ctrl, &regs->ctrl);
+
+ /* Timing in LRCK mode: */
+ writel(pi2s_tx->bitspersample, &regs->timing);
+
+ /* I2S mode has [TX/RX]_DATA_OFFSET both set to 1 */
+ writel(((1 << I2S_OFFSET_RX_DATA_OFFSET_SHIFT) |
+ (1 << I2S_OFFSET_TX_DATA_OFFSET_SHIFT)), &regs->offset);
+
+ /* FSYNC_WIDTH = 2 clocks wide, TOTAL_SLOTS = 2 slots per fsync */
+ writel((2 - 1) << I2S_CH_CTRL_FSYNC_WIDTH_SHIFT, &regs->ch_ctrl);
+
+ return 0;
+}
+
+static int tegra_i2s_tx_data(struct udevice *dev, void *data, uint data_size)
+{
+ struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
+ struct i2s_ctlr *regs = (struct i2s_ctlr *)priv->base_address;
+ int ret;
+
+ tegra_i2s_transmit_enable(regs, 1);
+ ret = misc_write(dev_get_parent(dev), 0, data, data_size);
+ tegra_i2s_transmit_enable(regs, 0);
+ if (ret < 0)
+ return ret;
+ else if (ret < data_size)
+ return -EIO;
+
+ return 0;
+}
+
+static int tegra_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) {
+ debug("%s: Missing i2s base\n", __func__);
+ 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 i2s_tx_init(priv);
+}
+
+static const struct i2s_ops tegra_i2s_ops = {
+ .tx_data = tegra_i2s_tx_data,
+};
+
+static const struct udevice_id tegra_i2s_ids[] = {
+ { .compatible = "nvidia,tegra124-i2s" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_i2s) = {
+ .name = "tegra_i2s",
+ .id = UCLASS_I2S,
+ .of_match = tegra_i2s_ids,
+ .probe = tegra_i2s_probe,
+ .ops = &tegra_i2s_ops,
+};
diff --git a/drivers/sound/tegra_i2s_priv.h b/drivers/sound/tegra_i2s_priv.h
new file mode 100644
index 0000000..7cd3fc8
--- /dev/null
+++ b/drivers/sound/tegra_i2s_priv.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2018 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __TEGRA_I2S_PRIV_H
+#define __TEGRA_I2S_PRIV_H
+
+enum {
+ /* Set i2s device (in buf) */
+ AHUB_MISCOP_SET_I2S,
+};
+
+/*
+ * tegra_i2s_set_cif_tx_ctrl() - Set the I2C port to send to
+ *
+ * The CIF is not really part of I2S -- it's for Audio Hub to control
+ * the interface between I2S and Audio Hub. However since it's put in
+ * the I2S registers domain instead of the Audio Hub, we need to export
+ * this as a function.
+ *
+ * @dev: I2S device
+ * @value: Value to write to CIF_TX_CTRL register
+ * @return 0
+ */
+int tegra_i2s_set_cif_tx_ctrl(struct udevice *dev, u32 value);
+
+#endif
diff --git a/drivers/sound/tegra_sound.c b/drivers/sound/tegra_sound.c
new file mode 100644
index 0000000..7c2ed53
--- /dev/null
+++ b/drivers/sound/tegra_sound.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 Google, LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_I2S
+
+#include <common.h>
+#include <audio_codec.h>
+#include <dm.h>
+#include <i2s.h>
+#include <misc.h>
+#include <sound.h>
+#include <asm/gpio.h>
+#include "tegra_i2s_priv.h"
+
+static int tegra_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 tegra_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 tegra_sound_probe(struct udevice *dev)
+{
+ struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct gpio_desc en_gpio;
+ struct udevice *ahub;
+ int ret;
+
+ ret = gpio_request_by_name(dev, "codec-enable-gpio", 0, &en_gpio,
+ GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+
+ ret = uclass_get_device_by_phandle(UCLASS_AUDIO_CODEC, dev,
+ "nvidia,audio-codec",
+ &uc_priv->codec);
+ if (ret) {
+ log_debug("Failed to probe audio codec\n");
+ return ret;
+ }
+ ret = uclass_get_device_by_phandle(UCLASS_I2S, dev,
+ "nvidia,i2s-controller",
+ &uc_priv->i2s);
+ if (ret) {
+ log_debug("Cannot find i2s: %d\n", ret);
+ return ret;
+ }
+
+ /* Set up the audio hub, telling it the currect i2s to use */
+ ahub = dev_get_parent(uc_priv->i2s);
+ ret = misc_ioctl(ahub, AHUB_MISCOP_SET_I2S, &uc_priv->i2s);
+ if (ret) {
+ log_debug("Cannot set i2c: %d\n", ret);
+ 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 tegra_sound_ops = {
+ .setup = tegra_sound_setup,
+ .play = tegra_sound_play,
+};
+
+static const struct udevice_id tegra_sound_ids[] = {
+ { .compatible = "nvidia,tegra-audio-max98090-nyan-big" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_sound) = {
+ .name = "tegra_sound",
+ .id = UCLASS_SOUND,
+ .of_match = tegra_sound_ids,
+ .probe = tegra_sound_probe,
+ .ops = &tegra_sound_ops,
+};