162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Hewlett-Packard Harmony audio driver 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * This is a driver for the Harmony audio chipset found 562306a36Sopenharmony_ci * on the LASI ASIC of various early HP PA-RISC workstations. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2004, Kyle McMartin <kyle@{debian.org,parisc-linux.org}> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Based on the previous Harmony incarnations by, 1062306a36Sopenharmony_ci * Copyright 2000 (c) Linuxcare Canada, Alex deVries 1162306a36Sopenharmony_ci * Copyright 2000-2003 (c) Helge Deller 1262306a36Sopenharmony_ci * Copyright 2001 (c) Matthieu Delahaye 1362306a36Sopenharmony_ci * Copyright 2001 (c) Jean-Christophe Vaugeois 1462306a36Sopenharmony_ci * Copyright 2003 (c) Laurent Canet 1562306a36Sopenharmony_ci * Copyright 2004 (c) Stuart Brady 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Notes: 1862306a36Sopenharmony_ci * - graveyard and silence buffers last for lifetime of 1962306a36Sopenharmony_ci * the driver. playback and capture buffers are allocated 2062306a36Sopenharmony_ci * per _open()/_close(). 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * TODO: 2362306a36Sopenharmony_ci */ 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include <linux/init.h> 2662306a36Sopenharmony_ci#include <linux/slab.h> 2762306a36Sopenharmony_ci#include <linux/time.h> 2862306a36Sopenharmony_ci#include <linux/wait.h> 2962306a36Sopenharmony_ci#include <linux/delay.h> 3062306a36Sopenharmony_ci#include <linux/module.h> 3162306a36Sopenharmony_ci#include <linux/interrupt.h> 3262306a36Sopenharmony_ci#include <linux/spinlock.h> 3362306a36Sopenharmony_ci#include <linux/dma-mapping.h> 3462306a36Sopenharmony_ci#include <linux/io.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#include <sound/core.h> 3762306a36Sopenharmony_ci#include <sound/pcm.h> 3862306a36Sopenharmony_ci#include <sound/control.h> 3962306a36Sopenharmony_ci#include <sound/rawmidi.h> 4062306a36Sopenharmony_ci#include <sound/initval.h> 4162306a36Sopenharmony_ci#include <sound/info.h> 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#include <asm/hardware.h> 4462306a36Sopenharmony_ci#include <asm/parisc-device.h> 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#include "harmony.h" 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ 4962306a36Sopenharmony_cistatic char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ 5062306a36Sopenharmony_cimodule_param(index, int, 0444); 5162306a36Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for Harmony driver."); 5262306a36Sopenharmony_cimodule_param(id, charp, 0444); 5362306a36Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for Harmony driver."); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic const struct parisc_device_id snd_harmony_devtable[] __initconst = { 5762306a36Sopenharmony_ci /* bushmaster / flounder */ 5862306a36Sopenharmony_ci { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, 5962306a36Sopenharmony_ci /* 712 / 715 */ 6062306a36Sopenharmony_ci { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, 6162306a36Sopenharmony_ci /* pace */ 6262306a36Sopenharmony_ci { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, 6362306a36Sopenharmony_ci /* outfield / coral II */ 6462306a36Sopenharmony_ci { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, 6562306a36Sopenharmony_ci { 0, } 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(parisc, snd_harmony_devtable); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci#define NAME "harmony" 7162306a36Sopenharmony_ci#define PFX NAME ": " 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic const unsigned int snd_harmony_rates[] = { 7462306a36Sopenharmony_ci 5512, 6615, 8000, 9600, 7562306a36Sopenharmony_ci 11025, 16000, 18900, 22050, 7662306a36Sopenharmony_ci 27428, 32000, 33075, 37800, 7762306a36Sopenharmony_ci 44100, 48000 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic const unsigned int rate_bits[14] = { 8162306a36Sopenharmony_ci HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ, 8262306a36Sopenharmony_ci HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ, 8362306a36Sopenharmony_ci HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ, 8462306a36Sopenharmony_ci HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ, 8562306a36Sopenharmony_ci HARMONY_SR_44KHZ, HARMONY_SR_48KHZ 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic const struct snd_pcm_hw_constraint_list hw_constraint_rates = { 8962306a36Sopenharmony_ci .count = ARRAY_SIZE(snd_harmony_rates), 9062306a36Sopenharmony_ci .list = snd_harmony_rates, 9162306a36Sopenharmony_ci .mask = 0, 9262306a36Sopenharmony_ci}; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic inline unsigned long 9562306a36Sopenharmony_ciharmony_read(struct snd_harmony *h, unsigned r) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci return __raw_readl(h->iobase + r); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic inline void 10162306a36Sopenharmony_ciharmony_write(struct snd_harmony *h, unsigned r, unsigned long v) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci __raw_writel(v, h->iobase + r); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic inline void 10762306a36Sopenharmony_ciharmony_wait_for_control(struct snd_harmony *h) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic inline void 11362306a36Sopenharmony_ciharmony_reset(struct snd_harmony *h) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci harmony_write(h, HARMONY_RESET, 1); 11662306a36Sopenharmony_ci mdelay(50); 11762306a36Sopenharmony_ci harmony_write(h, HARMONY_RESET, 0); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void 12162306a36Sopenharmony_ciharmony_disable_interrupts(struct snd_harmony *h) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci u32 dstatus; 12462306a36Sopenharmony_ci harmony_wait_for_control(h); 12562306a36Sopenharmony_ci dstatus = harmony_read(h, HARMONY_DSTATUS); 12662306a36Sopenharmony_ci dstatus &= ~HARMONY_DSTATUS_IE; 12762306a36Sopenharmony_ci harmony_write(h, HARMONY_DSTATUS, dstatus); 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic void 13162306a36Sopenharmony_ciharmony_enable_interrupts(struct snd_harmony *h) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci u32 dstatus; 13462306a36Sopenharmony_ci harmony_wait_for_control(h); 13562306a36Sopenharmony_ci dstatus = harmony_read(h, HARMONY_DSTATUS); 13662306a36Sopenharmony_ci dstatus |= HARMONY_DSTATUS_IE; 13762306a36Sopenharmony_ci harmony_write(h, HARMONY_DSTATUS, dstatus); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void 14162306a36Sopenharmony_ciharmony_mute(struct snd_harmony *h) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci unsigned long flags; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci spin_lock_irqsave(&h->mixer_lock, flags); 14662306a36Sopenharmony_ci harmony_wait_for_control(h); 14762306a36Sopenharmony_ci harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE); 14862306a36Sopenharmony_ci spin_unlock_irqrestore(&h->mixer_lock, flags); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic void 15262306a36Sopenharmony_ciharmony_unmute(struct snd_harmony *h) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci unsigned long flags; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci spin_lock_irqsave(&h->mixer_lock, flags); 15762306a36Sopenharmony_ci harmony_wait_for_control(h); 15862306a36Sopenharmony_ci harmony_write(h, HARMONY_GAINCTL, h->st.gain); 15962306a36Sopenharmony_ci spin_unlock_irqrestore(&h->mixer_lock, flags); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic void 16362306a36Sopenharmony_ciharmony_set_control(struct snd_harmony *h) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci u32 ctrl; 16662306a36Sopenharmony_ci unsigned long flags; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci spin_lock_irqsave(&h->lock, flags); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci ctrl = (HARMONY_CNTL_C | 17162306a36Sopenharmony_ci (h->st.format << 6) | 17262306a36Sopenharmony_ci (h->st.stereo << 5) | 17362306a36Sopenharmony_ci (h->st.rate)); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci harmony_wait_for_control(h); 17662306a36Sopenharmony_ci harmony_write(h, HARMONY_CNTL, ctrl); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci spin_unlock_irqrestore(&h->lock, flags); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic irqreturn_t 18262306a36Sopenharmony_cisnd_harmony_interrupt(int irq, void *dev) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci u32 dstatus; 18562306a36Sopenharmony_ci struct snd_harmony *h = dev; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci spin_lock(&h->lock); 18862306a36Sopenharmony_ci harmony_disable_interrupts(h); 18962306a36Sopenharmony_ci harmony_wait_for_control(h); 19062306a36Sopenharmony_ci dstatus = harmony_read(h, HARMONY_DSTATUS); 19162306a36Sopenharmony_ci spin_unlock(&h->lock); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (dstatus & HARMONY_DSTATUS_PN) { 19462306a36Sopenharmony_ci if (h->psubs && h->st.playing) { 19562306a36Sopenharmony_ci spin_lock(&h->lock); 19662306a36Sopenharmony_ci h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */ 19762306a36Sopenharmony_ci h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */ 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci harmony_write(h, HARMONY_PNXTADD, 20062306a36Sopenharmony_ci h->pbuf.addr + h->pbuf.buf); 20162306a36Sopenharmony_ci h->stats.play_intr++; 20262306a36Sopenharmony_ci spin_unlock(&h->lock); 20362306a36Sopenharmony_ci snd_pcm_period_elapsed(h->psubs); 20462306a36Sopenharmony_ci } else { 20562306a36Sopenharmony_ci spin_lock(&h->lock); 20662306a36Sopenharmony_ci harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); 20762306a36Sopenharmony_ci h->stats.silence_intr++; 20862306a36Sopenharmony_ci spin_unlock(&h->lock); 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci if (dstatus & HARMONY_DSTATUS_RN) { 21362306a36Sopenharmony_ci if (h->csubs && h->st.capturing) { 21462306a36Sopenharmony_ci spin_lock(&h->lock); 21562306a36Sopenharmony_ci h->cbuf.buf += h->cbuf.count; 21662306a36Sopenharmony_ci h->cbuf.buf %= h->cbuf.size; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci harmony_write(h, HARMONY_RNXTADD, 21962306a36Sopenharmony_ci h->cbuf.addr + h->cbuf.buf); 22062306a36Sopenharmony_ci h->stats.rec_intr++; 22162306a36Sopenharmony_ci spin_unlock(&h->lock); 22262306a36Sopenharmony_ci snd_pcm_period_elapsed(h->csubs); 22362306a36Sopenharmony_ci } else { 22462306a36Sopenharmony_ci spin_lock(&h->lock); 22562306a36Sopenharmony_ci harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); 22662306a36Sopenharmony_ci h->stats.graveyard_intr++; 22762306a36Sopenharmony_ci spin_unlock(&h->lock); 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci spin_lock(&h->lock); 23262306a36Sopenharmony_ci harmony_enable_interrupts(h); 23362306a36Sopenharmony_ci spin_unlock(&h->lock); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return IRQ_HANDLED; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic unsigned int 23962306a36Sopenharmony_cisnd_harmony_rate_bits(int rate) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci unsigned int i; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++) 24462306a36Sopenharmony_ci if (snd_harmony_rates[i] == rate) 24562306a36Sopenharmony_ci return rate_bits[i]; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return HARMONY_SR_44KHZ; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic const struct snd_pcm_hardware snd_harmony_playback = 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | 25362306a36Sopenharmony_ci SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | 25462306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER), 25562306a36Sopenharmony_ci .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | 25662306a36Sopenharmony_ci SNDRV_PCM_FMTBIT_A_LAW), 25762306a36Sopenharmony_ci .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | 25862306a36Sopenharmony_ci SNDRV_PCM_RATE_KNOT), 25962306a36Sopenharmony_ci .rate_min = 5512, 26062306a36Sopenharmony_ci .rate_max = 48000, 26162306a36Sopenharmony_ci .channels_min = 1, 26262306a36Sopenharmony_ci .channels_max = 2, 26362306a36Sopenharmony_ci .buffer_bytes_max = MAX_BUF_SIZE, 26462306a36Sopenharmony_ci .period_bytes_min = BUF_SIZE, 26562306a36Sopenharmony_ci .period_bytes_max = BUF_SIZE, 26662306a36Sopenharmony_ci .periods_min = 1, 26762306a36Sopenharmony_ci .periods_max = MAX_BUFS, 26862306a36Sopenharmony_ci .fifo_size = 0, 26962306a36Sopenharmony_ci}; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic const struct snd_pcm_hardware snd_harmony_capture = 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | 27462306a36Sopenharmony_ci SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | 27562306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER), 27662306a36Sopenharmony_ci .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | 27762306a36Sopenharmony_ci SNDRV_PCM_FMTBIT_A_LAW), 27862306a36Sopenharmony_ci .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | 27962306a36Sopenharmony_ci SNDRV_PCM_RATE_KNOT), 28062306a36Sopenharmony_ci .rate_min = 5512, 28162306a36Sopenharmony_ci .rate_max = 48000, 28262306a36Sopenharmony_ci .channels_min = 1, 28362306a36Sopenharmony_ci .channels_max = 2, 28462306a36Sopenharmony_ci .buffer_bytes_max = MAX_BUF_SIZE, 28562306a36Sopenharmony_ci .period_bytes_min = BUF_SIZE, 28662306a36Sopenharmony_ci .period_bytes_max = BUF_SIZE, 28762306a36Sopenharmony_ci .periods_min = 1, 28862306a36Sopenharmony_ci .periods_max = MAX_BUFS, 28962306a36Sopenharmony_ci .fifo_size = 0, 29062306a36Sopenharmony_ci}; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic int 29362306a36Sopenharmony_cisnd_harmony_playback_trigger(struct snd_pcm_substream *ss, int cmd) 29462306a36Sopenharmony_ci{ 29562306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (h->st.capturing) 29862306a36Sopenharmony_ci return -EBUSY; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci spin_lock(&h->lock); 30162306a36Sopenharmony_ci switch (cmd) { 30262306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 30362306a36Sopenharmony_ci h->st.playing = 1; 30462306a36Sopenharmony_ci harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr); 30562306a36Sopenharmony_ci harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); 30662306a36Sopenharmony_ci harmony_unmute(h); 30762306a36Sopenharmony_ci harmony_enable_interrupts(h); 30862306a36Sopenharmony_ci break; 30962306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 31062306a36Sopenharmony_ci h->st.playing = 0; 31162306a36Sopenharmony_ci harmony_mute(h); 31262306a36Sopenharmony_ci harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); 31362306a36Sopenharmony_ci harmony_disable_interrupts(h); 31462306a36Sopenharmony_ci break; 31562306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 31662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 31762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 31862306a36Sopenharmony_ci default: 31962306a36Sopenharmony_ci spin_unlock(&h->lock); 32062306a36Sopenharmony_ci snd_BUG(); 32162306a36Sopenharmony_ci return -EINVAL; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci spin_unlock(&h->lock); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci return 0; 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic int 32962306a36Sopenharmony_cisnd_harmony_capture_trigger(struct snd_pcm_substream *ss, int cmd) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (h->st.playing) 33462306a36Sopenharmony_ci return -EBUSY; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci spin_lock(&h->lock); 33762306a36Sopenharmony_ci switch (cmd) { 33862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 33962306a36Sopenharmony_ci h->st.capturing = 1; 34062306a36Sopenharmony_ci harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); 34162306a36Sopenharmony_ci harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr); 34262306a36Sopenharmony_ci harmony_unmute(h); 34362306a36Sopenharmony_ci harmony_enable_interrupts(h); 34462306a36Sopenharmony_ci break; 34562306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 34662306a36Sopenharmony_ci h->st.capturing = 0; 34762306a36Sopenharmony_ci harmony_mute(h); 34862306a36Sopenharmony_ci harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); 34962306a36Sopenharmony_ci harmony_disable_interrupts(h); 35062306a36Sopenharmony_ci break; 35162306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 35262306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 35362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 35462306a36Sopenharmony_ci default: 35562306a36Sopenharmony_ci spin_unlock(&h->lock); 35662306a36Sopenharmony_ci snd_BUG(); 35762306a36Sopenharmony_ci return -EINVAL; 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci spin_unlock(&h->lock); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci return 0; 36262306a36Sopenharmony_ci} 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_cistatic int 36562306a36Sopenharmony_cisnd_harmony_set_data_format(struct snd_harmony *h, int fmt, int force) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci int o = h->st.format; 36862306a36Sopenharmony_ci int n; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci switch(fmt) { 37162306a36Sopenharmony_ci case SNDRV_PCM_FORMAT_S16_BE: 37262306a36Sopenharmony_ci n = HARMONY_DF_16BIT_LINEAR; 37362306a36Sopenharmony_ci break; 37462306a36Sopenharmony_ci case SNDRV_PCM_FORMAT_A_LAW: 37562306a36Sopenharmony_ci n = HARMONY_DF_8BIT_ALAW; 37662306a36Sopenharmony_ci break; 37762306a36Sopenharmony_ci case SNDRV_PCM_FORMAT_MU_LAW: 37862306a36Sopenharmony_ci n = HARMONY_DF_8BIT_ULAW; 37962306a36Sopenharmony_ci break; 38062306a36Sopenharmony_ci default: 38162306a36Sopenharmony_ci n = HARMONY_DF_16BIT_LINEAR; 38262306a36Sopenharmony_ci break; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (force || o != n) { 38662306a36Sopenharmony_ci snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ / 38762306a36Sopenharmony_ci (snd_pcm_format_physical_width(fmt) 38862306a36Sopenharmony_ci / 8)); 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return n; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic int 39562306a36Sopenharmony_cisnd_harmony_playback_prepare(struct snd_pcm_substream *ss) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 39862306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci if (h->st.capturing) 40162306a36Sopenharmony_ci return -EBUSY; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci h->pbuf.size = snd_pcm_lib_buffer_bytes(ss); 40462306a36Sopenharmony_ci h->pbuf.count = snd_pcm_lib_period_bytes(ss); 40562306a36Sopenharmony_ci if (h->pbuf.buf >= h->pbuf.size) 40662306a36Sopenharmony_ci h->pbuf.buf = 0; 40762306a36Sopenharmony_ci h->st.playing = 0; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci h->st.rate = snd_harmony_rate_bits(rt->rate); 41062306a36Sopenharmony_ci h->st.format = snd_harmony_set_data_format(h, rt->format, 0); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci if (rt->channels == 2) 41362306a36Sopenharmony_ci h->st.stereo = HARMONY_SS_STEREO; 41462306a36Sopenharmony_ci else 41562306a36Sopenharmony_ci h->st.stereo = HARMONY_SS_MONO; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci harmony_set_control(h); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci h->pbuf.addr = rt->dma_addr; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci return 0; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_cistatic int 42562306a36Sopenharmony_cisnd_harmony_capture_prepare(struct snd_pcm_substream *ss) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 42862306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci if (h->st.playing) 43162306a36Sopenharmony_ci return -EBUSY; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci h->cbuf.size = snd_pcm_lib_buffer_bytes(ss); 43462306a36Sopenharmony_ci h->cbuf.count = snd_pcm_lib_period_bytes(ss); 43562306a36Sopenharmony_ci if (h->cbuf.buf >= h->cbuf.size) 43662306a36Sopenharmony_ci h->cbuf.buf = 0; 43762306a36Sopenharmony_ci h->st.capturing = 0; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci h->st.rate = snd_harmony_rate_bits(rt->rate); 44062306a36Sopenharmony_ci h->st.format = snd_harmony_set_data_format(h, rt->format, 0); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci if (rt->channels == 2) 44362306a36Sopenharmony_ci h->st.stereo = HARMONY_SS_STEREO; 44462306a36Sopenharmony_ci else 44562306a36Sopenharmony_ci h->st.stereo = HARMONY_SS_MONO; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci harmony_set_control(h); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci h->cbuf.addr = rt->dma_addr; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci return 0; 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic snd_pcm_uframes_t 45562306a36Sopenharmony_cisnd_harmony_playback_pointer(struct snd_pcm_substream *ss) 45662306a36Sopenharmony_ci{ 45762306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 45862306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 45962306a36Sopenharmony_ci unsigned long pcuradd; 46062306a36Sopenharmony_ci unsigned long played; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci if (!(h->st.playing) || (h->psubs == NULL)) 46362306a36Sopenharmony_ci return 0; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if ((h->pbuf.addr == 0) || (h->pbuf.size == 0)) 46662306a36Sopenharmony_ci return 0; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci pcuradd = harmony_read(h, HARMONY_PCURADD); 46962306a36Sopenharmony_ci played = pcuradd - h->pbuf.addr; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci#ifdef HARMONY_DEBUG 47262306a36Sopenharmony_ci printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n", 47362306a36Sopenharmony_ci pcuradd, h->pbuf.addr, played); 47462306a36Sopenharmony_ci#endif 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci if (pcuradd > h->pbuf.addr + h->pbuf.size) { 47762306a36Sopenharmony_ci return 0; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci return bytes_to_frames(rt, played); 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic snd_pcm_uframes_t 48462306a36Sopenharmony_cisnd_harmony_capture_pointer(struct snd_pcm_substream *ss) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 48762306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 48862306a36Sopenharmony_ci unsigned long rcuradd; 48962306a36Sopenharmony_ci unsigned long caught; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci if (!(h->st.capturing) || (h->csubs == NULL)) 49262306a36Sopenharmony_ci return 0; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci if ((h->cbuf.addr == 0) || (h->cbuf.size == 0)) 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci rcuradd = harmony_read(h, HARMONY_RCURADD); 49862306a36Sopenharmony_ci caught = rcuradd - h->cbuf.addr; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci#ifdef HARMONY_DEBUG 50162306a36Sopenharmony_ci printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n", 50262306a36Sopenharmony_ci rcuradd, h->cbuf.addr, caught); 50362306a36Sopenharmony_ci#endif 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci if (rcuradd > h->cbuf.addr + h->cbuf.size) { 50662306a36Sopenharmony_ci return 0; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci return bytes_to_frames(rt, caught); 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cistatic int 51362306a36Sopenharmony_cisnd_harmony_playback_open(struct snd_pcm_substream *ss) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 51662306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 51762306a36Sopenharmony_ci int err; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci h->psubs = ss; 52062306a36Sopenharmony_ci rt->hw = snd_harmony_playback; 52162306a36Sopenharmony_ci snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, 52262306a36Sopenharmony_ci &hw_constraint_rates); 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); 52562306a36Sopenharmony_ci if (err < 0) 52662306a36Sopenharmony_ci return err; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return 0; 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_cistatic int 53262306a36Sopenharmony_cisnd_harmony_capture_open(struct snd_pcm_substream *ss) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 53562306a36Sopenharmony_ci struct snd_pcm_runtime *rt = ss->runtime; 53662306a36Sopenharmony_ci int err; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci h->csubs = ss; 53962306a36Sopenharmony_ci rt->hw = snd_harmony_capture; 54062306a36Sopenharmony_ci snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, 54162306a36Sopenharmony_ci &hw_constraint_rates); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); 54462306a36Sopenharmony_ci if (err < 0) 54562306a36Sopenharmony_ci return err; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci return 0; 54862306a36Sopenharmony_ci} 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic int 55162306a36Sopenharmony_cisnd_harmony_playback_close(struct snd_pcm_substream *ss) 55262306a36Sopenharmony_ci{ 55362306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 55462306a36Sopenharmony_ci h->psubs = NULL; 55562306a36Sopenharmony_ci return 0; 55662306a36Sopenharmony_ci} 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_cistatic int 55962306a36Sopenharmony_cisnd_harmony_capture_close(struct snd_pcm_substream *ss) 56062306a36Sopenharmony_ci{ 56162306a36Sopenharmony_ci struct snd_harmony *h = snd_pcm_substream_chip(ss); 56262306a36Sopenharmony_ci h->csubs = NULL; 56362306a36Sopenharmony_ci return 0; 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_harmony_playback_ops = { 56762306a36Sopenharmony_ci .open = snd_harmony_playback_open, 56862306a36Sopenharmony_ci .close = snd_harmony_playback_close, 56962306a36Sopenharmony_ci .prepare = snd_harmony_playback_prepare, 57062306a36Sopenharmony_ci .trigger = snd_harmony_playback_trigger, 57162306a36Sopenharmony_ci .pointer = snd_harmony_playback_pointer, 57262306a36Sopenharmony_ci}; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_harmony_capture_ops = { 57562306a36Sopenharmony_ci .open = snd_harmony_capture_open, 57662306a36Sopenharmony_ci .close = snd_harmony_capture_close, 57762306a36Sopenharmony_ci .prepare = snd_harmony_capture_prepare, 57862306a36Sopenharmony_ci .trigger = snd_harmony_capture_trigger, 57962306a36Sopenharmony_ci .pointer = snd_harmony_capture_pointer, 58062306a36Sopenharmony_ci}; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_cistatic int 58362306a36Sopenharmony_cisnd_harmony_pcm_init(struct snd_harmony *h) 58462306a36Sopenharmony_ci{ 58562306a36Sopenharmony_ci struct snd_pcm *pcm; 58662306a36Sopenharmony_ci int err; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci if (snd_BUG_ON(!h)) 58962306a36Sopenharmony_ci return -EINVAL; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci harmony_disable_interrupts(h); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci err = snd_pcm_new(h->card, "harmony", 0, 1, 1, &pcm); 59462306a36Sopenharmony_ci if (err < 0) 59562306a36Sopenharmony_ci return err; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 59862306a36Sopenharmony_ci &snd_harmony_playback_ops); 59962306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, 60062306a36Sopenharmony_ci &snd_harmony_capture_ops); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci pcm->private_data = h; 60362306a36Sopenharmony_ci pcm->info_flags = 0; 60462306a36Sopenharmony_ci strcpy(pcm->name, "harmony"); 60562306a36Sopenharmony_ci h->pcm = pcm; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci h->psubs = NULL; 60862306a36Sopenharmony_ci h->csubs = NULL; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci /* initialize graveyard buffer */ 61162306a36Sopenharmony_ci h->dma.type = SNDRV_DMA_TYPE_DEV; 61262306a36Sopenharmony_ci h->dma.dev = &h->dev->dev; 61362306a36Sopenharmony_ci err = snd_dma_alloc_pages(h->dma.type, 61462306a36Sopenharmony_ci h->dma.dev, 61562306a36Sopenharmony_ci BUF_SIZE*GRAVEYARD_BUFS, 61662306a36Sopenharmony_ci &h->gdma); 61762306a36Sopenharmony_ci if (err < 0) { 61862306a36Sopenharmony_ci printk(KERN_ERR PFX "cannot allocate graveyard buffer!\n"); 61962306a36Sopenharmony_ci return err; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci /* initialize silence buffers */ 62362306a36Sopenharmony_ci err = snd_dma_alloc_pages(h->dma.type, 62462306a36Sopenharmony_ci h->dma.dev, 62562306a36Sopenharmony_ci BUF_SIZE*SILENCE_BUFS, 62662306a36Sopenharmony_ci &h->sdma); 62762306a36Sopenharmony_ci if (err < 0) { 62862306a36Sopenharmony_ci printk(KERN_ERR PFX "cannot allocate silence buffer!\n"); 62962306a36Sopenharmony_ci return err; 63062306a36Sopenharmony_ci } 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci /* pre-allocate space for DMA */ 63362306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(pcm, h->dma.type, h->dma.dev, 63462306a36Sopenharmony_ci MAX_BUF_SIZE, MAX_BUF_SIZE); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci h->st.format = snd_harmony_set_data_format(h, 63762306a36Sopenharmony_ci SNDRV_PCM_FORMAT_S16_BE, 1); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci return 0; 64062306a36Sopenharmony_ci} 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_cistatic void 64362306a36Sopenharmony_cisnd_harmony_set_new_gain(struct snd_harmony *h) 64462306a36Sopenharmony_ci{ 64562306a36Sopenharmony_ci harmony_wait_for_control(h); 64662306a36Sopenharmony_ci harmony_write(h, HARMONY_GAINCTL, h->st.gain); 64762306a36Sopenharmony_ci} 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_cistatic int 65062306a36Sopenharmony_cisnd_harmony_mixercontrol_info(struct snd_kcontrol *kc, 65162306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 65262306a36Sopenharmony_ci{ 65362306a36Sopenharmony_ci int mask = (kc->private_value >> 16) & 0xff; 65462306a36Sopenharmony_ci int left_shift = (kc->private_value) & 0xff; 65562306a36Sopenharmony_ci int right_shift = (kc->private_value >> 8) & 0xff; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : 65862306a36Sopenharmony_ci SNDRV_CTL_ELEM_TYPE_INTEGER; 65962306a36Sopenharmony_ci uinfo->count = left_shift == right_shift ? 1 : 2; 66062306a36Sopenharmony_ci uinfo->value.integer.min = 0; 66162306a36Sopenharmony_ci uinfo->value.integer.max = mask; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci return 0; 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic int 66762306a36Sopenharmony_cisnd_harmony_volume_get(struct snd_kcontrol *kc, 66862306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 66962306a36Sopenharmony_ci{ 67062306a36Sopenharmony_ci struct snd_harmony *h = snd_kcontrol_chip(kc); 67162306a36Sopenharmony_ci int shift_left = (kc->private_value) & 0xff; 67262306a36Sopenharmony_ci int shift_right = (kc->private_value >> 8) & 0xff; 67362306a36Sopenharmony_ci int mask = (kc->private_value >> 16) & 0xff; 67462306a36Sopenharmony_ci int invert = (kc->private_value >> 24) & 0xff; 67562306a36Sopenharmony_ci int left, right; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci spin_lock_irq(&h->mixer_lock); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci left = (h->st.gain >> shift_left) & mask; 68062306a36Sopenharmony_ci right = (h->st.gain >> shift_right) & mask; 68162306a36Sopenharmony_ci if (invert) { 68262306a36Sopenharmony_ci left = mask - left; 68362306a36Sopenharmony_ci right = mask - right; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci ucontrol->value.integer.value[0] = left; 68762306a36Sopenharmony_ci if (shift_left != shift_right) 68862306a36Sopenharmony_ci ucontrol->value.integer.value[1] = right; 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci spin_unlock_irq(&h->mixer_lock); 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci return 0; 69362306a36Sopenharmony_ci} 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_cistatic int 69662306a36Sopenharmony_cisnd_harmony_volume_put(struct snd_kcontrol *kc, 69762306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 69862306a36Sopenharmony_ci{ 69962306a36Sopenharmony_ci struct snd_harmony *h = snd_kcontrol_chip(kc); 70062306a36Sopenharmony_ci int shift_left = (kc->private_value) & 0xff; 70162306a36Sopenharmony_ci int shift_right = (kc->private_value >> 8) & 0xff; 70262306a36Sopenharmony_ci int mask = (kc->private_value >> 16) & 0xff; 70362306a36Sopenharmony_ci int invert = (kc->private_value >> 24) & 0xff; 70462306a36Sopenharmony_ci int left, right; 70562306a36Sopenharmony_ci int old_gain = h->st.gain; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci spin_lock_irq(&h->mixer_lock); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci left = ucontrol->value.integer.value[0] & mask; 71062306a36Sopenharmony_ci if (invert) 71162306a36Sopenharmony_ci left = mask - left; 71262306a36Sopenharmony_ci h->st.gain &= ~( (mask << shift_left ) ); 71362306a36Sopenharmony_ci h->st.gain |= (left << shift_left); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci if (shift_left != shift_right) { 71662306a36Sopenharmony_ci right = ucontrol->value.integer.value[1] & mask; 71762306a36Sopenharmony_ci if (invert) 71862306a36Sopenharmony_ci right = mask - right; 71962306a36Sopenharmony_ci h->st.gain &= ~( (mask << shift_right) ); 72062306a36Sopenharmony_ci h->st.gain |= (right << shift_right); 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci snd_harmony_set_new_gain(h); 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci spin_unlock_irq(&h->mixer_lock); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci return h->st.gain != old_gain; 72862306a36Sopenharmony_ci} 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_cistatic int 73162306a36Sopenharmony_cisnd_harmony_captureroute_info(struct snd_kcontrol *kc, 73262306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 73362306a36Sopenharmony_ci{ 73462306a36Sopenharmony_ci static const char * const texts[2] = { "Line", "Mic" }; 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci return snd_ctl_enum_info(uinfo, 1, 2, texts); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_cistatic int 74062306a36Sopenharmony_cisnd_harmony_captureroute_get(struct snd_kcontrol *kc, 74162306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 74262306a36Sopenharmony_ci{ 74362306a36Sopenharmony_ci struct snd_harmony *h = snd_kcontrol_chip(kc); 74462306a36Sopenharmony_ci int value; 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci spin_lock_irq(&h->mixer_lock); 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1; 74962306a36Sopenharmony_ci ucontrol->value.enumerated.item[0] = value; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci spin_unlock_irq(&h->mixer_lock); 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci return 0; 75462306a36Sopenharmony_ci} 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_cistatic int 75762306a36Sopenharmony_cisnd_harmony_captureroute_put(struct snd_kcontrol *kc, 75862306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 75962306a36Sopenharmony_ci{ 76062306a36Sopenharmony_ci struct snd_harmony *h = snd_kcontrol_chip(kc); 76162306a36Sopenharmony_ci int value; 76262306a36Sopenharmony_ci int old_gain = h->st.gain; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci spin_lock_irq(&h->mixer_lock); 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci value = ucontrol->value.enumerated.item[0] & 1; 76762306a36Sopenharmony_ci h->st.gain &= ~HARMONY_GAIN_IS_MASK; 76862306a36Sopenharmony_ci h->st.gain |= value << HARMONY_GAIN_IS_SHIFT; 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci snd_harmony_set_new_gain(h); 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci spin_unlock_irq(&h->mixer_lock); 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci return h->st.gain != old_gain; 77562306a36Sopenharmony_ci} 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci#define HARMONY_CONTROLS ARRAY_SIZE(snd_harmony_controls) 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci#define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \ 78062306a36Sopenharmony_ci{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ 78162306a36Sopenharmony_ci .info = snd_harmony_mixercontrol_info, \ 78262306a36Sopenharmony_ci .get = snd_harmony_volume_get, .put = snd_harmony_volume_put, \ 78362306a36Sopenharmony_ci .private_value = ((left_shift) | ((right_shift) << 8) | \ 78462306a36Sopenharmony_ci ((mask) << 16) | ((invert) << 24)) } 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_cistatic const struct snd_kcontrol_new snd_harmony_controls[] = { 78762306a36Sopenharmony_ci HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT, 78862306a36Sopenharmony_ci HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1), 78962306a36Sopenharmony_ci HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT, 79062306a36Sopenharmony_ci HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0), 79162306a36Sopenharmony_ci HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT, 79262306a36Sopenharmony_ci HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1), 79362306a36Sopenharmony_ci { 79462306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 79562306a36Sopenharmony_ci .name = "Input Route", 79662306a36Sopenharmony_ci .info = snd_harmony_captureroute_info, 79762306a36Sopenharmony_ci .get = snd_harmony_captureroute_get, 79862306a36Sopenharmony_ci .put = snd_harmony_captureroute_put 79962306a36Sopenharmony_ci }, 80062306a36Sopenharmony_ci HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT, 80162306a36Sopenharmony_ci HARMONY_GAIN_SE_SHIFT, 1, 0), 80262306a36Sopenharmony_ci HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT, 80362306a36Sopenharmony_ci HARMONY_GAIN_LE_SHIFT, 1, 0), 80462306a36Sopenharmony_ci HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT, 80562306a36Sopenharmony_ci HARMONY_GAIN_HE_SHIFT, 1, 0), 80662306a36Sopenharmony_ci}; 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_cistatic void 80962306a36Sopenharmony_cisnd_harmony_mixer_reset(struct snd_harmony *h) 81062306a36Sopenharmony_ci{ 81162306a36Sopenharmony_ci harmony_mute(h); 81262306a36Sopenharmony_ci harmony_reset(h); 81362306a36Sopenharmony_ci h->st.gain = HARMONY_GAIN_DEFAULT; 81462306a36Sopenharmony_ci harmony_unmute(h); 81562306a36Sopenharmony_ci} 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_cistatic int 81862306a36Sopenharmony_cisnd_harmony_mixer_init(struct snd_harmony *h) 81962306a36Sopenharmony_ci{ 82062306a36Sopenharmony_ci struct snd_card *card; 82162306a36Sopenharmony_ci int idx, err; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (snd_BUG_ON(!h)) 82462306a36Sopenharmony_ci return -EINVAL; 82562306a36Sopenharmony_ci card = h->card; 82662306a36Sopenharmony_ci strcpy(card->mixername, "Harmony Gain control interface"); 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci for (idx = 0; idx < HARMONY_CONTROLS; idx++) { 82962306a36Sopenharmony_ci err = snd_ctl_add(card, 83062306a36Sopenharmony_ci snd_ctl_new1(&snd_harmony_controls[idx], h)); 83162306a36Sopenharmony_ci if (err < 0) 83262306a36Sopenharmony_ci return err; 83362306a36Sopenharmony_ci } 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci snd_harmony_mixer_reset(h); 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci return 0; 83862306a36Sopenharmony_ci} 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_cistatic int 84162306a36Sopenharmony_cisnd_harmony_free(struct snd_harmony *h) 84262306a36Sopenharmony_ci{ 84362306a36Sopenharmony_ci if (h->gdma.addr) 84462306a36Sopenharmony_ci snd_dma_free_pages(&h->gdma); 84562306a36Sopenharmony_ci if (h->sdma.addr) 84662306a36Sopenharmony_ci snd_dma_free_pages(&h->sdma); 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci if (h->irq >= 0) 84962306a36Sopenharmony_ci free_irq(h->irq, h); 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci iounmap(h->iobase); 85262306a36Sopenharmony_ci kfree(h); 85362306a36Sopenharmony_ci return 0; 85462306a36Sopenharmony_ci} 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_cistatic int 85762306a36Sopenharmony_cisnd_harmony_dev_free(struct snd_device *dev) 85862306a36Sopenharmony_ci{ 85962306a36Sopenharmony_ci struct snd_harmony *h = dev->device_data; 86062306a36Sopenharmony_ci return snd_harmony_free(h); 86162306a36Sopenharmony_ci} 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_cistatic int 86462306a36Sopenharmony_cisnd_harmony_create(struct snd_card *card, 86562306a36Sopenharmony_ci struct parisc_device *padev, 86662306a36Sopenharmony_ci struct snd_harmony **rchip) 86762306a36Sopenharmony_ci{ 86862306a36Sopenharmony_ci int err; 86962306a36Sopenharmony_ci struct snd_harmony *h; 87062306a36Sopenharmony_ci static const struct snd_device_ops ops = { 87162306a36Sopenharmony_ci .dev_free = snd_harmony_dev_free, 87262306a36Sopenharmony_ci }; 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci *rchip = NULL; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci h = kzalloc(sizeof(*h), GFP_KERNEL); 87762306a36Sopenharmony_ci if (h == NULL) 87862306a36Sopenharmony_ci return -ENOMEM; 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci h->hpa = padev->hpa.start; 88162306a36Sopenharmony_ci h->card = card; 88262306a36Sopenharmony_ci h->dev = padev; 88362306a36Sopenharmony_ci h->irq = -1; 88462306a36Sopenharmony_ci h->iobase = ioremap(padev->hpa.start, HARMONY_SIZE); 88562306a36Sopenharmony_ci if (h->iobase == NULL) { 88662306a36Sopenharmony_ci printk(KERN_ERR PFX "unable to remap hpa 0x%lx\n", 88762306a36Sopenharmony_ci (unsigned long)padev->hpa.start); 88862306a36Sopenharmony_ci err = -EBUSY; 88962306a36Sopenharmony_ci goto free_and_ret; 89062306a36Sopenharmony_ci } 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci err = request_irq(padev->irq, snd_harmony_interrupt, 0, 89362306a36Sopenharmony_ci "harmony", h); 89462306a36Sopenharmony_ci if (err) { 89562306a36Sopenharmony_ci printk(KERN_ERR PFX "could not obtain interrupt %d", 89662306a36Sopenharmony_ci padev->irq); 89762306a36Sopenharmony_ci goto free_and_ret; 89862306a36Sopenharmony_ci } 89962306a36Sopenharmony_ci h->irq = padev->irq; 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci spin_lock_init(&h->mixer_lock); 90262306a36Sopenharmony_ci spin_lock_init(&h->lock); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, h, &ops); 90562306a36Sopenharmony_ci if (err < 0) 90662306a36Sopenharmony_ci goto free_and_ret; 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci *rchip = h; 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci return 0; 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_cifree_and_ret: 91362306a36Sopenharmony_ci snd_harmony_free(h); 91462306a36Sopenharmony_ci return err; 91562306a36Sopenharmony_ci} 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_cistatic int __init 91862306a36Sopenharmony_cisnd_harmony_probe(struct parisc_device *padev) 91962306a36Sopenharmony_ci{ 92062306a36Sopenharmony_ci int err; 92162306a36Sopenharmony_ci struct snd_card *card; 92262306a36Sopenharmony_ci struct snd_harmony *h; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); 92562306a36Sopenharmony_ci if (err < 0) 92662306a36Sopenharmony_ci return err; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci err = snd_harmony_create(card, padev, &h); 92962306a36Sopenharmony_ci if (err < 0) 93062306a36Sopenharmony_ci goto free_and_ret; 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci err = snd_harmony_pcm_init(h); 93362306a36Sopenharmony_ci if (err < 0) 93462306a36Sopenharmony_ci goto free_and_ret; 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_ci err = snd_harmony_mixer_init(h); 93762306a36Sopenharmony_ci if (err < 0) 93862306a36Sopenharmony_ci goto free_and_ret; 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci strcpy(card->driver, "harmony"); 94162306a36Sopenharmony_ci strcpy(card->shortname, "Harmony"); 94262306a36Sopenharmony_ci sprintf(card->longname, "%s at 0x%lx, irq %i", 94362306a36Sopenharmony_ci card->shortname, h->hpa, h->irq); 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci err = snd_card_register(card); 94662306a36Sopenharmony_ci if (err < 0) 94762306a36Sopenharmony_ci goto free_and_ret; 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_ci parisc_set_drvdata(padev, card); 95062306a36Sopenharmony_ci return 0; 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_cifree_and_ret: 95362306a36Sopenharmony_ci snd_card_free(card); 95462306a36Sopenharmony_ci return err; 95562306a36Sopenharmony_ci} 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_cistatic void __exit 95862306a36Sopenharmony_cisnd_harmony_remove(struct parisc_device *padev) 95962306a36Sopenharmony_ci{ 96062306a36Sopenharmony_ci snd_card_free(parisc_get_drvdata(padev)); 96162306a36Sopenharmony_ci} 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_cistatic struct parisc_driver snd_harmony_driver __refdata = { 96462306a36Sopenharmony_ci .name = "harmony", 96562306a36Sopenharmony_ci .id_table = snd_harmony_devtable, 96662306a36Sopenharmony_ci .probe = snd_harmony_probe, 96762306a36Sopenharmony_ci .remove = __exit_p(snd_harmony_remove), 96862306a36Sopenharmony_ci}; 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_cistatic int __init 97162306a36Sopenharmony_cialsa_harmony_init(void) 97262306a36Sopenharmony_ci{ 97362306a36Sopenharmony_ci return register_parisc_driver(&snd_harmony_driver); 97462306a36Sopenharmony_ci} 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_cistatic void __exit 97762306a36Sopenharmony_cialsa_harmony_fini(void) 97862306a36Sopenharmony_ci{ 97962306a36Sopenharmony_ci unregister_parisc_driver(&snd_harmony_driver); 98062306a36Sopenharmony_ci} 98162306a36Sopenharmony_ci 98262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 98362306a36Sopenharmony_ciMODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>"); 98462306a36Sopenharmony_ciMODULE_DESCRIPTION("Harmony sound driver"); 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_cimodule_init(alsa_harmony_init); 98762306a36Sopenharmony_cimodule_exit(alsa_harmony_fini); 988