Re: ARM Audio Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Praveen,

The only Linux WM8731 driver that I know of is non PXA and is used in
the Linux iPod project
(http://ipodlinux.sourceforge.net/techdetails.shtml). However, I've
attached a WM8753 PXA2xx driver (still work in progress) that uses both
I2S and SSP for audio data. This should hopefully give you a good
starting point in term of I2S for the 8731.

Liam  

/*
 * pxa-wm8753.c  --  Bulverde audio driver for WM8753
 *
 * Copyright 2003 Wolfson Microelectronics PLC.
 * Author: Liam Girdwood
 *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
 *
 *  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.
 *
 * Notes:
 *  The WM8753 is a low power, high quality stereo codec with integrated PCM
 *  codec designed for portable digital telephony applications.
 *  
 *  
 *  Features:
 *       - supports 16 bit mono voice DAC and left ADC in PCM mode
 *       - support VDAC sample rates of 8k, 12k, 16k, 24k, 48k.
 *       - support 16 bit stereo playback on HiFi DAC.
 *       - support HiFi DAC sample rates of 8k, 12k, 16k, 48k.
 *       - low power consumption 
 *       - support 8753 stand alone and 8753/9712 combi boards
 *       - workaround for bug#1190412 on Bulverde B0
 *
 *  TODO:
 *       - Test PM, IOCTL's, recording
 *       - TEST, TEST and more TESTING and then fix the bugs !
 *
 *  Bugs:
 *       - SSP2 RX is held high and not working
 *
 *  Revision history
 *    26th Aug 2003   Initial version.
 *     1st Dec 2003   Version 0.5. Added new sample rates and cleaned up.
 *    21st Jan 2004   Audio distortion workaround added, power settings
 *                    have been refined to minimize consumption.
 *     2nd Feb 2004   HiFi DAC added. 
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/i2c.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/proc_fs.h>
#include <linux/pm.h>

#include <asm/hardware.h>
#include <asm/arch/ssp-bvd.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
#include <asm/dma.h>
#include <asm/arch/mainstone.h>

#include "pxa-audio.h"
#include "wm8753.h"

#define AUDIO_NAME "pxa-wm8753"
#define WM8753_VERSION "0.8"

/*
 * HiFi DAC.
 * 
 * Set hifi to 1 to enable the FiFi DAC over the I2S link.
 */
MODULE_PARM(hifi,"i");
MODULE_PARM_DESC(hifi, "Enable the HiFi DAC.");
static int hifi = 0;

/* OSS interface to WM8753 */
#define WM8753_STEREO_MASK (SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_IGAIN)

#define WM8753_SUPPORTED_MASK (WM8753_STEREO_MASK | \
	SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_MIC)

#define WM8753_RECORD_MASK (SOUND_MASK_MIC | SOUND_MASK_IGAIN)


/* taken from ac97_codec.h */
/* original check is not good enough in case FOO is greater than
 * SOUND_MIXER_NRDEVICES because the supported_mixers has exactly
 * SOUND_MIXER_NRDEVICES elements.
 * before matching the given mixer against the bitmask in supported_mixers we
 * check if mixer number exceeds maximum allowed size which is as mentioned
 * above SOUND_MIXER_NRDEVICES */
#define supported_mixer(CODEC,FOO) ((FOO >= 0) && \
                                    (FOO < SOUND_MIXER_NRDEVICES) && \
                                    (CODEC)->supported_mixers & (1<<FOO) )

/* PLL divisors */
#define PLL_N 0x7
#define PLL_K 0x23F548

/* SSP Port settings 
* We need to put the SSP port into 17bit mode because of corruption in
* the MSB of the audio stream. The codec is in 16 bit mode and ignores 
* the corrupted bit 17. bug#1190412
*/
#define WM8753_SSP_MODE 	SSCRO_FRF(SSP_MODE_PSP) | SSCR0_DSS(0x0) | SSCR0_EDSS
#define WM8753_SSP_SETUP 	SSCR1_RFT(14) | SSCR1_TFT(1) | SSCR1_TRAIL |\
							SSCR1_TSRE | SSCR1_RSRE | SSCR1_TIE |\
							SSCR1_RIE | SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_RWOT
#define WM8753_SSP_PSP		SSPSP_SCMODE(3) | SSPSP_SFRMP
#define WM8753_SSP_8K		101
#define WM8753_SSP_12K		66
#define WM8753_SSP_16K		50
#define WM8753_SSP_24K		33
#define WM8753_SSP_32K		24
#define WM8753_SSP_48K		16
#define WM8753_SSP_96K		7

/* default ADC, HiFi and Voice DAC sample rates */
#define WM8753_DEFAULT_HSRATE	48000
#define WM8753_DEFAULT_VSRATE	WM8753_VD48K

/* I2S sample rates */
#define WM8753_I2S_8K		SADIV_513K
#define WM8753_I2S_12K		SADIV_702K
#define WM8753_I2S_16K		SADIV_1_026M
#define WM8753_I2S_22K		SADIV_1_405M
#define WM8753_I2S_44K		SADIV_2_836M
#define WM8753_I2S_48K		SADIV_3_058M

/*
 * Debug
 */
 
#define PFX AUDIO_NAME
#define WM8753_DEBUG 0

#ifdef WM8753_DEBUG
#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg)
#else
#define dbg(format, arg...) do {} while (0)
#endif
#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg)
#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg)
#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg)

