aboutsummaryrefslogtreecommitdiff
path: root/drivers/sound/tegra_i2s.c
blob: 357aac36ceaae64d4f6423f173616c39a8aed3cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2018 Google LLC
 * Written by Simon Glass <sjg@chromium.org>
 */
#define LOG_CATEGORY UCLASS_I2S

#include <dm.h>
#include <i2s.h>
#include <log.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,
};