cs5535audio.c 9.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
/*
 * Driver for audio on multifunction CS5535 companion device
 * Copyright (C) Jaya Kumar
 *
 * Based on Jaroslav Kysela and Takashi Iwai's examples.
 * This work was sponsored by CIS(M) Sdn Bhd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */

#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <asm/io.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/asoundef.h>
#include "cs5535audio.h"

#define DRIVER_NAME "cs5535audio"


static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

static struct pci_device_id snd_cs5535audio_ids[] = {
	{ PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO, PCI_ANY_ID,
		PCI_ANY_ID, 0, 0, 0, },
	{}
};

MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids);

56
static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout)
57
{
58
	unsigned int tmp;
59 60 61 62 63 64 65 66 67 68
	do {
		tmp = cs_readl(cs5535au, ACC_CODEC_CNTL);
		if (!(tmp & CMD_NEW))
			break;
		msleep(10);
	} while (--timeout);
	if (!timeout)
		snd_printk(KERN_ERR "Failure writing to cs5535 codec\n");
}

69 70
static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au,
						 unsigned short reg)
71
{
72
	unsigned int regdata;
73
	unsigned int timeout;
74
	unsigned int val;
75

76
	regdata = ((unsigned int) reg) << 24;
77 78 79 80 81 82 83 84 85
	regdata |= ACC_CODEC_CNTL_RD_CMD;
	regdata |= CMD_NEW;

	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
	wait_till_cmd_acked(cs5535au, 500);

	timeout = 50;
	do {
		val = cs_readl(cs5535au, ACC_CODEC_STATUS);
86
		if ((val & STS_NEW) && reg == (val >> 24))
87 88 89 90 91 92
			break;
		msleep(10);
	} while (--timeout);
	if (!timeout)
		snd_printk(KERN_ERR "Failure reading cs5535 codec\n");

93
	return (unsigned short) val;
94 95
}

96 97
static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au,
					unsigned short reg, unsigned short val)
98
{
99
	unsigned int regdata;
100

101 102
	regdata = ((unsigned int) reg) << 24;
	regdata |= val;
103 104 105 106 107 108 109 110
	regdata &= CMD_MASK;
	regdata |= CMD_NEW;
	regdata &= ACC_CODEC_CNTL_WR_CMD;

	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
	wait_till_cmd_acked(cs5535au, 50);
}

111 112
static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97,
					     unsigned short reg, unsigned short val)
113
{
114
	struct cs5535audio *cs5535au = ac97->private_data;
115 116 117
	snd_cs5535audio_codec_write(cs5535au, reg, val);
}

118 119
static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97,
						      unsigned short reg)
120
{
121
	struct cs5535audio *cs5535au = ac97->private_data;
122 123 124
	return snd_cs5535audio_codec_read(cs5535au, reg);
}

125
static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au)
126
{
127 128 129
	struct snd_card *card = cs5535au->card;
	struct snd_ac97_bus *pbus;
	struct snd_ac97_template ac97;
130
	int err;
131
	static struct snd_ac97_bus_ops ops = {
132 133 134 135 136 137 138 139 140 141 142 143 144
		.write = snd_cs5535audio_ac97_codec_write,
		.read = snd_cs5535audio_ac97_codec_read,
	};

	if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
		return err;

	memset(&ac97, 0, sizeof(ac97));
	ac97.scaps = AC97_SCAP_AUDIO|AC97_SCAP_SKIP_MODEM;
	ac97.private_data = cs5535au;
	ac97.pci = cs5535au->pci;

	if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 0) {
145
		snd_printk(KERN_ERR "mixer failed\n");
146 147 148 149 150 151
		return err;
	}

	return 0;
}

152
static void process_bm0_irq(struct cs5535audio *cs5535au)
153 154 155 156 157 158
{
	u8 bm_stat;
	spin_lock(&cs5535au->reg_lock);
	bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
	spin_unlock(&cs5535au->reg_lock);
	if (bm_stat & EOP) {
159
		struct cs5535audio_dma *dma;
160 161 162 163 164 165 166 167
		dma = cs5535au->playback_substream->runtime->private_data;
		snd_pcm_period_elapsed(cs5535au->playback_substream);
	} else {
		snd_printk(KERN_ERR "unexpected bm0 irq src, bm_stat=%x\n",
					bm_stat);
	}
}

168
static void process_bm1_irq(struct cs5535audio *cs5535au)
169 170 171 172 173 174
{
	u8 bm_stat;
	spin_lock(&cs5535au->reg_lock);
	bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
	spin_unlock(&cs5535au->reg_lock);
	if (bm_stat & EOP) {
175
		struct cs5535audio_dma *dma;
176 177 178 179 180 181
		dma = cs5535au->capture_substream->runtime->private_data;
		snd_pcm_period_elapsed(cs5535au->capture_substream);
	}
}

static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id,
182
					     struct pt_regs *regs)
183 184 185 186
{
	u16 acc_irq_stat;
	u8 bm_stat;
	unsigned char count;
187
	struct cs5535audio *cs5535au = dev_id;
188 189 190 191 192 193 194 195

	if (cs5535au == NULL)
		return IRQ_NONE;

	acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS);

	if (!acc_irq_stat)
		return IRQ_NONE;