/* 
 * WM8753 2 wire address is determined by GPIO5
 * state during powerup.
 *    low  = WM8753_2W_ADDR1
 *    high = WM8753_2W_ADDR2
 */
#define WM8753_2W_ADDR1	0x1a
#define WM8753_2W_ADDR2	0x1b

static unsigned short normal_i2c[] = { 
	WM8753_2W_ADDR1 ,I2C_CLIENT_END 
};

static unsigned short normal_i2c_range[] = { 
	I2C_CLIENT_END 
};

#define I2C_DRIVERID_WM8753 0xfefe /* liam need a proper ID */

/* Magic definition of all other i2c variables and things */
I2C_CLIENT_INSMOD;

struct wm8753_codec_t {
	char * name;
	int modcnt;
	int supported_mixers;
	int stereo_mixers;
	int record_sources;
	int codec_rate;
	unsigned int mixer_state[SOUND_MIXER_NRDEVICES];
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry * ps;
#endif
#ifdef CONFIG_PM
	struct pm_dev* pm;
#endif
	struct ssp_dev ssp;
	int is_ssp_open:1;
	struct i2c_client* wm8753_2w_client;
};

static struct wm8753_codec_t wm8753_codec = {
	name: AUDIO_NAME,
	modcnt: 0,
	codec_rate: WM8753_DEFAULT_HSRATE,
	supported_mixers:  WM8753_SUPPORTED_MASK,
	stereo_mixers: WM8753_STEREO_MASK,
	record_sources: WM8753_RECORD_MASK,
	is_ssp_open: 0,
};

/*
 * wm8753 register cache
 * We can't read the WM8753 register space when we 
 * are using 2 wire for device control, so we cache them instead. 
 */
static u16 reg_cache[64] = {
	0x0008, 0x0000, 0x000a, 0x000a,
	0x0003, 0x0000, 0x0007, 0x01ff,
	0x01ff, 0x000f, 0x000f, 0x007b,
	0x0000, 0x0032, 0x0000, 0x00c3,
	0x00c3, 0x00c0, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0055, 
	0x0005, 0x0050, 0x0055, 0x0050,
	0x0055, 0x0050, 0x0055, 0x0079,
	0x0079, 0x0079, 0x0079, 0x0079,
	0x0000, 0x0000, 0x0000, 0x0000, 
	0x0097, 0x0097, 0x0000, 0x0004,
	0x0000, 0x0083, 0x0024, 0x01ba,
	0x0000, 0x0083, 0x0024, 0x01ba
};

static int wm8753_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind);
static int wm8753_detach(struct i2c_client *client);
static int wm8753_probe(struct i2c_adapter *adap);
static int wm8753_reset(struct i2c_client *client);
static int wm8753_2w_write(struct i2c_client *client, u8 reg, u16 value);
static int wm8753_psp_open(u8 speed);
static int wm8753_i2s_open(int speed);
static void wm8753_i2s_close(void);
static int wm8753_set_adcdac_rate(int val);
static u16 wm8753_read_reg_cache(u8 reg);
static void wm8753_write_reg_cache(u8 reg, u16 value);
static int wm8753_init(void);
static int wm8753_init_pll(void);
static int wm8753_get_mixer(int cmd);
static int wm8753_set_mixer(int cmd, int val);
static int wm8753_power_up(void);
static int wm8753_power_down(void);;
#ifdef CONFIG_PM
static void wm8753_suspend(void);
static void wm8753_resume(void);
#endif
#ifdef CONFIG_PROC_FS
static int wm8753_read_proc (char *page, char **start, off_t off,
		    int count, int *eof, void *data);
#endif

/* WM8753 2 Wire layer */ 
static struct i2c_driver wm8753_driver = {
	name:           "i2c wm8753 driver",
	id:             I2C_DRIVERID_WM8753,
	flags:          I2C_DF_NOTIFY,
	attach_adapter: wm8753_probe,
	detach_client:  wm8753_detach,
	command:        NULL,
};

static struct i2c_client client_template = {
	name:   "WM8753",
	flags:  I2C_CLIENT_ALLOW_USE,
	driver: &wm8753_driver,
};
	
/*
 * Attach WM8753 2 wire client 
 */
static int wm8753_attach(struct i2c_adapter *adap, int addr,
		      unsigned short flags, int kind)
{	

    client_template.adapter = adap;
    client_template.addr = addr;
	
    if ((wm8753_codec.wm8753_2w_client = kmalloc(sizeof(struct i2c_client),GFP_KERNEL)) == NULL)
        return -ENOMEM;
	
    memcpy(wm8753_codec.wm8753_2w_client, &client_template, sizeof(struct i2c_client));
	wm8753_codec.wm8753_2w_client->data = (void*)&wm8753_codec;

	if (wm8753_reset(wm8753_codec.wm8753_2w_client) != 0) {
		kfree (wm8753_codec.wm8753_2w_client);
		err("cannot reset the WM8753");
		return -EIO;
	}
   
    return i2c_attach_client(wm8753_codec.wm8753_2w_client);
}

/*
 * Detach WM8753 2 wire client 
 */
static int wm8753_detach(struct i2c_client *client)
{
	i2c_detach_client(client);
	kfree(client);
	return 0;
}

/* 
 * Probe Bulverde I2C adapter for WM8753
 */
