.
aboutsummaryrefslogblamecommitdiffstats
path: root/sound/oss/au1550_ac97.c
blob: a8f626d99c5b9245e4a0eaf3bf074718caf1889d (plain) (tree)




































                                                                            











                            

                           
                             

                        


                        

                                         
                                   

















                                                                           
                                       
                        
                           











                                                                                 

                                
                                   



































































                                                              


                           
                                                                 




                                          
                                                     






























                                                           

                         











                                                      

                         









                                                 
     








                                                    
                                                     
























































































































































































































                                                                            
                                              



























                                                  
                                              
















































                                                                          
                                              






























                                                                          
                                              



                                                
                                                              



                                         



                         








                                               













                                                             
                                                                          
                                                                 






































































































                                                                                    
                                                    




                                                                

                            





                                                                


                                                                        












                                                            

                              


 
                                                    





                                                                

                            






                                                                   
                                      







                                                         
                                                                  
                                                               












                                                   
                              











                                                           
                                       
                                           
                                         















                                                             

                                                                           
 
                                                    
                                            

                
                                       
                                            
                                         
 
                   


                                                              




                                                

































































































































































































                                                                               
                                                    













                                                    
                            





                                                     
                                                           

                                             









                                                                        
                                                      





                                                                   
                                                    


























                                                                     
                              








                                                                                
                                                    














                                                   
                            
















                                                                        
                                                      





                                                                   
                                                    























                                                                             
                                                              

                                                                        


















                                                                    
                              










                                                              
                                                    





































                                                                               
                                                    



                             
                                       
                            
























                                                                                      
                              
                                         























































                                                                    
                                                                    
 
                                                    









                                                                       
                                                                 


                                                
                                          
























































































































































































































                                                                               

                                                                   
                                             

                                                                        


                                                 

                                                                   
                                             

                                                                        






































                                                                                                                 
                                         
                                            
                                           

















































































































































                                                                             




                                                                             
                                       
                                           
                                         


                   
















                                                     
                                       
                                            
                                   
                                             


                                               

                                                        
                                             


                                                        
                                   
                                            
                                  
                                           


























                                                                            
                                 


                                               
                                 


                                                                  
                            



                                     
                                         
                   




                                                      
                                                    
 
                                       

                                         
                                                 
                                                         
                                               

         
                                   










                                                                     
                                     
                               
                                         



                                                              








                                                



                                                                    

                      











                                                  
                                   



























































































                                                                                  
                                             





















                                                                          
                                             







































































































                                                                           
/*
 * au1550_ac97.c  --  Sound driver for Alchemy Au1550 MIPS Internet Edge
 *                    Processor.
 *
 * Copyright 2004 Embedded Edge, LLC
 *	dan@embeddededge.com
 *
 * Mostly copied from the au1000.c driver and some from the
 * PowerMac dbdma driver.
 * We assume the processor can do memory coherent DMA.
 *
 * Ported to 2.6 by Matt Porter <mporter@kernel.crashing.org>
 *
 *  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  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  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.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#undef DEBUG

#include <linux/module.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/sound.h>
#include <linux/slab.h>
#include <linux/soundcard.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/poll.h>
#include <linux/bitops.h>
#include <linux/spinlock.h>
#include <linux/ac97_codec.h>
#include <linux/mutex.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/hardirq.h>
#include <asm/mach-au1x00/au1xxx_psc.h>
#include <asm/mach-au1x00/au1xxx_dbdma.h>
#include <asm/mach-au1x00/au1xxx.h>

#undef OSS_DOCUMENTED_MIXER_SEMANTICS

/* misc stuff */
#define POLL_COUNT   0x50000
#define AC97_EXT_DACS (AC97_EXTID_SDAC | AC97_EXTID_CDAC | AC97_EXTID_LDAC)

/* The number of DBDMA ring descriptors to allocate.  No sense making
 * this too large....if you can't keep up with a few you aren't likely
 * to be able to with lots of them, either.
 */
#define NUM_DBDMA_DESCRIPTORS 4

#define err(format, arg...) printk(KERN_ERR format "\n" , ## arg)

/* Boot options
 * 0 = no VRA, 1 = use VRA if codec supports it
 */
static DEFINE_MUTEX(au1550_ac97_mutex);
static int      vra = 1;
module_param(vra, bool, 0);
MODULE_PARM_DESC(vra, "if 1 use VRA if codec supports it");

static struct au1550_state {
	/* soundcore stuff */
	int             dev_audio;

	struct ac97_codec *codec;
	unsigned        codec_base_caps; /* AC'97 reg 00h, "Reset Register" */
	unsigned        codec_ext_caps;  /* AC'97 reg 28h, "Extended Audio ID" */
	int             no_vra;		/* do not use VRA */

