aboutsummaryrefslogblamecommitdiffstats
path: root/mm/pagewalk.c
blob: d5878bed78412542b82c03022756db62c9d73d63 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11




                                                                            
                                               




                                        
                  
                                                                         

                             




                                  





                                                                            
                                               









                                                 
                                                                       




                                      
                                                                     
                                            
                                                                    







                                                                            
                                               









                                                 
                                                                       




                                      
                                                                     
                                                                 
                                                                    








                                                                    



                                                               





                                                                         


                                                                       






                                                                              

                                                          







                           



                                         



                                                 
                                                                       




                                      
                                                                     

                                                                            
                                                                    





                                                  
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/sched.h>

static int walk_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
			  struct mm_walk *walk)
{
	pte_t *pte;
	int err = 0;

	pte = pte_offset_map(pmd, addr);
	for (;;) {
		err = walk->pte_entry(pte, addr, addr + PAGE_SIZE, walk);
		if (err)
		       break;
		addr += PAGE_SIZE;
		if (addr == end)
			break;
		pte++;
	}

	pte_unmap(pte);
	return err;
}

static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
			  struct mm_walk *walk)
{
	pmd_t *pmd;
	unsigned long next;
	int err = 0;

	pmd = pmd_offset(pud, addr);
	do {
		next = pmd_addr_end(addr, end);
		if (pmd_none_or_clear_bad(pmd)) {
			if (walk->pte_hole)
				err = walk->pte_hole(addr, next, walk);
			if (err)
				break;
			continue;
		}
		if (walk->pmd_entry)
			err = walk->pmd_entry(pmd, addr, next, walk);
		if (!err && walk->pte_entry)
			err = walk_pte_range(pmd, addr, next, walk);
		if (err)
			break;
	} while (pmd++, addr = next, addr != end);

	return err;
}

static int walk_pud_range(pgd_t *pgd, unsigned long addr, unsigned long end,
			  struct mm_walk *walk)
{
	pud_t *pud;
	unsigned long next;
	int err = 0;

	pud = pud_offset(pgd, addr);
	do {
		next = pud_addr_end(addr, end);
		if (pud_none_or_clear_bad(pud)) {
			if (walk->pte_hole)
				err = walk->pte_hole(addr, next, walk);
			if (err)
				break;
			continue;
		}
		if (walk->pud_entry)
			err = walk->pud_entry(pud, addr, next, walk);
		if (!err && (walk->pmd_entry || walk->pte_entry))
			err = walk_pmd_range(pud, addr, next, walk);
		if (err)
			break;
	} while (pud++, addr = next, addr != end);

	return err;
}

/**
 * walk_page_range - walk a memory map's page tables with a callback
 * @mm: memory map to walk
 * @addr: starting address
 * @end: ending address
 * @walk: set of callbacks to invoke for each level of the tree
 *
 * Recursively walk the page table for the memory area in a VMA,
 * calling supplied callbacks. Callbacks are called in-order (first
 * PGD, first PUD, first PMD, first PTE, second PTE... second PMD,
 * etc.). If lower-level callbacks are omitted, walking depth is reduced.
 *
 * Each callback receives an entry pointer and the start and end of the
 * associated range, and a copy of the original mm_walk for access to
 * the ->private or ->mm fields.
 *
 * No locks are taken, but the bottom level iterator will map PTE
 * directories from highmem if necessary.
 *
 * If any callback returns a non-zero value, the walk is aborted and
 * the return value is propagated back to the caller. Otherwise 0 is returned.
 */
int walk_page_range(unsigned long addr, unsigned long end,
		    struct mm_walk *walk)
{
	pgd_t *pgd;
	unsigned long next;
	int err = 0;

	if (addr >= end)
		return err;

	if (!walk->mm)
		return -EINVAL;

	pgd = pgd_offset(walk->mm, addr);
	do {
		next = pgd_addr_end(addr, end);
		if (pgd_none_or_clear_bad(pgd)) {
			if (walk->pte_hole)
				err = walk->pte_hole(addr, next, walk);
			if (err)
				break;
			continue;
		}
		if (walk->pgd_entry)
			err = walk->pgd_entry(pgd, addr, next, walk);
		if (!err &&
		    (walk->pud_entry || walk->pmd_entry || walk->pte_entry))
			err = walk_pud_range(pgd, addr, next, walk);
		if (err)
			break;
	} while (pgd++, addr = next, addr != end);

	return err;
}
67 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369
/*
 *
 * AD1816 lowlevel sound driver for Linux 2.6.0 and above
 *
 * Copyright (C) 1998-2003 by Thorsten Knabe <linux@thorsten-knabe.de>
 *
 * Based on the CS4232/AD1848 driver Copyright (C) by Hannu Savolainen 1993-1996
 *
 *
 * version: 1.5
 * status: beta
 * date: 2003/07/15
 *
 * Changes:
 *	Oleg Drokin: Some cleanup of load/unload functions.	1998/11/24
 *	
 *	Thorsten Knabe: attach and unload rewritten, 
 *	some argument checks added				1998/11/30
 *
 *	Thorsten Knabe: Buggy isa bridge workaround added	1999/01/16
 *	
 *	David Moews/Thorsten Knabe: Introduced options 
 *	parameter. Added slightly modified patch from 
 *	David Moews to disable dsp audio sources by setting 
 *	bit 0 of options parameter. This seems to be
 *	required by some Aztech/Newcom SC-16 cards.		1999/04/18
 *
 *	Christoph Hellwig: Adapted to module_init/module_exit.	2000/03/03
 *
 *	Christoph Hellwig: Added isapnp support			2000/03/15
 *
 *	Arnaldo Carvalho de Melo: get rid of check_region	2001/10/07
 *      
 *      Thorsten Knabe: Compiling with CONFIG_PNP enabled
 *	works again. It is now possible to use more than one 
 *	AD1816 sound card. Sample rate now may be changed during
 *	playback/capture. printk() uses log levels everywhere.
 *	SMP fixes. DMA handling fixes.
 *	Other minor code cleanup.				2003/07/15
 *
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/isapnp.h>
#include <linux/stddef.h>
#include <linux/spinlock.h>
#include "sound_config.h"

#define DEBUGNOISE(x)

#define CHECK_FOR_POWER { int timeout=100; \
  while (timeout > 0 && (inb(devc->base)&0x80)!= 0x80) {\
          timeout--; \
  } \
  if (timeout==0) {\
          printk(KERN_WARNING "ad1816: Check for power failed in %s line: %d\n",__FILE__,__LINE__); \
  } \
}

/* structure to hold device specific information */
typedef struct
{
        int            base;          /* set in attach */
	int            irq;
	int            dma_playback;
        int            dma_capture;
  
	int            opened;         /* open */
        int            speed;	
	int            channels;
	int            audio_format;
        int            audio_mode; 
  
        int            recmask;        /* setup */
	unsigned char  format_bits;
	int            supported_devices;
	int            supported_rec_devices;
	unsigned short levels[SOUND_MIXER_NRDEVICES];
					/* misc */
	struct pnp_dev *pnpdev;	 /* configured via pnp */
        int            dev_no;   /* this is the # in audio_devs and NOT 
				    in ad1816_info */
	spinlock_t	lock;  
} ad1816_info;