static int wm8753_probe(struct i2c_adapter *adap)
{
	if (adap->id == (I2C_ALGO_PXA)) {
		return i2c_probe(adap, &addr_data, wm8753_attach);
	}
	return 0;
}

/* 
 * reset the WM8753
 */
static int wm8753_reset(struct i2c_client *client)
{
	return wm8753_2w_write (client, WM8753_RESET, 0);
}

/*
 * write to the WM8753 register space
 */
static int wm8753_2w_write(struct i2c_client *client, u8 reg, u16 value)
{
	u8 data[2];
	
	/* data is 
	 *   D15..D9 WM8753 register offset
	 *   D8...D0 register data
	 */
	data[0] = (reg << 1) | ((value >> 8) & 0x0001);
	data[1] = value & 0x00ff;

	wm8753_write_reg_cache (reg, value);
	if (i2c_master_send(client, data, 2) == 2)
		return 0;
	else
		return -1;
}

/*
 * read wm8753 register cache
 */
static u16 wm8753_read_reg_cache(u8 reg)
{
	return reg_cache[reg - 1];
}

/*
 * write wm8753 register cache
 */
static void wm8753_write_reg_cache(u8 reg, u16 value)
{
	reg_cache[reg - 1] = value;
}


/*
 * WM8753 SSP Stuff
 */

/*
 * open Bulverde SSP port 2 in PSP slave mode
 * for streaming PCM data to the WM8753
 */
static int wm8753_psp_open (u8 speed)
{
	/* do we need to close existing connection ? */
	if (wm8753_codec.is_ssp_open)
		ssp_exit(&wm8753_codec.ssp);
	
	if ((ssp_init (&wm8753_codec.ssp, SSP_PORT2, WM8753_SSP_MODE,
					WM8753_SSP_SETUP, WM8753_SSP_PSP,
					SSCR0_SCR(speed))) == 0) {
		wm8753_codec.is_ssp_open = 1;
		return 0;
	}

	/* something went wrong */
	err("could not open SSP port 2");
	return -EIO;
}

/*
 * Open Bulverde I2S port in master mode
 * for streaming stereo 16bit data to WM8753
 */
static int wm8753_i2s_open(int speed)
{
	CKEN |= CKEN8_I2S;
	SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1) | SACR0_BCKD;
	SACR1 &= ~(SACR1_DRPL | SACR1_DREC | SACR1_AMSL);
	SAIMR |= SAIMR_RFS | SAIMR_TFS;
	SADIV = speed;
	SACR0 |= SACR0_ENB;
	return 0;
}

static void wm8753_i2s_close()
{
	SACR0 &= ~SACR0_ENB;
	CKEN &= ~CKEN8_I2S;
}


#ifdef CONFIG_PM
/* 
 * WM8753 Power Management
 */
static int wm8753_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data)
{
	switch(rqst) {
		case PM_SUSPEND:
			wm8753_suspend();
			break;
		case PM_RESUME:
			wm8753_resume();
			break;
	}
	return 0;
}

/*
 * Power down the codec
 */

/* PM NOT TESTED */
static void wm8753_suspend(void)
{
	/* powerdown everything, set Vmid to 500kOhm */
	if ((wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR1, 0x0100) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR2, 0x0000) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR3, 0x0000) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR4, 0x0000) != 0))
			err("could not powerdown WM8753");
}

/*
 * Power up the Codec
 */
static void wm8753_resume(void)
{
	/* powerup everything, set Vmid to 50kOhm */
	if (wm8753_power_up())
			err("could not powerup WM8753");
}

#endif

#ifdef CONFIG_PROC_FS
static int wm8753_read_proc (char *page, char **start, off_t off,
		    int count, int *eof, void *data)
{
	int len = 0, i;
	
	struct wm8753_codec_t* c;

	if ((c = data) == NULL)
		return -ENODEV;
	
	/* driver version */
	len = sprintf (page+len, "Wolfson WM8753 Version %s\n", WM8753_VERSION);
	
	/* reg dump */
	for (i = 1; i<64; i++) 
		len = sprintf (page+len, "0x%2.2x %3x\n", i, wm8753_read_reg_cache(i));
	
	return len;
}
#endif

/*
 * WM8753 Mixer stuff
 */

