1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for audio on multifunction CS5535/6 companion device |
4 | * Copyright (C) Jaya Kumar |
5 | * |
6 | * Based on Jaroslav Kysela and Takashi Iwai's examples. |
7 | * This work was sponsored by CIS(M) Sdn Bhd. |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/init.h> |
13 | #include <linux/pci.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/module.h> |
16 | #include <linux/io.h> |
17 | #include <sound/core.h> |
18 | #include <sound/control.h> |
19 | #include <sound/pcm.h> |
20 | #include <sound/rawmidi.h> |
21 | #include <sound/ac97_codec.h> |
22 | #include <sound/initval.h> |
23 | #include <sound/asoundef.h> |
24 | #include "cs5535audio.h" |
25 | |
26 | #define DRIVER_NAME "cs5535audio" |
27 | |
28 | static char *ac97_quirk; |
29 | module_param(ac97_quirk, charp, 0444); |
30 | MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds." ); |
31 | |
32 | static const struct ac97_quirk ac97_quirks[] = { |
33 | #if 0 /* Not yet confirmed if all 5536 boards are HP only */ |
34 | { |
35 | .subvendor = PCI_VENDOR_ID_AMD, |
36 | .subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, |
37 | .name = "AMD RDK" , |
38 | .type = AC97_TUNE_HP_ONLY |
39 | }, |
40 | #endif |
41 | {} |
42 | }; |
43 | |
44 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
45 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; |
46 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; |
47 | |
48 | module_param_array(index, int, NULL, 0444); |
49 | MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME); |
50 | module_param_array(id, charp, NULL, 0444); |
51 | MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME); |
52 | module_param_array(enable, bool, NULL, 0444); |
53 | MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME); |
54 | |
55 | static const struct pci_device_id snd_cs5535audio_ids[] = { |
56 | { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) }, |
57 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) }, |
58 | {} |
59 | }; |
60 | |
61 | MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids); |
62 | |
63 | static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout) |
64 | { |
65 | unsigned int tmp; |
66 | do { |
67 | tmp = cs_readl(cs5535au, ACC_CODEC_CNTL); |
68 | if (!(tmp & CMD_NEW)) |
69 | break; |
70 | udelay(1); |
71 | } while (--timeout); |
72 | if (!timeout) |
73 | dev_err(cs5535au->card->dev, |
74 | "Failure writing to cs5535 codec\n" ); |
75 | } |
76 | |
77 | static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au, |
78 | unsigned short reg) |
79 | { |
80 | unsigned int regdata; |
81 | unsigned int timeout; |
82 | unsigned int val; |
83 | |
84 | regdata = ((unsigned int) reg) << 24; |
85 | regdata |= ACC_CODEC_CNTL_RD_CMD; |
86 | regdata |= CMD_NEW; |
87 | |
88 | cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); |
89 | wait_till_cmd_acked(cs5535au, timeout: 50); |
90 | |
91 | timeout = 50; |
92 | do { |
93 | val = cs_readl(cs5535au, ACC_CODEC_STATUS); |
94 | if ((val & STS_NEW) && reg == (val >> 24)) |
95 | break; |
96 | udelay(1); |
97 | } while (--timeout); |
98 | if (!timeout) |
99 | dev_err(cs5535au->card->dev, |
100 | "Failure reading codec reg 0x%x, Last value=0x%x\n" , |
101 | reg, val); |
102 | |
103 | return (unsigned short) val; |
104 | } |
105 | |
106 | static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au, |
107 | unsigned short reg, unsigned short val) |
108 | { |
109 | unsigned int regdata; |
110 | |
111 | regdata = ((unsigned int) reg) << 24; |
112 | regdata |= val; |
113 | regdata &= CMD_MASK; |
114 | regdata |= CMD_NEW; |
115 | regdata &= ACC_CODEC_CNTL_WR_CMD; |
116 | |
117 | cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); |
118 | wait_till_cmd_acked(cs5535au, timeout: 50); |
119 | } |
120 | |
121 | static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97, |
122 | unsigned short reg, unsigned short val) |
123 | { |
124 | struct cs5535audio *cs5535au = ac97->private_data; |
125 | snd_cs5535audio_codec_write(cs5535au, reg, val); |
126 | } |
127 | |
128 | static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97, |
129 | unsigned short reg) |
130 | { |
131 | struct cs5535audio *cs5535au = ac97->private_data; |
132 | return snd_cs5535audio_codec_read(cs5535au, reg); |
133 | } |
134 | |
135 | static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au) |
136 | { |
137 | struct snd_card *card = cs5535au->card; |
138 | struct snd_ac97_bus *pbus; |
139 | struct snd_ac97_template ac97; |
140 | int err; |
141 | static const struct snd_ac97_bus_ops ops = { |
142 | .write = snd_cs5535audio_ac97_codec_write, |
143 | .read = snd_cs5535audio_ac97_codec_read, |
144 | }; |
145 | |
146 | err = snd_ac97_bus(card, num: 0, ops: &ops, NULL, rbus: &pbus); |
147 | if (err < 0) |
148 | return err; |
149 | |
150 | memset(&ac97, 0, sizeof(ac97)); |
151 | ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM |
152 | | AC97_SCAP_POWER_SAVE; |
153 | ac97.private_data = cs5535au; |
154 | ac97.pci = cs5535au->pci; |
155 | |
156 | /* set any OLPC-specific scaps */ |
157 | olpc_prequirks(card, ac97: &ac97); |
158 | |
159 | err = snd_ac97_mixer(bus: pbus, template: &ac97, rac97: &cs5535au->ac97); |
160 | if (err < 0) { |
161 | dev_err(card->dev, "mixer failed\n" ); |
162 | return err; |
163 | } |
164 | |
165 | snd_ac97_tune_hardware(ac97: cs5535au->ac97, quirk: ac97_quirks, override: ac97_quirk); |
166 | |
167 | err = olpc_quirks(card, ac97: cs5535au->ac97); |
168 | if (err < 0) { |
169 | dev_err(card->dev, "olpc quirks failed\n" ); |
170 | return err; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static void process_bm0_irq(struct cs5535audio *cs5535au) |
177 | { |
178 | u8 bm_stat; |
179 | spin_lock(lock: &cs5535au->reg_lock); |
180 | bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS); |
181 | spin_unlock(lock: &cs5535au->reg_lock); |
182 | if (bm_stat & EOP) { |
183 | snd_pcm_period_elapsed(substream: cs5535au->playback_substream); |
184 | } else { |
185 | dev_err(cs5535au->card->dev, |
186 | "unexpected bm0 irq src, bm_stat=%x\n" , |
187 | bm_stat); |
188 | } |
189 | } |
190 | |
191 | static void process_bm1_irq(struct cs5535audio *cs5535au) |
192 | { |
193 | u8 bm_stat; |
194 | spin_lock(lock: &cs5535au->reg_lock); |
195 | bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS); |
196 | spin_unlock(lock: &cs5535au->reg_lock); |
197 | if (bm_stat & EOP) |
198 | snd_pcm_period_elapsed(substream: cs5535au->capture_substream); |
199 | } |
200 | |
201 | static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id) |
202 | { |
203 | u16 acc_irq_stat; |
204 | unsigned char count; |
205 | struct cs5535audio *cs5535au = dev_id; |
206 | |
207 | if (cs5535au == NULL) |
208 | return IRQ_NONE; |
209 | |
210 | acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS); |
211 | |
212 | if (!acc_irq_stat) |
213 | return IRQ_NONE; |
214 | for (count = 0; count < 4; count++) { |
215 | if (acc_irq_stat & (1 << count)) { |
216 | switch (count) { |
217 | case IRQ_STS: |
218 | cs_readl(cs5535au, ACC_GPIO_STATUS); |
219 | break; |
220 | case WU_IRQ_STS: |
221 | cs_readl(cs5535au, ACC_GPIO_STATUS); |
222 | break; |
223 | case BM0_IRQ_STS: |
224 | process_bm0_irq(cs5535au); |
225 | break; |
226 | case BM1_IRQ_STS: |
227 | process_bm1_irq(cs5535au); |
228 | break; |
229 | default: |
230 | dev_err(cs5535au->card->dev, |
231 | "Unexpected irq src: 0x%x\n" , |
232 | acc_irq_stat); |
233 | break; |
234 | } |
235 | } |
236 | } |
237 | return IRQ_HANDLED; |
238 | } |
239 | |
240 | static void snd_cs5535audio_free(struct snd_card *card) |
241 | { |
242 | olpc_quirks_cleanup(); |
243 | } |
244 | |
245 | static int snd_cs5535audio_create(struct snd_card *card, |
246 | struct pci_dev *pci) |
247 | { |
248 | struct cs5535audio *cs5535au = card->private_data; |
249 | int err; |
250 | |
251 | err = pcim_enable_device(pdev: pci); |
252 | if (err < 0) |
253 | return err; |
254 | |
255 | if (dma_set_mask_and_coherent(dev: &pci->dev, DMA_BIT_MASK(32))) { |
256 | dev_warn(card->dev, "unable to get 32bit dma\n" ); |
257 | return -ENXIO; |
258 | } |
259 | |
260 | spin_lock_init(&cs5535au->reg_lock); |
261 | cs5535au->card = card; |
262 | cs5535au->pci = pci; |
263 | cs5535au->irq = -1; |
264 | |
265 | err = pci_request_regions(pci, "CS5535 Audio" ); |
266 | if (err < 0) |
267 | return err; |
268 | |
269 | cs5535au->port = pci_resource_start(pci, 0); |
270 | |
271 | if (devm_request_irq(dev: &pci->dev, irq: pci->irq, handler: snd_cs5535audio_interrupt, |
272 | IRQF_SHARED, KBUILD_MODNAME, dev_id: cs5535au)) { |
273 | dev_err(card->dev, "unable to grab IRQ %d\n" , pci->irq); |
274 | return -EBUSY; |
275 | } |
276 | |
277 | cs5535au->irq = pci->irq; |
278 | card->sync_irq = cs5535au->irq; |
279 | pci_set_master(dev: pci); |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | static int __snd_cs5535audio_probe(struct pci_dev *pci, |
285 | const struct pci_device_id *pci_id) |
286 | { |
287 | static int dev; |
288 | struct snd_card *card; |
289 | struct cs5535audio *cs5535au; |
290 | int err; |
291 | |
292 | if (dev >= SNDRV_CARDS) |
293 | return -ENODEV; |
294 | if (!enable[dev]) { |
295 | dev++; |
296 | return -ENOENT; |
297 | } |
298 | |
299 | err = snd_devm_card_new(parent: &pci->dev, idx: index[dev], xid: id[dev], THIS_MODULE, |
300 | extra_size: sizeof(*cs5535au), card_ret: &card); |
301 | if (err < 0) |
302 | return err; |
303 | cs5535au = card->private_data; |
304 | card->private_free = snd_cs5535audio_free; |
305 | |
306 | err = snd_cs5535audio_create(card, pci); |
307 | if (err < 0) |
308 | return err; |
309 | |
310 | err = snd_cs5535audio_mixer(cs5535au); |
311 | if (err < 0) |
312 | return err; |
313 | |
314 | err = snd_cs5535audio_pcm(cs5535audio: cs5535au); |
315 | if (err < 0) |
316 | return err; |
317 | |
318 | strcpy(p: card->driver, DRIVER_NAME); |
319 | |
320 | strcpy(p: card->shortname, q: "CS5535 Audio" ); |
321 | sprintf(buf: card->longname, fmt: "%s %s at 0x%lx, irq %i" , |
322 | card->shortname, card->driver, |
323 | cs5535au->port, cs5535au->irq); |
324 | |
325 | err = snd_card_register(card); |
326 | if (err < 0) |
327 | return err; |
328 | |
329 | pci_set_drvdata(pdev: pci, data: card); |
330 | dev++; |
331 | return 0; |
332 | } |
333 | |
334 | static int snd_cs5535audio_probe(struct pci_dev *pci, |
335 | const struct pci_device_id *pci_id) |
336 | { |
337 | return snd_card_free_on_error(dev: &pci->dev, ret: __snd_cs5535audio_probe(pci, pci_id)); |
338 | } |
339 | |
340 | static struct pci_driver cs5535audio_driver = { |
341 | .name = KBUILD_MODNAME, |
342 | .id_table = snd_cs5535audio_ids, |
343 | .probe = snd_cs5535audio_probe, |
344 | #ifdef CONFIG_PM_SLEEP |
345 | .driver = { |
346 | .pm = &snd_cs5535audio_pm, |
347 | }, |
348 | #endif |
349 | }; |
350 | |
351 | module_pci_driver(cs5535audio_driver); |
352 | |
353 | MODULE_AUTHOR("Jaya Kumar" ); |
354 | MODULE_LICENSE("GPL" ); |
355 | MODULE_DESCRIPTION("CS5535 Audio" ); |
356 | |