static int nr_ad1816_devs;
static int ad1816_clockfreq = 33000;
static int options;

/* supported audio formats */
static int  ad_format_mask =
AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW;

/* array of device info structures */
static ad1816_info dev_info[MAX_AUDIO_DEV];


/* ------------------------------------------------------------------- */

/* functions for easier access to inderect registers */

static int ad_read (ad1816_info * devc, int reg)
{
	int result;
	
	CHECK_FOR_POWER;
	outb ((unsigned char) (reg & 0x3f), devc->base+0);
	result = inb(devc->base+2);
	result+= inb(devc->base+3)<<8;
	return (result);
}


static void ad_write (ad1816_info * devc, int reg, int data)
{
	CHECK_FOR_POWER;
	outb ((unsigned char) (reg & 0xff), devc->base+0);
	outb ((unsigned char) (data & 0xff),devc->base+2);
	outb ((unsigned char) ((data>>8)&0xff),devc->base+3);
}

/* ------------------------------------------------------------------- */

/* function interface required by struct audio_driver */

static void ad1816_halt_input (int dev)
{
	unsigned long flags;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	unsigned char buffer;
	
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_input called\n"));
	
	spin_lock_irqsave(&devc->lock,flags); 
	
	if(!isa_dma_bridge_buggy) {
	        disable_dma(audio_devs[dev]->dmap_in->dma);
	}
	
	buffer=inb(devc->base+9);
	if (buffer & 0x01) {
		/* disable capture */
		outb(buffer & ~0x01,devc->base+9); 
	}

	if(!isa_dma_bridge_buggy) {
	        enable_dma(audio_devs[dev]->dmap_in->dma);
	}

	/* Clear interrupt status */
	outb (~0x40, devc->base+1);	
	
	devc->audio_mode &= ~PCM_ENABLE_INPUT;
	spin_unlock_irqrestore(&devc->lock,flags);
}

static void ad1816_halt_output (int dev)
{
	unsigned long  flags;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	
	unsigned char buffer;

	DEBUGNOISE(printk(KERN_DEBUG "ad1816: halt_output called!\n"));

	spin_lock_irqsave(&devc->lock,flags); 
	/* Mute pcm output */
	ad_write(devc, 4, ad_read(devc,4)|0x8080);

	if(!isa_dma_bridge_buggy) {
	        disable_dma(audio_devs[dev]->dmap_out->dma);
	}

	buffer=inb(devc->base+8);
	if (buffer & 0x01) {
		/* disable capture */
		outb(buffer & ~0x01,devc->base+8); 
	}

	if(!isa_dma_bridge_buggy) {
	        enable_dma(audio_devs[dev]->dmap_out->dma);
	}

	/* Clear interrupt status */
	outb ((unsigned char)~0x80, devc->base+1);	

	devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
	spin_unlock_irqrestore(&devc->lock,flags);
}

static void ad1816_output_block (int dev, unsigned long buf, 
				 int count, int intrflag)
{
	unsigned long flags;
	unsigned long cnt;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: output_block called buf=%ld count=%d flags=%d\n",buf,count,intrflag));
  
	cnt = count/4 - 1;
  
	spin_lock_irqsave(&devc->lock,flags);
	
	/* set transfer count */
	ad_write (devc, 8, cnt & 0xffff); 
	
	devc->audio_mode |= PCM_ENABLE_OUTPUT; 
	spin_unlock_irqrestore(&devc->lock,flags);
}


static void ad1816_start_input (int dev, unsigned long buf, int count,
				int intrflag)
{
	unsigned long flags;
	unsigned long  cnt;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: start_input called buf=%ld count=%d flags=%d\n",buf,count,intrflag));

	cnt = count/4 - 1;

	spin_lock_irqsave(&devc->lock,flags);

	/* set transfer count */
	ad_write (devc, 10, cnt & 0xffff); 
	devc->audio_mode |= PCM_ENABLE_INPUT;
	spin_unlock_irqrestore(&devc->lock,flags);
}

static int ad1816_prepare_for_input (int dev, int bsize, int bcount)
{
	unsigned long flags;
	unsigned int freq;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	unsigned char fmt_bits;
	
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_input called: bsize=%d bcount=%d\n",bsize,bcount));

	spin_lock_irqsave(&devc->lock,flags);
	fmt_bits= (devc->format_bits&0x7)<<3;
	
	/* set mono/stereo mode */
	if (devc->channels > 1) {
		fmt_bits |=0x4;
	}
	/* set Mono/Stereo in playback/capture register */
	outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8); 
	outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9);

	freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; 

	/* write playback/capture speeds */
	ad_write (devc, 2, freq & 0xffff);	
	ad_write (devc, 3, freq & 0xffff);	

	spin_unlock_irqrestore(&devc->lock,flags);

	ad1816_halt_input(dev);
	return 0;
}

