sgalaxy.c 9.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 *  Driver for Aztech Sound Galaxy cards
 *  Copyright (c) by Christopher Butler <chrisb@sandy.force9.co.uk.
 *
 *  I don't have documentation for this card, I based this driver on the
 *  driver for OSS/Free included in the kernel source (drivers/sound/sgalaxy.c)
 *
 *   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/init.h>
25
#include <linux/err.h>
26
#include <linux/isa.h>
L
Linus Torvalds 已提交
27 28 29 30
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
31
#include <asm/dma.h>
L
Linus Torvalds 已提交
32 33
#include <sound/core.h>
#include <sound/sb.h>
34
#include <sound/wss.h>
L
Linus Torvalds 已提交
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#include <sound/control.h>
#define SNDRV_LEGACY_FIND_FREE_IRQ
#define SNDRV_LEGACY_FIND_FREE_DMA
#include <sound/initval.h>

MODULE_AUTHOR("Christopher Butler <chrisb@sandy.force9.co.uk>");
MODULE_DESCRIPTION("Aztech Sound Galaxy");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Aztech Systems,Sound Galaxy}}");

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240 */
static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x530,0xe80,0xf40,0x604 */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 7,9,10,11 */
static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */

module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Sound Galaxy soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Sound Galaxy soundcard.");
module_param_array(sbport, long, NULL, 0444);
MODULE_PARM_DESC(sbport, "Port # for Sound Galaxy SB driver.");
module_param_array(wssport, long, NULL, 0444);
MODULE_PARM_DESC(wssport, "Port # for Sound Galaxy WSS driver.");
module_param_array(irq, int, NULL, 0444);
MODULE_PARM_DESC(irq, "IRQ # for Sound Galaxy driver.");
module_param_array(dma1, int, NULL, 0444);
MODULE_PARM_DESC(dma1, "DMA1 # for Sound Galaxy driver.");

#define SGALAXY_AUXC_LEFT 18
#define SGALAXY_AUXC_RIGHT 19

69 70
#define PFX	"sgalaxy: "

L
Linus Torvalds 已提交
71 72 73 74 75 76
/*

 */

#define AD1848P1( port, x ) ( port + c_d_c_AD1848##x )

77
/* from lowlevel/sb/sb.c - to avoid having to allocate a struct snd_sb for the */
L
Linus Torvalds 已提交
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
/* short time we actually need it.. */

static int snd_sgalaxy_sbdsp_reset(unsigned long port)
{
	int i;

	outb(1, SBP1(port, RESET));
	udelay(10);
	outb(0, SBP1(port, RESET));
	udelay(30);
	for (i = 0; i < 1000 && !(inb(SBP1(port, DATA_AVAIL)) & 0x80); i++);
	if (inb(SBP1(port, READ)) != 0xaa) {
		snd_printd("sb_reset: failed at 0x%lx!!!\n", port);
		return -ENODEV;
	}
	return 0;
}

96 97
static int __devinit snd_sgalaxy_sbdsp_command(unsigned long port,
					       unsigned char val)
L
Linus Torvalds 已提交
98 99 100 101 102 103 104 105 106 107 108 109
{
	int i;
       	
	for (i = 10000; i; i--)
		if ((inb(SBP1(port, STATUS)) & 0x80) == 0) {
			outb(val, SBP1(port, COMMAND));
			return 1;
		}

	return 0;
}

110
static irqreturn_t snd_sgalaxy_dummy_interrupt(int irq, void *dev_id)
L
Linus Torvalds 已提交
111 112 113 114
{
	return IRQ_NONE;
}

115
static int __devinit snd_sgalaxy_setup_wss(unsigned long port, int irq, int dma)
L
Linus Torvalds 已提交
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
{
	static int interrupt_bits[] = {-1, -1, -1, -1, -1, -1, -1, 0x08, -1, 
				       0x10, 0x18, 0x20, -1, -1, -1, -1};
	static int dma_bits[] = {1, 2, 0, 3};
	int tmp, tmp1;

	if ((tmp = inb(port + 3)) == 0xff)
	{
		snd_printdd("I/O address dead (0x%lx)\n", port);
		return 0;
	}
#if 0
	snd_printdd("WSS signature = 0x%x\n", tmp);
#endif

        if ((tmp & 0x3f) != 0x04 &&
            (tmp & 0x3f) != 0x0f &&
            (tmp & 0x3f) != 0x00) {
		snd_printdd("No WSS signature detected on port 0x%lx\n",
			    port + 3);
		return 0;
	}

#if 0
140
	snd_printdd(PFX "setting up IRQ/DMA for WSS\n");
L
Linus Torvalds 已提交
141 142 143 144 145 146 147
#endif

        /* initialize IRQ for WSS codec */
        tmp = interrupt_bits[irq % 16];
        if (tmp < 0)
                return -EINVAL;

148
	if (request_irq(irq, snd_sgalaxy_dummy_interrupt, IRQF_DISABLED, "sgalaxy", NULL)) {
L
Linus Torvalds 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161
		snd_printk(KERN_ERR "sgalaxy: can't grab irq %d\n", irq);
		return -EIO;
	}

        outb(tmp | 0x40, port);
        tmp1 = dma_bits[dma % 4];
        outb(tmp | tmp1, port);

	free_irq(irq, NULL);

	return 0;
}

