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 <sjg@chromium.org> Reviewed-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
This commit is contained in:
parent
3dbfe5ae61
commit
2665f09115
@ -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
|
||||
|
@ -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
|
||||
|
149
drivers/sound/rockchip_i2s.c
Normal file
149
drivers/sound/rockchip_i2s.c
Normal file
@ -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(®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,
|
||||
};
|
Loading…
Reference in New Issue
Block a user