1862c2c0aSThomas Bogendoerfer /* 2862c2c0aSThomas Bogendoerfer * Sound driver for Silicon Graphics O2 Workstations A/V board audio. 3862c2c0aSThomas Bogendoerfer * 4862c2c0aSThomas Bogendoerfer * Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org> 5862c2c0aSThomas Bogendoerfer * Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de> 6862c2c0aSThomas Bogendoerfer * Mxier part taken from mace_audio.c: 7862c2c0aSThomas Bogendoerfer * Copyright 2007 Thorben Jändling <tj.trevelyan@gmail.com> 8862c2c0aSThomas Bogendoerfer * 9862c2c0aSThomas Bogendoerfer * This program is free software; you can redistribute it and/or modify 10862c2c0aSThomas Bogendoerfer * it under the terms of the GNU General Public License as published by 11862c2c0aSThomas Bogendoerfer * the Free Software Foundation; either version 2 of the License, or 12862c2c0aSThomas Bogendoerfer * (at your option) any later version. 13862c2c0aSThomas Bogendoerfer * 14862c2c0aSThomas Bogendoerfer * This program is distributed in the hope that it will be useful, 15862c2c0aSThomas Bogendoerfer * but WITHOUT ANY WARRANTY; without even the implied warranty of 16862c2c0aSThomas Bogendoerfer * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17862c2c0aSThomas Bogendoerfer * GNU General Public License for more details. 18862c2c0aSThomas Bogendoerfer * 19862c2c0aSThomas Bogendoerfer * You should have received a copy of the GNU General Public License 20862c2c0aSThomas Bogendoerfer * along with this program; if not, write to the Free Software 21862c2c0aSThomas Bogendoerfer * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22862c2c0aSThomas Bogendoerfer * 23862c2c0aSThomas Bogendoerfer */ 24862c2c0aSThomas Bogendoerfer 25862c2c0aSThomas Bogendoerfer #include <linux/init.h> 26862c2c0aSThomas Bogendoerfer #include <linux/delay.h> 27862c2c0aSThomas Bogendoerfer #include <linux/spinlock.h> 28862c2c0aSThomas Bogendoerfer #include <linux/interrupt.h> 29862c2c0aSThomas Bogendoerfer #include <linux/dma-mapping.h> 30862c2c0aSThomas Bogendoerfer #include <linux/platform_device.h> 31862c2c0aSThomas Bogendoerfer #include <linux/io.h> 325a0e3ad6STejun Heo #include <linux/slab.h> 33da155d5bSPaul Gortmaker #include <linux/module.h> 34862c2c0aSThomas Bogendoerfer 35862c2c0aSThomas Bogendoerfer #include <asm/ip32/ip32_ints.h> 36862c2c0aSThomas Bogendoerfer #include <asm/ip32/mace.h> 37862c2c0aSThomas Bogendoerfer 38862c2c0aSThomas Bogendoerfer #include <sound/core.h> 39862c2c0aSThomas Bogendoerfer #include <sound/control.h> 40862c2c0aSThomas Bogendoerfer #include <sound/pcm.h> 41862c2c0aSThomas Bogendoerfer #define SNDRV_GET_ID 42862c2c0aSThomas Bogendoerfer #include <sound/initval.h> 43862c2c0aSThomas Bogendoerfer #include <sound/ad1843.h> 44862c2c0aSThomas Bogendoerfer 45862c2c0aSThomas Bogendoerfer 46862c2c0aSThomas Bogendoerfer MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org>"); 47862c2c0aSThomas Bogendoerfer MODULE_DESCRIPTION("SGI O2 Audio"); 48862c2c0aSThomas Bogendoerfer MODULE_LICENSE("GPL"); 49862c2c0aSThomas Bogendoerfer MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 Audio}}"); 50862c2c0aSThomas Bogendoerfer 51862c2c0aSThomas Bogendoerfer static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ 52862c2c0aSThomas Bogendoerfer static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ 53862c2c0aSThomas Bogendoerfer 54862c2c0aSThomas Bogendoerfer module_param(index, int, 0444); 55862c2c0aSThomas Bogendoerfer MODULE_PARM_DESC(index, "Index value for SGI O2 soundcard."); 56862c2c0aSThomas Bogendoerfer module_param(id, charp, 0444); 57862c2c0aSThomas Bogendoerfer MODULE_PARM_DESC(id, "ID string for SGI O2 soundcard."); 58862c2c0aSThomas Bogendoerfer 59862c2c0aSThomas Bogendoerfer 60862c2c0aSThomas Bogendoerfer #define AUDIO_CONTROL_RESET BIT(0) /* 1: reset audio interface */ 61862c2c0aSThomas Bogendoerfer #define AUDIO_CONTROL_CODEC_PRESENT BIT(1) /* 1: codec detected */ 62862c2c0aSThomas Bogendoerfer 63862c2c0aSThomas Bogendoerfer #define CODEC_CONTROL_WORD_SHIFT 0 64862c2c0aSThomas Bogendoerfer #define CODEC_CONTROL_READ BIT(16) 65862c2c0aSThomas Bogendoerfer #define CODEC_CONTROL_ADDRESS_SHIFT 17 66862c2c0aSThomas Bogendoerfer 67862c2c0aSThomas Bogendoerfer #define CHANNEL_CONTROL_RESET BIT(10) /* 1: reset channel */ 68862c2c0aSThomas Bogendoerfer #define CHANNEL_DMA_ENABLE BIT(9) /* 1: enable DMA transfer */ 69862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_DISABLED (0 << 5) /* interrupt disabled */ 70862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_25 (1 << 5) /* int on buffer >25% full */ 71862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_50 (2 << 5) /* int on buffer >50% full */ 72862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_75 (3 << 5) /* int on buffer >75% full */ 73862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_EMPTY (4 << 5) /* int on buffer empty */ 74862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */ 75862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_FULL (6 << 5) /* int on buffer empty */ 76862c2c0aSThomas Bogendoerfer #define CHANNEL_INT_THRESHOLD_NOT_FULL (7 << 5) /* int on buffer !empty */ 77862c2c0aSThomas Bogendoerfer 78862c2c0aSThomas Bogendoerfer #define CHANNEL_RING_SHIFT 12 79862c2c0aSThomas Bogendoerfer #define CHANNEL_RING_SIZE (1 << CHANNEL_RING_SHIFT) 80862c2c0aSThomas Bogendoerfer #define CHANNEL_RING_MASK (CHANNEL_RING_SIZE - 1) 81862c2c0aSThomas Bogendoerfer 82862c2c0aSThomas Bogendoerfer #define CHANNEL_LEFT_SHIFT 40 83862c2c0aSThomas Bogendoerfer #define CHANNEL_RIGHT_SHIFT 8 84862c2c0aSThomas Bogendoerfer 85862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan { 86862c2c0aSThomas Bogendoerfer int idx; 87862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 88862c2c0aSThomas Bogendoerfer int pos; 89862c2c0aSThomas Bogendoerfer snd_pcm_uframes_t size; 90862c2c0aSThomas Bogendoerfer spinlock_t lock; 91862c2c0aSThomas Bogendoerfer }; 92862c2c0aSThomas Bogendoerfer 93862c2c0aSThomas Bogendoerfer /* definition of the chip-specific record */ 94862c2c0aSThomas Bogendoerfer struct snd_sgio2audio { 95862c2c0aSThomas Bogendoerfer struct snd_card *card; 96862c2c0aSThomas Bogendoerfer 97862c2c0aSThomas Bogendoerfer /* codec */ 98862c2c0aSThomas Bogendoerfer struct snd_ad1843 ad1843; 99862c2c0aSThomas Bogendoerfer spinlock_t ad1843_lock; 100862c2c0aSThomas Bogendoerfer 101862c2c0aSThomas Bogendoerfer /* channels */ 102862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan channel[3]; 103862c2c0aSThomas Bogendoerfer 104862c2c0aSThomas Bogendoerfer /* resources */ 105862c2c0aSThomas Bogendoerfer void *ring_base; 106862c2c0aSThomas Bogendoerfer dma_addr_t ring_base_dma; 107862c2c0aSThomas Bogendoerfer }; 108862c2c0aSThomas Bogendoerfer 109862c2c0aSThomas Bogendoerfer /* AD1843 access */ 110862c2c0aSThomas Bogendoerfer 111862c2c0aSThomas Bogendoerfer /* 112862c2c0aSThomas Bogendoerfer * read_ad1843_reg returns the current contents of a 16 bit AD1843 register. 113862c2c0aSThomas Bogendoerfer * 114862c2c0aSThomas Bogendoerfer * Returns unsigned register value on success, -errno on failure. 115862c2c0aSThomas Bogendoerfer */ 116862c2c0aSThomas Bogendoerfer static int read_ad1843_reg(void *priv, int reg) 117862c2c0aSThomas Bogendoerfer { 118862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = priv; 119862c2c0aSThomas Bogendoerfer int val; 120862c2c0aSThomas Bogendoerfer unsigned long flags; 121862c2c0aSThomas Bogendoerfer 122862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->ad1843_lock, flags); 123862c2c0aSThomas Bogendoerfer 124862c2c0aSThomas Bogendoerfer writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | 125862c2c0aSThomas Bogendoerfer CODEC_CONTROL_READ, &mace->perif.audio.codec_control); 126862c2c0aSThomas Bogendoerfer wmb(); 127862c2c0aSThomas Bogendoerfer val = readq(&mace->perif.audio.codec_control); /* flush bus */ 128862c2c0aSThomas Bogendoerfer udelay(200); 129862c2c0aSThomas Bogendoerfer 130862c2c0aSThomas Bogendoerfer val = readq(&mace->perif.audio.codec_read); 131862c2c0aSThomas Bogendoerfer 132862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->ad1843_lock, flags); 133862c2c0aSThomas Bogendoerfer return val; 134862c2c0aSThomas Bogendoerfer } 135862c2c0aSThomas Bogendoerfer 136862c2c0aSThomas Bogendoerfer /* 137862c2c0aSThomas Bogendoerfer * write_ad1843_reg writes the specified value to a 16 bit AD1843 register. 138862c2c0aSThomas Bogendoerfer */ 139862c2c0aSThomas Bogendoerfer static int write_ad1843_reg(void *priv, int reg, int word) 140862c2c0aSThomas Bogendoerfer { 141862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = priv; 142862c2c0aSThomas Bogendoerfer int val; 143862c2c0aSThomas Bogendoerfer unsigned long flags; 144862c2c0aSThomas Bogendoerfer 145862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->ad1843_lock, flags); 146862c2c0aSThomas Bogendoerfer 147862c2c0aSThomas Bogendoerfer writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | 148862c2c0aSThomas Bogendoerfer (word << CODEC_CONTROL_WORD_SHIFT), 149862c2c0aSThomas Bogendoerfer &mace->perif.audio.codec_control); 150862c2c0aSThomas Bogendoerfer wmb(); 151862c2c0aSThomas Bogendoerfer val = readq(&mace->perif.audio.codec_control); /* flush bus */ 152862c2c0aSThomas Bogendoerfer udelay(200); 153862c2c0aSThomas Bogendoerfer 154862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->ad1843_lock, flags); 155862c2c0aSThomas Bogendoerfer return 0; 156862c2c0aSThomas Bogendoerfer } 157862c2c0aSThomas Bogendoerfer 158862c2c0aSThomas Bogendoerfer static int sgio2audio_gain_info(struct snd_kcontrol *kcontrol, 159862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_info *uinfo) 160862c2c0aSThomas Bogendoerfer { 161862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 162862c2c0aSThomas Bogendoerfer 163862c2c0aSThomas Bogendoerfer uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 164862c2c0aSThomas Bogendoerfer uinfo->count = 2; 165862c2c0aSThomas Bogendoerfer uinfo->value.integer.min = 0; 166862c2c0aSThomas Bogendoerfer uinfo->value.integer.max = ad1843_get_gain_max(&chip->ad1843, 167862c2c0aSThomas Bogendoerfer (int)kcontrol->private_value); 168862c2c0aSThomas Bogendoerfer return 0; 169862c2c0aSThomas Bogendoerfer } 170862c2c0aSThomas Bogendoerfer 171862c2c0aSThomas Bogendoerfer static int sgio2audio_gain_get(struct snd_kcontrol *kcontrol, 172862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 173862c2c0aSThomas Bogendoerfer { 174862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 175862c2c0aSThomas Bogendoerfer int vol; 176862c2c0aSThomas Bogendoerfer 177862c2c0aSThomas Bogendoerfer vol = ad1843_get_gain(&chip->ad1843, (int)kcontrol->private_value); 178862c2c0aSThomas Bogendoerfer 179862c2c0aSThomas Bogendoerfer ucontrol->value.integer.value[0] = (vol >> 8) & 0xFF; 180862c2c0aSThomas Bogendoerfer ucontrol->value.integer.value[1] = vol & 0xFF; 181862c2c0aSThomas Bogendoerfer 182862c2c0aSThomas Bogendoerfer return 0; 183862c2c0aSThomas Bogendoerfer } 184862c2c0aSThomas Bogendoerfer 185862c2c0aSThomas Bogendoerfer static int sgio2audio_gain_put(struct snd_kcontrol *kcontrol, 186862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 187862c2c0aSThomas Bogendoerfer { 188862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 189862c2c0aSThomas Bogendoerfer int newvol, oldvol; 190862c2c0aSThomas Bogendoerfer 191862c2c0aSThomas Bogendoerfer oldvol = ad1843_get_gain(&chip->ad1843, kcontrol->private_value); 192862c2c0aSThomas Bogendoerfer newvol = (ucontrol->value.integer.value[0] << 8) | 193862c2c0aSThomas Bogendoerfer ucontrol->value.integer.value[1]; 194862c2c0aSThomas Bogendoerfer 195862c2c0aSThomas Bogendoerfer newvol = ad1843_set_gain(&chip->ad1843, kcontrol->private_value, 196862c2c0aSThomas Bogendoerfer newvol); 197862c2c0aSThomas Bogendoerfer 198862c2c0aSThomas Bogendoerfer return newvol != oldvol; 199862c2c0aSThomas Bogendoerfer } 200862c2c0aSThomas Bogendoerfer 201862c2c0aSThomas Bogendoerfer static int sgio2audio_source_info(struct snd_kcontrol *kcontrol, 202862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_info *uinfo) 203862c2c0aSThomas Bogendoerfer { 204862c2c0aSThomas Bogendoerfer static const char *texts[3] = { 205862c2c0aSThomas Bogendoerfer "Cam Mic", "Mic", "Line" 206862c2c0aSThomas Bogendoerfer }; 207862c2c0aSThomas Bogendoerfer uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; 208862c2c0aSThomas Bogendoerfer uinfo->count = 1; 209862c2c0aSThomas Bogendoerfer uinfo->value.enumerated.items = 3; 210862c2c0aSThomas Bogendoerfer if (uinfo->value.enumerated.item >= 3) 211862c2c0aSThomas Bogendoerfer uinfo->value.enumerated.item = 1; 212862c2c0aSThomas Bogendoerfer strcpy(uinfo->value.enumerated.name, 213862c2c0aSThomas Bogendoerfer texts[uinfo->value.enumerated.item]); 214862c2c0aSThomas Bogendoerfer return 0; 215862c2c0aSThomas Bogendoerfer } 216862c2c0aSThomas Bogendoerfer 217862c2c0aSThomas Bogendoerfer static int sgio2audio_source_get(struct snd_kcontrol *kcontrol, 218862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 219862c2c0aSThomas Bogendoerfer { 220862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 221862c2c0aSThomas Bogendoerfer 222862c2c0aSThomas Bogendoerfer ucontrol->value.enumerated.item[0] = ad1843_get_recsrc(&chip->ad1843); 223862c2c0aSThomas Bogendoerfer return 0; 224862c2c0aSThomas Bogendoerfer } 225862c2c0aSThomas Bogendoerfer 226862c2c0aSThomas Bogendoerfer static int sgio2audio_source_put(struct snd_kcontrol *kcontrol, 227862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 228862c2c0aSThomas Bogendoerfer { 229862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 230862c2c0aSThomas Bogendoerfer int newsrc, oldsrc; 231862c2c0aSThomas Bogendoerfer 232862c2c0aSThomas Bogendoerfer oldsrc = ad1843_get_recsrc(&chip->ad1843); 233862c2c0aSThomas Bogendoerfer newsrc = ad1843_set_recsrc(&chip->ad1843, 234862c2c0aSThomas Bogendoerfer ucontrol->value.enumerated.item[0]); 235862c2c0aSThomas Bogendoerfer 236862c2c0aSThomas Bogendoerfer return newsrc != oldsrc; 237862c2c0aSThomas Bogendoerfer } 238862c2c0aSThomas Bogendoerfer 239862c2c0aSThomas Bogendoerfer /* dac1/pcm0 mixer control */ 240862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_pcm0 __devinitdata = { 241862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 242862c2c0aSThomas Bogendoerfer .name = "PCM Playback Volume", 243862c2c0aSThomas Bogendoerfer .index = 0, 244862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 245862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_PCM_0, 246862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 247862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 248862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 249862c2c0aSThomas Bogendoerfer }; 250862c2c0aSThomas Bogendoerfer 251862c2c0aSThomas Bogendoerfer /* dac2/pcm1 mixer control */ 252862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_pcm1 __devinitdata = { 253862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 254862c2c0aSThomas Bogendoerfer .name = "PCM Playback Volume", 255862c2c0aSThomas Bogendoerfer .index = 1, 256862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 257862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_PCM_1, 258862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 259862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 260862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 261862c2c0aSThomas Bogendoerfer }; 262862c2c0aSThomas Bogendoerfer 263862c2c0aSThomas Bogendoerfer /* record level mixer control */ 264862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_reclevel __devinitdata = { 265862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 266862c2c0aSThomas Bogendoerfer .name = "Capture Volume", 267862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 268862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_RECLEV, 269862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 270862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 271862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 272862c2c0aSThomas Bogendoerfer }; 273862c2c0aSThomas Bogendoerfer 274862c2c0aSThomas Bogendoerfer /* record level source control */ 275862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_recsource __devinitdata = { 276862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 277862c2c0aSThomas Bogendoerfer .name = "Capture Source", 278862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 279862c2c0aSThomas Bogendoerfer .info = sgio2audio_source_info, 280862c2c0aSThomas Bogendoerfer .get = sgio2audio_source_get, 281862c2c0aSThomas Bogendoerfer .put = sgio2audio_source_put, 282862c2c0aSThomas Bogendoerfer }; 283862c2c0aSThomas Bogendoerfer 284862c2c0aSThomas Bogendoerfer /* line mixer control */ 285862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_line __devinitdata = { 286862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 287862c2c0aSThomas Bogendoerfer .name = "Line Playback Volume", 288862c2c0aSThomas Bogendoerfer .index = 0, 289862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 290862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_LINE, 291862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 292862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 293862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 294862c2c0aSThomas Bogendoerfer }; 295862c2c0aSThomas Bogendoerfer 296862c2c0aSThomas Bogendoerfer /* cd mixer control */ 297862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_cd __devinitdata = { 298862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 299862c2c0aSThomas Bogendoerfer .name = "Line Playback Volume", 300862c2c0aSThomas Bogendoerfer .index = 1, 301862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 302862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_LINE_2, 303862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 304862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 305862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 306862c2c0aSThomas Bogendoerfer }; 307862c2c0aSThomas Bogendoerfer 308862c2c0aSThomas Bogendoerfer /* mic mixer control */ 309862c2c0aSThomas Bogendoerfer static struct snd_kcontrol_new sgio2audio_ctrl_mic __devinitdata = { 310862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 311862c2c0aSThomas Bogendoerfer .name = "Mic Playback Volume", 312862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 313862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_MIC, 314862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 315862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 316862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 317862c2c0aSThomas Bogendoerfer }; 318862c2c0aSThomas Bogendoerfer 319862c2c0aSThomas Bogendoerfer 320862c2c0aSThomas Bogendoerfer static int __devinit snd_sgio2audio_new_mixer(struct snd_sgio2audio *chip) 321862c2c0aSThomas Bogendoerfer { 322862c2c0aSThomas Bogendoerfer int err; 323862c2c0aSThomas Bogendoerfer 324862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 325862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_pcm0, chip)); 326862c2c0aSThomas Bogendoerfer if (err < 0) 327862c2c0aSThomas Bogendoerfer return err; 328862c2c0aSThomas Bogendoerfer 329862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 330862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_pcm1, chip)); 331862c2c0aSThomas Bogendoerfer if (err < 0) 332862c2c0aSThomas Bogendoerfer return err; 333862c2c0aSThomas Bogendoerfer 334862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 335862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_reclevel, chip)); 336862c2c0aSThomas Bogendoerfer if (err < 0) 337862c2c0aSThomas Bogendoerfer return err; 338862c2c0aSThomas Bogendoerfer 339862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 340862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_recsource, chip)); 341862c2c0aSThomas Bogendoerfer if (err < 0) 342862c2c0aSThomas Bogendoerfer return err; 343862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 344862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_line, chip)); 345862c2c0aSThomas Bogendoerfer if (err < 0) 346862c2c0aSThomas Bogendoerfer return err; 347862c2c0aSThomas Bogendoerfer 348862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 349862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_cd, chip)); 350862c2c0aSThomas Bogendoerfer if (err < 0) 351862c2c0aSThomas Bogendoerfer return err; 352862c2c0aSThomas Bogendoerfer 353862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 354862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_mic, chip)); 355862c2c0aSThomas Bogendoerfer if (err < 0) 356862c2c0aSThomas Bogendoerfer return err; 357862c2c0aSThomas Bogendoerfer 358862c2c0aSThomas Bogendoerfer return 0; 359862c2c0aSThomas Bogendoerfer } 360862c2c0aSThomas Bogendoerfer 361862c2c0aSThomas Bogendoerfer /* low-level audio interface DMA */ 362862c2c0aSThomas Bogendoerfer 363862c2c0aSThomas Bogendoerfer /* get data out of bounce buffer, count must be a multiple of 32 */ 364862c2c0aSThomas Bogendoerfer /* returns 1 if a period has elapsed */ 365862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_pull_frag(struct snd_sgio2audio *chip, 366862c2c0aSThomas Bogendoerfer unsigned int ch, unsigned int count) 367862c2c0aSThomas Bogendoerfer { 368862c2c0aSThomas Bogendoerfer int ret; 369862c2c0aSThomas Bogendoerfer unsigned long src_base, src_pos, dst_mask; 370862c2c0aSThomas Bogendoerfer unsigned char *dst_base; 371862c2c0aSThomas Bogendoerfer int dst_pos; 372862c2c0aSThomas Bogendoerfer u64 *src; 373862c2c0aSThomas Bogendoerfer s16 *dst; 374862c2c0aSThomas Bogendoerfer u64 x; 375862c2c0aSThomas Bogendoerfer unsigned long flags; 376862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; 377862c2c0aSThomas Bogendoerfer 378862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 379862c2c0aSThomas Bogendoerfer 380862c2c0aSThomas Bogendoerfer src_base = (unsigned long) chip->ring_base | (ch << CHANNEL_RING_SHIFT); 381862c2c0aSThomas Bogendoerfer src_pos = readq(&mace->perif.audio.chan[ch].read_ptr); 382862c2c0aSThomas Bogendoerfer dst_base = runtime->dma_area; 383862c2c0aSThomas Bogendoerfer dst_pos = chip->channel[ch].pos; 384862c2c0aSThomas Bogendoerfer dst_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; 385862c2c0aSThomas Bogendoerfer 386862c2c0aSThomas Bogendoerfer /* check if a period has elapsed */ 387862c2c0aSThomas Bogendoerfer chip->channel[ch].size += (count >> 3); /* in frames */ 388862c2c0aSThomas Bogendoerfer ret = chip->channel[ch].size >= runtime->period_size; 389862c2c0aSThomas Bogendoerfer chip->channel[ch].size %= runtime->period_size; 390862c2c0aSThomas Bogendoerfer 391862c2c0aSThomas Bogendoerfer while (count) { 392862c2c0aSThomas Bogendoerfer src = (u64 *)(src_base + src_pos); 393862c2c0aSThomas Bogendoerfer dst = (s16 *)(dst_base + dst_pos); 394862c2c0aSThomas Bogendoerfer 395862c2c0aSThomas Bogendoerfer x = *src; 396862c2c0aSThomas Bogendoerfer dst[0] = (x >> CHANNEL_LEFT_SHIFT) & 0xffff; 397862c2c0aSThomas Bogendoerfer dst[1] = (x >> CHANNEL_RIGHT_SHIFT) & 0xffff; 398862c2c0aSThomas Bogendoerfer 399862c2c0aSThomas Bogendoerfer src_pos = (src_pos + sizeof(u64)) & CHANNEL_RING_MASK; 400862c2c0aSThomas Bogendoerfer dst_pos = (dst_pos + 2 * sizeof(s16)) & dst_mask; 401862c2c0aSThomas Bogendoerfer count -= sizeof(u64); 402862c2c0aSThomas Bogendoerfer } 403862c2c0aSThomas Bogendoerfer 404862c2c0aSThomas Bogendoerfer writeq(src_pos, &mace->perif.audio.chan[ch].read_ptr); /* in bytes */ 405862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = dst_pos; 406862c2c0aSThomas Bogendoerfer 407862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 408862c2c0aSThomas Bogendoerfer return ret; 409862c2c0aSThomas Bogendoerfer } 410862c2c0aSThomas Bogendoerfer 411862c2c0aSThomas Bogendoerfer /* put some DMA data in bounce buffer, count must be a multiple of 32 */ 412862c2c0aSThomas Bogendoerfer /* returns 1 if a period has elapsed */ 413862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_push_frag(struct snd_sgio2audio *chip, 414862c2c0aSThomas Bogendoerfer unsigned int ch, unsigned int count) 415862c2c0aSThomas Bogendoerfer { 416862c2c0aSThomas Bogendoerfer int ret; 417862c2c0aSThomas Bogendoerfer s64 l, r; 418862c2c0aSThomas Bogendoerfer unsigned long dst_base, dst_pos, src_mask; 419862c2c0aSThomas Bogendoerfer unsigned char *src_base; 420862c2c0aSThomas Bogendoerfer int src_pos; 421862c2c0aSThomas Bogendoerfer u64 *dst; 422862c2c0aSThomas Bogendoerfer s16 *src; 423862c2c0aSThomas Bogendoerfer unsigned long flags; 424862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; 425862c2c0aSThomas Bogendoerfer 426862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 427862c2c0aSThomas Bogendoerfer 428862c2c0aSThomas Bogendoerfer dst_base = (unsigned long)chip->ring_base | (ch << CHANNEL_RING_SHIFT); 429862c2c0aSThomas Bogendoerfer dst_pos = readq(&mace->perif.audio.chan[ch].write_ptr); 430862c2c0aSThomas Bogendoerfer src_base = runtime->dma_area; 431862c2c0aSThomas Bogendoerfer src_pos = chip->channel[ch].pos; 432862c2c0aSThomas Bogendoerfer src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; 433862c2c0aSThomas Bogendoerfer 434862c2c0aSThomas Bogendoerfer /* check if a period has elapsed */ 435862c2c0aSThomas Bogendoerfer chip->channel[ch].size += (count >> 3); /* in frames */ 436862c2c0aSThomas Bogendoerfer ret = chip->channel[ch].size >= runtime->period_size; 437862c2c0aSThomas Bogendoerfer chip->channel[ch].size %= runtime->period_size; 438862c2c0aSThomas Bogendoerfer 439862c2c0aSThomas Bogendoerfer while (count) { 440862c2c0aSThomas Bogendoerfer src = (s16 *)(src_base + src_pos); 441862c2c0aSThomas Bogendoerfer dst = (u64 *)(dst_base + dst_pos); 442862c2c0aSThomas Bogendoerfer 443862c2c0aSThomas Bogendoerfer l = src[0]; /* sign extend */ 444862c2c0aSThomas Bogendoerfer r = src[1]; /* sign extend */ 445862c2c0aSThomas Bogendoerfer 446862c2c0aSThomas Bogendoerfer *dst = ((l & 0x00ffffff) << CHANNEL_LEFT_SHIFT) | 447862c2c0aSThomas Bogendoerfer ((r & 0x00ffffff) << CHANNEL_RIGHT_SHIFT); 448862c2c0aSThomas Bogendoerfer 449862c2c0aSThomas Bogendoerfer dst_pos = (dst_pos + sizeof(u64)) & CHANNEL_RING_MASK; 450862c2c0aSThomas Bogendoerfer src_pos = (src_pos + 2 * sizeof(s16)) & src_mask; 451862c2c0aSThomas Bogendoerfer count -= sizeof(u64); 452862c2c0aSThomas Bogendoerfer } 453862c2c0aSThomas Bogendoerfer 454862c2c0aSThomas Bogendoerfer writeq(dst_pos, &mace->perif.audio.chan[ch].write_ptr); /* in bytes */ 455862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = src_pos; 456862c2c0aSThomas Bogendoerfer 457862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 458862c2c0aSThomas Bogendoerfer return ret; 459862c2c0aSThomas Bogendoerfer } 460862c2c0aSThomas Bogendoerfer 461862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_start(struct snd_pcm_substream *substream) 462862c2c0aSThomas Bogendoerfer { 463862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 464862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 465862c2c0aSThomas Bogendoerfer int ch = chan->idx; 466862c2c0aSThomas Bogendoerfer 467862c2c0aSThomas Bogendoerfer /* reset DMA channel */ 468862c2c0aSThomas Bogendoerfer writeq(CHANNEL_CONTROL_RESET, &mace->perif.audio.chan[ch].control); 469862c2c0aSThomas Bogendoerfer udelay(10); 470862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.chan[ch].control); 471862c2c0aSThomas Bogendoerfer 472862c2c0aSThomas Bogendoerfer if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 473862c2c0aSThomas Bogendoerfer /* push a full buffer */ 474862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_push_frag(chip, ch, CHANNEL_RING_SIZE - 32); 475862c2c0aSThomas Bogendoerfer } 476862c2c0aSThomas Bogendoerfer /* set DMA to wake on 50% empty and enable interrupt */ 477862c2c0aSThomas Bogendoerfer writeq(CHANNEL_DMA_ENABLE | CHANNEL_INT_THRESHOLD_50, 478862c2c0aSThomas Bogendoerfer &mace->perif.audio.chan[ch].control); 479862c2c0aSThomas Bogendoerfer return 0; 480862c2c0aSThomas Bogendoerfer } 481862c2c0aSThomas Bogendoerfer 482862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_stop(struct snd_pcm_substream *substream) 483862c2c0aSThomas Bogendoerfer { 484862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 485862c2c0aSThomas Bogendoerfer 486862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.chan[chan->idx].control); 487862c2c0aSThomas Bogendoerfer return 0; 488862c2c0aSThomas Bogendoerfer } 489862c2c0aSThomas Bogendoerfer 490862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_dma_in_isr(int irq, void *dev_id) 491862c2c0aSThomas Bogendoerfer { 492862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 493862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 494862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 495862c2c0aSThomas Bogendoerfer int count, ch; 496862c2c0aSThomas Bogendoerfer 497862c2c0aSThomas Bogendoerfer substream = chan->substream; 498862c2c0aSThomas Bogendoerfer chip = snd_pcm_substream_chip(substream); 499862c2c0aSThomas Bogendoerfer ch = chan->idx; 500862c2c0aSThomas Bogendoerfer 501862c2c0aSThomas Bogendoerfer /* empty the ring */ 502862c2c0aSThomas Bogendoerfer count = CHANNEL_RING_SIZE - 503862c2c0aSThomas Bogendoerfer readq(&mace->perif.audio.chan[ch].depth) - 32; 504862c2c0aSThomas Bogendoerfer if (snd_sgio2audio_dma_pull_frag(chip, ch, count)) 505862c2c0aSThomas Bogendoerfer snd_pcm_period_elapsed(substream); 506862c2c0aSThomas Bogendoerfer 507862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 508862c2c0aSThomas Bogendoerfer } 509862c2c0aSThomas Bogendoerfer 510862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_dma_out_isr(int irq, void *dev_id) 511862c2c0aSThomas Bogendoerfer { 512862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 513862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 514862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 515862c2c0aSThomas Bogendoerfer int count, ch; 516862c2c0aSThomas Bogendoerfer 517862c2c0aSThomas Bogendoerfer substream = chan->substream; 518862c2c0aSThomas Bogendoerfer chip = snd_pcm_substream_chip(substream); 519862c2c0aSThomas Bogendoerfer ch = chan->idx; 520862c2c0aSThomas Bogendoerfer /* fill the ring */ 521862c2c0aSThomas Bogendoerfer count = CHANNEL_RING_SIZE - 522862c2c0aSThomas Bogendoerfer readq(&mace->perif.audio.chan[ch].depth) - 32; 523862c2c0aSThomas Bogendoerfer if (snd_sgio2audio_dma_push_frag(chip, ch, count)) 524862c2c0aSThomas Bogendoerfer snd_pcm_period_elapsed(substream); 525862c2c0aSThomas Bogendoerfer 526862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 527862c2c0aSThomas Bogendoerfer } 528862c2c0aSThomas Bogendoerfer 529862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_error_isr(int irq, void *dev_id) 530862c2c0aSThomas Bogendoerfer { 531862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 532862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 533862c2c0aSThomas Bogendoerfer 534862c2c0aSThomas Bogendoerfer substream = chan->substream; 535862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_stop(substream); 536862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_start(substream); 537862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 538862c2c0aSThomas Bogendoerfer } 539862c2c0aSThomas Bogendoerfer 540862c2c0aSThomas Bogendoerfer /* PCM part */ 541862c2c0aSThomas Bogendoerfer /* PCM hardware definition */ 542862c2c0aSThomas Bogendoerfer static struct snd_pcm_hardware snd_sgio2audio_pcm_hw = { 543862c2c0aSThomas Bogendoerfer .info = (SNDRV_PCM_INFO_MMAP | 544862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_MMAP_VALID | 545862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_INTERLEAVED | 546862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_BLOCK_TRANSFER), 547862c2c0aSThomas Bogendoerfer .formats = SNDRV_PCM_FMTBIT_S16_BE, 548862c2c0aSThomas Bogendoerfer .rates = SNDRV_PCM_RATE_8000_48000, 549862c2c0aSThomas Bogendoerfer .rate_min = 8000, 550862c2c0aSThomas Bogendoerfer .rate_max = 48000, 551862c2c0aSThomas Bogendoerfer .channels_min = 2, 552862c2c0aSThomas Bogendoerfer .channels_max = 2, 553862c2c0aSThomas Bogendoerfer .buffer_bytes_max = 65536, 554862c2c0aSThomas Bogendoerfer .period_bytes_min = 32768, 555862c2c0aSThomas Bogendoerfer .period_bytes_max = 65536, 556862c2c0aSThomas Bogendoerfer .periods_min = 1, 557862c2c0aSThomas Bogendoerfer .periods_max = 1024, 558862c2c0aSThomas Bogendoerfer }; 559862c2c0aSThomas Bogendoerfer 560862c2c0aSThomas Bogendoerfer /* PCM playback open callback */ 561862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_playback1_open(struct snd_pcm_substream *substream) 562862c2c0aSThomas Bogendoerfer { 563862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 564862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 565862c2c0aSThomas Bogendoerfer 566862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 567862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[1]; 568862c2c0aSThomas Bogendoerfer return 0; 569862c2c0aSThomas Bogendoerfer } 570862c2c0aSThomas Bogendoerfer 571862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_playback2_open(struct snd_pcm_substream *substream) 572862c2c0aSThomas Bogendoerfer { 573862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 574862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 575862c2c0aSThomas Bogendoerfer 576862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 577862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[2]; 578862c2c0aSThomas Bogendoerfer return 0; 579862c2c0aSThomas Bogendoerfer } 580862c2c0aSThomas Bogendoerfer 581862c2c0aSThomas Bogendoerfer /* PCM capture open callback */ 582862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_capture_open(struct snd_pcm_substream *substream) 583862c2c0aSThomas Bogendoerfer { 584862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 585862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 586862c2c0aSThomas Bogendoerfer 587862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 588862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[0]; 589862c2c0aSThomas Bogendoerfer return 0; 590862c2c0aSThomas Bogendoerfer } 591862c2c0aSThomas Bogendoerfer 592862c2c0aSThomas Bogendoerfer /* PCM close callback */ 593862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_close(struct snd_pcm_substream *substream) 594862c2c0aSThomas Bogendoerfer { 595862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 596862c2c0aSThomas Bogendoerfer 597862c2c0aSThomas Bogendoerfer runtime->private_data = NULL; 598862c2c0aSThomas Bogendoerfer return 0; 599862c2c0aSThomas Bogendoerfer } 600862c2c0aSThomas Bogendoerfer 601862c2c0aSThomas Bogendoerfer 602862c2c0aSThomas Bogendoerfer /* hw_params callback */ 603862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, 604862c2c0aSThomas Bogendoerfer struct snd_pcm_hw_params *hw_params) 605862c2c0aSThomas Bogendoerfer { 6066cedf869SClemens Ladisch return snd_pcm_lib_alloc_vmalloc_buffer(substream, 6076cedf869SClemens Ladisch params_buffer_bytes(hw_params)); 608862c2c0aSThomas Bogendoerfer } 609862c2c0aSThomas Bogendoerfer 610862c2c0aSThomas Bogendoerfer /* hw_free callback */ 611862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream) 612862c2c0aSThomas Bogendoerfer { 6136cedf869SClemens Ladisch return snd_pcm_lib_free_vmalloc_buffer(substream); 614862c2c0aSThomas Bogendoerfer } 615862c2c0aSThomas Bogendoerfer 616862c2c0aSThomas Bogendoerfer /* prepare callback */ 617862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_prepare(struct snd_pcm_substream *substream) 618862c2c0aSThomas Bogendoerfer { 619862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 620862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 621862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 622862c2c0aSThomas Bogendoerfer int ch = chan->idx; 623862c2c0aSThomas Bogendoerfer unsigned long flags; 624862c2c0aSThomas Bogendoerfer 625862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 626862c2c0aSThomas Bogendoerfer 627862c2c0aSThomas Bogendoerfer /* Setup the pseudo-dma transfer pointers. */ 628862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = 0; 629862c2c0aSThomas Bogendoerfer chip->channel[ch].size = 0; 630862c2c0aSThomas Bogendoerfer chip->channel[ch].substream = substream; 631862c2c0aSThomas Bogendoerfer 632862c2c0aSThomas Bogendoerfer /* set AD1843 format */ 633862c2c0aSThomas Bogendoerfer /* hardware format is always S16_LE */ 634862c2c0aSThomas Bogendoerfer switch (substream->stream) { 635862c2c0aSThomas Bogendoerfer case SNDRV_PCM_STREAM_PLAYBACK: 636862c2c0aSThomas Bogendoerfer ad1843_setup_dac(&chip->ad1843, 637862c2c0aSThomas Bogendoerfer ch - 1, 638862c2c0aSThomas Bogendoerfer runtime->rate, 639862c2c0aSThomas Bogendoerfer SNDRV_PCM_FORMAT_S16_LE, 640862c2c0aSThomas Bogendoerfer runtime->channels); 641862c2c0aSThomas Bogendoerfer break; 642862c2c0aSThomas Bogendoerfer case SNDRV_PCM_STREAM_CAPTURE: 643862c2c0aSThomas Bogendoerfer ad1843_setup_adc(&chip->ad1843, 644862c2c0aSThomas Bogendoerfer runtime->rate, 645862c2c0aSThomas Bogendoerfer SNDRV_PCM_FORMAT_S16_LE, 646862c2c0aSThomas Bogendoerfer runtime->channels); 647862c2c0aSThomas Bogendoerfer break; 648862c2c0aSThomas Bogendoerfer } 649862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 650862c2c0aSThomas Bogendoerfer return 0; 651862c2c0aSThomas Bogendoerfer } 652862c2c0aSThomas Bogendoerfer 653862c2c0aSThomas Bogendoerfer /* trigger callback */ 654862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_trigger(struct snd_pcm_substream *substream, 655862c2c0aSThomas Bogendoerfer int cmd) 656862c2c0aSThomas Bogendoerfer { 657862c2c0aSThomas Bogendoerfer switch (cmd) { 658862c2c0aSThomas Bogendoerfer case SNDRV_PCM_TRIGGER_START: 659862c2c0aSThomas Bogendoerfer /* start the PCM engine */ 660862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_start(substream); 661862c2c0aSThomas Bogendoerfer break; 662862c2c0aSThomas Bogendoerfer case SNDRV_PCM_TRIGGER_STOP: 663862c2c0aSThomas Bogendoerfer /* stop the PCM engine */ 664862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_stop(substream); 665862c2c0aSThomas Bogendoerfer break; 666862c2c0aSThomas Bogendoerfer default: 667862c2c0aSThomas Bogendoerfer return -EINVAL; 668862c2c0aSThomas Bogendoerfer } 669862c2c0aSThomas Bogendoerfer return 0; 670862c2c0aSThomas Bogendoerfer } 671862c2c0aSThomas Bogendoerfer 672862c2c0aSThomas Bogendoerfer /* pointer callback */ 673862c2c0aSThomas Bogendoerfer static snd_pcm_uframes_t 674862c2c0aSThomas Bogendoerfer snd_sgio2audio_pcm_pointer(struct snd_pcm_substream *substream) 675862c2c0aSThomas Bogendoerfer { 676862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 677862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 678862c2c0aSThomas Bogendoerfer 679862c2c0aSThomas Bogendoerfer /* get the current hardware pointer */ 680862c2c0aSThomas Bogendoerfer return bytes_to_frames(substream->runtime, 681862c2c0aSThomas Bogendoerfer chip->channel[chan->idx].pos); 682862c2c0aSThomas Bogendoerfer } 683862c2c0aSThomas Bogendoerfer 684862c2c0aSThomas Bogendoerfer /* operators */ 685862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_playback1_ops = { 686862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_playback1_open, 687862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 688862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 689862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 690862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 691862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 692862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 693862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 6946cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 695c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 696862c2c0aSThomas Bogendoerfer }; 697862c2c0aSThomas Bogendoerfer 698862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_playback2_ops = { 699862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_playback2_open, 700862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 701862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 702862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 703862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 704862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 705862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 706862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 7076cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 708c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 709862c2c0aSThomas Bogendoerfer }; 710862c2c0aSThomas Bogendoerfer 711862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_capture_ops = { 712862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_capture_open, 713862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 714862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 715862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 716862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 717862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 718862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 719862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 7206cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 721c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 722862c2c0aSThomas Bogendoerfer }; 723862c2c0aSThomas Bogendoerfer 724862c2c0aSThomas Bogendoerfer /* 725862c2c0aSThomas Bogendoerfer * definitions of capture are omitted here... 726862c2c0aSThomas Bogendoerfer */ 727862c2c0aSThomas Bogendoerfer 728862c2c0aSThomas Bogendoerfer /* create a pcm device */ 729862c2c0aSThomas Bogendoerfer static int __devinit snd_sgio2audio_new_pcm(struct snd_sgio2audio *chip) 730862c2c0aSThomas Bogendoerfer { 731862c2c0aSThomas Bogendoerfer struct snd_pcm *pcm; 732862c2c0aSThomas Bogendoerfer int err; 733862c2c0aSThomas Bogendoerfer 734862c2c0aSThomas Bogendoerfer /* create first pcm device with one outputs and one input */ 735862c2c0aSThomas Bogendoerfer err = snd_pcm_new(chip->card, "SGI O2 Audio", 0, 1, 1, &pcm); 736862c2c0aSThomas Bogendoerfer if (err < 0) 737862c2c0aSThomas Bogendoerfer return err; 738862c2c0aSThomas Bogendoerfer 739862c2c0aSThomas Bogendoerfer pcm->private_data = chip; 740862c2c0aSThomas Bogendoerfer strcpy(pcm->name, "SGI O2 DAC1"); 741862c2c0aSThomas Bogendoerfer 742862c2c0aSThomas Bogendoerfer /* set operators */ 743862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 744862c2c0aSThomas Bogendoerfer &snd_sgio2audio_playback1_ops); 745862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, 746862c2c0aSThomas Bogendoerfer &snd_sgio2audio_capture_ops); 747862c2c0aSThomas Bogendoerfer 748862c2c0aSThomas Bogendoerfer /* create second pcm device with one outputs and no input */ 749862c2c0aSThomas Bogendoerfer err = snd_pcm_new(chip->card, "SGI O2 Audio", 1, 1, 0, &pcm); 750862c2c0aSThomas Bogendoerfer if (err < 0) 751862c2c0aSThomas Bogendoerfer return err; 752862c2c0aSThomas Bogendoerfer 753862c2c0aSThomas Bogendoerfer pcm->private_data = chip; 754862c2c0aSThomas Bogendoerfer strcpy(pcm->name, "SGI O2 DAC2"); 755862c2c0aSThomas Bogendoerfer 756862c2c0aSThomas Bogendoerfer /* set operators */ 757862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 758862c2c0aSThomas Bogendoerfer &snd_sgio2audio_playback2_ops); 759862c2c0aSThomas Bogendoerfer 760862c2c0aSThomas Bogendoerfer return 0; 761862c2c0aSThomas Bogendoerfer } 762862c2c0aSThomas Bogendoerfer 763862c2c0aSThomas Bogendoerfer static struct { 764862c2c0aSThomas Bogendoerfer int idx; 765862c2c0aSThomas Bogendoerfer int irq; 766862c2c0aSThomas Bogendoerfer irqreturn_t (*isr)(int, void *); 767862c2c0aSThomas Bogendoerfer const char *desc; 768862c2c0aSThomas Bogendoerfer } snd_sgio2_isr_table[] = { 769862c2c0aSThomas Bogendoerfer { 770862c2c0aSThomas Bogendoerfer .idx = 0, 771862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO1_DMAT_IRQ, 772862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_in_isr, 773862c2c0aSThomas Bogendoerfer .desc = "Capture DMA Channel 0" 774862c2c0aSThomas Bogendoerfer }, { 775862c2c0aSThomas Bogendoerfer .idx = 0, 776862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO1_OF_IRQ, 777862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 778862c2c0aSThomas Bogendoerfer .desc = "Capture Overflow" 779862c2c0aSThomas Bogendoerfer }, { 780862c2c0aSThomas Bogendoerfer .idx = 1, 781862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO2_DMAT_IRQ, 782862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_out_isr, 783862c2c0aSThomas Bogendoerfer .desc = "Playback DMA Channel 1" 784862c2c0aSThomas Bogendoerfer }, { 785862c2c0aSThomas Bogendoerfer .idx = 1, 786862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO2_MERR_IRQ, 787862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 788862c2c0aSThomas Bogendoerfer .desc = "Memory Error Channel 1" 789862c2c0aSThomas Bogendoerfer }, { 790862c2c0aSThomas Bogendoerfer .idx = 2, 791862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO3_DMAT_IRQ, 792862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_out_isr, 793862c2c0aSThomas Bogendoerfer .desc = "Playback DMA Channel 2" 794862c2c0aSThomas Bogendoerfer }, { 795862c2c0aSThomas Bogendoerfer .idx = 2, 796862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO3_MERR_IRQ, 797862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 798862c2c0aSThomas Bogendoerfer .desc = "Memory Error Channel 2" 799862c2c0aSThomas Bogendoerfer } 800862c2c0aSThomas Bogendoerfer }; 801862c2c0aSThomas Bogendoerfer 802862c2c0aSThomas Bogendoerfer /* ALSA driver */ 803862c2c0aSThomas Bogendoerfer 804862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_free(struct snd_sgio2audio *chip) 805862c2c0aSThomas Bogendoerfer { 806862c2c0aSThomas Bogendoerfer int i; 807862c2c0aSThomas Bogendoerfer 808862c2c0aSThomas Bogendoerfer /* reset interface */ 809862c2c0aSThomas Bogendoerfer writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); 810862c2c0aSThomas Bogendoerfer udelay(1); 811862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.control); 812862c2c0aSThomas Bogendoerfer 813862c2c0aSThomas Bogendoerfer /* release IRQ's */ 814862c2c0aSThomas Bogendoerfer for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) 815862c2c0aSThomas Bogendoerfer free_irq(snd_sgio2_isr_table[i].irq, 816862c2c0aSThomas Bogendoerfer &chip->channel[snd_sgio2_isr_table[i].idx]); 817862c2c0aSThomas Bogendoerfer 818862c2c0aSThomas Bogendoerfer dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, 819862c2c0aSThomas Bogendoerfer chip->ring_base, chip->ring_base_dma); 820862c2c0aSThomas Bogendoerfer 821862c2c0aSThomas Bogendoerfer /* release card data */ 822862c2c0aSThomas Bogendoerfer kfree(chip); 823862c2c0aSThomas Bogendoerfer return 0; 824862c2c0aSThomas Bogendoerfer } 825862c2c0aSThomas Bogendoerfer 826862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dev_free(struct snd_device *device) 827862c2c0aSThomas Bogendoerfer { 828862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = device->device_data; 829862c2c0aSThomas Bogendoerfer 830862c2c0aSThomas Bogendoerfer return snd_sgio2audio_free(chip); 831862c2c0aSThomas Bogendoerfer } 832862c2c0aSThomas Bogendoerfer 833862c2c0aSThomas Bogendoerfer static struct snd_device_ops ops = { 834862c2c0aSThomas Bogendoerfer .dev_free = snd_sgio2audio_dev_free, 835862c2c0aSThomas Bogendoerfer }; 836862c2c0aSThomas Bogendoerfer 837862c2c0aSThomas Bogendoerfer static int __devinit snd_sgio2audio_create(struct snd_card *card, 838862c2c0aSThomas Bogendoerfer struct snd_sgio2audio **rchip) 839862c2c0aSThomas Bogendoerfer { 840862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 841862c2c0aSThomas Bogendoerfer int i, err; 842862c2c0aSThomas Bogendoerfer 843862c2c0aSThomas Bogendoerfer *rchip = NULL; 844862c2c0aSThomas Bogendoerfer 845862c2c0aSThomas Bogendoerfer /* check if a codec is attached to the interface */ 846862c2c0aSThomas Bogendoerfer /* (Audio or Audio/Video board present) */ 847862c2c0aSThomas Bogendoerfer if (!(readq(&mace->perif.audio.control) & AUDIO_CONTROL_CODEC_PRESENT)) 848862c2c0aSThomas Bogendoerfer return -ENOENT; 849862c2c0aSThomas Bogendoerfer 850862c2c0aSThomas Bogendoerfer chip = kzalloc(sizeof(struct snd_sgio2audio), GFP_KERNEL); 851862c2c0aSThomas Bogendoerfer if (chip == NULL) 852862c2c0aSThomas Bogendoerfer return -ENOMEM; 853862c2c0aSThomas Bogendoerfer 854862c2c0aSThomas Bogendoerfer chip->card = card; 855862c2c0aSThomas Bogendoerfer 856862c2c0aSThomas Bogendoerfer chip->ring_base = dma_alloc_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, 857862c2c0aSThomas Bogendoerfer &chip->ring_base_dma, GFP_USER); 858862c2c0aSThomas Bogendoerfer if (chip->ring_base == NULL) { 859862c2c0aSThomas Bogendoerfer printk(KERN_ERR 860862c2c0aSThomas Bogendoerfer "sgio2audio: could not allocate ring buffers\n"); 861862c2c0aSThomas Bogendoerfer kfree(chip); 862862c2c0aSThomas Bogendoerfer return -ENOMEM; 863862c2c0aSThomas Bogendoerfer } 864862c2c0aSThomas Bogendoerfer 865862c2c0aSThomas Bogendoerfer spin_lock_init(&chip->ad1843_lock); 866862c2c0aSThomas Bogendoerfer 867862c2c0aSThomas Bogendoerfer /* initialize channels */ 868862c2c0aSThomas Bogendoerfer for (i = 0; i < 3; i++) { 869862c2c0aSThomas Bogendoerfer spin_lock_init(&chip->channel[i].lock); 870862c2c0aSThomas Bogendoerfer chip->channel[i].idx = i; 871862c2c0aSThomas Bogendoerfer } 872862c2c0aSThomas Bogendoerfer 873862c2c0aSThomas Bogendoerfer /* allocate IRQs */ 874862c2c0aSThomas Bogendoerfer for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) { 875862c2c0aSThomas Bogendoerfer if (request_irq(snd_sgio2_isr_table[i].irq, 876862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].isr, 877862c2c0aSThomas Bogendoerfer 0, 878862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].desc, 879862c2c0aSThomas Bogendoerfer &chip->channel[snd_sgio2_isr_table[i].idx])) { 880862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 881862c2c0aSThomas Bogendoerfer printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", 882862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].irq); 883862c2c0aSThomas Bogendoerfer return -EBUSY; 884862c2c0aSThomas Bogendoerfer } 885862c2c0aSThomas Bogendoerfer } 886862c2c0aSThomas Bogendoerfer 887862c2c0aSThomas Bogendoerfer /* reset the interface */ 888862c2c0aSThomas Bogendoerfer writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); 889862c2c0aSThomas Bogendoerfer udelay(1); 890862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.control); 891862c2c0aSThomas Bogendoerfer msleep_interruptible(1); /* give time to recover */ 892862c2c0aSThomas Bogendoerfer 893862c2c0aSThomas Bogendoerfer /* set ring base */ 894862c2c0aSThomas Bogendoerfer writeq(chip->ring_base_dma, &mace->perif.ctrl.ringbase); 895862c2c0aSThomas Bogendoerfer 896862c2c0aSThomas Bogendoerfer /* attach the AD1843 codec */ 897862c2c0aSThomas Bogendoerfer chip->ad1843.read = read_ad1843_reg; 898862c2c0aSThomas Bogendoerfer chip->ad1843.write = write_ad1843_reg; 899862c2c0aSThomas Bogendoerfer chip->ad1843.chip = chip; 900862c2c0aSThomas Bogendoerfer 901862c2c0aSThomas Bogendoerfer /* initialize the AD1843 codec */ 902862c2c0aSThomas Bogendoerfer err = ad1843_init(&chip->ad1843); 903862c2c0aSThomas Bogendoerfer if (err < 0) { 904862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 905862c2c0aSThomas Bogendoerfer return err; 906862c2c0aSThomas Bogendoerfer } 907862c2c0aSThomas Bogendoerfer 908862c2c0aSThomas Bogendoerfer err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); 909862c2c0aSThomas Bogendoerfer if (err < 0) { 910862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 911862c2c0aSThomas Bogendoerfer return err; 912862c2c0aSThomas Bogendoerfer } 913862c2c0aSThomas Bogendoerfer *rchip = chip; 914862c2c0aSThomas Bogendoerfer return 0; 915862c2c0aSThomas Bogendoerfer } 916862c2c0aSThomas Bogendoerfer 917862c2c0aSThomas Bogendoerfer static int __devinit snd_sgio2audio_probe(struct platform_device *pdev) 918862c2c0aSThomas Bogendoerfer { 919862c2c0aSThomas Bogendoerfer struct snd_card *card; 920862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 921862c2c0aSThomas Bogendoerfer int err; 922862c2c0aSThomas Bogendoerfer 923bd7dd77cSTakashi Iwai err = snd_card_create(index, id, THIS_MODULE, 0, &card); 924bd7dd77cSTakashi Iwai if (err < 0) 925bd7dd77cSTakashi Iwai return err; 926862c2c0aSThomas Bogendoerfer 927862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_create(card, &chip); 928862c2c0aSThomas Bogendoerfer if (err < 0) { 929862c2c0aSThomas Bogendoerfer snd_card_free(card); 930862c2c0aSThomas Bogendoerfer return err; 931862c2c0aSThomas Bogendoerfer } 932862c2c0aSThomas Bogendoerfer snd_card_set_dev(card, &pdev->dev); 933862c2c0aSThomas Bogendoerfer 934862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_new_pcm(chip); 935862c2c0aSThomas Bogendoerfer if (err < 0) { 936862c2c0aSThomas Bogendoerfer snd_card_free(card); 937862c2c0aSThomas Bogendoerfer return err; 938862c2c0aSThomas Bogendoerfer } 939862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_new_mixer(chip); 940862c2c0aSThomas Bogendoerfer if (err < 0) { 941862c2c0aSThomas Bogendoerfer snd_card_free(card); 942862c2c0aSThomas Bogendoerfer return err; 943862c2c0aSThomas Bogendoerfer } 944862c2c0aSThomas Bogendoerfer 945862c2c0aSThomas Bogendoerfer strcpy(card->driver, "SGI O2 Audio"); 946862c2c0aSThomas Bogendoerfer strcpy(card->shortname, "SGI O2 Audio"); 947862c2c0aSThomas Bogendoerfer sprintf(card->longname, "%s irq %i-%i", 948862c2c0aSThomas Bogendoerfer card->shortname, 949862c2c0aSThomas Bogendoerfer MACEISA_AUDIO1_DMAT_IRQ, 950862c2c0aSThomas Bogendoerfer MACEISA_AUDIO3_MERR_IRQ); 951862c2c0aSThomas Bogendoerfer 952862c2c0aSThomas Bogendoerfer err = snd_card_register(card); 953862c2c0aSThomas Bogendoerfer if (err < 0) { 954862c2c0aSThomas Bogendoerfer snd_card_free(card); 955862c2c0aSThomas Bogendoerfer return err; 956862c2c0aSThomas Bogendoerfer } 957862c2c0aSThomas Bogendoerfer platform_set_drvdata(pdev, card); 958862c2c0aSThomas Bogendoerfer return 0; 959862c2c0aSThomas Bogendoerfer } 960862c2c0aSThomas Bogendoerfer 9612f229a31STakashi Iwai static int __devexit snd_sgio2audio_remove(struct platform_device *pdev) 962862c2c0aSThomas Bogendoerfer { 963862c2c0aSThomas Bogendoerfer struct snd_card *card = platform_get_drvdata(pdev); 964862c2c0aSThomas Bogendoerfer 965862c2c0aSThomas Bogendoerfer snd_card_free(card); 966862c2c0aSThomas Bogendoerfer platform_set_drvdata(pdev, NULL); 967862c2c0aSThomas Bogendoerfer return 0; 968862c2c0aSThomas Bogendoerfer } 969862c2c0aSThomas Bogendoerfer 970862c2c0aSThomas Bogendoerfer static struct platform_driver sgio2audio_driver = { 971862c2c0aSThomas Bogendoerfer .probe = snd_sgio2audio_probe, 972862c2c0aSThomas Bogendoerfer .remove = __devexit_p(snd_sgio2audio_remove), 973862c2c0aSThomas Bogendoerfer .driver = { 974862c2c0aSThomas Bogendoerfer .name = "sgio2audio", 975862c2c0aSThomas Bogendoerfer .owner = THIS_MODULE, 976862c2c0aSThomas Bogendoerfer } 977862c2c0aSThomas Bogendoerfer }; 978862c2c0aSThomas Bogendoerfer 979*51451b8dSAxel Lin module_platform_driver(sgio2audio_driver); 980