162
static int __devinit snd_sgalaxy_detect(int dev, int irq, int dma)
L
Linus Torvalds 已提交
163 164
{
#if 0
165
	snd_printdd(PFX "switching to WSS mode\n");
L
Linus Torvalds 已提交
166 167 168 169 170 171 172 173 174 175 176 177
#endif

	/* switch to WSS mode */
	snd_sgalaxy_sbdsp_reset(sbport[dev]);

	snd_sgalaxy_sbdsp_command(sbport[dev], 9);
	snd_sgalaxy_sbdsp_command(sbport[dev], 0);

	udelay(400);
	return snd_sgalaxy_setup_wss(wssport[dev], irq, dma);
}

178 179 180 181 182
static struct snd_kcontrol_new snd_sgalaxy_controls[] = {
WSS_DOUBLE("Aux Playback Switch", 0,
		SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 7, 7, 1, 1),
WSS_DOUBLE("Aux Playback Volume", 0,
		SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 0, 0, 31, 0)
L
Linus Torvalds 已提交
183 184
};

185
static int __devinit snd_sgalaxy_mixer(struct snd_wss *chip)
L
Linus Torvalds 已提交
186
{
187 188
	struct snd_card *card = chip->card;
	struct snd_ctl_elem_id id1, id2;
L
Linus Torvalds 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
	unsigned int idx;
	int err;

	memset(&id1, 0, sizeof(id1));
	memset(&id2, 0, sizeof(id2));
	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
	/* reassign AUX0 to LINE */
	strcpy(id1.name, "Aux Playback Switch");
	strcpy(id2.name, "Line Playback Switch");
	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
		return err;
	strcpy(id1.name, "Aux Playback Volume");
	strcpy(id2.name, "Line Playback Volume");
	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
		return err;
	/* reassign AUX1 to FM */
	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
	strcpy(id2.name, "FM Playback Switch");
	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
		return err;
	strcpy(id1.name, "Aux Playback Volume");
	strcpy(id2.name, "FM Playback Volume");
	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
		return err;
	/* build AUX2 input */
	for (idx = 0; idx < ARRAY_SIZE(snd_sgalaxy_controls); idx++) {
215 216 217
		err = snd_ctl_add(card,
				snd_ctl_new1(&snd_sgalaxy_controls[idx], chip));
		if (err < 0)
L
Linus Torvalds 已提交
218 219 220 221 222
			return err;
	}
	return 0;
}

223
static int __devinit snd_sgalaxy_match(struct device *devptr, unsigned int dev)
L
Linus Torvalds 已提交
224
{
225 226
	if (!enable[dev])
		return 0;
L
Linus Torvalds 已提交
227
	if (sbport[dev] == SNDRV_AUTO_PORT) {
228
		snd_printk(KERN_ERR PFX "specify SB port\n");
229
		return 0;
L
Linus Torvalds 已提交
230 231
	}
	if (wssport[dev] == SNDRV_AUTO_PORT) {
232
		snd_printk(KERN_ERR PFX "specify WSS port\n");
233
		return 0;
L
Linus Torvalds 已提交
234
	}
235 236 237 238 239 240 241 242 243
	return 1;
}

static int __devinit snd_sgalaxy_probe(struct device *devptr, unsigned int dev)
{
	static int possible_irqs[] = {7, 9, 10, 11, -1};
	static int possible_dmas[] = {1, 3, 0, -1};
	int err, xirq, xdma1;
	struct snd_card *card;
244
	struct snd_wss *chip;
245

246 247 248
	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
	if (err < 0)
		return err;
L
Linus Torvalds 已提交
249 250 251 252

	xirq = irq[dev];
	if (xirq == SNDRV_AUTO_IRQ) {
		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
253 254 255
			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
			err = -EBUSY;
			goto _err;
L
Linus Torvalds 已提交
256 257 258 259 260
		}
	}
	xdma1 = dma1[dev];
        if (xdma1 == SNDRV_AUTO_DMA) {
		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
261 262 263
			snd_printk(KERN_ERR PFX "unable to find a free DMA\n");
			err = -EBUSY;
			goto _err;
L
Linus Torvalds 已提交
264 265 266
		}
	}

