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 { 204*2a2085abSTakashi Iwai static const char * const texts[3] = { 205862c2c0aSThomas Bogendoerfer "Cam Mic", "Mic", "Line" 206862c2c0aSThomas Bogendoerfer }; 207*2a2085abSTakashi Iwai return snd_ctl_enum_info(uinfo, 1, 3, texts); 208862c2c0aSThomas Bogendoerfer } 209862c2c0aSThomas Bogendoerfer 210862c2c0aSThomas Bogendoerfer static int sgio2audio_source_get(struct snd_kcontrol *kcontrol, 211862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 212862c2c0aSThomas Bogendoerfer { 213862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 214862c2c0aSThomas Bogendoerfer 215862c2c0aSThomas Bogendoerfer ucontrol->value.enumerated.item[0] = ad1843_get_recsrc(&chip->ad1843); 216862c2c0aSThomas Bogendoerfer return 0; 217862c2c0aSThomas Bogendoerfer } 218862c2c0aSThomas Bogendoerfer 219862c2c0aSThomas Bogendoerfer static int sgio2audio_source_put(struct snd_kcontrol *kcontrol, 220862c2c0aSThomas Bogendoerfer struct snd_ctl_elem_value *ucontrol) 221862c2c0aSThomas Bogendoerfer { 222862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); 223862c2c0aSThomas Bogendoerfer int newsrc, oldsrc; 224862c2c0aSThomas Bogendoerfer 225862c2c0aSThomas Bogendoerfer oldsrc = ad1843_get_recsrc(&chip->ad1843); 226862c2c0aSThomas Bogendoerfer newsrc = ad1843_set_recsrc(&chip->ad1843, 227862c2c0aSThomas Bogendoerfer ucontrol->value.enumerated.item[0]); 228862c2c0aSThomas Bogendoerfer 229862c2c0aSThomas Bogendoerfer return newsrc != oldsrc; 230862c2c0aSThomas Bogendoerfer } 231862c2c0aSThomas Bogendoerfer 232862c2c0aSThomas Bogendoerfer /* dac1/pcm0 mixer control */ 233e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_pcm0 = { 234862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 235862c2c0aSThomas Bogendoerfer .name = "PCM Playback Volume", 236862c2c0aSThomas Bogendoerfer .index = 0, 237862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 238862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_PCM_0, 239862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 240862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 241862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 242862c2c0aSThomas Bogendoerfer }; 243862c2c0aSThomas Bogendoerfer 244862c2c0aSThomas Bogendoerfer /* dac2/pcm1 mixer control */ 245e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_pcm1 = { 246862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 247862c2c0aSThomas Bogendoerfer .name = "PCM Playback Volume", 248862c2c0aSThomas Bogendoerfer .index = 1, 249862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 250862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_PCM_1, 251862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 252862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 253862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 254862c2c0aSThomas Bogendoerfer }; 255862c2c0aSThomas Bogendoerfer 256862c2c0aSThomas Bogendoerfer /* record level mixer control */ 257e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_reclevel = { 258862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 259862c2c0aSThomas Bogendoerfer .name = "Capture Volume", 260862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 261862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_RECLEV, 262862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 263862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 264862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 265862c2c0aSThomas Bogendoerfer }; 266862c2c0aSThomas Bogendoerfer 267862c2c0aSThomas Bogendoerfer /* record level source control */ 268e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_recsource = { 269862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 270862c2c0aSThomas Bogendoerfer .name = "Capture Source", 271862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 272862c2c0aSThomas Bogendoerfer .info = sgio2audio_source_info, 273862c2c0aSThomas Bogendoerfer .get = sgio2audio_source_get, 274862c2c0aSThomas Bogendoerfer .put = sgio2audio_source_put, 275862c2c0aSThomas Bogendoerfer }; 276862c2c0aSThomas Bogendoerfer 277862c2c0aSThomas Bogendoerfer /* line mixer control */ 278e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_line = { 279862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 280862c2c0aSThomas Bogendoerfer .name = "Line Playback Volume", 281862c2c0aSThomas Bogendoerfer .index = 0, 282862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 283862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_LINE, 284862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 285862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 286862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 287862c2c0aSThomas Bogendoerfer }; 288862c2c0aSThomas Bogendoerfer 289862c2c0aSThomas Bogendoerfer /* cd mixer control */ 290e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_cd = { 291862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 292862c2c0aSThomas Bogendoerfer .name = "Line Playback Volume", 293862c2c0aSThomas Bogendoerfer .index = 1, 294862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 295862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_LINE_2, 296862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 297862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 298862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 299862c2c0aSThomas Bogendoerfer }; 300862c2c0aSThomas Bogendoerfer 301862c2c0aSThomas Bogendoerfer /* mic mixer control */ 302e0f8cb5fSBill Pemberton static struct snd_kcontrol_new sgio2audio_ctrl_mic = { 303862c2c0aSThomas Bogendoerfer .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 304862c2c0aSThomas Bogendoerfer .name = "Mic Playback Volume", 305862c2c0aSThomas Bogendoerfer .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 306862c2c0aSThomas Bogendoerfer .private_value = AD1843_GAIN_MIC, 307862c2c0aSThomas Bogendoerfer .info = sgio2audio_gain_info, 308862c2c0aSThomas Bogendoerfer .get = sgio2audio_gain_get, 309862c2c0aSThomas Bogendoerfer .put = sgio2audio_gain_put, 310862c2c0aSThomas Bogendoerfer }; 311862c2c0aSThomas Bogendoerfer 312862c2c0aSThomas Bogendoerfer 313e0f8cb5fSBill Pemberton static int snd_sgio2audio_new_mixer(struct snd_sgio2audio *chip) 314862c2c0aSThomas Bogendoerfer { 315862c2c0aSThomas Bogendoerfer int err; 316862c2c0aSThomas Bogendoerfer 317862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 318862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_pcm0, chip)); 319862c2c0aSThomas Bogendoerfer if (err < 0) 320862c2c0aSThomas Bogendoerfer return err; 321862c2c0aSThomas Bogendoerfer 322862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 323862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_pcm1, chip)); 324862c2c0aSThomas Bogendoerfer if (err < 0) 325862c2c0aSThomas Bogendoerfer return err; 326862c2c0aSThomas Bogendoerfer 327862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 328862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_reclevel, chip)); 329862c2c0aSThomas Bogendoerfer if (err < 0) 330862c2c0aSThomas Bogendoerfer return err; 331862c2c0aSThomas Bogendoerfer 332862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 333862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_recsource, chip)); 334862c2c0aSThomas Bogendoerfer if (err < 0) 335862c2c0aSThomas Bogendoerfer return err; 336862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 337862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_line, chip)); 338862c2c0aSThomas Bogendoerfer if (err < 0) 339862c2c0aSThomas Bogendoerfer return err; 340862c2c0aSThomas Bogendoerfer 341862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 342862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_cd, chip)); 343862c2c0aSThomas Bogendoerfer if (err < 0) 344862c2c0aSThomas Bogendoerfer return err; 345862c2c0aSThomas Bogendoerfer 346862c2c0aSThomas Bogendoerfer err = snd_ctl_add(chip->card, 347862c2c0aSThomas Bogendoerfer snd_ctl_new1(&sgio2audio_ctrl_mic, chip)); 348862c2c0aSThomas Bogendoerfer if (err < 0) 349862c2c0aSThomas Bogendoerfer return err; 350862c2c0aSThomas Bogendoerfer 351862c2c0aSThomas Bogendoerfer return 0; 352862c2c0aSThomas Bogendoerfer } 353862c2c0aSThomas Bogendoerfer 354862c2c0aSThomas Bogendoerfer /* low-level audio interface DMA */ 355862c2c0aSThomas Bogendoerfer 356862c2c0aSThomas Bogendoerfer /* get data out of bounce buffer, count must be a multiple of 32 */ 357862c2c0aSThomas Bogendoerfer /* returns 1 if a period has elapsed */ 358862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_pull_frag(struct snd_sgio2audio *chip, 359862c2c0aSThomas Bogendoerfer unsigned int ch, unsigned int count) 360862c2c0aSThomas Bogendoerfer { 361862c2c0aSThomas Bogendoerfer int ret; 362862c2c0aSThomas Bogendoerfer unsigned long src_base, src_pos, dst_mask; 363862c2c0aSThomas Bogendoerfer unsigned char *dst_base; 364862c2c0aSThomas Bogendoerfer int dst_pos; 365862c2c0aSThomas Bogendoerfer u64 *src; 366862c2c0aSThomas Bogendoerfer s16 *dst; 367862c2c0aSThomas Bogendoerfer u64 x; 368862c2c0aSThomas Bogendoerfer unsigned long flags; 369862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; 370862c2c0aSThomas Bogendoerfer 371862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 372862c2c0aSThomas Bogendoerfer 373862c2c0aSThomas Bogendoerfer src_base = (unsigned long) chip->ring_base | (ch << CHANNEL_RING_SHIFT); 374862c2c0aSThomas Bogendoerfer src_pos = readq(&mace->perif.audio.chan[ch].read_ptr); 375862c2c0aSThomas Bogendoerfer dst_base = runtime->dma_area; 376862c2c0aSThomas Bogendoerfer dst_pos = chip->channel[ch].pos; 377862c2c0aSThomas Bogendoerfer dst_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; 378862c2c0aSThomas Bogendoerfer 379862c2c0aSThomas Bogendoerfer /* check if a period has elapsed */ 380862c2c0aSThomas Bogendoerfer chip->channel[ch].size += (count >> 3); /* in frames */ 381862c2c0aSThomas Bogendoerfer ret = chip->channel[ch].size >= runtime->period_size; 382862c2c0aSThomas Bogendoerfer chip->channel[ch].size %= runtime->period_size; 383862c2c0aSThomas Bogendoerfer 384862c2c0aSThomas Bogendoerfer while (count) { 385862c2c0aSThomas Bogendoerfer src = (u64 *)(src_base + src_pos); 386862c2c0aSThomas Bogendoerfer dst = (s16 *)(dst_base + dst_pos); 387862c2c0aSThomas Bogendoerfer 388862c2c0aSThomas Bogendoerfer x = *src; 389862c2c0aSThomas Bogendoerfer dst[0] = (x >> CHANNEL_LEFT_SHIFT) & 0xffff; 390862c2c0aSThomas Bogendoerfer dst[1] = (x >> CHANNEL_RIGHT_SHIFT) & 0xffff; 391862c2c0aSThomas Bogendoerfer 392862c2c0aSThomas Bogendoerfer src_pos = (src_pos + sizeof(u64)) & CHANNEL_RING_MASK; 393862c2c0aSThomas Bogendoerfer dst_pos = (dst_pos + 2 * sizeof(s16)) & dst_mask; 394862c2c0aSThomas Bogendoerfer count -= sizeof(u64); 395862c2c0aSThomas Bogendoerfer } 396862c2c0aSThomas Bogendoerfer 397862c2c0aSThomas Bogendoerfer writeq(src_pos, &mace->perif.audio.chan[ch].read_ptr); /* in bytes */ 398862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = dst_pos; 399862c2c0aSThomas Bogendoerfer 400862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 401862c2c0aSThomas Bogendoerfer return ret; 402862c2c0aSThomas Bogendoerfer } 403862c2c0aSThomas Bogendoerfer 404862c2c0aSThomas Bogendoerfer /* put some DMA data in bounce buffer, count must be a multiple of 32 */ 405862c2c0aSThomas Bogendoerfer /* returns 1 if a period has elapsed */ 406862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_push_frag(struct snd_sgio2audio *chip, 407862c2c0aSThomas Bogendoerfer unsigned int ch, unsigned int count) 408862c2c0aSThomas Bogendoerfer { 409862c2c0aSThomas Bogendoerfer int ret; 410862c2c0aSThomas Bogendoerfer s64 l, r; 411862c2c0aSThomas Bogendoerfer unsigned long dst_base, dst_pos, src_mask; 412862c2c0aSThomas Bogendoerfer unsigned char *src_base; 413862c2c0aSThomas Bogendoerfer int src_pos; 414862c2c0aSThomas Bogendoerfer u64 *dst; 415862c2c0aSThomas Bogendoerfer s16 *src; 416862c2c0aSThomas Bogendoerfer unsigned long flags; 417862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; 418862c2c0aSThomas Bogendoerfer 419862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 420862c2c0aSThomas Bogendoerfer 421862c2c0aSThomas Bogendoerfer dst_base = (unsigned long)chip->ring_base | (ch << CHANNEL_RING_SHIFT); 422862c2c0aSThomas Bogendoerfer dst_pos = readq(&mace->perif.audio.chan[ch].write_ptr); 423862c2c0aSThomas Bogendoerfer src_base = runtime->dma_area; 424862c2c0aSThomas Bogendoerfer src_pos = chip->channel[ch].pos; 425862c2c0aSThomas Bogendoerfer src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; 426862c2c0aSThomas Bogendoerfer 427862c2c0aSThomas Bogendoerfer /* check if a period has elapsed */ 428862c2c0aSThomas Bogendoerfer chip->channel[ch].size += (count >> 3); /* in frames */ 429862c2c0aSThomas Bogendoerfer ret = chip->channel[ch].size >= runtime->period_size; 430862c2c0aSThomas Bogendoerfer chip->channel[ch].size %= runtime->period_size; 431862c2c0aSThomas Bogendoerfer 432862c2c0aSThomas Bogendoerfer while (count) { 433862c2c0aSThomas Bogendoerfer src = (s16 *)(src_base + src_pos); 434862c2c0aSThomas Bogendoerfer dst = (u64 *)(dst_base + dst_pos); 435862c2c0aSThomas Bogendoerfer 436862c2c0aSThomas Bogendoerfer l = src[0]; /* sign extend */ 437862c2c0aSThomas Bogendoerfer r = src[1]; /* sign extend */ 438862c2c0aSThomas Bogendoerfer 439862c2c0aSThomas Bogendoerfer *dst = ((l & 0x00ffffff) << CHANNEL_LEFT_SHIFT) | 440862c2c0aSThomas Bogendoerfer ((r & 0x00ffffff) << CHANNEL_RIGHT_SHIFT); 441862c2c0aSThomas Bogendoerfer 442862c2c0aSThomas Bogendoerfer dst_pos = (dst_pos + sizeof(u64)) & CHANNEL_RING_MASK; 443862c2c0aSThomas Bogendoerfer src_pos = (src_pos + 2 * sizeof(s16)) & src_mask; 444862c2c0aSThomas Bogendoerfer count -= sizeof(u64); 445862c2c0aSThomas Bogendoerfer } 446862c2c0aSThomas Bogendoerfer 447862c2c0aSThomas Bogendoerfer writeq(dst_pos, &mace->perif.audio.chan[ch].write_ptr); /* in bytes */ 448862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = src_pos; 449862c2c0aSThomas Bogendoerfer 450862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 451862c2c0aSThomas Bogendoerfer return ret; 452862c2c0aSThomas Bogendoerfer } 453862c2c0aSThomas Bogendoerfer 454862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_start(struct snd_pcm_substream *substream) 455862c2c0aSThomas Bogendoerfer { 456862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 457862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 458862c2c0aSThomas Bogendoerfer int ch = chan->idx; 459862c2c0aSThomas Bogendoerfer 460862c2c0aSThomas Bogendoerfer /* reset DMA channel */ 461862c2c0aSThomas Bogendoerfer writeq(CHANNEL_CONTROL_RESET, &mace->perif.audio.chan[ch].control); 462862c2c0aSThomas Bogendoerfer udelay(10); 463862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.chan[ch].control); 464862c2c0aSThomas Bogendoerfer 465862c2c0aSThomas Bogendoerfer if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 466862c2c0aSThomas Bogendoerfer /* push a full buffer */ 467862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_push_frag(chip, ch, CHANNEL_RING_SIZE - 32); 468862c2c0aSThomas Bogendoerfer } 469862c2c0aSThomas Bogendoerfer /* set DMA to wake on 50% empty and enable interrupt */ 470862c2c0aSThomas Bogendoerfer writeq(CHANNEL_DMA_ENABLE | CHANNEL_INT_THRESHOLD_50, 471862c2c0aSThomas Bogendoerfer &mace->perif.audio.chan[ch].control); 472862c2c0aSThomas Bogendoerfer return 0; 473862c2c0aSThomas Bogendoerfer } 474862c2c0aSThomas Bogendoerfer 475862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dma_stop(struct snd_pcm_substream *substream) 476862c2c0aSThomas Bogendoerfer { 477862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 478862c2c0aSThomas Bogendoerfer 479862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.chan[chan->idx].control); 480862c2c0aSThomas Bogendoerfer return 0; 481862c2c0aSThomas Bogendoerfer } 482862c2c0aSThomas Bogendoerfer 483862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_dma_in_isr(int irq, void *dev_id) 484862c2c0aSThomas Bogendoerfer { 485862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 486862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 487862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 488862c2c0aSThomas Bogendoerfer int count, ch; 489862c2c0aSThomas Bogendoerfer 490862c2c0aSThomas Bogendoerfer substream = chan->substream; 491862c2c0aSThomas Bogendoerfer chip = snd_pcm_substream_chip(substream); 492862c2c0aSThomas Bogendoerfer ch = chan->idx; 493862c2c0aSThomas Bogendoerfer 494862c2c0aSThomas Bogendoerfer /* empty the ring */ 495862c2c0aSThomas Bogendoerfer count = CHANNEL_RING_SIZE - 496862c2c0aSThomas Bogendoerfer readq(&mace->perif.audio.chan[ch].depth) - 32; 497862c2c0aSThomas Bogendoerfer if (snd_sgio2audio_dma_pull_frag(chip, ch, count)) 498862c2c0aSThomas Bogendoerfer snd_pcm_period_elapsed(substream); 499862c2c0aSThomas Bogendoerfer 500862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 501862c2c0aSThomas Bogendoerfer } 502862c2c0aSThomas Bogendoerfer 503862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_dma_out_isr(int irq, void *dev_id) 504862c2c0aSThomas Bogendoerfer { 505862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 506862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 507862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 508862c2c0aSThomas Bogendoerfer int count, ch; 509862c2c0aSThomas Bogendoerfer 510862c2c0aSThomas Bogendoerfer substream = chan->substream; 511862c2c0aSThomas Bogendoerfer chip = snd_pcm_substream_chip(substream); 512862c2c0aSThomas Bogendoerfer ch = chan->idx; 513862c2c0aSThomas Bogendoerfer /* fill the ring */ 514862c2c0aSThomas Bogendoerfer count = CHANNEL_RING_SIZE - 515862c2c0aSThomas Bogendoerfer readq(&mace->perif.audio.chan[ch].depth) - 32; 516862c2c0aSThomas Bogendoerfer if (snd_sgio2audio_dma_push_frag(chip, ch, count)) 517862c2c0aSThomas Bogendoerfer snd_pcm_period_elapsed(substream); 518862c2c0aSThomas Bogendoerfer 519862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 520862c2c0aSThomas Bogendoerfer } 521862c2c0aSThomas Bogendoerfer 522862c2c0aSThomas Bogendoerfer static irqreturn_t snd_sgio2audio_error_isr(int irq, void *dev_id) 523862c2c0aSThomas Bogendoerfer { 524862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = dev_id; 525862c2c0aSThomas Bogendoerfer struct snd_pcm_substream *substream; 526862c2c0aSThomas Bogendoerfer 527862c2c0aSThomas Bogendoerfer substream = chan->substream; 528862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_stop(substream); 529862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_start(substream); 530862c2c0aSThomas Bogendoerfer return IRQ_HANDLED; 531862c2c0aSThomas Bogendoerfer } 532862c2c0aSThomas Bogendoerfer 533862c2c0aSThomas Bogendoerfer /* PCM part */ 534862c2c0aSThomas Bogendoerfer /* PCM hardware definition */ 535862c2c0aSThomas Bogendoerfer static struct snd_pcm_hardware snd_sgio2audio_pcm_hw = { 536862c2c0aSThomas Bogendoerfer .info = (SNDRV_PCM_INFO_MMAP | 537862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_MMAP_VALID | 538862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_INTERLEAVED | 539862c2c0aSThomas Bogendoerfer SNDRV_PCM_INFO_BLOCK_TRANSFER), 540862c2c0aSThomas Bogendoerfer .formats = SNDRV_PCM_FMTBIT_S16_BE, 541862c2c0aSThomas Bogendoerfer .rates = SNDRV_PCM_RATE_8000_48000, 542862c2c0aSThomas Bogendoerfer .rate_min = 8000, 543862c2c0aSThomas Bogendoerfer .rate_max = 48000, 544862c2c0aSThomas Bogendoerfer .channels_min = 2, 545862c2c0aSThomas Bogendoerfer .channels_max = 2, 546862c2c0aSThomas Bogendoerfer .buffer_bytes_max = 65536, 547862c2c0aSThomas Bogendoerfer .period_bytes_min = 32768, 548862c2c0aSThomas Bogendoerfer .period_bytes_max = 65536, 549862c2c0aSThomas Bogendoerfer .periods_min = 1, 550862c2c0aSThomas Bogendoerfer .periods_max = 1024, 551862c2c0aSThomas Bogendoerfer }; 552862c2c0aSThomas Bogendoerfer 553862c2c0aSThomas Bogendoerfer /* PCM playback open callback */ 554862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_playback1_open(struct snd_pcm_substream *substream) 555862c2c0aSThomas Bogendoerfer { 556862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 557862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 558862c2c0aSThomas Bogendoerfer 559862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 560862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[1]; 561862c2c0aSThomas Bogendoerfer return 0; 562862c2c0aSThomas Bogendoerfer } 563862c2c0aSThomas Bogendoerfer 564862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_playback2_open(struct snd_pcm_substream *substream) 565862c2c0aSThomas Bogendoerfer { 566862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 567862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 568862c2c0aSThomas Bogendoerfer 569862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 570862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[2]; 571862c2c0aSThomas Bogendoerfer return 0; 572862c2c0aSThomas Bogendoerfer } 573862c2c0aSThomas Bogendoerfer 574862c2c0aSThomas Bogendoerfer /* PCM capture open callback */ 575862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_capture_open(struct snd_pcm_substream *substream) 576862c2c0aSThomas Bogendoerfer { 577862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 578862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 579862c2c0aSThomas Bogendoerfer 580862c2c0aSThomas Bogendoerfer runtime->hw = snd_sgio2audio_pcm_hw; 581862c2c0aSThomas Bogendoerfer runtime->private_data = &chip->channel[0]; 582862c2c0aSThomas Bogendoerfer return 0; 583862c2c0aSThomas Bogendoerfer } 584862c2c0aSThomas Bogendoerfer 585862c2c0aSThomas Bogendoerfer /* PCM close callback */ 586862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_close(struct snd_pcm_substream *substream) 587862c2c0aSThomas Bogendoerfer { 588862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 589862c2c0aSThomas Bogendoerfer 590862c2c0aSThomas Bogendoerfer runtime->private_data = NULL; 591862c2c0aSThomas Bogendoerfer return 0; 592862c2c0aSThomas Bogendoerfer } 593862c2c0aSThomas Bogendoerfer 594862c2c0aSThomas Bogendoerfer 595862c2c0aSThomas Bogendoerfer /* hw_params callback */ 596862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, 597862c2c0aSThomas Bogendoerfer struct snd_pcm_hw_params *hw_params) 598862c2c0aSThomas Bogendoerfer { 5996cedf869SClemens Ladisch return snd_pcm_lib_alloc_vmalloc_buffer(substream, 6006cedf869SClemens Ladisch params_buffer_bytes(hw_params)); 601862c2c0aSThomas Bogendoerfer } 602862c2c0aSThomas Bogendoerfer 603862c2c0aSThomas Bogendoerfer /* hw_free callback */ 604862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream) 605862c2c0aSThomas Bogendoerfer { 6066cedf869SClemens Ladisch return snd_pcm_lib_free_vmalloc_buffer(substream); 607862c2c0aSThomas Bogendoerfer } 608862c2c0aSThomas Bogendoerfer 609862c2c0aSThomas Bogendoerfer /* prepare callback */ 610862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_prepare(struct snd_pcm_substream *substream) 611862c2c0aSThomas Bogendoerfer { 612862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 613862c2c0aSThomas Bogendoerfer struct snd_pcm_runtime *runtime = substream->runtime; 614862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 615862c2c0aSThomas Bogendoerfer int ch = chan->idx; 616862c2c0aSThomas Bogendoerfer unsigned long flags; 617862c2c0aSThomas Bogendoerfer 618862c2c0aSThomas Bogendoerfer spin_lock_irqsave(&chip->channel[ch].lock, flags); 619862c2c0aSThomas Bogendoerfer 620862c2c0aSThomas Bogendoerfer /* Setup the pseudo-dma transfer pointers. */ 621862c2c0aSThomas Bogendoerfer chip->channel[ch].pos = 0; 622862c2c0aSThomas Bogendoerfer chip->channel[ch].size = 0; 623862c2c0aSThomas Bogendoerfer chip->channel[ch].substream = substream; 624862c2c0aSThomas Bogendoerfer 625862c2c0aSThomas Bogendoerfer /* set AD1843 format */ 626862c2c0aSThomas Bogendoerfer /* hardware format is always S16_LE */ 627862c2c0aSThomas Bogendoerfer switch (substream->stream) { 628862c2c0aSThomas Bogendoerfer case SNDRV_PCM_STREAM_PLAYBACK: 629862c2c0aSThomas Bogendoerfer ad1843_setup_dac(&chip->ad1843, 630862c2c0aSThomas Bogendoerfer ch - 1, 631862c2c0aSThomas Bogendoerfer runtime->rate, 632862c2c0aSThomas Bogendoerfer SNDRV_PCM_FORMAT_S16_LE, 633862c2c0aSThomas Bogendoerfer runtime->channels); 634862c2c0aSThomas Bogendoerfer break; 635862c2c0aSThomas Bogendoerfer case SNDRV_PCM_STREAM_CAPTURE: 636862c2c0aSThomas Bogendoerfer ad1843_setup_adc(&chip->ad1843, 637862c2c0aSThomas Bogendoerfer runtime->rate, 638862c2c0aSThomas Bogendoerfer SNDRV_PCM_FORMAT_S16_LE, 639862c2c0aSThomas Bogendoerfer runtime->channels); 640862c2c0aSThomas Bogendoerfer break; 641862c2c0aSThomas Bogendoerfer } 642862c2c0aSThomas Bogendoerfer spin_unlock_irqrestore(&chip->channel[ch].lock, flags); 643862c2c0aSThomas Bogendoerfer return 0; 644862c2c0aSThomas Bogendoerfer } 645862c2c0aSThomas Bogendoerfer 646862c2c0aSThomas Bogendoerfer /* trigger callback */ 647862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_pcm_trigger(struct snd_pcm_substream *substream, 648862c2c0aSThomas Bogendoerfer int cmd) 649862c2c0aSThomas Bogendoerfer { 650862c2c0aSThomas Bogendoerfer switch (cmd) { 651862c2c0aSThomas Bogendoerfer case SNDRV_PCM_TRIGGER_START: 652862c2c0aSThomas Bogendoerfer /* start the PCM engine */ 653862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_start(substream); 654862c2c0aSThomas Bogendoerfer break; 655862c2c0aSThomas Bogendoerfer case SNDRV_PCM_TRIGGER_STOP: 656862c2c0aSThomas Bogendoerfer /* stop the PCM engine */ 657862c2c0aSThomas Bogendoerfer snd_sgio2audio_dma_stop(substream); 658862c2c0aSThomas Bogendoerfer break; 659862c2c0aSThomas Bogendoerfer default: 660862c2c0aSThomas Bogendoerfer return -EINVAL; 661862c2c0aSThomas Bogendoerfer } 662862c2c0aSThomas Bogendoerfer return 0; 663862c2c0aSThomas Bogendoerfer } 664862c2c0aSThomas Bogendoerfer 665862c2c0aSThomas Bogendoerfer /* pointer callback */ 666862c2c0aSThomas Bogendoerfer static snd_pcm_uframes_t 667862c2c0aSThomas Bogendoerfer snd_sgio2audio_pcm_pointer(struct snd_pcm_substream *substream) 668862c2c0aSThomas Bogendoerfer { 669862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); 670862c2c0aSThomas Bogendoerfer struct snd_sgio2audio_chan *chan = substream->runtime->private_data; 671862c2c0aSThomas Bogendoerfer 672862c2c0aSThomas Bogendoerfer /* get the current hardware pointer */ 673862c2c0aSThomas Bogendoerfer return bytes_to_frames(substream->runtime, 674862c2c0aSThomas Bogendoerfer chip->channel[chan->idx].pos); 675862c2c0aSThomas Bogendoerfer } 676862c2c0aSThomas Bogendoerfer 677862c2c0aSThomas Bogendoerfer /* operators */ 678862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_playback1_ops = { 679862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_playback1_open, 680862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 681862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 682862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 683862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 684862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 685862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 686862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 6876cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 688c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 689862c2c0aSThomas Bogendoerfer }; 690862c2c0aSThomas Bogendoerfer 691862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_playback2_ops = { 692862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_playback2_open, 693862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 694862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 695862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 696862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 697862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 698862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 699862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 7006cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 701c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 702862c2c0aSThomas Bogendoerfer }; 703862c2c0aSThomas Bogendoerfer 704862c2c0aSThomas Bogendoerfer static struct snd_pcm_ops snd_sgio2audio_capture_ops = { 705862c2c0aSThomas Bogendoerfer .open = snd_sgio2audio_capture_open, 706862c2c0aSThomas Bogendoerfer .close = snd_sgio2audio_pcm_close, 707862c2c0aSThomas Bogendoerfer .ioctl = snd_pcm_lib_ioctl, 708862c2c0aSThomas Bogendoerfer .hw_params = snd_sgio2audio_pcm_hw_params, 709862c2c0aSThomas Bogendoerfer .hw_free = snd_sgio2audio_pcm_hw_free, 710862c2c0aSThomas Bogendoerfer .prepare = snd_sgio2audio_pcm_prepare, 711862c2c0aSThomas Bogendoerfer .trigger = snd_sgio2audio_pcm_trigger, 712862c2c0aSThomas Bogendoerfer .pointer = snd_sgio2audio_pcm_pointer, 7136cedf869SClemens Ladisch .page = snd_pcm_lib_get_vmalloc_page, 714c32d977bSTakashi Iwai .mmap = snd_pcm_lib_mmap_vmalloc, 715862c2c0aSThomas Bogendoerfer }; 716862c2c0aSThomas Bogendoerfer 717862c2c0aSThomas Bogendoerfer /* 718862c2c0aSThomas Bogendoerfer * definitions of capture are omitted here... 719862c2c0aSThomas Bogendoerfer */ 720862c2c0aSThomas Bogendoerfer 721862c2c0aSThomas Bogendoerfer /* create a pcm device */ 722e0f8cb5fSBill Pemberton static int snd_sgio2audio_new_pcm(struct snd_sgio2audio *chip) 723862c2c0aSThomas Bogendoerfer { 724862c2c0aSThomas Bogendoerfer struct snd_pcm *pcm; 725862c2c0aSThomas Bogendoerfer int err; 726862c2c0aSThomas Bogendoerfer 727862c2c0aSThomas Bogendoerfer /* create first pcm device with one outputs and one input */ 728862c2c0aSThomas Bogendoerfer err = snd_pcm_new(chip->card, "SGI O2 Audio", 0, 1, 1, &pcm); 729862c2c0aSThomas Bogendoerfer if (err < 0) 730862c2c0aSThomas Bogendoerfer return err; 731862c2c0aSThomas Bogendoerfer 732862c2c0aSThomas Bogendoerfer pcm->private_data = chip; 733862c2c0aSThomas Bogendoerfer strcpy(pcm->name, "SGI O2 DAC1"); 734862c2c0aSThomas Bogendoerfer 735862c2c0aSThomas Bogendoerfer /* set operators */ 736862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 737862c2c0aSThomas Bogendoerfer &snd_sgio2audio_playback1_ops); 738862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, 739862c2c0aSThomas Bogendoerfer &snd_sgio2audio_capture_ops); 740862c2c0aSThomas Bogendoerfer 741862c2c0aSThomas Bogendoerfer /* create second pcm device with one outputs and no input */ 742862c2c0aSThomas Bogendoerfer err = snd_pcm_new(chip->card, "SGI O2 Audio", 1, 1, 0, &pcm); 743862c2c0aSThomas Bogendoerfer if (err < 0) 744862c2c0aSThomas Bogendoerfer return err; 745862c2c0aSThomas Bogendoerfer 746862c2c0aSThomas Bogendoerfer pcm->private_data = chip; 747862c2c0aSThomas Bogendoerfer strcpy(pcm->name, "SGI O2 DAC2"); 748862c2c0aSThomas Bogendoerfer 749862c2c0aSThomas Bogendoerfer /* set operators */ 750862c2c0aSThomas Bogendoerfer snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 751862c2c0aSThomas Bogendoerfer &snd_sgio2audio_playback2_ops); 752862c2c0aSThomas Bogendoerfer 753862c2c0aSThomas Bogendoerfer return 0; 754862c2c0aSThomas Bogendoerfer } 755862c2c0aSThomas Bogendoerfer 756862c2c0aSThomas Bogendoerfer static struct { 757862c2c0aSThomas Bogendoerfer int idx; 758862c2c0aSThomas Bogendoerfer int irq; 759862c2c0aSThomas Bogendoerfer irqreturn_t (*isr)(int, void *); 760862c2c0aSThomas Bogendoerfer const char *desc; 761862c2c0aSThomas Bogendoerfer } snd_sgio2_isr_table[] = { 762862c2c0aSThomas Bogendoerfer { 763862c2c0aSThomas Bogendoerfer .idx = 0, 764862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO1_DMAT_IRQ, 765862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_in_isr, 766862c2c0aSThomas Bogendoerfer .desc = "Capture DMA Channel 0" 767862c2c0aSThomas Bogendoerfer }, { 768862c2c0aSThomas Bogendoerfer .idx = 0, 769862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO1_OF_IRQ, 770862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 771862c2c0aSThomas Bogendoerfer .desc = "Capture Overflow" 772862c2c0aSThomas Bogendoerfer }, { 773862c2c0aSThomas Bogendoerfer .idx = 1, 774862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO2_DMAT_IRQ, 775862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_out_isr, 776862c2c0aSThomas Bogendoerfer .desc = "Playback DMA Channel 1" 777862c2c0aSThomas Bogendoerfer }, { 778862c2c0aSThomas Bogendoerfer .idx = 1, 779862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO2_MERR_IRQ, 780862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 781862c2c0aSThomas Bogendoerfer .desc = "Memory Error Channel 1" 782862c2c0aSThomas Bogendoerfer }, { 783862c2c0aSThomas Bogendoerfer .idx = 2, 784862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO3_DMAT_IRQ, 785862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_dma_out_isr, 786862c2c0aSThomas Bogendoerfer .desc = "Playback DMA Channel 2" 787862c2c0aSThomas Bogendoerfer }, { 788862c2c0aSThomas Bogendoerfer .idx = 2, 789862c2c0aSThomas Bogendoerfer .irq = MACEISA_AUDIO3_MERR_IRQ, 790862c2c0aSThomas Bogendoerfer .isr = snd_sgio2audio_error_isr, 791862c2c0aSThomas Bogendoerfer .desc = "Memory Error Channel 2" 792862c2c0aSThomas Bogendoerfer } 793862c2c0aSThomas Bogendoerfer }; 794862c2c0aSThomas Bogendoerfer 795862c2c0aSThomas Bogendoerfer /* ALSA driver */ 796862c2c0aSThomas Bogendoerfer 797862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_free(struct snd_sgio2audio *chip) 798862c2c0aSThomas Bogendoerfer { 799862c2c0aSThomas Bogendoerfer int i; 800862c2c0aSThomas Bogendoerfer 801862c2c0aSThomas Bogendoerfer /* reset interface */ 802862c2c0aSThomas Bogendoerfer writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); 803862c2c0aSThomas Bogendoerfer udelay(1); 804862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.control); 805862c2c0aSThomas Bogendoerfer 806862c2c0aSThomas Bogendoerfer /* release IRQ's */ 807862c2c0aSThomas Bogendoerfer for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) 808862c2c0aSThomas Bogendoerfer free_irq(snd_sgio2_isr_table[i].irq, 809862c2c0aSThomas Bogendoerfer &chip->channel[snd_sgio2_isr_table[i].idx]); 810862c2c0aSThomas Bogendoerfer 811862c2c0aSThomas Bogendoerfer dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, 812862c2c0aSThomas Bogendoerfer chip->ring_base, chip->ring_base_dma); 813862c2c0aSThomas Bogendoerfer 814862c2c0aSThomas Bogendoerfer /* release card data */ 815862c2c0aSThomas Bogendoerfer kfree(chip); 816862c2c0aSThomas Bogendoerfer return 0; 817862c2c0aSThomas Bogendoerfer } 818862c2c0aSThomas Bogendoerfer 819862c2c0aSThomas Bogendoerfer static int snd_sgio2audio_dev_free(struct snd_device *device) 820862c2c0aSThomas Bogendoerfer { 821862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip = device->device_data; 822862c2c0aSThomas Bogendoerfer 823862c2c0aSThomas Bogendoerfer return snd_sgio2audio_free(chip); 824862c2c0aSThomas Bogendoerfer } 825862c2c0aSThomas Bogendoerfer 826862c2c0aSThomas Bogendoerfer static struct snd_device_ops ops = { 827862c2c0aSThomas Bogendoerfer .dev_free = snd_sgio2audio_dev_free, 828862c2c0aSThomas Bogendoerfer }; 829862c2c0aSThomas Bogendoerfer 830e0f8cb5fSBill Pemberton static int snd_sgio2audio_create(struct snd_card *card, 831862c2c0aSThomas Bogendoerfer struct snd_sgio2audio **rchip) 832862c2c0aSThomas Bogendoerfer { 833862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 834862c2c0aSThomas Bogendoerfer int i, err; 835862c2c0aSThomas Bogendoerfer 836862c2c0aSThomas Bogendoerfer *rchip = NULL; 837862c2c0aSThomas Bogendoerfer 838862c2c0aSThomas Bogendoerfer /* check if a codec is attached to the interface */ 839862c2c0aSThomas Bogendoerfer /* (Audio or Audio/Video board present) */ 840862c2c0aSThomas Bogendoerfer if (!(readq(&mace->perif.audio.control) & AUDIO_CONTROL_CODEC_PRESENT)) 841862c2c0aSThomas Bogendoerfer return -ENOENT; 842862c2c0aSThomas Bogendoerfer 843862c2c0aSThomas Bogendoerfer chip = kzalloc(sizeof(struct snd_sgio2audio), GFP_KERNEL); 844862c2c0aSThomas Bogendoerfer if (chip == NULL) 845862c2c0aSThomas Bogendoerfer return -ENOMEM; 846862c2c0aSThomas Bogendoerfer 847862c2c0aSThomas Bogendoerfer chip->card = card; 848862c2c0aSThomas Bogendoerfer 849862c2c0aSThomas Bogendoerfer chip->ring_base = dma_alloc_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, 850862c2c0aSThomas Bogendoerfer &chip->ring_base_dma, GFP_USER); 851862c2c0aSThomas Bogendoerfer if (chip->ring_base == NULL) { 852862c2c0aSThomas Bogendoerfer printk(KERN_ERR 853862c2c0aSThomas Bogendoerfer "sgio2audio: could not allocate ring buffers\n"); 854862c2c0aSThomas Bogendoerfer kfree(chip); 855862c2c0aSThomas Bogendoerfer return -ENOMEM; 856862c2c0aSThomas Bogendoerfer } 857862c2c0aSThomas Bogendoerfer 858862c2c0aSThomas Bogendoerfer spin_lock_init(&chip->ad1843_lock); 859862c2c0aSThomas Bogendoerfer 860862c2c0aSThomas Bogendoerfer /* initialize channels */ 861862c2c0aSThomas Bogendoerfer for (i = 0; i < 3; i++) { 862862c2c0aSThomas Bogendoerfer spin_lock_init(&chip->channel[i].lock); 863862c2c0aSThomas Bogendoerfer chip->channel[i].idx = i; 864862c2c0aSThomas Bogendoerfer } 865862c2c0aSThomas Bogendoerfer 866862c2c0aSThomas Bogendoerfer /* allocate IRQs */ 867862c2c0aSThomas Bogendoerfer for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) { 868862c2c0aSThomas Bogendoerfer if (request_irq(snd_sgio2_isr_table[i].irq, 869862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].isr, 870862c2c0aSThomas Bogendoerfer 0, 871862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].desc, 872862c2c0aSThomas Bogendoerfer &chip->channel[snd_sgio2_isr_table[i].idx])) { 873862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 874862c2c0aSThomas Bogendoerfer printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", 875862c2c0aSThomas Bogendoerfer snd_sgio2_isr_table[i].irq); 876862c2c0aSThomas Bogendoerfer return -EBUSY; 877862c2c0aSThomas Bogendoerfer } 878862c2c0aSThomas Bogendoerfer } 879862c2c0aSThomas Bogendoerfer 880862c2c0aSThomas Bogendoerfer /* reset the interface */ 881862c2c0aSThomas Bogendoerfer writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); 882862c2c0aSThomas Bogendoerfer udelay(1); 883862c2c0aSThomas Bogendoerfer writeq(0, &mace->perif.audio.control); 884862c2c0aSThomas Bogendoerfer msleep_interruptible(1); /* give time to recover */ 885862c2c0aSThomas Bogendoerfer 886862c2c0aSThomas Bogendoerfer /* set ring base */ 887862c2c0aSThomas Bogendoerfer writeq(chip->ring_base_dma, &mace->perif.ctrl.ringbase); 888862c2c0aSThomas Bogendoerfer 889862c2c0aSThomas Bogendoerfer /* attach the AD1843 codec */ 890862c2c0aSThomas Bogendoerfer chip->ad1843.read = read_ad1843_reg; 891862c2c0aSThomas Bogendoerfer chip->ad1843.write = write_ad1843_reg; 892862c2c0aSThomas Bogendoerfer chip->ad1843.chip = chip; 893862c2c0aSThomas Bogendoerfer 894862c2c0aSThomas Bogendoerfer /* initialize the AD1843 codec */ 895862c2c0aSThomas Bogendoerfer err = ad1843_init(&chip->ad1843); 896862c2c0aSThomas Bogendoerfer if (err < 0) { 897862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 898862c2c0aSThomas Bogendoerfer return err; 899862c2c0aSThomas Bogendoerfer } 900862c2c0aSThomas Bogendoerfer 901862c2c0aSThomas Bogendoerfer err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); 902862c2c0aSThomas Bogendoerfer if (err < 0) { 903862c2c0aSThomas Bogendoerfer snd_sgio2audio_free(chip); 904862c2c0aSThomas Bogendoerfer return err; 905862c2c0aSThomas Bogendoerfer } 906862c2c0aSThomas Bogendoerfer *rchip = chip; 907862c2c0aSThomas Bogendoerfer return 0; 908862c2c0aSThomas Bogendoerfer } 909862c2c0aSThomas Bogendoerfer 910e0f8cb5fSBill Pemberton static int snd_sgio2audio_probe(struct platform_device *pdev) 911862c2c0aSThomas Bogendoerfer { 912862c2c0aSThomas Bogendoerfer struct snd_card *card; 913862c2c0aSThomas Bogendoerfer struct snd_sgio2audio *chip; 914862c2c0aSThomas Bogendoerfer int err; 915862c2c0aSThomas Bogendoerfer 916bee1bb19STakashi Iwai err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); 917bd7dd77cSTakashi Iwai if (err < 0) 918bd7dd77cSTakashi Iwai return err; 919862c2c0aSThomas Bogendoerfer 920862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_create(card, &chip); 921862c2c0aSThomas Bogendoerfer if (err < 0) { 922862c2c0aSThomas Bogendoerfer snd_card_free(card); 923862c2c0aSThomas Bogendoerfer return err; 924862c2c0aSThomas Bogendoerfer } 925862c2c0aSThomas Bogendoerfer 926862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_new_pcm(chip); 927862c2c0aSThomas Bogendoerfer if (err < 0) { 928862c2c0aSThomas Bogendoerfer snd_card_free(card); 929862c2c0aSThomas Bogendoerfer return err; 930862c2c0aSThomas Bogendoerfer } 931862c2c0aSThomas Bogendoerfer err = snd_sgio2audio_new_mixer(chip); 932862c2c0aSThomas Bogendoerfer if (err < 0) { 933862c2c0aSThomas Bogendoerfer snd_card_free(card); 934862c2c0aSThomas Bogendoerfer return err; 935862c2c0aSThomas Bogendoerfer } 936862c2c0aSThomas Bogendoerfer 937862c2c0aSThomas Bogendoerfer strcpy(card->driver, "SGI O2 Audio"); 938862c2c0aSThomas Bogendoerfer strcpy(card->shortname, "SGI O2 Audio"); 939862c2c0aSThomas Bogendoerfer sprintf(card->longname, "%s irq %i-%i", 940862c2c0aSThomas Bogendoerfer card->shortname, 941862c2c0aSThomas Bogendoerfer MACEISA_AUDIO1_DMAT_IRQ, 942862c2c0aSThomas Bogendoerfer MACEISA_AUDIO3_MERR_IRQ); 943862c2c0aSThomas Bogendoerfer 944862c2c0aSThomas Bogendoerfer err = snd_card_register(card); 945862c2c0aSThomas Bogendoerfer if (err < 0) { 946862c2c0aSThomas Bogendoerfer snd_card_free(card); 947862c2c0aSThomas Bogendoerfer return err; 948862c2c0aSThomas Bogendoerfer } 949862c2c0aSThomas Bogendoerfer platform_set_drvdata(pdev, card); 950862c2c0aSThomas Bogendoerfer return 0; 951862c2c0aSThomas Bogendoerfer } 952862c2c0aSThomas Bogendoerfer 953e0f8cb5fSBill Pemberton static int snd_sgio2audio_remove(struct platform_device *pdev) 954862c2c0aSThomas Bogendoerfer { 955862c2c0aSThomas Bogendoerfer struct snd_card *card = platform_get_drvdata(pdev); 956862c2c0aSThomas Bogendoerfer 957862c2c0aSThomas Bogendoerfer snd_card_free(card); 958862c2c0aSThomas Bogendoerfer return 0; 959862c2c0aSThomas Bogendoerfer } 960862c2c0aSThomas Bogendoerfer 961862c2c0aSThomas Bogendoerfer static struct platform_driver sgio2audio_driver = { 962862c2c0aSThomas Bogendoerfer .probe = snd_sgio2audio_probe, 963e0f8cb5fSBill Pemberton .remove = snd_sgio2audio_remove, 964862c2c0aSThomas Bogendoerfer .driver = { 965862c2c0aSThomas Bogendoerfer .name = "sgio2audio", 966862c2c0aSThomas Bogendoerfer .owner = THIS_MODULE, 967862c2c0aSThomas Bogendoerfer } 968862c2c0aSThomas Bogendoerfer }; 969862c2c0aSThomas Bogendoerfer 97051451b8dSAxel Lin module_platform_driver(sgio2audio_driver); 971