static int ad1816_prepare_for_output (int dev, int bsize, int bcount)
{
	unsigned long flags;
	unsigned int freq;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	unsigned char fmt_bits;

	DEBUGNOISE(printk(KERN_DEBUG "ad1816: prepare_for_output called: bsize=%d bcount=%d\n",bsize,bcount));

	spin_lock_irqsave(&devc->lock,flags);

	fmt_bits= (devc->format_bits&0x7)<<3;
	/* set mono/stereo mode */
	if (devc->channels > 1) {
		fmt_bits |=0x4;
	}

	/* write format bits to playback/capture registers */
	outb( (inb(devc->base+8) & ~0x3C)|fmt_bits, devc->base+8); 
	outb( (inb(devc->base+9) & ~0x3C)|fmt_bits, devc->base+9);
  
	freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; 
	
	/* write playback/capture speeds */
	ad_write (devc, 2, freq & 0xffff);
	ad_write (devc, 3, freq & 0xffff);

	spin_unlock_irqrestore(&devc->lock,flags);
	
	ad1816_halt_output(dev);
	return 0;

}

static void ad1816_trigger (int dev, int state) 
{
	unsigned long flags;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;

	DEBUGNOISE(printk(KERN_DEBUG "ad1816: trigger called! (devc=%d,devc->base=%d\n", devc, devc->base));

	/* mode may have changed */

	spin_lock_irqsave(&devc->lock,flags);

	/* mask out modes not specified on open call */
	state &= devc->audio_mode; 
				
	/* setup soundchip to new io-mode */
	if (state & PCM_ENABLE_INPUT) {
		/* enable capture */
		outb(inb(devc->base+9)|0x01, devc->base+9);
	} else {
		/* disable capture */
		outb(inb(devc->base+9)&~0x01, devc->base+9);
	}

	if (state & PCM_ENABLE_OUTPUT) {
		/* enable playback */
		outb(inb(devc->base+8)|0x01, devc->base+8);
		/* unmute pcm output */
		ad_write(devc, 4, ad_read(devc,4)&~0x8080);
	} else {
		/* mute pcm output */
		ad_write(devc, 4, ad_read(devc,4)|0x8080);
		/* disable capture */
		outb(inb(devc->base+8)&~0x01, devc->base+8);
	}
	spin_unlock_irqrestore(&devc->lock,flags);
}


/* halt input & output */
static void ad1816_halt (int dev)
{
	ad1816_halt_input(dev);
	ad1816_halt_output(dev);
}

static void ad1816_reset (int dev)
{
	ad1816_halt (dev);
}

/* set playback speed */
static int ad1816_set_speed (int dev, int arg)
{
	unsigned long flags;
	unsigned int freq;
	int ret;

	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	
	spin_lock_irqsave(&devc->lock, flags);
	if (arg == 0) {
		ret = devc->speed;
		spin_unlock_irqrestore(&devc->lock, flags);
		return ret;
	}
	/* range checking */
	if (arg < 4000) {
		arg = 4000;
	}
	if (arg > 55000) {
		arg = 55000;
	}
	devc->speed = arg;

	/* change speed during playback */
	freq=((unsigned int)devc->speed*33000)/ad1816_clockfreq; 
	/* write playback/capture speeds */
	ad_write (devc, 2, freq & 0xffff);	
	ad_write (devc, 3, freq & 0xffff);	

	ret = devc->speed;
	spin_unlock_irqrestore(&devc->lock, flags);
	return ret;

}

static unsigned int ad1816_set_bits (int dev, unsigned int arg)
{
	unsigned long flags;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;
	
	static struct format_tbl {
		int             format;
		unsigned char   bits;
	} format2bits[] = {
		{ 0, 0 },
		{ AFMT_MU_LAW, 1 },
		{ AFMT_A_LAW, 3 },
		{ AFMT_IMA_ADPCM, 0 },
		{ AFMT_U8, 0 },
		{ AFMT_S16_LE, 2 },
		{ AFMT_S16_BE, 6 },
		{ AFMT_S8, 0 },
		{ AFMT_U16_LE, 0 },
		{ AFMT_U16_BE, 0 }
  	};

	int  i, n = sizeof (format2bits) / sizeof (struct format_tbl);

	spin_lock_irqsave(&devc->lock, flags);
	/* return current format */
	if (arg == 0) {
	  	arg = devc->audio_format;
		spin_unlock_irqrestore(&devc->lock, flags);
		return arg;
	}
	devc->audio_format = arg;

	/* search matching format bits */
	for (i = 0; i < n; i++)
		if (format2bits[i].format == arg) {
			devc->format_bits = format2bits[i].bits;
			devc->audio_format = arg;
			spin_unlock_irqrestore(&devc->lock, flags);
			return arg;
		}

	/* Still hanging here. Something must be terribly wrong */
	devc->format_bits = 0;
	devc->audio_format = AFMT_U8;
	spin_unlock_irqrestore(&devc->lock, flags);
	return(AFMT_U8); 
}

static short ad1816_set_channels (int dev, short arg)
{
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;

	if (arg != 1 && arg != 2)
		return devc->channels;

	devc->channels = arg;
	return arg;
}