static struct wm8753_mixer_t wm8753_mixer;
/* ** NOT TESTED ** */
static int wm8753_mixer_ioctl( struct inode *inode, struct file *file,
			unsigned int cmd, unsigned long arg)
{
	int i, val = 0;

	if (cmd == SOUND_MIXER_INFO) {
		mixer_info info;
		strncpy(info.id, wm8753_codec.name, sizeof(info.id));
		strncpy(info.name, wm8753_codec.name, sizeof(info.name));
		info.modify_counter = wm8753_codec.modcnt;
		if (copy_to_user((void *)arg, &info, sizeof(info)))
			return -EFAULT;
		return 0;
	}
	if (cmd == SOUND_OLD_MIXER_INFO) {
		_old_mixer_info info;
		strncpy(info.id, wm8753_codec.name, sizeof(info.id));
		strncpy(info.name, wm8753_codec.name, sizeof(info.name));
		if (copy_to_user((void *)arg, &info, sizeof(info)))
			return -EFAULT;
		return 0;
	}

	if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int))
		return -EINVAL;

	if (cmd == OSS_GETVERSION)
		return put_user(SOUND_VERSION, (int *)arg);

	if (_SIOC_DIR(cmd) == _SIOC_READ) {
		switch (_IOC_NR(cmd)) {

			case SOUND_MIXER_DEVMASK: /* give them the supported mixers */
				val = wm8753_codec.supported_mixers;
				break;
	
			case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */
				val = wm8753_codec.record_sources;
				break;
	
			case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */
				val = wm8753_codec.stereo_mixers;
				break;

			case SOUND_MIXER_CAPS:
				val = SOUND_CAP_EXCL_INPUT;
				break;
	
			default: /* read a specific mixer */
				i = _IOC_NR(cmd);
	
				if (!supported_mixer(&wm8753_codec, i)) 
					return -EINVAL;
				val = wm8753_get_mixer(i);
				break;
			}
			return put_user(val, (int *)arg);
		}

	if (_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) {
		wm8753_codec.modcnt++;
		if (get_user(val, (int *)arg))
			return -EFAULT;

		switch (_IOC_NR(cmd)) {

		default: /* write a specific mixer */
			i = _IOC_NR(cmd);

			if (!supported_mixer(&wm8753_codec, i)) 
				return -EINVAL;

			return wm8753_set_mixer(i, val);
		}
	}
	return -EINVAL;
}
	
/* ** NOT TESTED ** */		
static int wm8753_get_mixer(int cmd)
{		
	int val = 0;
	u16 r = 0,l = 0;
	
	switch (cmd) {
		case SOUND_MIXER_VOLUME: /* OUT1 Volume */
			l = wm8753_read_reg_cache(WM8753_LOUT1V) & 0x7f;
			r = wm8753_read_reg_cache(WM8753_ROUT1V) & 0x7f;
			break;		
		case SOUND_MIXER_BASS:	/* bass */
			l = wm8753_read_reg_cache(WM8753_BASS) & 0x0f;
			break;
		case SOUND_MIXER_TREBLE:	/* treble */
			l = wm8753_read_reg_cache(WM8753_TREBLE) & 0x0f;
			break;
		case SOUND_MIXER_SYNTH:
			break;			
		case SOUND_MIXER_PCM:
			break;			
		case SOUND_MIXER_SPEAKER: /* OUT2 Volume */
			l = wm8753_read_reg_cache(WM8753_LOUT2V) & 0x7f;
			r = wm8753_read_reg_cache(WM8753_ROUT2V) & 0x7f;
			break;
		case SOUND_MIXER_LINE:	
			break;
		case SOUND_MIXER_MIC:
			l = wm8753_read_reg_cache(WM8753_INCTL1) & 0x060;
			r = wm8753_read_reg_cache(WM8753_INCTL1) & 0x180;
			break;			
		case SOUND_MIXER_CD:	
			break;			
		case SOUND_MIXER_IMIX:		/* Recording monitor */
			break;
		case SOUND_MIXER_ALTPCM:
			break;			
		case SOUND_MIXER_RECLEV:	/* Recording level */
			break;
		case SOUND_MIXER_IGAIN:		/* Input gain */
			l = wm8753_read_reg_cache(WM8753_LADC) & 0xff;
			r = wm8753_read_reg_cache(WM8753_RADC) & 0xff;
			break;
		case SOUND_MIXER_OGAIN:		/* Output gain */	
			l = wm8753_read_reg_cache(WM8753_LDAC) & 0xff;
			r = wm8753_read_reg_cache(WM8753_RDAC) & 0xff;			
			break;
		default:
			warn("unknown mixer IOCTL");
			return -EINVAL;
	}
	
	/* mix left and right */
	val = ((l << 8) & 0xff00) + (r & 0xff); 
	return val;
}

/* ** NOT TESTED ** */
static int wm8753_set_mixer(int cmd, int val)
{		
	int ret = 0;
	u16 reg;
	unsigned int left,right;

	/* separate left and right settings */
	right = ((val >> 8)  & 0xff) ;
	left = (val  & 0xff) ;
	if (right > 100) 
		right = 100;
	if (left > 100) 
		left = 100;
	
	switch (cmd) {
		case SOUND_MIXER_VOLUME: /* volume OUT1 */
			reg = wm8753_read_reg_cache(WM8753_LOUT1V) & 0x180;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LOUT1V, reg | left);
			reg = wm8753_read_reg_cache(WM8753_ROUT1V) & 0x180;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ROUT1V, reg | right);
			break;		
		case SOUND_MIXER_BASS:	/* bass */
			reg = wm8753_read_reg_cache(WM8753_BASS) & 0x1f0;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_BASS, reg | (left & 0x0f));
			break;
		case SOUND_MIXER_TREBLE:	/* treble */
			reg = wm8753_read_reg_cache(WM8753_TREBLE) & 0x1f0;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_TREBLE, reg | (left & 0x0f));
			break;
		case SOUND_MIXER_SYNTH:
			break;			
		case SOUND_MIXER_PCM:
			break;			
		case SOUND_MIXER_SPEAKER: /* volume OUT2 */
			reg = wm8753_read_reg_cache(WM8753_LOUT2V) & 0x180;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LOUT2V, reg | left);
			reg = wm8753_read_reg_cache(WM8753_ROUT1V) & 0x180;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ROUT2V, reg | right);
			break;
		case SOUND_MIXER_LINE:	
			break;
		case SOUND_MIXER_MIC:
			break;			
		case SOUND_MIXER_CD:	
			break;			
		case SOUND_MIXER_IMIX:		/*  Recording monitor  */
			break;
		case SOUND_MIXER_ALTPCM:
			break;			
		case SOUND_MIXER_RECLEV:	/* Recording level */
			break;
		case SOUND_MIXER_IGAIN:		/* Input gain LR ADC */
			reg = wm8753_read_reg_cache(WM8753_LADC) & 0x100;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LADC, reg | left);
			reg = wm8753_read_reg_cache(WM8753_RADC) & 0x100;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_RADC, reg | right);
			break;
		case SOUND_MIXER_OGAIN:		/* Output gain LR DAC*/	
			reg = wm8753_read_reg_cache(WM8753_LDAC) & 0x100;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LDAC, reg | left);
			reg = wm8753_read_reg_cache(WM8753_RDAC) & 0x100;
			wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_RDAC, reg | right);		
			break;
		default:
			warn("unknown mixer IOCTL");
			ret = -EINVAL;
			break;
		}
	return ret;
}


