From c5a120b3f09c03e87b2cbc116af8250afb2dd500 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 1 Apr 2019 13:38:40 -0700 Subject: tegra: sound: Add an I2S driver Add a driver which supports transmitting digital sound to an audio codec. This uses fixed parameters as a device-tree binding is not currently defined. Signed-off-by: Simon Glass Acked-by: Jon Hunter Signed-off-by: Tom Warren --- drivers/sound/Makefile | 2 +- drivers/sound/tegra_i2s.c | 123 +++++++++++++++++++++++++++++++++++++++++ drivers/sound/tegra_i2s_priv.h | 29 ++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 drivers/sound/tegra_i2s.c create mode 100644 drivers/sound/tegra_i2s_priv.h (limited to 'drivers/sound') diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 077012f8a6..358d5f920b 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -11,7 +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 +obj-$(CONFIG_I2S_TEGRA) += tegra_ahub.o tegra_i2s.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_i2s.c b/drivers/sound/tegra_i2s.c new file mode 100644 index 0000000000..8022dbba64 --- /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 + */ +#define LOG_CATEGORY UCLASS_I2S +#define LOG_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#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, ®s->cif_tx_ctrl); + + return 0; +} + +static void tegra_i2s_transmit_enable(struct i2s_ctlr *regs, int on) +{ + clrsetbits_le32(®s->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(®s->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, ®s->ctrl); + + /* Timing in LRCK mode: */ + writel(pi2s_tx->bitspersample, ®s->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)), ®s->offset); + + /* FSYNC_WIDTH = 2 clocks wide, TOTAL_SLOTS = 2 slots per fsync */ + writel((2 - 1) << I2S_CH_CTRL_FSYNC_WIDTH_SHIFT, ®s->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 0000000000..7cd3fc808c --- /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 + */ + +#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 -- cgit v1.2.3