]>
Commit | Line | Data |
---|---|---|
cbdfe599 SG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Intel Broadwell I2S driver | |
4 | * | |
5 | * Copyright 2019 Google LLC | |
6 | * | |
7 | * Modified from dc i2s/broadwell/broadwell.c | |
8 | */ | |
9 | ||
10 | #define LOG_CATEGORY UCLASS_I2S | |
11 | ||
12 | #include <common.h> | |
13 | #include <dm.h> | |
14 | #include <i2s.h> | |
f7ae49fc | 15 | #include <log.h> |
1045315d | 16 | #include <time.h> |
cbdfe599 SG |
17 | #include <asm/io.h> |
18 | #include "broadwell_i2s.h" | |
19 | ||
20 | enum { | |
21 | BDW_SHIM_START_ADDRESS = 0xfb000, | |
22 | BDW_SSP0_START_ADDRESS = 0xfc000, | |
23 | BDW_SSP1_START_ADDRESS = 0xfd000, | |
24 | }; | |
25 | ||
26 | struct broadwell_i2s_priv { | |
27 | enum frame_sync_rel_timing_t rel_timing; | |
28 | enum frame_sync_pol_t sfrm_polarity; | |
29 | enum end_transfer_state_t end_transfer_state; | |
30 | enum clock_mode_t sclk_mode; | |
31 | uint sclk_dummy_stop; /* 0-31 */ | |
32 | uint sclk_frame_width; /* 1-38 */ | |
33 | struct i2s_shim_regs *shim; | |
34 | struct broadwell_i2s_regs *regs; | |
35 | }; | |
36 | ||
37 | static void init_shim_csr(struct broadwell_i2s_priv *priv) | |
38 | { | |
39 | /* | |
40 | * Select SSP clock | |
41 | * Turn off low power clock | |
42 | * Set PIO mode | |
43 | * Stall DSP core | |
44 | */ | |
45 | clrsetbits_le32(&priv->shim->csr, | |
46 | SHIM_CS_S0IOCS | SHIM_CS_LPCS | SHIM_CS_DCS_MASK, | |
47 | SHIM_CS_S1IOCS | SHIM_CS_SBCS_SSP1_24MHZ | | |
48 | SHIM_CS_SBCS_SSP0_24MHZ | SHIM_CS_SDPM_PIO_SSP1 | | |
49 | SHIM_CS_SDPM_PIO_SSP0 | SHIM_CS_STALL | | |
50 | SHIM_CS_DCS_DSP32_AF32); | |
51 | } | |
52 | ||
53 | static void init_shim_clkctl(struct i2s_uc_priv *uc_priv, | |
54 | struct broadwell_i2s_priv *priv) | |
55 | { | |
56 | u32 clkctl = readl(&priv->shim->clkctl); | |
57 | ||
58 | /* Set 24Mhz mclk, prevent local clock gating, enable SSP0 clock */ | |
59 | clkctl &= SHIM_CLKCTL_RESERVED; | |
60 | clkctl |= SHIM_CLKCTL_MCLK_24MHZ | SHIM_CLKCTL_DCPLCG; | |
61 | ||
62 | /* Enable requested SSP interface */ | |
63 | if (uc_priv->id) | |
64 | clkctl |= SHIM_CLKCTL_SCOE_SSP1 | SHIM_CLKCTL_SFLCGB_SSP1_CGD; | |
65 | else | |
66 | clkctl |= SHIM_CLKCTL_SCOE_SSP0 | SHIM_CLKCTL_SFLCGB_SSP0_CGD; | |
67 | ||
68 | writel(clkctl, &priv->shim->clkctl); | |
69 | } | |
70 | ||
71 | static void init_sscr0(struct i2s_uc_priv *uc_priv, | |
72 | struct broadwell_i2s_priv *priv) | |
73 | { | |
74 | u32 sscr0; | |
75 | uint scale; | |
76 | ||
77 | /* Set data size based on BPS */ | |
78 | if (uc_priv->bitspersample > 16) | |
79 | sscr0 = (uc_priv->bitspersample - 16 - 1) << SSP_SSC0_DSS_SHIFT | |
80 | | SSP_SSC0_EDSS; | |
81 | else | |
82 | sscr0 = (uc_priv->bitspersample - 1) << SSP_SSC0_DSS_SHIFT; | |
83 | ||
84 | /* Set network mode, Stereo PSP frame format */ | |
85 | sscr0 |= SSP_SSC0_MODE_NETWORK | | |
86 | SSP_SSC0_FRDC_STEREO | | |
87 | SSP_SSC0_FRF_PSP | | |
88 | SSP_SSC0_TIM | | |
89 | SSP_SSC0_RIM | | |
90 | SSP_SSC0_ECS_PCH | | |
91 | SSP_SSC0_NCS_PCH | | |
92 | SSP_SSC0_ACS_PCH; | |
93 | ||
94 | /* Scale 24MHz MCLK */ | |
95 | scale = uc_priv->audio_pll_clk / uc_priv->samplingrate / uc_priv->bfs; | |
96 | sscr0 |= scale << SSP_SSC0_SCR_SHIFT; | |
97 | ||
98 | writel(sscr0, &priv->regs->sscr0); | |
99 | } | |
100 | ||
101 | static void init_sscr1(struct broadwell_i2s_priv *priv) | |
102 | { | |
103 | u32 sscr1 = readl(&priv->regs->sscr1); | |
104 | ||
105 | sscr1 &= SSP_SSC1_RESERVED; | |
106 | ||
107 | /* Set as I2S master */ | |
108 | sscr1 |= SSP_SSC1_SCLKDIR_MASTER | SSP_SSC1_SCLKDIR_MASTER; | |
109 | ||
110 | /* Enable TXD tristate behavior for PCH */ | |
111 | sscr1 |= SSP_SSC1_TTELP | SSP_SSC1_TTE; | |
112 | ||
113 | /* Disable DMA Tx/Rx service request */ | |
114 | sscr1 |= SSP_SSC1_TSRE | SSP_SSC1_RSRE; | |
115 | ||
116 | /* Clock on during transfer */ | |
117 | sscr1 |= SSP_SSC1_SCFR; | |
118 | ||
119 | /* Set FIFO thresholds */ | |
120 | sscr1 |= SSP_FIFO_SIZE << SSP_SSC1_RFT_SHIFT; | |
121 | sscr1 |= SSP_FIFO_SIZE << SSP_SSC1_TFT_SHIFT; | |
122 | ||
123 | /* Disable interrupts */ | |
124 | sscr1 &= ~(SSP_SSC1_EBCEI | SSP_SSC1_TINTE | SSP_SSC1_PINTE); | |
125 | sscr1 &= ~(SSP_SSC1_LBM | SSP_SSC1_RWOT); | |
126 | ||
127 | writel(sscr1, &priv->regs->sscr1); | |
128 | } | |
129 | ||
130 | static void init_sspsp(struct broadwell_i2s_priv *priv) | |
131 | { | |
132 | u32 sspsp = readl(&priv->regs->sspsp); | |
133 | ||
134 | sspsp &= SSP_PSP_RESERVED; | |
135 | sspsp |= priv->sclk_mode << SSP_PSP_SCMODE_SHIFT; | |
136 | sspsp |= (priv->sclk_dummy_stop << SSP_PSP_DMYSTOP_SHIFT) & | |
137 | SSP_PSP_DMYSTOP_MASK; | |
138 | sspsp |= (priv->sclk_dummy_stop >> 2 << SSP_PSP_EDYMSTOP_SHIFT) & | |
139 | SSP_PSP_EDMYSTOP_MASK; | |
140 | sspsp |= priv->sclk_frame_width << SSP_PSP_SFRMWDTH_SHIFT; | |
141 | ||
142 | /* Frame Sync Relative Timing */ | |
143 | if (priv->rel_timing == NEXT_FRMS_AFTER_END_OF_T4) | |
144 | sspsp |= SSP_PSP_FSRT; | |
145 | else | |
146 | sspsp &= ~SSP_PSP_FSRT; | |
147 | ||
148 | /* Serial Frame Polarity */ | |
149 | if (priv->sfrm_polarity == SSP_FRMS_ACTIVE_HIGH) | |
150 | sspsp |= SSP_PSP_SFRMP; | |
151 | else | |
152 | sspsp &= ~SSP_PSP_SFRMP; | |
153 | ||
154 | /* End Data Transfer State */ | |
155 | if (priv->end_transfer_state == SSP_END_TRANSFER_STATE_LOW) | |
156 | sspsp &= ~SSP_PSP_ETDS; | |
157 | else | |
158 | sspsp |= SSP_PSP_ETDS; | |
159 | ||
160 | writel(sspsp, &priv->regs->sspsp); | |
161 | } | |
162 | ||
163 | static void init_ssp_time_slot(struct broadwell_i2s_priv *priv) | |
164 | { | |
165 | writel(3, &priv->regs->sstsa); | |
166 | writel(3, &priv->regs->ssrsa); | |
167 | } | |
168 | ||
169 | static int bdw_i2s_init(struct udevice *dev) | |
170 | { | |
171 | struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev); | |
172 | struct broadwell_i2s_priv *priv = dev_get_priv(dev); | |
173 | ||
174 | init_shim_csr(priv); | |
175 | init_shim_clkctl(uc_priv, priv); | |
176 | init_sscr0(uc_priv, priv); | |
177 | init_sscr1(priv); | |
178 | init_sspsp(priv); | |
179 | init_ssp_time_slot(priv); | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
184 | static void bdw_i2s_enable(struct broadwell_i2s_priv *priv) | |
185 | { | |
186 | setbits_le32(&priv->regs->sscr0, SSP_SSC0_SSE); | |
187 | setbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN); | |
188 | } | |
189 | ||
190 | static void bdw_i2s_disable(struct broadwell_i2s_priv *priv) | |
191 | { | |
192 | clrbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN); | |
193 | clrbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN); | |
194 | } | |
195 | ||
196 | static int broadwell_i2s_tx_data(struct udevice *dev, void *data, | |
197 | uint data_size) | |
198 | { | |
199 | struct broadwell_i2s_priv *priv = dev_get_priv(dev); | |
200 | u32 *ptr = data; | |
201 | ||
202 | log_debug("data=%p, data_size=%x\n", data, data_size); | |
203 | if (data_size < SSP_FIFO_SIZE) { | |
204 | log_err("Invalid I2S data size\n"); | |
205 | return -ENODATA; | |
206 | } | |
207 | ||
208 | /* Enable I2S interface */ | |
209 | bdw_i2s_enable(priv); | |
210 | ||
211 | /* Transfer data */ | |
212 | while (data_size > 0) { | |
213 | ulong start = timer_get_us() + 100000; | |
214 | ||
215 | /* Write data if transmit FIFO has room */ | |
216 | if (readl(&priv->regs->sssr) & SSP_SSS_TNF) { | |
217 | writel(*ptr++, &priv->regs->ssdr); | |
218 | data_size -= sizeof(*ptr); | |
219 | } else { | |
220 | if ((long)(timer_get_us() - start) > 0) { | |
221 | /* Disable I2S interface */ | |
222 | bdw_i2s_disable(priv); | |
223 | log_debug("I2S Transfer Timeout\n"); | |
224 | return -ETIMEDOUT; | |
225 | } | |
226 | } | |
227 | } | |
228 | ||
229 | /* Disable I2S interface */ | |
230 | bdw_i2s_disable(priv); | |
231 | log_debug("done\n"); | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | static int broadwell_i2s_probe(struct udevice *dev) | |
237 | { | |
238 | struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev); | |
239 | struct broadwell_i2s_priv *priv = dev_get_priv(dev); | |
240 | struct udevice *adsp = dev_get_parent(dev); | |
241 | u32 bar0, offset; | |
242 | int ret; | |
243 | ||
244 | bar0 = dm_pci_read_bar32(adsp, 0); | |
245 | if (!bar0) { | |
246 | log_debug("Cannot read adsp bar0\n"); | |
247 | return -EINVAL; | |
248 | } | |
249 | offset = dev_read_addr_index(dev, 0); | |
250 | if (offset == FDT_ADDR_T_NONE) { | |
251 | log_debug("Cannot read address index 0\n"); | |
252 | return -EINVAL; | |
253 | } | |
254 | uc_priv->base_address = bar0 + offset; | |
255 | ||
256 | /* | |
257 | * Hard-code these values. If other settings are required we can add | |
258 | * this to the device tree. | |
259 | */ | |
260 | uc_priv->rfs = 64; | |
261 | uc_priv->bfs = 32; | |
262 | uc_priv->audio_pll_clk = 24 * 1000 * 1000; | |
263 | uc_priv->samplingrate = 48000; | |
264 | uc_priv->bitspersample = 16; | |
265 | uc_priv->channels = 2; | |
266 | uc_priv->id = 0; | |
267 | ||
268 | priv->shim = (struct i2s_shim_regs *)uc_priv->base_address; | |
269 | priv->sfrm_polarity = SSP_FRMS_ACTIVE_LOW; | |
270 | priv->end_transfer_state = SSP_END_TRANSFER_STATE_LOW; | |
271 | priv->sclk_mode = SCLK_MODE_DDF_DSR_ISL; | |
272 | priv->rel_timing = NEXT_FRMS_WITH_LSB_PREVIOUS_FRM; | |
273 | priv->sclk_dummy_stop = 0; | |
274 | priv->sclk_frame_width = 31; | |
275 | ||
276 | offset = dev_read_addr_index(dev, 1 + uc_priv->id); | |
277 | if (offset == FDT_ADDR_T_NONE) { | |
278 | log_debug("Cannot read address index %d\n", 1 + uc_priv->id); | |
279 | return -EINVAL; | |
280 | } | |
281 | log_debug("bar0=%x, uc_priv->base_address=%x, offset=%x\n", bar0, | |
282 | uc_priv->base_address, offset); | |
283 | priv->regs = (struct broadwell_i2s_regs *)(bar0 + offset); | |
284 | ||
285 | ret = bdw_i2s_init(dev); | |
286 | if (ret) | |
287 | return ret; | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static const struct i2s_ops broadwell_i2s_ops = { | |
293 | .tx_data = broadwell_i2s_tx_data, | |
294 | }; | |
295 | ||
296 | static const struct udevice_id broadwell_i2s_ids[] = { | |
297 | { .compatible = "intel,broadwell-i2s" }, | |
298 | { } | |
299 | }; | |
300 | ||
301 | U_BOOT_DRIVER(broadwell_i2s) = { | |
302 | .name = "broadwell_i2s", | |
303 | .id = UCLASS_I2S, | |
304 | .of_match = broadwell_i2s_ids, | |
305 | .probe = broadwell_i2s_probe, | |
306 | .ops = &broadwell_i2s_ops, | |
307 | .priv_auto_alloc_size = sizeof(struct broadwell_i2s_priv), | |
308 | }; |