static struct file_operations wm8753_mixer_fops = {
	ioctl:		wm8753_mixer_ioctl,
	llseek:		no_llseek,
	owner:		THIS_MODULE
};

/*
 * WM8753 DSP codec stuff
 */

/*
 * Set WM8753 ADC and DAC sample rates and
 * enable SSP port 2 at the correct speed.
 * 
 * returns sample rate on success, else error.
 */
static int wm8753_set_adcdac_rate(int val)
{
	int ret = val;
	u16 clock;
	
	clock = wm8753_read_reg_cache(WM8753_CLOCK) & 0x3f;
	
	switch (val) {
		case 8000: /* ADC/DAC 8kHz */
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE1, WM8753_A8D8) != 0)
				ret = -EIO;
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_VD8K | clock) !=0)
				ret = -EIO;
			if (wm8753_psp_open(WM8753_SSP_8K) != 0)
				ret = -EIO;
			if (hifi)
				wm8753_i2s_open(WM8753_I2S_8K);
			break;
		case 12000:	 /* ADC/DAC 12kHz */
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE1, WM8753_A12D12) != 0)
				ret = -EIO;
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_VD12K | clock) !=0)
				ret = -EIO;
			if (wm8753_psp_open(WM8753_SSP_12K) != 0)
				ret = -EIO;
			if (hifi)
				wm8753_i2s_open(WM8753_I2S_12K);
			break;
		case 16000:	 /* ADC/DAC 16kHz */
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE1, WM8753_A16D16) != 0)
				ret = -EIO;
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_VD16K | clock) !=0)
				ret = -EIO;
			if (wm8753_psp_open(WM8753_SSP_16K) != 0)
				ret = -EIO;
			if (hifi)
				wm8753_i2s_open(WM8753_I2S_16K);
			break;
		case 24000:	 /* ADC/DAC 24kHz - Voice DAC only*/
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE1, WM8753_A24D24) != 0)
				ret = -EIO;
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_VD24K | clock) !=0)
				ret = -EIO;
			if (wm8753_psp_open(WM8753_SSP_24K) != 0)
				ret = -EIO;
			break;		
		case 48000:	 /* ADC/DAC 48kHz */
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE1, WM8753_A48D48) != 0)
				ret = -EIO;
			if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_VD48K | clock) !=0)
				ret = -EIO;
			if (wm8753_psp_open(WM8753_SSP_48K) != 0)
				ret = -EIO;
			if (hifi)
				wm8753_i2s_open(WM8753_I2S_48K);
			break;
		default:
			/* desired rate is not permitted */
			ret = -EPERM;
			break;
	}	
	
	if (ret != -EIO)
		ssp_enable(&wm8753_codec.ssp);
		
	return ret;
}


/*
 * WM8753 DSP ioctls  ** NOT TESTED **
 */
static int wm8753_pcm_ioctl(struct inode *inode, struct file *file,
		      unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	long val = 0;

	switch(cmd) {
	case SNDCTL_DSP_STEREO:
		ret = get_user(val, (int *) arg);
		if (ret)
			return ret;
		ret = (val == 0) ? -EINVAL : 1;
		return put_user(ret, (int *) arg);

	case SNDCTL_DSP_CHANNELS:
	case SOUND_PCM_READ_CHANNELS:
		return put_user(1, (long *) arg);

	case SNDCTL_DSP_SPEED:
		ret = get_user(val, (long *) arg);
		if (ret)
			return ret;
		if ((file->f_mode & FMODE_READ) || (file->f_mode & FMODE_WRITE)) {
			if ((ret = wm8753_set_adcdac_rate(val)) == val)
				wm8753_codec.codec_rate = val;
			else
				return ret;
		}
		/* fall through */

	case SOUND_PCM_READ_RATE:
		if ((file->f_mode & FMODE_READ) || (file->f_mode & FMODE_WRITE))
			val = wm8753_codec.codec_rate;
		return put_user(val, (long *) arg);

	case SNDCTL_DSP_SETFMT:
	case SNDCTL_DSP_GETFMTS:
		/* FIXME: can we do other fmts? */
		return put_user(AFMT_S16_LE, (long *) arg);
	default:
		/* Maybe this is meant for the mixer (As per OSS Docs) */
		return wm8753_mixer_ioctl(inode, file, cmd, arg);
	}
	return 0;
}

/*
 * WM8753 SSP Audio stuff
 */

