/* Hewlett-Packard Harmony audio driver
*
* This is a driver for the Harmony audio chipset found
* on the LASI ASIC of various early HP PA-RISC workstations.
*
* Copyright (C) 2004, Kyle McMartin <kyle@{debian.org,parisc-linux.org}>
*
* Based on the previous Harmony incarnations by,
* Copyright 2000 (c) Linuxcare Canada, Alex deVries
* Copyright 2000-2003 (c) Helge Deller
* Copyright 2001 (c) Matthieu Delahaye
* Copyright 2001 (c) Jean-Christophe Vaugeois
* Copyright 2003 (c) Laurent Canet
* Copyright 2004 (c) Stuart Brady
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Notes:
* - graveyard and silence buffers last for lifetime of
* the driver. playback and capture buffers are allocated
* per _open()/_close().
*
* TODO:
*
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
#include <sound/info.h>
#include <asm/hardware.h>
#include <asm/parisc-device.h>
#include "harmony.h"
static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for Harmony driver.");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for Harmony driver.");
static const struct parisc_device_id snd_harmony_devtable[] __initconst = {
/* bushmaster / flounder */
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A },
/* 712 / 715 */
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B },
/* pace */
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E },
/* outfield / coral II */
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F },
{ 0, }
};
MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable);
#define NAME "harmony"
#define PFX NAME ": "
static const unsigned int snd_harmony_rates[] = {
5512, 6615, 8000, 9600,
11025, 16000, 18900, 22050,
27428, 32000, 33075, 37800,
44100, 48000
};
static const unsigned int rate_bits[14] = {
HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ,
HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ,
HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ,
HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ,
HARMONY_SR_44KHZ, HARMONY_SR_48KHZ
};
static const struct snd_pcm_hw_constraint_list hw_constraint_rates = {
.count = ARRAY_SIZE(snd_harmony_rates),
.list = snd_harmony_rates,
.mask = 0,
};
static inline unsigned long
harmony_read(struct snd_harmony *h, unsigned r)
{
return __raw_readl(h->iobase + r);
}
static inline void
harmony_write(struct snd_harmony *h, unsigned r, unsigned long v)
{
__raw_writel(v, h->iobase + r);
}
static inline void
harmony_wait_for_control(struct snd_harmony *h)
{
while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ;
}
static inline void
harmony_reset(struct snd_harmony *h)
{
harmony_write(h, HARMONY_RESET, 1);
mdelay(50);
harmony_write(h, HARMONY_RESET, 0);
}
static void
harmony_disable_interrupts(struct snd_harmony *h)
{
u32 dstatus;
harmony_wait_for_control(h);
dstatus = harmony_read(h, HARMONY_DSTATUS);
dstatus &= ~HARMONY_DSTATUS_IE;
harmony_write(h, HARMONY_DSTATUS, dstatus);
}
static void
harmony_enable_interrupts(struct snd_harmony *h)
{
u32 dstatus;
harmony_wait_for_control(h);
dstatus = harmony_read(h, HARMONY_DSTATUS);
dstatus |= HARMONY_DSTATUS_IE;
harmony_write(h, HARMONY_DSTATUS, dstatus);
}
static void
harmony_mute(struct snd_harmony *h)
{
unsigned long flags;
spin_lock_irqsave(&h->mixer_lock, flags);
harmony_wait_for_control(h);
harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE);
spin_unlock_irqrestore(&h->mixer_lock, flags);
}
static void
harmony_unmute(struct snd_harmony *h)
{
unsigned long flags;