267 268
	if ((err = snd_sgalaxy_detect(dev, xirq, xdma1)) < 0)
		goto _err;
L
Linus Torvalds 已提交
269

270 271 272 273
	err = snd_wss_create(card, wssport[dev] + 4, -1,
			     xirq, xdma1, -1,
			     WSS_HW_DETECT, 0, &chip);
	if (err < 0)
274
		goto _err;
275
	card->private_data = chip;
L
Linus Torvalds 已提交
276

277 278 279
	err = snd_wss_pcm(chip, 0, NULL);
	if (err < 0) {
		snd_printdd(PFX "error creating new WSS PCM device\n");
280
		goto _err;
L
Linus Torvalds 已提交
281
	}
282 283 284
	err = snd_wss_mixer(chip);
	if (err < 0) {
		snd_printdd(PFX "error creating new WSS mixer\n");
285
		goto _err;
L
Linus Torvalds 已提交
286
	}
287 288 289
	if ((err = snd_sgalaxy_mixer(chip)) < 0) {
		snd_printdd(PFX "the mixer rewrite failed\n");
		goto _err;
L
Linus Torvalds 已提交
290 291 292 293 294 295 296
	}

	strcpy(card->driver, "Sound Galaxy");
	strcpy(card->shortname, "Sound Galaxy");
	sprintf(card->longname, "Sound Galaxy at 0x%lx, irq %d, dma %d",
		wssport[dev], xirq, xdma1);

297
	snd_card_set_dev(card, devptr);
298 299 300 301

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

302
	dev_set_drvdata(devptr, card);
L
Linus Torvalds 已提交
303
	return 0;
304 305 306 307

 _err:
	snd_card_free(card);
	return err;
L
Linus Torvalds 已提交
308 309
}

310
static int __devexit snd_sgalaxy_remove(struct device *devptr, unsigned int dev)
311
{
312 313
	snd_card_free(dev_get_drvdata(devptr));
	dev_set_drvdata(devptr, NULL);
314 315 316 317
	return 0;
}

#ifdef CONFIG_PM
318 319
static int snd_sgalaxy_suspend(struct device *pdev, unsigned int n,
			       pm_message_t state)
320
{
321
	struct snd_card *card = dev_get_drvdata(pdev);
322
	struct snd_wss *chip = card->private_data;
323 324 325 326 327 328

	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
	chip->suspend(chip);
	return 0;
}

329
static int snd_sgalaxy_resume(struct device *pdev, unsigned int n)
330
{
331
	struct snd_card *card = dev_get_drvdata(pdev);
332
	struct snd_wss *chip = card->private_data;
333 334

	chip->resume(chip);
335 336
	snd_wss_out(chip, SGALAXY_AUXC_LEFT, chip->image[SGALAXY_AUXC_LEFT]);
	snd_wss_out(chip, SGALAXY_AUXC_RIGHT, chip->image[SGALAXY_AUXC_RIGHT]);
337 338 339 340 341 342

	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
	return 0;
}
#endif

343
#define DEV_NAME "sgalaxy"
344

345 346
static struct isa_driver snd_sgalaxy_driver = {
	.match		= snd_sgalaxy_match,
347 348 349 350 351 352 353
	.probe		= snd_sgalaxy_probe,
	.remove		= __devexit_p(snd_sgalaxy_remove),
#ifdef CONFIG_PM
	.suspend	= snd_sgalaxy_suspend,
	.resume		= snd_sgalaxy_resume,
#endif
	.driver		= {
354
		.name	= DEV_NAME
355 356 357
	},
};

L
Linus Torvalds 已提交
358 359
static int __init alsa_card_sgalaxy_init(void)
{
360
	return isa_register_driver(&snd_sgalaxy_driver, SNDRV_CARDS);
L
Linus Torvalds 已提交
361 362 363 364
}

static void __exit alsa_card_sgalaxy_exit(void)
{
365
	isa_unregister_driver(&snd_sgalaxy_driver);
L
Linus Torvalds 已提交
366 367 368 369
}

module_init(alsa_card_sgalaxy_init)
module_exit(alsa_card_sgalaxy_exit)