/* open device */
static int ad1816_open (int dev, int mode) 
{
	ad1816_info    *devc = NULL;
	unsigned long   flags;

	/* is device number valid ? */
	if (dev < 0 || dev >= num_audiodevs)
		return -(ENXIO);

	/* get device info of this dev */
	devc = (ad1816_info *) audio_devs[dev]->devc; 

	/* make check if device already open atomic */
	spin_lock_irqsave(&devc->lock,flags);

	if (devc->opened) {
		spin_unlock_irqrestore(&devc->lock,flags);
		return -(EBUSY);
	}

	/* mark device as open */
	devc->opened = 1; 

	devc->audio_mode = 0;
	devc->speed = 8000;
	devc->audio_format=AFMT_U8;
	devc->channels=1;
	spin_unlock_irqrestore(&devc->lock,flags);
	ad1816_reset(devc->dev_no); /* halt all pending output */
	return 0;
}

static void ad1816_close (int dev) /* close device */
{
	unsigned long flags;
	ad1816_info    *devc = (ad1816_info *) audio_devs[dev]->devc;

	/* halt all pending output */
	ad1816_reset(devc->dev_no); 

	spin_lock_irqsave(&devc->lock,flags);
	devc->opened = 0;
	devc->audio_mode = 0;
	devc->speed = 8000;
	devc->audio_format=AFMT_U8;
	devc->format_bits = 0;
	spin_unlock_irqrestore(&devc->lock,flags);
}


/* ------------------------------------------------------------------- */

/* Audio driver structure */

static struct audio_driver ad1816_audio_driver =
{
	.owner			= THIS_MODULE,
	.open			= ad1816_open,
	.close			= ad1816_close,
	.output_block		= ad1816_output_block,
	.start_input		= ad1816_start_input,
	.prepare_for_input	= ad1816_prepare_for_input,
	.prepare_for_output	= ad1816_prepare_for_output,
	.halt_io		= ad1816_halt,
	.halt_input		= ad1816_halt_input,
	.halt_output		= ad1816_halt_output,
	.trigger		= ad1816_trigger,
	.set_speed		= ad1816_set_speed,
	.set_bits		= ad1816_set_bits,
	.set_channels		= ad1816_set_channels,
};


/* ------------------------------------------------------------------- */

/* Interrupt handler */


static irqreturn_t ad1816_interrupt (int irq, void *dev_id, struct pt_regs *dummy)
{
	unsigned char	status;
	ad1816_info	*devc = (ad1816_info *)dev_id;
	
	if (irq < 0 || irq > 15) {
	        printk(KERN_WARNING "ad1816: Got bogus interrupt %d\n", irq);
		return IRQ_NONE;
	}

	spin_lock(&devc->lock);

	/* read interrupt register */
	status = inb (devc->base+1); 
	/* Clear all interrupt  */
	outb (~status, devc->base+1);	

	DEBUGNOISE(printk(KERN_DEBUG "ad1816: Got interrupt subclass %d\n",status));

	if (status == 0) {
		DEBUGNOISE(printk(KERN_DEBUG "ad1816: interrupt: Got interrupt, but no source.\n"));
		spin_unlock(&devc->lock);
		return IRQ_NONE;
	}

	if (devc->opened && (devc->audio_mode & PCM_ENABLE_INPUT) && (status&64))
		DMAbuf_inputintr (devc->dev_no);

	if (devc->opened && (devc->audio_mode & PCM_ENABLE_OUTPUT) && (status & 128))
		DMAbuf_outputintr (devc->dev_no, 1);

	spin_unlock(&devc->lock);
	return IRQ_HANDLED;
}

/* ------------------------------------------------------------------- */

/* Mixer stuff */

struct mixer_def {
	unsigned int regno: 7;
	unsigned int polarity:1;	/* 0=normal, 1=reversed */
	unsigned int bitpos:4;
	unsigned int nbits:4;
};

static char mix_cvt[101] = {
	 0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42,
	43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,
	65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79,
	80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90,
	91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99,
	100
};

typedef struct mixer_def mixer_ent;

/*
 * Most of the mixer entries work in backwards. Setting the polarity field
 * makes them to work correctly.
 *
 * The channel numbering used by individual soundcards is not fixed. Some
 * cards have assigned different meanings for the AUX1, AUX2 and LINE inputs.
 * The current version doesn't try to compensate this.
 */

#define MIX_ENT(name, reg_l, pola_l, pos_l, len_l, reg_r, pola_r, pos_r, len_r)	\
  {{reg_l, pola_l, pos_l, len_l}, {reg_r, pola_r, pos_r, len_r}}


mixer_ent mix_devices[SOUND_MIXER_NRDEVICES][2] = {
MIX_ENT(SOUND_MIXER_VOLUME,	14, 1, 8, 5,	14, 1, 0, 5),
MIX_ENT(SOUND_MIXER_BASS,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_TREBLE,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_SYNTH,	 5, 1, 8, 6,	 5, 1, 0, 6),
MIX_ENT(SOUND_MIXER_PCM,	 4, 1, 8, 6,	 4, 1, 0, 6),
MIX_ENT(SOUND_MIXER_SPEAKER,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_LINE,	18, 1, 8, 5,	18, 1, 0, 5),
MIX_ENT(SOUND_MIXER_MIC,	19, 1, 8, 5,	19, 1, 0, 5),
MIX_ENT(SOUND_MIXER_CD,	 	15, 1, 8, 5,	15, 1, 0, 5),
MIX_ENT(SOUND_MIXER_IMIX,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_RECLEV,	20, 0, 8, 4,	20, 0, 0, 4),
MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_OGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0),
MIX_ENT(SOUND_MIXER_LINE1, 	17, 1, 8, 5,	17, 1, 0, 5),
MIX_ENT(SOUND_MIXER_LINE2,	16, 1, 8, 5,	16, 1, 0, 5),
MIX_ENT(SOUND_MIXER_LINE3,      39, 0, 9, 4,    39, 1, 0, 5)
};