static audio_stream_t wm8753_audio_out = {
	name:			"WM8753 audio out",
	dcmd:			(DCMD_INCSRCADDR|DCMD_FLOWTRG|DCMD_BURST16|DCMD_WIDTH2),
	drcmr:			&DRCMRTXSS2DR,
	dev_addr:		__PREG(SSDR_(SSP_PORT2)),
};

static audio_stream_t wm8753_audio_in = {
	name:			"WM8753 audio in",
	dcmd:			(DCMD_INCTRGADDR|DCMD_FLOWSRC|DCMD_BURST16|DCMD_WIDTH2),
	drcmr:			&DRCMRRXSS2DR,
	dev_addr:		__PREG(SSDR_(SSP_PORT2)),
};

static audio_state_t wm8753_pcm = {
	output_stream:		&wm8753_audio_out,
	input_stream:		&wm8753_audio_in,
	client_ioctl:		wm8753_pcm_ioctl,
	sem:			__MUTEX_INITIALIZER(wm8753_pcm.sem),
};

static audio_stream_t wm8753_hifi_audio_out = {
	name:			"WM8753 hifi audio out",
	dcmd:			(DCMD_INCSRCADDR|DCMD_FLOWTRG|DCMD_BURST32|DCMD_WIDTH4),
	drcmr:			&DRCMRTXSADR,
	dev_addr:		__PREG(SADR),
};

static audio_stream_t wm8753_hifi_audio_in = {
	name:			"WM8753 hifi audio in",
	dcmd:			(DCMD_INCTRGADDR|DCMD_FLOWSRC|DCMD_BURST32|DCMD_WIDTH4),
	drcmr:			&DRCMRRXSADR,
	dev_addr:		__PREG(SADR),
};

static audio_state_t wm8753_hifi = {
	output_stream:		&wm8753_hifi_audio_out,
	input_stream:		&wm8753_hifi_audio_in,
	client_ioctl:		wm8753_pcm_ioctl,
	sem:			__MUTEX_INITIALIZER(wm8753_hifi.sem),
};

/*
 * open the WM8753 pcm audio driver
 */
static int wm8753_pcm_open(struct inode *inode, struct file *file)
{
	return pxa_audio_attach(inode, file, &wm8753_pcm);
}

/*
 * open the WM8753 hifi audio driver
 */
static int wm8753_hifi_open(struct inode *inode, struct file *file)
{
	return pxa_audio_attach(inode, file, &wm8753_hifi);
}

/*
 * Missing fields of this structure will be patched with the call
 * to pxa_audio_attach().
 */

static struct file_operations wm8753_pcm_fops = {
	open:		wm8753_pcm_open,
	owner:		THIS_MODULE
};

static struct file_operations wm8753_hifi_fops = {
	open:		wm8753_hifi_open,
	owner:		THIS_MODULE
};

/*
 * set the PLL2 to generate 12.288MHz and then div by 6 to generate
 * DAC/ADC clocks from 13MHz MCLK 
 */
static int wm8753_init_pll(void)
{
	u16 value;
	
	if (hifi) {
		/* set up N and K PLL divisor ratios */
		/* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
		value = (PLL_N << 5) + ((PLL_K & 0x3c0000) >> 18);	
		if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL1CTL2, value) != 0)
			return -EIO;
		
		/* bits 8:0 = PLL_K[17:9] */
		value = (PLL_K & 0x03fe00) >> 9;
		if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL1CTL3, value) != 0)
			return -EIO;
		
		/* bits 8:0 = PLL_K[8:0] */
		value = PLL_K & 0x0001ff;
		if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL1CTL4, value) != 0)
			return -EIO;
		
		/* set PLL1 as input and enable */
		if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL1CTL1, 0x0027) != 0)
			return -EIO;
	}
	
	/* set up N and K PLL divisor ratios */
	/* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
	value = (PLL_N << 5) + ((PLL_K & 0x3c0000) >> 18);	
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL2CTL2, value) != 0)
		return -EIO;
	
	/* bits 8:0 = PLL_K[17:9] */
	value = (PLL_K & 0x03fe00) >> 9;
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL2CTL3, value) != 0)
		return -EIO;
	
	/* bits 8:0 = PLL_K[8:0] */
	value = PLL_K & 0x0001ff;
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL2CTL4, value) != 0)
		return -EIO;
	
	/* set PLL2 as input and enable */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PLL2CTL1, 0x0027) != 0)
		return -EIO;
	
	/* now enable the clock */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_CLOCK, WM8753_DEFAULT_VSRATE | 0x18) != 0)
		return -EIO;
	return 0;
}

/*
 * Power up the codec
 */
static int wm8753_power_up(void)
{	
	u16 pwr = 0xf0;
	
	if (hifi)
		pwr |= 0xc;
	
	/* set Vmid to 5kOhm, enable VREF and VDAC */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR1, 0x0100 | pwr) != 0)
		return -EIO;
	
	/* enable MIC1 preamp, ALC mix and left ADC */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR2, 0x0168) != 0)	
		return -EIO;
	
	/* enable LOUT 1, ROUT1, MONO1 */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR3, 0x0184) != 0)
		return -EIO;
	
	/* enable left, mono mixer */
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR4, 0x000b) != 0)
		return -EIO;
	
	/* let WM8753 powerup and then set Vmid to 50kOhm */
	mdelay(1);
	if (wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR1, pwr) != 0)
		return -EIO;
	
	return 0;
}