	spinlock_t      lock;
	struct mutex open_mutex;
	struct mutex sem;
	fmode_t          open_mode;
	wait_queue_head_t open_wait;

	struct dmabuf {
		u32		dmanr;
		unsigned        sample_rate;
		unsigned	src_factor;
		unsigned        sample_size;
		int             num_channels;
		int		dma_bytes_per_sample;
		int		user_bytes_per_sample;
		int		cnt_factor;

		void		*rawbuf;
		unsigned        buforder;
		unsigned	numfrag;
		unsigned        fragshift;
		void		*nextIn;
		void		*nextOut;
		int		count;
		unsigned        total_bytes;
		unsigned        error;
		wait_queue_head_t wait;

		/* redundant, but makes calculations easier */
		unsigned	fragsize;
		unsigned	dma_fragsize;
		unsigned	dmasize;
		unsigned	dma_qcount;

		/* OSS stuff */
		unsigned        mapped:1;
		unsigned        ready:1;
		unsigned        stopped:1;
		unsigned        ossfragshift;
		int             ossmaxfrags;
		unsigned        subdivision;
	} dma_dac, dma_adc;
} au1550_state;

static unsigned
ld2(unsigned int x)
{
	unsigned        r = 0;

	if (x >= 0x10000) {
		x >>= 16;
		r += 16;
	}
	if (x >= 0x100) {
		x >>= 8;
		r += 8;
	}
	if (x >= 0x10) {
		x >>= 4;
		r += 4;
	}
	if (x >= 4) {
		x >>= 2;
		r += 2;
	}
	if (x >= 2)
		r++;
	return r;
}

static void
au1550_delay(int msec)
{
	if (in_interrupt())
		return;

	schedule_timeout_uninterruptible(msecs_to_jiffies(msec));
}

static u16
rdcodec(struct ac97_codec *codec, u8 addr)
{
	struct au1550_state *s = codec->private_data;
	unsigned long   flags;
	u32             cmd, val;
	u16             data;
	int             i;

	spin_lock_irqsave(&s->lock, flags);

	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97STAT);
		au_sync();
		if (!(val & PSC_AC97STAT_CP))
			break;
	}
	if (i == POLL_COUNT)
		err("rdcodec: codec cmd pending expired!");

	cmd = (u32)PSC_AC97CDC_INDX(addr);
	cmd |= PSC_AC97CDC_RD;	/* read command */
	au_writel(cmd, PSC_AC97CDC);
	au_sync();

	/* now wait for the data
	*/
	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97STAT);
		au_sync();
		if (!(val & PSC_AC97STAT_CP))
			break;
	}
	if (i == POLL_COUNT) {
		err("rdcodec: read poll expired!");
		data = 0;
		goto out;
	}

	/* wait for command done?
	*/
	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97EVNT);
		au_sync();
		if (val & PSC_AC97EVNT_CD)
			break;
	}
	if (i == POLL_COUNT) {
		err("rdcodec: read cmdwait expired!");
		data = 0;
		goto out;
	}

	data = au_readl(PSC_AC97CDC) & 0xffff;
	au_sync();

	/* Clear command done event.
	*/
	au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT);
	au_sync();

 out:
	spin_unlock_irqrestore(&s->lock, flags);

	return data;
}


static void
wrcodec(struct ac97_codec *codec, u8 addr, u16 data)
{
	struct au1550_state *s = codec->private_data;
	unsigned long   flags;
	u32             cmd, val;
	int             i;

	spin_lock_irqsave(&s->lock, flags);

	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97STAT);
		au_sync();
		if (!(val & PSC_AC97STAT_CP))
			break;
	}
	if (i == POLL_COUNT)
		err("wrcodec: codec cmd pending expired!");

	cmd = (u32)PSC_AC97CDC_INDX(addr);
	cmd |= (u32)data;
	au_writel(cmd, PSC_AC97CDC);
	au_sync();

	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97STAT);
		au_sync();
		if (!(val & PSC_AC97STAT_CP))
			break;
	}
	if (i == POLL_COUNT)
		err("wrcodec: codec cmd pending expired!");

	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97EVNT);
		au_sync();
		if (val & PSC_AC97EVNT_CD)
			break;
	}
	if (i == POLL_COUNT)
		err("wrcodec: read cmdwait expired!");

	/* Clear command done event.
	*/
	au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT);
	au_sync();

	spin_unlock_irqrestore(&s->lock, flags);
}