static unsigned short default_mixer_levels[SOUND_MIXER_NRDEVICES] =
{
	0x4343,		/* Master Volume */
	0x3232,		/* Bass */
	0x3232,		/* Treble */
	0x0000,		/* FM */
	0x4343,		/* PCM */
	0x0000,		/* PC Speaker */
	0x0000,		/* Ext Line */
	0x0000,		/* Mic */
	0x0000,		/* CD */
	0x0000,		/* Recording monitor */
	0x0000,		/* SB PCM */
	0x0000,		/* Recording level */
	0x0000,		/* Input gain */
	0x0000,		/* Output gain */
	0x0000,		/* Line1 */
	0x0000,		/* Line2 */
	0x0000		/* Line3 (usually line in)*/
};

#define LEFT_CHN	0
#define RIGHT_CHN	1



static int
ad1816_set_recmask (ad1816_info * devc, int mask)
{
  	unsigned long 	flags;
	unsigned char   recdev;
	int             i, n;
	
	spin_lock_irqsave(&devc->lock, flags);
	mask &= devc->supported_rec_devices;
	
	n = 0;
	/* Count selected device bits */
	for (i = 0; i < 32; i++)
		if (mask & (1 << i))
			n++;
	
	if (n == 0)
		mask = SOUND_MASK_MIC;
	else if (n != 1) { /* Too many devices selected */
		/* Filter out active settings */
		mask &= ~devc->recmask;	
		
		n = 0;
		/* Count selected device bits */
		for (i = 0; i < 32; i++) 
			if (mask & (1 << i))
				n++;
		
		if (n != 1)
			mask = SOUND_MASK_MIC;
	}
	
	switch (mask) {
	case SOUND_MASK_MIC:
		recdev = 5;
		break;
		
	case SOUND_MASK_LINE:
		recdev = 0;
		break;
		
	case SOUND_MASK_CD:
		recdev = 2;
		break;
		
	case SOUND_MASK_LINE1:
		recdev = 4;
		break;
		
	case SOUND_MASK_LINE2:
		recdev = 3;
		break;
		
	case SOUND_MASK_VOLUME:
		recdev = 1;
		break;
		
	default:
		mask = SOUND_MASK_MIC;
		recdev = 5;
	}
	
	recdev <<= 4;
	ad_write (devc, 20, 
		  (ad_read (devc, 20) & 0x8f8f) | recdev | (recdev<<8));

	devc->recmask = mask;
	spin_unlock_irqrestore(&devc->lock, flags);
	return mask;
}

static void
change_bits (int *regval, int dev, int chn, int newval)
{
	unsigned char   mask;
	int             shift;
  
	/* Reverse polarity*/

	if (mix_devices[dev][chn].polarity == 1) 
		newval = 100 - newval;

	mask = (1 << mix_devices[dev][chn].nbits) - 1;
	shift = mix_devices[dev][chn].bitpos;
	/* Scale it */
	newval = (int) ((newval * mask) + 50) / 100;	
	/* Clear bits */
	*regval &= ~(mask << shift);	
	/* Set new value */
	*regval |= (newval & mask) << shift;	
}

static int
ad1816_mixer_get (ad1816_info * devc, int dev)
{
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_get called!\n"));
	
	/* range check + supported mixer check */
	if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES )
	        return (-(EINVAL));
	if (!((1 << dev) & devc->supported_devices))
		return -(EINVAL);
	
	return devc->levels[dev];
}

static int
ad1816_mixer_set (ad1816_info * devc, int dev, int value)
{
	int   left = value & 0x000000ff;
	int   right = (value & 0x0000ff00) >> 8;
	int   retvol;

	int   regoffs;
	int   val;
	int   valmute;
	unsigned long flags;

	DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_set called!\n"));
	
	if (dev < 0 || dev >= SOUND_MIXER_NRDEVICES )
		return -(EINVAL);

	if (left > 100)
		left = 100;
	if (left < 0)
		left = 0;
	if (right > 100)
		right = 100;
	if (right < 0)
		right = 0;
	
	/* Mono control */
	if (mix_devices[dev][RIGHT_CHN].nbits == 0) 
		right = left;
	retvol = left | (right << 8);
	
	/* Scale it */
	
	left = mix_cvt[left];
	right = mix_cvt[right];

	/* reject all mixers that are not supported */
	if (!(devc->supported_devices & (1 << dev)))
		return -(EINVAL);
	
	/* sanity check */
	if (mix_devices[dev][LEFT_CHN].nbits == 0)
		return -(EINVAL);
	spin_lock_irqsave(&devc->lock, flags);

	/* keep precise volume internal */
	devc->levels[dev] = retvol;

	/* Set the left channel */
	regoffs = mix_devices[dev][LEFT_CHN].regno;
	val = ad_read (devc, regoffs);
	change_bits (&val, dev, LEFT_CHN, left);

	valmute=val;

	/* Mute bit masking on some registers */
	if ( regoffs==5 || regoffs==14 || regoffs==15 ||
	     regoffs==16 || regoffs==17 || regoffs==18 || 
	     regoffs==19 || regoffs==39) {
		if (left==0)
			valmute |= 0x8000;
		else
			valmute &= ~0x8000;
	}
	ad_write (devc, regoffs, valmute); /* mute */

	/*
	 * Set the right channel
	 */
 
	/* Was just a mono channel */
	if (mix_devices[dev][RIGHT_CHN].nbits == 0) {
		spin_unlock_irqrestore(&devc->lock, flags);
		return retvol;		
	}

	regoffs = mix_devices[dev][RIGHT_CHN].regno;
	val = ad_read (devc, regoffs);
	change_bits (&val, dev, RIGHT_CHN, right);

	valmute=val;
	if ( regoffs==5 || regoffs==14 || regoffs==15 ||
	     regoffs==16 || regoffs==17 || regoffs==18 || 
	     regoffs==19 || regoffs==39) {
		if (right==0)
			valmute |= 0x80;
		else
			valmute &= ~0x80;
	}
	ad_write (devc, regoffs, valmute); /* mute */
	spin_unlock_irqrestore(&devc->lock, flags);
       	return retvol;
}

