]>
Commit | Line | Data |
---|---|---|
112f2e14 SG |
1 | // SPDX-License-Identifier: GPL-2.0+159 |
2 | /* | |
3 | * Take from dc tegra_ahub.c | |
4 | * | |
5 | * Copyright 2018 Google LLC | |
6 | */ | |
7 | ||
8 | #define LOG_CATEGORY UCLASS_MISC | |
9 | ||
10 | #include <common.h> | |
11 | #include <dm.h> | |
12 | #include <i2s.h> | |
f7ae49fc | 13 | #include <log.h> |
112f2e14 SG |
14 | #include <misc.h> |
15 | #include <asm/io.h> | |
16 | #include <asm/arch-tegra/tegra_ahub.h> | |
17 | #include <asm/arch-tegra/tegra_i2s.h> | |
18 | #include "tegra_i2s_priv.h" | |
19 | ||
20 | struct tegra_ahub_priv { | |
21 | struct apbif_regs *apbif_regs; | |
22 | struct xbar_regs *xbar_regs; | |
23 | u32 full_mask; | |
24 | int capacity_words; /* FIFO capacity in words */ | |
25 | ||
26 | /* | |
27 | * This is unset intially, but is set by tegra_ahub_ioctl() called | |
28 | * from the misc_ioctl() in tegra_sound_probe() | |
29 | */ | |
30 | struct udevice *i2s; | |
31 | struct udevice *dma; | |
32 | }; | |
33 | ||
34 | static int tegra_ahub_xbar_enable_i2s(struct xbar_regs *regs, int i2s_id) | |
35 | { | |
36 | /* | |
37 | * Enables I2S as the receiver of APBIF by writing APBIF_TX0 (0x01) to | |
38 | * the rx0 register | |
39 | */ | |
40 | switch (i2s_id) { | |
41 | case 0: | |
42 | writel(1, ®s->i2s0_rx0); | |
43 | break; | |
44 | case 1: | |
45 | writel(1, ®s->i2s1_rx0); | |
46 | break; | |
47 | case 2: | |
48 | writel(1, ®s->i2s2_rx0); | |
49 | break; | |
50 | case 3: | |
51 | writel(1, ®s->i2s3_rx0); | |
52 | break; | |
53 | case 4: | |
54 | writel(1, ®s->i2s4_rx0); | |
55 | break; | |
56 | default: | |
57 | log_err("Invalid I2S component id: %d\n", i2s_id); | |
58 | return -EINVAL; | |
59 | } | |
60 | return 0; | |
61 | } | |
62 | ||
63 | static int tegra_ahub_apbif_is_full(struct udevice *dev) | |
64 | { | |
65 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
66 | ||
67 | return readl(&priv->apbif_regs->apbdma_live_stat) & priv->full_mask; | |
68 | } | |
69 | ||
70 | /** | |
71 | * tegra_ahub_wait_for_space() - Wait for space in the FIFO | |
72 | * | |
73 | * @return 0 if OK, -ETIMEDOUT if no space was available in time | |
74 | */ | |
75 | static int tegra_ahub_wait_for_space(struct udevice *dev) | |
76 | { | |
77 | int i = 100000; | |
78 | ulong start; | |
79 | ||
80 | /* Busy-wait initially, since this should take almost no time */ | |
81 | while (i--) { | |
82 | if (!tegra_ahub_apbif_is_full(dev)) | |
83 | return 0; | |
84 | } | |
85 | ||
86 | /* Failed, so do a slower loop for 100ms */ | |
87 | start = get_timer(0); | |
88 | while (tegra_ahub_apbif_is_full(dev)) { | |
89 | if (get_timer(start) > 100) | |
90 | return -ETIMEDOUT; | |
91 | } | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | static int tegra_ahub_apbif_send(struct udevice *dev, int offset, | |
97 | const void *buf, int len) | |
98 | { | |
99 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
100 | const u32 *data = (const u32 *)buf; | |
101 | ssize_t written = 0; | |
102 | ||
103 | if (len % sizeof(*data)) { | |
104 | log_err("Data size (%zd) must be aligned to %zd.\n", len, | |
105 | sizeof(*data)); | |
106 | return -EFAULT; | |
107 | } | |
108 | while (written < len) { | |
109 | int ret = tegra_ahub_wait_for_space(dev); | |
110 | ||
111 | if (ret) | |
112 | return ret; | |
113 | ||
114 | writel(*data++, &priv->apbif_regs->channel0_txfifo); | |
115 | written += sizeof(*data); | |
116 | } | |
117 | ||
118 | return written; | |
119 | } | |
120 | ||
121 | static void tegra_ahub_apbif_set_cif(struct udevice *dev, u32 value) | |
122 | { | |
123 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
124 | ||
125 | writel(value, &priv->apbif_regs->channel0_cif_tx0_ctrl); | |
126 | } | |
127 | ||
128 | static void tegra_ahub_apbif_enable_channel0(struct udevice *dev, | |
129 | int fifo_threshold) | |
130 | { | |
131 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
132 | ||
133 | u32 ctrl = TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN | | |
134 | TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16 | | |
135 | TEGRA_AHUB_CHANNEL_CTRL_TX_EN; | |
136 | ||
137 | fifo_threshold--; /* fifo_threshold starts from 1 */ | |
138 | ctrl |= (fifo_threshold << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT); | |
139 | writel(ctrl, &priv->apbif_regs->channel0_ctrl); | |
140 | } | |
141 | ||
142 | static u32 tegra_ahub_get_cif(bool is_receive, uint channels, | |
143 | uint bits_per_sample, uint fifo_threshold) | |
144 | { | |
145 | uint audio_bits = (bits_per_sample >> 2) - 1; | |
146 | u32 val; | |
147 | ||
148 | channels--; /* Channels in CIF starts from 1 */ | |
149 | fifo_threshold--; /* FIFO threshold starts from 1 */ | |
150 | /* Assume input and output are always using same channel / bits */ | |
151 | val = channels << TEGRA_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT | | |
152 | channels << TEGRA_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT | | |
153 | audio_bits << TEGRA_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT | | |
154 | audio_bits << TEGRA_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT | | |
155 | fifo_threshold << TEGRA_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT | | |
156 | (is_receive ? TEGRA_AUDIOCIF_DIRECTION_RX << | |
157 | TEGRA_AUDIOCIF_CTRL_DIRECTION_SHIFT : 0); | |
158 | ||
159 | return val; | |
160 | } | |
161 | ||
162 | static int tegra_ahub_enable(struct udevice *dev) | |
163 | { | |
164 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
165 | struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(priv->i2s); | |
166 | u32 cif_ctrl = 0; | |
167 | int ret; | |
168 | ||
169 | /* We use APBIF channel0 as a sender */ | |
170 | priv->full_mask = TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL; | |
171 | priv->capacity_words = 8; | |
172 | ||
173 | /* | |
174 | * FIFO is inactive until (fifo_threshold) of words are sent. For | |
175 | * better performance, we want to set it to half of capacity. | |
176 | */ | |
177 | u32 fifo_threshold = priv->capacity_words / 2; | |
178 | ||
179 | /* | |
180 | * Setup audio client interface (ACIF): APBIF (channel0) as sender and | |
181 | * I2S as receiver | |
182 | */ | |
183 | cif_ctrl = tegra_ahub_get_cif(true, uc_priv->channels, | |
184 | uc_priv->bitspersample, fifo_threshold); | |
185 | tegra_i2s_set_cif_tx_ctrl(priv->i2s, cif_ctrl); | |
186 | ||
187 | cif_ctrl = tegra_ahub_get_cif(false, uc_priv->channels, | |
188 | uc_priv->bitspersample, fifo_threshold); | |
189 | tegra_ahub_apbif_set_cif(dev, cif_ctrl); | |
190 | tegra_ahub_apbif_enable_channel0(dev, fifo_threshold); | |
191 | ||
192 | ret = tegra_ahub_xbar_enable_i2s(priv->xbar_regs, uc_priv->id); | |
193 | if (ret) | |
194 | return ret; | |
195 | log_debug("ahub: channels=%d, bitspersample=%d, cif_ctrl=%x, fifo_threshold=%d, id=%d\n", | |
196 | uc_priv->channels, uc_priv->bitspersample, cif_ctrl, | |
197 | fifo_threshold, uc_priv->id); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static int tegra_ahub_ioctl(struct udevice *dev, unsigned long request, | |
203 | void *buf) | |
204 | { | |
205 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
206 | ||
207 | if (request != AHUB_MISCOP_SET_I2S) | |
208 | return -ENOSYS; | |
209 | ||
210 | priv->i2s = *(struct udevice **)buf; | |
211 | log_debug("i2s set to '%s'\n", priv->i2s->name); | |
212 | ||
213 | return tegra_ahub_enable(dev); | |
214 | } | |
215 | ||
216 | static int tegra_ahub_probe(struct udevice *dev) | |
217 | { | |
218 | struct tegra_ahub_priv *priv = dev_get_priv(dev); | |
219 | ulong addr; | |
220 | ||
221 | addr = dev_read_addr_index(dev, 0); | |
222 | if (addr == FDT_ADDR_T_NONE) { | |
223 | log_debug("Invalid apbif address\n"); | |
224 | return -EINVAL; | |
225 | } | |
226 | priv->apbif_regs = (struct apbif_regs *)addr; | |
227 | ||
228 | addr = dev_read_addr_index(dev, 1); | |
229 | if (addr == FDT_ADDR_T_NONE) { | |
230 | log_debug("Invalid xbar address\n"); | |
231 | return -EINVAL; | |
232 | } | |
233 | priv->xbar_regs = (struct xbar_regs *)addr; | |
234 | log_debug("ahub apbif_regs=%p, xbar_regs=%p\n", priv->apbif_regs, | |
235 | priv->xbar_regs); | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static struct misc_ops tegra_ahub_ops = { | |
241 | .write = tegra_ahub_apbif_send, | |
242 | .ioctl = tegra_ahub_ioctl, | |
243 | }; | |
244 | ||
245 | static const struct udevice_id tegra_ahub_ids[] = { | |
246 | { .compatible = "nvidia,tegra124-ahub" }, | |
247 | { } | |
248 | }; | |
249 | ||
250 | U_BOOT_DRIVER(tegra_ahub) = { | |
251 | .name = "tegra_ahub", | |
252 | .id = UCLASS_MISC, | |
253 | .of_match = tegra_ahub_ids, | |
254 | .ops = &tegra_ahub_ops, | |
255 | .probe = tegra_ahub_probe, | |
41575d8e | 256 | .priv_auto = sizeof(struct tegra_ahub_priv), |
112f2e14 | 257 | }; |