static int wm8753_power_down()
{
	if ((wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR1, 0x0100) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR2, 0x0000) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR3, 0x0000) != 0) &&
		(wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PWR4, 0x0000) != 0)) {
			err("could not powerdown WM8753");
			return -EIO;
		}
	return 0;
}


/* 
 * initiliase the WM8753
 */
static int wm8753_init(void)
{
	int ret = 0;

	/* power up the device */
	if ((ret = wm8753_power_up()) != 0) {
		ret = -EIO;
		goto out;
	}
	
	/* start adc/dac clocks */
	if ((ret = wm8753_init_pll()) != 0) {
		err("could not initialise PLL's");
		ret = -EIO;
		goto out;
	}
	
	/* set up GPIO and alternate functions for SSP2/I2S */
	set_GPIO_mode(GPIO13_SSP2TX_MD);
	set_GPIO_mode(GPIO22_SSP2CLK_MD);
	set_GPIO_mode(GPIO86_SSP2RX_MD); // not sure of correct GPIO for SSP2 RX
	set_GPIO_mode(GPIO88_SSP2FRM_MD);
	
	if (hifi) {
		set_GPIO_mode(GPIO29_SDATA_IN_I2S_MD);
		set_GPIO_mode(28 | GPIO_ALT_FN_1_OUT);
		set_GPIO_mode(GPIO30_SDATA_OUT_I2S_MD);
		set_GPIO_mode(GPIO31_SYNC_I2S_MD);
	}
	
	/* set up default ADC/DAC sample rate */
	if ((ret = wm8753_set_adcdac_rate(WM8753_DEFAULT_HSRATE)) != WM8753_DEFAULT_HSRATE) {
		err("could not set WM8753 sample rate to %d", WM8753_DEFAULT_HSRATE);
		ret = -EIO;
		goto out;
	}
	
	/* set up ADC mode */
	ret = wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ADC, 0x001c);
		
	/* set up sample rate mode 87*/ 
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_SRATE2, 0x0097);
	
	/* set up PCM digital interface format and master mode */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_PCM, 0x0063);
	

	/* set up PCM digital interface as output */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_IOCTL, 0x0003);
	
	
	/* set up default input audio paths, enable MIC1 */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_INCTL2, 0x0002);
	
	
	/* set up analog mono mix using LADC  */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ADCIN, 0x0010);
	
	
	/* set up left input PGA */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LINVOL, 0x010c);
	
	
	/* set up default output audio paths, enable left out 2 */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LOUTM2, 0x0103);
	
	
	/* set up default output audio paths, enable mono 1 */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_MOUTM2, 0x005a);
	
	/* hifi stuff */
	if (hifi == 0) 
		goto out;
	
	/* enable hifi DAC */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_HIFI, 0x0002);
	
	/* enable Left mixer */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_LOUTM1, 0x0125);
	
	/* enable right mixer */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ROUTM1, 0x0125);
	
	/* set up default output audio paths, enable right out 2 */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_ROUTM2, 0x0103);
	
	/* unmute hifi DAC */
	ret += wm8753_2w_write(wm8753_codec.wm8753_2w_client, WM8753_DAC, 0x0);
	
out:	
	return ret != 0 ? -EIO : 0;
}

/*
 * initialise the WM8753 driver
 * register the mixer and dsp interfaces with the kernel 
 */
static int __init pxa_wm8753_init(void)
{
	int ret;
	char proc_str[64];
	
	info("Wolfson WM8753 Audio Codec");
	info("Version %s  liam.girdwood@wolfsonmicro.com", WM8753_VERSION);
	
	/* get Bulverde I2C adapter */
	if ((ret = i2c_add_driver(&wm8753_driver)) != 0) {
		err("can't add i2c driver");
		goto out;
	}
	
	/* initialise WM8753 */
	if ((ret = wm8753_init()) != 0) {
		err("can't initialise WM8753");
		goto out;
	}
	
	/* register audio interfaces with kernel */
	wm8753_pcm.dev_dsp = register_sound_dsp(&wm8753_pcm_fops, -1);
	wm8753_mixer.dev_mixer = register_sound_mixer(&wm8753_mixer_fops, -1);
	if (hifi)
		wm8753_hifi.dev_dsp = register_sound_dsp(&wm8753_hifi_fops, -1);
	
#ifdef CONFIG_PROC_FS
	/* register proc interface */
	sprintf(proc_str, "driver/%s", AUDIO_NAME);
	if ((wm8753_codec.ps = create_proc_read_entry (proc_str, 0, NULL,
					     wm8753_read_proc, &wm8753_codec)) == 0)
		err("could not register proc interface /proc/%s", proc_str);
#endif
#ifdef CONFIG_PM
	/* register with power manager */
	if ((wm8753_codec.pm = pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, wm8753_pm_event)) == 0)
		err("could not register with power management");
#endif
#if defined (CONFIG_ARCH_MAINSTONE)
	/* enable USB on the go MUX so we can use SSPFRM2 */
	MST_MSCWR2 |= (1 << 5);
	MST_MSCWR2 &= ~(1 << 6);
	
	/* turn on board amplifier */
	MST_MSCWR2 &= ~(1 << 2);
#endif	
	return 0;
	
out:
	return ret;
}

/* 
 * unregister interfaces and clean up
 */