#define MIXER_DEVICES ( SOUND_MASK_VOLUME | \
			SOUND_MASK_SYNTH | \
			SOUND_MASK_PCM | \
			SOUND_MASK_LINE | \
			SOUND_MASK_LINE1 | \
			SOUND_MASK_LINE2 | \
			SOUND_MASK_LINE3 | \
			SOUND_MASK_MIC | \
			SOUND_MASK_CD | \
			SOUND_MASK_RECLEV  \
			)
#define REC_DEVICES ( SOUND_MASK_LINE2 |\
		      SOUND_MASK_LINE |\
		      SOUND_MASK_LINE1 |\
		      SOUND_MASK_MIC |\
		      SOUND_MASK_CD |\
		      SOUND_MASK_VOLUME \
		      )
     
static void
ad1816_mixer_reset (ad1816_info * devc)
{
	int  i;

	devc->supported_devices = MIXER_DEVICES;
	
	devc->supported_rec_devices = REC_DEVICES;

	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
		if (devc->supported_devices & (1 << i))
			ad1816_mixer_set (devc, i, default_mixer_levels[i]);
	ad1816_set_recmask (devc, SOUND_MASK_MIC);
}

static int
ad1816_mixer_ioctl (int dev, unsigned int cmd, void __user * arg)
{
	ad1816_info    *devc = mixer_devs[dev]->devc;
	int val;
	int __user *p = arg;
  
	DEBUGNOISE(printk(KERN_DEBUG "ad1816: mixer_ioctl called!\n"));
  
	/* Mixer ioctl */
	if (((cmd >> 8) & 0xff) == 'M') { 
		
		/* set ioctl */
		if (_SIOC_DIR (cmd) & _SIOC_WRITE) { 
			switch (cmd & 0xff){
			case SOUND_MIXER_RECSRC:
				
				if (get_user(val, p))
					return -EFAULT;
				val=ad1816_set_recmask (devc, val);
				return put_user(val, p);
				break;
				
			default:
				if (get_user(val, p))
					return -EFAULT;
				if ((val=ad1816_mixer_set (devc, cmd & 0xff, val))<0)
				        return val;
				else
				        return put_user(val, p);
			}
		} else { 
			/* read ioctl */
			switch (cmd & 0xff) {
				
			case SOUND_MIXER_RECSRC:
				val=devc->recmask;
				return put_user(val, p);
				break;
				
			case SOUND_MIXER_DEVMASK:
				val=devc->supported_devices;
				return put_user(val, p);
				break;

			case SOUND_MIXER_STEREODEVS:
				val=devc->supported_devices & ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
				return put_user(val, p);
				break;
				
			case SOUND_MIXER_RECMASK:
				val=devc->supported_rec_devices;
				return put_user(val, p);
				break;
				
			case SOUND_MIXER_CAPS:
				val=SOUND_CAP_EXCL_INPUT;
				return put_user(val, p);
				break;
				
			default:
			        if ((val=ad1816_mixer_get (devc, cmd & 0xff))<0)
				        return val;
				else
				        return put_user(val, p);
			}
		}
	} else
		/* not for mixer */
		return -(EINVAL);
}

/* ------------------------------------------------------------------- */

/* Mixer structure */

static struct mixer_operations ad1816_mixer_operations = {
	.owner	= THIS_MODULE,
	.id	= "AD1816",
	.name	= "AD1816 Mixer",
	.ioctl	= ad1816_mixer_ioctl
};


/* ------------------------------------------------------------------- */

/* stuff for card recognition, init and unloading PNP ...*/


/* check if AD1816 present at specified hw_config and register device with OS 
 * return 1 if initialization was successful, 0 otherwise
 */
static int __init ad1816_init_card (struct address_info *hw_config, 
	struct pnp_dev *pnp)
{
	ad1816_info    *devc = NULL;
	int tmp;
	int oss_devno = -1;

	printk(KERN_INFO "ad1816: initializing card: io=0x%x, irq=%d, dma=%d, "
			 "dma2=%d, clockfreq=%d, options=%d isadmabug=%d "
			 "%s\n",
	       hw_config->io_base,
	       hw_config->irq,
	       hw_config->dma,
	       hw_config->dma2,
	       ad1816_clockfreq,
	       options,
	       isa_dma_bridge_buggy,
	       pnp?"(PNP)":"");

	/* ad1816_info structure remaining ? */
	if (nr_ad1816_devs >= MAX_AUDIO_DEV) {
		printk(KERN_WARNING "ad1816: no more ad1816_info structures "
			"left\n");
		goto out;
	}

	devc = &dev_info[nr_ad1816_devs];
	devc->base = hw_config->io_base;
	devc->irq = hw_config->irq;
	devc->dma_playback=hw_config->dma;
	devc->dma_capture=hw_config->dma2;
	devc->opened = 0;
	devc->pnpdev = pnp;
	spin_lock_init(&devc->lock);

	if (!request_region(devc->base, 16, "AD1816 Sound")) {
		printk(KERN_WARNING "ad1816: I/O port 0x%03x not free\n",
				    devc->base);
		goto out;
	}

	printk(KERN_INFO "ad1816: Examining AD1816 at address 0x%03x.\n", 
		devc->base);
	

	/* tests for ad1816 */
	/* base+0: bit 1 must be set but not 255 */
	tmp=inb(devc->base);
	if ( (tmp&0x80)==0 || tmp==255 ) {
		printk (KERN_INFO "ad1816: Chip is not an AD1816 or chip "
			"is not active (Test 0)\n");
		goto out_release_region;
	}

