Commit | Line | Data |
---|---|---|
88eb404c KL |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | // linux/sound/bcm/bcm63xx-i2s-whistler.c | |
3 | // BCM63xx whistler i2s driver | |
4 | // Copyright (c) 2020 Broadcom Corporation | |
5 | // Author: Kevin-Ke Li <kevin-ke.li@broadcom.com> | |
6 | ||
7 | #include <linux/clk.h> | |
8 | #include <linux/dma-mapping.h> | |
9 | #include <linux/io.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/regmap.h> | |
12 | #include <sound/pcm_params.h> | |
13 | #include <sound/soc.h> | |
14 | #include "bcm63xx-i2s.h" | |
15 | ||
16 | #define DRV_NAME "brcm-i2s" | |
17 | ||
18 | static bool brcm_i2s_wr_reg(struct device *dev, unsigned int reg) | |
19 | { | |
20 | switch (reg) { | |
21 | case I2S_TX_CFG ... I2S_TX_DESC_IFF_LEN: | |
22 | case I2S_TX_CFG_2 ... I2S_RX_DESC_IFF_LEN: | |
23 | case I2S_RX_CFG_2 ... I2S_REG_MAX: | |
24 | return true; | |
25 | default: | |
26 | return false; | |
27 | } | |
28 | } | |
29 | ||
30 | static bool brcm_i2s_rd_reg(struct device *dev, unsigned int reg) | |
31 | { | |
32 | switch (reg) { | |
33 | case I2S_TX_CFG ... I2S_REG_MAX: | |
34 | return true; | |
35 | default: | |
36 | return false; | |
37 | } | |
38 | } | |
39 | ||
40 | static bool brcm_i2s_volatile_reg(struct device *dev, unsigned int reg) | |
41 | { | |
42 | switch (reg) { | |
43 | case I2S_TX_CFG: | |
44 | case I2S_TX_IRQ_CTL: | |
45 | case I2S_TX_DESC_IFF_ADDR: | |
46 | case I2S_TX_DESC_IFF_LEN: | |
47 | case I2S_TX_DESC_OFF_ADDR: | |
48 | case I2S_TX_DESC_OFF_LEN: | |
49 | case I2S_TX_CFG_2: | |
50 | case I2S_RX_CFG: | |
51 | case I2S_RX_IRQ_CTL: | |
52 | case I2S_RX_DESC_OFF_ADDR: | |
53 | case I2S_RX_DESC_OFF_LEN: | |
54 | case I2S_RX_DESC_IFF_LEN: | |
55 | case I2S_RX_DESC_IFF_ADDR: | |
56 | case I2S_RX_CFG_2: | |
57 | return true; | |
58 | default: | |
59 | return false; | |
60 | } | |
61 | } | |
62 | ||
63 | static const struct regmap_config brcm_i2s_regmap_config = { | |
64 | .reg_bits = 32, | |
65 | .reg_stride = 4, | |
66 | .val_bits = 32, | |
67 | .max_register = I2S_REG_MAX, | |
68 | .writeable_reg = brcm_i2s_wr_reg, | |
69 | .readable_reg = brcm_i2s_rd_reg, | |
70 | .volatile_reg = brcm_i2s_volatile_reg, | |
71 | .cache_type = REGCACHE_FLAT, | |
72 | }; | |
73 | ||
74 | static int bcm63xx_i2s_hw_params(struct snd_pcm_substream *substream, | |
75 | struct snd_pcm_hw_params *params, | |
76 | struct snd_soc_dai *dai) | |
77 | { | |
78 | int ret = 0; | |
79 | struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); | |
80 | ||
81 | ret = clk_set_rate(i2s_priv->i2s_clk, params_rate(params)); | |
82 | if (ret < 0) | |
83 | dev_err(i2s_priv->dev, | |
84 | "Can't set sample rate, err: %d\n", ret); | |
85 | ||
86 | return ret; | |
87 | } | |
88 | ||
89 | static int bcm63xx_i2s_startup(struct snd_pcm_substream *substream, | |
90 | struct snd_soc_dai *dai) | |
91 | { | |
92 | unsigned int slavemode; | |
93 | struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); | |
94 | struct regmap *regmap_i2s = i2s_priv->regmap_i2s; | |
95 | ||
96 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
97 | regmap_update_bits(regmap_i2s, I2S_TX_CFG, | |
98 | I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | | |
99 | I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, | |
100 | I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | | |
101 | I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE); | |
102 | regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 0); | |
103 | regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 0); | |
104 | regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 1); | |
105 | ||
106 | /* TX and RX block each have an independent bit to indicate | |
107 | * if it is generating the clock for the I2S bus. The bus | |
108 | * clocks need to be generated from either the TX or RX block, | |
109 | * but not both | |
110 | */ | |
111 | regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); | |
112 | if (slavemode & I2S_RX_SLAVE_MODE_MASK) | |
113 | regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, | |
114 | I2S_TX_SLAVE_MODE_MASK, | |
115 | I2S_TX_MASTER_MODE); | |
116 | else | |
117 | regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, | |
118 | I2S_TX_SLAVE_MODE_MASK, | |
119 | I2S_TX_SLAVE_MODE); | |
120 | } else { | |
121 | regmap_update_bits(regmap_i2s, I2S_RX_CFG, | |
122 | I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | | |
123 | I2S_RX_CLOCK_ENABLE, | |
124 | I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | | |
125 | I2S_RX_CLOCK_ENABLE); | |
126 | regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 0); | |
127 | regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 0); | |
128 | regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 1); | |
129 | ||
130 | regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); | |
131 | if (slavemode & I2S_TX_SLAVE_MODE_MASK) | |
132 | regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, | |
133 | I2S_RX_SLAVE_MODE_MASK, 0); | |
134 | else | |
135 | regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, | |
136 | I2S_RX_SLAVE_MODE_MASK, | |
137 | I2S_RX_SLAVE_MODE); | |
138 | } | |
139 | return 0; | |
140 | } | |
141 | ||
142 | static void bcm63xx_i2s_shutdown(struct snd_pcm_substream *substream, | |
143 | struct snd_soc_dai *dai) | |
144 | { | |
145 | unsigned int enabled, slavemode; | |
146 | struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); | |
147 | struct regmap *regmap_i2s = i2s_priv->regmap_i2s; | |
148 | ||
149 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
150 | regmap_update_bits(regmap_i2s, I2S_TX_CFG, | |
151 | I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | | |
152 | I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, 0); | |
153 | regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 1); | |
154 | regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 4); | |
155 | regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 4); | |
156 | ||
157 | regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); | |
158 | slavemode = slavemode & I2S_TX_SLAVE_MODE_MASK; | |
159 | if (!slavemode) { | |
160 | regmap_read(regmap_i2s, I2S_RX_CFG, &enabled); | |
161 | enabled = enabled & I2S_RX_ENABLE_MASK; | |
162 | if (enabled) | |
163 | regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, | |
164 | I2S_RX_SLAVE_MODE_MASK, | |
165 | I2S_RX_MASTER_MODE); | |
166 | } | |
167 | regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, | |
168 | I2S_TX_SLAVE_MODE_MASK, | |
169 | I2S_TX_SLAVE_MODE); | |
170 | } else { | |
171 | regmap_update_bits(regmap_i2s, I2S_RX_CFG, | |
172 | I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | | |
173 | I2S_RX_CLOCK_ENABLE, 0); | |
174 | regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 1); | |
175 | regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 4); | |
176 | regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 4); | |
177 | ||
178 | regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); | |
179 | slavemode = slavemode & I2S_RX_SLAVE_MODE_MASK; | |
180 | if (!slavemode) { | |
181 | regmap_read(regmap_i2s, I2S_TX_CFG, &enabled); | |
182 | enabled = enabled & I2S_TX_ENABLE_MASK; | |
183 | if (enabled) | |
184 | regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, | |
185 | I2S_TX_SLAVE_MODE_MASK, | |
186 | I2S_TX_MASTER_MODE); | |
187 | } | |
188 | ||
189 | regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, | |
190 | I2S_RX_SLAVE_MODE_MASK, I2S_RX_SLAVE_MODE); | |
191 | } | |
192 | } | |
193 | ||
194 | static const struct snd_soc_dai_ops bcm63xx_i2s_dai_ops = { | |
195 | .startup = bcm63xx_i2s_startup, | |
196 | .shutdown = bcm63xx_i2s_shutdown, | |
197 | .hw_params = bcm63xx_i2s_hw_params, | |
198 | }; | |
199 | ||
200 | static struct snd_soc_dai_driver bcm63xx_i2s_dai = { | |
201 | .name = DRV_NAME, | |
202 | .playback = { | |
203 | .channels_min = 2, | |
204 | .channels_max = 2, | |
205 | .rates = SNDRV_PCM_RATE_8000_192000, | |
206 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | |
207 | }, | |
208 | .capture = { | |
209 | .channels_min = 2, | |
210 | .channels_max = 2, | |
211 | .rates = SNDRV_PCM_RATE_8000_192000, | |
212 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | |
213 | }, | |
214 | .ops = &bcm63xx_i2s_dai_ops, | |
a8e94022 | 215 | .symmetric_rate = 1, |
88eb404c KL |
216 | .symmetric_channels = 1, |
217 | }; | |
218 | ||
219 | static const struct snd_soc_component_driver bcm63xx_i2s_component = { | |
220 | .name = "bcm63xx", | |
b9a0db0a | 221 | .legacy_dai_naming = 1, |
88eb404c KL |
222 | }; |
223 | ||
224 | static int bcm63xx_i2s_dev_probe(struct platform_device *pdev) | |
225 | { | |
226 | int ret = 0; | |
227 | void __iomem *regs; | |
228 | struct resource *r_mem, *region; | |
229 | struct bcm_i2s_priv *i2s_priv; | |
230 | struct regmap *regmap_i2s; | |
231 | struct clk *i2s_clk; | |
232 | ||
233 | i2s_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_priv), GFP_KERNEL); | |
234 | if (!i2s_priv) | |
235 | return -ENOMEM; | |
236 | ||
237 | i2s_clk = devm_clk_get(&pdev->dev, "i2sclk"); | |
238 | if (IS_ERR(i2s_clk)) { | |
239 | dev_err(&pdev->dev, "%s: cannot get a brcm clock: %ld\n", | |
240 | __func__, PTR_ERR(i2s_clk)); | |
241 | return PTR_ERR(i2s_clk); | |
242 | } | |
243 | ||
244 | r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
245 | if (!r_mem) { | |
246 | dev_err(&pdev->dev, "Unable to get register resource.\n"); | |
247 | return -ENODEV; | |
248 | } | |
249 | ||
250 | region = devm_request_mem_region(&pdev->dev, r_mem->start, | |
251 | resource_size(r_mem), DRV_NAME); | |
252 | if (!region) { | |
253 | dev_err(&pdev->dev, "Memory region already claimed\n"); | |
254 | return -EBUSY; | |
255 | } | |
256 | ||
257 | regs = devm_ioremap_resource(&pdev->dev, r_mem); | |
258 | if (IS_ERR(regs)) { | |
259 | ret = PTR_ERR(regs); | |
260 | return ret; | |
261 | } | |
262 | ||
263 | regmap_i2s = devm_regmap_init_mmio(&pdev->dev, | |
264 | regs, &brcm_i2s_regmap_config); | |
265 | if (IS_ERR(regmap_i2s)) | |
266 | return PTR_ERR(regmap_i2s); | |
267 | ||
268 | regmap_update_bits(regmap_i2s, I2S_MISC_CFG, | |
269 | I2S_PAD_LVL_LOOP_DIS_MASK, | |
270 | I2S_PAD_LVL_LOOP_DIS_ENABLE); | |
271 | ||
272 | ret = devm_snd_soc_register_component(&pdev->dev, | |
273 | &bcm63xx_i2s_component, | |
274 | &bcm63xx_i2s_dai, 1); | |
275 | if (ret) { | |
276 | dev_err(&pdev->dev, "failed to register the dai\n"); | |
277 | return ret; | |
278 | } | |
279 | ||
280 | i2s_priv->dev = &pdev->dev; | |
281 | i2s_priv->i2s_clk = i2s_clk; | |
282 | i2s_priv->regmap_i2s = regmap_i2s; | |
283 | dev_set_drvdata(&pdev->dev, i2s_priv); | |
284 | ||
285 | ret = bcm63xx_soc_platform_probe(pdev, i2s_priv); | |
286 | if (ret) | |
287 | dev_err(&pdev->dev, "failed to register the pcm\n"); | |
288 | ||
289 | return ret; | |
290 | } | |
291 | ||
ee357de3 | 292 | static void bcm63xx_i2s_dev_remove(struct platform_device *pdev) |
88eb404c KL |
293 | { |
294 | bcm63xx_soc_platform_remove(pdev); | |
88eb404c KL |
295 | } |
296 | ||
297 | #ifdef CONFIG_OF | |
298 | static const struct of_device_id snd_soc_bcm_audio_match[] = { | |
299 | {.compatible = "brcm,bcm63xx-i2s"}, | |
300 | { } | |
301 | }; | |
302 | #endif | |
303 | ||
304 | static struct platform_driver bcm63xx_i2s_driver = { | |
305 | .driver = { | |
306 | .name = DRV_NAME, | |
307 | .of_match_table = of_match_ptr(snd_soc_bcm_audio_match), | |
308 | }, | |
309 | .probe = bcm63xx_i2s_dev_probe, | |
ee357de3 | 310 | .remove_new = bcm63xx_i2s_dev_remove, |
88eb404c KL |
311 | }; |
312 | ||
313 | module_platform_driver(bcm63xx_i2s_driver); | |
314 | ||
315 | MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>"); | |
316 | MODULE_DESCRIPTION("Broadcom DSL XPON ASOC I2S Interface"); | |
317 | MODULE_LICENSE("GPL v2"); |