From: Simon Glass Date: Fri, 28 Dec 2018 03:15:21 +0000 (-0700) Subject: rockchip: Add an I2S driver X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=2665f091158a37bea7db387fce379eecb04a1d71;p=u-boot.git rockchip: Add an I2S driver Add a driver for I2S which allows audio data to be sent from the SoC to the audio codec. The sample rate and other settings are hard-coded for now as there is no suitable device-tree binding available. Signed-off-by: Simon Glass Reviewed-by: Philipp Tomsich --- 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..91f255b30d 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 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 +#include +#include +#include +#include + +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(®s->xfer, I2S_TX_TRAN_BIT); + mode = readl(®s->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, ®s->txcr); + + mode = readl(®s->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, ®s->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++, ®s->txdr); + + length -= min(32u, length); + + /* enable both tx and rx */ + setbits_le32(®s->xfer, I2S_TRAN_MASK); + while (length) { + if ((readl(®s->fifolr) & 0x3f) < 0x20) { + writel(*data++, ®s->txdr); + length--; + } + } + while (readl(®s->fifolr) & 0x3f) + /* wait until FIFO empty */; + clrbits_le32(®s->xfer, I2S_TRAN_MASK); + writel(0, ®s->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, +};