	/* writes to ireg 8 are copied to ireg 9 */
	ad_write(devc,8,12345); 
	if (ad_read(devc,9)!=12345) {
		printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 1)\n");
		goto out_release_region;
	}
  
	/* writes to ireg 8 are copied to ireg 9 */
	ad_write(devc,8,54321); 
	if (ad_read(devc,9)!=54321) {
		printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 2)\n");
		goto out_release_region;
	}

	/* writes to ireg 10 are copied to ireg 11 */
	ad_write(devc,10,54321); 
	if (ad_read(devc,11)!=54321) {
		printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 3)\n");
		goto out_release_region;
	}

	/* writes to ireg 10 are copied to ireg 11 */
	ad_write(devc,10,12345); 
	if (ad_read(devc,11)!=12345) {
		printk (KERN_INFO "ad1816: Chip is not an AD1816 (Test 4)\n");
		goto out_release_region;
	}

	/* bit in base +1 cannot be set to 1 */
	tmp=inb(devc->base+1);
	outb(0xff,devc->base+1); 
	if (inb(devc->base+1)!=tmp) {
		printk(KERN_INFO "ad1816: Chip is not an AD1816 (Test 5)\n");
		goto out_release_region;
	}
  
	printk(KERN_INFO "ad1816: AD1816 (version %d) successfully detected!\n",
		ad_read(devc,45));

	/* disable all interrupts */
	ad_write(devc,1,0);     

	/* Clear pending interrupts */
	outb (0, devc->base+1);	

	/* allocate irq */
	if (devc->irq < 0 || devc->irq > 15)
		goto out_release_region;
	if (request_irq(devc->irq, ad1816_interrupt,0,
			"SoundPort", devc) < 0)	{
	        printk(KERN_WARNING "ad1816: IRQ in use\n");
		goto out_release_region;
	}

	/* DMA stuff */
	if (sound_alloc_dma (devc->dma_playback, "Sound System")) {
		printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n",
				    devc->dma_playback);
		goto out_free_irq;
	}
	
	if ( devc->dma_capture >= 0 && 
	  	devc->dma_capture != devc->dma_playback) {
		if (sound_alloc_dma(devc->dma_capture,
				    "Sound System (capture)")) {
			printk(KERN_WARNING "ad1816: Can't allocate DMA%d\n",
					    devc->dma_capture);
			goto out_free_dma;
		}
		devc->audio_mode=DMA_AUTOMODE|DMA_DUPLEX;
	} else {
	  	printk(KERN_WARNING "ad1816: Only one DMA channel "
			"available/configured. No duplex operation possible\n");
		devc->audio_mode=DMA_AUTOMODE;
	}

	conf_printf2 ("AD1816 audio driver",
		      devc->base, devc->irq, devc->dma_playback, 
		      devc->dma_capture);

	/* register device */
	if ((oss_devno = sound_install_audiodrv (AUDIO_DRIVER_VERSION,
					      "AD1816 audio driver",
					      &ad1816_audio_driver,
					      sizeof (struct audio_driver),
					      devc->audio_mode,
					      ad_format_mask,
					      devc,
					      devc->dma_playback, 
					      devc->dma_capture)) < 0) {
		printk(KERN_WARNING "ad1816: Can't install sound driver\n");
		goto out_free_dma_2;
	}


	ad_write(devc,32,0x80f0); /* sound system mode */
	if (options&1) {
	        ad_write(devc,33,0); /* disable all audiosources for dsp */
	} else {
	        ad_write(devc,33,0x03f8); /* enable all audiosources for dsp */
	}
	ad_write(devc,4,0x8080);  /* default values for volumes (muted)*/
	ad_write(devc,5,0x8080);
	ad_write(devc,6,0x8080);
	ad_write(devc,7,0x8080);
	ad_write(devc,15,0x8888);
	ad_write(devc,16,0x8888);
	ad_write(devc,17,0x8888);
	ad_write(devc,18,0x8888);
	ad_write(devc,19,0xc888); /* +20db mic active */
	ad_write(devc,14,0x0000); /* Master volume unmuted */
	ad_write(devc,39,0x009f); /* 3D effect on 0% phone out muted */
	ad_write(devc,44,0x0080); /* everything on power, 3d enabled for d/a */
	outb(0x10,devc->base+8); /* set dma mode */
	outb(0x10,devc->base+9);
  
	/* enable capture + playback interrupt */
	ad_write(devc,1,0xc000); 
	
	/* set mixer defaults */
	ad1816_mixer_reset (devc); 
  
	/* register mixer */
	if ((audio_devs[oss_devno]->mixer_dev=sound_install_mixer(
				       MIXER_DRIVER_VERSION,
				       "AD1816 audio driver",
				       &ad1816_mixer_operations,
				       sizeof (struct mixer_operations),
				       devc)) < 0) {
	  	printk(KERN_WARNING "Can't install mixer\n");
	}
	/* make ad1816_info active */
	nr_ad1816_devs++;
	printk(KERN_INFO "ad1816: card successfully installed!\n");
	return 1;
	/* error handling */
out_free_dma_2:
	if (devc->dma_capture >= 0 && devc->dma_capture != devc->dma_playback)
	        sound_free_dma(devc->dma_capture);
out_free_dma:
	sound_free_dma(devc->dma_playback);
out_free_irq:
	free_irq(devc->irq, devc);
out_release_region:
	release_region(devc->base, 16);
out:
	return 0;
}

static void __exit unload_card(ad1816_info *devc)
{
	int  mixer, dev = 0;
	
	if (devc != NULL) {
		printk("ad1816: Unloading card at address 0x%03x\n",devc->base);
		
		dev = devc->dev_no;
		mixer = audio_devs[dev]->mixer_dev;

		/* unreg mixer*/
		if(mixer>=0) {
			sound_unload_mixerdev(mixer);
		}
		/* unreg audiodev */
		sound_unload_audiodev(dev);
		
		/* free dma channels */
		if (devc->dma_capture>=0 && 
		  	devc->dma_capture != devc->dma_playback) {
			sound_free_dma(devc->dma_capture);
		}
		sound_free_dma (devc->dma_playback);
		/* free irq */
		free_irq(devc->irq, devc);
		/* free io */
		release_region (devc->base, 16);
#ifdef __ISAPNP__
		if (devc->pnpdev) {
		  	pnp_disable_dev(devc->pnpdev);
			pnp_device_detach(devc->pnpdev);
		}
#endif
		
	} else
		printk(KERN_WARNING "ad1816: no device/card specified\n");
}

