xref: /openbmc/linux/sound/parisc/harmony.c (revision 3a165680168758733b7a9f7fb835954fbe6b91a8)
11da177e4SLinus Torvalds /* Hewlett-Packard Harmony audio driver
21da177e4SLinus Torvalds  *
31da177e4SLinus Torvalds  *   This is a driver for the Harmony audio chipset found
41da177e4SLinus Torvalds  *   on the LASI ASIC of various early HP PA-RISC workstations.
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  *   Copyright (C) 2004, Kyle McMartin <kyle@{debian.org,parisc-linux.org}>
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  *     Based on the previous Harmony incarnations by,
91da177e4SLinus Torvalds  *       Copyright 2000 (c) Linuxcare Canada, Alex deVries
101da177e4SLinus Torvalds  *       Copyright 2000-2003 (c) Helge Deller
111da177e4SLinus Torvalds  *       Copyright 2001 (c) Matthieu Delahaye
121da177e4SLinus Torvalds  *       Copyright 2001 (c) Jean-Christophe Vaugeois
131da177e4SLinus Torvalds  *       Copyright 2003 (c) Laurent Canet
141da177e4SLinus Torvalds  *       Copyright 2004 (c) Stuart Brady
151da177e4SLinus Torvalds  *
161da177e4SLinus Torvalds  *   This program is free software; you can redistribute it and/or modify
171da177e4SLinus Torvalds  *   it under the terms of the GNU General Public License, version 2, as
181da177e4SLinus Torvalds  *   published by the Free Software Foundation.
191da177e4SLinus Torvalds  *
201da177e4SLinus Torvalds  *   This program is distributed in the hope that it will be useful,
211da177e4SLinus Torvalds  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
221da177e4SLinus Torvalds  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
231da177e4SLinus Torvalds  *   GNU General Public License for more details.
241da177e4SLinus Torvalds  *
251da177e4SLinus Torvalds  *   You should have received a copy of the GNU General Public License
261da177e4SLinus Torvalds  *   along with this program; if not, write to the Free Software
271da177e4SLinus Torvalds  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
281da177e4SLinus Torvalds  *
291da177e4SLinus Torvalds  * Notes:
301da177e4SLinus Torvalds  *   - graveyard and silence buffers last for lifetime of
311da177e4SLinus Torvalds  *     the driver. playback and capture buffers are allocated
321da177e4SLinus Torvalds  *     per _open()/_close().
331da177e4SLinus Torvalds  *
341da177e4SLinus Torvalds  * TODO:
351da177e4SLinus Torvalds  *
361da177e4SLinus Torvalds  */
371da177e4SLinus Torvalds 
381da177e4SLinus Torvalds #include <linux/init.h>
391da177e4SLinus Torvalds #include <linux/slab.h>
401da177e4SLinus Torvalds #include <linux/time.h>
411da177e4SLinus Torvalds #include <linux/wait.h>
421da177e4SLinus Torvalds #include <linux/delay.h>
431da177e4SLinus Torvalds #include <linux/module.h>
441da177e4SLinus Torvalds #include <linux/interrupt.h>
451da177e4SLinus Torvalds #include <linux/spinlock.h>
461da177e4SLinus Torvalds #include <linux/dma-mapping.h>
471da177e4SLinus Torvalds 
481da177e4SLinus Torvalds #include <sound/driver.h>
491da177e4SLinus Torvalds #include <sound/core.h>
501da177e4SLinus Torvalds #include <sound/pcm.h>
511da177e4SLinus Torvalds #include <sound/control.h>
521da177e4SLinus Torvalds #include <sound/rawmidi.h>
531da177e4SLinus Torvalds #include <sound/initval.h>
541da177e4SLinus Torvalds #include <sound/info.h>
551da177e4SLinus Torvalds 
561da177e4SLinus Torvalds #include <asm/io.h>
571da177e4SLinus Torvalds #include <asm/hardware.h>
581da177e4SLinus Torvalds #include <asm/parisc-device.h>
591da177e4SLinus Torvalds 
601da177e4SLinus Torvalds #include "harmony.h"
611da177e4SLinus Torvalds 
621da177e4SLinus Torvalds static struct parisc_device_id snd_harmony_devtable[] = {
631da177e4SLinus Torvalds 	/* bushmaster / flounder */
641da177e4SLinus Torvalds 	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A },
651da177e4SLinus Torvalds 	/* 712 / 715 */
661da177e4SLinus Torvalds 	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B },
671da177e4SLinus Torvalds 	/* pace */
681da177e4SLinus Torvalds 	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E },
691da177e4SLinus Torvalds 	/* outfield / coral II */
701da177e4SLinus Torvalds 	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F },
711da177e4SLinus Torvalds 	{ 0, }
721da177e4SLinus Torvalds };
731da177e4SLinus Torvalds 
741da177e4SLinus Torvalds MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable);
751da177e4SLinus Torvalds 
761da177e4SLinus Torvalds #define NAME "harmony"
771da177e4SLinus Torvalds #define PFX  NAME ": "
781da177e4SLinus Torvalds 
791da177e4SLinus Torvalds static unsigned int snd_harmony_rates[] = {
801da177e4SLinus Torvalds 	5512, 6615, 8000, 9600,
811da177e4SLinus Torvalds 	11025, 16000, 18900, 22050,
821da177e4SLinus Torvalds 	27428, 32000, 33075, 37800,
831da177e4SLinus Torvalds 	44100, 48000
841da177e4SLinus Torvalds };
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds static unsigned int rate_bits[14] = {
871da177e4SLinus Torvalds 	HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ,
881da177e4SLinus Torvalds 	HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ,
891da177e4SLinus Torvalds 	HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ,
901da177e4SLinus Torvalds 	HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ,
911da177e4SLinus Torvalds 	HARMONY_SR_44KHZ, HARMONY_SR_48KHZ
921da177e4SLinus Torvalds };
931da177e4SLinus Torvalds 
941da177e4SLinus Torvalds static snd_pcm_hw_constraint_list_t hw_constraint_rates = {
951da177e4SLinus Torvalds 	.count = ARRAY_SIZE(snd_harmony_rates),
961da177e4SLinus Torvalds 	.list = snd_harmony_rates,
971da177e4SLinus Torvalds 	.mask = 0,
981da177e4SLinus Torvalds };
991da177e4SLinus Torvalds 
1001da177e4SLinus Torvalds inline unsigned long
1011da177e4SLinus Torvalds harmony_read(harmony_t *h, unsigned r)
1021da177e4SLinus Torvalds {
1031da177e4SLinus Torvalds 	return __raw_readl(h->iobase + r);
1041da177e4SLinus Torvalds }
1051da177e4SLinus Torvalds 
1061da177e4SLinus Torvalds inline void
1071da177e4SLinus Torvalds harmony_write(harmony_t *h, unsigned r, unsigned long v)
1081da177e4SLinus Torvalds {
1091da177e4SLinus Torvalds 	__raw_writel(v, h->iobase + r);
1101da177e4SLinus Torvalds }
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds static void
1131da177e4SLinus Torvalds harmony_wait_for_control(harmony_t *h)
1141da177e4SLinus Torvalds {
1151da177e4SLinus Torvalds 	while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ;
1161da177e4SLinus Torvalds }
1171da177e4SLinus Torvalds 
1181da177e4SLinus Torvalds inline void
1191da177e4SLinus Torvalds harmony_reset(harmony_t *h)
1201da177e4SLinus Torvalds {
1211da177e4SLinus Torvalds 	harmony_write(h, HARMONY_RESET, 1);
1221da177e4SLinus Torvalds 	mdelay(50);
1231da177e4SLinus Torvalds 	harmony_write(h, HARMONY_RESET, 0);
1241da177e4SLinus Torvalds }
1251da177e4SLinus Torvalds 
1261da177e4SLinus Torvalds static void
1271da177e4SLinus Torvalds harmony_disable_interrupts(harmony_t *h)
1281da177e4SLinus Torvalds {
1291da177e4SLinus Torvalds 	u32 dstatus;
1301da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1311da177e4SLinus Torvalds 	dstatus = harmony_read(h, HARMONY_DSTATUS);
1321da177e4SLinus Torvalds 	dstatus &= ~HARMONY_DSTATUS_IE;
1331da177e4SLinus Torvalds 	harmony_write(h, HARMONY_DSTATUS, dstatus);
1341da177e4SLinus Torvalds }
1351da177e4SLinus Torvalds 
1361da177e4SLinus Torvalds static void
1371da177e4SLinus Torvalds harmony_enable_interrupts(harmony_t *h)
1381da177e4SLinus Torvalds {
1391da177e4SLinus Torvalds 	u32 dstatus;
1401da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1411da177e4SLinus Torvalds 	dstatus = harmony_read(h, HARMONY_DSTATUS);
1421da177e4SLinus Torvalds 	dstatus |= HARMONY_DSTATUS_IE;
1431da177e4SLinus Torvalds 	harmony_write(h, HARMONY_DSTATUS, dstatus);
1441da177e4SLinus Torvalds }
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds static void
1471da177e4SLinus Torvalds harmony_mute(harmony_t *h)
1481da177e4SLinus Torvalds {
1491da177e4SLinus Torvalds 	unsigned long flags;
1501da177e4SLinus Torvalds 
1511da177e4SLinus Torvalds 	spin_lock_irqsave(&h->mixer_lock, flags);
1521da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1531da177e4SLinus Torvalds 	harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE);
1541da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->mixer_lock, flags);
1551da177e4SLinus Torvalds }
1561da177e4SLinus Torvalds 
1571da177e4SLinus Torvalds static void
1581da177e4SLinus Torvalds harmony_unmute(harmony_t *h)
1591da177e4SLinus Torvalds {
1601da177e4SLinus Torvalds 	unsigned long flags;
1611da177e4SLinus Torvalds 
1621da177e4SLinus Torvalds 	spin_lock_irqsave(&h->mixer_lock, flags);
1631da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1641da177e4SLinus Torvalds 	harmony_write(h, HARMONY_GAINCTL, h->st.gain);
1651da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->mixer_lock, flags);
1661da177e4SLinus Torvalds }
1671da177e4SLinus Torvalds 
1681da177e4SLinus Torvalds static void
1691da177e4SLinus Torvalds harmony_set_control(harmony_t *h)
1701da177e4SLinus Torvalds {
1711da177e4SLinus Torvalds 	u32 ctrl;
1721da177e4SLinus Torvalds 	unsigned long flags;
1731da177e4SLinus Torvalds 
1741da177e4SLinus Torvalds 	spin_lock_irqsave(&h->lock, flags);
1751da177e4SLinus Torvalds 
1761da177e4SLinus Torvalds 	ctrl = (HARMONY_CNTL_C      |
1771da177e4SLinus Torvalds 		(h->st.format << 6) |
1781da177e4SLinus Torvalds 		(h->st.stereo << 5) |
1791da177e4SLinus Torvalds 		(h->st.rate));
1801da177e4SLinus Torvalds 
1811da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1821da177e4SLinus Torvalds 	harmony_write(h, HARMONY_CNTL, ctrl);
1831da177e4SLinus Torvalds 
1841da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->lock, flags);
1851da177e4SLinus Torvalds }
1861da177e4SLinus Torvalds 
1871da177e4SLinus Torvalds static irqreturn_t
1881da177e4SLinus Torvalds snd_harmony_interrupt(int irq, void *dev, struct pt_regs *regs)
1891da177e4SLinus Torvalds {
1901da177e4SLinus Torvalds 	u32 dstatus;
1911da177e4SLinus Torvalds 	harmony_t *h = dev;
1921da177e4SLinus Torvalds 
1931da177e4SLinus Torvalds 	spin_lock(&h->lock);
1941da177e4SLinus Torvalds 	harmony_disable_interrupts(h);
1951da177e4SLinus Torvalds 	harmony_wait_for_control(h);
1961da177e4SLinus Torvalds 	dstatus = harmony_read(h, HARMONY_DSTATUS);
1971da177e4SLinus Torvalds 	spin_unlock(&h->lock);
1981da177e4SLinus Torvalds 
1991da177e4SLinus Torvalds 	if (dstatus & HARMONY_DSTATUS_PN) {
200*3a165680SStuart Brady 		if (h->psubs && h->st.playing) {
2011da177e4SLinus Torvalds 			spin_lock(&h->lock);
2021da177e4SLinus Torvalds 			h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */
2031da177e4SLinus Torvalds 			h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */
2041da177e4SLinus Torvalds 
2051da177e4SLinus Torvalds 			harmony_write(h, HARMONY_PNXTADD,
2061da177e4SLinus Torvalds 				      h->pbuf.addr + h->pbuf.buf);
2071da177e4SLinus Torvalds 			h->stats.play_intr++;
2081da177e4SLinus Torvalds 			spin_unlock(&h->lock);
2091da177e4SLinus Torvalds                         snd_pcm_period_elapsed(h->psubs);
2101da177e4SLinus Torvalds 		} else {
2111da177e4SLinus Torvalds 			spin_lock(&h->lock);
2121da177e4SLinus Torvalds 			harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
2131da177e4SLinus Torvalds 			h->stats.silence_intr++;
2141da177e4SLinus Torvalds 			spin_unlock(&h->lock);
2151da177e4SLinus Torvalds 		}
2161da177e4SLinus Torvalds 	}
2171da177e4SLinus Torvalds 
2181da177e4SLinus Torvalds 	if (dstatus & HARMONY_DSTATUS_RN) {
219*3a165680SStuart Brady 		if (h->csubs && h->st.capturing) {
2201da177e4SLinus Torvalds 			spin_lock(&h->lock);
2211da177e4SLinus Torvalds 			h->cbuf.buf += h->cbuf.count;
2221da177e4SLinus Torvalds 			h->cbuf.buf %= h->cbuf.size;
2231da177e4SLinus Torvalds 
2241da177e4SLinus Torvalds 			harmony_write(h, HARMONY_RNXTADD,
2251da177e4SLinus Torvalds 				      h->cbuf.addr + h->cbuf.buf);
2261da177e4SLinus Torvalds 			h->stats.rec_intr++;
2271da177e4SLinus Torvalds 			spin_unlock(&h->lock);
2281da177e4SLinus Torvalds                         snd_pcm_period_elapsed(h->csubs);
2291da177e4SLinus Torvalds 		} else {
2301da177e4SLinus Torvalds 			spin_lock(&h->lock);
2311da177e4SLinus Torvalds 			harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
2321da177e4SLinus Torvalds 			h->stats.graveyard_intr++;
2331da177e4SLinus Torvalds 			spin_unlock(&h->lock);
2341da177e4SLinus Torvalds 		}
2351da177e4SLinus Torvalds 	}
2361da177e4SLinus Torvalds 
2371da177e4SLinus Torvalds 	spin_lock(&h->lock);
2381da177e4SLinus Torvalds 	harmony_enable_interrupts(h);
2391da177e4SLinus Torvalds 	spin_unlock(&h->lock);
2401da177e4SLinus Torvalds 
2411da177e4SLinus Torvalds 	return IRQ_HANDLED;
2421da177e4SLinus Torvalds }
2431da177e4SLinus Torvalds 
2441da177e4SLinus Torvalds static unsigned int
2451da177e4SLinus Torvalds snd_harmony_rate_bits(int rate)
2461da177e4SLinus Torvalds {
2471da177e4SLinus Torvalds 	unsigned int i;
2481da177e4SLinus Torvalds 
2491da177e4SLinus Torvalds 	for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++)
2501da177e4SLinus Torvalds 		if (snd_harmony_rates[i] == rate)
2511da177e4SLinus Torvalds 			return rate_bits[i];
2521da177e4SLinus Torvalds 
2531da177e4SLinus Torvalds 	return HARMONY_SR_44KHZ;
2541da177e4SLinus Torvalds }
2551da177e4SLinus Torvalds 
2561da177e4SLinus Torvalds static snd_pcm_hardware_t snd_harmony_playback =
2571da177e4SLinus Torvalds {
2581da177e4SLinus Torvalds 	.info =	(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
2591da177e4SLinus Torvalds 		 SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID |
2601da177e4SLinus Torvalds 		 SNDRV_PCM_INFO_BLOCK_TRANSFER),
2611da177e4SLinus Torvalds 	.formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW |
2621da177e4SLinus Torvalds 		    SNDRV_PCM_FMTBIT_A_LAW),
2631da177e4SLinus Torvalds 	.rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 |
2641da177e4SLinus Torvalds 		  SNDRV_PCM_RATE_KNOT),
2651da177e4SLinus Torvalds 	.rate_min = 5512,
2661da177e4SLinus Torvalds 	.rate_max = 48000,
2671da177e4SLinus Torvalds 	.channels_min =	1,
2681da177e4SLinus Torvalds 	.channels_max =	2,
2691da177e4SLinus Torvalds 	.buffer_bytes_max = MAX_BUF_SIZE,
2701da177e4SLinus Torvalds 	.period_bytes_min = BUF_SIZE,
2711da177e4SLinus Torvalds 	.period_bytes_max = BUF_SIZE,
2721da177e4SLinus Torvalds 	.periods_min = 1,
2731da177e4SLinus Torvalds 	.periods_max = MAX_BUFS,
2741da177e4SLinus Torvalds 	.fifo_size = 0,
2751da177e4SLinus Torvalds };
2761da177e4SLinus Torvalds 
2771da177e4SLinus Torvalds static snd_pcm_hardware_t snd_harmony_capture =
2781da177e4SLinus Torvalds {
2791da177e4SLinus Torvalds         .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
2801da177e4SLinus Torvalds                  SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID |
2811da177e4SLinus Torvalds                  SNDRV_PCM_INFO_BLOCK_TRANSFER),
2821da177e4SLinus Torvalds         .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW |
2831da177e4SLinus Torvalds                     SNDRV_PCM_FMTBIT_A_LAW),
2841da177e4SLinus Torvalds         .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 |
2851da177e4SLinus Torvalds 		  SNDRV_PCM_RATE_KNOT),
2861da177e4SLinus Torvalds         .rate_min = 5512,
2871da177e4SLinus Torvalds         .rate_max = 48000,
2881da177e4SLinus Torvalds         .channels_min = 1,
2891da177e4SLinus Torvalds         .channels_max = 2,
2901da177e4SLinus Torvalds         .buffer_bytes_max = MAX_BUF_SIZE,
2911da177e4SLinus Torvalds         .period_bytes_min = BUF_SIZE,
2921da177e4SLinus Torvalds         .period_bytes_max = BUF_SIZE,
2931da177e4SLinus Torvalds         .periods_min = 1,
2941da177e4SLinus Torvalds         .periods_max = MAX_BUFS,
2951da177e4SLinus Torvalds         .fifo_size = 0,
2961da177e4SLinus Torvalds };
2971da177e4SLinus Torvalds 
2981da177e4SLinus Torvalds static int
2991da177e4SLinus Torvalds snd_harmony_playback_trigger(snd_pcm_substream_t *ss, int cmd)
3001da177e4SLinus Torvalds {
3011da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
3021da177e4SLinus Torvalds 	unsigned long flags;
3031da177e4SLinus Torvalds 
3041da177e4SLinus Torvalds 	if (h->st.capturing)
3051da177e4SLinus Torvalds 		return -EBUSY;
3061da177e4SLinus Torvalds 
3071da177e4SLinus Torvalds 	spin_lock_irqsave(&h->lock, flags);
3081da177e4SLinus Torvalds 	switch (cmd) {
3091da177e4SLinus Torvalds 	case SNDRV_PCM_TRIGGER_START:
3101da177e4SLinus Torvalds 		h->st.playing = 1;
3111da177e4SLinus Torvalds 		harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr);
3121da177e4SLinus Torvalds 		harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
3131da177e4SLinus Torvalds 		harmony_unmute(h);
3141da177e4SLinus Torvalds 		harmony_enable_interrupts(h);
3151da177e4SLinus Torvalds 		break;
3161da177e4SLinus Torvalds 	case SNDRV_PCM_TRIGGER_STOP:
3171da177e4SLinus Torvalds 		h->st.playing = 0;
3181da177e4SLinus Torvalds 		harmony_mute(h);
319*3a165680SStuart Brady 		harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
3201da177e4SLinus Torvalds 		harmony_disable_interrupts(h);
3211da177e4SLinus Torvalds 		break;
3221da177e4SLinus Torvalds 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
3231da177e4SLinus Torvalds 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
3241da177e4SLinus Torvalds 	case SNDRV_PCM_TRIGGER_SUSPEND:
3251da177e4SLinus Torvalds 	default:
3261da177e4SLinus Torvalds 		spin_unlock_irqrestore(&h->lock, flags);
3271da177e4SLinus Torvalds 		snd_BUG();
3281da177e4SLinus Torvalds 		return -EINVAL;
3291da177e4SLinus Torvalds 	}
3301da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->lock, flags);
3311da177e4SLinus Torvalds 
3321da177e4SLinus Torvalds 	return 0;
3331da177e4SLinus Torvalds }
3341da177e4SLinus Torvalds 
3351da177e4SLinus Torvalds static int
3361da177e4SLinus Torvalds snd_harmony_capture_trigger(snd_pcm_substream_t *ss, int cmd)
3371da177e4SLinus Torvalds {
3381da177e4SLinus Torvalds         harmony_t *h = snd_pcm_substream_chip(ss);
3391da177e4SLinus Torvalds 	unsigned long flags;
3401da177e4SLinus Torvalds 
3411da177e4SLinus Torvalds 	if (h->st.playing)
3421da177e4SLinus Torvalds 		return -EBUSY;
3431da177e4SLinus Torvalds 
3441da177e4SLinus Torvalds 	spin_lock_irqsave(&h->lock, flags);
3451da177e4SLinus Torvalds         switch (cmd) {
3461da177e4SLinus Torvalds         case SNDRV_PCM_TRIGGER_START:
3471da177e4SLinus Torvalds 		h->st.capturing = 1;
3481da177e4SLinus Torvalds                 harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
3491da177e4SLinus Torvalds                 harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr);
3501da177e4SLinus Torvalds 		harmony_unmute(h);
3511da177e4SLinus Torvalds                 harmony_enable_interrupts(h);
3521da177e4SLinus Torvalds 		break;
3531da177e4SLinus Torvalds         case SNDRV_PCM_TRIGGER_STOP:
3541da177e4SLinus Torvalds 		h->st.capturing = 0;
3551da177e4SLinus Torvalds 		harmony_mute(h);
356*3a165680SStuart Brady 		harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
3571da177e4SLinus Torvalds 		harmony_disable_interrupts(h);
3581da177e4SLinus Torvalds 		break;
3591da177e4SLinus Torvalds         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
3601da177e4SLinus Torvalds         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
3611da177e4SLinus Torvalds         case SNDRV_PCM_TRIGGER_SUSPEND:
3621da177e4SLinus Torvalds 	default:
3631da177e4SLinus Torvalds 		spin_unlock_irqrestore(&h->lock, flags);
3641da177e4SLinus Torvalds 		snd_BUG();
3651da177e4SLinus Torvalds                 return -EINVAL;
3661da177e4SLinus Torvalds         }
3671da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->lock, flags);
3681da177e4SLinus Torvalds 
3691da177e4SLinus Torvalds         return 0;
3701da177e4SLinus Torvalds }
3711da177e4SLinus Torvalds 
3721da177e4SLinus Torvalds static int
3731da177e4SLinus Torvalds snd_harmony_set_data_format(harmony_t *h, int fmt, int force)
3741da177e4SLinus Torvalds {
3751da177e4SLinus Torvalds 	int o = h->st.format;
3761da177e4SLinus Torvalds 	int n;
3771da177e4SLinus Torvalds 
3781da177e4SLinus Torvalds 	switch(fmt) {
3791da177e4SLinus Torvalds 	case SNDRV_PCM_FORMAT_S16_BE:
3801da177e4SLinus Torvalds 		n = HARMONY_DF_16BIT_LINEAR;
3811da177e4SLinus Torvalds 		break;
3821da177e4SLinus Torvalds 	case SNDRV_PCM_FORMAT_A_LAW:
3831da177e4SLinus Torvalds 		n = HARMONY_DF_8BIT_ALAW;
3841da177e4SLinus Torvalds 		break;
3851da177e4SLinus Torvalds 	case SNDRV_PCM_FORMAT_MU_LAW:
3861da177e4SLinus Torvalds 		n = HARMONY_DF_8BIT_ULAW;
3871da177e4SLinus Torvalds 		break;
3881da177e4SLinus Torvalds 	default:
3891da177e4SLinus Torvalds 		n = HARMONY_DF_16BIT_LINEAR;
3901da177e4SLinus Torvalds 		break;
3911da177e4SLinus Torvalds 	}
3921da177e4SLinus Torvalds 
3931da177e4SLinus Torvalds 	if (force || o != n) {
3941da177e4SLinus Torvalds 		snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ /
3951da177e4SLinus Torvalds 					   (snd_pcm_format_physical_width(fmt)
3961da177e4SLinus Torvalds 					    / 8));
3971da177e4SLinus Torvalds 	}
3981da177e4SLinus Torvalds 
3991da177e4SLinus Torvalds 	return n;
4001da177e4SLinus Torvalds }
4011da177e4SLinus Torvalds 
4021da177e4SLinus Torvalds static int
4031da177e4SLinus Torvalds snd_harmony_playback_prepare(snd_pcm_substream_t *ss)
4041da177e4SLinus Torvalds {
4051da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
4061da177e4SLinus Torvalds 	snd_pcm_runtime_t *rt = ss->runtime;
4071da177e4SLinus Torvalds 
4081da177e4SLinus Torvalds 	if (h->st.capturing)
4091da177e4SLinus Torvalds 		return -EBUSY;
4101da177e4SLinus Torvalds 
4111da177e4SLinus Torvalds 	h->pbuf.size = snd_pcm_lib_buffer_bytes(ss);
4121da177e4SLinus Torvalds 	h->pbuf.count = snd_pcm_lib_period_bytes(ss);
413*3a165680SStuart Brady 	if (h->pbuf.buf >= h->pbuf.size)
4141da177e4SLinus Torvalds 		h->pbuf.buf = 0;
4151da177e4SLinus Torvalds 	h->st.playing = 0;
4161da177e4SLinus Torvalds 
4171da177e4SLinus Torvalds 	h->st.rate = snd_harmony_rate_bits(rt->rate);
4181da177e4SLinus Torvalds 	h->st.format = snd_harmony_set_data_format(h, rt->format, 0);
4191da177e4SLinus Torvalds 
4201da177e4SLinus Torvalds 	if (rt->channels == 2)
4211da177e4SLinus Torvalds 		h->st.stereo = HARMONY_SS_STEREO;
4221da177e4SLinus Torvalds 	else
4231da177e4SLinus Torvalds 		h->st.stereo = HARMONY_SS_MONO;
4241da177e4SLinus Torvalds 
4251da177e4SLinus Torvalds 	harmony_set_control(h);
4261da177e4SLinus Torvalds 
4271da177e4SLinus Torvalds 	h->pbuf.addr = rt->dma_addr;
4281da177e4SLinus Torvalds 
4291da177e4SLinus Torvalds 	return 0;
4301da177e4SLinus Torvalds }
4311da177e4SLinus Torvalds 
4321da177e4SLinus Torvalds static int
4331da177e4SLinus Torvalds snd_harmony_capture_prepare(snd_pcm_substream_t *ss)
4341da177e4SLinus Torvalds {
4351da177e4SLinus Torvalds         harmony_t *h = snd_pcm_substream_chip(ss);
4361da177e4SLinus Torvalds         snd_pcm_runtime_t *rt = ss->runtime;
4371da177e4SLinus Torvalds 
4381da177e4SLinus Torvalds 	if (h->st.playing)
4391da177e4SLinus Torvalds 		return -EBUSY;
4401da177e4SLinus Torvalds 
4411da177e4SLinus Torvalds         h->cbuf.size = snd_pcm_lib_buffer_bytes(ss);
4421da177e4SLinus Torvalds         h->cbuf.count = snd_pcm_lib_period_bytes(ss);
443*3a165680SStuart Brady 	if (h->cbuf.buf >= h->cbuf.size)
4441da177e4SLinus Torvalds 	        h->cbuf.buf = 0;
4451da177e4SLinus Torvalds 	h->st.capturing = 0;
4461da177e4SLinus Torvalds 
4471da177e4SLinus Torvalds         h->st.rate = snd_harmony_rate_bits(rt->rate);
4481da177e4SLinus Torvalds         h->st.format = snd_harmony_set_data_format(h, rt->format, 0);
4491da177e4SLinus Torvalds 
4501da177e4SLinus Torvalds         if (rt->channels == 2)
4511da177e4SLinus Torvalds                 h->st.stereo = HARMONY_SS_STEREO;
4521da177e4SLinus Torvalds         else
4531da177e4SLinus Torvalds                 h->st.stereo = HARMONY_SS_MONO;
4541da177e4SLinus Torvalds 
4551da177e4SLinus Torvalds         harmony_set_control(h);
4561da177e4SLinus Torvalds 
4571da177e4SLinus Torvalds         h->cbuf.addr = rt->dma_addr;
4581da177e4SLinus Torvalds 
4591da177e4SLinus Torvalds         return 0;
4601da177e4SLinus Torvalds }
4611da177e4SLinus Torvalds 
4621da177e4SLinus Torvalds static snd_pcm_uframes_t
4631da177e4SLinus Torvalds snd_harmony_playback_pointer(snd_pcm_substream_t *ss)
4641da177e4SLinus Torvalds {
4651da177e4SLinus Torvalds 	snd_pcm_runtime_t *rt = ss->runtime;
4661da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
4671da177e4SLinus Torvalds 	unsigned long pcuradd;
4681da177e4SLinus Torvalds 	unsigned long played;
4691da177e4SLinus Torvalds 
4701da177e4SLinus Torvalds 	if (!(h->st.playing) || (h->psubs == NULL))
4711da177e4SLinus Torvalds 		return 0;
4721da177e4SLinus Torvalds 
4731da177e4SLinus Torvalds 	if ((h->pbuf.addr == 0) || (h->pbuf.size == 0))
4741da177e4SLinus Torvalds 		return 0;
4751da177e4SLinus Torvalds 
4761da177e4SLinus Torvalds 	pcuradd = harmony_read(h, HARMONY_PCURADD);
4771da177e4SLinus Torvalds 	played = pcuradd - h->pbuf.addr;
4781da177e4SLinus Torvalds 
4791da177e4SLinus Torvalds #ifdef HARMONY_DEBUG
4801da177e4SLinus Torvalds 	printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n",
4811da177e4SLinus Torvalds 	       pcuradd, h->pbuf.addr, played);
4821da177e4SLinus Torvalds #endif
4831da177e4SLinus Torvalds 
4841da177e4SLinus Torvalds 	if (pcuradd > h->pbuf.addr + h->pbuf.size) {
4851da177e4SLinus Torvalds 		return 0;
4861da177e4SLinus Torvalds 	}
4871da177e4SLinus Torvalds 
4881da177e4SLinus Torvalds 	return bytes_to_frames(rt, played);
4891da177e4SLinus Torvalds }
4901da177e4SLinus Torvalds 
4911da177e4SLinus Torvalds static snd_pcm_uframes_t
4921da177e4SLinus Torvalds snd_harmony_capture_pointer(snd_pcm_substream_t *ss)
4931da177e4SLinus Torvalds {
4941da177e4SLinus Torvalds         snd_pcm_runtime_t *rt = ss->runtime;
4951da177e4SLinus Torvalds         harmony_t *h = snd_pcm_substream_chip(ss);
4961da177e4SLinus Torvalds         unsigned long rcuradd;
4971da177e4SLinus Torvalds         unsigned long caught;
4981da177e4SLinus Torvalds 
4991da177e4SLinus Torvalds         if (!(h->st.capturing) || (h->csubs == NULL))
5001da177e4SLinus Torvalds                 return 0;
5011da177e4SLinus Torvalds 
5021da177e4SLinus Torvalds         if ((h->cbuf.addr == 0) || (h->cbuf.size == 0))
5031da177e4SLinus Torvalds                 return 0;
5041da177e4SLinus Torvalds 
5051da177e4SLinus Torvalds         rcuradd = harmony_read(h, HARMONY_RCURADD);
5061da177e4SLinus Torvalds         caught = rcuradd - h->cbuf.addr;
5071da177e4SLinus Torvalds 
5081da177e4SLinus Torvalds #ifdef HARMONY_DEBUG
5091da177e4SLinus Torvalds         printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n",
5101da177e4SLinus Torvalds                rcuradd, h->cbuf.addr, caught);
5111da177e4SLinus Torvalds #endif
5121da177e4SLinus Torvalds 
5131da177e4SLinus Torvalds         if (rcuradd > h->cbuf.addr + h->cbuf.size) {
5141da177e4SLinus Torvalds 		return 0;
5151da177e4SLinus Torvalds 	}
5161da177e4SLinus Torvalds 
5171da177e4SLinus Torvalds         return bytes_to_frames(rt, caught);
5181da177e4SLinus Torvalds }
5191da177e4SLinus Torvalds 
5201da177e4SLinus Torvalds static int
5211da177e4SLinus Torvalds snd_harmony_playback_open(snd_pcm_substream_t *ss)
5221da177e4SLinus Torvalds {
5231da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
5241da177e4SLinus Torvalds 	snd_pcm_runtime_t *rt = ss->runtime;
5251da177e4SLinus Torvalds 	int err;
5261da177e4SLinus Torvalds 
5271da177e4SLinus Torvalds 	h->psubs = ss;
5281da177e4SLinus Torvalds 	rt->hw = snd_harmony_playback;
5291da177e4SLinus Torvalds 	snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
5301da177e4SLinus Torvalds 				   &hw_constraint_rates);
5311da177e4SLinus Torvalds 
5321da177e4SLinus Torvalds 	err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
5331da177e4SLinus Torvalds 	if (err < 0)
5341da177e4SLinus Torvalds 		return err;
5351da177e4SLinus Torvalds 
5361da177e4SLinus Torvalds 	return 0;
5371da177e4SLinus Torvalds }
5381da177e4SLinus Torvalds 
5391da177e4SLinus Torvalds static int
5401da177e4SLinus Torvalds snd_harmony_capture_open(snd_pcm_substream_t *ss)
5411da177e4SLinus Torvalds {
5421da177e4SLinus Torvalds         harmony_t *h = snd_pcm_substream_chip(ss);
5431da177e4SLinus Torvalds         snd_pcm_runtime_t *rt = ss->runtime;
5441da177e4SLinus Torvalds         int err;
5451da177e4SLinus Torvalds 
5461da177e4SLinus Torvalds         h->csubs = ss;
5471da177e4SLinus Torvalds         rt->hw = snd_harmony_capture;
5481da177e4SLinus Torvalds         snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
5491da177e4SLinus Torvalds                                    &hw_constraint_rates);
5501da177e4SLinus Torvalds 
5511da177e4SLinus Torvalds         err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
5521da177e4SLinus Torvalds         if (err < 0)
5531da177e4SLinus Torvalds                 return err;
5541da177e4SLinus Torvalds 
5551da177e4SLinus Torvalds         return 0;
5561da177e4SLinus Torvalds }
5571da177e4SLinus Torvalds 
5581da177e4SLinus Torvalds static int
5591da177e4SLinus Torvalds snd_harmony_playback_close(snd_pcm_substream_t *ss)
5601da177e4SLinus Torvalds {
5611da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
5621da177e4SLinus Torvalds 	h->psubs = NULL;
5631da177e4SLinus Torvalds 	return 0;
5641da177e4SLinus Torvalds }
5651da177e4SLinus Torvalds 
5661da177e4SLinus Torvalds static int
5671da177e4SLinus Torvalds snd_harmony_capture_close(snd_pcm_substream_t *ss)
5681da177e4SLinus Torvalds {
5691da177e4SLinus Torvalds         harmony_t *h = snd_pcm_substream_chip(ss);
5701da177e4SLinus Torvalds         h->csubs = NULL;
5711da177e4SLinus Torvalds         return 0;
5721da177e4SLinus Torvalds }
5731da177e4SLinus Torvalds 
5741da177e4SLinus Torvalds static int
5751da177e4SLinus Torvalds snd_harmony_hw_params(snd_pcm_substream_t *ss,
5761da177e4SLinus Torvalds 		      snd_pcm_hw_params_t *hw)
5771da177e4SLinus Torvalds {
5781da177e4SLinus Torvalds 	int err;
5791da177e4SLinus Torvalds 	harmony_t *h = snd_pcm_substream_chip(ss);
5801da177e4SLinus Torvalds 
5811da177e4SLinus Torvalds 	err = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw));
5821da177e4SLinus Torvalds 	if (err > 0 && h->dma.type == SNDRV_DMA_TYPE_CONTINUOUS)
5831da177e4SLinus Torvalds 		ss->runtime->dma_addr = __pa(ss->runtime->dma_area);
5841da177e4SLinus Torvalds 
5851da177e4SLinus Torvalds 	return err;
5861da177e4SLinus Torvalds }
5871da177e4SLinus Torvalds 
5881da177e4SLinus Torvalds static int
5891da177e4SLinus Torvalds snd_harmony_hw_free(snd_pcm_substream_t *ss)
5901da177e4SLinus Torvalds {
5911da177e4SLinus Torvalds 	return snd_pcm_lib_free_pages(ss);
5921da177e4SLinus Torvalds }
5931da177e4SLinus Torvalds 
5941da177e4SLinus Torvalds static snd_pcm_ops_t snd_harmony_playback_ops = {
5951da177e4SLinus Torvalds 	.open =	snd_harmony_playback_open,
5961da177e4SLinus Torvalds 	.close = snd_harmony_playback_close,
5971da177e4SLinus Torvalds 	.ioctl = snd_pcm_lib_ioctl,
5981da177e4SLinus Torvalds 	.hw_params = snd_harmony_hw_params,
5991da177e4SLinus Torvalds 	.hw_free = snd_harmony_hw_free,
6001da177e4SLinus Torvalds 	.prepare = snd_harmony_playback_prepare,
6011da177e4SLinus Torvalds 	.trigger = snd_harmony_playback_trigger,
6021da177e4SLinus Torvalds  	.pointer = snd_harmony_playback_pointer,
6031da177e4SLinus Torvalds };
6041da177e4SLinus Torvalds 
6051da177e4SLinus Torvalds static snd_pcm_ops_t snd_harmony_capture_ops = {
6061da177e4SLinus Torvalds         .open = snd_harmony_capture_open,
6071da177e4SLinus Torvalds         .close = snd_harmony_capture_close,
6081da177e4SLinus Torvalds         .ioctl = snd_pcm_lib_ioctl,
6091da177e4SLinus Torvalds         .hw_params = snd_harmony_hw_params,
6101da177e4SLinus Torvalds         .hw_free = snd_harmony_hw_free,
6111da177e4SLinus Torvalds         .prepare = snd_harmony_capture_prepare,
6121da177e4SLinus Torvalds         .trigger = snd_harmony_capture_trigger,
6131da177e4SLinus Torvalds         .pointer = snd_harmony_capture_pointer,
6141da177e4SLinus Torvalds };
6151da177e4SLinus Torvalds 
6161da177e4SLinus Torvalds static int
6171da177e4SLinus Torvalds snd_harmony_pcm_init(harmony_t *h)
6181da177e4SLinus Torvalds {
6191da177e4SLinus Torvalds 	snd_pcm_t *pcm;
6201da177e4SLinus Torvalds 	int err;
6211da177e4SLinus Torvalds 
6221da177e4SLinus Torvalds 	harmony_disable_interrupts(h);
6231da177e4SLinus Torvalds 
6241da177e4SLinus Torvalds    	err = snd_pcm_new(h->card, "harmony", 0, 1, 1, &pcm);
6251da177e4SLinus Torvalds 	if (err < 0)
6261da177e4SLinus Torvalds 		return err;
6271da177e4SLinus Torvalds 
6281da177e4SLinus Torvalds 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
6291da177e4SLinus Torvalds 			&snd_harmony_playback_ops);
6301da177e4SLinus Torvalds 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
6311da177e4SLinus Torvalds 			&snd_harmony_capture_ops);
6321da177e4SLinus Torvalds 
6331da177e4SLinus Torvalds 	pcm->private_data = h;
6341da177e4SLinus Torvalds 	pcm->info_flags = 0;
6351da177e4SLinus Torvalds 	strcpy(pcm->name, "harmony");
6361da177e4SLinus Torvalds 	h->pcm = pcm;
6371da177e4SLinus Torvalds 
6381da177e4SLinus Torvalds 	h->psubs = NULL;
6391da177e4SLinus Torvalds 	h->csubs = NULL;
6401da177e4SLinus Torvalds 
6411da177e4SLinus Torvalds 	/* initialize graveyard buffer */
6421da177e4SLinus Torvalds 	h->dma.type = SNDRV_DMA_TYPE_DEV;
6431da177e4SLinus Torvalds 	h->dma.dev = &h->dev->dev;
6441da177e4SLinus Torvalds 	err = snd_dma_alloc_pages(h->dma.type,
6451da177e4SLinus Torvalds 				  h->dma.dev,
6461da177e4SLinus Torvalds 				  BUF_SIZE*GRAVEYARD_BUFS,
6471da177e4SLinus Torvalds 				  &h->gdma);
6481da177e4SLinus Torvalds 	if (err < 0) {
6491da177e4SLinus Torvalds 		printk(KERN_ERR PFX "cannot allocate graveyard buffer!\n");
6501da177e4SLinus Torvalds 		return err;
6511da177e4SLinus Torvalds 	}
6521da177e4SLinus Torvalds 
6531da177e4SLinus Torvalds 	/* initialize silence buffers */
6541da177e4SLinus Torvalds 	err = snd_dma_alloc_pages(h->dma.type,
6551da177e4SLinus Torvalds 				  h->dma.dev,
6561da177e4SLinus Torvalds 				  BUF_SIZE*SILENCE_BUFS,
6571da177e4SLinus Torvalds 				  &h->sdma);
6581da177e4SLinus Torvalds 	if (err < 0) {
6591da177e4SLinus Torvalds 		printk(KERN_ERR PFX "cannot allocate silence buffer!\n");
6601da177e4SLinus Torvalds 		return err;
6611da177e4SLinus Torvalds 	}
6621da177e4SLinus Torvalds 
6631da177e4SLinus Torvalds 	/* pre-allocate space for DMA */
6641da177e4SLinus Torvalds 	err = snd_pcm_lib_preallocate_pages_for_all(pcm, h->dma.type,
6651da177e4SLinus Torvalds 						    h->dma.dev,
6661da177e4SLinus Torvalds 						    MAX_BUF_SIZE,
6671da177e4SLinus Torvalds 						    MAX_BUF_SIZE);
6681da177e4SLinus Torvalds 	if (err < 0) {
6691da177e4SLinus Torvalds 		printk(KERN_ERR PFX "buffer allocation error: %d\n", err);
6701da177e4SLinus Torvalds 		return err;
6711da177e4SLinus Torvalds 	}
6721da177e4SLinus Torvalds 
6731da177e4SLinus Torvalds 	h->st.format = snd_harmony_set_data_format(h,
6741da177e4SLinus Torvalds 		SNDRV_PCM_FORMAT_S16_BE, 1);
6751da177e4SLinus Torvalds 
6761da177e4SLinus Torvalds 	return 0;
6771da177e4SLinus Torvalds }
6781da177e4SLinus Torvalds 
6791da177e4SLinus Torvalds static void
6801da177e4SLinus Torvalds snd_harmony_set_new_gain(harmony_t *h)
6811da177e4SLinus Torvalds {
6821da177e4SLinus Torvalds  	harmony_wait_for_control(h);
6831da177e4SLinus Torvalds 	harmony_write(h, HARMONY_GAINCTL, h->st.gain);
6841da177e4SLinus Torvalds }
6851da177e4SLinus Torvalds 
6861da177e4SLinus Torvalds static int
6871da177e4SLinus Torvalds snd_harmony_mixercontrol_info(snd_kcontrol_t *kc,
6881da177e4SLinus Torvalds 			      snd_ctl_elem_info_t *uinfo)
6891da177e4SLinus Torvalds {
6901da177e4SLinus Torvalds 	int mask = (kc->private_value >> 16) & 0xff;
6911da177e4SLinus Torvalds 	int left_shift = (kc->private_value) & 0xff;
6921da177e4SLinus Torvalds 	int right_shift = (kc->private_value >> 8) & 0xff;
6931da177e4SLinus Torvalds 
6941da177e4SLinus Torvalds 	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
6951da177e4SLinus Torvalds 		       SNDRV_CTL_ELEM_TYPE_INTEGER;
6961da177e4SLinus Torvalds 	uinfo->count = left_shift == right_shift ? 1 : 2;
6971da177e4SLinus Torvalds 	uinfo->value.integer.min = 0;
6981da177e4SLinus Torvalds 	uinfo->value.integer.max = mask;
6991da177e4SLinus Torvalds 
7001da177e4SLinus Torvalds 	return 0;
7011da177e4SLinus Torvalds }
7021da177e4SLinus Torvalds 
7031da177e4SLinus Torvalds static int
7041da177e4SLinus Torvalds snd_harmony_volume_get(snd_kcontrol_t *kc,
7051da177e4SLinus Torvalds 		       snd_ctl_elem_value_t *ucontrol)
7061da177e4SLinus Torvalds {
7071da177e4SLinus Torvalds 	harmony_t *h = snd_kcontrol_chip(kc);
7081da177e4SLinus Torvalds 	int shift_left = (kc->private_value) & 0xff;
7091da177e4SLinus Torvalds 	int shift_right = (kc->private_value >> 8) & 0xff;
7101da177e4SLinus Torvalds 	int mask = (kc->private_value >> 16) & 0xff;
7111da177e4SLinus Torvalds 	int invert = (kc->private_value >> 24) & 0xff;
7121da177e4SLinus Torvalds 	int left, right;
7131da177e4SLinus Torvalds 	unsigned long flags;
7141da177e4SLinus Torvalds 
7151da177e4SLinus Torvalds 	spin_lock_irqsave(&h->mixer_lock, flags);
7161da177e4SLinus Torvalds 
7171da177e4SLinus Torvalds 	left = (h->st.gain >> shift_left) & mask;
7181da177e4SLinus Torvalds 	right = (h->st.gain >> shift_right) & mask;
7191da177e4SLinus Torvalds 	if (invert) {
7201da177e4SLinus Torvalds 		left = mask - left;
7211da177e4SLinus Torvalds 		right = mask - right;
7221da177e4SLinus Torvalds 	}
723*3a165680SStuart Brady 
7241da177e4SLinus Torvalds 	ucontrol->value.integer.value[0] = left;
725*3a165680SStuart Brady 	if (shift_left != shift_right)
7261da177e4SLinus Torvalds 		ucontrol->value.integer.value[1] = right;
7271da177e4SLinus Torvalds 
7281da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->mixer_lock, flags);
7291da177e4SLinus Torvalds 
7301da177e4SLinus Torvalds 	return 0;
7311da177e4SLinus Torvalds }
7321da177e4SLinus Torvalds 
7331da177e4SLinus Torvalds static int
7341da177e4SLinus Torvalds snd_harmony_volume_put(snd_kcontrol_t *kc,
7351da177e4SLinus Torvalds 		       snd_ctl_elem_value_t *ucontrol)
7361da177e4SLinus Torvalds {
7371da177e4SLinus Torvalds 	harmony_t *h = snd_kcontrol_chip(kc);
7381da177e4SLinus Torvalds 	int shift_left = (kc->private_value) & 0xff;
7391da177e4SLinus Torvalds 	int shift_right = (kc->private_value >> 8) & 0xff;
7401da177e4SLinus Torvalds 	int mask = (kc->private_value >> 16) & 0xff;
7411da177e4SLinus Torvalds 	int invert = (kc->private_value >> 24) & 0xff;
7421da177e4SLinus Torvalds 	int left, right;
7431da177e4SLinus Torvalds 	int old_gain = h->st.gain;
7441da177e4SLinus Torvalds 	unsigned long flags;
7451da177e4SLinus Torvalds 
7461da177e4SLinus Torvalds 	spin_lock_irqsave(&h->mixer_lock, flags);
7471da177e4SLinus Torvalds 
748*3a165680SStuart Brady 	left = ucontrol->value.integer.value[0] & mask;
749*3a165680SStuart Brady 	if (invert)
750*3a165680SStuart Brady 		left = mask - left;
751*3a165680SStuart Brady 	h->st.gain &= ~( (mask << shift_left ) );
752*3a165680SStuart Brady  	h->st.gain |= (left << shift_left);
753*3a165680SStuart Brady 
754*3a165680SStuart Brady 	if (shift_left != shift_right) {
755*3a165680SStuart Brady 		right = ucontrol->value.integer.value[1] & mask;
756*3a165680SStuart Brady 		if (invert)
757*3a165680SStuart Brady 			right = mask - right;
758*3a165680SStuart Brady 		h->st.gain &= ~( (mask << shift_right) );
759*3a165680SStuart Brady 		h->st.gain |= (right << shift_right);
760*3a165680SStuart Brady 	}
761*3a165680SStuart Brady 
7621da177e4SLinus Torvalds 	snd_harmony_set_new_gain(h);
7631da177e4SLinus Torvalds 
7641da177e4SLinus Torvalds 	spin_unlock_irqrestore(&h->mixer_lock, flags);
7651da177e4SLinus Torvalds 
766*3a165680SStuart Brady 	return h->st.gain != old_gain;
767*3a165680SStuart Brady }
768*3a165680SStuart Brady 
769*3a165680SStuart Brady static int
770*3a165680SStuart Brady snd_harmony_captureroute_info(snd_kcontrol_t *kc,
771*3a165680SStuart Brady 			      snd_ctl_elem_info_t *uinfo)
772*3a165680SStuart Brady {
773*3a165680SStuart Brady 	static char *texts[2] = { "Line", "Mic" };
774*3a165680SStuart Brady 	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
775*3a165680SStuart Brady 	uinfo->count = 1;
776*3a165680SStuart Brady 	uinfo->value.enumerated.items = 2;
777*3a165680SStuart Brady 	if (uinfo->value.enumerated.item > 1)
778*3a165680SStuart Brady 		uinfo->value.enumerated.item = 1;
779*3a165680SStuart Brady 	strcpy(uinfo->value.enumerated.name,
780*3a165680SStuart Brady 	       texts[uinfo->value.enumerated.item]);
781*3a165680SStuart Brady 	return 0;
782*3a165680SStuart Brady }
783*3a165680SStuart Brady 
784*3a165680SStuart Brady static int
785*3a165680SStuart Brady snd_harmony_captureroute_get(snd_kcontrol_t *kc,
786*3a165680SStuart Brady 			     snd_ctl_elem_value_t *ucontrol)
787*3a165680SStuart Brady {
788*3a165680SStuart Brady 	harmony_t *h = snd_kcontrol_chip(kc);
789*3a165680SStuart Brady 	int value;
790*3a165680SStuart Brady 	unsigned long flags;
791*3a165680SStuart Brady 
792*3a165680SStuart Brady 	spin_lock_irqsave(&h->mixer_lock, flags);
793*3a165680SStuart Brady 
794*3a165680SStuart Brady 	value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1;
795*3a165680SStuart Brady 	ucontrol->value.enumerated.item[0] = value;
796*3a165680SStuart Brady 
797*3a165680SStuart Brady 	spin_unlock_irqrestore(&h->mixer_lock, flags);
798*3a165680SStuart Brady 
799*3a165680SStuart Brady 	return 0;
800*3a165680SStuart Brady }
801*3a165680SStuart Brady 
802*3a165680SStuart Brady static int
803*3a165680SStuart Brady snd_harmony_captureroute_put(snd_kcontrol_t *kc,
804*3a165680SStuart Brady 			     snd_ctl_elem_value_t *ucontrol)
805*3a165680SStuart Brady {
806*3a165680SStuart Brady 	harmony_t *h = snd_kcontrol_chip(kc);
807*3a165680SStuart Brady 	int value;
808*3a165680SStuart Brady 	int old_gain = h->st.gain;
809*3a165680SStuart Brady 	unsigned long flags;
810*3a165680SStuart Brady 
811*3a165680SStuart Brady 	spin_lock_irqsave(&h->mixer_lock, flags);
812*3a165680SStuart Brady 
813*3a165680SStuart Brady 	value = ucontrol->value.enumerated.item[0] & 1;
814*3a165680SStuart Brady 	h->st.gain &= ~HARMONY_GAIN_IS_MASK;
815*3a165680SStuart Brady  	h->st.gain |= value << HARMONY_GAIN_IS_SHIFT;
816*3a165680SStuart Brady 
817*3a165680SStuart Brady 	snd_harmony_set_new_gain(h);
818*3a165680SStuart Brady 
819*3a165680SStuart Brady 	spin_unlock_irqrestore(&h->mixer_lock, flags);
820*3a165680SStuart Brady 
821*3a165680SStuart Brady 	return h->st.gain != old_gain;
8221da177e4SLinus Torvalds }
8231da177e4SLinus Torvalds 
8241da177e4SLinus Torvalds #define HARMONY_CONTROLS (sizeof(snd_harmony_controls)/ \
8251da177e4SLinus Torvalds                           sizeof(snd_kcontrol_new_t))
8261da177e4SLinus Torvalds 
8271da177e4SLinus Torvalds #define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \
8281da177e4SLinus Torvalds { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,                \
8291da177e4SLinus Torvalds   .info = snd_harmony_mixercontrol_info,                             \
8301da177e4SLinus Torvalds   .get = snd_harmony_volume_get, .put = snd_harmony_volume_put,      \
8311da177e4SLinus Torvalds   .private_value = ((left_shift) | ((right_shift) << 8) |            \
8321da177e4SLinus Torvalds                    ((mask) << 16) | ((invert) << 24)) }
8331da177e4SLinus Torvalds 
8341da177e4SLinus Torvalds static snd_kcontrol_new_t snd_harmony_controls[] = {
835*3a165680SStuart Brady 	HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT,
8361da177e4SLinus Torvalds 		       HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1),
8371da177e4SLinus Torvalds 	HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT,
8381da177e4SLinus Torvalds 		       HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0),
839*3a165680SStuart Brady 	HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT,
840*3a165680SStuart Brady 		       HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1),
841*3a165680SStuart Brady 	{
842*3a165680SStuart Brady 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
843*3a165680SStuart Brady 		.name = "Input Route",
844*3a165680SStuart Brady 		.info = snd_harmony_captureroute_info,
845*3a165680SStuart Brady 		.get = snd_harmony_captureroute_get,
846*3a165680SStuart Brady 		.put = snd_harmony_captureroute_put
847*3a165680SStuart Brady 	},
848*3a165680SStuart Brady 	HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT,
849*3a165680SStuart Brady 		       HARMONY_GAIN_SE_SHIFT, 1, 0),
850*3a165680SStuart Brady 	HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT,
851*3a165680SStuart Brady 		       HARMONY_GAIN_LE_SHIFT, 1, 0),
852*3a165680SStuart Brady 	HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT,
853*3a165680SStuart Brady 		       HARMONY_GAIN_HE_SHIFT, 1, 0),
8541da177e4SLinus Torvalds };
8551da177e4SLinus Torvalds 
8561da177e4SLinus Torvalds static void __init
8571da177e4SLinus Torvalds snd_harmony_mixer_reset(harmony_t *h)
8581da177e4SLinus Torvalds {
8591da177e4SLinus Torvalds 	harmony_mute(h);
8601da177e4SLinus Torvalds 	harmony_reset(h);
8611da177e4SLinus Torvalds 	h->st.gain = HARMONY_GAIN_DEFAULT;
8621da177e4SLinus Torvalds 	harmony_unmute(h);
8631da177e4SLinus Torvalds }
8641da177e4SLinus Torvalds 
8651da177e4SLinus Torvalds static int __init
8661da177e4SLinus Torvalds snd_harmony_mixer_init(harmony_t *h)
8671da177e4SLinus Torvalds {
8681da177e4SLinus Torvalds 	snd_card_t *card = h->card;
8691da177e4SLinus Torvalds 	int idx, err;
8701da177e4SLinus Torvalds 
8711da177e4SLinus Torvalds 	snd_assert(h != NULL, return -EINVAL);
8721da177e4SLinus Torvalds 	strcpy(card->mixername, "Harmony Gain control interface");
8731da177e4SLinus Torvalds 
8741da177e4SLinus Torvalds 	for (idx = 0; idx < HARMONY_CONTROLS; idx++) {
8751da177e4SLinus Torvalds 		err = snd_ctl_add(card,
8761da177e4SLinus Torvalds 				  snd_ctl_new1(&snd_harmony_controls[idx], h));
8771da177e4SLinus Torvalds 		if (err < 0)
8781da177e4SLinus Torvalds 			return err;
8791da177e4SLinus Torvalds 	}
8801da177e4SLinus Torvalds 
8811da177e4SLinus Torvalds 	snd_harmony_mixer_reset(h);
8821da177e4SLinus Torvalds 
8831da177e4SLinus Torvalds 	return 0;
8841da177e4SLinus Torvalds }
8851da177e4SLinus Torvalds 
8861da177e4SLinus Torvalds static int
8871da177e4SLinus Torvalds snd_harmony_free(harmony_t *h)
8881da177e4SLinus Torvalds {
8891da177e4SLinus Torvalds         if (h->gdma.addr)
8901da177e4SLinus Torvalds                 snd_dma_free_pages(&h->gdma);
8911da177e4SLinus Torvalds         if (h->sdma.addr)
8921da177e4SLinus Torvalds                 snd_dma_free_pages(&h->sdma);
8931da177e4SLinus Torvalds 
8941da177e4SLinus Torvalds 	if (h->irq >= 0)
8951da177e4SLinus Torvalds 		free_irq(h->irq, h);
8961da177e4SLinus Torvalds 
8971da177e4SLinus Torvalds 	if (h->iobase)
8981da177e4SLinus Torvalds 		iounmap(h->iobase);
8991da177e4SLinus Torvalds 
9001da177e4SLinus Torvalds 	parisc_set_drvdata(h->dev, NULL);
9011da177e4SLinus Torvalds 
9021da177e4SLinus Torvalds 	kfree(h);
9031da177e4SLinus Torvalds 	return 0;
9041da177e4SLinus Torvalds }
9051da177e4SLinus Torvalds 
9061da177e4SLinus Torvalds static int
9071da177e4SLinus Torvalds snd_harmony_dev_free(snd_device_t *dev)
9081da177e4SLinus Torvalds {
9091da177e4SLinus Torvalds 	harmony_t *h = dev->device_data;
9101da177e4SLinus Torvalds 	return snd_harmony_free(h);
9111da177e4SLinus Torvalds }
9121da177e4SLinus Torvalds 
9131da177e4SLinus Torvalds static int __devinit
9141da177e4SLinus Torvalds snd_harmony_create(snd_card_t *card,
9151da177e4SLinus Torvalds 		   struct parisc_device *padev,
9161da177e4SLinus Torvalds 		   harmony_t **rchip)
9171da177e4SLinus Torvalds {
9181da177e4SLinus Torvalds 	int err;
9191da177e4SLinus Torvalds 	harmony_t *h;
9201da177e4SLinus Torvalds 	static snd_device_ops_t ops = {
9211da177e4SLinus Torvalds 		.dev_free = snd_harmony_dev_free,
9221da177e4SLinus Torvalds 	};
9231da177e4SLinus Torvalds 
9241da177e4SLinus Torvalds 	*rchip = NULL;
9251da177e4SLinus Torvalds 
9261da177e4SLinus Torvalds 	h = kmalloc(sizeof(*h), GFP_KERNEL);
9271da177e4SLinus Torvalds 	if (h == NULL)
9281da177e4SLinus Torvalds 		return -ENOMEM;
9291da177e4SLinus Torvalds 
9301da177e4SLinus Torvalds 	memset(&h->st, 0, sizeof(h->st));
9311da177e4SLinus Torvalds 	memset(&h->stats, 0, sizeof(h->stats));
9321da177e4SLinus Torvalds 	memset(&h->pbuf, 0, sizeof(h->pbuf));
9331da177e4SLinus Torvalds 	memset(&h->cbuf, 0, sizeof(h->cbuf));
9341da177e4SLinus Torvalds 
93553f01bbaSMatthew Wilcox 	h->hpa = padev->hpa.start;
9361da177e4SLinus Torvalds 	h->card = card;
9371da177e4SLinus Torvalds 	h->dev = padev;
9381da177e4SLinus Torvalds 	h->irq = padev->irq;
93953f01bbaSMatthew Wilcox 	h->iobase = ioremap_nocache(padev->hpa.start, HARMONY_SIZE);
9401da177e4SLinus Torvalds 	if (h->iobase == NULL) {
9411da177e4SLinus Torvalds 		printk(KERN_ERR PFX "unable to remap hpa 0x%lx\n",
94253f01bbaSMatthew Wilcox 		       padev->hpa.start);
9431da177e4SLinus Torvalds 		err = -EBUSY;
9441da177e4SLinus Torvalds 		goto free_and_ret;
9451da177e4SLinus Torvalds 	}
9461da177e4SLinus Torvalds 
9471da177e4SLinus Torvalds 	err = request_irq(h->irq, snd_harmony_interrupt, 0,
9481da177e4SLinus Torvalds 			  "harmony", h);
9491da177e4SLinus Torvalds 	if (err) {
9501da177e4SLinus Torvalds 		printk(KERN_ERR PFX "could not obtain interrupt %d",
9511da177e4SLinus Torvalds 		       h->irq);
9521da177e4SLinus Torvalds 		goto free_and_ret;
9531da177e4SLinus Torvalds 	}
9541da177e4SLinus Torvalds 
9551da177e4SLinus Torvalds 	spin_lock_init(&h->mixer_lock);
9561da177e4SLinus Torvalds 	spin_lock_init(&h->lock);
9571da177e4SLinus Torvalds 
9581da177e4SLinus Torvalds         if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
9591da177e4SLinus Torvalds                                   h, &ops)) < 0) {
9601da177e4SLinus Torvalds                 goto free_and_ret;
9611da177e4SLinus Torvalds         }
9621da177e4SLinus Torvalds 
963a76af199STakashi Iwai 	snd_card_set_dev(card, &padev->dev);
964a76af199STakashi Iwai 
9651da177e4SLinus Torvalds 	*rchip = h;
9661da177e4SLinus Torvalds 
9671da177e4SLinus Torvalds 	return 0;
9681da177e4SLinus Torvalds 
9691da177e4SLinus Torvalds free_and_ret:
9701da177e4SLinus Torvalds 	snd_harmony_free(h);
9711da177e4SLinus Torvalds 	return err;
9721da177e4SLinus Torvalds }
9731da177e4SLinus Torvalds 
9741da177e4SLinus Torvalds static int __devinit
9751da177e4SLinus Torvalds snd_harmony_probe(struct parisc_device *padev)
9761da177e4SLinus Torvalds {
9771da177e4SLinus Torvalds 	int err;
9781da177e4SLinus Torvalds 	static int dev;
9791da177e4SLinus Torvalds 	snd_card_t *card;
9801da177e4SLinus Torvalds 	harmony_t *h;
9811da177e4SLinus Torvalds 	static int index = SNDRV_DEFAULT_IDX1;
9821da177e4SLinus Torvalds 	static char *id = SNDRV_DEFAULT_STR1;
9831da177e4SLinus Torvalds 
9841da177e4SLinus Torvalds 	h = parisc_get_drvdata(padev);
9851da177e4SLinus Torvalds 	if (h != NULL) {
9861da177e4SLinus Torvalds 		return -ENODEV;
9871da177e4SLinus Torvalds 	}
9881da177e4SLinus Torvalds 
9891da177e4SLinus Torvalds 	card = snd_card_new(index, id, THIS_MODULE, 0);
9901da177e4SLinus Torvalds 	if (card == NULL)
9911da177e4SLinus Torvalds 		return -ENOMEM;
9921da177e4SLinus Torvalds 
9931da177e4SLinus Torvalds 	err = snd_harmony_create(card, padev, &h);
9941da177e4SLinus Torvalds 	if (err < 0) {
9951da177e4SLinus Torvalds 		goto free_and_ret;
9961da177e4SLinus Torvalds 	}
9971da177e4SLinus Torvalds 
9981da177e4SLinus Torvalds 	err = snd_harmony_pcm_init(h);
9991da177e4SLinus Torvalds 	if (err < 0) {
10001da177e4SLinus Torvalds 		goto free_and_ret;
10011da177e4SLinus Torvalds 	}
10021da177e4SLinus Torvalds 
10031da177e4SLinus Torvalds 	err = snd_harmony_mixer_init(h);
10041da177e4SLinus Torvalds 	if (err < 0) {
10051da177e4SLinus Torvalds 		goto free_and_ret;
10061da177e4SLinus Torvalds 	}
10071da177e4SLinus Torvalds 
10081da177e4SLinus Torvalds 	strcpy(card->driver, "harmony");
10091da177e4SLinus Torvalds 	strcpy(card->shortname, "Harmony");
10101da177e4SLinus Torvalds 	sprintf(card->longname, "%s at 0x%lx, irq %i",
10111da177e4SLinus Torvalds 		card->shortname, h->hpa, h->irq);
10121da177e4SLinus Torvalds 
10131da177e4SLinus Torvalds 	err = snd_card_register(card);
10141da177e4SLinus Torvalds 	if (err < 0) {
10151da177e4SLinus Torvalds 		goto free_and_ret;
10161da177e4SLinus Torvalds 	}
10171da177e4SLinus Torvalds 
10181da177e4SLinus Torvalds 	dev++;
10191da177e4SLinus Torvalds 	parisc_set_drvdata(padev, h);
10201da177e4SLinus Torvalds 
10211da177e4SLinus Torvalds 	return 0;
10221da177e4SLinus Torvalds 
10231da177e4SLinus Torvalds free_and_ret:
10241da177e4SLinus Torvalds 	snd_card_free(card);
10251da177e4SLinus Torvalds 	return err;
10261da177e4SLinus Torvalds }
10271da177e4SLinus Torvalds 
10281da177e4SLinus Torvalds static int __devexit
10291da177e4SLinus Torvalds snd_harmony_remove(struct parisc_device *padev)
10301da177e4SLinus Torvalds {
10311da177e4SLinus Torvalds 	harmony_t *h = parisc_get_drvdata(padev);
10321da177e4SLinus Torvalds 	snd_card_free(h->card);
10331da177e4SLinus Torvalds 	return 0;
10341da177e4SLinus Torvalds }
10351da177e4SLinus Torvalds 
10361da177e4SLinus Torvalds static struct parisc_driver snd_harmony_driver = {
10371da177e4SLinus Torvalds 	.name = "harmony",
10381da177e4SLinus Torvalds 	.id_table = snd_harmony_devtable,
10391da177e4SLinus Torvalds 	.probe = snd_harmony_probe,
10401da177e4SLinus Torvalds 	.remove = snd_harmony_remove,
10411da177e4SLinus Torvalds };
10421da177e4SLinus Torvalds 
10431da177e4SLinus Torvalds static int __init
10441da177e4SLinus Torvalds alsa_harmony_init(void)
10451da177e4SLinus Torvalds {
10461da177e4SLinus Torvalds 	int err;
10471da177e4SLinus Torvalds 
10481da177e4SLinus Torvalds 	err = register_parisc_driver(&snd_harmony_driver);
10491da177e4SLinus Torvalds 	if (err < 0) {
10501da177e4SLinus Torvalds 		printk(KERN_ERR PFX "device not found\n");
10511da177e4SLinus Torvalds 		return err;
10521da177e4SLinus Torvalds 	}
10531da177e4SLinus Torvalds 
10541da177e4SLinus Torvalds 	return 0;
10551da177e4SLinus Torvalds }
10561da177e4SLinus Torvalds 
10571da177e4SLinus Torvalds static void __exit
10581da177e4SLinus Torvalds alsa_harmony_fini(void)
10591da177e4SLinus Torvalds {
10601da177e4SLinus Torvalds 	int err;
10611da177e4SLinus Torvalds 
10621da177e4SLinus Torvalds 	err = unregister_parisc_driver(&snd_harmony_driver);
10631da177e4SLinus Torvalds 	if (err < 0) {
10641da177e4SLinus Torvalds 		printk(KERN_ERR PFX "failed to unregister\n");
10651da177e4SLinus Torvalds 	}
10661da177e4SLinus Torvalds 
10671da177e4SLinus Torvalds 	return;
10681da177e4SLinus Torvalds }
10691da177e4SLinus Torvalds 
10701da177e4SLinus Torvalds MODULE_LICENSE("GPL");
10711da177e4SLinus Torvalds MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>");
10721da177e4SLinus Torvalds MODULE_DESCRIPTION("Harmony sound driver");
10731da177e4SLinus Torvalds 
10741da177e4SLinus Torvalds module_init(alsa_harmony_init);
10751da177e4SLinus Torvalds module_exit(alsa_harmony_fini);
1076