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