static int __initdata io = -1;
static int __initdata irq = -1;
static int __initdata dma = -1;
static int __initdata dma2 = -1;

#ifdef __ISAPNP__
/* use isapnp for configuration */
static int isapnp	= 1;
static int isapnpjump;
module_param(isapnp, bool, 0);
module_param(isapnpjump, int, 0);
#endif

module_param(io, int, 0);
module_param(irq, int, 0);
module_param(dma, int, 0);
module_param(dma2, int, 0);
module_param(ad1816_clockfreq, int, 0);
module_param(options, int, 0);

#ifdef __ISAPNP__
static struct {
	unsigned short card_vendor, card_device;
	unsigned short vendor;
	unsigned short function;
	struct ad1816_data *data;
} isapnp_ad1816_list[] __initdata = {
	{	ISAPNP_ANY_ID, ISAPNP_ANY_ID,
		ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7150), 
		NULL },
	{	ISAPNP_ANY_ID, ISAPNP_ANY_ID,
		ISAPNP_VENDOR('A','D','S'), ISAPNP_FUNCTION(0x7180),
		NULL },
	{0}
};

MODULE_DEVICE_TABLE(isapnp, isapnp_ad1816_list);


static void __init ad1816_config_pnp_card(struct pnp_card *card,
					  unsigned short vendor,
					  unsigned short function)
{
	struct address_info cfg;
  	struct pnp_dev *card_dev = pnp_find_dev(card, vendor, function, NULL);
	if (!card_dev) return;
	if (pnp_device_attach(card_dev) < 0) {
	  	printk(KERN_WARNING "ad1816: Failed to attach PnP device\n");
		return;
	}
	if (pnp_activate_dev(card_dev) < 0) {
		printk(KERN_WARNING "ad1816: Failed to activate PnP device\n");
		pnp_device_detach(card_dev);
		return;
	}
	cfg.io_base = pnp_port_start(card_dev, 2);
	cfg.irq = pnp_irq(card_dev, 0);
	cfg.dma = pnp_irq(card_dev, 0);
	cfg.dma2 = pnp_irq(card_dev, 1);
	if (!ad1816_init_card(&cfg, card_dev)) {
	  	pnp_disable_dev(card_dev);
		pnp_device_detach(card_dev);
	}
}

static void __init ad1816_config_pnp_cards(void)
{
	int nr_pnp_cfg;
	int i;
	
	/* Count entries in isapnp_ad1816_list */
	for (nr_pnp_cfg = 0; isapnp_ad1816_list[nr_pnp_cfg].card_vendor != 0; 
		nr_pnp_cfg++);
	/* Check and adjust isapnpjump */
	if( isapnpjump < 0 || isapnpjump >= nr_pnp_cfg) {
		printk(KERN_WARNING 
			"ad1816: Valid range for isapnpjump is 0-%d. "
			"Adjusted to 0.\n", nr_pnp_cfg-1);
		isapnpjump = 0;
	}
	for (i = isapnpjump; isapnp_ad1816_list[i].card_vendor != 0; i++) {
	 	struct pnp_card *card = NULL;
		/* iterate over all pnp cards */		
		while ((card = pnp_find_card(isapnp_ad1816_list[i].card_vendor,
		              	isapnp_ad1816_list[i].card_device, card))) 
			ad1816_config_pnp_card(card, 
				isapnp_ad1816_list[i].vendor,
				isapnp_ad1816_list[i].function);
	}
}
#endif

/* module initialization */
static int __init init_ad1816(void)
{
	printk(KERN_INFO "ad1816: AD1816 sounddriver "
			 "Copyright (C) 1998-2003 by Thorsten Knabe and "
			 "others\n");
#ifdef AD1816_CLOCK 
	/* set ad1816_clockfreq if set during compilation */
	ad1816_clockfreq=AD1816_CLOCK;
#endif
	if (ad1816_clockfreq<5000 || ad1816_clockfreq>100000) {
		ad1816_clockfreq=33000;
	}

#ifdef __ISAPNP__
	/* configure PnP cards */
	if(isapnp) ad1816_config_pnp_cards();
#endif
	/* configure card by module params */
	if (io != -1 && irq != -1 && dma != -1) {
		struct address_info cfg;
		cfg.io_base = io;
		cfg.irq = irq;
		cfg.dma = dma;
		cfg.dma2 = dma2;
		ad1816_init_card(&cfg, NULL);
	}
	if (nr_ad1816_devs <= 0)
	  	return -ENODEV;
	return 0;
}

/* module cleanup */
static void __exit cleanup_ad1816 (void)
{
	int          i;
	ad1816_info  *devc = NULL;
  
	/* remove any soundcard */
	for (i = 0;  i < nr_ad1816_devs; i++) {
		devc = &dev_info[i];
		unload_card(devc);
	}     
	nr_ad1816_devs=0;
	printk(KERN_INFO "ad1816: driver unloaded!\n");
}

module_init(init_ad1816);
module_exit(cleanup_ad1816);

#ifndef MODULE
/* kernel command line parameter evaluation */
static int __init setup_ad1816(char *str)
{
	/* io, irq, dma, dma2 */
	int ints[5];
	
	str = get_options(str, ARRAY_SIZE(ints), ints);
	
	io	= ints[1];
	irq	= ints[2];
	dma	= ints[3];
	dma2	= ints[4];
	return 1;
}

__setup("ad1816=", setup_ad1816);
#endif
MODULE_LICENSE("GPL");