static void
waitcodec(struct ac97_codec *codec)
{
	u16	temp;
	u32	val;
	int	i;

	/* codec_wait is used to wait for a ready state after
	 * an AC97C_RESET.
	 */
	au1550_delay(10);

	/* first poll the CODEC_READY tag bit
	*/
	for (i = 0; i < POLL_COUNT; i++) {
		val = au_readl(PSC_AC97STAT);
		au_sync();
		if (val & PSC_AC97STAT_CR)
			break;
	}
	if (i == POLL_COUNT) {
		err("waitcodec: CODEC_READY poll expired!");
		return;
	}

	/* get AC'97 powerdown control/status register
	*/
	temp = rdcodec(codec, AC97_POWER_CONTROL);

	/* If anything is powered down, power'em up
	*/
	if (temp & 0x7f00) {
		/* Power on
		*/
		wrcodec(codec, AC97_POWER_CONTROL, 0);
		au1550_delay(100);

		/* Reread
		*/
		temp = rdcodec(codec, AC97_POWER_CONTROL);
	}

	/* Check if Codec REF,ANL,DAC,ADC ready
	*/
	if ((temp & 0x7f0f) != 0x000f)
		err("codec reg 26 status (0x%x) not ready!!", temp);
}

/* stop the ADC before calling */
static void
set_adc_rate(struct au1550_state *s, unsigned rate)
{
	struct dmabuf  *adc = &s->dma_adc;
	struct dmabuf  *dac = &s->dma_dac;
	unsigned        adc_rate, dac_rate;
	u16             ac97_extstat;

	if (s->no_vra) {
		/* calc SRC factor
		*/
		adc->src_factor = ((96000 / rate) + 1) >> 1;
		adc->sample_rate = 48000 / adc->src_factor;
		return;
	}

	adc->src_factor = 1;

	ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS);

	rate = rate > 48000 ? 48000 : rate;

	/* enable VRA
	*/
	wrcodec(s->codec, AC97_EXTENDED_STATUS,
		ac97_extstat | AC97_EXTSTAT_VRA);

	/* now write the sample rate
	*/
	wrcodec(s->codec, AC97_PCM_LR_ADC_RATE, (u16) rate);

	/* read it back for actual supported rate
	*/
	adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE);

	pr_debug("set_adc_rate: set to %d Hz\n", adc_rate);

	/* some codec's don't allow unequal DAC and ADC rates, in which case
	 * writing one rate reg actually changes both.
	 */
	dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE);
	if (dac->num_channels > 2)
		wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, dac_rate);
	if (dac->num_channels > 4)
		wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, dac_rate);

	adc->sample_rate = adc_rate;
	dac->sample_rate = dac_rate;
}

/* stop the DAC before calling */
static void
set_dac_rate(struct au1550_state *s, unsigned rate)
{
	struct dmabuf  *dac = &s->dma_dac;
	struct dmabuf  *adc = &s->dma_adc;
	unsigned        adc_rate, dac_rate;
	u16             ac97_extstat;

	if (s->no_vra) {
		/* calc SRC factor
		*/
		dac->src_factor = ((96000 / rate) + 1) >> 1;
		dac->sample_rate = 48000 / dac->src_factor;
		return;
	}

	dac->src_factor = 1;

	ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS);

	rate = rate > 48000 ? 48000 : rate;

	/* enable VRA
	*/
	wrcodec(s->codec, AC97_EXTENDED_STATUS,
		ac97_extstat | AC97_EXTSTAT_VRA);

	/* now write the sample rate
	*/
	wrcodec(s->codec, AC97_PCM_FRONT_DAC_RATE, (u16) rate);

	/* I don't support different sample rates for multichannel,
	 * so make these channels the same.
	 */
	if (dac->num_channels > 2)
		wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, (u16) rate);
	if (dac->num_channels > 4)
		wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, (u16) rate);
	/* read it back for actual supported rate
	*/
	dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE);

	pr_debug("set_dac_rate: set to %d Hz\n", dac_rate);

	/* some codec's don't allow unequal DAC and ADC rates, in which case
	 * writing one rate reg actually changes both.
	 */
	adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE);

	dac->sample_rate = dac_rate;
	adc->sample_rate = adc_rate;
}

static void
stop_dac(struct au1550_state *s)
{
	struct dmabuf  *db = &s->dma_dac;
	u32		stat;
	unsigned long   flags;

	if (db->stopped)
		return;

	spin_lock_irqsave(&s->lock, flags);

	au_writel(PSC_AC97PCR_TP, PSC_AC97PCR);
	au_sync();

	/* Wait for Transmit Busy to show disabled.
	*/
	do {
		stat = au_readl(PSC_AC97STAT);
		au_sync();
	} while ((stat & PSC_AC97STAT_TB) != 0);

	au1xxx_dbdma_reset(db->dmanr);

	db->stopped = 1;

	spin_unlock_irqrestore(&s->lock, flags);
}