static void __exit pxa_wm8753_exit(void)
{
#if defined (CONFIG_ARCH_MAINSTONE)	
	/* disable USB on the go MUX so we can use ttyS0 */
	MST_MSCWR2 &= ~(1 << 5);
	MST_MSCWR2 |= (1 << 6);
	
	/* turn off amplifier */
	MST_MSCWR2 |= (1 << 2);
#endif	
#ifdef CONFIG_PM
	pm_unregister (wm8753_codec.pm);
#endif
	wm8753_power_down();
	unregister_sound_dsp(wm8753_pcm.dev_dsp);
	unregister_sound_mixer(wm8753_mixer.dev_mixer);
	if (hifi) {
		unregister_sound_dsp(wm8753_hifi.dev_dsp);
		wm8753_i2s_close();
	}
	wm8753_detach(wm8753_codec.wm8753_2w_client);
	i2c_del_driver(&wm8753_driver);
	ssp_exit(&wm8753_codec.ssp);
}

module_init(pxa_wm8753_init);
module_exit(pxa_wm8753_exit);

MODULE_DESCRIPTION("Bulverde WM8753 driver");
MODULE_AUTHOR("Liam Girdwood");
MODULE_LICENSE("GPL");
/*
 * wm8753.h  --  audio driver for WM8753
 *
 * Copyright 2003 Wolfson Microelectronics PLC.
 * Author: Liam Girdwood
 *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
 *
 *  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.
 *
 */

#ifndef _WM8753_H
#define _WM8753_H

/* WM8753 register space */

#define WM8753_DAC		0x01
#define WM8753_ADC		0x02
#define WM8753_PCM		0x03
#define WM8753_HIFI		0x04
#define WM8753_IOCTL	0x05
#define WM8753_SRATE1	0x06
#define WM8753_SRATE2	0x07
#define WM8753_LDAC		0x08
#define WM8753_RDAC		0x09
#define WM8753_BASS		0x0a
#define WM8753_TREBLE	0x0b
#define WM8753_ALC1		0x0c
#define WM8753_ALC2		0x0d
#define WM8753_ALC3		0x0e
#define WM8753_NGATE	0x0f
#define WM8753_LADC		0x10
#define WM8753_RADC		0x11
#define WM8753_ADCTL1	0x12
#define WM8753_3D		0x13
#define WM8753_PWR1		0x14
#define WM8753_PWR2		0x15
#define WM8753_PWR3		0x16
#define WM8753_PWR4		0x17
#define WM8753_ID		0x18
#define WM8753_INTPOL	0x19
#define WM8753_INTEN	0x1a
#define WM8753_GPIO1	0x1b
#define WM8753_GPIO2	0x1c
#define WM8753_RESET	0x1f
#define WM8753_RECMIX1	0x20
#define WM8753_RECMIX2	0x21
#define WM8753_LOUTM1	0x22
#define WM8753_LOUTM2	0x23
#define WM8753_ROUTM1	0x24
#define WM8753_ROUTM2	0x25
#define WM8753_MOUTM1	0x26
#define WM8753_MOUTM2	0x27
#define WM8753_LOUT1V	0x28
#define WM8753_ROUT1V	0x29
#define WM8753_LOUT2V	0x2a
#define WM8753_ROUT2V	0x2b
#define WM8753_MOUTV	0x2c
#define WM8753_OUTCTL	0x2d
#define WM8753_ADCIN	0x2e
#define WM8753_INCTL1	0x2f
#define WM8753_INCTL2	0x30
#define WM8753_LINVOL	0x31
#define WM8753_RINVOL	0x32
#define WM8753_MICBIAS	0x33
#define WM8753_CLOCK	0x34
#define WM8753_PLL1CTL1	0x35
#define WM8753_PLL1CTL2	0x36
#define WM8753_PLL1CTL3	0x37
#define WM8753_PLL1CTL4	0x38
#define WM8753_PLL2CTL1	0x39
#define WM8753_PLL2CTL2	0x3a
#define WM8753_PLL2CTL3	0x3b
#define WM8753_PLL2CTL4	0x3c
#define WM8753_BIASCTL	0x3d
#define WM8753_ADCTL2	0x3f

/* Voice DAC sample rates */
#define WM8753_VD8K		(0x6 << 6)
#define WM8753_VD12K	(0x5 << 6)
#define WM8753_VD16K	(0x2 << 6)
#define WM8753_VD24K	(0x4 << 6)
#define WM8753_VD48K	(0x0 << 6)


/* Hifi ADC/DAC sample rates */
#define WM8753_A8D8		0x0006
#define WM8753_A8D48	0x0004
#define WM8753_A12D12	0x0008
#define WM8753_A16D16	0x000a
#define WM8753_A24D24	0x001c
#define WM8753_A32D32	0x000c
#define WM8753_A48D8	0x0002
#define WM8753_A48D48	0x0000
#define WM8753_A96D96	0x000e

struct wm8753_mixer_t {
	int dev_mixer;
};

#endif
-------------------------------------------------------------------
Subscription options: http://lists.arm.linux.org.uk/mailman/listinfo/linux-arm-kernel
FAQ:       http://www.arm.linux.org.uk/armlinux/mlfaq.php
Etiquette: http://www.arm.linux.org.uk/armlinux/mletiquette.php

[Index of Archives]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [CentOS ARM]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]     [Photos]