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