static void
stop_adc(struct au1550_state *s)
{
	struct dmabuf  *db = &s->dma_adc;
	unsigned long   flags;
	u32		stat;

	if (db->stopped)
		return;

	spin_lock_irqsave(&s->lock, flags);

	au_writel(PSC_AC97PCR_RP, PSC_AC97PCR);
	au_sync();

	/* Wait for Receive Busy to show disabled.
	*/
	do {
		stat = au_readl(PSC_AC97STAT);
		au_sync();
	} while ((stat & PSC_AC97STAT_RB) != 0);

	au1xxx_dbdma_reset(db->dmanr);

	db->stopped = 1;

	spin_unlock_irqrestore(&s->lock, flags);
}


static void
set_xmit_slots(int num_channels)
{
	u32	ac97_config, stat;

	ac97_config = au_readl(PSC_AC97CFG);
	au_sync();
	ac97_config &= ~(PSC_AC97CFG_TXSLOT_MASK | PSC_AC97CFG_DE_ENABLE);
	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	switch (num_channels) {
	case 6:		/* stereo with surround and center/LFE,
			 * slots 3,4,6,7,8,9
			 */
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(6);
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(9);

	case 4:		/* stereo with surround, slots 3,4,7,8 */
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(7);
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(8);

	case 2:		/* stereo, slots 3,4 */
	case 1:		/* mono */
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(3);
		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(4);
	}

	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	ac97_config |= PSC_AC97CFG_DE_ENABLE;
	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	/* Wait for Device ready.
	*/
	do {
		stat = au_readl(PSC_AC97STAT);
		au_sync();
	} while ((stat & PSC_AC97STAT_DR) == 0);
}

static void
set_recv_slots(int num_channels)
{
	u32	ac97_config, stat;

	ac97_config = au_readl(PSC_AC97CFG);
	au_sync();
	ac97_config &= ~(PSC_AC97CFG_RXSLOT_MASK | PSC_AC97CFG_DE_ENABLE);
	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	/* Always enable slots 3 and 4 (stereo). Slot 6 is
	 * optional Mic ADC, which we don't support yet.
	 */
	ac97_config |= PSC_AC97CFG_RXSLOT_ENA(3);
	ac97_config |= PSC_AC97CFG_RXSLOT_ENA(4);

	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	ac97_config |= PSC_AC97CFG_DE_ENABLE;
	au_writel(ac97_config, PSC_AC97CFG);
	au_sync();

	/* Wait for Device ready.
	*/
	do {
		stat = au_readl(PSC_AC97STAT);
		au_sync();
	} while ((stat & PSC_AC97STAT_DR) == 0);
}

/* Hold spinlock for both start_dac() and start_adc() calls */
static void
start_dac(struct au1550_state *s)
{
	struct dmabuf  *db = &s->dma_dac;

	if (!db->stopped)
		return;

	set_xmit_slots(db->num_channels);
	au_writel(PSC_AC97PCR_TC, PSC_AC97PCR);
	au_sync();
	au_writel(PSC_AC97PCR_TS, PSC_AC97PCR);
	au_sync();

	au1xxx_dbdma_start(db->dmanr);

	db->stopped = 0;
}

static void
start_adc(struct au1550_state *s)
{
	struct dmabuf  *db = &s->dma_adc;
	int	i;

	if (!db->stopped)
		return;

	/* Put two buffers on the ring to get things started.
	*/
	for (i=0; i<2; i++) {
		au1xxx_dbdma_put_dest(db->dmanr, virt_to_phys(db->nextIn),
				db->dma_fragsize, DDMA_FLAGS_IE);

		db->nextIn += db->dma_fragsize;
		if (db->nextIn >= db->rawbuf + db->dmasize)
			db->nextIn -= db->dmasize;
	}

	set_recv_slots(db->num_channels);
	au1xxx_dbdma_start(db->dmanr);
	au_writel(PSC_AC97PCR_RC, PSC_AC97PCR);
	au_sync();
	au_writel(PSC_AC97PCR_RS, PSC_AC97PCR);
	au_sync();

	db->stopped = 0;
}

static int
prog_dmabuf(struct au1550_state *s, struct dmabuf *db)
{
	unsigned user_bytes_per_sec;
	unsigned        bufs;
	unsigned        rate = db->sample_rate;

	if (!db->rawbuf) {
		db->ready = db->mapped = 0;
		db->buforder = 5;	/* 32 * PAGE_SIZE */
		db->rawbuf = kmalloc((PAGE_SIZE << db->buforder), GFP_KERNEL);
		if (!db->rawbuf)
			return -ENOMEM;
	}

	db->cnt_factor = 1;
	if (db->sample_size == 8)
		db->cnt_factor *= 2;
	if (db->num_channels == 1)
		db->cnt_factor *= 2;
	db->cnt_factor *= db->src_factor;

	db->count = 0;
	db->dma_qcount = 0;
	db->nextIn = db->nextOut = db->rawbuf;

	db->user_bytes_per_sample = (db->sample_size>>3) * db->num_channels;