aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CREDITS1
-rw-r--r--MAINTAINERS5
-rw-r--r--include/linux/pci_ids.h4
-rw-r--r--sound/pci/Kconfig13
-rw-r--r--sound/pci/Makefile1
-rw-r--r--sound/pci/cs5535audio/Makefile8
-rw-r--r--sound/pci/cs5535audio/cs5535audio.c410
-rw-r--r--sound/pci/cs5535audio/cs5535audio.h123
-rw-r--r--sound/pci/cs5535audio/cs5535audio_pcm.c430
9 files changed, 995 insertions, 0 deletions
diff --git a/CREDITS b/CREDITS
index 1b4f8694fa48..521f00d1b549 100644
--- a/CREDITS
+++ b/CREDITS
@@ -1883,6 +1883,7 @@ N: Jaya Kumar
1883E: jayalk@intworks.biz 1883E: jayalk@intworks.biz
1884W: http://www.intworks.biz 1884W: http://www.intworks.biz
1885D: Arc monochrome LCD framebuffer driver, x86 reboot fixups 1885D: Arc monochrome LCD framebuffer driver, x86 reboot fixups
1886D: pirq addr, CS5535 alsa audio driver
1886S: Gurgaon, India 1887S: Gurgaon, India
1887S: Kuala Lumpur, Malaysia 1888S: Kuala Lumpur, Malaysia
1888 1889
diff --git a/MAINTAINERS b/MAINTAINERS
index 6af683025ae0..93f97b3afac5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -650,6 +650,11 @@ L: linux-crypto@vger.kernel.org
650T: git kernel.org:/pub/scm/linux/kernel/git/herbert/crypto-2.6.git 650T: git kernel.org:/pub/scm/linux/kernel/git/herbert/crypto-2.6.git
651S: Maintained 651S: Maintained
652 652
653CS5535 Audio ALSA driver
654P: Jaya Kumar
655M: jayakumar.alsa@gmail.com
656S: Maintained
657
653CYBERPRO FB DRIVER 658CYBERPRO FB DRIVER
654P: Russell King 659P: Russell King
655M: rmk@arm.linux.org.uk 660M: rmk@arm.linux.org.uk
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 4db67b3b05cc..9093f118f99d 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -376,6 +376,10 @@
376#define PCI_DEVICE_ID_NS_87560_USB 0x0012 376#define PCI_DEVICE_ID_NS_87560_USB 0x0012
377#define PCI_DEVICE_ID_NS_83815 0x0020 377#define PCI_DEVICE_ID_NS_83815 0x0020
378#define PCI_DEVICE_ID_NS_83820 0x0022 378#define PCI_DEVICE_ID_NS_83820 0x0022
379#define PCI_DEVICE_ID_NS_CS5535_IDE 0x002d
380#define PCI_DEVICE_ID_NS_CS5535_AUDIO 0x002e
381#define PCI_DEVICE_ID_NS_CS5535_USB 0x002f
382#define PCI_DEVICE_ID_NS_CS5535_VIDEO 0x0030
379#define PCI_DEVICE_ID_NS_SATURN 0x0035 383#define PCI_DEVICE_ID_NS_SATURN 0x0035
380#define PCI_DEVICE_ID_NS_SCx200_BRIDGE 0x0500 384#define PCI_DEVICE_ID_NS_SCx200_BRIDGE 0x0500
381#define PCI_DEVICE_ID_NS_SCx200_SMI 0x0501 385#define PCI_DEVICE_ID_NS_SCx200_SMI 0x0501
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 0fb16cf335ea..920305c7402f 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -359,6 +359,19 @@ config SND_ENS1370
359 To compile this driver as a module, choose M here: the module 359 To compile this driver as a module, choose M here: the module
360 will be called snd-ens1370. 360 will be called snd-ens1370.
361 361
362config SND_CS5535AUDIO
363 tristate "CS5535 Audio"
364 depends on SND && X86 && !X86_64
365 select SND_PCM
366 select SND_AC97_CODEC
367 help
368 Say Y here to include support for audio on CS5535 chips. It is
369 referred to as NS CS5535 IO or AMD CS5535 IO companion in
370 various literature.
371
372 To compile this driver as a module, choose M here: the module
373 will be called snd-cs5535audio.
374
362config SND_ENS1371 375config SND_ENS1371
363 tristate "(Creative) Ensoniq AudioPCI 1371/1373" 376 tristate "(Creative) Ensoniq AudioPCI 1371/1373"
364 depends on SND 377 depends on SND
diff --git a/sound/pci/Makefile b/sound/pci/Makefile
index 42fabfcfc2a9..82a9c734f84d 100644
--- a/sound/pci/Makefile
+++ b/sound/pci/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_SND) += \
54 au88x0/ \ 54 au88x0/ \
55 ca0106/ \ 55 ca0106/ \
56 cs46xx/ \ 56 cs46xx/ \
57 cs5535audio/ \
57 emu10k1/ \ 58 emu10k1/ \
58 hda/ \ 59 hda/ \
59 ice1712/ \ 60 ice1712/ \
diff --git a/sound/pci/cs5535audio/Makefile b/sound/pci/cs5535audio/Makefile
new file mode 100644
index 000000000000..08d8ee6547d3
--- /dev/null
+++ b/sound/pci/cs5535audio/Makefile
@@ -0,0 +1,8 @@
1#
2# Makefile for cs5535audio
3#
4
5snd-cs5535audio-objs := cs5535audio.o cs5535audio_pcm.o
6
7# Toplevel Module Dependency
8obj-$(CONFIG_SND_CS5535AUDIO) += snd-cs5535audio.o
diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c
new file mode 100644
index 000000000000..920c857fc223
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio.c
@@ -0,0 +1,410 @@
1/*
2 * Driver for audio on multifunction CS5535 companion device
3 * Copyright (C) Jaya Kumar
4 *
5 * Based on Jaroslav Kysela and Takashi Iwai's examples.
6 * This work was sponsored by CIS(M) Sdn Bhd.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
23
24#include <linux/delay.h>
25#include <linux/interrupt.h>
26#include <linux/init.h>
27#include <linux/pci.h>
28#include <linux/slab.h>
29#include <linux/moduleparam.h>
30#include <asm/io.h>
31#include <sound/driver.h>
32#include <sound/core.h>
33#include <sound/control.h>
34#include <sound/pcm.h>
35#include <sound/rawmidi.h>
36#include <sound/ac97_codec.h>
37#include <sound/initval.h>
38#include <sound/asoundef.h>
39#include "cs5535audio.h"
40
41#define DRIVER_NAME "cs5535audio"
42
43
44static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
45static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
46static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
47
48static struct pci_device_id snd_cs5535audio_ids[] = {
49 { PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO, PCI_ANY_ID,
50 PCI_ANY_ID, 0, 0, 0, },
51 {}
52};
53
54MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids);
55
56static void wait_till_cmd_acked(cs5535audio_t *cs5535au, unsigned long timeout)
57{
58 unsigned long tmp;
59 do {
60 tmp = cs_readl(cs5535au, ACC_CODEC_CNTL);
61 if (!(tmp & CMD_NEW))
62 break;
63 msleep(10);
64 } while (--timeout);
65 if (!timeout)
66 snd_printk(KERN_ERR "Failure writing to cs5535 codec\n");
67}
68
69static unsigned short snd_cs5535audio_codec_read(cs5535audio_t *cs5535au,
70 unsigned short reg)
71{
72 unsigned long regdata;
73 unsigned long timeout;
74 unsigned long val;
75
76 regdata = ((unsigned long) reg) << 24;
77 regdata |= ACC_CODEC_CNTL_RD_CMD;
78 regdata |= CMD_NEW;
79
80 cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
81 wait_till_cmd_acked(cs5535au, 500);
82
83 timeout = 50;
84 do {
85 val = cs_readl(cs5535au, ACC_CODEC_STATUS);
86 if ( (val & STS_NEW) &&
87 ((unsigned long) reg == ((0xFF000000 & val)>>24)) )
88 break;
89 msleep(10);
90 } while (--timeout);
91 if (!timeout)
92 snd_printk(KERN_ERR "Failure reading cs5535 codec\n");
93
94 return ((unsigned short) val);
95}
96
97static void snd_cs5535audio_codec_write(cs5535audio_t *cs5535au,
98 unsigned short reg, unsigned short val)
99{
100 unsigned long regdata;
101
102 regdata = ((unsigned long) reg) << 24;
103 regdata |= (unsigned long) val;
104 regdata &= CMD_MASK;
105 regdata |= CMD_NEW;
106 regdata &= ACC_CODEC_CNTL_WR_CMD;
107
108 cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
109 wait_till_cmd_acked(cs5535au, 50);
110}
111
112static void snd_cs5535audio_ac97_codec_write(ac97_t *ac97,
113 unsigned short reg, unsigned short val)
114{
115 cs5535audio_t *cs5535au = ac97->private_data;
116 snd_cs5535audio_codec_write(cs5535au, reg, val);
117}
118
119static unsigned short snd_cs5535audio_ac97_codec_read(ac97_t *ac97,
120 unsigned short reg)
121{
122 cs5535audio_t *cs5535au = ac97->private_data;
123 return snd_cs5535audio_codec_read(cs5535au, reg);
124}
125
126static void snd_cs5535audio_mixer_free_ac97(ac97_t *ac97)
127{
128 cs5535audio_t *cs5535audio = ac97->private_data;
129 cs5535audio->ac97 = NULL;
130}
131
132static int snd_cs5535audio_mixer(cs5535audio_t *cs5535au)
133{
134 snd_card_t *card = cs5535au->card;
135 ac97_bus_t *pbus;
136 ac97_template_t ac97;
137 int err;
138 static ac97_bus_ops_t ops = {
139 .write = snd_cs5535audio_ac97_codec_write,
140 .read = snd_cs5535audio_ac97_codec_read,
141 };
142
143 if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
144 return err;
145
146 memset(&ac97, 0, sizeof(ac97));
147 ac97.scaps = AC97_SCAP_AUDIO|AC97_SCAP_SKIP_MODEM;
148 ac97.private_data = cs5535au;
149 ac97.pci = cs5535au->pci;
150 ac97.private_free = snd_cs5535audio_mixer_free_ac97;
151
152 if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 0) {
153 snd_printk("mixer failed\n");
154 return err;
155 }
156
157 return 0;
158}
159
160static void process_bm0_irq(cs5535audio_t *cs5535au)
161{
162 u8 bm_stat;
163 spin_lock(&cs5535au->reg_lock);
164 bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
165 spin_unlock(&cs5535au->reg_lock);
166 if (bm_stat & EOP) {
167 cs5535audio_dma_t *dma;
168 dma = cs5535au->playback_substream->runtime->private_data;
169 snd_pcm_period_elapsed(cs5535au->playback_substream);
170 } else {
171 snd_printk(KERN_ERR "unexpected bm0 irq src, bm_stat=%x\n",
172 bm_stat);
173 }
174}
175
176static void process_bm1_irq(cs5535audio_t *cs5535au)
177{
178 u8 bm_stat;
179 spin_lock(&cs5535au->reg_lock);
180 bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
181 spin_unlock(&cs5535au->reg_lock);
182 if (bm_stat & EOP) {
183 cs5535audio_dma_t *dma;
184 dma = cs5535au->capture_substream->runtime->private_data;
185 snd_pcm_period_elapsed(cs5535au->capture_substream);
186 }
187}
188
189static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id,
190 struct pt_regs *regs)
191{
192 u16 acc_irq_stat;
193 u8 bm_stat;
194 unsigned char count;
195 cs5535audio_t *cs5535au = dev_id;
196
197 if (cs5535au == NULL)
198 return IRQ_NONE;
199
200 acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS);
201
202 if (!acc_irq_stat)
203 return IRQ_NONE;
204 for (count=0; count < 10; count++) {
205 if (acc_irq_stat & (1<<count)) {
206 switch (count) {
207 case IRQ_STS:
208 cs_readl(cs5535au, ACC_GPIO_STATUS);
209 break;
210 case WU_IRQ_STS:
211 cs_readl(cs5535au, ACC_GPIO_STATUS);
212 break;
213 case BM0_IRQ_STS:
214 process_bm0_irq(cs5535au);
215 break;
216 case BM1_IRQ_STS:
217 process_bm1_irq(cs5535au);
218 break;
219 case BM2_IRQ_STS:
220 bm_stat = cs_readb(cs5535au, ACC_BM2_STATUS);
221 break;
222 case BM3_IRQ_STS:
223 bm_stat = cs_readb(cs5535au, ACC_BM3_STATUS);
224 break;
225 case BM4_IRQ_STS:
226 bm_stat = cs_readb(cs5535au, ACC_BM4_STATUS);
227 break;
228 case BM5_IRQ_STS:
229 bm_stat = cs_readb(cs5535au, ACC_BM5_STATUS);
230 break;
231 case BM6_IRQ_STS:
232 bm_stat = cs_readb(cs5535au, ACC_BM6_STATUS);
233 break;
234 case BM7_IRQ_STS:
235 bm_stat = cs_readb(cs5535au, ACC_BM7_STATUS);
236 break;
237 default:
238 snd_printk(KERN_ERR "Unexpected irq src\n");
239 break;
240 }
241 }
242 }
243 return IRQ_HANDLED;
244}
245
246static int snd_cs5535audio_free(cs5535audio_t *cs5535au)
247{
248 synchronize_irq(cs5535au->irq);
249 pci_set_power_state(cs5535au->pci, 3);
250
251 if (cs5535au->irq >= 0)
252 free_irq(cs5535au->irq, cs5535au);
253
254 pci_release_regions(cs5535au->pci);
255 pci_disable_device(cs5535au->pci);
256 kfree(cs5535au);
257 return 0;
258}
259
260static int snd_cs5535audio_dev_free(snd_device_t *device)
261{
262 cs5535audio_t *cs5535au = device->device_data;
263 return snd_cs5535audio_free(cs5535au);
264}
265
266static int __devinit snd_cs5535audio_create(snd_card_t *card,
267 struct pci_dev *pci,
268 cs5535audio_t **rcs5535au)
269{
270 cs5535audio_t *cs5535au;
271
272 int err;
273 static snd_device_ops_t ops = {
274 .dev_free = snd_cs5535audio_dev_free,
275 };
276
277 *rcs5535au = NULL;
278 if ((err = pci_enable_device(pci)) < 0)
279 return err;
280
281 if (pci_set_dma_mask(pci, DMA_32BIT_MASK) < 0 ||
282 pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK) < 0) {
283 printk(KERN_WARNING "unable to get 32bit dma\n");
284 err = -ENXIO;
285 goto pcifail;
286 }
287
288 cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
289 if (cs5535au == NULL) {
290 err = -ENOMEM;
291 goto pcifail;
292 }
293
294 spin_lock_init(&cs5535au->reg_lock);
295 cs5535au->card = card;
296 cs5535au->pci = pci;
297 cs5535au->irq = -1;
298
299 if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
300 kfree(cs5535au);
301 goto pcifail;
302 }
303
304 cs5535au->port = pci_resource_start(pci, 0);
305
306 if (request_irq(pci->irq, snd_cs5535audio_interrupt,
307 SA_INTERRUPT|SA_SHIRQ, "CS5535 Audio", cs5535au)) {
308 snd_printk("unable to grab IRQ %d\n", pci->irq);
309 err = -EBUSY;
310 goto sndfail;
311 }
312
313 cs5535au->irq = pci->irq;
314 pci_set_master(pci);
315
316 if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
317 cs5535au, &ops)) < 0)
318 goto sndfail;
319
320 snd_card_set_dev(card, &pci->dev);
321
322 *rcs5535au = cs5535au;
323 return 0;
324
325sndfail: /* leave the device alive, just kill the snd */
326 snd_cs5535audio_free(cs5535au);
327 return err;
328
329pcifail:
330 pci_disable_device(pci);
331 return err;
332}
333
334static int __devinit snd_cs5535audio_probe(struct pci_dev *pci,
335 const struct pci_device_id *pci_id)
336{
337 static int dev;
338 snd_card_t *card;
339 cs5535audio_t *cs5535au;
340 int err;
341
342 if (dev >= SNDRV_CARDS)
343 return -ENODEV;
344 if (!enable[dev]) {
345 dev++;
346 return -ENOENT;
347 }
348
349 card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
350 if (card == NULL)
351 return -ENOMEM;
352
353 if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
354 goto probefail_out;
355
356 if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
357 goto probefail_out;
358
359 if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
360 goto probefail_out;
361
362 strcpy(card->driver, DRIVER_NAME);
363
364 strcpy(card->shortname, "CS5535 Audio");
365 sprintf(card->longname, "%s %s at 0x%lx, irq %i",
366 card->shortname, card->driver,
367 cs5535au->port, cs5535au->irq);
368
369 if ((err = snd_card_register(card)) < 0)
370 goto probefail_out;
371
372 pci_set_drvdata(pci, card);
373 dev++;
374 return 0;
375
376probefail_out:
377 snd_card_free(card);
378 return err;
379}
380
381static void __devexit snd_cs5535audio_remove(struct pci_dev *pci)
382{
383 snd_card_free(pci_get_drvdata(pci));
384 pci_set_drvdata(pci, NULL);
385}
386
387static struct pci_driver driver = {
388 .name = DRIVER_NAME,
389 .id_table = snd_cs5535audio_ids,
390 .probe = snd_cs5535audio_probe,
391 .remove = __devexit_p(snd_cs5535audio_remove),
392};
393
394static int __init alsa_card_cs5535audio_init(void)
395{
396 return pci_module_init(&driver);
397}
398
399static void __exit alsa_card_cs5535audio_exit(void)
400{
401 pci_unregister_driver(&driver);
402}
403
404module_init(alsa_card_cs5535audio_init)
405module_exit(alsa_card_cs5535audio_exit)
406
407MODULE_AUTHOR("Jaya Kumar");
408MODULE_LICENSE("GPL");
409MODULE_DESCRIPTION("CS5535 Audio");
410MODULE_SUPPORTED_DEVICE("CS5535 Audio");
diff --git a/sound/pci/cs5535audio/cs5535audio.h b/sound/pci/cs5535audio/cs5535audio.h
new file mode 100644
index 000000000000..e28177fb0991
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio.h
@@ -0,0 +1,123 @@
1#ifndef __SOUND_CS5535AUDIO_H
2#define __SOUND_CS5535AUDIO_H
3
4#define cs_writel(cs5535au, reg, val) outl(val, (int) cs5535au->port + reg)
5#define cs_writeb(cs5535au, reg, val) outb(val, (int) cs5535au->port + reg)
6#define cs_readl(cs5535au, reg) inl((unsigned short) (cs5535au->port + reg))
7#define cs_readw(cs5535au, reg) inw((unsigned short) (cs5535au->port + reg))
8#define cs_readb(cs5535au, reg) inb((unsigned short) (cs5535au->port + reg))
9
10#define CS5535AUDIO_MAX_DESCRIPTORS 128
11
12/* acc_codec bar0 reg addrs */
13#define ACC_GPIO_STATUS 0x00
14#define ACC_CODEC_STATUS 0x08
15#define ACC_CODEC_CNTL 0x0C
16#define ACC_IRQ_STATUS 0x12
17#define ACC_BM0_CMD 0x20
18#define ACC_BM1_CMD 0x28
19#define ACC_BM2_CMD 0x30
20#define ACC_BM3_CMD 0x38
21#define ACC_BM4_CMD 0x40
22#define ACC_BM5_CMD 0x48
23#define ACC_BM6_CMD 0x50
24#define ACC_BM7_CMD 0x58
25#define ACC_BM0_PRD 0x24
26#define ACC_BM1_PRD 0x2C
27#define ACC_BM2_PRD 0x34
28#define ACC_BM3_PRD 0x3C
29#define ACC_BM4_PRD 0x44
30#define ACC_BM5_PRD 0x4C
31#define ACC_BM6_PRD 0x54
32#define ACC_BM7_PRD 0x5C
33#define ACC_BM0_STATUS 0x21
34#define ACC_BM1_STATUS 0x29
35#define ACC_BM2_STATUS 0x31
36#define ACC_BM3_STATUS 0x39
37#define ACC_BM4_STATUS 0x41
38#define ACC_BM5_STATUS 0x49
39#define ACC_BM6_STATUS 0x51
40#define ACC_BM7_STATUS 0x59
41#define ACC_BM0_PNTR 0x60
42#define ACC_BM1_PNTR 0x64
43#define ACC_BM2_PNTR 0x68
44#define ACC_BM3_PNTR 0x6C
45#define ACC_BM4_PNTR 0x70
46#define ACC_BM5_PNTR 0x74
47#define ACC_BM6_PNTR 0x78
48#define ACC_BM7_PNTR 0x7C
49/* acc_codec bar0 reg bits */
50/* ACC_IRQ_STATUS */
51#define IRQ_STS 0
52#define WU_IRQ_STS 1
53#define BM0_IRQ_STS 2
54#define BM1_IRQ_STS 3
55#define BM2_IRQ_STS 4
56#define BM3_IRQ_STS 5
57#define BM4_IRQ_STS 6
58#define BM5_IRQ_STS 7
59#define BM6_IRQ_STS 8
60#define BM7_IRQ_STS 9
61/* ACC_BMX_STATUS */
62#define EOP (1<<0)
63#define BM_EOP_ERR (1<<1)
64/* ACC_BMX_CTL */
65#define BM_CTL_EN 0x00000001
66#define BM_CTL_PAUSE 0x00000011
67#define BM_CTL_DIS 0x00000000
68#define BM_CTL_BYTE_ORD_LE 0x00000000
69#define BM_CTL_BYTE_ORD_BE 0x00000100
70/* cs5535 specific ac97 codec register defines */
71#define CMD_MASK 0xFF00FFFF
72#define CMD_NEW 0x00010000
73#define STS_NEW 0x00020000
74#define PRM_RDY_STS 0x00800000
75#define ACC_CODEC_CNTL_WR_CMD (~0x80000000)
76#define ACC_CODEC_CNTL_RD_CMD 0x80000000
77#define PRD_JMP 0x2000
78#define PRD_EOP 0x4000
79#define PRD_EOT 0x8000
80
81typedef struct _snd_cs5535audio cs5535audio_t;
82typedef struct snd_cs5535audio_dma cs5535audio_dma_t;
83typedef struct snd_cs5535audio_dma_ops cs5535audio_dma_ops_t;
84
85enum { CS5535AUDIO_DMA_PLAYBACK, CS5535AUDIO_DMA_CAPTURE, NUM_CS5535AUDIO_DMAS };
86struct snd_cs5535audio_dma_ops {
87 int type;
88 void (*enable_dma)(cs5535audio_t *cs5535au);
89 void (*disable_dma)(cs5535audio_t *cs5535au);
90 void (*pause_dma)(cs5535audio_t *cs5535au);
91 void (*setup_prd)(cs5535audio_t *cs5535au, u32 prd_addr);
92 u32 (*read_dma_pntr)(cs5535audio_t *cs5535au);
93};
94
95typedef struct cs5535audio_dma_desc {
96 u32 addr;
97 u16 size;
98 u16 ctlreserved;
99} cs5535audio_dma_desc_t;
100
101struct snd_cs5535audio_dma {
102 const cs5535audio_dma_ops_t *ops;
103 struct snd_dma_buffer desc_buf;
104 snd_pcm_substream_t *substream;
105 unsigned int buf_addr, buf_bytes;
106 unsigned int period_bytes, periods;
107};
108
109struct _snd_cs5535audio {
110 snd_card_t *card;
111 ac97_t *ac97;
112 int irq;
113 struct pci_dev *pci;
114 unsigned long port;
115 spinlock_t reg_lock;
116 snd_pcm_substream_t *playback_substream;
117 snd_pcm_substream_t *capture_substream;
118 cs5535audio_dma_t dmas[NUM_CS5535AUDIO_DMAS];
119};
120
121int __devinit snd_cs5535audio_pcm(cs5535audio_t *cs5535audio);
122#endif /* __SOUND_CS5535AUDIO_H */
123
diff --git a/sound/pci/cs5535audio/cs5535audio_pcm.c b/sound/pci/cs5535audio/cs5535audio_pcm.c
new file mode 100644
index 000000000000..5802ed9d57be
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_pcm.c
@@ -0,0 +1,430 @@
1/*
2 * Driver for audio on multifunction CS5535 companion device
3 * Copyright (C) Jaya Kumar
4 *
5 * Based on Jaroslav Kysela and Takashi Iwai's examples.
6 * This work was sponsored by CIS(M) Sdn Bhd.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 * todo: add be fmt support, spdif, pm
23 */
24
25#include <linux/init.h>
26#include <linux/slab.h>
27#include <linux/pci.h>
28#include <sound/driver.h>
29#include <sound/core.h>
30#include <sound/control.h>
31#include <sound/initval.h>
32#include <sound/asoundef.h>
33#include <sound/pcm.h>
34#include <sound/pcm_params.h>
35#include <sound/ac97_codec.h>
36#include "cs5535audio.h"
37
38static snd_pcm_hardware_t snd_cs5535audio_playback =
39{
40 .info = (
41 SNDRV_PCM_INFO_MMAP |
42 SNDRV_PCM_INFO_INTERLEAVED |
43 SNDRV_PCM_INFO_BLOCK_TRANSFER |
44 SNDRV_PCM_INFO_MMAP_VALID |
45 SNDRV_PCM_INFO_PAUSE |
46 SNDRV_PCM_INFO_SYNC_START
47 ),
48 .formats = (
49 SNDRV_PCM_FMTBIT_S16_LE
50 ),
51 .rates = (
52 SNDRV_PCM_RATE_CONTINUOUS |
53 SNDRV_PCM_RATE_8000_48000
54 ),
55 .rate_min = 4000,
56 .rate_max = 48000,
57 .channels_min = 2,
58 .channels_max = 2,
59 .buffer_bytes_max = (128*1024),
60 .period_bytes_min = 64,
61 .period_bytes_max = (64*1024 - 16),
62 .periods_min = 1,
63 .periods_max = CS5535AUDIO_MAX_DESCRIPTORS,
64 .fifo_size = 0,
65};
66
67static snd_pcm_hardware_t snd_cs5535audio_capture =
68{
69 .info = (
70 SNDRV_PCM_INFO_MMAP |
71 SNDRV_PCM_INFO_INTERLEAVED |
72 SNDRV_PCM_INFO_BLOCK_TRANSFER |
73 SNDRV_PCM_INFO_MMAP_VALID |
74 SNDRV_PCM_INFO_SYNC_START
75 ),
76 .formats = (
77 SNDRV_PCM_FMTBIT_S16_LE
78 ),
79 .rates = (
80 SNDRV_PCM_RATE_CONTINUOUS |
81 SNDRV_PCM_RATE_8000_48000
82 ),
83 .rate_min = 4000,
84 .rate_max = 48000,
85 .channels_min = 2,
86 .channels_max = 2,
87 .buffer_bytes_max = (128*1024),
88 .period_bytes_min = 64,
89 .period_bytes_max = (64*1024 - 16),
90 .periods_min = 1,
91 .periods_max = CS5535AUDIO_MAX_DESCRIPTORS,
92 .fifo_size = 0,
93};
94
95static int snd_cs5535audio_playback_open(snd_pcm_substream_t *substream)
96{
97 int err;
98 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
99 snd_pcm_runtime_t *runtime = substream->runtime;
100
101 runtime->hw = snd_cs5535audio_playback;
102 cs5535au->playback_substream = substream;
103 runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);
104 snd_pcm_set_sync(substream);
105 if ((err = snd_pcm_hw_constraint_integer(runtime,
106 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
107 return err;
108
109 return 0;
110}
111
112static int snd_cs5535audio_playback_close(snd_pcm_substream_t *substream)
113{
114 return 0;
115}
116
117#define CS5535AUDIO_DESC_LIST_SIZE \
118 PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(cs5535audio_dma_desc_t))
119
120static int cs5535audio_build_dma_packets(cs5535audio_t *cs5535au,
121 cs5535audio_dma_t *dma,
122 snd_pcm_substream_t *substream,
123 unsigned int periods,
124 unsigned int period_bytes)
125{
126 unsigned int i;
127 u32 addr, desc_addr, jmpprd_addr;
128 cs5535audio_dma_desc_t *lastdesc;
129
130 if (periods > CS5535AUDIO_MAX_DESCRIPTORS)
131 return -ENOMEM;
132
133 if (dma->desc_buf.area == NULL) {
134 if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
135 snd_dma_pci_data(cs5535au->pci),
136 CS5535AUDIO_DESC_LIST_SIZE+1,
137 &dma->desc_buf) < 0)
138 return -ENOMEM;
139 dma->period_bytes = dma->periods = 0;
140 }
141
142 if (dma->periods == periods && dma->period_bytes == period_bytes)
143 return 0;
144
145 /* the u32 cast is okay because in snd*create we succesfully told
146 pci alloc that we're only 32 bit capable so the uppper will be 0 */
147 addr = (u32) substream->runtime->dma_addr;
148 desc_addr = (u32) dma->desc_buf.addr;
149 for (i = 0; i < periods; i++) {
150 cs5535audio_dma_desc_t *desc =
151 &((cs5535audio_dma_desc_t *) dma->desc_buf.area)[i];
152 desc->addr = cpu_to_le32(addr);
153 desc->size = period_bytes;
154 desc->ctlreserved = PRD_EOP;
155 desc_addr += sizeof(cs5535audio_dma_desc_t);
156 addr += period_bytes;
157 }
158 /* we reserved one dummy descriptor at the end to do the PRD jump */
159 lastdesc = &((cs5535audio_dma_desc_t *) dma->desc_buf.area)[periods];
160 lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);
161 lastdesc->size = 0;
162 lastdesc->ctlreserved = PRD_JMP;
163 jmpprd_addr = cpu_to_le32(lastdesc->addr +
164 (sizeof(cs5535audio_dma_desc_t)*periods));
165
166 dma->period_bytes = period_bytes;
167 dma->periods = periods;
168 spin_lock_irq(&cs5535au->reg_lock);
169 dma->ops->disable_dma(cs5535au);
170 dma->ops->setup_prd(cs5535au, jmpprd_addr);
171 spin_unlock_irq(&cs5535au->reg_lock);
172 return 0;
173}
174
175static void cs5535audio_playback_enable_dma(cs5535audio_t *cs5535au)
176{
177 cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);
178}
179
180static void cs5535audio_playback_disable_dma(cs5535audio_t *cs5535au)
181{
182 cs_writeb(cs5535au, ACC_BM0_CMD, 0);
183}
184
185static void cs5535audio_playback_pause_dma(cs5535audio_t *cs5535au)
186{
187 cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);
188}
189
190static void cs5535audio_playback_setup_prd(cs5535audio_t *cs5535au,
191 u32 prd_addr)
192{
193 cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);
194}
195
196static u32 cs5535audio_playback_read_dma_pntr(cs5535audio_t *cs5535au)
197{
198 return cs_readl(cs5535au, ACC_BM0_PNTR);
199}
200
201static void cs5535audio_capture_enable_dma(cs5535audio_t *cs5535au)
202{
203 cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);
204}
205
206static void cs5535audio_capture_disable_dma(cs5535audio_t *cs5535au)
207{
208 cs_writeb(cs5535au, ACC_BM1_CMD, 0);
209}
210
211static void cs5535audio_capture_pause_dma(cs5535audio_t *cs5535au)
212{
213 cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);
214}
215
216static void cs5535audio_capture_setup_prd(cs5535audio_t *cs5535au,
217 u32 prd_addr)
218{
219 cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);
220}
221
222static u32 cs5535audio_capture_read_dma_pntr(cs5535audio_t *cs5535au)
223{
224 return cs_readl(cs5535au, ACC_BM1_PNTR);
225}
226
227static void cs5535audio_clear_dma_packets(cs5535audio_t *cs5535au,
228 cs5535audio_dma_t *dma,
229 snd_pcm_substream_t *substream)
230{
231 snd_dma_free_pages(&dma->desc_buf);
232 dma->desc_buf.area = NULL;
233}
234
235static int snd_cs5535audio_hw_params(snd_pcm_substream_t *substream,
236 snd_pcm_hw_params_t *hw_params)
237{
238 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
239 cs5535audio_dma_t *dma = substream->runtime->private_data;
240 int err;
241
242 err = snd_pcm_lib_malloc_pages(substream,
243 params_buffer_bytes(hw_params));
244 if (err < 0)
245 return err;
246 dma->buf_addr = substream->runtime->dma_addr;
247 dma->buf_bytes = params_buffer_bytes(hw_params);
248
249 err = cs5535audio_build_dma_packets(cs5535au, dma, substream,
250 params_periods(hw_params),
251 params_period_bytes(hw_params));
252 return err;
253}
254
255static int snd_cs5535audio_hw_free(snd_pcm_substream_t *substream)
256{
257 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
258 cs5535audio_dma_t *dma = substream->runtime->private_data;
259
260 cs5535audio_clear_dma_packets(cs5535au, dma, substream);
261 return snd_pcm_lib_free_pages(substream);
262}
263
264static int snd_cs5535audio_playback_prepare(snd_pcm_substream_t *substream)
265{
266 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
267 return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,
268 substream->runtime->rate);
269}
270
271static int snd_cs5535audio_trigger(snd_pcm_substream_t *substream, int cmd)
272{
273 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
274 cs5535audio_dma_t *dma = substream->runtime->private_data;
275
276 switch (cmd) {
277 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
278 spin_lock_irq(&cs5535au->reg_lock);
279 dma->ops->pause_dma(cs5535au);
280 spin_unlock_irq(&cs5535au->reg_lock);
281 break;
282 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
283 spin_lock_irq(&cs5535au->reg_lock);
284 dma->ops->enable_dma(cs5535au);
285 spin_unlock_irq(&cs5535au->reg_lock);
286 break;
287 case SNDRV_PCM_TRIGGER_START:
288 spin_lock_irq(&cs5535au->reg_lock);
289 dma->ops->enable_dma(cs5535au);
290 spin_unlock_irq(&cs5535au->reg_lock);
291 break;
292 case SNDRV_PCM_TRIGGER_STOP:
293 spin_lock_irq(&cs5535au->reg_lock);
294 dma->ops->disable_dma(cs5535au);
295 spin_unlock_irq(&cs5535au->reg_lock);
296 break;
297 default:
298 snd_printk(KERN_ERR "unhandled trigger\n");
299 return -EINVAL;
300 break;
301 }
302 return 0;
303}
304
305static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(snd_pcm_substream_t
306 *substream)
307{
308 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
309 u32 curdma;
310 cs5535audio_dma_t *dma;
311
312 dma = substream->runtime->private_data;
313 curdma = dma->ops->read_dma_pntr(cs5535au);
314 if (curdma < dma->buf_addr) {
315 snd_printk(KERN_ERR "curdma=%x < %x bufaddr.\n",
316 curdma, dma->buf_addr);
317 return 0;
318 }
319 curdma -= dma->buf_addr;
320 if (curdma >= dma->buf_bytes) {
321 snd_printk(KERN_ERR "diff=%x >= %x buf_bytes.\n",
322 curdma, dma->buf_bytes);
323 return 0;
324 }
325 return bytes_to_frames(substream->runtime, curdma);
326}
327
328static int snd_cs5535audio_capture_open(snd_pcm_substream_t *substream)
329{
330 int err;
331 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
332 snd_pcm_runtime_t *runtime = substream->runtime;
333
334 runtime->hw = snd_cs5535audio_capture;
335 cs5535au->capture_substream = substream;
336 runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);
337 snd_pcm_set_sync(substream);
338 if ((err = snd_pcm_hw_constraint_integer(runtime,
339 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
340 return err;
341 return 0;
342}
343
344static int snd_cs5535audio_capture_close(snd_pcm_substream_t *substream)
345{
346 return 0;
347}
348
349static int snd_cs5535audio_capture_prepare(snd_pcm_substream_t *substream)
350{
351 cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream);
352 return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,
353 substream->runtime->rate);
354}
355
356static snd_pcm_ops_t snd_cs5535audio_playback_ops = {
357 .open = snd_cs5535audio_playback_open,
358 .close = snd_cs5535audio_playback_close,
359 .ioctl = snd_pcm_lib_ioctl,
360 .hw_params = snd_cs5535audio_hw_params,
361 .hw_free = snd_cs5535audio_hw_free,
362 .prepare = snd_cs5535audio_playback_prepare,
363 .trigger = snd_cs5535audio_trigger,
364 .pointer = snd_cs5535audio_pcm_pointer,
365};
366
367static snd_pcm_ops_t snd_cs5535audio_capture_ops = {
368 .open = snd_cs5535audio_capture_open,
369 .close = snd_cs5535audio_capture_close,
370 .ioctl = snd_pcm_lib_ioctl,
371 .hw_params = snd_cs5535audio_hw_params,
372 .hw_free = snd_cs5535audio_hw_free,
373 .prepare = snd_cs5535audio_capture_prepare,
374 .trigger = snd_cs5535audio_trigger,
375 .pointer = snd_cs5535audio_pcm_pointer,
376};
377
378static void snd_cs5535audio_pcm_free(snd_pcm_t *pcm)
379{
380 snd_pcm_lib_preallocate_free_for_all(pcm);
381}
382
383static cs5535audio_dma_ops_t snd_cs5535audio_playback_dma_ops = {
384 .type = CS5535AUDIO_DMA_PLAYBACK,
385 .enable_dma = cs5535audio_playback_enable_dma,
386 .disable_dma = cs5535audio_playback_disable_dma,
387 .setup_prd = cs5535audio_playback_setup_prd,
388 .pause_dma = cs5535audio_playback_pause_dma,
389 .read_dma_pntr = cs5535audio_playback_read_dma_pntr,
390};
391
392static cs5535audio_dma_ops_t snd_cs5535audio_capture_dma_ops = {
393 .type = CS5535AUDIO_DMA_CAPTURE,
394 .enable_dma = cs5535audio_capture_enable_dma,
395 .disable_dma = cs5535audio_capture_disable_dma,
396 .setup_prd = cs5535audio_capture_setup_prd,
397 .pause_dma = cs5535audio_capture_pause_dma,
398 .read_dma_pntr = cs5535audio_capture_read_dma_pntr,
399};
400
401int __devinit snd_cs5535audio_pcm(cs5535audio_t *cs5535au)
402{
403 snd_pcm_t *pcm;
404 int err;
405
406 err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);
407 if (err < 0)
408 return err;
409
410 cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =
411 &snd_cs5535audio_playback_dma_ops;
412 cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =
413 &snd_cs5535audio_capture_dma_ops;
414 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
415 &snd_cs5535audio_playback_ops);
416 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
417 &snd_cs5535audio_capture_ops);
418
419 pcm->private_data = cs5535au;
420 pcm->private_free = snd_cs5535audio_pcm_free;
421 pcm->info_flags = 0;
422 strcpy(pcm->name, "CS5535 Audio");
423
424 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
425 snd_dma_pci_data(cs5535au->pci),
426 64*1024, 128*1024);
427
428 return 0;
429}
430