X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sound%2Fparisc%2Fharmony.c;h=cf603337b321d01bba027480e29fc5abbe593b98;hb=refs%2Fheads%2Fvserver;hp=423ab05c0480f0003f2d88375d94543ea6be4061;hpb=9213980e6a70d8473e0ffd4b39ab5b6caaba9ff5;p=linux-2.6.git diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index 423ab05c0..cf603337b 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -1,272 +1,96 @@ -/* - * Harmony chipset driver +/* Hewlett-Packard Harmony audio driver * - * This is a sound driver for ASP's and Lasi's Harmony sound chip - * and is unlikely to be used for anything other than on a HP PA-RISC. + * This is a driver for the Harmony audio chipset found + * on the LASI ASIC of various early HP PA-RISC workstations. * - * Harmony is found in HP 712s, 715/new and many other GSC based machines. - * On older 715 machines you'll find the technically identical chip - * called 'Vivace'. Both Harmony and Vicace are supported by this driver. + * Copyright (C) 2004, Kyle McMartin * - * this ALSA driver is based on OSS driver by: - * Copyright 2000 (c) Linuxcare Canada, Alex deVries - * Copyright 2000-2002 (c) Helge Deller - * Copyright 2001 (c) Matthieu Delahaye + * 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 * - * TODO: - * - use generic DMA interface and ioremap()/iounmap() - * - capture is still untested (and probaby non-working) - * - spin locks - * - implement non-consistent DMA pages - * - implement gain meter - * - module parameters - * - correct cleaning sequence - * - better error checking - * - try to have a better quality. - * - */ - -/* - * Harmony chipset 'modus operandi'. - * - This chipset is found in some HP 32bit workstations, like 712, or B132 class. - * most of controls are done through registers. Register are found at a fixed offset - * from the hard physical adress, given in struct dev by register_parisc_driver. + * 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. * - * Playback and recording use 4kb pages (dma or not, depending on the machine). + * 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. * - * Most of PCM playback & capture is done through interrupt. When harmony needs - * a new buffer to put recorded data or read played PCM, it sends an interrupt. - * Bits 2 and 10 of DSTATUS register are '1' when harmony needs respectively - * a new page for recording and playing. - * Interrupt are disabled/enabled by writing to bit 32 of DSTATUS. - * Adresses of next page to be played is put in PNXTADD register, next page - * to be recorded is put in RNXTADD. There is 2 read-only registers, PCURADD and - * RCURADD that provides adress of current page. - * - * Harmony has no way to controll full duplex or half duplex mode. It means - * that we always need to provide adresses of playback and capture data, even - * when this is not needed. That's why we statically alloc one graveyard - * buffer (to put recorded data in play-only mode) and a silence buffer. - * - * Bitrate, number of channels and data format are controlled with - * the CNTL register. + * 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. * - * Mixer work is done through one register (GAINCTL). Only input gain, - * output attenuation and general attenuation control is provided. There is - * also controls for enabling/disabling internal speaker and line - * input. + * Notes: + * - graveyard and silence buffers last for lifetime of + * the driver. playback and capture buffers are allocated + * per _open()/_close(). + * + * TODO: * - * Buffers used by this driver are all DMA consistent. */ -#include -#include #include -#include #include #include #include -#include +#include +#include +#include +#include +#include + +#include #include -#include #include +#include #include #include #include -#include + #include +#include #include -MODULE_AUTHOR("Laurent Canet "); -MODULE_DESCRIPTION("ALSA Harmony sound driver"); -MODULE_LICENSE("GPL"); -MODULE_CLASSES("{sound}"); -MODULE_DEVICES("{{ALSA,Harmony soundcard}}"); - -#undef DEBUG -#ifdef DEBUG -# define DPRINTK printk -#else -# define DPRINTK(x,...) -#endif - -#define PFX "harmony: " - -#define MAX_PCM_DEVICES 1 -#define MAX_PCM_SUBSTREAMS 4 -#define MAX_MIDI_DEVICES 0 - -#define HARMONY_BUF_SIZE 4096 -#define MAX_BUFS 10 -#define MAX_BUFFER_SIZE (MAX_BUFS * HARMONY_BUF_SIZE) - -/* number of silence & graveyard buffers */ -#define GRAVEYARD_BUFS 3 -#define SILENCE_BUFS 3 - -#define HARMONY_CNTL_C 0x80000000 - -#define HARMONY_DSTATUS_PN 0x00000200 -#define HARMONY_DSTATUS_RN 0x00000002 -#define HARMONY_DSTATUS_IE 0x80000000 - -#define HARMONY_DF_16BIT_LINEAR 0x00000000 -#define HARMONY_DF_8BIT_ULAW 0x00000001 -#define HARMONY_DF_8BIT_ALAW 0x00000002 - -#define HARMONY_SS_MONO 0x00000000 -#define HARMONY_SS_STEREO 0x00000001 - -/* - * Channels Mask in mixer register - * try some "reasonable" default gain values - */ - -#define HARMONY_GAIN_TOTAL_SILENCE 0x00F00FFF - -/* the following should be enough (mixer is - * very sensible on harmony) - */ -#define HARMONY_GAIN_DEFAULT 0x0F2FF082 - - -/* useless since only one card is supported ATM */ -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; -static int boot_devs; - -module_param_array(index, int, boot_devs, 0444); -MODULE_PARM_DESC(index, "Index value for Sun CS4231 soundcard."); -MODULE_PARM_SYNTAX(index, SNDRV_INDEX_DESC); -module_param_array(id, charp, boot_devs, 0444); -MODULE_PARM_DESC(id, "ID string for Sun CS4231 soundcard."); -MODULE_PARM_SYNTAX(id, SNDRV_ID_DESC); -module_param_array(enable, bool, boot_devs, 0444); -MODULE_PARM_DESC(enable, "Enable Sun CS4231 soundcard."); -MODULE_PARM_SYNTAX(enable, SNDRV_ENABLE_DESC); - -/* Register offset (from base hpa) */ -#define REG_ID 0x00 -#define REG_RESET 0x04 -#define REG_CNTL 0x08 -#define REG_GAINCTL 0x0C -#define REG_PNXTADD 0x10 -#define REG_PCURADD 0x14 -#define REG_RNXTADD 0x18 -#define REG_RCURADD 0x1C -#define REG_DSTATUS 0x20 -#define REG_OV 0x24 -#define REG_PIO 0x28 -#define REG_DIAG 0x3C - -/* - * main harmony structure - */ - -typedef struct snd_card_harmony { - - /* spinlocks (To be done) */ - spinlock_t mixer_lock; - spinlock_t control_lock; - - /* parameters */ - int irq; - unsigned long hpa; - int id; - int rev; - - u32 current_gain; - int data_format; /* HARMONY_DF_xx_BIT_xxx */ - int sample_rate; /* HARMONY_SR_xx_KHZ */ - int stereo_select; /* HARMONY_SS_MONO or HARMONY_SS_STEREO */ - int format_initialized; - - unsigned long ply_buffer; - int ply_buf; - int ply_count; - int ply_size; - int ply_stopped; - int ply_total; - - unsigned long cap_buffer; - int cap_buf; - int cap_count; - int cap_size; - int cap_stopped; - int cap_total; - - struct parisc_device *pa_dev; - - struct snd_dma_device dma_dev; - - /* the graveyard buffer is used as recording buffer when playback, - * because harmony always want a buffer to put recorded data */ - struct snd_dma_buffer graveyard_dma; - int graveyard_count; - - /* same thing for silence buffer */ - struct snd_dma_buffer silence_dma; - int silence_count; - - /* alsa stuff */ - snd_card_t *card; - snd_pcm_t *pcm; - snd_pcm_substream_t *playback_substream; - snd_pcm_substream_t *capture_substream; - snd_info_entry_t *proc_entry; -} snd_card_harmony_t; -#define chip_t snd_card_harmony_t - -static snd_card_t *snd_harmony_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; - -/* wait to be out of control mode */ -static inline void snd_harmony_wait_cntl(snd_card_harmony_t *harmony) -{ - int timeout = 5000; +#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 struct parisc_device_id snd_harmony_devtable[] = { + /* 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, } +}; - while ( (gsc_readl(harmony->hpa+REG_CNTL) & HARMONY_CNTL_C) && --timeout) - { - /* Wait */ ; - } - if (timeout == 0) DPRINTK(KERN_DEBUG PFX "Error: wait cntl timeouted\n"); -} +MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable); +#define NAME "harmony" +#define PFX NAME ": " -/* - * sample rate routines - */ -static unsigned int snd_card_harmony_rates[] = { - 5125, 6615, 8000, 9600, +static unsigned int snd_harmony_rates[] = { + 5512, 6615, 8000, 9600, 11025, 16000, 18900, 22050, 27428, 32000, 33075, 37800, 44100, 48000 }; -#define RATES sizeof(snd_card_harmony_rates) / sizeof(snd_card_harmony_rates[0]) - -static snd_pcm_hw_constraint_list_t hw_constraint_rates = { - .count = RATES, - .list = snd_card_harmony_rates, - .mask = 0, -}; - -#define HARMONY_SR_8KHZ 0x08 -#define HARMONY_SR_16KHZ 0x09 -#define HARMONY_SR_27KHZ 0x0A -#define HARMONY_SR_32KHZ 0x0B -#define HARMONY_SR_48KHZ 0x0E -#define HARMONY_SR_9KHZ 0x0F -#define HARMONY_SR_5KHZ 0x10 -#define HARMONY_SR_11KHZ 0x11 -#define HARMONY_SR_18KHZ 0x12 -#define HARMONY_SR_22KHZ 0x13 -#define HARMONY_SR_37KHZ 0x14 -#define HARMONY_SR_44KHZ 0x15 -#define HARMONY_SR_33KHZ 0x16 -#define HARMONY_SR_6KHZ 0x17 - -/* bits corresponding to the entries of snd_card_harmony_rates */ static unsigned int rate_bits[14] = { HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ, HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ, @@ -275,848 +99,945 @@ static unsigned int rate_bits[14] = { HARMONY_SR_44KHZ, HARMONY_SR_48KHZ }; -/* snd_card_harmony_rate_bits - * @rate: index of current data rate in list - * returns: harmony hex code for registers - */ -static unsigned int snd_card_harmony_rate_bits(int rate) +static 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) { - unsigned int idx; - - for (idx = 0; idx <= RATES; idx++) - if (snd_card_harmony_rates[idx] == rate) - return rate_bits[idx]; - return HARMONY_SR_44KHZ; /* fallback */ + return __raw_readl(h->iobase + r); } -/* - * update controls (data format, sample rate, number of channels) - * according to value supplied in data structure - */ -void snd_harmony_update_control(snd_card_harmony_t *harmony) +static inline void +harmony_write(struct snd_harmony *h, unsigned r, unsigned long v) { - u32 default_cntl; - - /* Set CNTL */ - default_cntl = (HARMONY_CNTL_C | /* The C bit */ - (harmony->data_format << 6) | /* Set the data format */ - (harmony->stereo_select << 5) | /* Stereo select */ - (harmony->sample_rate)); /* Set sample rate */ - - /* initialize CNTL */ - snd_harmony_wait_cntl(harmony); - - gsc_writel(default_cntl, harmony->hpa+REG_CNTL); - + __raw_writel(v, h->iobase + r); } -/* - * interruption controls routines - */ +static inline void +harmony_wait_for_control(struct snd_harmony *h) +{ + while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ; +} -static void snd_harmony_disable_interrupts(snd_card_harmony_t *chip) +static inline void +harmony_reset(struct snd_harmony *h) { - snd_harmony_wait_cntl(chip); - gsc_writel(0, chip->hpa+REG_DSTATUS); + harmony_write(h, HARMONY_RESET, 1); + mdelay(50); + harmony_write(h, HARMONY_RESET, 0); } -static void snd_harmony_enable_interrupts(snd_card_harmony_t *chip) +static void +harmony_disable_interrupts(struct snd_harmony *h) { - snd_harmony_wait_cntl(chip); - gsc_writel(HARMONY_DSTATUS_IE, chip->hpa+REG_DSTATUS); + u32 dstatus; + harmony_wait_for_control(h); + dstatus = harmony_read(h, HARMONY_DSTATUS); + dstatus &= ~HARMONY_DSTATUS_IE; + harmony_write(h, HARMONY_DSTATUS, dstatus); } -/* - * interruption routine: - * The interrupt routine must provide adresse of next physical pages - * used by harmony - */ -static int snd_card_harmony_interrupt(int irq, void *dev, struct pt_regs *regs) +static void +harmony_enable_interrupts(struct snd_harmony *h) { - snd_card_harmony_t *harmony = (snd_card_harmony_t *)dev; - u32 dstatus = 0; - unsigned long hpa = harmony->hpa; - - /* Turn off interrupts */ - snd_harmony_disable_interrupts(harmony); - - /* wait for control to free */ - snd_harmony_wait_cntl(harmony); - - /* Read dstatus and pcuradd (the current address) */ - dstatus = gsc_readl(hpa+REG_DSTATUS); - - /* Check if this is a request to get the next play buffer */ + 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; + + spin_lock_irqsave(&h->mixer_lock, flags); + harmony_wait_for_control(h); + harmony_write(h, HARMONY_GAINCTL, h->st.gain); + spin_unlock_irqrestore(&h->mixer_lock, flags); +} + +static void +harmony_set_control(struct snd_harmony *h) +{ + u32 ctrl; + unsigned long flags; + + spin_lock_irqsave(&h->lock, flags); + + ctrl = (HARMONY_CNTL_C | + (h->st.format << 6) | + (h->st.stereo << 5) | + (h->st.rate)); + + harmony_wait_for_control(h); + harmony_write(h, HARMONY_CNTL, ctrl); + + spin_unlock_irqrestore(&h->lock, flags); +} + +static irqreturn_t +snd_harmony_interrupt(int irq, void *dev) +{ + u32 dstatus; + struct snd_harmony *h = dev; + + spin_lock(&h->lock); + harmony_disable_interrupts(h); + harmony_wait_for_control(h); + dstatus = harmony_read(h, HARMONY_DSTATUS); + spin_unlock(&h->lock); + if (dstatus & HARMONY_DSTATUS_PN) { - if (harmony->playback_substream) { - harmony->ply_buf += harmony->ply_count; - harmony->ply_buf %= harmony->ply_size; - - gsc_writel(harmony->ply_buffer + harmony->ply_buf, - hpa+REG_PNXTADD); - - snd_pcm_period_elapsed(harmony->playback_substream); - harmony->ply_total++; + if (h->psubs && h->st.playing) { + spin_lock(&h->lock); + h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */ + h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */ + + harmony_write(h, HARMONY_PNXTADD, + h->pbuf.addr + h->pbuf.buf); + h->stats.play_intr++; + spin_unlock(&h->lock); + snd_pcm_period_elapsed(h->psubs); } else { - gsc_writel(harmony->silence_dma.addr + - (HARMONY_BUF_SIZE*harmony->silence_count), - hpa+REG_PNXTADD); - harmony->silence_count++; - harmony->silence_count %= SILENCE_BUFS; + spin_lock(&h->lock); + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + h->stats.silence_intr++; + spin_unlock(&h->lock); } } - - /* Check if we're being asked to fill in a recording buffer */ + if (dstatus & HARMONY_DSTATUS_RN) { - if (harmony->capture_substream) { - harmony->cap_buf += harmony->cap_count; - harmony->cap_buf %= harmony->cap_size; - - gsc_writel(harmony->cap_buffer + harmony->cap_buf, - hpa+REG_RNXTADD); - - snd_pcm_period_elapsed(harmony->capture_substream); - harmony->cap_total++; + if (h->csubs && h->st.capturing) { + spin_lock(&h->lock); + h->cbuf.buf += h->cbuf.count; + h->cbuf.buf %= h->cbuf.size; + + harmony_write(h, HARMONY_RNXTADD, + h->cbuf.addr + h->cbuf.buf); + h->stats.rec_intr++; + spin_unlock(&h->lock); + snd_pcm_period_elapsed(h->csubs); } else { - /* graveyard buffer */ - gsc_writel(harmony->graveyard_dma.addr + - (HARMONY_BUF_SIZE*harmony->graveyard_count), - hpa+REG_RNXTADD); - harmony->graveyard_count++; - harmony->graveyard_count %= GRAVEYARD_BUFS; + spin_lock(&h->lock); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + h->stats.graveyard_intr++; + spin_unlock(&h->lock); } } - snd_harmony_enable_interrupts(harmony); + + spin_lock(&h->lock); + harmony_enable_interrupts(h); + spin_unlock(&h->lock); return IRQ_HANDLED; } -/* - * proc entry - * this proc file will give some debugging info - */ - -static void snd_harmony_proc_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +static unsigned int +snd_harmony_rate_bits(int rate) { - snd_card_harmony_t *harmony = (snd_card_harmony_t *)entry->private_data; - - snd_iprintf(buffer, "LASI Harmony driver\nLaurent Canet \n\n"); - snd_iprintf(buffer, "IRQ %d, hpa %lx, id %d rev %d\n", - harmony->irq, harmony->hpa, - harmony->id, harmony->rev); - snd_iprintf(buffer, "Current gain %lx\n", (unsigned long) harmony->current_gain); - snd_iprintf(buffer, "\tsample rate=%d\n", harmony->sample_rate); - snd_iprintf(buffer, "\tstereo select=%d\n", harmony->stereo_select); - snd_iprintf(buffer, "\tbitperchan=%d\n\n", harmony->data_format); - - snd_iprintf(buffer, "Play status:\n"); - snd_iprintf(buffer, "\tstopped %d\n", harmony->ply_stopped); - snd_iprintf(buffer, "\tbuffer %lx, count %d\n", harmony->ply_buffer, harmony->ply_count); - snd_iprintf(buffer, "\tbuf %d size %d\n\n", harmony->ply_buf, harmony->ply_size); + unsigned int i; - snd_iprintf(buffer, "Capture status:\n"); - snd_iprintf(buffer, "\tstopped %d\n", harmony->cap_stopped); - snd_iprintf(buffer, "\tbuffer %lx, count %d\n", harmony->cap_buffer, harmony->cap_count); - snd_iprintf(buffer, "\tbuf %d, size %d\n\n", harmony->cap_buf, harmony->cap_size); + for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++) + if (snd_harmony_rates[i] == rate) + return rate_bits[i]; - snd_iprintf(buffer, "Funny stats: total played=%d, recorded=%d\n\n", harmony->ply_total, harmony->cap_total); - - snd_iprintf(buffer, "Register:\n"); - snd_iprintf(buffer, "\tgainctl: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_GAINCTL)); - snd_iprintf(buffer, "\tcntl: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_CNTL)); - snd_iprintf(buffer, "\tid: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_ID)); - snd_iprintf(buffer, "\tpcuradd: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_PCURADD)); - snd_iprintf(buffer, "\trcuradd: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_RCURADD)); - snd_iprintf(buffer, "\tpnxtadd: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_PNXTADD)); - snd_iprintf(buffer, "\trnxtadd: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_RNXTADD)); - snd_iprintf(buffer, "\tdstatus: %lx\n", (unsigned long) gsc_readl(harmony->hpa+REG_DSTATUS)); - snd_iprintf(buffer, "\tov: %lx\n\n", (unsigned long) gsc_readl(harmony->hpa+REG_OV)); - + return HARMONY_SR_44KHZ; } -static void __devinit snd_harmony_proc_init(snd_card_harmony_t *harmony) +static struct snd_pcm_hardware snd_harmony_playback = { - snd_info_entry_t *entry; - - if (! snd_card_proc_new(harmony->card, "harmony", &entry)) - snd_info_set_text_ops(entry, harmony, 2048, snd_harmony_proc_read); -} - -/* - * PCM Stuff - */ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW), + .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUF_SIZE, + .period_bytes_min = BUF_SIZE, + .period_bytes_max = BUF_SIZE, + .periods_min = 1, + .periods_max = MAX_BUFS, + .fifo_size = 0, +}; -static int snd_card_harmony_playback_ioctl(snd_pcm_substream_t * substream, - unsigned int cmd, - void *arg) +static struct snd_pcm_hardware snd_harmony_capture = { - return snd_pcm_lib_ioctl(substream, cmd, arg); -} + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW), + .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUF_SIZE, + .period_bytes_min = BUF_SIZE, + .period_bytes_max = BUF_SIZE, + .periods_min = 1, + .periods_max = MAX_BUFS, + .fifo_size = 0, +}; -static int snd_card_harmony_capture_ioctl(snd_pcm_substream_t * substream, - unsigned int cmd, - void *arg) +static int +snd_harmony_playback_trigger(struct snd_pcm_substream *ss, int cmd) { - return snd_pcm_lib_ioctl(substream, cmd, arg); -} + struct snd_harmony *h = snd_pcm_substream_chip(ss); -static int snd_card_harmony_playback_trigger(snd_pcm_substream_t * substream, - int cmd) -{ - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - + if (h->st.capturing) + return -EBUSY; + + spin_lock(&h->lock); switch (cmd) { - case SNDRV_PCM_TRIGGER_STOP: - if (harmony->ply_stopped) - return -EBUSY; - harmony->ply_stopped = 1; - snd_harmony_disable_interrupts(harmony); - break; - case SNDRV_PCM_TRIGGER_START: - if (!harmony->ply_stopped) - return -EBUSY; - harmony->ply_stopped = 0; - /* write the location of the first buffer to play */ - gsc_writel(harmony->ply_buffer, harmony->hpa+REG_PNXTADD); - snd_harmony_enable_interrupts(harmony); - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_SUSPEND: - DPRINTK(KERN_INFO PFX "received unimplemented trigger: %d\n", cmd); - default: - return -EINVAL; + case SNDRV_PCM_TRIGGER_START: + h->st.playing = 1; + harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + harmony_unmute(h); + harmony_enable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_STOP: + h->st.playing = 0; + harmony_mute(h); + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + harmony_disable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + default: + spin_unlock(&h->lock); + snd_BUG(); + return -EINVAL; } + spin_unlock(&h->lock); + return 0; } -static int snd_card_harmony_capture_trigger(snd_pcm_substream_t * substream, - int cmd) +static int +snd_harmony_capture_trigger(struct snd_pcm_substream *ss, int cmd) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_STOP: - if (harmony->cap_stopped) - return -EBUSY; - harmony->cap_stopped = 1; - snd_harmony_disable_interrupts(harmony); - break; - case SNDRV_PCM_TRIGGER_START: - if (!harmony->cap_stopped) - return -EBUSY; - harmony->cap_stopped = 0; - snd_harmony_enable_interrupts(harmony); - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_SUSPEND: - DPRINTK(KERN_INFO PFX "Received unimplemented trigger: %d\n", cmd); - default: - return -EINVAL; - } - return 0; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + + if (h->st.playing) + return -EBUSY; + + spin_lock(&h->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + h->st.capturing = 1; + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr); + harmony_unmute(h); + harmony_enable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_STOP: + h->st.capturing = 0; + harmony_mute(h); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + harmony_disable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + default: + spin_unlock(&h->lock); + snd_BUG(); + return -EINVAL; + } + spin_unlock(&h->lock); + + return 0; } -/* set data format */ -static int snd_harmony_set_data_format(snd_card_harmony_t *harmony, int pcm_format) +static int +snd_harmony_set_data_format(struct snd_harmony *h, int fmt, int force) { - int old_format = harmony->data_format; - int new_format = old_format; - switch (pcm_format) { + int o = h->st.format; + int n; + + switch(fmt) { case SNDRV_PCM_FORMAT_S16_BE: - new_format = HARMONY_DF_16BIT_LINEAR; + n = HARMONY_DF_16BIT_LINEAR; break; case SNDRV_PCM_FORMAT_A_LAW: - new_format = HARMONY_DF_8BIT_ALAW; + n = HARMONY_DF_8BIT_ALAW; break; case SNDRV_PCM_FORMAT_MU_LAW: - new_format = HARMONY_DF_8BIT_ULAW; + n = HARMONY_DF_8BIT_ULAW; break; + default: + n = HARMONY_DF_16BIT_LINEAR; + break; + } + + if (force || o != n) { + snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ / + (snd_pcm_format_physical_width(fmt) + / 8)); } - /* re-initialize silence buffer if needed */ - if (old_format != new_format) - snd_pcm_format_set_silence(pcm_format, harmony->silence_dma.area, - (HARMONY_BUF_SIZE * SILENCE_BUFS * 8) / snd_pcm_format_width(pcm_format)); - return new_format; + return n; } -static int snd_card_harmony_playback_prepare(snd_pcm_substream_t * substream) +static int +snd_harmony_playback_prepare(struct snd_pcm_substream *ss) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; - harmony->ply_size = snd_pcm_lib_buffer_bytes(substream); - harmony->ply_count = snd_pcm_lib_period_bytes(substream); - harmony->ply_buf = 0; - harmony->ply_stopped = 1; + if (h->st.capturing) + return -EBUSY; - /* initialize given sample rate */ - harmony->sample_rate = snd_card_harmony_rate_bits(runtime->rate); - - /* data format */ - harmony->data_format = snd_harmony_set_data_format(haromny, runtime->format); + h->pbuf.size = snd_pcm_lib_buffer_bytes(ss); + h->pbuf.count = snd_pcm_lib_period_bytes(ss); + if (h->pbuf.buf >= h->pbuf.size) + h->pbuf.buf = 0; + h->st.playing = 0; - /* number of channels */ - if (runtime->channels == 2) - harmony->stereo_select = HARMONY_SS_STEREO; - else - harmony->stereo_select = HARMONY_SS_MONO; - - DPRINTK(KERN_INFO PFX "Playback_prepare, sr=%d(%x), df=%x, ss=%x hpa=%lx\n", runtime->rate, - harmony->sample_rate, harmony->data_format, harmony->stereo_select, harmony->hpa); - snd_harmony_update_control(harmony); - harmony->format_initialized = 1; - harmony->ply_buffer = runtime->dma_addr; + h->st.rate = snd_harmony_rate_bits(rt->rate); + h->st.format = snd_harmony_set_data_format(h, rt->format, 0); - return 0; -} + if (rt->channels == 2) + h->st.stereo = HARMONY_SS_STEREO; + else + h->st.stereo = HARMONY_SS_MONO; -static int snd_card_harmony_capture_prepare(snd_pcm_substream_t * substream) -{ - snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - - harmony->cap_size = snd_pcm_lib_buffer_bytes(substream); - harmony->cap_count = snd_pcm_lib_period_bytes(substream); - harmony->cap_count = 0; - harmony->cap_stopped = 1; + harmony_set_control(h); - /* initialize given sample rate */ - harmony->sample_rate = snd_card_harmony_rate_bits(runtime->rate); - - /* data format */ - harmony->data_format = snd_harmony_set_data_format(haromny, runtime->format); - - /* number of channels */ - if (runtime->channels == 1) - harmony->stereo_select = HARMONY_SS_MONO; - else if (runtime->channels == 2) - harmony->stereo_select = HARMONY_SS_STEREO; - - snd_harmony_update_control(harmony); - harmony->format_initialized = 1; - - harmony->cap_buffer = runtime->dma_addr; + h->pbuf.addr = rt->dma_addr; return 0; } -static snd_pcm_uframes_t snd_card_harmony_capture_pointer(snd_pcm_substream_t * substream) +static int +snd_harmony_capture_prepare(struct snd_pcm_substream *ss) { - snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - unsigned long rcuradd; - int recorded; - - if (harmony->cap_stopped) return 0; - if (harmony->capture_substream == NULL) return 0; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; - rcuradd = gsc_readl(harmony->hpa+REG_RCURADD); - recorded = (rcuradd - harmony->cap_buffer); - recorded %= harmony->cap_size; - - return bytes_to_frames(runtime, recorded); -} + if (h->st.playing) + return -EBUSY; -/* - */ + h->cbuf.size = snd_pcm_lib_buffer_bytes(ss); + h->cbuf.count = snd_pcm_lib_period_bytes(ss); + if (h->cbuf.buf >= h->cbuf.size) + h->cbuf.buf = 0; + h->st.capturing = 0; + + h->st.rate = snd_harmony_rate_bits(rt->rate); + h->st.format = snd_harmony_set_data_format(h, rt->format, 0); + + if (rt->channels == 2) + h->st.stereo = HARMONY_SS_STEREO; + else + h->st.stereo = HARMONY_SS_MONO; + + harmony_set_control(h); + + h->cbuf.addr = rt->dma_addr; + + return 0; +} -static snd_pcm_uframes_t snd_card_harmony_playback_pointer(snd_pcm_substream_t * substream) +static snd_pcm_uframes_t +snd_harmony_playback_pointer(struct snd_pcm_substream *ss) { - snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - int played; - long int pcuradd = gsc_readl(harmony->hpa+REG_PCURADD); - - if ((harmony->ply_stopped) || (harmony->playback_substream == NULL)) return 0; - if ((harmony->ply_buffer == 0) || (harmony->ply_size == 0)) return 0; - - played = (pcuradd - harmony->ply_buffer); - - printk(KERN_DEBUG PFX "Pointer is %lx-%lx = %d\n", pcuradd, harmony->ply_buffer, played); + struct snd_pcm_runtime *rt = ss->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + unsigned long pcuradd; + unsigned long played; - if (pcuradd > harmony->ply_buffer + harmony->ply_size) return 0; + if (!(h->st.playing) || (h->psubs == NULL)) + return 0; + + if ((h->pbuf.addr == 0) || (h->pbuf.size == 0)) + return 0; - return bytes_to_frames(runtime, played); + pcuradd = harmony_read(h, HARMONY_PCURADD); + played = pcuradd - h->pbuf.addr; + +#ifdef HARMONY_DEBUG + printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n", + pcuradd, h->pbuf.addr, played); +#endif + + if (pcuradd > h->pbuf.addr + h->pbuf.size) { + return 0; + } + + return bytes_to_frames(rt, played); } -static snd_pcm_hardware_t snd_card_harmony_playback = +static snd_pcm_uframes_t +snd_harmony_capture_pointer(struct snd_pcm_substream *ss) { - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_JOINT_DUPLEX | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BLOCK_TRANSFER), - .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_BE | - SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_MU_LAW), - .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, - .rate_min = 5500, - .rate_max = 48000, - .channels_min = 1, - .channels_max = 2, - .buffer_bytes_max = MAX_BUFFER_SIZE, - .period_bytes_min = HARMONY_BUF_SIZE, - .period_bytes_max = HARMONY_BUF_SIZE, - .periods_min = 1, - .periods_max = MAX_BUFS, - .fifo_size = 0, -}; + struct snd_pcm_runtime *rt = ss->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + unsigned long rcuradd; + unsigned long caught; -static snd_pcm_hardware_t snd_card_harmony_capture = -{ - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_JOINT_DUPLEX | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BLOCK_TRANSFER), - .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_BE | - SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_MU_LAW), - .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, - .rate_min = 5500, - .rate_max = 48000, - .channels_min = 1, - .channels_max = 2, - .buffer_bytes_max = MAX_BUFFER_SIZE, - .period_bytes_min = HARMONY_BUF_SIZE, - .period_bytes_max = HARMONY_BUF_SIZE, - .periods_min = 1, - .periods_max = MAX_BUFS, - .fifo_size = 0, -}; + if (!(h->st.capturing) || (h->csubs == NULL)) + return 0; + + if ((h->cbuf.addr == 0) || (h->cbuf.size == 0)) + return 0; + + rcuradd = harmony_read(h, HARMONY_RCURADD); + caught = rcuradd - h->cbuf.addr; + +#ifdef HARMONY_DEBUG + printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n", + rcuradd, h->cbuf.addr, caught); +#endif + + if (rcuradd > h->cbuf.addr + h->cbuf.size) { + return 0; + } + + return bytes_to_frames(rt, caught); +} -static int snd_card_harmony_playback_open(snd_pcm_substream_t * substream) +static int +snd_harmony_playback_open(struct snd_pcm_substream *ss) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; int err; - harmony->playback_substream = substream; - runtime->hw = snd_card_harmony_playback; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraint_rates); + h->psubs = ss; + rt->hw = snd_harmony_playback; + snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraint_rates); - if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) return err; return 0; } -static int snd_card_harmony_capture_open(snd_pcm_substream_t * substream) +static int +snd_harmony_capture_open(struct snd_pcm_substream *ss) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; - int err; - - harmony->capture_substream = substream; - runtime->hw = snd_card_harmony_capture; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraint_rates); - if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) - return err; - return 0; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + int err; + h->csubs = ss; + rt->hw = snd_harmony_capture; + snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraint_rates); + + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; } -static int snd_card_harmony_playback_close(snd_pcm_substream_t * substream) +static int +snd_harmony_playback_close(struct snd_pcm_substream *ss) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - - harmony->playback_substream = NULL; - harmony->ply_size = 0; - harmony->ply_buf = 0; - harmony->ply_buffer = 0; - harmony->ply_count = 0; - harmony->ply_stopped = 1; - harmony->format_initialized = 0; - + struct snd_harmony *h = snd_pcm_substream_chip(ss); + h->psubs = NULL; return 0; } -static int snd_card_harmony_capture_close(snd_pcm_substream_t * substream) +static int +snd_harmony_capture_close(struct snd_pcm_substream *ss) { - snd_card_harmony_t *harmony = snd_pcm_substream_chip(substream); - - harmony->capture_substream = NULL; - harmony->cap_size = 0; - harmony->cap_buf = 0; - harmony->cap_buffer = 0; - harmony->cap_count = 0; - harmony->cap_stopped = 1; - harmony->format_initialized = 0; - - return 0; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + h->csubs = NULL; + return 0; } -static int snd_card_harmony_hw_params(snd_pcm_substream_t *substream, - snd_pcm_hw_params_t * hw_params) +static int +snd_harmony_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw) { int err; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + + err = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw)); + if (err > 0 && h->dma.type == SNDRV_DMA_TYPE_CONTINUOUS) + ss->runtime->dma_addr = __pa(ss->runtime->dma_area); - err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); - DPRINTK(KERN_INFO PFX "HW Params returned %d, dma_addr %lx\n", err, - (unsigned long)substream->runtime->dma_addr); return err; } -static int snd_card_harmony_hw_free(snd_pcm_substream_t *substream) +static int +snd_harmony_hw_free(struct snd_pcm_substream *ss) { - snd_pcm_lib_free_pages(substream); - return 0; + return snd_pcm_lib_free_pages(ss); } -static snd_pcm_ops_t snd_card_harmony_playback_ops = { - .open = snd_card_harmony_playback_open, - .close = snd_card_harmony_playback_close, - .ioctl = snd_card_harmony_playback_ioctl, - .hw_params = snd_card_harmony_hw_params, - .hw_free = snd_card_harmony_hw_free, - .prepare = snd_card_harmony_playback_prepare, - .trigger = snd_card_harmony_playback_trigger, - .pointer = snd_card_harmony_playback_pointer, +static struct snd_pcm_ops snd_harmony_playback_ops = { + .open = snd_harmony_playback_open, + .close = snd_harmony_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_harmony_hw_params, + .hw_free = snd_harmony_hw_free, + .prepare = snd_harmony_playback_prepare, + .trigger = snd_harmony_playback_trigger, + .pointer = snd_harmony_playback_pointer, }; -static snd_pcm_ops_t snd_card_harmony_capture_ops = { - .open = snd_card_harmony_capture_open, - .close = snd_card_harmony_capture_close, - .ioctl = snd_card_harmony_capture_ioctl, - .hw_params = snd_card_harmony_hw_params, - .hw_free = snd_card_harmony_hw_free, - .prepare = snd_card_harmony_capture_prepare, - .trigger = snd_card_harmony_capture_trigger, - .pointer = snd_card_harmony_capture_pointer, +static struct snd_pcm_ops snd_harmony_capture_ops = { + .open = snd_harmony_capture_open, + .close = snd_harmony_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_harmony_hw_params, + .hw_free = snd_harmony_hw_free, + .prepare = snd_harmony_capture_prepare, + .trigger = snd_harmony_capture_trigger, + .pointer = snd_harmony_capture_pointer, }; -static int snd_card_harmony_pcm_init(snd_card_harmony_t *harmony, int device) +static int +snd_harmony_pcm_init(struct snd_harmony *h) { - snd_pcm_t *pcm; + struct snd_pcm *pcm; int err; - /* Request that IRQ */ - if (request_irq(harmony->irq, snd_card_harmony_interrupt, 0 ,"harmony", harmony)) { - printk(KERN_ERR PFX "Error requesting irq %d.\n", harmony->irq); - return -EFAULT; - } + harmony_disable_interrupts(h); - snd_harmony_disable_interrupts(harmony); - - if ((err = snd_pcm_new(harmony->card, "Harmony", device, 1, 1, &pcm)) < 0) + err = snd_pcm_new(h->card, "harmony", 0, 1, 1, &pcm); + if (err < 0) return err; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_harmony_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_harmony_capture_ops); - - pcm->private_data = harmony; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_harmony_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_harmony_capture_ops); + + pcm->private_data = h; pcm->info_flags = 0; - strcpy(pcm->name, "Harmony"); - harmony->pcm = pcm; + strcpy(pcm->name, "harmony"); + h->pcm = pcm; + + h->psubs = NULL; + h->csubs = NULL; /* initialize graveyard buffer */ - harmony->dma_dev.type = SNDRV_DMA_TYPE_DEV; - harmony->dma_dev.dev = &harmony->pa_dev->dev; - err = snd_dma_alloc_pages(&harmony->dma_dev, HARMONY_BUF_SIZE*GRAVEYARD_BUFS, - &harmony->graveyard_dma); - if (err < 0) + h->dma.type = SNDRV_DMA_TYPE_DEV; + h->dma.dev = &h->dev->dev; + err = snd_dma_alloc_pages(h->dma.type, + h->dma.dev, + BUF_SIZE*GRAVEYARD_BUFS, + &h->gdma); + if (err < 0) { + printk(KERN_ERR PFX "cannot allocate graveyard buffer!\n"); return err; - harmony->graveyard_count = 0; + } /* initialize silence buffers */ - err = snd_dma_alloc_pages(&harmony->dma_dev, HARMONY_BUF_SIZE*SILENCE_BUFS, - &harmony->silence_dma); - if (err < 0) + err = snd_dma_alloc_pages(h->dma.type, + h->dma.dev, + BUF_SIZE*SILENCE_BUFS, + &h->sdma); + if (err < 0) { + printk(KERN_ERR PFX "cannot allocate silence buffer!\n"); return err; - harmony->silence_count = 0; + } - harmony->ply_stopped = harmony->cap_stopped = 1; - - harmony->playback_substream = NULL; - harmony->capture_substream = NULL; - harmony->graveyard_count = 0; - - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, - &harmony->pa_dev->dev, - MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); + /* pre-allocate space for DMA */ + err = snd_pcm_lib_preallocate_pages_for_all(pcm, h->dma.type, + h->dma.dev, + MAX_BUF_SIZE, + MAX_BUF_SIZE); + if (err < 0) { + printk(KERN_ERR PFX "buffer allocation error: %d\n", err); + return err; + } + + h->st.format = snd_harmony_set_data_format(h, + SNDRV_PCM_FORMAT_S16_BE, 1); return 0; } -/* - * mixer routines - */ - -static void snd_harmony_set_new_gain(snd_card_harmony_t *harmony) +static void +snd_harmony_set_new_gain(struct snd_harmony *h) { - DPRINTK(KERN_INFO PFX "Setting new gain %x at %lx\n", harmony->current_gain, harmony->hpa+REG_GAINCTL); - /* Wait until we're out of control mode */ - snd_harmony_wait_cntl(harmony); - - gsc_writel(harmony->current_gain, harmony->hpa+REG_GAINCTL); + harmony_wait_for_control(h); + harmony_write(h, HARMONY_GAINCTL, h->st.gain); } -#define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_harmony_mixercontrol_info, \ - .get = snd_harmony_volume_get, .put = snd_harmony_volume_put, \ - .private_value = ((left_shift) | ((right_shift) << 8) | ((mask) << 16) | ((invert) << 24)) } - -static int snd_harmony_mixercontrol_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo) +static int +snd_harmony_mixercontrol_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) { - int mask = (kcontrol->private_value >> 16) & 0xff; - int left_shift = (kcontrol->private_value) & 0xff; - int right_shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kc->private_value >> 16) & 0xff; + int left_shift = (kc->private_value) & 0xff; + int right_shift = (kc->private_value >> 8) & 0xff; - uinfo->type = (mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER); - uinfo->count = (left_shift == right_shift) ? 1 : 2; + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : + SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = left_shift == right_shift ? 1 : 2; uinfo->value.integer.min = 0; uinfo->value.integer.max = mask; + return 0; } - -static int snd_harmony_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) + +static int +snd_harmony_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) { - snd_card_harmony_t *harmony = _snd_kcontrol_chip(kcontrol); - int shift_left = (kcontrol->private_value) & 0xff; - int shift_right = (kcontrol->private_value >> 8) & 0xff; - int mask = (kcontrol->private_value >> 16) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0xff; - unsigned long flags; + struct snd_harmony *h = snd_kcontrol_chip(kc); + int shift_left = (kc->private_value) & 0xff; + int shift_right = (kc->private_value >> 8) & 0xff; + int mask = (kc->private_value >> 16) & 0xff; + int invert = (kc->private_value >> 24) & 0xff; int left, right; - spin_lock_irqsave(&harmony->mixer_lock, flags); - left = (harmony->current_gain >> shift_left) & mask; - right = (harmony->current_gain >> shift_right) & mask; + spin_lock_irq(&h->mixer_lock); + left = (h->st.gain >> shift_left) & mask; + right = (h->st.gain >> shift_right) & mask; if (invert) { left = mask - left; right = mask - right; } + ucontrol->value.integer.value[0] = left; - ucontrol->value.integer.value[1] = right; - spin_unlock_irqrestore(&harmony->mixer_lock, flags); + if (shift_left != shift_right) + ucontrol->value.integer.value[1] = right; + + spin_unlock_irq(&h->mixer_lock); return 0; } -static int snd_harmony_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +static int +snd_harmony_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) { - snd_card_harmony_t *harmony = _snd_kcontrol_chip(kcontrol); - int shift_left = (kcontrol->private_value) & 0xff; - int shift_right = (kcontrol->private_value >> 8) & 0xff; - int mask = (kcontrol->private_value >> 16) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0xff; - unsigned long flags; + struct snd_harmony *h = snd_kcontrol_chip(kc); + int shift_left = (kc->private_value) & 0xff; + int shift_right = (kc->private_value >> 8) & 0xff; + int mask = (kc->private_value >> 16) & 0xff; + int invert = (kc->private_value >> 24) & 0xff; int left, right; - int old_gain = harmony->current_gain; + int old_gain = h->st.gain; + spin_lock_irq(&h->mixer_lock); + left = ucontrol->value.integer.value[0] & mask; - right = ucontrol->value.integer.value[1] & mask; - if (invert) { + if (invert) left = mask - left; - right = mask - right; + h->st.gain &= ~( (mask << shift_left ) ); + h->st.gain |= (left << shift_left); + + if (shift_left != shift_right) { + right = ucontrol->value.integer.value[1] & mask; + if (invert) + right = mask - right; + h->st.gain &= ~( (mask << shift_right) ); + h->st.gain |= (right << shift_right); } + + snd_harmony_set_new_gain(h); + + spin_unlock_irq(&h->mixer_lock); - spin_lock_irqsave(&harmony->mixer_lock, flags); - harmony->current_gain = harmony->current_gain & ~( (mask << shift_right) | (mask << shift_left)); - harmony->current_gain = harmony->current_gain | ((left << shift_left) | (right << shift_right) ); - snd_harmony_set_new_gain(harmony); - spin_unlock_irqrestore(&harmony->mixer_lock, flags); - - return (old_gain - harmony->current_gain); + return h->st.gain != old_gain; } -#define HARMONY_CONTROLS (sizeof(snd_harmony_controls)/sizeof(snd_kcontrol_new_t)) +static int +snd_harmony_captureroute_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[2] = { "Line", "Mic" }; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} -static snd_kcontrol_new_t snd_harmony_controls[] = { -HARMONY_VOLUME("PCM Capture Volume", 12, 16, 0x0f, 0), -HARMONY_VOLUME("Master Volume", 20, 20, 0x0f, 1), -HARMONY_VOLUME("PCM Playback Volume", 6, 0, 0x3f, 1), -}; +static int +snd_harmony_captureroute_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_harmony *h = snd_kcontrol_chip(kc); + int value; + + spin_lock_irq(&h->mixer_lock); + + value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1; + ucontrol->value.enumerated.item[0] = value; + + spin_unlock_irq(&h->mixer_lock); + + return 0; +} -static void __init snd_harmony_reset_codec(snd_card_harmony_t *harmony) +static int +snd_harmony_captureroute_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) { - snd_harmony_wait_cntl(harmony); - gsc_writel(1, harmony->hpa+REG_RESET); - mdelay(50); /* wait 50 ms */ - gsc_writel(0, harmony->hpa+REG_RESET); + struct snd_harmony *h = snd_kcontrol_chip(kc); + int value; + int old_gain = h->st.gain; + + spin_lock_irq(&h->mixer_lock); + + value = ucontrol->value.enumerated.item[0] & 1; + h->st.gain &= ~HARMONY_GAIN_IS_MASK; + h->st.gain |= value << HARMONY_GAIN_IS_SHIFT; + + snd_harmony_set_new_gain(h); + + spin_unlock_irq(&h->mixer_lock); + + return h->st.gain != old_gain; } -/* - * Mute all the output and reset Harmony. - */ +#define HARMONY_CONTROLS ARRAY_SIZE(snd_harmony_controls) + +#define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_harmony_mixercontrol_info, \ + .get = snd_harmony_volume_get, .put = snd_harmony_volume_put, \ + .private_value = ((left_shift) | ((right_shift) << 8) | \ + ((mask) << 16) | ((invert) << 24)) } + +static struct snd_kcontrol_new snd_harmony_controls[] = { + HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT, + HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1), + HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT, + HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0), + HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT, + HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Route", + .info = snd_harmony_captureroute_info, + .get = snd_harmony_captureroute_get, + .put = snd_harmony_captureroute_put + }, + HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT, + HARMONY_GAIN_SE_SHIFT, 1, 0), + HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT, + HARMONY_GAIN_LE_SHIFT, 1, 0), + HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT, + HARMONY_GAIN_HE_SHIFT, 1, 0), +}; -static void __init snd_harmony_mixer_reset(snd_card_harmony_t *harmony) +static void __init +snd_harmony_mixer_reset(struct snd_harmony *h) { - harmony->current_gain = HARMONY_GAIN_TOTAL_SILENCE; - snd_harmony_set_new_gain(harmony); - snd_harmony_reset_codec(harmony); - harmony->current_gain = HARMONY_GAIN_DEFAULT; - snd_harmony_set_new_gain(harmony); + harmony_mute(h); + harmony_reset(h); + h->st.gain = HARMONY_GAIN_DEFAULT; + harmony_unmute(h); } - -static int __init snd_card_harmony_mixer_init(snd_card_harmony_t *harmony) +static int __init +snd_harmony_mixer_init(struct snd_harmony *h) { - snd_card_t *card = harmony->card; + struct snd_card *card = h->card; int idx, err; - snd_assert(harmony != NULL, return -EINVAL); + snd_assert(h != NULL, return -EINVAL); strcpy(card->mixername, "Harmony Gain control interface"); for (idx = 0; idx < HARMONY_CONTROLS; idx++) { - if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_harmony_controls[idx], harmony))) < 0) + err = snd_ctl_add(card, + snd_ctl_new1(&snd_harmony_controls[idx], h)); + if (err < 0) return err; } - snd_harmony_mixer_reset(harmony); + snd_harmony_mixer_reset(h); return 0; } -static int snd_card_harmony_create(snd_card_t *card, struct parisc_device *pa_dev, snd_card_harmony_t *harmony) +static int +snd_harmony_free(struct snd_harmony *h) { - u32 cntl; - - harmony->card = card; - - harmony->pa_dev = pa_dev; + if (h->gdma.addr) + snd_dma_free_pages(&h->gdma); + if (h->sdma.addr) + snd_dma_free_pages(&h->sdma); - /* Set the HPA of harmony */ - harmony->hpa = pa_dev->hpa; - - harmony->irq = pa_dev->irq; - if (!harmony->irq) { - printk(KERN_ERR PFX "no irq found\n"); - return -ENODEV; - } + if (h->irq >= 0) + free_irq(h->irq, h); - /* Grab the ID and revision from the device */ - harmony->id = (gsc_readl(harmony->hpa+REG_ID)&0x00ff0000) >> 16; - if ((harmony->id | 1) != 0x15) { - printk(KERN_WARNING PFX "wrong harmony id 0x%02x\n", harmony->id); - return -EBUSY; - } - cntl = gsc_readl(harmony->hpa+REG_CNTL); - harmony->rev = (cntl>>20) & 0xff; + if (h->iobase) + iounmap(h->iobase); - printk(KERN_INFO "Lasi Harmony Audio driver h/w id %i, rev. %i at 0x%lx, IRQ %i\n", harmony->id, harmony->rev, pa_dev->hpa, harmony->irq); - - /* Make sure the control bit isn't set, although I don't think it - ever is. */ - if (cntl & HARMONY_CNTL_C) { - printk(KERN_WARNING PFX "CNTL busy\n"); - harmony->hpa = 0; - return -EBUSY; - } - + parisc_set_drvdata(h->dev, NULL); + + kfree(h); return 0; } - -static int __init snd_card_harmony_probe(struct parisc_device *pa_dev) + +static int +snd_harmony_dev_free(struct snd_device *dev) +{ + struct snd_harmony *h = dev->device_data; + return snd_harmony_free(h); +} + +static int __devinit +snd_harmony_create(struct snd_card *card, + struct parisc_device *padev, + struct snd_harmony **rchip) { - static int dev; - snd_card_harmony_t *chip; - snd_card_t *card; int err; - - if (dev >= SNDRV_CARDS) - return -ENODEV; - if (!enable[dev]) { - dev++; - return -ENOENT; - } - - snd_harmony_cards[dev] = snd_card_new(index[dev], id[dev], THIS_MODULE, - sizeof(snd_card_harmony_t)); - card = snd_harmony_cards[dev]; - - if (card == NULL) + struct snd_harmony *h; + static struct snd_device_ops ops = { + .dev_free = snd_harmony_dev_free, + }; + + *rchip = NULL; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (h == NULL) return -ENOMEM; - chip = (struct snd_card_harmony *)card->private_data; - spin_lock_init(&chip->control_lock); - spin_lock_init(&chip->mixer_lock); - - if ((err = snd_card_harmony_create(card, pa_dev, chip)) < 0) { - printk(KERN_ERR PFX "Creation failed\n"); - snd_card_free(card); - return err; - } - if ((err = snd_card_harmony_pcm_init(chip, dev)) < 0) { - printk(KERN_ERR PFX "PCM Init failed\n"); - snd_card_free(card); - return err; + + h->hpa = padev->hpa.start; + h->card = card; + h->dev = padev; + h->irq = -1; + h->iobase = ioremap_nocache(padev->hpa.start, HARMONY_SIZE); + if (h->iobase == NULL) { + printk(KERN_ERR PFX "unable to remap hpa 0x%lx\n", + padev->hpa.start); + err = -EBUSY; + goto free_and_ret; } - if ((err = snd_card_harmony_mixer_init(chip)) < 0) { - printk(KERN_ERR PFX "Mixer init failed\n"); - snd_card_free(card); - return err; + + err = request_irq(padev->irq, snd_harmony_interrupt, 0, + "harmony", h); + if (err) { + printk(KERN_ERR PFX "could not obtain interrupt %d", + padev->irq); + goto free_and_ret; } - - snd_harmony_proc_init(chip); - - strcpy(card->driver, "Harmony"); - strcpy(card->shortname, "ALSA driver for LASI Harmony"); - sprintf(card->longname, "%s at h/w, id %i, rev. %i hpa 0x%lx, IRQ %i\n",card->shortname, chip->id, chip->rev, pa_dev->hpa, chip->irq); + h->irq = padev->irq; - if ((err = snd_card_register(card)) < 0) { - snd_card_free(card); - return err; - } + spin_lock_init(&h->mixer_lock); + spin_lock_init(&h->lock); - printk(KERN_DEBUG PFX "Successfully registered harmony pcm backend & mixer %d\n", dev); - dev++; - return 0; -} + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + h, &ops)) < 0) { + goto free_and_ret; + } -static struct parisc_device_id snd_card_harmony_devicetbl[] = { - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, /* Bushmaster/Flounder */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, /* 712/715 Audio */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, /* Pace Audio */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, /* Outfield / Coral II */ - { 0, } -}; + snd_card_set_dev(card, &padev->dev); -MODULE_DEVICE_TABLE(parisc, snd_card_harmony_devicetbl); + *rchip = h; -/* - * bloc device parisc. c'est une structure qui definit un device - * que l'on trouve sur parisc. - * On y trouve les differents numeros HVERSION correspondant au device - * en question (ce qui permet a l'inventory de l'identifier) et la fonction - * d'initialisation du chose - */ + return 0; -static struct parisc_driver snd_card_harmony_driver = { - .name = "Lasi ALSA Harmony", - .id_table = snd_card_harmony_devicetbl, - .probe = snd_card_harmony_probe, -}; +free_and_ret: + snd_harmony_free(h); + return err; +} -static int __init alsa_card_harmony_init(void) +static int __devinit +snd_harmony_probe(struct parisc_device *padev) { int err; - - if ((err = register_parisc_driver(&snd_card_harmony_driver)) < 0) { - printk(KERN_ERR "Harmony soundcard not found or device busy\n"); - return err; - } + struct snd_card *card; + struct snd_harmony *h; + + card = snd_card_new(index, id, THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + err = snd_harmony_create(card, padev, &h); + if (err < 0) + goto free_and_ret; + + err = snd_harmony_pcm_init(h); + if (err < 0) + goto free_and_ret; + + err = snd_harmony_mixer_init(h); + if (err < 0) + goto free_and_ret; + + strcpy(card->driver, "harmony"); + strcpy(card->shortname, "Harmony"); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, h->hpa, h->irq); + err = snd_card_register(card); + if (err < 0) + goto free_and_ret; + + parisc_set_drvdata(padev, card); return 0; + +free_and_ret: + snd_card_free(card); + return err; } -static void __exit alsa_card_harmony_exit(void) +static int __devexit +snd_harmony_remove(struct parisc_device *padev) { - int idx; - snd_card_harmony_t *harmony; - - for (idx = 0; idx < SNDRV_CARDS; idx++) - { - if (snd_harmony_cards[idx] != NULL) - { - DPRINTK(KERN_INFO PFX "Freeing card %d\n", idx); - harmony = snd_harmony_cards[idx]->private_data; - free_irq(harmony->irq, snd_card_harmony_interrupt); - printk(KERN_INFO PFX "Card unloaded %d, irq=%d\n", idx, harmony->irq); - snd_card_free(snd_harmony_cards[idx]); - } - } + snd_card_free(parisc_get_drvdata(padev)); + parisc_set_drvdata(padev, NULL); + return 0; } -module_init(alsa_card_harmony_init) -module_exit(alsa_card_harmony_exit) +static struct parisc_driver snd_harmony_driver = { + .name = "harmony", + .id_table = snd_harmony_devtable, + .probe = snd_harmony_probe, + .remove = snd_harmony_remove, +}; + +static int __init +alsa_harmony_init(void) +{ + return register_parisc_driver(&snd_harmony_driver); +} + +static void __exit +alsa_harmony_fini(void) +{ + unregister_parisc_driver(&snd_harmony_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyle McMartin "); +MODULE_DESCRIPTION("Harmony sound driver"); + +module_init(alsa_harmony_init); +module_exit(alsa_harmony_fini);