196 197
	for (count = 0; count < 10; count++) {
		if (acc_irq_stat & (1 << count)) {
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
			switch (count) {
			case IRQ_STS:
				cs_readl(cs5535au, ACC_GPIO_STATUS);
				break;
			case WU_IRQ_STS:
				cs_readl(cs5535au, ACC_GPIO_STATUS);
				break;
			case BM0_IRQ_STS:
				process_bm0_irq(cs5535au);
				break;
			case BM1_IRQ_STS:
				process_bm1_irq(cs5535au);
				break;
			case BM2_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM2_STATUS);
				break;
			case BM3_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM3_STATUS);
				break;
			case BM4_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM4_STATUS);
				break;
			case BM5_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM5_STATUS);
				break;
			case BM6_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM6_STATUS);
				break;
			case BM7_IRQ_STS:
				bm_stat = cs_readb(cs5535au, ACC_BM7_STATUS);
				break;
			default:
				snd_printk(KERN_ERR "Unexpected irq src\n");
				break;
			}
		}
	}
	return IRQ_HANDLED;
}

238
static int snd_cs5535audio_free(struct cs5535audio *cs5535au)
239 240 241 242 243 244 245 246 247 248 249 250 251
{
	synchronize_irq(cs5535au->irq);
	pci_set_power_state(cs5535au->pci, 3);

	if (cs5535au->irq >= 0)
		free_irq(cs5535au->irq, cs5535au);

	pci_release_regions(cs5535au->pci);
	pci_disable_device(cs5535au->pci);
	kfree(cs5535au);
	return 0;
}

252
static int snd_cs5535audio_dev_free(struct snd_device *device)
253
{
254
	struct cs5535audio *cs5535au = device->device_data;
255 256 257
	return snd_cs5535audio_free(cs5535au);
}

258 259 260
static int __devinit snd_cs5535audio_create(struct snd_card *card,
					    struct pci_dev *pci,
					    struct cs5535audio **rcs5535au)
261
{
262
	struct cs5535audio *cs5535au;
263 264

	int err;
265
	static struct snd_device_ops ops = {
266 267 268 269 270 271 272 273
		.dev_free =	snd_cs5535audio_dev_free,
	};

	*rcs5535au = NULL;
	if ((err = pci_enable_device(pci)) < 0)
		return err;

	if (pci_set_dma_mask(pci, DMA_32BIT_MASK) < 0 ||
274
	    pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK) < 0) {
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
		printk(KERN_WARNING "unable to get 32bit dma\n");
		err = -ENXIO;
		goto pcifail;
	}

	cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
	if (cs5535au == NULL) {
		err = -ENOMEM;
		goto pcifail;
	}

	spin_lock_init(&cs5535au->reg_lock);
	cs5535au->card = card;
	cs5535au->pci = pci;
	cs5535au->irq = -1;

	if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
		kfree(cs5535au);
		goto pcifail;
	}

	cs5535au->port = pci_resource_start(pci, 0);

	if (request_irq(pci->irq, snd_cs5535audio_interrupt,
299
			SA_INTERRUPT|SA_SHIRQ, "CS5535 Audio", cs5535au)) {
300 301 302 303 304 305 306 307 308
		snd_printk("unable to grab IRQ %d\n", pci->irq);
		err = -EBUSY;
		goto sndfail;
	}

	cs5535au->irq = pci->irq;
	pci_set_master(pci);

	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
309
				  cs5535au, &ops)) < 0)
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
		goto sndfail;

	snd_card_set_dev(card, &pci->dev);

	*rcs5535au = cs5535au;
	return 0;

sndfail: /* leave the device alive, just kill the snd */
	snd_cs5535audio_free(cs5535au);
	return err;

pcifail:
	pci_disable_device(pci);
	return err;
}

static int __devinit snd_cs5535audio_probe(struct pci_dev *pci,
327
					   const struct pci_device_id *pci_id)
328 329
{
	static int dev;
330 331
	struct snd_card *card;
	struct cs5535audio *cs5535au;
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
	int err;

	if (dev >= SNDRV_CARDS)
		return -ENODEV;
	if (!enable[dev]) {
		dev++;
		return -ENOENT;
	}

	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
	if (card == NULL)
		return -ENOMEM;

	if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
		goto probefail_out;

	if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
		goto probefail_out;

	if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
		goto probefail_out;

	strcpy(card->driver, DRIVER_NAME);

	strcpy(card->shortname, "CS5535 Audio");
	sprintf(card->longname, "%s %s at 0x%lx, irq %i",
		card->shortname, card->driver,
		cs5535au->port, cs5535au->irq);

	if ((err = snd_card_register(card)) < 0)
		goto probefail_out;

	pci_set_drvdata(pci, card);
	dev++;
	return 0;

probefail_out:
	snd_card_free(card);
	return err;
}

static void __devexit snd_cs5535audio_remove(struct pci_dev *pci)
{
	snd_card_free(pci_get_drvdata(pci));
	pci_set_drvdata(pci, NULL);
}

static struct pci_driver driver = {
	.name = DRIVER_NAME,
	.id_table = snd_cs5535audio_ids,
	.probe = snd_cs5535audio_probe,
	.remove = __devexit_p(snd_cs5535audio_remove),
};

static int __init alsa_card_cs5535audio_init(void)
{
	return pci_module_init(&driver);
}

static void __exit alsa_card_cs5535audio_exit(void)
{
	pci_unregister_driver(&driver);
}

module_init(alsa_card_cs5535audio_init)
module_exit(alsa_card_cs5535audio_exit)

MODULE_AUTHOR("Jaya Kumar");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CS5535 Audio");
MODULE_SUPPORTED_DEVICE("CS5535 Audio");