diff options
Diffstat (limited to 'sound')
182 files changed, 20772 insertions, 4033 deletions
diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c index fbf5c933baa..586965f9605 100644 --- a/sound/aoa/fabrics/layout.c +++ b/sound/aoa/fabrics/layout.c @@ -1037,7 +1037,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev) } ldev->selfptr_headphone.ptr = ldev; ldev->selfptr_lineout.ptr = ldev; - sdev->ofdev.dev.driver_data = ldev; + dev_set_drvdata(&sdev->ofdev.dev, ldev); list_add(&ldev->list, &layouts_list); layouts_list_items++; @@ -1081,7 +1081,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev) static int aoa_fabric_layout_remove(struct soundbus_dev *sdev) { - struct layout_dev *ldev = sdev->ofdev.dev.driver_data; + struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); int i; for (i=0; i<MAX_CODECS_PER_BUS; i++) { @@ -1114,7 +1114,7 @@ static int aoa_fabric_layout_remove(struct soundbus_dev *sdev) #ifdef CONFIG_PM static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state) { - struct layout_dev *ldev = sdev->ofdev.dev.driver_data; + struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) ldev->gpio.methods->all_amps_off(&ldev->gpio); @@ -1124,7 +1124,7 @@ static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t sta static int aoa_fabric_layout_resume(struct soundbus_dev *sdev) { - struct layout_dev *ldev = sdev->ofdev.dev.driver_data; + struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) ldev->gpio.methods->all_amps_restore(&ldev->gpio); diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index 418c84c99d6..4e3b819d499 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -358,14 +358,14 @@ static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match) return -ENODEV; } - dev->ofdev.dev.driver_data = control; + dev_set_drvdata(&dev->ofdev.dev, control); return 0; } static int i2sbus_remove(struct macio_dev* dev) { - struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); struct i2sbus_dev *i2sdev, *tmp; list_for_each_entry_safe(i2sdev, tmp, &control->list, item) @@ -377,7 +377,7 @@ static int i2sbus_remove(struct macio_dev* dev) #ifdef CONFIG_PM static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) { - struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); struct codec_info_item *cii; struct i2sbus_dev* i2sdev; int err, ret = 0; @@ -407,7 +407,7 @@ static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) static int i2sbus_resume(struct macio_dev* dev) { - struct i2sbus_control *control = dev->ofdev.dev.driver_data; + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); struct codec_info_item *cii; struct i2sbus_dev* i2sdev; int err, ret = 0; diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index 7fbd68fab94..5c48e36038f 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -1074,7 +1074,7 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) return i; } -static int __devinit aaci_probe(struct amba_device *dev, void *id) +static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id) { struct aaci *aaci; int ret, i; diff --git a/sound/arm/pxa2xx-ac97-lib.c b/sound/arm/pxa2xx-ac97-lib.c index a2c12d105c9..6fdca97186e 100644 --- a/sound/arm/pxa2xx-ac97-lib.c +++ b/sound/arm/pxa2xx-ac97-lib.c @@ -65,7 +65,7 @@ static void set_resetgpio_mode(int resetgpio_action) switch (resetgpio_action) { case RESETGPIO_NORMAL_ALTFUNC: if (reset_gpio == 113) - mode = 113 | GPIO_OUT | GPIO_DFLT_LOW; + mode = 113 | GPIO_ALT_FN_2_OUT; if (reset_gpio == 95) mode = 95 | GPIO_ALT_FN_1_OUT; break; diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 7bbdda041a9..6061fb5f4e1 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -205,3 +205,5 @@ config SND_PCM_XRUN_DEBUG config SND_VMASTER bool + +source "sound/core/seq/Kconfig" diff --git a/sound/core/init.c b/sound/core/init.c index fd56afe846e..d5d40d78c40 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -152,15 +152,8 @@ int snd_card_create(int idx, const char *xid, card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); if (!card) return -ENOMEM; - if (xid) { - if (!snd_info_check_reserved_words(xid)) { - snd_printk(KERN_ERR - "given id string '%s' is reserved.\n", xid); - err = -EBUSY; - goto __error; - } + if (xid) strlcpy(card->id, xid, sizeof(card->id)); - } err = 0; mutex_lock(&snd_card_mutex); if (idx < 0) { @@ -483,22 +476,28 @@ int snd_card_free(struct snd_card *card) EXPORT_SYMBOL(snd_card_free); -static void choose_default_id(struct snd_card *card) +static void snd_card_set_id_no_lock(struct snd_card *card, const char *nid) { int i, len, idx_flag = 0, loops = SNDRV_CARDS; - char *id, *spos; + const char *spos, *src; + char *id; - id = spos = card->shortname; - while (*id != '\0') { - if (*id == ' ') - spos = id + 1; - id++; + if (nid == NULL) { + id = card->shortname; + spos = src = id; + while (*id != '\0') { + if (*id == ' ') + spos = id + 1; + id++; + } + } else { + spos = src = nid; } id = card->id; while (*spos != '\0' && !isalnum(*spos)) spos++; if (isdigit(*spos)) - *id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D'; + *id++ = isalpha(src[0]) ? src[0] : 'D'; while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) { if (isalnum(*spos)) *id++ = *spos; @@ -513,7 +512,7 @@ static void choose_default_id(struct snd_card *card) while (1) { if (loops-- == 0) { - snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id); + snd_printk(KERN_ERR "unable to set card id (%s)\n", id); strcpy(card->id, card->proc_root->name); return; } @@ -539,14 +538,33 @@ static void choose_default_id(struct snd_card *card) spos = id + len - 2; if ((size_t)len <= sizeof(card->id) - 2) spos++; - *spos++ = '_'; - *spos++ = '1'; - *spos++ = '\0'; + *(char *)spos++ = '_'; + *(char *)spos++ = '1'; + *(char *)spos++ = '\0'; idx_flag++; } } } +/** + * snd_card_set_id - set card identification name + * @card: soundcard structure + * @nid: new identification string + * + * This function sets the card identification and checks for name + * collisions. + */ +void snd_card_set_id(struct snd_card *card, const char *nid) +{ + /* check if user specified own card->id */ + if (card->id[0] != '\0') + return; + mutex_lock(&snd_card_mutex); + snd_card_set_id_no_lock(card, nid); + mutex_unlock(&snd_card_mutex); +} +EXPORT_SYMBOL(snd_card_set_id); + #ifndef CONFIG_SYSFS_DEPRECATED static ssize_t card_id_show_attr(struct device *dev, @@ -640,8 +658,7 @@ int snd_card_register(struct snd_card *card) mutex_unlock(&snd_card_mutex); return 0; } - if (card->id[0] == '\0') - choose_default_id(card); + snd_card_set_id_no_lock(card, card->id[0] == '\0' ? NULL : card->id); snd_cards[card->number] = card; mutex_unlock(&snd_card_mutex); init_info_for_card(card); diff --git a/sound/core/jack.c b/sound/core/jack.c index d54d1a05fe6..f705eec7372 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -63,7 +63,7 @@ static int snd_jack_dev_register(struct snd_device *device) /* Default to the sound card device. */ if (!jack->input_dev->dev.parent) - jack->input_dev->dev.parent = card->dev; + jack->input_dev->dev.parent = snd_card_get_device_link(card); err = input_register_device(jack->input_dev); if (err == 0) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index dda000b9684..dbe406b8259 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -31,6 +31,7 @@ #include <linux/time.h> #include <linux/vmalloc.h> #include <linux/moduleparam.h> +#include <linux/math64.h> #include <linux/string.h> #include <sound/core.h> #include <sound/minors.h> @@ -617,9 +618,7 @@ static long snd_pcm_oss_bytes(struct snd_pcm_substream *substream, long frames) #else { u64 bsize = (u64)runtime->oss.buffer_bytes * (u64)bytes; - u32 rem; - div64_32(&bsize, buffer_size, &rem); - return (long)bsize; + return div_u64(bsize, buffer_size); } #endif } diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index a2a792c18c4..333e4dd2945 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -22,6 +22,7 @@ #include <linux/slab.h> #include <linux/time.h> +#include <linux/math64.h> #include <sound/core.h> #include <sound/control.h> #include <sound/info.h> @@ -126,24 +127,37 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram } #ifdef CONFIG_SND_PCM_XRUN_DEBUG -#define xrun_debug(substream) ((substream)->pstr->xrun_debug) +#define xrun_debug(substream, mask) ((substream)->pstr->xrun_debug & (mask)) #else -#define xrun_debug(substream) 0 +#define xrun_debug(substream, mask) 0 #endif -#define dump_stack_on_xrun(substream) do { \ - if (xrun_debug(substream) > 1) \ - dump_stack(); \ +#define dump_stack_on_xrun(substream) do { \ + if (xrun_debug(substream, 2)) \ + dump_stack(); \ } while (0) +static void pcm_debug_name(struct snd_pcm_substream *substream, + char *name, size_t len) +{ + snprintf(name, len, "pcmC%dD%d%c:%d", + substream->pcm->card->number, + substream->pcm->device, + substream->stream ? 'c' : 'p', + substream->number); +} + static void xrun(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) + snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - if (xrun_debug(substream)) { - snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n", - substream->pcm->card->number, - substream->pcm->device, - substream->stream ? 'c' : 'p'); + if (xrun_debug(substream, 1)) { + char name[16]; + pcm_debug_name(substream, name, sizeof(name)); + snd_printd(KERN_DEBUG "XRUN: %s\n", name); dump_stack_on_xrun(substream); } } @@ -154,16 +168,16 @@ snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream, { snd_pcm_uframes_t pos; - if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) - snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); pos = substream->ops->pointer(substream); if (pos == SNDRV_PCM_POS_XRUN) return pos; /* XRUN */ if (pos >= runtime->buffer_size) { if (printk_ratelimit()) { - snd_printd(KERN_ERR "BUG: stream = %i, pos = 0x%lx, " + char name[16]; + pcm_debug_name(substream, name, sizeof(name)); + snd_printd(KERN_ERR "BUG: %s, pos = 0x%lx, " "buffer size = 0x%lx, period size = 0x%lx\n", - substream->stream, pos, runtime->buffer_size, + name, pos, runtime->buffer_size, runtime->period_size); } pos = 0; @@ -197,7 +211,7 @@ static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream, #define hw_ptr_error(substream, fmt, args...) \ do { \ - if (xrun_debug(substream)) { \ + if (xrun_debug(substream, 1)) { \ if (printk_ratelimit()) { \ snd_printd("PCM: " fmt, ##args); \ } \ @@ -249,6 +263,11 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) new_hw_ptr = hw_base + pos; } } + + /* Do jiffies check only in xrun_debug mode */ + if (!xrun_debug(substream, 4)) + goto no_jiffies_check; + /* Skip the jiffies check for hardwares with BATCH flag. * Such hardware usually just increases the position at each IRQ, * thus it can't give any strange position. @@ -256,6 +275,9 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) if (runtime->hw.info & SNDRV_PCM_INFO_BATCH) goto no_jiffies_check; hdelta = new_hw_ptr - old_hw_ptr; + if (hdelta < runtime->delay) + goto no_jiffies_check; + hdelta -= runtime->delay; jdelta = jiffies - runtime->hw_ptr_jiffies; if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) { delta = jdelta / @@ -289,14 +311,20 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream) hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size; } + runtime->hw_ptr_interrupt = hw_ptr_interrupt; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0) snd_pcm_playback_silence(substream, new_hw_ptr); + if (runtime->status->hw_ptr == new_hw_ptr) + return 0; + runtime->hw_ptr_base = hw_base; runtime->status->hw_ptr = new_hw_ptr; runtime->hw_ptr_jiffies = jiffies; - runtime->hw_ptr_interrupt = hw_ptr_interrupt; + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) + snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); return snd_pcm_update_hw_ptr_post(substream, runtime); } @@ -336,6 +364,12 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) hw_base = 0; new_hw_ptr = hw_base + pos; } + /* Do jiffies check only in xrun_debug mode */ + if (!xrun_debug(substream, 4)) + goto no_jiffies_check; + if (delta < runtime->delay) + goto no_jiffies_check; + delta -= runtime->delay; if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) { hw_ptr_error(substream, "hw_ptr skipping! " @@ -345,13 +379,19 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) ((delta * HZ) / runtime->rate)); return 0; } + no_jiffies_check: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0) snd_pcm_playback_silence(substream, new_hw_ptr); + if (runtime->status->hw_ptr == new_hw_ptr) + return 0; + runtime->hw_ptr_base = hw_base; runtime->status->hw_ptr = new_hw_ptr; runtime->hw_ptr_jiffies = jiffies; + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) + snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); return snd_pcm_update_hw_ptr_post(substream, runtime); } @@ -445,7 +485,7 @@ static inline unsigned int muldiv32(unsigned int a, unsigned int b, *r = 0; return UINT_MAX; } - div64_32(&n, c, r); + n = div_u64_rem(n, c, r); if (n >= UINT_MAX) { *r = 0; return UINT_MAX; @@ -1478,7 +1518,6 @@ static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, runtime->status->hw_ptr %= runtime->buffer_size; else runtime->status->hw_ptr = 0; - runtime->hw_ptr_jiffies = jiffies; snd_pcm_stream_unlock_irqrestore(substream, flags); return 0; } @@ -1518,6 +1557,23 @@ static int snd_pcm_lib_ioctl_channel_info(struct snd_pcm_substream *substream, return 0; } +static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_hw_params *params = arg; + snd_pcm_format_t format; + int channels, width; + + params->fifo_size = substream->runtime->hw.fifo_size; + if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_FIFO_IN_FRAMES)) { + format = params_format(params); + channels = params_channels(params); + width = snd_pcm_format_physical_width(format); + params->fifo_size /= width * channels; + } + return 0; +} + /** * snd_pcm_lib_ioctl - a generic PCM ioctl callback * @substream: the pcm substream instance @@ -1539,6 +1595,8 @@ int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, return snd_pcm_lib_ioctl_reset(substream, arg); case SNDRV_PCM_IOCTL1_CHANNEL_INFO: return snd_pcm_lib_ioctl_channel_info(substream, arg); + case SNDRV_PCM_IOCTL1_FIFO_SIZE: + return snd_pcm_lib_ioctl_fifo_size(substream, arg); } return -ENXIO; } diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index fc6f98e257d..84da3ba17c8 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -312,9 +312,18 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream, hw = &substream->runtime->hw; if (!params->info) - params->info = hw->info; - if (!params->fifo_size) - params->fifo_size = hw->fifo_size; + params->info = hw->info & ~SNDRV_PCM_INFO_FIFO_IN_FRAMES; + if (!params->fifo_size) { + if (snd_mask_min(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT]) == + snd_mask_max(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT]) && + snd_mask_min(¶ms->masks[SNDRV_PCM_HW_PARAM_CHANNELS]) == + snd_mask_max(¶ms->masks[SNDRV_PCM_HW_PARAM_CHANNELS])) { + changed = substream->ops->ioctl(substream, + SNDRV_PCM_IOCTL1_FIFO_SIZE, params); + if (params < 0) + return changed; + } + } params->rmask = 0; return 0; } @@ -587,14 +596,15 @@ int snd_pcm_status(struct snd_pcm_substream *substream, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { status->avail = snd_pcm_playback_avail(runtime); if (runtime->status->state == SNDRV_PCM_STATE_RUNNING || - runtime->status->state == SNDRV_PCM_STATE_DRAINING) + runtime->status->state == SNDRV_PCM_STATE_DRAINING) { status->delay = runtime->buffer_size - status->avail; - else + status->delay += runtime->delay; + } else status->delay = 0; } else { status->avail = snd_pcm_capture_avail(runtime); if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) - status->delay = status->avail; + status->delay = status->avail + runtime->delay; else status->delay = 0; } @@ -848,6 +858,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_trigger_tstamp(substream); + runtime->hw_ptr_jiffies = jiffies; runtime->status->state = state; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0) @@ -961,6 +972,11 @@ static int snd_pcm_do_pause(struct snd_pcm_substream *substream, int push) { if (substream->runtime->trigger_master != substream) return 0; + /* The jiffies check in snd_pcm_update_hw_ptr*() is done by + * a delta betwen the current jiffies, this gives a large enough + * delta, effectively to skip the check once. + */ + substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000; return substream->ops->trigger(substream, push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH : SNDRV_PCM_TRIGGER_PAUSE_RELEASE); @@ -2404,6 +2420,7 @@ static int snd_pcm_delay(struct snd_pcm_substream *substream, n = snd_pcm_playback_hw_avail(runtime); else n = snd_pcm_capture_avail(runtime); + n += runtime->delay; break; case SNDRV_PCM_STATE_XRUN: err = -EPIPE; diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig new file mode 100644 index 00000000000..b851fd890a8 --- /dev/null +++ b/sound/core/seq/Kconfig @@ -0,0 +1,16 @@ +# define SND_XXX_SEQ to min(SND_SEQUENCER,SND_XXX) + +config SND_RAWMIDI_SEQ + def_tristate SND_SEQUENCER && SND_RAWMIDI + +config SND_OPL3_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL3_LIB + +config SND_OPL4_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL4_LIB + +config SND_SBAWE_SEQ + def_tristate SND_SEQUENCER && SND_SBAWE + +config SND_EMU10K1_SEQ + def_tristate SND_SEQUENCER && SND_EMU10K1 diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile index 069593717fb..1bcb360330e 100644 --- a/sound/core/seq/Makefile +++ b/sound/core/seq/Makefile @@ -17,14 +17,6 @@ snd-seq-midi-event-objs := seq_midi_event.o snd-seq-dummy-objs := seq_dummy.o snd-seq-virmidi-objs := seq_virmidi.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o snd-seq-device.o ifeq ($(CONFIG_SND_SEQUENCER_OSS),y) obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o @@ -33,8 +25,8 @@ obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o # Toplevel Module Dependency obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o -obj-$(call sequencer,$(CONFIG_SND_RAWMIDI)) += snd-seq-midi.o snd-seq-midi-event.o -obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o -obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o -obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-seq-midi-emul.o snd-seq-virmidi.o -obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-seq-midi-emul.o snd-seq-virmidi.o +obj-$(CONFIG_SND_RAWMIDI_SEQ) += snd-seq-midi.o snd-seq-midi-event.o +obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o +obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o +obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o diff --git a/sound/drivers/opl3/Makefile b/sound/drivers/opl3/Makefile index 19767a6a5c5..7f2c2a10c4e 100644 --- a/sound/drivers/opl3/Makefile +++ b/sound/drivers/opl3/Makefile @@ -7,14 +7,6 @@ snd-opl3-lib-objs := opl3_lib.o opl3_synth.o snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o snd-opl3-synth-$(CONFIG_SND_SEQUENCER_OSS) += opl3_oss.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o -obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-opl3-synth.o +obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-opl3-synth.o diff --git a/sound/drivers/opl4/Makefile b/sound/drivers/opl4/Makefile index d178b39ffa6..b94009b0b19 100644 --- a/sound/drivers/opl4/Makefile +++ b/sound/drivers/opl4/Makefile @@ -6,13 +6,5 @@ snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o opl4_proc.o snd-opl4-synth-objs := opl4_seq.o opl4_synth.o yrw801.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o -obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-opl4-synth.o +obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-opl4-synth.o diff --git a/sound/drivers/pcsp/pcsp_mixer.c b/sound/drivers/pcsp/pcsp_mixer.c index caeb0f57fcc..199b0337714 100644 --- a/sound/drivers/pcsp/pcsp_mixer.c +++ b/sound/drivers/pcsp/pcsp_mixer.c @@ -50,8 +50,8 @@ static int pcsp_treble_info(struct snd_kcontrol *kcontrol, uinfo->value.enumerated.items = chip->max_treble + 1; if (uinfo->value.enumerated.item > chip->max_treble) uinfo->value.enumerated.item = chip->max_treble; - sprintf(uinfo->value.enumerated.name, "%d", - PCSP_CALC_RATE(uinfo->value.enumerated.item)); + sprintf(uinfo->value.enumerated.name, "%lu", + (unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item)); return 0; } diff --git a/sound/drivers/serial-u16550.c b/sound/drivers/serial-u16550.c index b2b6d50c942..a25fb7b1f44 100644 --- a/sound/drivers/serial-u16550.c +++ b/sound/drivers/serial-u16550.c @@ -963,16 +963,11 @@ static int __devinit snd_serial_probe(struct platform_device *devptr) if (err < 0) goto _err; - sprintf(card->longname, "%s at 0x%lx, irq %d speed %d div %d outs %d ins %d adaptor %s droponfull %d", + sprintf(card->longname, "%s [%s] at %#lx, irq %d", card->shortname, - uart->base, - uart->irq, - uart->speed, - (int)uart->divisor, - outs[dev], - ins[dev], adaptor_names[uart->adaptor], - uart->drop_on_full); + uart->base, + uart->irq); snd_card_set_dev(card, &devptr->dev); diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index c6942a4de99..51a7e3777e1 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -177,15 +177,18 @@ config SND_ES18XX will be called snd-es18xx. config SND_SC6000 - tristate "Gallant SC-6000, Audio Excel DSP 16" + tristate "Gallant SC-6000/6600/7000 and Audio Excel DSP 16" depends on HAS_IOPORT select SND_WSS_LIB select SND_OPL3_LIB select SND_MPU401_UART help - Say Y here to include support for Gallant SC-6000 card and clones: + Say Y here to include support for Gallant SC-6000, SC-6600, SC-7000 + cards and clones: Audio Excel DSP 16 and Zoltrix AV302. + These cards are based on CompuMedia ASC-9308 or ASC-9408 chips. + To compile this driver as a module, choose M here: the module will be called snd-sc6000. diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index 442b081cafb..07df201ed8f 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -193,7 +193,7 @@ static int __devexit snd_es1688_remove(struct device *dev, unsigned int n) static struct isa_driver snd_es1688_driver = { .match = snd_es1688_match, .probe = snd_es1688_probe, - .remove = snd_es1688_remove, + .remove = __devexit_p(snd_es1688_remove), #if 0 /* FIXME */ .suspend = snd_es1688_suspend, .resume = snd_es1688_resume, diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c index 180a8dea6bd..65e4b18581a 100644 --- a/sound/isa/gus/gusextreme.c +++ b/sound/isa/gus/gusextreme.c @@ -348,7 +348,7 @@ static int __devexit snd_gusextreme_remove(struct device *dev, unsigned int n) static struct isa_driver snd_gusextreme_driver = { .match = snd_gusextreme_match, .probe = snd_gusextreme_probe, - .remove = snd_gusextreme_remove, + .remove = __devexit_p(snd_gusextreme_remove), #if 0 /* FIXME */ .suspend = snd_gusextreme_suspend, .resume = snd_gusextreme_resume, diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile index 1098a56b2f4..faeffceb01b 100644 --- a/sound/isa/sb/Makefile +++ b/sound/isa/sb/Makefile @@ -13,14 +13,6 @@ snd-sbawe-objs := sbawe.o emu8000.o snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o snd-es968-objs := es968.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - # Toplevel Module Dependency obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o @@ -33,4 +25,4 @@ ifeq ($(CONFIG_SND_SB16_CSP),y) obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o endif -obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-emu8000-synth.o +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 782010608ef..9a8bbf6dd62 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -2,6 +2,8 @@ * Driver for Gallant SC-6000 soundcard. This card is also known as * Audio Excel DSP 16 or Zoltrix AV302. * These cards use CompuMedia ASC-9308 chip + AD1848 codec. + * SC-6600 and SC-7000 cards are also supported. They are based on + * CompuMedia ASC-9408 chip and CS4231 codec. * * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl> * @@ -54,6 +56,7 @@ static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x300, 0x310, 0x320, 0x330 */ static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ +static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false }; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); @@ -73,6 +76,8 @@ module_param_array(mpu_irq, int, NULL, 0444); MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); module_param_array(dma, int, NULL, 0444); MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); +module_param_array(joystick, bool, NULL, 0444); +MODULE_PARM_DESC(joystick, "Enable gameport."); /* * Commands of SC6000's DSP (SBPRO+special). @@ -191,7 +196,7 @@ static __devinit unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) return val; } -static __devinit int sc6000_wait_data(char __iomem *vport) +static int sc6000_wait_data(char __iomem *vport) { int loop = 1000; unsigned char val = 0; @@ -206,7 +211,7 @@ static __devinit int sc6000_wait_data(char __iomem *vport) return -EAGAIN; } -static __devinit int sc6000_read(char __iomem *vport) +static int sc6000_read(char __iomem *vport) { if (sc6000_wait_data(vport)) return -EBUSY; @@ -215,7 +220,7 @@ static __devinit int sc6000_read(char __iomem *vport) } -static __devinit int sc6000_write(char __iomem *vport, int cmd) +static int sc6000_write(char __iomem *vport, int cmd) { unsigned char val; int loop = 500000; @@ -276,8 +281,33 @@ static int __devinit sc6000_dsp_reset(char __iomem *vport) } /* detection and initialization */ -static int __devinit sc6000_cfg_write(char __iomem *vport, - unsigned char softcfg) +static int __devinit sc6000_hw_cfg_write(char __iomem *vport, const int *cfg) +{ + if (sc6000_write(vport, COMMAND_6C) < 0) { + snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C); + return -EIO; + } + if (sc6000_write(vport, COMMAND_5C) < 0) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C); + return -EIO; + } + if (sc6000_write(vport, cfg[0]) < 0) { + snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]); + return -EIO; + } + if (sc6000_write(vport, cfg[1]) < 0) { + snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]); + return -EIO; + } + if (sc6000_write(vport, COMMAND_C5) < 0) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5); + return -EIO; + } + + return 0; +} + +static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg) { if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { @@ -291,7 +321,7 @@ static int __devinit sc6000_cfg_write(char __iomem *vport, return 0; } -static int __devinit sc6000_setup_board(char __iomem *vport, int config) +static int sc6000_setup_board(char __iomem *vport, int config) { int loop = 10; @@ -334,16 +364,39 @@ static int __devinit sc6000_init_mss(char __iomem *vport, int config, return 0; } -static int __devinit sc6000_init_board(char __iomem *vport, int irq, int dma, - char __iomem *vmss_port, int mpu_irq) +static void __devinit sc6000_hw_cfg_encode(char __iomem *vport, int *cfg, + long xport, long xmpu, + long xmss_port, int joystick) +{ + cfg[0] = 0; + cfg[1] = 0; + if (xport == 0x240) + cfg[0] |= 1; + if (xmpu != SNDRV_AUTO_PORT) { + cfg[0] |= (xmpu & 0x30) >> 2; + cfg[1] |= 0x20; + } + if (xmss_port == 0xe80) + cfg[0] |= 0x10; + cfg[0] |= 0x40; /* always set */ + if (!joystick) + cfg[0] |= 0x02; + cfg[1] |= 0x80; /* enable WSS system */ + cfg[1] &= ~0x40; /* disable IDE */ + snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]); +} + +static int __devinit sc6000_init_board(char __iomem *vport, + char __iomem *vmss_port, int dev) { char answer[15]; char version[2]; - int mss_config = sc6000_irq_to_softcfg(irq) | - sc6000_dma_to_softcfg(dma); + int mss_config = sc6000_irq_to_softcfg(irq[dev]) | + sc6000_dma_to_softcfg(dma[dev]); int config = mss_config | - sc6000_mpu_irq_to_softcfg(mpu_irq); + sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); int err; + int old = 0; err = sc6000_dsp_reset(vport); if (err < 0) { @@ -360,7 +413,6 @@ static int __devinit sc6000_init_board(char __iomem *vport, int irq, int dma, /* * My SC-6000 card return "SC-6000" in DSPCopyright, so * if we have something different, we have to be warned. - * Mine returns "SC-6000A " - KH */ if (strncmp("SC-6000", answer, 7)) snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); @@ -372,13 +424,32 @@ static int __devinit sc6000_init_board(char __iomem *vport, int irq, int dma, printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", answer, version[0], version[1]); - /* - * 0x0A == (IRQ 7, DMA 1, MIRQ 0) - */ - err = sc6000_cfg_write(vport, 0x0a); + /* set configuration */ + sc6000_write(vport, COMMAND_5C); + if (sc6000_read(vport) < 0) + old = 1; + + if (!old) { + int cfg[2]; + sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev], + mss_port[dev], joystick[dev]); + if (sc6000_hw_cfg_write(vport, cfg) < 0) { + snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n"); + return -EIO; + } + } + err = sc6000_setup_board(vport, config); if (err < 0) { - snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); - return -EFAULT; + snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + sc6000_dsp_reset(vport); + + if (!old) { + sc6000_write(vport, COMMAND_60); + sc6000_write(vport, 0x02); + sc6000_dsp_reset(vport); } err = sc6000_setup_board(vport, config); @@ -386,10 +457,9 @@ static int __devinit sc6000_init_board(char __iomem *vport, int irq, int dma, snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); return -ENODEV; } - err = sc6000_init_mss(vport, config, vmss_port, mss_config); if (err < 0) { - snd_printk(KERN_ERR "Can not initialize " + snd_printk(KERN_ERR "Cannot initialize " "Microsoft Sound System mode.\n"); return -ENODEV; } @@ -485,14 +555,16 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) struct snd_card *card; struct snd_wss *chip; struct snd_opl3 *opl3; - char __iomem *vport; + char __iomem **vport; char __iomem *vmss_port; - err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); + err = snd_card_create(index[dev], id[dev], THIS_MODULE, sizeof(vport), + &card); if (err < 0) return err; + vport = card->private_data; if (xirq == SNDRV_AUTO_IRQ) { xirq = snd_legacy_find_free_irq(possible_irqs); if (xirq < 0) { @@ -517,8 +589,8 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) err = -EBUSY; goto err_exit; } - vport = devm_ioport_map(devptr, port[dev], 0x10); - if (!vport) { + *vport = devm_ioport_map(devptr, port[dev], 0x10); + if (*vport == NULL) { snd_printk(KERN_ERR PFX "I/O port cannot be iomaped.\n"); err = -EBUSY; @@ -533,7 +605,7 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) goto err_unmap1; } vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); - if (!vport) { + if (!vmss_port) { snd_printk(KERN_ERR PFX "MSS port I/O cannot be iomaped.\n"); err = -EBUSY; @@ -544,7 +616,7 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) port[dev], xirq, xdma, mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); - err = sc6000_init_board(vport, xirq, xdma, vmss_port, mpu_irq[dev]); + err = sc6000_init_board(*vport, vmss_port, dev); if (err < 0) goto err_unmap2; @@ -552,7 +624,6 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) WSS_HW_DETECT, 0, &chip); if (err < 0) goto err_unmap2; - card->private_data = chip; err = snd_wss_pcm(chip, 0, NULL); if (err < 0) { @@ -608,6 +679,7 @@ static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev) return 0; err_unmap2: + sc6000_setup_board(*vport, 0); release_region(mss_port[dev], 4); err_unmap1: release_region(port[dev], 0x10); @@ -618,11 +690,17 @@ err_exit: static int __devexit snd_sc6000_remove(struct device *devptr, unsigned int dev) { + struct snd_card *card = dev_get_drvdata(devptr); + char __iomem **vport = card->private_data; + + if (sc6000_setup_board(*vport, 0) < 0) + snd_printk(KERN_WARNING "sc6000_setup_board failed on exit!\n"); + release_region(port[dev], 0x10); release_region(mss_port[dev], 4); - snd_card_free(dev_get_drvdata(devptr)); dev_set_drvdata(devptr, NULL); + snd_card_free(card); return 0; } diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 66f3b48ceaf..e497525bc11 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -619,8 +619,7 @@ static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, /* hw_free callback */ static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream) { - if (substream->runtime->dma_area) - vfree(substream->runtime->dma_area); + vfree(substream->runtime->dma_area); substream->runtime->dma_area = NULL; return 0; } diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index 6055fd6d3b3..e924492df21 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -935,7 +935,7 @@ snd_harmony_create(struct snd_card *card, 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); + (unsigned long)padev->hpa.start); err = -EBUSY; goto free_and_ret; } @@ -1020,7 +1020,7 @@ static struct parisc_driver snd_harmony_driver = { .name = "harmony", .id_table = snd_harmony_devtable, .probe = snd_harmony_probe, - .remove = snd_harmony_remove, + .remove = __devexit_p(snd_harmony_remove), }; static int __init diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 3a7640feaf9..748f6b7d90b 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -542,6 +542,9 @@ config SND_HDSP To compile this driver as a module, choose M here: the module will be called snd-hdsp. +comment "Don't forget to add built-in firmwares for HDSP driver" + depends on SND_HDSP=y + config SND_HDSPM tristate "RME Hammerfall DSP MADI" select SND_HWDEP @@ -632,6 +635,16 @@ config SND_KORG1212 To compile this driver as a module, choose M here: the module will be called snd-korg1212. +config SND_LX6464ES + tristate "Digigram LX6464ES" + select SND_PCM + help + Say Y here to include support for Digigram LX6464ES boards. + + To compile this driver as a module, choose M here: the module + will be called snd-lx6464es. + + config SND_MAESTRO3 tristate "ESS Allegro/Maestro3" select SND_AC97_CODEC @@ -774,8 +787,8 @@ config SND_VIRTUOSO select SND_OXYGEN_LIB help Say Y here to include support for sound cards based on the - Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, and - Essence STX. + Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, + Essence ST (Deluxe), and Essence STX. Support for the HDAV1.3 (Deluxe) is very experimental. To compile this driver as a module, choose M here: the module diff --git a/sound/pci/Makefile b/sound/pci/Makefile index 6a1281ec01e..ecfc609d2b9 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_SND) += \ ca0106/ \ cs46xx/ \ cs5535audio/ \ + lx6464es/ \ echoaudio/ \ emu10k1/ \ hda/ \ diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 81bc93e5f1e..7337abdbe4e 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -958,10 +958,13 @@ static int patch_sigmatel_stac9708_3d(struct snd_ac97 * ac97) } static const struct snd_kcontrol_new snd_ac97_sigmatel_4speaker = -AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch", AC97_SIGMATEL_DAC2INVERT, 2, 1, 0); +AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch", + AC97_SIGMATEL_DAC2INVERT, 2, 1, 0); +/* "Sigmatel " removed due to excessive name length: */ static const struct snd_kcontrol_new snd_ac97_sigmatel_phaseinvert = -AC97_SINGLE("Sigmatel Surround Phase Inversion Playback Switch", AC97_SIGMATEL_DAC2INVERT, 3, 1, 0); +AC97_SINGLE("Surround Phase Inversion Playback Switch", + AC97_SIGMATEL_DAC2INVERT, 3, 1, 0); static const struct snd_kcontrol_new snd_ac97_sigmatel_controls[] = { AC97_SINGLE("Sigmatel DAC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 1, 1, 0), diff --git a/sound/pci/au88x0/au88x0_core.c b/sound/pci/au88x0/au88x0_core.c index 3906f5afe27..23f49f356e0 100644 --- a/sound/pci/au88x0/au88x0_core.c +++ b/sound/pci/au88x0/au88x0_core.c @@ -1255,8 +1255,8 @@ static int inline vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma) int temp; temp = hwread(vortex->mmio, VORTEX_ADBDMA_STAT + (adbdma << 2)); - temp = (dma->period_virt * dma->period_bytes) + (temp & POS_MASK); - return (temp); + temp = (dma->period_virt * dma->period_bytes) + (temp & (dma->period_bytes - 1)); + return temp; } static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma) @@ -1504,8 +1504,7 @@ static int inline vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma) int temp; temp = hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)); - //temp = (temp & POS_MASK) + (((temp>>WT_SUBBUF_SHIFT) & WT_SUBBUF_MASK)*(dma->cfg0&POS_MASK)); - temp = (temp & POS_MASK) + ((dma->period_virt) * (dma->period_bytes)); + temp = (dma->period_virt * dma->period_bytes) + (temp & (dma->period_bytes - 1)); return temp; } @@ -2441,7 +2440,8 @@ static irqreturn_t vortex_interrupt(int irq, void *dev_id) spin_lock(&vortex->lock); for (i = 0; i < NR_ADB; i++) { if (vortex->dma_adb[i].fifo_status == FIFO_START) { - if (vortex_adbdma_bufshift(vortex, i)) ; + if (!vortex_adbdma_bufshift(vortex, i)) + continue; spin_unlock(&vortex->lock); snd_pcm_period_elapsed(vortex->dma_adb[i]. substream); diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index ce3f2e90f4d..24585c6c6d0 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -810,6 +810,8 @@ static struct pci_device_id snd_bt87x_ids[] = { BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x107d, 0x6606, GENERIC), /* Voodoo TV 200 */ BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x121a, 0x3000, GENERIC), + /* Askey Computer Corp. MagicTView'99 */ + BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x144f, 0x3000, GENERIC), /* AVerMedia Studio No. 103, 203, ...? */ BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1461, 0x0003, AVPHONE98), /* Prolink PixelView PV-M4900 */ diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index bfac30f7929..57b992a5c05 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -1319,7 +1319,6 @@ static int __devinit snd_ca0106_pcm(struct snd_ca0106 *emu, int device) } pcm->info_flags = 0; - pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; strcpy(pcm->name, "CA0106"); for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; diff --git a/sound/pci/ca0106/ca0106_mixer.c b/sound/pci/ca0106/ca0106_mixer.c index ad2888705d2..c8c6f437f5b 100644 --- a/sound/pci/ca0106/ca0106_mixer.c +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -739,7 +739,7 @@ static int __devinit rename_ctl(struct snd_card *card, const char *src, const ch } while (0) static __devinitdata -DECLARE_TLV_DB_SCALE(snd_ca0106_master_db_scale, -6375, 50, 1); +DECLARE_TLV_DB_SCALE(snd_ca0106_master_db_scale, -6375, 25, 1); static char *slave_vols[] __devinitdata = { "Analog Front Playback Volume", @@ -800,7 +800,7 @@ int __devinit snd_ca0106_mixer(struct snd_ca0106 *emu) "Capture Volume", "External Amplifier", "Sigmatel 4-Speaker Stereo Playback Switch", - "Sigmatel Surround Phase Inversion Playback ", + "Surround Phase Inversion Playback Switch", NULL }; static char *ca0106_rename_ctls[] = { @@ -841,6 +841,9 @@ int __devinit snd_ca0106_mixer(struct snd_ca0106 *emu) snd_ca0106_master_db_scale); if (!vmaster) return -ENOMEM; + err = snd_ctl_add(card, vmaster); + if (err < 0) + return err; add_slaves(card, vmaster, slave_vols); if (emu->details->spi_dac == 1) { @@ -848,8 +851,13 @@ int __devinit snd_ca0106_mixer(struct snd_ca0106 *emu) NULL); if (!vmaster) return -ENOMEM; + err = snd_ctl_add(card, vmaster); + if (err < 0) + return err; add_slaves(card, vmaster, slave_sws); } + + strcpy(card->mixername, "CA0106"); return 0; } diff --git a/sound/pci/emu10k1/Makefile b/sound/pci/emu10k1/Makefile index cf2d5636d8b..fc5591e7777 100644 --- a/sound/pci/emu10k1/Makefile +++ b/sound/pci/emu10k1/Makefile @@ -9,15 +9,7 @@ snd-emu10k1-objs := emu10k1.o emu10k1_main.o \ snd-emu10k1-synth-objs := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o snd-emu10k1x-objs := emu10k1x.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - # Toplevel Module Dependency obj-$(CONFIG_SND_EMU10K1) += snd-emu10k1.o -obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-emu10k1-synth.o +obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emu10k1-synth.o obj-$(CONFIG_SND_EMU10K1X) += snd-emu10k1x.o diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index 1970f0e70f3..4d3ad793e98 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -858,7 +858,6 @@ static int __devinit snd_emu10k1x_pcm(struct emu10k1x *emu, int device, struct s } pcm->info_flags = 0; - pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; switch(device) { case 0: strcpy(pcm->name, "EMU10K1X Front"); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 78f62fd404c..55b83ef73c6 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1736,7 +1736,7 @@ static struct snd_pcm_hardware snd_emu10k1_fx8010_playback = .buffer_bytes_max = (128*1024), .period_bytes_min = 1024, .period_bytes_max = (128*1024), - .periods_min = 1, + .periods_min = 2, .periods_max = 1024, .fifo_size = 0, }; diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index eb2a19b894a..c710150d506 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -139,6 +139,19 @@ config SND_HDA_CODEC_CONEXANT snd-hda-codec-conexant. This module is automatically loaded at probing. +config SND_HDA_CODEC_CA0110 + bool "Build Creative CA0110-IBG codec support" + depends on SND_HDA_INTEL + default y + help + Say Y here to include Creative CA0110-IBG codec support in + snd-hda-intel driver, found on some Creative X-Fi cards. + + When the HD-audio driver is built as a module, the codec + support code is also built as another module, + snd-hda-codec-ca0110. + This module is automatically loaded at probing. + config SND_HDA_CODEC_CMEDIA bool "Build C-Media HD-audio codec support" default y diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 50f9d096725..e3081d4586c 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -13,6 +13,7 @@ snd-hda-codec-analog-objs := patch_analog.o snd-hda-codec-idt-objs := patch_sigmatel.o snd-hda-codec-si3054-objs := patch_si3054.o snd-hda-codec-atihdmi-objs := patch_atihdmi.o +snd-hda-codec-ca0110-objs := patch_ca0110.o snd-hda-codec-conexant-objs := patch_conexant.o snd-hda-codec-via-objs := patch_via.o snd-hda-codec-nvhdmi-objs := patch_nvhdmi.o @@ -40,6 +41,9 @@ endif ifdef CONFIG_SND_HDA_CODEC_ATIHDMI obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-atihdmi.o endif +ifdef CONFIG_SND_HDA_CODEC_CA0110 +obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-ca0110.o +endif ifdef CONFIG_SND_HDA_CODEC_CONEXANT obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-conexant.o endif diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c index 4de5bacd392..29272f2e95a 100644 --- a/sound/pci/hda/hda_beep.c +++ b/sound/pci/hda/hda_beep.c @@ -45,6 +45,46 @@ static void snd_hda_generate_beep(struct work_struct *work) AC_VERB_SET_BEEP_CONTROL, beep->tone); } +/* (non-standard) Linear beep tone calculation for IDT/STAC codecs + * + * The tone frequency of beep generator on IDT/STAC codecs is + * defined from the 8bit tone parameter, in Hz, + * freq = 48000 * (257 - tone) / 1024 + * that is from 12kHz to 93.75kHz in step of 46.875 hz + */ +static int beep_linear_tone(struct hda_beep *beep, int hz) +{ + hz *= 1000; /* fixed point */ + hz = hz - DIGBEEP_HZ_MIN; + if (hz < 0) + hz = 0; /* turn off PC beep*/ + else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN)) + hz = 0xff; + else { + hz /= DIGBEEP_HZ_STEP; + hz++; + } + return hz; +} + +/* HD-audio standard beep tone parameter calculation + * + * The tone frequency in Hz is calculated as + * freq = 48000 / (tone * 4) + * from 47Hz to 12kHz + */ +static int beep_standard_tone(struct hda_beep *beep, int hz) +{ + if (hz <= 0) + return 0; /* disabled */ + hz = 12000 / hz; + if (hz > 0xff) + return 0xff; + if (hz <= 0) + return 1; + return hz; +} + static int snd_hda_beep_event(struct input_dev *dev, unsigned int type, unsigned int code, int hz) { @@ -55,21 +95,14 @@ static int snd_hda_beep_event(struct input_dev *dev, unsigned int type, if (hz) hz = 1000; case SND_TONE: - hz *= 1000; /* fixed point */ - hz = hz - DIGBEEP_HZ_MIN; - if (hz < 0) - hz = 0; /* turn off PC beep*/ - else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN)) - hz = 0xff; - else { - hz /= DIGBEEP_HZ_STEP; - hz++; - } + if (beep->linear_tone) + beep->tone = beep_linear_tone(beep, hz); + else + beep->tone = beep_standard_tone(beep, hz); break; default: return -1; } - beep->tone = hz; /* schedule beep event */ schedule_work(&beep->beep_work); diff --git a/sound/pci/hda/hda_beep.h b/sound/pci/hda/hda_beep.h index 51bf6a5daf3..0c3de787c71 100644 --- a/sound/pci/hda/hda_beep.h +++ b/sound/pci/hda/hda_beep.h @@ -30,8 +30,9 @@ struct hda_beep { struct hda_codec *codec; char phys[32]; int tone; - int nid; - int enabled; + hda_nid_t nid; + unsigned int enabled:1; + unsigned int linear_tone:1; /* linear tone for IDT/STAC codec */ struct work_struct beep_work; /* scheduled task for beep event */ }; diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 8820faf6c9d..462e2cedaa6 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -48,6 +48,7 @@ static struct hda_vendor_id hda_vendor_ids[] = { { 0x1095, "Silicon Image" }, { 0x10de, "Nvidia" }, { 0x10ec, "Realtek" }, + { 0x1102, "Creative" }, { 0x1106, "VIA" }, { 0x111d, "IDT" }, { 0x11c1, "LSI" }, @@ -157,6 +158,39 @@ make_codec_cmd(struct hda_codec *codec, hda_nid_t nid, int direct, return val; } +/* + * Send and receive a verb + */ +static int codec_exec_verb(struct hda_codec *codec, unsigned int cmd, + unsigned int *res) +{ + struct hda_bus *bus = codec->bus; + int err; + + if (res) + *res = -1; + again: + snd_hda_power_up(codec); + mutex_lock(&bus->cmd_mutex); + err = bus->ops.command(bus, cmd); + if (!err && res) + *res = bus->ops.get_response(bus); + mutex_unlock(&bus->cmd_mutex); + snd_hda_power_down(codec); + if (res && *res == -1 && bus->rirb_error) { + if (bus->response_reset) { + snd_printd("hda_codec: resetting BUS due to " + "fatal communication error\n"); + bus->ops.bus_reset(bus); + } + goto again; + } + /* clear reset-flag when the communication gets recovered */ + if (!err) + bus->response_reset = 0; + return err; +} + /** * snd_hda_codec_read - send a command and get the response * @codec: the HDA codec @@ -173,18 +207,9 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, int direct, unsigned int verb, unsigned int parm) { - struct hda_bus *bus = codec->bus; + unsigned cmd = make_codec_cmd(codec, nid, direct, verb, parm); unsigned int res; - - res = make_codec_cmd(codec, nid, direct, verb, parm); - snd_hda_power_up(codec); - mutex_lock(&bus->cmd_mutex); - if (!bus->ops.command(bus, res)) - res = bus->ops.get_response(bus); - else - res = (unsigned int)-1; - mutex_unlock(&bus->cmd_mutex); - snd_hda_power_down(codec); + codec_exec_verb(codec, cmd, &res); return res; } EXPORT_SYMBOL_HDA(snd_hda_codec_read); @@ -204,17 +229,10 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_read); int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct, unsigned int verb, unsigned int parm) { - struct hda_bus *bus = codec->bus; + unsigned int cmd = make_codec_cmd(codec, nid, direct, verb, parm); unsigned int res; - int err; - - res = make_codec_cmd(codec, nid, direct, verb, parm); - snd_hda_power_up(codec); - mutex_lock(&bus->cmd_mutex); - err = bus->ops.command(bus, res); - mutex_unlock(&bus->cmd_mutex); - snd_hda_power_down(codec); - return err; + return codec_exec_verb(codec, cmd, + codec->bus->sync_write ? &res : NULL); } EXPORT_SYMBOL_HDA(snd_hda_codec_write); @@ -613,7 +631,10 @@ static int get_codec_name(struct hda_codec *codec) const struct hda_vendor_id *c; const char *vendor = NULL; u16 vendor_id = codec->vendor_id >> 16; - char tmp[16], name[32]; + char tmp[16]; + + if (codec->vendor_name) + goto get_chip_name; for (c = hda_vendor_ids; c->id; c++) { if (c->id == vendor_id) { @@ -625,14 +646,21 @@ static int get_codec_name(struct hda_codec *codec) sprintf(tmp, "Generic %04x", vendor_id); vendor = tmp; } + codec->vendor_name = kstrdup(vendor, GFP_KERNEL); + if (!codec->vendor_name) + return -ENOMEM; + + get_chip_name: + if (codec->chip_name) + return 0; + if (codec->preset && codec->preset->name) - snprintf(name, sizeof(name), "%s %s", vendor, - codec->preset->name); - else - snprintf(name, sizeof(name), "%s ID %x", vendor, - codec->vendor_id & 0xffff); - codec->name = kstrdup(name, GFP_KERNEL); - if (!codec->name) + codec->chip_name = kstrdup(codec->preset->name, GFP_KERNEL); + else { + sprintf(tmp, "ID %x", codec->vendor_id & 0xffff); + codec->chip_name = kstrdup(tmp, GFP_KERNEL); + } + if (!codec->chip_name) return -ENOMEM; return 0; } @@ -838,7 +866,8 @@ static void snd_hda_codec_free(struct hda_codec *codec) module_put(codec->owner); free_hda_cache(&codec->amp_cache); free_hda_cache(&codec->cmd_cache); - kfree(codec->name); + kfree(codec->vendor_name); + kfree(codec->chip_name); kfree(codec->modelname); kfree(codec->wcaps); kfree(codec); @@ -943,8 +972,6 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_SUBSYSTEM_ID, 0); } - if (bus->modelname) - codec->modelname = kstrdup(bus->modelname, GFP_KERNEL); /* power-up all before initialization */ hda_set_power_state(codec, @@ -979,15 +1006,16 @@ int snd_hda_codec_configure(struct hda_codec *codec) int err; codec->preset = find_codec_preset(codec); - if (!codec->name) { + if (!codec->vendor_name || !codec->chip_name) { err = get_codec_name(codec); if (err < 0) return err; } /* audio codec should override the mixer name */ if (codec->afg || !*codec->bus->card->mixername) - strlcpy(codec->bus->card->mixername, codec->name, - sizeof(codec->bus->card->mixername)); + snprintf(codec->bus->card->mixername, + sizeof(codec->bus->card->mixername), + "%s %s", codec->vendor_name, codec->chip_name); if (is_generic_config(codec)) { err = snd_hda_parse_generic_codec(codec); @@ -1055,6 +1083,8 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream); /* FIXME: more better hash key? */ #define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24)) #define HDA_HASH_PINCAP_KEY(nid) (u32)((nid) + (0x02 << 24)) +#define HDA_HASH_PARPCM_KEY(nid) (u32)((nid) + (0x03 << 24)) +#define HDA_HASH_PARSTR_KEY(nid) (u32)((nid) + (0x04 << 24)) #define INFO_AMP_CAPS (1<<0) #define INFO_AMP_VOL(ch) (1 << (1 + (ch))) @@ -1145,19 +1175,32 @@ int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, } EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps); -u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) +static unsigned int +query_caps_hash(struct hda_codec *codec, hda_nid_t nid, u32 key, + unsigned int (*func)(struct hda_codec *, hda_nid_t)) { struct hda_amp_info *info; - info = get_alloc_amp_hash(codec, HDA_HASH_PINCAP_KEY(nid)); + info = get_alloc_amp_hash(codec, key); if (!info) return 0; if (!info->head.val) { - info->amp_caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); info->head.val |= INFO_AMP_CAPS; + info->amp_caps = func(codec, nid); } return info->amp_caps; } + +static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); +} + +u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) +{ + return query_caps_hash(codec, nid, HDA_HASH_PINCAP_KEY(nid), + read_pin_cap); +} EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); /* @@ -1432,6 +1475,8 @@ _snd_hda_find_mixer_ctl(struct hda_codec *codec, memset(&id, 0, sizeof(id)); id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; id.index = idx; + if (snd_BUG_ON(strlen(name) >= sizeof(id.name))) + return NULL; strcpy(id.name, name); return snd_ctl_find_id(codec->bus->card, &id); } @@ -2242,28 +2287,22 @@ EXPORT_SYMBOL_HDA(snd_hda_create_spdif_in_ctls); int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, int direct, unsigned int verb, unsigned int parm) { - struct hda_bus *bus = codec->bus; - unsigned int res; - int err; + int err = snd_hda_codec_write(codec, nid, direct, verb, parm); + struct hda_cache_head *c; + u32 key; - res = make_codec_cmd(codec, nid, direct, verb, parm); - snd_hda_power_up(codec); - mutex_lock(&bus->cmd_mutex); - err = bus->ops.command(bus, res); - if (!err) { - struct hda_cache_head *c; - u32 key; - /* parm may contain the verb stuff for get/set amp */ - verb = verb | (parm >> 8); - parm &= 0xff; - key = build_cmd_cache_key(nid, verb); - c = get_alloc_hash(&codec->cmd_cache, key); - if (c) - c->val = parm; - } - mutex_unlock(&bus->cmd_mutex); - snd_hda_power_down(codec); - return err; + if (err < 0) + return err; + /* parm may contain the verb stuff for get/set amp */ + verb = verb | (parm >> 8); + parm &= 0xff; + key = build_cmd_cache_key(nid, verb); + mutex_lock(&codec->bus->cmd_mutex); + c = get_alloc_hash(&codec->cmd_cache, key); + if (c) + c->val = parm; + mutex_unlock(&codec->bus->cmd_mutex); + return 0; } EXPORT_SYMBOL_HDA(snd_hda_codec_write_cache); @@ -2321,7 +2360,8 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, if (wcaps & AC_WCAP_POWER) { unsigned int wid_type = (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; - if (wid_type == AC_WID_PIN) { + if (power_state == AC_PWRST_D3 && + wid_type == AC_WID_PIN) { unsigned int pincap; /* * don't power down the widget if it controls @@ -2333,7 +2373,7 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, nid, 0, AC_VERB_GET_EAPD_BTLENABLE, 0); eapd &= 0x02; - if (power_state == AC_PWRST_D3 && eapd) + if (eapd) continue; } } @@ -2544,6 +2584,41 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate, } EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format); +static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int val = 0; + if (nid != codec->afg && + (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) + val = snd_hda_param_read(codec, nid, AC_PAR_PCM); + if (!val || val == -1) + val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM); + if (!val || val == -1) + return 0; + return val; +} + +static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid) +{ + return query_caps_hash(codec, nid, HDA_HASH_PARPCM_KEY(nid), + get_pcm_param); +} + +static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); + if (!streams || streams == -1) + streams = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM); + if (!streams || streams == -1) + return 0; + return streams; +} + +static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid) +{ + return query_caps_hash(codec, nid, HDA_HASH_PARSTR_KEY(nid), + get_stream_param); +} + /** * snd_hda_query_supported_pcm - query the supported PCM rates and formats * @codec: the HDA codec @@ -2562,15 +2637,8 @@ static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid, { unsigned int i, val, wcaps; - val = 0; wcaps = get_wcaps(codec, nid); - if (nid != codec->afg && (wcaps & AC_WCAP_FORMAT_OVRD)) { - val = snd_hda_param_read(codec, nid, AC_PAR_PCM); - if (val == -1) - return -EIO; - } - if (!val) - val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM); + val = query_pcm_param(codec, nid); if (ratesp) { u32 rates = 0; @@ -2592,15 +2660,9 @@ static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid, u64 formats = 0; unsigned int streams, bps; - streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); - if (streams == -1) + streams = query_stream_param(codec, nid); + if (!streams) return -EIO; - if (!streams) { - streams = snd_hda_param_read(codec, codec->afg, - AC_PAR_STREAM); - if (streams == -1) - return -EIO; - } bps = 0; if (streams & AC_SUPFMT_PCM) { @@ -2674,17 +2736,9 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid, int i; unsigned int val = 0, rate, stream; - if (nid != codec->afg && - (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) { - val = snd_hda_param_read(codec, nid, AC_PAR_PCM); - if (val == -1) - return 0; - } - if (!val) { - val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM); - if (val == -1) - return 0; - } + val = query_pcm_param(codec, nid); + if (!val) + return 0; rate = format & 0xff00; for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++) @@ -2696,12 +2750,8 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid, if (i >= AC_PAR_PCM_RATE_BITS) return 0; - stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM); - if (stream == -1) - return 0; - if (!stream && nid != codec->afg) - stream = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM); - if (!stream || stream == -1) + stream = query_stream_param(codec, nid); + if (!stream) return 0; if (stream & AC_SUPFMT_PCM) { @@ -3835,11 +3885,10 @@ EXPORT_SYMBOL_HDA(auto_pin_cfg_labels); /** * snd_hda_suspend - suspend the codecs * @bus: the HDA bus - * @state: suspsend state * * Returns 0 if successful. */ -int snd_hda_suspend(struct hda_bus *bus, pm_message_t state) +int snd_hda_suspend(struct hda_bus *bus) { struct hda_codec *codec; diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 2fdecf4b0eb..cad79efaabc 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -574,6 +574,8 @@ struct hda_bus_ops { /* attach a PCM stream */ int (*attach_pcm)(struct hda_bus *bus, struct hda_codec *codec, struct hda_pcm *pcm); + /* reset bus for retry verb */ + void (*bus_reset)(struct hda_bus *bus); #ifdef CONFIG_SND_HDA_POWER_SAVE /* notify power-up/down from codec to controller */ void (*pm_notify)(struct hda_bus *bus); @@ -622,7 +624,13 @@ struct hda_bus { /* misc op flags */ unsigned int needs_damn_long_delay :1; + unsigned int allow_bus_reset:1; /* allow bus reset at fatal error */ + unsigned int sync_write:1; /* sync after verb write */ + /* status for codec/controller */ unsigned int shutdown :1; /* being unloaded */ + unsigned int rirb_error:1; /* error in codec communication */ + unsigned int response_reset:1; /* controller was reset */ + unsigned int in_reset:1; /* during reset operation */ }; /* @@ -747,7 +755,8 @@ struct hda_codec { /* detected preset */ const struct hda_codec_preset *preset; struct module *owner; - const char *name; /* codec name */ + const char *vendor_name; /* codec vendor name */ + const char *chip_name; /* codec chip name */ const char *modelname; /* model name for preset */ /* set by patch */ @@ -905,7 +914,7 @@ void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen); * power management */ #ifdef CONFIG_PM -int snd_hda_suspend(struct hda_bus *bus, pm_message_t state); +int snd_hda_suspend(struct hda_bus *bus); int snd_hda_resume(struct hda_bus *bus); #endif diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c index 1c57505c287..6812fbe80fa 100644 --- a/sound/pci/hda/hda_hwdep.c +++ b/sound/pci/hda/hda_hwdep.c @@ -242,7 +242,8 @@ CODEC_INFO_SHOW(subsystem_id); CODEC_INFO_SHOW(revision_id); CODEC_INFO_SHOW(afg); CODEC_INFO_SHOW(mfg); -CODEC_INFO_STR_SHOW(name); +CODEC_INFO_STR_SHOW(vendor_name); +CODEC_INFO_STR_SHOW(chip_name); CODEC_INFO_STR_SHOW(modelname); #define CODEC_INFO_STORE(type) \ @@ -275,7 +276,8 @@ static ssize_t type##_store(struct device *dev, \ CODEC_INFO_STORE(vendor_id); CODEC_INFO_STORE(subsystem_id); CODEC_INFO_STORE(revision_id); -CODEC_INFO_STR_STORE(name); +CODEC_INFO_STR_STORE(vendor_name); +CODEC_INFO_STR_STORE(chip_name); CODEC_INFO_STR_STORE(modelname); #define CODEC_ACTION_STORE(type) \ @@ -499,7 +501,8 @@ static struct device_attribute codec_attrs[] = { CODEC_ATTR_RW(revision_id), CODEC_ATTR_RO(afg), CODEC_ATTR_RO(mfg), - CODEC_ATTR_RW(name), + CODEC_ATTR_RW(vendor_name), + CODEC_ATTR_RW(chip_name), CODEC_ATTR_RW(modelname), CODEC_ATTR_RW(init_verbs), CODEC_ATTR_RW(hints), diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 21e99cfa8c4..4e9ea708027 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -128,21 +128,33 @@ MODULE_SUPPORTED_DEVICE("{{Intel, ICH6}," "{ULI, M5461}}"); MODULE_DESCRIPTION("Intel HDA driver"); +#ifdef CONFIG_SND_VERBOSE_PRINTK +#define SFX /* nop */ +#else #define SFX "hda-intel: " - +#endif /* * registers */ #define ICH6_REG_GCAP 0x00 +#define ICH6_GCAP_64OK (1 << 0) /* 64bit address support */ +#define ICH6_GCAP_NSDO (3 << 1) /* # of serial data out signals */ +#define ICH6_GCAP_BSS (31 << 3) /* # of bidirectional streams */ +#define ICH6_GCAP_ISS (15 << 8) /* # of input streams */ +#define ICH6_GCAP_OSS (15 << 12) /* # of output streams */ #define ICH6_REG_VMIN 0x02 #define ICH6_REG_VMAJ 0x03 #define ICH6_REG_OUTPAY 0x04 #define ICH6_REG_INPAY 0x06 #define ICH6_REG_GCTL 0x08 +#define ICH6_GCTL_RESET (1 << 0) /* controller reset */ +#define ICH6_GCTL_FCNTRL (1 << 1) /* flush control */ +#define ICH6_GCTL_UNSOL (1 << 8) /* accept unsol. response enable */ #define ICH6_REG_WAKEEN 0x0c #define ICH6_REG_STATESTS 0x0e #define ICH6_REG_GSTS 0x10 +#define ICH6_GSTS_FSTS (1 << 1) /* flush status */ #define ICH6_REG_INTCTL 0x20 #define ICH6_REG_INTSTS 0x24 #define ICH6_REG_WALCLK 0x30 @@ -150,17 +162,27 @@ MODULE_DESCRIPTION("Intel HDA driver"); #define ICH6_REG_CORBLBASE 0x40 #define ICH6_REG_CORBUBASE 0x44 #define ICH6_REG_CORBWP 0x48 -#define ICH6_REG_CORBRP 0x4A +#define ICH6_REG_CORBRP 0x4a +#define ICH6_CORBRP_RST (1 << 15) /* read pointer reset */ #define ICH6_REG_CORBCTL 0x4c +#define ICH6_CORBCTL_RUN (1 << 1) /* enable DMA */ +#define ICH6_CORBCTL_CMEIE (1 << 0) /* enable memory error irq */ #define ICH6_REG_CORBSTS 0x4d +#define ICH6_CORBSTS_CMEI (1 << 0) /* memory error indication */ #define ICH6_REG_CORBSIZE 0x4e #define ICH6_REG_RIRBLBASE 0x50 #define ICH6_REG_RIRBUBASE 0x54 #define ICH6_REG_RIRBWP 0x58 +#define ICH6_RIRBWP_RST (1 << 15) /* write pointer reset */ #define ICH6_REG_RINTCNT 0x5a #define ICH6_REG_RIRBCTL 0x5c +#define ICH6_RBCTL_IRQ_EN (1 << 0) /* enable IRQ */ +#define ICH6_RBCTL_DMA_EN (1 << 1) /* enable DMA */ +#define ICH6_RBCTL_OVERRUN_EN (1 << 2) /* enable overrun irq */ #define ICH6_REG_RIRBSTS 0x5d +#define ICH6_RBSTS_IRQ (1 << 0) /* response irq */ +#define ICH6_RBSTS_OVERRUN (1 << 2) /* overrun irq */ #define ICH6_REG_RIRBSIZE 0x5e #define ICH6_REG_IC 0x60 @@ -257,16 +279,6 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */ #define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */ -/* GCTL unsolicited response enable bit */ -#define ICH6_GCTL_UREN (1<<8) - -/* GCTL reset bit */ -#define ICH6_GCTL_RESET (1<<0) - -/* CORB/RIRB control, read/write pointer */ -#define ICH6_RBCTL_DMA_EN 0x02 /* enable DMA */ -#define ICH6_RBCTL_IRQ_EN 0x01 /* enable IRQ */ -#define ICH6_RBRWP_CLR 0x8000 /* read/write pointer clear */ /* below are so far hardcoded - should read registers in future */ #define ICH6_MAX_CORB_ENTRIES 256 #define ICH6_MAX_RIRB_ENTRIES 256 @@ -512,25 +524,25 @@ static void azx_init_cmd_io(struct azx *chip) /* set the corb write pointer to 0 */ azx_writew(chip, CORBWP, 0); /* reset the corb hw read pointer */ - azx_writew(chip, CORBRP, ICH6_RBRWP_CLR); + azx_writew(chip, CORBRP, ICH6_CORBRP_RST); /* enable corb dma */ - azx_writeb(chip, CORBCTL, ICH6_RBCTL_DMA_EN); + azx_writeb(chip, CORBCTL, ICH6_CORBCTL_RUN); /* RIRB set up */ chip->rirb.addr = chip->rb.addr + 2048; chip->rirb.buf = (u32 *)(chip->rb.area + 2048); + chip->rirb.wp = chip->rirb.rp = chip->rirb.cmds = 0; azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr); azx_writel(chip, RIRBUBASE, upper_32_bits(chip->rirb.addr)); /* set the rirb size to 256 entries (ULI requires explicitly) */ azx_writeb(chip, RIRBSIZE, 0x02); /* reset the rirb hw write pointer */ - azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR); + azx_writew(chip, RIRBWP, ICH6_RIRBWP_RST); /* set N=1, get RIRB response interrupt for new entry */ azx_writew(chip, RINTCNT, 1); /* enable rirb dma and response irq */ azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN); - chip->rirb.rp = chip->rirb.cmds = 0; } static void azx_free_cmd_io(struct azx *chip) @@ -606,6 +618,7 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus) } if (!chip->rirb.cmds) { smp_rmb(); + bus->rirb_error = 0; return chip->rirb.res; /* the last value */ } if (time_after(jiffies, timeout)) @@ -619,19 +632,21 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus) } if (chip->msi) { - snd_printk(KERN_WARNING "hda_intel: No response from codec, " + snd_printk(KERN_WARNING SFX "No response from codec, " "disabling MSI: last cmd=0x%08x\n", chip->last_cmd); free_irq(chip->irq, chip); chip->irq = -1; pci_disable_msi(chip->pci); chip->msi = 0; - if (azx_acquire_irq(chip, 1) < 0) + if (azx_acquire_irq(chip, 1) < 0) { + bus->rirb_error = 1; return -1; + } goto again; } if (!chip->polling_mode) { - snd_printk(KERN_WARNING "hda_intel: azx_get_response timeout, " + snd_printk(KERN_WARNING SFX "azx_get_response timeout, " "switching to polling mode: last cmd=0x%08x\n", chip->last_cmd); chip->polling_mode = 1; @@ -646,14 +661,23 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus) return -1; } + /* a fatal communication error; need either to reset or to fallback + * to the single_cmd mode + */ + bus->rirb_error = 1; + if (bus->allow_bus_reset && !bus->response_reset && !bus->in_reset) { + bus->response_reset = 1; + return -1; /* give a chance to retry */ + } + snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, " "switching to single_cmd mode: last cmd=0x%08x\n", chip->last_cmd); - chip->rirb.rp = azx_readb(chip, RIRBWP); - chip->rirb.cmds = 0; - /* switch to single_cmd mode */ chip->single_cmd = 1; + bus->response_reset = 0; + /* re-initialize CORB/RIRB */ azx_free_cmd_io(chip); + azx_init_cmd_io(chip); return -1; } @@ -667,12 +691,34 @@ static unsigned int azx_rirb_get_response(struct hda_bus *bus) * I left the codes, however, for debugging/testing purposes. */ +/* receive a response */ +static int azx_single_wait_for_response(struct azx *chip) +{ + int timeout = 50; + + while (timeout--) { + /* check IRV busy bit */ + if (azx_readw(chip, IRS) & ICH6_IRS_VALID) { + /* reuse rirb.res as the response return value */ + chip->rirb.res = azx_readl(chip, IR); + return 0; + } + udelay(1); + } + if (printk_ratelimit()) + snd_printd(SFX "get_response timeout: IRS=0x%x\n", + azx_readw(chip, IRS)); + chip->rirb.res = -1; + return -EIO; +} + /* send a command */ static int azx_single_send_cmd(struct hda_bus *bus, u32 val) { struct azx *chip = bus->private_data; int timeout = 50; + bus->rirb_error = 0; while (timeout--) { /* check ICB busy bit */ if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) { @@ -682,7 +728,7 @@ static int azx_single_send_cmd(struct hda_bus *bus, u32 val) azx_writel(chip, IC, val); azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_BUSY); - return 0; + return azx_single_wait_for_response(chip); } udelay(1); } @@ -696,18 +742,7 @@ static int azx_single_send_cmd(struct hda_bus *bus, u32 val) static unsigned int azx_single_get_response(struct hda_bus *bus) { struct azx *chip = bus->private_data; - int timeout = 50; - - while (timeout--) { - /* check IRV busy bit */ - if (azx_readw(chip, IRS) & ICH6_IRS_VALID) - return azx_readl(chip, IR); - udelay(1); - } - if (printk_ratelimit()) - snd_printd(SFX "get_response timeout: IRS=0x%x\n", - azx_readw(chip, IRS)); - return (unsigned int)-1; + return chip->rirb.res; } /* @@ -775,17 +810,17 @@ static int azx_reset(struct azx *chip) /* check to see if controller is ready */ if (!azx_readb(chip, GCTL)) { - snd_printd("azx_reset: controller not ready!\n"); + snd_printd(SFX "azx_reset: controller not ready!\n"); return -EBUSY; } /* Accept unsolicited responses */ - azx_writel(chip, GCTL, azx_readl(chip, GCTL) | ICH6_GCTL_UREN); + azx_writel(chip, GCTL, azx_readl(chip, GCTL) | ICH6_GCTL_UNSOL); /* detect codecs */ if (!chip->codec_mask) { chip->codec_mask = azx_readw(chip, STATESTS); - snd_printdd("codec_mask = 0x%x\n", chip->codec_mask); + snd_printdd(SFX "codec_mask = 0x%x\n", chip->codec_mask); } return 0; @@ -895,8 +930,7 @@ static void azx_init_chip(struct azx *chip) azx_int_enable(chip); /* initialize the codec command I/O */ - if (!chip->single_cmd) - azx_init_cmd_io(chip); + azx_init_cmd_io(chip); /* program the position buffer */ azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr); @@ -953,12 +987,12 @@ static void azx_init_pci(struct azx *chip) case AZX_DRIVER_SCH: pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop); if (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) { - pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC, \ + pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC, snoop & (~INTEL_SCH_HDA_DEVC_NOSNOOP)); pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop); - snd_printdd("HDA snoop disabled, enabling ... %s\n",\ - (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) \ + snd_printdd(SFX "HDA snoop disabled, enabling ... %s\n", + (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) ? "Failed" : "OK"); } break; @@ -1012,7 +1046,7 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id) /* clear rirb int */ status = azx_readb(chip, RIRBSTS); if (status & RIRB_INT_MASK) { - if (!chip->single_cmd && (status & RIRB_INT_RESPONSE)) + if (status & RIRB_INT_RESPONSE) azx_update_rirb(chip); azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); } @@ -1098,7 +1132,7 @@ static int azx_setup_periods(struct azx *chip, pos_align; pos_adj = frames_to_bytes(runtime, pos_adj); if (pos_adj >= period_bytes) { - snd_printk(KERN_WARNING "Too big adjustment %d\n", + snd_printk(KERN_WARNING SFX "Too big adjustment %d\n", bdl_pos_adj[chip->dev_index]); pos_adj = 0; } else { @@ -1122,7 +1156,7 @@ static int azx_setup_periods(struct azx *chip, return 0; error: - snd_printk(KERN_ERR "Too many BDL entries: buffer=%d, period=%d\n", + snd_printk(KERN_ERR SFX "Too many BDL entries: buffer=%d, period=%d\n", azx_dev->bufsize, period_bytes); return -EINVAL; } @@ -1215,7 +1249,7 @@ static int probe_codec(struct azx *chip, int addr) chip->probing = 0; if (res == -1) return -EIO; - snd_printdd("hda_intel: codec #%d probed OK\n", addr); + snd_printdd(SFX "codec #%d probed OK\n", addr); return 0; } @@ -1223,6 +1257,26 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, struct hda_pcm *cpcm); static void azx_stop_chip(struct azx *chip); +static void azx_bus_reset(struct hda_bus *bus) +{ + struct azx *chip = bus->private_data; + + bus->in_reset = 1; + azx_stop_chip(chip); + azx_init_chip(chip); +#ifdef CONFIG_PM + if (chip->initialized) { + int i; + + for (i = 0; i < AZX_MAX_PCMS; i++) + snd_pcm_suspend_all(chip->pcm[i]); + snd_hda_suspend(chip->bus); + snd_hda_resume(chip->bus); + } +#endif + bus->in_reset = 0; +} + /* * Codec initialization */ @@ -1246,6 +1300,7 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model, bus_temp.ops.command = azx_send_cmd; bus_temp.ops.get_response = azx_get_response; bus_temp.ops.attach_pcm = azx_attach_pcm_stream; + bus_temp.ops.bus_reset = azx_bus_reset; #ifdef CONFIG_SND_HDA_POWER_SAVE bus_temp.power_save = &power_save; bus_temp.ops.pm_notify = azx_power_notify; @@ -1270,8 +1325,8 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model, /* Some BIOSen give you wrong codec addresses * that don't exist */ - snd_printk(KERN_WARNING - "hda_intel: Codec #%d probe error; " + snd_printk(KERN_WARNING SFX + "Codec #%d probe error; " "disabling it...\n", c); chip->codec_mask &= ~(1 << c); /* More badly, accessing to a non-existing @@ -1487,7 +1542,7 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) bufsize = snd_pcm_lib_buffer_bytes(substream); period_bytes = snd_pcm_lib_period_bytes(substream); - snd_printdd("azx_pcm_prepare: bufsize=0x%x, format=0x%x\n", + snd_printdd(SFX "azx_pcm_prepare: bufsize=0x%x, format=0x%x\n", bufsize, format_val); if (bufsize != azx_dev->bufsize || @@ -1830,7 +1885,7 @@ azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, &pcm); if (err < 0) return err; - strcpy(pcm->name, cpcm->name); + strlcpy(pcm->name, cpcm->name, sizeof(pcm->name)); apcm = kzalloc(sizeof(*apcm), GFP_KERNEL); if (apcm == NULL) return -ENOMEM; @@ -1973,7 +2028,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state) for (i = 0; i < AZX_MAX_PCMS; i++) snd_pcm_suspend_all(chip->pcm[i]); if (chip->initialized) - snd_hda_suspend(chip->bus, state); + snd_hda_suspend(chip->bus); azx_stop_chip(chip); if (chip->irq >= 0) { free_irq(chip->irq, chip); @@ -2141,6 +2196,7 @@ static struct snd_pci_quirk probe_mask_list[] __devinitdata = { /* including bogus ALC268 in slot#2 that conflicts with ALC888 */ SND_PCI_QUIRK(0x17c0, 0x4085, "Medion MD96630", 0x01), /* forced codec slots */ + SND_PCI_QUIRK(0x1043, 0x1262, "ASUS W5Fm", 0x103), SND_PCI_QUIRK(0x1046, 0x1262, "ASUS W5F", 0x103), {} }; @@ -2264,14 +2320,14 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, synchronize_irq(chip->irq); gcap = azx_readw(chip, GCAP); - snd_printdd("chipset global capabilities = 0x%x\n", gcap); + snd_printdd(SFX "chipset global capabilities = 0x%x\n", gcap); /* ATI chips seems buggy about 64bit DMA addresses */ if (chip->driver_type == AZX_DRIVER_ATI) - gcap &= ~0x01; + gcap &= ~ICH6_GCAP_64OK; /* allow 64bit DMA address if supported by H/W */ - if ((gcap & 0x01) && !pci_set_dma_mask(pci, DMA_BIT_MASK(64))) + if ((gcap & ICH6_GCAP_64OK) && !pci_set_dma_mask(pci, DMA_BIT_MASK(64))) pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64)); else { pci_set_dma_mask(pci, DMA_BIT_MASK(32)); @@ -2308,7 +2364,7 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, chip->azx_dev = kcalloc(chip->num_streams, sizeof(*chip->azx_dev), GFP_KERNEL); if (!chip->azx_dev) { - snd_printk(KERN_ERR "cannot malloc azx_dev\n"); + snd_printk(KERN_ERR SFX "cannot malloc azx_dev\n"); goto errout; } @@ -2331,11 +2387,9 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, goto errout; } /* allocate CORB/RIRB */ - if (!chip->single_cmd) { - err = azx_alloc_cmd_io(chip); - if (err < 0) - goto errout; - } + err = azx_alloc_cmd_io(chip); + if (err < 0) + goto errout; /* initialize streams */ azx_init_stream(chip); @@ -2358,9 +2412,11 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, } strcpy(card->driver, "HDA-Intel"); - strcpy(card->shortname, driver_short_names[chip->driver_type]); - sprintf(card->longname, "%s at 0x%lx irq %i", - card->shortname, chip->addr, chip->irq); + strlcpy(card->shortname, driver_short_names[chip->driver_type], + sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, chip->addr, chip->irq); *rchip = chip; return 0; @@ -2513,6 +2569,20 @@ static struct pci_device_id azx_ids[] = { { PCI_DEVICE(0x10de, 0x0d97), .driver_data = AZX_DRIVER_NVIDIA }, /* Teradici */ { PCI_DEVICE(0x6549, 0x1200), .driver_data = AZX_DRIVER_TERA }, + /* Creative X-Fi (CA0110-IBG) */ +#if !defined(CONFIG_SND_CTXFI) && !defined(CONFIG_SND_CTXFI_MODULE) + /* the following entry conflicts with snd-ctxfi driver, + * as ctxfi driver mutates from HD-audio to native mode with + * a special command sequence. + */ + { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_GENERIC }, +#else + /* this entry seems still valid -- i.e. without emu20kx chip */ + { PCI_DEVICE(0x1102, 0x0009), .driver_data = AZX_DRIVER_GENERIC }, +#endif /* AMD Generic, PCI class code and Vendor ID for HD Audio */ { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_ANY_ID), .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c index 93d7499350c..418c5d1bada 100644 --- a/sound/pci/hda/hda_proc.c +++ b/sound/pci/hda/hda_proc.c @@ -466,8 +466,12 @@ static void print_codec_info(struct snd_info_entry *entry, hda_nid_t nid; int i, nodes; - snd_iprintf(buffer, "Codec: %s\n", - codec->name ? codec->name : "Not Set"); + snd_iprintf(buffer, "Codec: "); + if (codec->vendor_name && codec->chip_name) + snd_iprintf(buffer, "%s %s\n", + codec->vendor_name, codec->chip_name); + else + snd_iprintf(buffer, "Not Set\n"); snd_iprintf(buffer, "Address: %d\n", codec->addr); snd_iprintf(buffer, "Function Id: 0x%x\n", codec->function_id); snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id); diff --git a/sound/pci/hda/patch_ca0110.c b/sound/pci/hda/patch_ca0110.c new file mode 100644 index 00000000000..392d108c355 --- /dev/null +++ b/sound/pci/hda/patch_ca0110.c @@ -0,0 +1,573 @@ +/* + * HD audio interface patch for Creative X-Fi CA0110-IBG chip + * + * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de> + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + +/* + */ + +struct ca0110_spec { + struct auto_pin_cfg autocfg; + struct hda_multi_out multiout; + hda_nid_t out_pins[AUTO_CFG_MAX_OUTS]; + hda_nid_t dacs[AUTO_CFG_MAX_OUTS]; + hda_nid_t hp_dac; + hda_nid_t input_pins[AUTO_PIN_LAST]; + hda_nid_t adcs[AUTO_PIN_LAST]; + hda_nid_t dig_out; + hda_nid_t dig_in; + unsigned int num_inputs; + const char *input_labels[AUTO_PIN_LAST]; + struct hda_pcm pcm_rec[2]; /* PCM information */ +}; + +/* + * PCM callbacks + */ +static int ca0110_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int ca0110_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static int ca0110_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + +/* + * Digital out + */ +static int ca0110_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int ca0110_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int ca0110_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +/* + * Analog capture + */ +static int ca0110_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, spec->adcs[substream->number], + stream_tag, 0, format); + return 0; +} + +static int ca0110_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0110_spec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, spec->adcs[substream->number]); + return 0; +} + +/* + */ + +static char *dirstr[2] = { "Playback", "Capture" }; + +static int _add_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx, + int chan, int dir) +{ + char namestr[44]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(namestr, nid, chan, 0, type); + sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + return snd_hda_ctl_add(codec, snd_ctl_new1(&knew, codec)); +} + +static int _add_volume(struct hda_codec *codec, hda_nid_t nid, const char *pfx, + int chan, int dir) +{ + char namestr[44]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, chan, 0, type); + sprintf(namestr, "%s %s Volume", pfx, dirstr[dir]); + return snd_hda_ctl_add(codec, snd_ctl_new1(&knew, codec)); +} + +#define add_out_switch(codec, nid, pfx) _add_switch(codec, nid, pfx, 3, 0) +#define add_out_volume(codec, nid, pfx) _add_volume(codec, nid, pfx, 3, 0) +#define add_in_switch(codec, nid, pfx) _add_switch(codec, nid, pfx, 3, 1) +#define add_in_volume(codec, nid, pfx) _add_volume(codec, nid, pfx, 3, 1) +#define add_mono_switch(codec, nid, pfx, chan) \ + _add_switch(codec, nid, pfx, chan, 0) +#define add_mono_volume(codec, nid, pfx, chan) \ + _add_volume(codec, nid, pfx, chan, 0) + +static int ca0110_build_controls(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + static char *prefix[AUTO_CFG_MAX_OUTS] = { + "Front", "Surround", NULL, "Side", "Multi" + }; + hda_nid_t mutenid; + int i, err; + + for (i = 0; i < spec->multiout.num_dacs; i++) { + if (get_wcaps(codec, spec->out_pins[i]) & AC_WCAP_OUT_AMP) + mutenid = spec->out_pins[i]; + else + mutenid = spec->multiout.dac_nids[i]; + if (!prefix[i]) { + err = add_mono_switch(codec, mutenid, + "Center", 1); + if (err < 0) + return err; + err = add_mono_switch(codec, mutenid, + "LFE", 1); + if (err < 0) + return err; + err = add_mono_volume(codec, spec->multiout.dac_nids[i], + "Center", 1); + if (err < 0) + return err; + err = add_mono_volume(codec, spec->multiout.dac_nids[i], + "LFE", 1); + if (err < 0) + return err; + } else { + err = add_out_switch(codec, mutenid, + prefix[i]); + if (err < 0) + return err; + err = add_out_volume(codec, spec->multiout.dac_nids[i], + prefix[i]); + if (err < 0) + return err; + } + } + if (cfg->hp_outs) { + if (get_wcaps(codec, cfg->hp_pins[0]) & AC_WCAP_OUT_AMP) + mutenid = cfg->hp_pins[0]; + else + mutenid = spec->multiout.dac_nids[i]; + + err = add_out_switch(codec, mutenid, "Headphone"); + if (err < 0) + return err; + if (spec->hp_dac) { + err = add_out_volume(codec, spec->hp_dac, "Headphone"); + if (err < 0) + return err; + } + } + for (i = 0; i < spec->num_inputs; i++) { + const char *label = spec->input_labels[i]; + if (get_wcaps(codec, spec->input_pins[i]) & AC_WCAP_IN_AMP) + mutenid = spec->input_pins[i]; + else + mutenid = spec->adcs[i]; + err = add_in_switch(codec, mutenid, label); + if (err < 0) + return err; + err = add_in_volume(codec, spec->adcs[i], label); + if (err < 0) + return err; + } + + if (spec->dig_out) { + err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out); + if (err < 0) + return err; + err = snd_hda_create_spdif_share_sw(codec, &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + if (spec->dig_in) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in); + if (err < 0) + return err; + err = add_in_volume(codec, spec->dig_in, "IEC958"); + } + return 0; +} + +/* + */ +static struct hda_pcm_stream ca0110_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .ops = { + .open = ca0110_playback_pcm_open, + .prepare = ca0110_playback_pcm_prepare, + .cleanup = ca0110_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ca0110_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .prepare = ca0110_capture_pcm_prepare, + .cleanup = ca0110_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ca0110_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .open = ca0110_dig_playback_pcm_open, + .close = ca0110_dig_playback_pcm_close, + .prepare = ca0110_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream ca0110_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +static int ca0110_build_pcms(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + codec->pcm_info = info; + codec->num_pcms = 0; + + info->name = "CA0110 Analog"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0110_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0]; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = + spec->multiout.max_channels; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0110_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_inputs; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0]; + codec->num_pcms++; + + if (!spec->dig_out && !spec->dig_in) + return 0; + + info++; + info->name = "CA0110 Digital"; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->dig_out) { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + ca0110_pcm_digital_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out; + } + if (spec->dig_in) { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0110_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in; + } + codec->num_pcms++; + + return 0; +} + +static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac) +{ + if (pin) { + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP); + if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + } + if (dac) + snd_hda_codec_write(codec, dac, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO); +} + +static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc) +{ + if (pin) { + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80); + if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); + } + if (adc) + snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); +} + +static int ca0110_init(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + for (i = 0; i < spec->multiout.num_dacs; i++) + init_output(codec, spec->out_pins[i], + spec->multiout.dac_nids[i]); + init_output(codec, cfg->hp_pins[0], spec->hp_dac); + init_output(codec, cfg->dig_out_pins[0], spec->dig_out); + + for (i = 0; i < spec->num_inputs; i++) + init_input(codec, spec->input_pins[i], spec->adcs[i]); + init_input(codec, cfg->dig_in_pin, spec->dig_in); + return 0; +} + +static void ca0110_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + +static struct hda_codec_ops ca0110_patch_ops = { + .build_controls = ca0110_build_controls, + .build_pcms = ca0110_build_pcms, + .init = ca0110_init, + .free = ca0110_free, +}; + + +static void parse_line_outs(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i, n; + unsigned int def_conf; + hda_nid_t nid; + + n = 0; + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + def_conf = snd_hda_codec_get_pincfg(codec, nid); + if (!def_conf) + continue; /* invalid pin */ + if (snd_hda_get_connections(codec, nid, &spec->dacs[i], 1) != 1) + continue; + spec->out_pins[n++] = nid; + } + spec->multiout.dac_nids = spec->dacs; + spec->multiout.num_dacs = n; + spec->multiout.max_channels = n * 2; +} + +static void parse_hp_out(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + unsigned int def_conf; + hda_nid_t nid, dac; + + if (!cfg->hp_outs) + return; + nid = cfg->hp_pins[0]; + def_conf = snd_hda_codec_get_pincfg(codec, nid); + if (!def_conf) { + cfg->hp_outs = 0; + return; + } + if (snd_hda_get_connections(codec, nid, &dac, 1) != 1) + return; + + for (i = 0; i < cfg->line_outs; i++) + if (dac == spec->dacs[i]) + break; + if (i >= cfg->line_outs) { + spec->hp_dac = dac; + spec->multiout.hp_nid = dac; + } +} + +static void parse_input(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + hda_nid_t nid, pin; + int n, i, j; + + n = 0; + nid = codec->start_nid; + for (i = 0; i < codec->num_nodes; i++, nid++) { + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int type = (wcaps & AC_WCAP_TYPE) >> + AC_WCAP_TYPE_SHIFT; + if (type != AC_WID_AUD_IN) + continue; + if (snd_hda_get_connections(codec, nid, &pin, 1) != 1) + continue; + if (pin == cfg->dig_in_pin) { + spec->dig_in = nid; + continue; + } + for (j = 0; j < AUTO_PIN_LAST; j++) + if (cfg->input_pins[j] == pin) + break; + if (j >= AUTO_PIN_LAST) + continue; + spec->input_pins[n] = pin; + spec->input_labels[n] = auto_pin_cfg_labels[j]; + spec->adcs[n] = nid; + n++; + } + spec->num_inputs = n; +} + +static void parse_digital(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + + if (cfg->dig_outs && + snd_hda_get_connections(codec, cfg->dig_out_pins[0], + &spec->dig_out, 1) == 1) + spec->multiout.dig_out_nid = cfg->dig_out_pins[0]; +} + +static int ca0110_parse_auto_config(struct hda_codec *codec) +{ + struct ca0110_spec *spec = codec->spec; + int err; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); + if (err < 0) + return err; + + parse_line_outs(codec); + parse_hp_out(codec); + parse_digital(codec); + parse_input(codec); + return 0; +} + + +int patch_ca0110(struct hda_codec *codec) +{ + struct ca0110_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + + codec->bus->needs_damn_long_delay = 1; + + err = ca0110_parse_auto_config(codec); + if (err < 0) + goto error; + + codec->patch_ops = ca0110_patch_ops; + + return 0; + + error: + kfree(codec->spec); + codec->spec = NULL; + return err; +} + + +/* + * patch entries + */ +static struct hda_codec_preset snd_hda_preset_ca0110[] = { + { .id = 0x1102000a, .name = "CA0110-IBG", .patch = patch_ca0110 }, + { .id = 0x1102000b, .name = "CA0110-IBG", .patch = patch_ca0110 }, + { .id = 0x1102000d, .name = "SB0880 X-Fi", .patch = patch_ca0110 }, + {} /* terminator */ +}; + +MODULE_ALIAS("snd-hda-codec-id:1102000a"); +MODULE_ALIAS("snd-hda-codec-id:1102000b"); +MODULE_ALIAS("snd-hda-codec-id:1102000d"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec"); + +static struct hda_codec_preset_list ca0110_list = { + .preset = snd_hda_preset_ca0110, + .owner = THIS_MODULE, +}; + +static int __init patch_ca0110_init(void) +{ + return snd_hda_add_codec_preset(&ca0110_list); +} + +static void __exit patch_ca0110_exit(void) +{ + snd_hda_delete_codec_preset(&ca0110_list); +} + +module_init(patch_ca0110_init) +module_exit(patch_ca0110_exit) diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 56ce19e68cb..4fcbe21829a 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -1848,6 +1848,7 @@ static const char *cxt5051_models[CXT5051_MODELS] = { static struct snd_pci_quirk cxt5051_cfg_tbl[] = { SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736), + SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP), SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board", CXT5051_LAPTOP), SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP), diff --git a/sound/pci/hda/patch_nvhdmi.c b/sound/pci/hda/patch_nvhdmi.c index d57d8132a06..f5792e2eea8 100644 --- a/sound/pci/hda/patch_nvhdmi.c +++ b/sound/pci/hda/patch_nvhdmi.c @@ -35,9 +35,28 @@ struct nvhdmi_spec { struct hda_pcm pcm_rec; }; +#define Nv_VERB_SET_Channel_Allocation 0xF79 +#define Nv_VERB_SET_Info_Frame_Checksum 0xF7A +#define Nv_VERB_SET_Audio_Protection_On 0xF98 +#define Nv_VERB_SET_Audio_Protection_Off 0xF99 + +#define Nv_Master_Convert_nid 0x04 +#define Nv_Master_Pin_nid 0x05 + +static hda_nid_t nvhdmi_convert_nids[4] = { + /*front, rear, clfe, rear_surr */ + 0x6, 0x8, 0xa, 0xc, +}; + static struct hda_verb nvhdmi_basic_init[] = { + /* set audio protect on */ + { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1}, /* enable digital output on pin widget */ - { 0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, {} /* terminator */ }; @@ -66,48 +85,205 @@ static int nvhdmi_init(struct hda_codec *codec) * Digital out */ static int nvhdmi_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) + struct hda_codec *codec, + struct snd_pcm_substream *substream) { struct nvhdmi_spec *spec = codec->spec; return snd_hda_multi_out_dig_open(codec, &spec->multiout); } -static int nvhdmi_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) +static int nvhdmi_dig_playback_pcm_close_8ch(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) { struct nvhdmi_spec *spec = codec->spec; + int i; + + snd_hda_codec_write(codec, Nv_Master_Convert_nid, + 0, AC_VERB_SET_CHANNEL_STREAMID, 0); + for (i = 0; i < 4; i++) { + /* set the stream id */ + snd_hda_codec_write(codec, nvhdmi_convert_nids[i], 0, + AC_VERB_SET_CHANNEL_STREAMID, 0); + /* set the stream format */ + snd_hda_codec_write(codec, nvhdmi_convert_nids[i], 0, + AC_VERB_SET_STREAM_FORMAT, 0); + } + return snd_hda_multi_out_dig_close(codec, &spec->multiout); } -static int nvhdmi_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +static int nvhdmi_dig_playback_pcm_close_2ch(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct nvhdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int nvhdmi_dig_playback_pcm_prepare_8ch(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + int chs; + unsigned int dataDCC1, dataDCC2, chan, chanmask, channel_id; + int i; + + mutex_lock(&codec->spdif_mutex); + + chs = substream->runtime->channels; + chan = chs ? (chs - 1) : 1; + + switch (chs) { + default: + case 0: + case 2: + chanmask = 0x00; + break; + case 4: + chanmask = 0x08; + break; + case 6: + chanmask = 0x0b; + break; + case 8: + chanmask = 0x13; + break; + } + dataDCC1 = AC_DIG1_ENABLE | AC_DIG1_COPYRIGHT; + dataDCC2 = 0x2; + + /* set the Audio InforFrame Channel Allocation */ + snd_hda_codec_write(codec, 0x1, 0, + Nv_VERB_SET_Channel_Allocation, chanmask); + + /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */ + if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) + snd_hda_codec_write(codec, + Nv_Master_Convert_nid, + 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff); + + /* set the stream id */ + snd_hda_codec_write(codec, Nv_Master_Convert_nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0); + + /* set the stream format */ + snd_hda_codec_write(codec, Nv_Master_Convert_nid, 0, + AC_VERB_SET_STREAM_FORMAT, format); + + /* turn on again (if needed) */ + /* enable and set the channel status audio/data flag */ + if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) { + snd_hda_codec_write(codec, + Nv_Master_Convert_nid, + 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & 0xff); + snd_hda_codec_write(codec, + Nv_Master_Convert_nid, + 0, + AC_VERB_SET_DIGI_CONVERT_2, dataDCC2); + } + + for (i = 0; i < 4; i++) { + if (chs == 2) + channel_id = 0; + else + channel_id = i * 2; + + /* turn off SPDIF once; + *otherwise the IEC958 bits won't be updated + */ + if (codec->spdif_status_reset && + (codec->spdif_ctls & AC_DIG1_ENABLE)) + snd_hda_codec_write(codec, + nvhdmi_convert_nids[i], + 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff); + /* set the stream id */ + snd_hda_codec_write(codec, + nvhdmi_convert_nids[i], + 0, + AC_VERB_SET_CHANNEL_STREAMID, + (stream_tag << 4) | channel_id); + /* set the stream format */ + snd_hda_codec_write(codec, + nvhdmi_convert_nids[i], + 0, + AC_VERB_SET_STREAM_FORMAT, + format); + /* turn on again (if needed) */ + /* enable and set the channel status audio/data flag */ + if (codec->spdif_status_reset && + (codec->spdif_ctls & AC_DIG1_ENABLE)) { + snd_hda_codec_write(codec, + nvhdmi_convert_nids[i], + 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & 0xff); + snd_hda_codec_write(codec, + nvhdmi_convert_nids[i], + 0, + AC_VERB_SET_DIGI_CONVERT_2, dataDCC2); + } + } + + /* set the Audio Info Frame Checksum */ + snd_hda_codec_write(codec, 0x1, 0, + Nv_VERB_SET_Info_Frame_Checksum, + (0x71 - chan - chanmask)); + + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +static int nvhdmi_dig_playback_pcm_prepare_2ch(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) { struct nvhdmi_spec *spec = codec->spec; return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, - format, substream); + format, substream); } -static struct hda_pcm_stream nvhdmi_pcm_digital_playback = { +static struct hda_pcm_stream nvhdmi_pcm_digital_playback_8ch = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .nid = Nv_Master_Convert_nid, + .rates = SNDRV_PCM_RATE_48000, + .maxbps = 16, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .ops = { + .open = nvhdmi_dig_playback_pcm_open, + .close = nvhdmi_dig_playback_pcm_close_8ch, + .prepare = nvhdmi_dig_playback_pcm_prepare_8ch + }, +}; + +static struct hda_pcm_stream nvhdmi_pcm_digital_playback_2ch = { .substreams = 1, .channels_min = 2, .channels_max = 2, - .nid = 0x4, /* NID to query formats and rates and setup streams */ + .nid = Nv_Master_Convert_nid, .rates = SNDRV_PCM_RATE_48000, .maxbps = 16, .formats = SNDRV_PCM_FMTBIT_S16_LE, .ops = { .open = nvhdmi_dig_playback_pcm_open, - .close = nvhdmi_dig_playback_pcm_close, - .prepare = nvhdmi_dig_playback_pcm_prepare + .close = nvhdmi_dig_playback_pcm_close_2ch, + .prepare = nvhdmi_dig_playback_pcm_prepare_2ch }, }; -static int nvhdmi_build_pcms(struct hda_codec *codec) +static int nvhdmi_build_pcms_8ch(struct hda_codec *codec) { struct nvhdmi_spec *spec = codec->spec; struct hda_pcm *info = &spec->pcm_rec; @@ -117,7 +293,24 @@ static int nvhdmi_build_pcms(struct hda_codec *codec) info->name = "NVIDIA HDMI"; info->pcm_type = HDA_PCM_TYPE_HDMI; - info->stream[SNDRV_PCM_STREAM_PLAYBACK] = nvhdmi_pcm_digital_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] + = nvhdmi_pcm_digital_playback_8ch; + + return 0; +} + +static int nvhdmi_build_pcms_2ch(struct hda_codec *codec) +{ + struct nvhdmi_spec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "NVIDIA HDMI"; + info->pcm_type = HDA_PCM_TYPE_HDMI; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] + = nvhdmi_pcm_digital_playback_2ch; return 0; } @@ -127,14 +320,40 @@ static void nvhdmi_free(struct hda_codec *codec) kfree(codec->spec); } -static struct hda_codec_ops nvhdmi_patch_ops = { +static struct hda_codec_ops nvhdmi_patch_ops_8ch = { + .build_controls = nvhdmi_build_controls, + .build_pcms = nvhdmi_build_pcms_8ch, + .init = nvhdmi_init, + .free = nvhdmi_free, +}; + +static struct hda_codec_ops nvhdmi_patch_ops_2ch = { .build_controls = nvhdmi_build_controls, - .build_pcms = nvhdmi_build_pcms, + .build_pcms = nvhdmi_build_pcms_2ch, .init = nvhdmi_init, .free = nvhdmi_free, }; -static int patch_nvhdmi(struct hda_codec *codec) +static int patch_nvhdmi_8ch(struct hda_codec *codec) +{ + struct nvhdmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 8; + spec->multiout.dig_out_nid = Nv_Master_Convert_nid; + + codec->patch_ops = nvhdmi_patch_ops_8ch; + + return 0; +} + +static int patch_nvhdmi_2ch(struct hda_codec *codec) { struct nvhdmi_spec *spec; @@ -144,13 +363,11 @@ static int patch_nvhdmi(struct hda_codec *codec) codec->spec = spec; - spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.num_dacs = 0; /* no analog */ spec->multiout.max_channels = 2; - spec->multiout.dig_out_nid = 0x4; /* NID for copying analog to digital, - * seems to be unused in pure-digital - * case. */ + spec->multiout.dig_out_nid = Nv_Master_Convert_nid; - codec->patch_ops = nvhdmi_patch_ops; + codec->patch_ops = nvhdmi_patch_ops_2ch; return 0; } @@ -159,11 +376,11 @@ static int patch_nvhdmi(struct hda_codec *codec) * patch entries */ static struct hda_codec_preset snd_hda_preset_nvhdmi[] = { - { .id = 0x10de0002, .name = "MCP78 HDMI", .patch = patch_nvhdmi }, - { .id = 0x10de0006, .name = "MCP78 HDMI", .patch = patch_nvhdmi }, - { .id = 0x10de0007, .name = "MCP7A HDMI", .patch = patch_nvhdmi }, - { .id = 0x10de0067, .name = "MCP67 HDMI", .patch = patch_nvhdmi }, - { .id = 0x10de8001, .name = "MCP73 HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0002, .name = "MCP78 HDMI", .patch = patch_nvhdmi_8ch }, + { .id = 0x10de0006, .name = "MCP78 HDMI", .patch = patch_nvhdmi_8ch }, + { .id = 0x10de0007, .name = "MCP7A HDMI", .patch = patch_nvhdmi_8ch }, + { .id = 0x10de0067, .name = "MCP67 HDMI", .patch = patch_nvhdmi_2ch }, + { .id = 0x10de8001, .name = "MCP73 HDMI", .patch = patch_nvhdmi_2ch }, {} /* terminator */ }; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index b8a0d3e7927..bf4b78a74a8 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -190,6 +190,7 @@ enum { ALC663_ASUS_MODE6, ALC272_DELL, ALC272_DELL_ZM1, + ALC272_SAMSUNG_NC10, ALC662_AUTO, ALC662_MODEL_LAST, }; @@ -205,6 +206,7 @@ enum { ALC882_ASUS_A7M, ALC885_MACPRO, ALC885_MBP3, + ALC885_MB5, ALC885_IMAC24, ALC882_AUTO, ALC882_MODEL_LAST, @@ -218,9 +220,12 @@ enum { ALC883_6ST_DIG, ALC883_TARGA_DIG, ALC883_TARGA_2ch_DIG, + ALC883_TARGA_8ch_DIG, ALC883_ACER, ALC883_ACER_ASPIRE, ALC888_ACER_ASPIRE_4930G, + ALC888_ACER_ASPIRE_6530G, + ALC888_ACER_ASPIRE_8930G, ALC883_MEDION, ALC883_MEDION_MD2, ALC883_LAPTOP_EAPD, @@ -238,7 +243,9 @@ enum { ALC883_3ST_6ch_INTEL, ALC888_ASUS_M90V, ALC888_ASUS_EEE1601, + ALC889A_MB31, ALC1200_ASUS_P5Q, + ALC883_SONY_VAIO_TT, ALC883_AUTO, ALC883_MODEL_LAST, }; @@ -253,6 +260,15 @@ enum { /* for GPIO Poll */ #define GPIO_MASK 0x03 +/* extra amp-initialization sequence types */ +enum { + ALC_INIT_NONE, + ALC_INIT_DEFAULT, + ALC_INIT_GPIO1, + ALC_INIT_GPIO2, + ALC_INIT_GPIO3, +}; + struct alc_spec { /* codec parameterization */ struct snd_kcontrol_new *mixers[5]; /* mixer arrays */ @@ -266,13 +282,13 @@ struct alc_spec { */ unsigned int num_init_verbs; - char *stream_name_analog; /* analog PCM stream */ + char stream_name_analog[16]; /* analog PCM stream */ struct hda_pcm_stream *stream_analog_playback; struct hda_pcm_stream *stream_analog_capture; struct hda_pcm_stream *stream_analog_alt_playback; struct hda_pcm_stream *stream_analog_alt_capture; - char *stream_name_digital; /* digital PCM stream */ + char stream_name_digital[16]; /* digital PCM stream */ struct hda_pcm_stream *stream_digital_playback; struct hda_pcm_stream *stream_digital_capture; @@ -301,6 +317,8 @@ struct alc_spec { const struct hda_channel_mode *channel_mode; int num_channel_mode; int need_dac_fix; + int const_channel_count; + int ext_channel_count; /* PCM information */ struct hda_pcm pcm_rec[3]; /* used in alc_build_pcms() */ @@ -322,6 +340,7 @@ struct alc_spec { /* other flags */ unsigned int no_analog :1; /* digital I/O only */ + int init_amp; /* for virtual master */ hda_nid_t vmaster_nid; @@ -355,6 +374,7 @@ struct alc_config_preset { unsigned int num_channel_mode; const struct hda_channel_mode *channel_mode; int need_dac_fix; + int const_channel_count; unsigned int num_mux_defs; const struct hda_input_mux *input_mux; void (*unsol_event)(struct hda_codec *, unsigned int); @@ -449,7 +469,7 @@ static int alc_ch_mode_get(struct snd_kcontrol *kcontrol, struct alc_spec *spec = codec->spec; return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode, spec->num_channel_mode, - spec->multiout.max_channels); + spec->ext_channel_count); } static int alc_ch_mode_put(struct snd_kcontrol *kcontrol, @@ -459,9 +479,12 @@ static int alc_ch_mode_put(struct snd_kcontrol *kcontrol, struct alc_spec *spec = codec->spec; int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode, spec->num_channel_mode, - &spec->multiout.max_channels); - if (err >= 0 && spec->need_dac_fix) - spec->multiout.num_dacs = spec->multiout.max_channels / 2; + &spec->ext_channel_count); + if (err >= 0 && !spec->const_channel_count) { + spec->multiout.max_channels = spec->ext_channel_count; + if (spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; + } return err; } @@ -776,6 +799,12 @@ static void alc_set_input_pin(struct hda_codec *codec, hda_nid_t nid, pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; if (pincap & AC_PINCAP_VREF_80) val = PIN_VREF80; + else if (pincap & AC_PINCAP_VREF_50) + val = PIN_VREF50; + else if (pincap & AC_PINCAP_VREF_100) + val = PIN_VREF100; + else if (pincap & AC_PINCAP_VREF_GRD) + val = PIN_VREFGRD; } snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, val); } @@ -835,8 +864,13 @@ static void setup_preset(struct alc_spec *spec, spec->channel_mode = preset->channel_mode; spec->num_channel_mode = preset->num_channel_mode; spec->need_dac_fix = preset->need_dac_fix; + spec->const_channel_count = preset->const_channel_count; - spec->multiout.max_channels = spec->channel_mode[0].channels; + if (preset->const_channel_count) + spec->multiout.max_channels = preset->const_channel_count; + else + spec->multiout.max_channels = spec->channel_mode[0].channels; + spec->ext_channel_count = spec->channel_mode[0].channels; spec->multiout.num_dacs = preset->num_dacs; spec->multiout.dac_nids = preset->dac_nids; @@ -915,23 +949,29 @@ static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid, alc_fix_pll(codec); } -static void alc_sku_automute(struct hda_codec *codec) +static void alc_automute_pin(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; unsigned int present; - unsigned int hp_nid = spec->autocfg.hp_pins[0]; - unsigned int sp_nid = spec->autocfg.speaker_pins[0]; + unsigned int nid = spec->autocfg.hp_pins[0]; + int i; /* need to execute and sync at first */ - snd_hda_codec_read(codec, hp_nid, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, hp_nid, 0, + snd_hda_codec_read(codec, nid, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_SENSE, 0); - spec->jack_present = (present & 0x80000000) != 0; - snd_hda_codec_write(codec, sp_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - spec->jack_present ? 0 : PIN_OUT); + spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; + for (i = 0; i < ARRAY_SIZE(spec->autocfg.speaker_pins); i++) { + nid = spec->autocfg.speaker_pins[i]; + if (!nid) + break; + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + spec->jack_present ? 0 : PIN_OUT); + } } -#if 0 /* it's broken in some acses -- temporarily disabled */ +#if 0 /* it's broken in some cases -- temporarily disabled */ static void alc_mic_automute(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; @@ -963,16 +1003,19 @@ static void alc_sku_unsol_event(struct hda_codec *codec, unsigned int res) res >>= 28; else res >>= 26; - if (res == ALC880_HP_EVENT) - alc_sku_automute(codec); - - if (res == ALC880_MIC_EVENT) + switch (res) { + case ALC880_HP_EVENT: + alc_automute_pin(codec); + break; + case ALC880_MIC_EVENT: alc_mic_automute(codec); + break; + } } static void alc_inithook(struct hda_codec *codec) { - alc_sku_automute(codec); + alc_automute_pin(codec); alc_mic_automute(codec); } @@ -994,69 +1037,21 @@ static void alc888_coef_init(struct hda_codec *codec) AC_VERB_SET_PROC_COEF, 0x3030); } -/* 32-bit subsystem ID for BIOS loading in HD Audio codec. - * 31 ~ 16 : Manufacture ID - * 15 ~ 8 : SKU ID - * 7 ~ 0 : Assembly ID - * port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36 - */ -static void alc_subsystem_id(struct hda_codec *codec, - unsigned int porta, unsigned int porte, - unsigned int portd) +static void alc_auto_init_amp(struct hda_codec *codec, int type) { - unsigned int ass, tmp, i; - unsigned nid; - struct alc_spec *spec = codec->spec; - - ass = codec->subsystem_id & 0xffff; - if ((ass != codec->bus->pci->subsystem_device) && (ass & 1)) - goto do_sku; - - /* - * 31~30 : port conetcivity - * 29~21 : reserve - * 20 : PCBEEP input - * 19~16 : Check sum (15:1) - * 15~1 : Custom - * 0 : override - */ - nid = 0x1d; - if (codec->vendor_id == 0x10ec0260) - nid = 0x17; - ass = snd_hda_codec_get_pincfg(codec, nid); - if (!(ass & 1) && !(ass & 0x100000)) - return; - if ((ass >> 30) != 1) /* no physical connection */ - return; + unsigned int tmp; - /* check sum */ - tmp = 0; - for (i = 1; i < 16; i++) { - if ((ass >> i) & 1) - tmp++; - } - if (((ass >> 16) & 0xf) != tmp) - return; -do_sku: - /* - * 0 : override - * 1 : Swap Jack - * 2 : 0 --> Desktop, 1 --> Laptop - * 3~5 : External Amplifier control - * 7~6 : Reserved - */ - tmp = (ass & 0x38) >> 3; /* external Amp control */ - switch (tmp) { - case 1: + switch (type) { + case ALC_INIT_GPIO1: snd_hda_sequence_write(codec, alc_gpio1_init_verbs); break; - case 3: + case ALC_INIT_GPIO2: snd_hda_sequence_write(codec, alc_gpio2_init_verbs); break; - case 7: + case ALC_INIT_GPIO3: snd_hda_sequence_write(codec, alc_gpio3_init_verbs); break; - case 5: /* set EAPD output high */ + case ALC_INIT_DEFAULT: switch (codec->vendor_id) { case 0x10ec0260: snd_hda_codec_write(codec, 0x0f, 0, @@ -1110,7 +1105,7 @@ do_sku: tmp | 0x2010); break; case 0x10ec0888: - /*alc888_coef_init(codec);*/ /* called in alc_init() */ + alc888_coef_init(codec); break; case 0x10ec0267: case 0x10ec0268: @@ -1125,7 +1120,107 @@ do_sku: tmp | 0x3000); break; } - default: + break; + } +} + +static void alc_init_auto_hp(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->autocfg.hp_pins[0]) + return; + + if (!spec->autocfg.speaker_pins[0]) { + if (spec->autocfg.line_out_pins[0] && + spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) + spec->autocfg.speaker_pins[0] = + spec->autocfg.line_out_pins[0]; + else + return; + } + + snd_printdd("realtek: Enable HP auto-muting on NID 0x%x\n", + spec->autocfg.hp_pins[0]); + snd_hda_codec_write_cache(codec, spec->autocfg.hp_pins[0], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + AC_USRSP_EN | ALC880_HP_EVENT); + spec->unsol_event = alc_sku_unsol_event; +} + +/* check subsystem ID and set up device-specific initialization; + * return 1 if initialized, 0 if invalid SSID + */ +/* 32-bit subsystem ID for BIOS loading in HD Audio codec. + * 31 ~ 16 : Manufacture ID + * 15 ~ 8 : SKU ID + * 7 ~ 0 : Assembly ID + * port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36 + */ +static int alc_subsystem_id(struct hda_codec *codec, + hda_nid_t porta, hda_nid_t porte, + hda_nid_t portd) +{ + unsigned int ass, tmp, i; + unsigned nid; + struct alc_spec *spec = codec->spec; + + ass = codec->subsystem_id & 0xffff; + if ((ass != codec->bus->pci->subsystem_device) && (ass & 1)) + goto do_sku; + + /* invalid SSID, check the special NID pin defcfg instead */ + /* + * 31~30 : port connectivity + * 29~21 : reserve + * 20 : PCBEEP input + * 19~16 : Check sum (15:1) + * 15~1 : Custom + * 0 : override + */ + nid = 0x1d; + if (codec->vendor_id == 0x10ec0260) + nid = 0x17; + ass = snd_hda_codec_get_pincfg(codec, nid); + snd_printd("realtek: No valid SSID, " + "checking pincfg 0x%08x for NID 0x%x\n", + ass, nid); + if (!(ass & 1) && !(ass & 0x100000)) + return 0; + if ((ass >> 30) != 1) /* no physical connection */ + return 0; + + /* check sum */ + tmp = 0; + for (i = 1; i < 16; i++) { + if ((ass >> i) & 1) + tmp++; + } + if (((ass >> 16) & 0xf) != tmp) + return 0; +do_sku: + snd_printd("realtek: Enabling init ASM_ID=0x%04x CODEC_ID=%08x\n", + ass & 0xffff, codec->vendor_id); + /* + * 0 : override + * 1 : Swap Jack + * 2 : 0 --> Desktop, 1 --> Laptop + * 3~5 : External Amplifier control + * 7~6 : Reserved + */ + tmp = (ass & 0x38) >> 3; /* external Amp control */ + switch (tmp) { + case 1: + spec->init_amp = ALC_INIT_GPIO1; + break; + case 3: + spec->init_amp = ALC_INIT_GPIO2; + break; + case 7: + spec->init_amp = ALC_INIT_GPIO3; + break; + case 5: + spec->init_amp = ALC_INIT_DEFAULT; break; } @@ -1133,7 +1228,7 @@ do_sku: * when the external headphone out jack is plugged" */ if (!(ass & 0x8000)) - return; + return 1; /* * 10~8 : Jack location * 12~11: Headphone out -> 00: PortA, 01: PortE, 02: PortD, 03: Resvered @@ -1141,14 +1236,6 @@ do_sku: * 15 : 1 --> enable the function "Mute internal speaker * when the external headphone out jack is plugged" */ - if (!spec->autocfg.speaker_pins[0]) { - if (spec->autocfg.line_out_pins[0]) - spec->autocfg.speaker_pins[0] = - spec->autocfg.line_out_pins[0]; - else - return; - } - if (!spec->autocfg.hp_pins[0]) { tmp = (ass >> 11) & 0x3; /* HP to chassis */ if (tmp == 0) @@ -1158,23 +1245,23 @@ do_sku: else if (tmp == 2) spec->autocfg.hp_pins[0] = portd; else - return; + return 1; } - if (spec->autocfg.hp_pins[0]) - snd_hda_codec_write(codec, spec->autocfg.hp_pins[0], 0, - AC_VERB_SET_UNSOLICITED_ENABLE, - AC_USRSP_EN | ALC880_HP_EVENT); - -#if 0 /* it's broken in some acses -- temporarily disabled */ - if (spec->autocfg.input_pins[AUTO_PIN_MIC] && - spec->autocfg.input_pins[AUTO_PIN_FRONT_MIC]) - snd_hda_codec_write(codec, - spec->autocfg.input_pins[AUTO_PIN_MIC], 0, - AC_VERB_SET_UNSOLICITED_ENABLE, - AC_USRSP_EN | ALC880_MIC_EVENT); -#endif /* disabled */ - spec->unsol_event = alc_sku_unsol_event; + alc_init_auto_hp(codec); + return 1; +} + +static void alc_ssid_check(struct hda_codec *codec, + hda_nid_t porta, hda_nid_t porte, hda_nid_t portd) +{ + if (!alc_subsystem_id(codec, porta, porte, portd)) { + struct alc_spec *spec = codec->spec; + snd_printd("realtek: " + "Enable default setup for auto mode as fallback\n"); + spec->init_amp = ALC_INIT_DEFAULT; + alc_init_auto_hp(codec); + } } /* @@ -1309,32 +1396,58 @@ static struct hda_verb alc888_fujitsu_xa3530_verbs[] = { {} }; -static void alc888_fujitsu_xa3530_automute(struct hda_codec *codec) +static void alc_automute_amp(struct hda_codec *codec) { - unsigned int present; - unsigned int bits; - /* Line out presence */ - present = snd_hda_codec_read(codec, 0x17, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - /* HP out presence */ - present = present || snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; + struct alc_spec *spec = codec->spec; + unsigned int val, mute; + hda_nid_t nid; + int i; + + spec->jack_present = 0; + for (i = 0; i < ARRAY_SIZE(spec->autocfg.hp_pins); i++) { + nid = spec->autocfg.hp_pins[i]; + if (!nid) + break; + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_SENSE, 0); + if (val & AC_PINSENSE_PRESENCE) { + spec->jack_present = 1; + break; + } + } + + mute = spec->jack_present ? HDA_AMP_MUTE : 0; /* Toggle internal speakers muting */ - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - /* Toggle internal bass muting */ - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); + for (i = 0; i < ARRAY_SIZE(spec->autocfg.speaker_pins); i++) { + nid = spec->autocfg.speaker_pins[i]; + if (!nid) + break; + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } } -static void alc888_fujitsu_xa3530_unsol_event(struct hda_codec *codec, - unsigned int res) +static void alc_automute_amp_unsol_event(struct hda_codec *codec, + unsigned int res) { - if (res >> 26 == ALC880_HP_EVENT) - alc888_fujitsu_xa3530_automute(codec); + if (codec->vendor_id == 0x10ec0880) + res >>= 28; + else + res >>= 26; + if (res == ALC880_HP_EVENT) + alc_automute_amp(codec); } +static void alc888_fujitsu_xa3530_init_hook(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x17; /* line-out */ + spec->autocfg.hp_pins[1] = 0x1b; /* hp */ + spec->autocfg.speaker_pins[0] = 0x14; /* speaker */ + spec->autocfg.speaker_pins[1] = 0x15; /* bass */ + alc_automute_amp(codec); +} /* * ALC888 Acer Aspire 4930G model @@ -1358,6 +1471,78 @@ static struct hda_verb alc888_acer_aspire_4930g_verbs[] = { { } }; +/* + * ALC888 Acer Aspire 6530G model + */ + +static struct hda_verb alc888_acer_aspire_6530g_verbs[] = { +/* Bias voltage on for external mic port */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN | PIN_VREF80}, +/* Enable unsolicited event for HP jack */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, +/* Enable speaker output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, +/* Enable headphone output */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + { } +}; + +/* + * ALC889 Acer Aspire 8930G model + */ + +static struct hda_verb alc889_acer_aspire_8930g_verbs[] = { +/* Front Mic: set to PIN_IN (empty by default) */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, +/* Unselect Front Mic by default in input mixer 3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0xb)}, +/* Enable unsolicited event for HP jack */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, +/* Connect Internal Front to Front */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, +/* Connect Internal Rear to Rear */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x01}, +/* Connect Internal CLFE to CLFE */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x02}, +/* Connect HP out to Front */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, +/* Enable all DACs */ +/* DAC DISABLE/MUTE 1? */ +/* setting bits 1-5 disables DAC nids 0x02-0x06 apparently. Init=0x38 */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x03}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0000}, +/* DAC DISABLE/MUTE 2? */ +/* some bit here disables the other DACs. Init=0x4900 */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x08}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0000}, +/* Enable amplifiers */ + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, +/* DMIC fix + * This laptop has a stereo digital microphone. The mics are only 1cm apart + * which makes the stereo useless. However, either the mic or the ALC889 + * makes the signal become a difference/sum signal instead of standard + * stereo, which is annoying. So instead we flip this bit which makes the + * codec replicate the sum signal to both channels, turning it into a + * normal mono mic. + */ +/* DMIC_CONTROL? Init value = 0x0001 */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x0b}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0003}, + { } +}; + static struct hda_input_mux alc888_2_capture_sources[2] = { /* Front mic only available on one ADC */ { @@ -1379,6 +1564,57 @@ static struct hda_input_mux alc888_2_capture_sources[2] = { } }; +static struct hda_input_mux alc888_acer_aspire_6530_sources[2] = { + /* Interal mic only available on one ADC */ + { + .num_items = 3, + .items = { + { "Ext Mic", 0x0 }, + { "CD", 0x4 }, + { "Int Mic", 0xb }, + }, + }, + { + .num_items = 2, + .items = { + { "Ext Mic", 0x0 }, + { "CD", 0x4 }, + }, + } +}; + +static struct hda_input_mux alc889_capture_sources[3] = { + /* Digital mic only available on first "ADC" */ + { + .num_items = 5, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Front Mic", 0xb }, + { "Input Mix", 0xa }, + }, + }, + { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Input Mix", 0xa }, + }, + }, + { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Input Mix", 0xa }, + }, + } +}; + static struct snd_kcontrol_new alc888_base_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), @@ -1401,22 +1637,24 @@ static struct snd_kcontrol_new alc888_base_mixer[] = { { } /* end */ }; -static void alc888_acer_aspire_4930g_automute(struct hda_codec *codec) +static void alc888_acer_aspire_4930g_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned int bits; - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); } -static void alc888_acer_aspire_4930g_unsol_event(struct hda_codec *codec, - unsigned int res) +static void alc889_acer_aspire_8930g_init_hook(struct hda_codec *codec) { - if (res >> 26 == ALC880_HP_EVENT) - alc888_acer_aspire_4930g_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x16; + spec->autocfg.speaker_pins[2] = 0x1b; + alc_automute_amp(codec); } /* @@ -2384,21 +2622,6 @@ static struct hda_verb alc880_beep_init_verbs[] = { { } }; -/* toggle speaker-output according to the hp-jack state */ -static void alc880_uniwill_hp_automute(struct hda_codec *codec) -{ - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} - /* auto-toggle front mic */ static void alc880_uniwill_mic_automute(struct hda_codec *codec) { @@ -2411,9 +2634,14 @@ static void alc880_uniwill_mic_automute(struct hda_codec *codec) snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, HDA_AMP_MUTE, bits); } -static void alc880_uniwill_automute(struct hda_codec *codec) +static void alc880_uniwill_init_hook(struct hda_codec *codec) { - alc880_uniwill_hp_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x16; + alc_automute_amp(codec); alc880_uniwill_mic_automute(codec); } @@ -2424,24 +2652,22 @@ static void alc880_uniwill_unsol_event(struct hda_codec *codec, * definition. 4bit tag is placed at 28 bit! */ switch (res >> 28) { - case ALC880_HP_EVENT: - alc880_uniwill_hp_automute(codec); - break; case ALC880_MIC_EVENT: alc880_uniwill_mic_automute(codec); break; + default: + alc_automute_amp_unsol_event(codec, res); + break; } } -static void alc880_uniwill_p53_hp_automute(struct hda_codec *codec) +static void alc880_uniwill_p53_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, HDA_AMP_MUTE, bits); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x15; + alc_automute_amp(codec); } static void alc880_uniwill_p53_dcvol_automute(struct hda_codec *codec) @@ -2463,10 +2689,10 @@ static void alc880_uniwill_p53_unsol_event(struct hda_codec *codec, /* Looks like the unsol event is incompatible with the standard * definition. 4bit tag is placed at 28 bit! */ - if ((res >> 28) == ALC880_HP_EVENT) - alc880_uniwill_p53_hp_automute(codec); if ((res >> 28) == ALC880_DCVOL_EVENT) alc880_uniwill_p53_dcvol_automute(codec); + else + alc_automute_amp_unsol_event(codec, res); } /* @@ -2536,6 +2762,7 @@ static struct hda_verb alc880_pin_asus_init_verbs[] = { /* Enable GPIO mask and set output */ #define alc880_gpio1_init_verbs alc_gpio1_init_verbs #define alc880_gpio2_init_verbs alc_gpio2_init_verbs +#define alc880_gpio3_init_verbs alc_gpio3_init_verbs /* Clevo m520g init */ static struct hda_verb alc880_pin_clevo_init_verbs[] = { @@ -2698,30 +2925,18 @@ static struct hda_verb alc880_lg_init_verbs[] = { {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* jack sense */ - {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | 0x1}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, { } }; /* toggle speaker-output according to the hp-jack state */ -static void alc880_lg_automute(struct hda_codec *codec) +static void alc880_lg_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} + struct alc_spec *spec = codec->spec; -static void alc880_lg_unsol_event(struct hda_codec *codec, unsigned int res) -{ - /* Looks like the unsol event is incompatible with the standard - * definition. 4bit tag is placed at 28 bit! - */ - if ((res >> 28) == 0x01) - alc880_lg_automute(codec); + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x17; + alc_automute_amp(codec); } /* @@ -2795,30 +3010,18 @@ static struct hda_verb alc880_lg_lw_init_verbs[] = { {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* jack sense */ - {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | 0x1}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, { } }; /* toggle speaker-output according to the hp-jack state */ -static void alc880_lg_lw_automute(struct hda_codec *codec) +static void alc880_lg_lw_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} - -static void alc880_lg_lw_unsol_event(struct hda_codec *codec, unsigned int res) -{ - /* Looks like the unsol event is incompatible with the standard - * definition. 4bit tag is placed at 28 bit! - */ - if ((res >> 28) == 0x01) - alc880_lg_lw_automute(codec); + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); } static struct snd_kcontrol_new alc880_medion_rim_mixer[] = { @@ -2865,16 +3068,10 @@ static struct hda_verb alc880_medion_rim_init_verbs[] = { /* toggle speaker-output according to the hp-jack state */ static void alc880_medion_rim_automute(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - if (present) + struct alc_spec *spec = codec->spec; + alc_automute_amp(codec); + /* toggle EAPD */ + if (spec->jack_present) snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0); else snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 2); @@ -2890,6 +3087,15 @@ static void alc880_medion_rim_unsol_event(struct hda_codec *codec, alc880_medion_rim_automute(codec); } +static void alc880_medion_rim_init_hook(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x1b; + alc880_medion_rim_automute(codec); +} + #ifdef CONFIG_SND_HDA_POWER_SAVE static struct hda_amp_list alc880_loopbacks[] = { { 0x0b, HDA_INPUT, 0 }, @@ -2918,8 +3124,7 @@ static int alc_init(struct hda_codec *codec) unsigned int i; alc_fix_pll(codec); - if (codec->vendor_id == 0x10ec0888) - alc888_coef_init(codec); + alc_auto_init_amp(codec, spec->init_amp); for (i = 0; i < spec->num_init_verbs; i++) snd_hda_sequence_write(codec, spec->init_verbs[i]); @@ -3121,7 +3326,10 @@ static int alc_build_pcms(struct hda_codec *codec) if (spec->no_analog) goto skip_analog; + snprintf(spec->stream_name_analog, sizeof(spec->stream_name_analog), + "%s Analog", codec->chip_name); info->name = spec->stream_name_analog; + if (spec->stream_analog_playback) { if (snd_BUG_ON(!spec->multiout.dac_nids)) return -EINVAL; @@ -3147,6 +3355,9 @@ static int alc_build_pcms(struct hda_codec *codec) skip_analog: /* SPDIF for stream index #1 */ if (spec->multiout.dig_out_nid || spec->dig_in_nid) { + snprintf(spec->stream_name_digital, + sizeof(spec->stream_name_digital), + "%s Digital", codec->chip_name); codec->num_pcms = 2; codec->slave_dig_outs = spec->multiout.slave_dig_outs; info = spec->pcm_rec + 1; @@ -3749,7 +3960,7 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_2_jack_modes, .input_mux = &alc880_f1734_capture_source, .unsol_event = alc880_uniwill_p53_unsol_event, - .init_hook = alc880_uniwill_p53_hp_automute, + .init_hook = alc880_uniwill_p53_init_hook, }, [ALC880_ASUS] = { .mixers = { alc880_asus_mixer }, @@ -3826,7 +4037,7 @@ static struct alc_config_preset alc880_presets[] = { .need_dac_fix = 1, .input_mux = &alc880_capture_source, .unsol_event = alc880_uniwill_unsol_event, - .init_hook = alc880_uniwill_automute, + .init_hook = alc880_uniwill_init_hook, }, [ALC880_UNIWILL_P53] = { .mixers = { alc880_uniwill_p53_mixer }, @@ -3838,7 +4049,7 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_threestack_modes, .input_mux = &alc880_capture_source, .unsol_event = alc880_uniwill_p53_unsol_event, - .init_hook = alc880_uniwill_p53_hp_automute, + .init_hook = alc880_uniwill_p53_init_hook, }, [ALC880_FUJITSU] = { .mixers = { alc880_fujitsu_mixer }, @@ -3852,7 +4063,7 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_2_jack_modes, .input_mux = &alc880_capture_source, .unsol_event = alc880_uniwill_p53_unsol_event, - .init_hook = alc880_uniwill_p53_hp_automute, + .init_hook = alc880_uniwill_p53_init_hook, }, [ALC880_CLEVO] = { .mixers = { alc880_three_stack_mixer }, @@ -3877,8 +4088,8 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_lg_ch_modes, .need_dac_fix = 1, .input_mux = &alc880_lg_capture_source, - .unsol_event = alc880_lg_unsol_event, - .init_hook = alc880_lg_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc880_lg_init_hook, #ifdef CONFIG_SND_HDA_POWER_SAVE .loopbacks = alc880_lg_loopbacks, #endif @@ -3893,8 +4104,8 @@ static struct alc_config_preset alc880_presets[] = { .num_channel_mode = ARRAY_SIZE(alc880_lg_lw_modes), .channel_mode = alc880_lg_lw_modes, .input_mux = &alc880_lg_lw_capture_source, - .unsol_event = alc880_lg_lw_unsol_event, - .init_hook = alc880_lg_lw_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc880_lg_lw_init_hook, }, [ALC880_MEDION_RIM] = { .mixers = { alc880_medion_rim_mixer }, @@ -3908,7 +4119,7 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_2_jack_modes, .input_mux = &alc880_medion_rim_capture_source, .unsol_event = alc880_medion_rim_unsol_event, - .init_hook = alc880_medion_rim_automute, + .init_hook = alc880_medion_rim_init_hook, }, #ifdef CONFIG_SND_DEBUG [ALC880_TEST] = { @@ -4193,7 +4404,6 @@ static void alc880_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x15, 0x1b, 0x14); for (i = 0; i < spec->autocfg.line_outs; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -4298,6 +4508,8 @@ static int alc880_parse_auto_config(struct hda_codec *codec) spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux[0]; + alc_ssid_check(codec, 0x15, 0x1b, 0x14); + return 1; } @@ -4355,8 +4567,8 @@ static int patch_alc880(struct hda_codec *codec) alc880_models, alc880_cfg_tbl); if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC880, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC880_AUTO; } @@ -4383,12 +4595,10 @@ static int patch_alc880(struct hda_codec *codec) if (board_config != ALC880_AUTO) setup_preset(spec, &alc880_presets[board_config]); - spec->stream_name_analog = "ALC880 Analog"; spec->stream_analog_playback = &alc880_pcm_analog_playback; spec->stream_analog_capture = &alc880_pcm_analog_capture; spec->stream_analog_alt_capture = &alc880_pcm_analog_alt_capture; - spec->stream_name_digital = "ALC880 Digital"; spec->stream_digital_playback = &alc880_pcm_digital_playback; spec->stream_digital_capture = &alc880_pcm_digital_capture; @@ -5673,7 +5883,6 @@ static void alc260_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; hda_nid_t nid; - alc_subsystem_id(codec, 0x10, 0x15, 0x0f); nid = spec->autocfg.line_out_pins[0]; if (nid) { int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -5783,6 +5992,8 @@ static int alc260_parse_auto_config(struct hda_codec *codec) spec->num_mux_defs = 1; spec->input_mux = &spec->private_imux[0]; + alc_ssid_check(codec, 0x10, 0x15, 0x0f); + return 1; } @@ -6000,8 +6211,9 @@ static int patch_alc260(struct hda_codec *codec) alc260_models, alc260_cfg_tbl); if (board_config < 0) { - snd_printd(KERN_INFO "hda_codec: Unknown model for ALC260, " - "trying auto-probe from BIOS...\n"); + snd_printd(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", + codec->chip_name); board_config = ALC260_AUTO; } @@ -6028,11 +6240,9 @@ static int patch_alc260(struct hda_codec *codec) if (board_config != ALC260_AUTO) setup_preset(spec, &alc260_presets[board_config]); - spec->stream_name_analog = "ALC260 Analog"; spec->stream_analog_playback = &alc260_pcm_analog_playback; spec->stream_analog_capture = &alc260_pcm_analog_capture; - spec->stream_name_digital = "ALC260 Digital"; spec->stream_digital_playback = &alc260_pcm_digital_playback; spec->stream_digital_capture = &alc260_pcm_digital_capture; @@ -6109,6 +6319,16 @@ static struct hda_input_mux alc882_capture_source = { { "CD", 0x4 }, }, }; + +static struct hda_input_mux mb5_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + /* * 2ch mode */ @@ -6166,7 +6386,7 @@ static struct hda_channel_mode alc882_sixstack_modes[2] = { }; /* - * macbook pro ALC885 can switch LineIn to LineOut without loosing Mic + * macbook pro ALC885 can switch LineIn to LineOut without losing Mic */ /* @@ -6196,6 +6416,34 @@ static struct hda_channel_mode alc885_mbp_6ch_modes[2] = { { 6, alc885_mbp_ch6_init }, }; +/* + * 2ch + * Speakers/Woofer/HP = Front + * LineIn = Input + */ +static struct hda_verb alc885_mb5_ch2_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + { } /* end */ +}; + +/* + * 6ch mode + * Speakers/HP = Front + * Woofer = LFE + * LineIn = Surround + */ +static struct hda_verb alc885_mb5_ch6_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + { } /* end */ +}; + +static struct hda_channel_mode alc885_mb5_6ch_modes[2] = { + { 2, alc885_mb5_ch2_init }, + { 6, alc885_mb5_ch6_init }, +}; /* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17 * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b @@ -6238,6 +6486,25 @@ static struct snd_kcontrol_new alc885_mbp3_mixer[] = { HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT), { } /* end */ }; + +static struct snd_kcontrol_new alc885_mb5_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("LFE Playback Volume", 0x0e, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("LFE Playback Switch", 0x0e, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("HP Playback Volume", 0x0f, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("HP Playback Switch", 0x0f, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE ("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE ("Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Line Boost", 0x15, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x19, 0x00, HDA_INPUT), + { } /* end */ +}; + static struct snd_kcontrol_new alc882_w2jc_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), @@ -6465,6 +6732,55 @@ static struct hda_verb alc882_macpro_init_verbs[] = { { } }; +/* Macbook 5,1 */ +static struct hda_verb alc885_mb5_init_verbs[] = { + /* DACs */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Front mixer */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Surround mixer */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* LFE mixer */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* HP mixer */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Front Pin (0x0c) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* LFE Pin (0x0e) */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* HP Pin (0x0f) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + { } +}; + /* Macbook Pro rev3 */ static struct hda_verb alc885_mbp3_init_verbs[] = { /* Front mixer: unmute input/output amp left and right (volume = 0) */ @@ -6554,45 +6870,23 @@ static struct hda_verb alc885_imac24_init_verbs[] = { }; /* Toggle speaker-output according to the hp-jack state */ -static void alc885_imac24_automute(struct hda_codec *codec) +static void alc885_imac24_automute_init_hook(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x18, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); -} + struct alc_spec *spec = codec->spec; -/* Processes unsolicited events. */ -static void alc885_imac24_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - /* Headphone insertion or removal. */ - if ((res >> 26) == ALC880_HP_EVENT) - alc885_imac24_automute(codec); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x18; + spec->autocfg.speaker_pins[1] = 0x1a; + alc_automute_amp(codec); } -static void alc885_mbp3_automute(struct hda_codec *codec) +static void alc885_mbp3_init_hook(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? 0 : HDA_AMP_MUTE); + struct alc_spec *spec = codec->spec; -} -static void alc885_mbp3_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - /* Headphone insertion or removal. */ - if ((res >> 26) == ALC880_HP_EVENT) - alc885_mbp3_automute(codec); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); } @@ -6617,24 +6911,25 @@ static struct hda_verb alc882_targa_verbs[] = { /* toggle speaker-output according to the hp-jack state */ static void alc882_targa_automute(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + struct alc_spec *spec = codec->spec; + alc_automute_amp(codec); snd_hda_codec_write_cache(codec, 1, 0, AC_VERB_SET_GPIO_DATA, - present ? 1 : 3); + spec->jack_present ? 1 : 3); +} + +static void alc882_targa_init_hook(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x1b; + alc882_targa_automute(codec); } static void alc882_targa_unsol_event(struct hda_codec *codec, unsigned int res) { - /* Looks like the unsol event is incompatible with the standard - * definition. 4bit tag is placed at 26 bit! - */ - if (((res >> 26) == ALC880_HP_EVENT)) { + if ((res >> 26) == ALC880_HP_EVENT) alc882_targa_automute(codec); - } } static struct hda_verb alc882_asus_a7j_verbs[] = { @@ -6716,7 +7011,7 @@ static void alc885_macpro_init_hook(struct hda_codec *codec) static void alc885_imac24_init_hook(struct hda_codec *codec) { alc885_macpro_init_hook(codec); - alc885_imac24_automute(codec); + alc885_imac24_automute_init_hook(codec); } /* @@ -6791,7 +7086,7 @@ static struct hda_verb alc882_auto_init_verbs[] = { #define alc882_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc882_pcm_analog_playback alc880_pcm_analog_playback #define alc882_pcm_analog_capture alc880_pcm_analog_capture #define alc882_pcm_digital_playback alc880_pcm_digital_playback @@ -6809,6 +7104,7 @@ static const char *alc882_models[ALC882_MODEL_LAST] = { [ALC882_ASUS_A7J] = "asus-a7j", [ALC882_ASUS_A7M] = "asus-a7m", [ALC885_MACPRO] = "macpro", + [ALC885_MB5] = "mb5", [ALC885_MBP3] = "mbp3", [ALC885_IMAC24] = "imac24", [ALC882_AUTO] = "auto", @@ -6886,8 +7182,20 @@ static struct alc_config_preset alc882_presets[] = { .input_mux = &alc882_capture_source, .dig_out_nid = ALC882_DIGOUT_NID, .dig_in_nid = ALC882_DIGIN_NID, - .unsol_event = alc885_mbp3_unsol_event, - .init_hook = alc885_mbp3_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc885_mbp3_init_hook, + }, + [ALC885_MB5] = { + .mixers = { alc885_mb5_mixer, alc882_chmode_mixer }, + .init_verbs = { alc885_mb5_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .channel_mode = alc885_mb5_6ch_modes, + .num_channel_mode = ARRAY_SIZE(alc885_mb5_6ch_modes), + .input_mux = &mb5_capture_source, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, }, [ALC885_MACPRO] = { .mixers = { alc882_macpro_mixer }, @@ -6911,7 +7219,7 @@ static struct alc_config_preset alc882_presets[] = { .num_channel_mode = ARRAY_SIZE(alc882_ch_modes), .channel_mode = alc882_ch_modes, .input_mux = &alc882_capture_source, - .unsol_event = alc885_imac24_unsol_event, + .unsol_event = alc_automute_amp_unsol_event, .init_hook = alc885_imac24_init_hook, }, [ALC882_TARGA] = { @@ -6928,7 +7236,7 @@ static struct alc_config_preset alc882_presets[] = { .need_dac_fix = 1, .input_mux = &alc882_capture_source, .unsol_event = alc882_targa_unsol_event, - .init_hook = alc882_targa_automute, + .init_hook = alc882_targa_init_hook, }, [ALC882_ASUS_A7J] = { .mixers = { alc882_asus_a7j_mixer, alc882_chmode_mixer }, @@ -7008,7 +7316,6 @@ static void alc882_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x15, 0x1b, 0x14); for (i = 0; i <= HDA_SIDE; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -7191,10 +7498,17 @@ static int patch_alc882(struct hda_codec *codec) case 0x106b00a1: /* Macbook (might be wrong - PCI SSID?) */ case 0x106b00a4: /* MacbookPro4,1 */ case 0x106b2c00: /* Macbook Pro rev3 */ - case 0x106b3600: /* Macbook 3.1 */ + /* Macbook 3.1 (0x106b3600) is handled by patch_alc883() */ case 0x106b3800: /* MacbookPro4,1 - latter revision */ board_config = ALC885_MBP3; break; + case 0x106b3f00: /* Macbook 5,1 */ + case 0x106b4000: /* Macbook Pro 5,1 - FIXME: HP jack sense + * seems not working, so apparently + * no perfect solution yet + */ + board_config = ALC885_MB5; + break; default: /* ALC889A is handled better as ALC888-compatible */ if (codec->revision_id == 0x100101 || @@ -7202,8 +7516,9 @@ static int patch_alc882(struct hda_codec *codec) alc_free(codec); return patch_alc883(codec); } - printk(KERN_INFO "hda_codec: Unknown model for ALC882, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", + codec->chip_name); board_config = ALC882_AUTO; } } @@ -7233,14 +7548,6 @@ static int patch_alc882(struct hda_codec *codec) if (board_config != ALC882_AUTO) setup_preset(spec, &alc882_presets[board_config]); - if (codec->vendor_id == 0x10ec0885) { - spec->stream_name_analog = "ALC885 Analog"; - spec->stream_name_digital = "ALC885 Digital"; - } else { - spec->stream_name_analog = "ALC882 Analog"; - spec->stream_name_digital = "ALC882 Digital"; - } - spec->stream_analog_playback = &alc882_pcm_analog_playback; spec->stream_analog_capture = &alc882_pcm_analog_capture; /* FIXME: setup DAC5 */ @@ -7393,6 +7700,17 @@ static struct hda_input_mux alc883_asus_eee1601_capture_source = { }, }; +static struct hda_input_mux alc889A_mb31_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + /* Front Mic (0x01) unused */ + { "Line", 0x2 }, + /* Line 2 (0x03) unused */ + /* CD (0x04) unsused? */ + }, +}; + /* * 2ch mode */ @@ -7442,6 +7760,73 @@ static struct hda_channel_mode alc883_3ST_6ch_modes[3] = { { 6, alc883_3ST_ch6_init }, }; + +/* + * 2ch mode + */ +static struct hda_verb alc883_4ST_ch2_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* + * 4ch mode + */ +static struct hda_verb alc883_4ST_ch4_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc883_4ST_ch6_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +/* + * 8ch mode + */ +static struct hda_verb alc883_4ST_ch8_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x17, AC_VERB_SET_CONNECT_SEL, 0x03 }, + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +static struct hda_channel_mode alc883_4ST_8ch_modes[4] = { + { 2, alc883_4ST_ch2_init }, + { 4, alc883_4ST_ch4_init }, + { 6, alc883_4ST_ch6_init }, + { 8, alc883_4ST_ch8_init }, +}; + + /* * 2ch mode */ @@ -7511,6 +7896,49 @@ static struct hda_channel_mode alc883_sixstack_modes[2] = { { 8, alc883_sixstack_ch8_init }, }; +/* 2ch mode (Speaker:front, Subwoofer:CLFE, Line:input, Headphones:front) */ +static struct hda_verb alc889A_mb31_ch2_init[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP as front */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Line as input */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Line off */ + { } /* end */ +}; + +/* 4ch mode (Speaker:front, Subwoofer:CLFE, Line:CLFE, Headphones:front) */ +static struct hda_verb alc889A_mb31_ch4_init[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP as front */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, /* Line as output */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Line on */ + { } /* end */ +}; + +/* 5ch mode (Speaker:front, Subwoofer:CLFE, Line:input, Headphones:rear) */ +static struct hda_verb alc889A_mb31_ch5_init[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, /* HP as rear */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Line as input */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Line off */ + { } /* end */ +}; + +/* 6ch mode (Speaker:front, Subwoofer:off, Line:CLFE, Headphones:Rear) */ +static struct hda_verb alc889A_mb31_ch6_init[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, /* HP as front */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Subwoofer off */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, /* Line as output */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Line on */ + { } /* end */ +}; + +static struct hda_channel_mode alc889A_mb31_6ch_modes[4] = { + { 2, alc889A_mb31_ch2_init }, + { 4, alc889A_mb31_ch4_init }, + { 5, alc889A_mb31_ch5_init }, + { 6, alc889A_mb31_ch6_init }, +}; + static struct hda_verb alc883_medion_eapd_verbs[] = { /* eanable EAPD on medion laptop */ {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, @@ -7679,7 +8107,7 @@ static struct snd_kcontrol_new alc883_fivestack_mixer[] = { { } /* end */ }; -static struct snd_kcontrol_new alc883_tagra_mixer[] = { +static struct snd_kcontrol_new alc883_targa_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), @@ -7699,7 +8127,7 @@ static struct snd_kcontrol_new alc883_tagra_mixer[] = { { } /* end */ }; -static struct snd_kcontrol_new alc883_tagra_2ch_mixer[] = { +static struct snd_kcontrol_new alc883_targa_2ch_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), @@ -7764,6 +8192,19 @@ static struct snd_kcontrol_new alc883_acer_aspire_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc888_acer_aspire_6530_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("LFE Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("LFE Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + static struct snd_kcontrol_new alc888_lenovo_sky_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), @@ -7776,8 +8217,6 @@ static struct snd_kcontrol_new alc888_lenovo_sky_mixer[] = { HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT), HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), - HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), - HDA_CODEC_MUTE("iSpeaker Playback Switch", 0x1a, 0x0, HDA_OUTPUT), HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), @@ -7791,6 +8230,42 @@ static struct snd_kcontrol_new alc888_lenovo_sky_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc889A_mb31_mixer[] = { + /* Output mixers */ + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x00, + HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x02, HDA_INPUT), + /* Output switches */ + HDA_CODEC_MUTE("Enable Speaker", 0x14, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("Enable Headphones", 0x15, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Enable LFE", 0x16, 2, 0x00, HDA_OUTPUT), + /* Boost mixers */ + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Line Boost", 0x1a, 0x00, HDA_INPUT), + /* Input mixers */ + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_vaiott_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + static struct hda_bind_ctls alc883_bind_cap_vol = { .ops = &snd_hda_bind_vol, .values = { @@ -7926,16 +8401,14 @@ static struct hda_verb alc883_init_verbs[] = { }; /* toggle speaker-output according to the hp-jack state */ -static void alc883_mitac_hp_automute(struct hda_codec *codec) +static void alc883_mitac_init_hook(struct hda_codec *codec) { - unsigned int present; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x17; + alc_automute_amp(codec); } /* auto-toggle front mic */ @@ -7952,25 +8425,6 @@ static void alc883_mitac_mic_automute(struct hda_codec *codec) } */ -static void alc883_mitac_automute(struct hda_codec *codec) -{ - alc883_mitac_hp_automute(codec); - /* alc883_mitac_mic_automute(codec); */ -} - -static void alc883_mitac_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - switch (res >> 26) { - case ALC880_HP_EVENT: - alc883_mitac_hp_automute(codec); - break; - case ALC880_MIC_EVENT: - /* alc883_mitac_mic_automute(codec); */ - break; - } -} - static struct hda_verb alc883_mitac_verbs[] = { /* HP */ {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, @@ -8015,21 +8469,31 @@ static struct hda_verb alc883_2ch_fujitsu_pi2515_verbs[] = { { } /* end */ }; -static struct hda_verb alc883_tagra_verbs[] = { +static struct hda_verb alc883_targa_verbs[] = { {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, - {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ - {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */ - {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ +/* Connect Line-Out side jack (SPDIF) to Side */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0x03}, +/* Connect Mic jack to CLFE */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, +/* Connect Line-in jack to Surround */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, +/* Connect HP out jack to Front */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, - {0x01, AC_VERB_SET_GPIO_MASK, 0x03}, - {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03}, - {0x01, AC_VERB_SET_GPIO_DATA, 0x03}, { } /* end */ }; @@ -8088,29 +8552,26 @@ static struct hda_verb alc888_6st_dell_verbs[] = { { } }; -static void alc888_3st_hp_front_automute(struct hda_codec *codec) -{ - unsigned int present, bits; +static struct hda_verb alc883_vaiott_verbs[] = { + /* HP */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - snd_hda_codec_amp_stereo(codec, 0x18, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} + /* enable unsolicited event */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, -static void alc888_3st_hp_unsol_event(struct hda_codec *codec, - unsigned int res) + { } /* end */ +}; + +static void alc888_3st_hp_init_hook(struct hda_codec *codec) { - switch (res >> 26) { - case ALC880_HP_EVENT: - alc888_3st_hp_front_automute(codec); - break; - } + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x16; + spec->autocfg.speaker_pins[2] = 0x18; + alc_automute_amp(codec); } static struct hda_verb alc888_3st_hp_verbs[] = { @@ -8207,56 +8668,18 @@ static struct hda_verb alc883_medion_md2_verbs[] = { }; /* toggle speaker-output according to the hp-jack state */ -static void alc883_medion_md2_automute(struct hda_codec *codec) -{ - unsigned int present; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); -} - -static void alc883_medion_md2_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc883_medion_md2_automute(codec); -} - -/* toggle speaker-output according to the hp-jack state */ -static void alc883_tagra_automute(struct hda_codec *codec) +static void alc883_medion_md2_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); - snd_hda_codec_write_cache(codec, 1, 0, AC_VERB_SET_GPIO_DATA, - present ? 1 : 3); -} + struct alc_spec *spec = codec->spec; -static void alc883_tagra_unsol_event(struct hda_codec *codec, unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc883_tagra_automute(codec); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x15; + alc_automute_amp(codec); } /* toggle speaker-output according to the hp-jack state */ -static void alc883_clevo_m720_hp_automute(struct hda_codec *codec) -{ - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} +#define alc883_targa_init_hook alc882_targa_init_hook +#define alc883_targa_unsol_event alc882_targa_unsol_event static void alc883_clevo_m720_mic_automute(struct hda_codec *codec) { @@ -8268,9 +8691,13 @@ static void alc883_clevo_m720_mic_automute(struct hda_codec *codec) HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); } -static void alc883_clevo_m720_automute(struct hda_codec *codec) +static void alc883_clevo_m720_init_hook(struct hda_codec *codec) { - alc883_clevo_m720_hp_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); alc883_clevo_m720_mic_automute(codec); } @@ -8278,52 +8705,32 @@ static void alc883_clevo_m720_unsol_event(struct hda_codec *codec, unsigned int res) { switch (res >> 26) { - case ALC880_HP_EVENT: - alc883_clevo_m720_hp_automute(codec); - break; case ALC880_MIC_EVENT: alc883_clevo_m720_mic_automute(codec); break; + default: + alc_automute_amp_unsol_event(codec, res); + break; } } /* toggle speaker-output according to the hp-jack state */ -static void alc883_2ch_fujitsu_pi2515_automute(struct hda_codec *codec) +static void alc883_2ch_fujitsu_pi2515_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x14, 0, AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} + struct alc_spec *spec = codec->spec; -static void alc883_2ch_fujitsu_pi2515_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc883_2ch_fujitsu_pi2515_automute(codec); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x15; + alc_automute_amp(codec); } -static void alc883_haier_w66_automute(struct hda_codec *codec) +static void alc883_haier_w66_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? 0x80 : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - 0x80, bits); -} - -static void alc883_haier_w66_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc883_haier_w66_automute(codec); + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); } static void alc883_lenovo_101e_ispeaker_automute(struct hda_codec *codec) @@ -8331,8 +8738,8 @@ static void alc883_lenovo_101e_ispeaker_automute(struct hda_codec *codec) unsigned int present; unsigned char bits; - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + present = snd_hda_codec_read(codec, 0x14, 0, AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; bits = present ? HDA_AMP_MUTE : 0; snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, HDA_AMP_MUTE, bits); @@ -8362,23 +8769,14 @@ static void alc883_lenovo_101e_unsol_event(struct hda_codec *codec, } /* toggle speaker-output according to the hp-jack state */ -static void alc883_acer_aspire_automute(struct hda_codec *codec) +static void alc883_acer_aspire_init_hook(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); -} + struct alc_spec *spec = codec->spec; -static void alc883_acer_aspire_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc883_acer_aspire_automute(codec); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x15; + spec->autocfg.speaker_pins[1] = 0x16; + alc_automute_amp(codec); } static struct hda_verb alc883_acer_eapd_verbs[] = { @@ -8399,75 +8797,39 @@ static struct hda_verb alc883_acer_eapd_verbs[] = { { } }; -static void alc888_6st_dell_front_automute(struct hda_codec *codec) +static void alc888_6st_dell_init_hook(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); - snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); -} + struct alc_spec *spec = codec->spec; -static void alc888_6st_dell_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - switch (res >> 26) { - case ALC880_HP_EVENT: - /* printk(KERN_DEBUG "hp_event\n"); */ - alc888_6st_dell_front_automute(codec); - break; - } + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x15; + spec->autocfg.speaker_pins[2] = 0x16; + spec->autocfg.speaker_pins[3] = 0x17; + alc_automute_amp(codec); } -static void alc888_lenovo_sky_front_automute(struct hda_codec *codec) +static void alc888_lenovo_sky_init_hook(struct hda_codec *codec) { - unsigned int mute; - unsigned int present; + struct alc_spec *spec = codec->spec; - snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0); - present = (present & 0x80000000) != 0; - if (present) { - /* mute internal speaker */ - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - } else { - /* unmute internal speaker if necessary */ - mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - } + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x15; + spec->autocfg.speaker_pins[2] = 0x16; + spec->autocfg.speaker_pins[3] = 0x17; + spec->autocfg.speaker_pins[4] = 0x1a; + alc_automute_amp(codec); } -static void alc883_lenovo_sky_unsol_event(struct hda_codec *codec, - unsigned int res) +static void alc883_vaiott_init_hook(struct hda_codec *codec) { - if ((res >> 26) == ALC880_HP_EVENT) - alc888_lenovo_sky_front_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x17; + alc_automute_amp(codec); } /* @@ -8555,39 +8917,33 @@ static void alc883_nb_mic_automute(struct hda_codec *codec) 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); } -static void alc883_M90V_speaker_automute(struct hda_codec *codec) +static void alc883_M90V_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - bits = present ? 0 : PIN_OUT; - snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - bits); - snd_hda_codec_write(codec, 0x15, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - bits); - snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - bits); + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + spec->autocfg.speaker_pins[1] = 0x15; + spec->autocfg.speaker_pins[2] = 0x16; + alc_automute_pin(codec); } static void alc883_mode2_unsol_event(struct hda_codec *codec, unsigned int res) { switch (res >> 26) { - case ALC880_HP_EVENT: - alc883_M90V_speaker_automute(codec); - break; case ALC880_MIC_EVENT: alc883_nb_mic_automute(codec); break; + default: + alc_sku_unsol_event(codec, res); + break; } } static void alc883_mode2_inithook(struct hda_codec *codec) { - alc883_M90V_speaker_automute(codec); + alc883_M90V_init_hook(codec); alc883_nb_mic_automute(codec); } @@ -8604,39 +8960,56 @@ static struct hda_verb alc888_asus_eee1601_verbs[] = { { } /* end */ }; -static void alc883_eee1601_speaker_automute(struct hda_codec *codec) +static void alc883_eee1601_inithook(struct hda_codec *codec) { - unsigned int present; - unsigned char bits; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - bits = present ? 0 : PIN_OUT; - snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - bits); + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x1b; + alc_automute_pin(codec); } -static void alc883_eee1601_unsol_event(struct hda_codec *codec, - unsigned int res) +static struct hda_verb alc889A_mb31_verbs[] = { + /* Init rear pin (used as headphone output) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc4}, /* Apple Headphones */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Connect to front */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* Init line pin (used as output in 4ch and 6ch mode) */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x02}, /* Connect to CLFE */ + /* Init line 2 pin (used as headphone out by default) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Use as input */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Mute output */ + { } /* end */ +}; + +/* Mute speakers according to the headphone jack state */ +static void alc889A_mb31_automute(struct hda_codec *codec) { - switch (res >> 26) { - case ALC880_HP_EVENT: - alc883_eee1601_speaker_automute(codec); - break; + unsigned int present; + + /* Mute only in 2ch or 4ch mode */ + if (snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_CONNECT_SEL, 0) + == 0x00) { + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & AC_PINSENSE_PRESENCE; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); } } -static void alc883_eee1601_inithook(struct hda_codec *codec) +static void alc889A_mb31_unsol_event(struct hda_codec *codec, unsigned int res) { - alc883_eee1601_speaker_automute(codec); + if ((res >> 26) == ALC880_HP_EVENT) + alc889A_mb31_automute(codec); } #ifdef CONFIG_SND_HDA_POWER_SAVE #define alc883_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc883_pcm_analog_playback alc880_pcm_analog_playback #define alc883_pcm_analog_capture alc880_pcm_analog_capture #define alc883_pcm_analog_alt_capture alc880_pcm_analog_alt_capture @@ -8653,9 +9026,12 @@ static const char *alc883_models[ALC883_MODEL_LAST] = { [ALC883_6ST_DIG] = "6stack-dig", [ALC883_TARGA_DIG] = "targa-dig", [ALC883_TARGA_2ch_DIG] = "targa-2ch-dig", + [ALC883_TARGA_8ch_DIG] = "targa-8ch-dig", [ALC883_ACER] = "acer", [ALC883_ACER_ASPIRE] = "acer-aspire", [ALC888_ACER_ASPIRE_4930G] = "acer-aspire-4930g", + [ALC888_ACER_ASPIRE_6530G] = "acer-aspire-6530g", + [ALC888_ACER_ASPIRE_8930G] = "acer-aspire-8930g", [ALC883_MEDION] = "medion", [ALC883_MEDION_MD2] = "medion-md2", [ALC883_LAPTOP_EAPD] = "laptop-eapd", @@ -8672,6 +9048,8 @@ static const char *alc883_models[ALC883_MODEL_LAST] = { [ALC888_FUJITSU_XA3530] = "fujitsu-xa3530", [ALC883_3ST_6ch_INTEL] = "3stack-6ch-intel", [ALC1200_ASUS_P5Q] = "asus-p5q", + [ALC889A_MB31] = "mb31", + [ALC883_SONY_VAIO_TT] = "sony-vaio-tt", [ALC883_AUTO] = "auto", }; @@ -8687,14 +9065,20 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { ALC888_ACER_ASPIRE_4930G), SND_PCI_QUIRK(0x1025, 0x013f, "Acer Aspire 5930G", ALC888_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x0145, "Acer Aspire 8930G", + ALC888_ACER_ASPIRE_8930G), + SND_PCI_QUIRK(0x1025, 0x0146, "Acer Aspire 6935G", + ALC888_ACER_ASPIRE_8930G), SND_PCI_QUIRK(0x1025, 0x0157, "Acer X3200", ALC883_AUTO), SND_PCI_QUIRK(0x1025, 0x0158, "Acer AX1700-U3700A", ALC883_AUTO), SND_PCI_QUIRK(0x1025, 0x015e, "Acer Aspire 6930G", ALC888_ACER_ASPIRE_4930G), SND_PCI_QUIRK(0x1025, 0x0166, "Acer Aspire 6530G", - ALC888_ACER_ASPIRE_4930G), - /* default Acer */ - SND_PCI_QUIRK_VENDOR(0x1025, "Acer laptop", ALC883_ACER), + ALC888_ACER_ASPIRE_6530G), + /* default Acer -- disabled as it causes more problems. + * model=auto should work fine now + */ + /* SND_PCI_QUIRK_VENDOR(0x1025, "Acer laptop", ALC883_ACER), */ SND_PCI_QUIRK(0x1028, 0x020d, "Dell Inspiron 530", ALC888_6ST_DELL), SND_PCI_QUIRK(0x103c, 0x2a3d, "HP Pavillion", ALC883_6ST_DIG), SND_PCI_QUIRK(0x103c, 0x2a4f, "HP Samba", ALC888_3ST_HP), @@ -8730,6 +9114,7 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x1462, 0x4314, "MSI", ALC883_TARGA_DIG), SND_PCI_QUIRK(0x1462, 0x4319, "MSI", ALC883_TARGA_DIG), SND_PCI_QUIRK(0x1462, 0x4324, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x6510, "MSI GX620", ALC883_TARGA_8ch_DIG), SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x7187, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x7250, "MSI", ALC883_6ST_DIG), @@ -8737,6 +9122,7 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x1462, 0x7267, "MSI", ALC883_3ST_6ch_DIG), SND_PCI_QUIRK(0x1462, 0x7280, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0x7327, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7350, "MSI", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1462, 0xa422, "MSI", ALC883_TARGA_2ch_DIG), SND_PCI_QUIRK(0x147b, 0x1083, "Abit IP35-PRO", ALC883_6ST_DIG), SND_PCI_QUIRK(0x1558, 0x0721, "Clevo laptop M720R", ALC883_CLEVO_M720), @@ -8762,6 +9148,7 @@ static struct snd_pci_quirk alc883_cfg_tbl[] = { SND_PCI_QUIRK(0x8086, 0x2503, "82801H", ALC883_MITAC), SND_PCI_QUIRK(0x8086, 0x0022, "DX58SO", ALC883_3ST_6ch_INTEL), SND_PCI_QUIRK(0x8086, 0xd601, "D102GGC", ALC883_3ST_6ch), + SND_PCI_QUIRK(0x104d, 0x9047, "Sony Vaio TT", ALC883_SONY_VAIO_TT), {} }; @@ -8832,8 +9219,8 @@ static struct alc_config_preset alc883_presets[] = { .input_mux = &alc883_capture_source, }, [ALC883_TARGA_DIG] = { - .mixers = { alc883_tagra_mixer, alc883_chmode_mixer }, - .init_verbs = { alc883_init_verbs, alc883_tagra_verbs}, + .mixers = { alc883_targa_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc883_targa_verbs}, .num_dacs = ARRAY_SIZE(alc883_dac_nids), .dac_nids = alc883_dac_nids, .dig_out_nid = ALC883_DIGOUT_NID, @@ -8841,12 +9228,12 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_3ST_6ch_modes, .need_dac_fix = 1, .input_mux = &alc883_capture_source, - .unsol_event = alc883_tagra_unsol_event, - .init_hook = alc883_tagra_automute, + .unsol_event = alc883_targa_unsol_event, + .init_hook = alc883_targa_init_hook, }, [ALC883_TARGA_2ch_DIG] = { - .mixers = { alc883_tagra_2ch_mixer}, - .init_verbs = { alc883_init_verbs, alc883_tagra_verbs}, + .mixers = { alc883_targa_2ch_mixer}, + .init_verbs = { alc883_init_verbs, alc883_targa_verbs}, .num_dacs = ARRAY_SIZE(alc883_dac_nids), .dac_nids = alc883_dac_nids, .adc_nids = alc883_adc_nids_alt, @@ -8855,8 +9242,26 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc883_tagra_unsol_event, - .init_hook = alc883_tagra_automute, + .unsol_event = alc883_targa_unsol_event, + .init_hook = alc883_targa_init_hook, + }, + [ALC883_TARGA_8ch_DIG] = { + .mixers = { alc883_base_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc880_gpio3_init_verbs, + alc883_targa_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev), + .adc_nids = alc883_adc_nids_rev, + .capsrc_nids = alc883_capsrc_nids_rev, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_4ST_8ch_modes), + .channel_mode = alc883_4ST_8ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_targa_unsol_event, + .init_hook = alc883_targa_init_hook, }, [ALC883_ACER] = { .mixers = { alc883_base_mixer }, @@ -8881,8 +9286,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc883_acer_aspire_unsol_event, - .init_hook = alc883_acer_aspire_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_acer_aspire_init_hook, }, [ALC888_ACER_ASPIRE_4930G] = { .mixers = { alc888_base_mixer, @@ -8901,8 +9306,47 @@ static struct alc_config_preset alc883_presets[] = { .num_mux_defs = ARRAY_SIZE(alc888_2_capture_sources), .input_mux = alc888_2_capture_sources, - .unsol_event = alc888_acer_aspire_4930g_unsol_event, - .init_hook = alc888_acer_aspire_4930g_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_acer_aspire_4930g_init_hook, + }, + [ALC888_ACER_ASPIRE_6530G] = { + .mixers = { alc888_acer_aspire_6530_mixer }, + .init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs, + alc888_acer_aspire_6530g_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev), + .adc_nids = alc883_adc_nids_rev, + .capsrc_nids = alc883_capsrc_nids_rev, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .num_mux_defs = + ARRAY_SIZE(alc888_2_capture_sources), + .input_mux = alc888_acer_aspire_6530_sources, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_acer_aspire_4930g_init_hook, + }, + [ALC888_ACER_ASPIRE_8930G] = { + .mixers = { alc888_base_mixer, + alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs, + alc889_acer_aspire_8930g_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc889_adc_nids), + .adc_nids = alc889_adc_nids, + .capsrc_nids = alc889_capsrc_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .const_channel_count = 6, + .num_mux_defs = + ARRAY_SIZE(alc889_capture_sources), + .input_mux = alc889_capture_sources, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc889_acer_aspire_8930g_init_hook, }, [ALC883_MEDION] = { .mixers = { alc883_fivestack_mixer, @@ -8926,8 +9370,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc883_medion_md2_unsol_event, - .init_hook = alc883_medion_md2_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_medion_md2_init_hook, }, [ALC883_LAPTOP_EAPD] = { .mixers = { alc883_base_mixer }, @@ -8948,7 +9392,7 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, .unsol_event = alc883_clevo_m720_unsol_event, - .init_hook = alc883_clevo_m720_automute, + .init_hook = alc883_clevo_m720_init_hook, }, [ALC883_LENOVO_101E_2ch] = { .mixers = { alc883_lenovo_101e_2ch_mixer}, @@ -8972,8 +9416,8 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_3ST_2ch_modes, .need_dac_fix = 1, .input_mux = &alc883_lenovo_nb0763_capture_source, - .unsol_event = alc883_medion_md2_unsol_event, - .init_hook = alc883_medion_md2_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_medion_md2_init_hook, }, [ALC888_LENOVO_MS7195_DIG] = { .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, @@ -8989,7 +9433,7 @@ static struct alc_config_preset alc883_presets[] = { .init_hook = alc888_lenovo_ms7195_front_automute, }, [ALC883_HAIER_W66] = { - .mixers = { alc883_tagra_2ch_mixer}, + .mixers = { alc883_targa_2ch_mixer}, .init_verbs = { alc883_init_verbs, alc883_haier_w66_verbs}, .num_dacs = ARRAY_SIZE(alc883_dac_nids), .dac_nids = alc883_dac_nids, @@ -8997,8 +9441,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc883_haier_w66_unsol_event, - .init_hook = alc883_haier_w66_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_haier_w66_init_hook, }, [ALC888_3ST_HP] = { .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, @@ -9009,8 +9453,8 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc888_3st_hp_modes, .need_dac_fix = 1, .input_mux = &alc883_capture_source, - .unsol_event = alc888_3st_hp_unsol_event, - .init_hook = alc888_3st_hp_front_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_3st_hp_init_hook, }, [ALC888_6ST_DELL] = { .mixers = { alc883_base_mixer, alc883_chmode_mixer }, @@ -9022,8 +9466,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), .channel_mode = alc883_sixstack_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc888_6st_dell_unsol_event, - .init_hook = alc888_6st_dell_front_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_6st_dell_init_hook, }, [ALC883_MITAC] = { .mixers = { alc883_mitac_mixer }, @@ -9033,8 +9477,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, - .unsol_event = alc883_mitac_unsol_event, - .init_hook = alc883_mitac_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_mitac_init_hook, }, [ALC883_FUJITSU_PI2515] = { .mixers = { alc883_2ch_fujitsu_pi2515_mixer }, @@ -9046,8 +9490,8 @@ static struct alc_config_preset alc883_presets[] = { .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_fujitsu_pi2515_capture_source, - .unsol_event = alc883_2ch_fujitsu_pi2515_unsol_event, - .init_hook = alc883_2ch_fujitsu_pi2515_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_2ch_fujitsu_pi2515_init_hook, }, [ALC888_FUJITSU_XA3530] = { .mixers = { alc888_base_mixer, alc883_chmode_mixer }, @@ -9064,8 +9508,8 @@ static struct alc_config_preset alc883_presets[] = { .num_mux_defs = ARRAY_SIZE(alc888_2_capture_sources), .input_mux = alc888_2_capture_sources, - .unsol_event = alc888_fujitsu_xa3530_unsol_event, - .init_hook = alc888_fujitsu_xa3530_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_fujitsu_xa3530_init_hook, }, [ALC888_LENOVO_SKY] = { .mixers = { alc888_lenovo_sky_mixer, alc883_chmode_mixer }, @@ -9077,8 +9521,8 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_sixstack_modes, .need_dac_fix = 1, .input_mux = &alc883_lenovo_sky_capture_source, - .unsol_event = alc883_lenovo_sky_unsol_event, - .init_hook = alc888_lenovo_sky_front_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc888_lenovo_sky_init_hook, }, [ALC888_ASUS_M90V] = { .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, @@ -9106,7 +9550,7 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_3ST_2ch_modes, .need_dac_fix = 1, .input_mux = &alc883_asus_eee1601_capture_source, - .unsol_event = alc883_eee1601_unsol_event, + .unsol_event = alc_sku_unsol_event, .init_hook = alc883_eee1601_inithook, }, [ALC1200_ASUS_P5Q] = { @@ -9121,6 +9565,32 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_sixstack_modes, .input_mux = &alc883_capture_source, }, + [ALC889A_MB31] = { + .mixers = { alc889A_mb31_mixer, alc883_chmode_mixer}, + .init_verbs = { alc883_init_verbs, alc889A_mb31_verbs, + alc880_gpio1_init_verbs }, + .adc_nids = alc883_adc_nids, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .dac_nids = alc883_dac_nids, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .channel_mode = alc889A_mb31_6ch_modes, + .num_channel_mode = ARRAY_SIZE(alc889A_mb31_6ch_modes), + .input_mux = &alc889A_mb31_capture_source, + .dig_out_nid = ALC883_DIGOUT_NID, + .unsol_event = alc889A_mb31_unsol_event, + .init_hook = alc889A_mb31_automute, + }, + [ALC883_SONY_VAIO_TT] = { + .mixers = { alc883_vaiott_mixer }, + .init_verbs = { alc883_init_verbs, alc883_vaiott_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc883_vaiott_init_hook, + }, }; @@ -9149,7 +9619,6 @@ static void alc883_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x15, 0x1b, 0x14); for (i = 0; i <= HDA_SIDE; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -9267,10 +9736,18 @@ static int patch_alc883(struct hda_codec *codec) board_config = snd_hda_check_board_config(codec, ALC883_MODEL_LAST, alc883_models, alc883_cfg_tbl); - if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC883, " - "trying auto-probe from BIOS...\n"); - board_config = ALC883_AUTO; + if (board_config < 0 || board_config >= ALC883_MODEL_LAST) { + /* Pick up systems that don't supply PCI SSID */ + switch (codec->subsystem_id) { + case 0x106b3600: /* Macbook 3.1 */ + board_config = ALC889A_MB31; + break; + default: + printk(KERN_INFO + "hda_codec: Unknown model for %s, trying " + "auto-probe from BIOS...\n", codec->chip_name); + board_config = ALC883_AUTO; + } } if (board_config == ALC883_AUTO) { @@ -9298,13 +9775,6 @@ static int patch_alc883(struct hda_codec *codec) switch (codec->vendor_id) { case 0x10ec0888: - if (codec->revision_id == 0x100101) { - spec->stream_name_analog = "ALC1200 Analog"; - spec->stream_name_digital = "ALC1200 Digital"; - } else { - spec->stream_name_analog = "ALC888 Analog"; - spec->stream_name_digital = "ALC888 Digital"; - } if (!spec->num_adc_nids) { spec->num_adc_nids = ARRAY_SIZE(alc883_adc_nids); spec->adc_nids = alc883_adc_nids; @@ -9312,10 +9782,9 @@ static int patch_alc883(struct hda_codec *codec) if (!spec->capsrc_nids) spec->capsrc_nids = alc883_capsrc_nids; spec->capture_style = CAPT_MIX; /* matrix-style capture */ + spec->init_amp = ALC_INIT_DEFAULT; /* always initialize */ break; case 0x10ec0889: - spec->stream_name_analog = "ALC889 Analog"; - spec->stream_name_digital = "ALC889 Digital"; if (!spec->num_adc_nids) { spec->num_adc_nids = ARRAY_SIZE(alc889_adc_nids); spec->adc_nids = alc889_adc_nids; @@ -9326,8 +9795,6 @@ static int patch_alc883(struct hda_codec *codec) capture */ break; default: - spec->stream_name_analog = "ALC883 Analog"; - spec->stream_name_digital = "ALC883 Digital"; if (!spec->num_adc_nids) { spec->num_adc_nids = ARRAY_SIZE(alc883_adc_nids); spec->adc_nids = alc883_adc_nids; @@ -9407,24 +9874,6 @@ static struct snd_kcontrol_new alc262_base_mixer[] = { { } /* end */ }; -static struct snd_kcontrol_new alc262_hippo1_mixer[] = { - HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), - HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), - HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), - HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), - HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), - HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), - HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), - HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), - HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), - HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), - HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), - HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), - /*HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0D, 0x0, HDA_OUTPUT),*/ - HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), - { } /* end */ -}; - /* update HP, line and mono-out pins according to the master switch */ static void alc262_hp_master_update(struct hda_codec *codec) { @@ -9480,14 +9929,7 @@ static void alc262_hp_wildwest_unsol_event(struct hda_codec *codec, alc262_hp_wildwest_automute(codec); } -static int alc262_hp_master_sw_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - struct alc_spec *spec = codec->spec; - *ucontrol->value.integer.value = spec->master_sw; - return 0; -} +#define alc262_hp_master_sw_get alc260_hp_master_sw_get static int alc262_hp_master_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -9503,14 +9945,17 @@ static int alc262_hp_master_sw_put(struct snd_kcontrol *kcontrol, return 1; } +#define ALC262_HP_MASTER_SWITCH \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Master Playback Switch", \ + .info = snd_ctl_boolean_mono_info, \ + .get = alc262_hp_master_sw_get, \ + .put = alc262_hp_master_sw_put, \ + } + static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Playback Switch", - .info = snd_ctl_boolean_mono_info, - .get = alc262_hp_master_sw_get, - .put = alc262_hp_master_sw_put, - }, + ALC262_HP_MASTER_SWITCH, HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), @@ -9534,13 +9979,7 @@ static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = { }; static struct snd_kcontrol_new alc262_HP_BPC_WildWest_mixer[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Playback Switch", - .info = snd_ctl_boolean_mono_info, - .get = alc262_hp_master_sw_get, - .put = alc262_hp_master_sw_put, - }, + ALC262_HP_MASTER_SWITCH, HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), @@ -9567,32 +10006,13 @@ static struct snd_kcontrol_new alc262_HP_BPC_WildWest_option_mixer[] = { }; /* mute/unmute internal speaker according to the hp jack and mute state */ -static void alc262_hp_t5735_automute(struct hda_codec *codec, int force) +static void alc262_hp_t5735_init_hook(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; - if (force || !spec->sense_updated) { - unsigned int present; - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0); - spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; - spec->sense_updated = 1; - } - snd_hda_codec_amp_stereo(codec, 0x0c, HDA_OUTPUT, 0, HDA_AMP_MUTE, - spec->jack_present ? HDA_AMP_MUTE : 0); -} - -static void alc262_hp_t5735_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc262_hp_t5735_automute(codec, 1); -} - -static void alc262_hp_t5735_init_hook(struct hda_codec *codec) -{ - alc262_hp_t5735_automute(codec, 1); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x0c; /* HACK: not actually a pin */ + alc_automute_amp(codec); } static struct snd_kcontrol_new alc262_hp_t5735_mixer[] = { @@ -9645,46 +10065,132 @@ static struct hda_input_mux alc262_hp_rp5700_capture_source = { }, }; -/* bind hp and internal speaker mute (with plug check) */ -static int alc262_sony_master_sw_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +/* bind hp and internal speaker mute (with plug check) as master switch */ +static void alc262_hippo_master_update(struct hda_codec *codec) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - long *valp = ucontrol->value.integer.value; - int change; + struct alc_spec *spec = codec->spec; + hda_nid_t hp_nid = spec->autocfg.hp_pins[0]; + hda_nid_t line_nid = spec->autocfg.line_out_pins[0]; + hda_nid_t speaker_nid = spec->autocfg.speaker_pins[0]; + unsigned int mute; - /* change hp mute */ - change = snd_hda_codec_amp_update(codec, 0x15, 0, HDA_OUTPUT, 0, - HDA_AMP_MUTE, - valp[0] ? 0 : HDA_AMP_MUTE); - change |= snd_hda_codec_amp_update(codec, 0x15, 1, HDA_OUTPUT, 0, - HDA_AMP_MUTE, - valp[1] ? 0 : HDA_AMP_MUTE); - if (change) { - /* change speaker according to HP jack state */ - struct alc_spec *spec = codec->spec; - unsigned int mute; - if (spec->jack_present) - mute = HDA_AMP_MUTE; - else - mute = snd_hda_codec_amp_read(codec, 0x15, 0, - HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + /* HP */ + mute = spec->master_sw ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, hp_nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + /* mute internal speaker per jack sense */ + if (spec->jack_present) + mute = HDA_AMP_MUTE; + if (line_nid) + snd_hda_codec_amp_stereo(codec, line_nid, HDA_OUTPUT, 0, HDA_AMP_MUTE, mute); + if (speaker_nid && speaker_nid != line_nid) + snd_hda_codec_amp_stereo(codec, speaker_nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); +} + +#define alc262_hippo_master_sw_get alc262_hp_master_sw_get + +static int alc262_hippo_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + int val = !!*ucontrol->value.integer.value; + + if (val == spec->master_sw) + return 0; + spec->master_sw = val; + alc262_hippo_master_update(codec); + return 1; +} + +#define ALC262_HIPPO_MASTER_SWITCH \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Master Playback Switch", \ + .info = snd_ctl_boolean_mono_info, \ + .get = alc262_hippo_master_sw_get, \ + .put = alc262_hippo_master_sw_put, \ } - return change; + +static struct snd_kcontrol_new alc262_hippo_mixer[] = { + ALC262_HIPPO_MASTER_SWITCH, + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_hippo1_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + ALC262_HIPPO_MASTER_SWITCH, + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + { } /* end */ +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_hippo_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_nid = spec->autocfg.hp_pins[0]; + unsigned int present; + + /* need to execute and sync at first */ + snd_hda_codec_read(codec, hp_nid, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, hp_nid, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & 0x80000000) != 0; + alc262_hippo_master_update(codec); } +static void alc262_hippo_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hippo_automute(codec); +} + +static void alc262_hippo_init_hook(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc262_hippo_automute(codec); +} + +static void alc262_hippo1_init_hook(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + alc262_hippo_automute(codec); +} + + static struct snd_kcontrol_new alc262_sony_mixer[] = { HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Playback Switch", - .info = snd_hda_mixer_amp_switch_info, - .get = snd_hda_mixer_amp_switch_get, - .put = alc262_sony_master_sw_put, - .private_value = HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), - }, + ALC262_HIPPO_MASTER_SWITCH, HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), @@ -9693,8 +10199,8 @@ static struct snd_kcontrol_new alc262_sony_mixer[] = { }; static struct snd_kcontrol_new alc262_benq_t31_mixer[] = { - HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), - HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + ALC262_HIPPO_MASTER_SWITCH, HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), @@ -9735,34 +10241,15 @@ static struct hda_verb alc262_tyan_verbs[] = { }; /* unsolicited event for HP jack sensing */ -static void alc262_tyan_automute(struct hda_codec *codec) +static void alc262_tyan_init_hook(struct hda_codec *codec) { - unsigned int mute; - unsigned int present; + struct alc_spec *spec = codec->spec; - snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0); - present = (present & 0x80000000) != 0; - if (present) { - /* mute line output on ATX panel */ - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - } else { - /* unmute line output if necessary */ - mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - } + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x15; + alc_automute_amp(codec); } -static void alc262_tyan_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc262_tyan_automute(codec); -} #define alc262_capture_mixer alc882_capture_mixer #define alc262_capture_alt_mixer alc882_capture_alt_mixer @@ -9917,99 +10404,25 @@ static void alc262_dmic_automute(struct hda_codec *codec) AC_VERB_SET_CONNECT_SEL, present ? 0x0 : 0x09); } -/* toggle speaker-output according to the hp-jack state */ -static void alc262_toshiba_s06_speaker_automute(struct hda_codec *codec) -{ - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? 0 : PIN_OUT; - snd_hda_codec_write(codec, 0x14, 0, - AC_VERB_SET_PIN_WIDGET_CONTROL, bits); -} - - /* unsolicited event for HP jack sensing */ static void alc262_toshiba_s06_unsol_event(struct hda_codec *codec, unsigned int res) { - if ((res >> 26) == ALC880_HP_EVENT) - alc262_toshiba_s06_speaker_automute(codec); if ((res >> 26) == ALC880_MIC_EVENT) alc262_dmic_automute(codec); - + else + alc_sku_unsol_event(codec, res); } static void alc262_toshiba_s06_init_hook(struct hda_codec *codec) { - alc262_toshiba_s06_speaker_automute(codec); - alc262_dmic_automute(codec); -} - -/* mute/unmute internal speaker according to the hp jack and mute state */ -static void alc262_hippo_automute(struct hda_codec *codec) -{ struct alc_spec *spec = codec->spec; - unsigned int mute; - unsigned int present; - - /* need to execute and sync at first */ - snd_hda_codec_read(codec, 0x15, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0); - spec->jack_present = (present & 0x80000000) != 0; - if (spec->jack_present) { - /* mute internal speaker */ - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - } else { - /* unmute internal speaker if necessary */ - mute = snd_hda_codec_amp_read(codec, 0x15, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - } -} - -/* unsolicited event for HP jack sensing */ -static void alc262_hippo_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc262_hippo_automute(codec); -} - -static void alc262_hippo1_automute(struct hda_codec *codec) -{ - unsigned int mute; - unsigned int present; - - snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0); - present = (present & 0x80000000) != 0; - if (present) { - /* mute internal speaker */ - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - } else { - /* unmute internal speaker if necessary */ - mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - } -} -/* unsolicited event for HP jack sensing */ -static void alc262_hippo1_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc262_hippo1_automute(codec); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_pin(codec); + alc262_dmic_automute(codec); } /* @@ -10279,14 +10692,7 @@ static struct snd_kcontrol_new alc262_lenovo_3000_mixer[] = { static struct snd_kcontrol_new alc262_toshiba_rx1_mixer[] = { HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol), - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Playback Switch", - .info = snd_hda_mixer_amp_switch_info, - .get = snd_hda_mixer_amp_switch_get, - .put = alc262_sony_master_sw_put, - .private_value = HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), - }, + ALC262_HIPPO_MASTER_SWITCH, HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), @@ -10633,31 +11039,46 @@ static struct hda_verb alc262_HP_BPC_init_verbs[] = { {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, - {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, - {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, /* FIXME: use matrix-type input source selection */ - /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */ - /* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 0b, 12 */ + /* Input mixer1: only unmute Mic */ {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, - {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, - {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, - {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))}, /* Input mixer2 */ {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, - {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, - {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, - {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))}, /* Input mixer3 */ {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, - {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, - {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, - {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))}, {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, @@ -10782,7 +11203,7 @@ static struct hda_verb alc262_toshiba_rx1_unsol_verbs[] = { #define alc262_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc262_pcm_analog_playback alc880_pcm_analog_playback #define alc262_pcm_analog_capture alc880_pcm_analog_capture #define alc262_pcm_digital_playback alc880_pcm_digital_playback @@ -10837,6 +11258,8 @@ static int alc262_parse_auto_config(struct hda_codec *codec) if (err < 0) return err; + alc_ssid_check(codec, 0x15, 0x14, 0x1b); + return 1; } @@ -10939,7 +11362,7 @@ static struct alc_config_preset alc262_presets[] = { .input_mux = &alc262_capture_source, }, [ALC262_HIPPO] = { - .mixers = { alc262_base_mixer }, + .mixers = { alc262_hippo_mixer }, .init_verbs = { alc262_init_verbs, alc262_hippo_unsol_verbs}, .num_dacs = ARRAY_SIZE(alc262_dac_nids), .dac_nids = alc262_dac_nids, @@ -10949,7 +11372,7 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, .unsol_event = alc262_hippo_unsol_event, - .init_hook = alc262_hippo_automute, + .init_hook = alc262_hippo_init_hook, }, [ALC262_HIPPO_1] = { .mixers = { alc262_hippo1_mixer }, @@ -10961,8 +11384,8 @@ static struct alc_config_preset alc262_presets[] = { .num_channel_mode = ARRAY_SIZE(alc262_modes), .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, - .unsol_event = alc262_hippo1_unsol_event, - .init_hook = alc262_hippo1_automute, + .unsol_event = alc262_hippo_unsol_event, + .init_hook = alc262_hippo1_init_hook, }, [ALC262_FUJITSU] = { .mixers = { alc262_fujitsu_mixer }, @@ -11024,7 +11447,7 @@ static struct alc_config_preset alc262_presets[] = { .num_channel_mode = ARRAY_SIZE(alc262_modes), .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, - .unsol_event = alc262_hp_t5735_unsol_event, + .unsol_event = alc_automute_amp_unsol_event, .init_hook = alc262_hp_t5735_init_hook, }, [ALC262_HP_RP5700] = { @@ -11056,7 +11479,7 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, .unsol_event = alc262_hippo_unsol_event, - .init_hook = alc262_hippo_automute, + .init_hook = alc262_hippo_init_hook, }, [ALC262_BENQ_T31] = { .mixers = { alc262_benq_t31_mixer }, @@ -11068,7 +11491,7 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, .unsol_event = alc262_hippo_unsol_event, - .init_hook = alc262_hippo_automute, + .init_hook = alc262_hippo_init_hook, }, [ALC262_ULTRA] = { .mixers = { alc262_ultra_mixer }, @@ -11133,7 +11556,7 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, .unsol_event = alc262_hippo_unsol_event, - .init_hook = alc262_hippo_automute, + .init_hook = alc262_hippo_init_hook, }, [ALC262_TYAN] = { .mixers = { alc262_tyan_mixer }, @@ -11145,8 +11568,8 @@ static struct alc_config_preset alc262_presets[] = { .num_channel_mode = ARRAY_SIZE(alc262_modes), .channel_mode = alc262_modes, .input_mux = &alc262_capture_source, - .unsol_event = alc262_tyan_unsol_event, - .init_hook = alc262_tyan_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc262_tyan_init_hook, }, }; @@ -11181,8 +11604,8 @@ static int patch_alc262(struct hda_codec *codec) alc262_cfg_tbl); if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC262, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC262_AUTO; } @@ -11211,11 +11634,9 @@ static int patch_alc262(struct hda_codec *codec) if (board_config != ALC262_AUTO) setup_preset(spec, &alc262_presets[board_config]); - spec->stream_name_analog = "ALC262 Analog"; spec->stream_analog_playback = &alc262_pcm_analog_playback; spec->stream_analog_capture = &alc262_pcm_analog_capture; - spec->stream_name_digital = "ALC262 Digital"; spec->stream_digital_playback = &alc262_pcm_digital_playback; spec->stream_digital_capture = &alc262_pcm_digital_capture; @@ -11290,6 +11711,17 @@ static struct snd_kcontrol_new alc268_base_mixer[] = { { } }; +static struct snd_kcontrol_new alc268_toshiba_mixer[] = { + /* output mixer control */ + HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT), + ALC262_HIPPO_MASTER_SWITCH, + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT), + { } +}; + /* bind Beep switches of both NID 0x0f and 0x10 */ static struct hda_bind_ctls alc268_bind_beep_sw = { .ops = &snd_hda_bind_sw, @@ -11313,8 +11745,6 @@ static struct hda_verb alc268_eapd_verbs[] = { }; /* Toshiba specific */ -#define alc268_toshiba_automute alc262_hippo_automute - static struct hda_verb alc268_toshiba_verbs[] = { {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, { } /* end */ @@ -11450,13 +11880,8 @@ static struct hda_verb alc268_acer_verbs[] = { }; /* unsolicited event for HP jack sensing */ -static void alc268_toshiba_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc268_toshiba_automute(codec); -} +#define alc268_toshiba_unsol_event alc262_hippo_unsol_event +#define alc268_toshiba_init_hook alc262_hippo_init_hook static void alc268_acer_unsol_event(struct hda_codec *codec, unsigned int res) @@ -11531,30 +11956,15 @@ static struct hda_verb alc268_dell_verbs[] = { }; /* mute/unmute internal speaker according to the hp jack and mute state */ -static void alc268_dell_automute(struct hda_codec *codec) +static void alc268_dell_init_hook(struct hda_codec *codec) { - unsigned int present; - unsigned int mute; + struct alc_spec *spec = codec->spec; - present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0); - if (present & 0x80000000) - mute = HDA_AMP_MUTE; - else - mute = snd_hda_codec_amp_read(codec, 0x15, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_pin(codec); } -static void alc268_dell_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) != ALC880_HP_EVENT) - return; - alc268_dell_automute(codec); -} - -#define alc268_dell_init_hook alc268_dell_automute - static struct snd_kcontrol_new alc267_quanta_il1_mixer[] = { HDA_CODEC_VOLUME("Speaker Playback Volume", 0x2, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), @@ -11573,16 +11983,6 @@ static struct hda_verb alc267_quanta_il1_verbs[] = { { } }; -static void alc267_quanta_il1_hp_automute(struct hda_codec *codec) -{ - unsigned int present; - - present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0) - & AC_PINSENSE_PRESENCE; - snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, - present ? 0 : PIN_OUT); -} - static void alc267_quanta_il1_mic_automute(struct hda_codec *codec) { unsigned int present; @@ -11594,9 +11994,13 @@ static void alc267_quanta_il1_mic_automute(struct hda_codec *codec) present ? 0x00 : 0x01); } -static void alc267_quanta_il1_automute(struct hda_codec *codec) +static void alc267_quanta_il1_init_hook(struct hda_codec *codec) { - alc267_quanta_il1_hp_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_pin(codec); alc267_quanta_il1_mic_automute(codec); } @@ -11604,12 +12008,12 @@ static void alc267_quanta_il1_unsol_event(struct hda_codec *codec, unsigned int res) { switch (res >> 26) { - case ALC880_HP_EVENT: - alc267_quanta_il1_hp_automute(codec); - break; case ALC880_MIC_EVENT: alc267_quanta_il1_mic_automute(codec); break; + default: + alc_sku_unsol_event(codec, res); + break; } } @@ -11954,7 +12358,7 @@ static void alc268_auto_init_mono_speaker_out(struct hda_codec *codec) AC_VERB_SET_AMP_GAIN_MUTE, dac_vol2); } -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc268_pcm_analog_playback alc880_pcm_analog_playback #define alc268_pcm_analog_capture alc880_pcm_analog_capture #define alc268_pcm_analog_alt_capture alc880_pcm_analog_alt_capture @@ -12057,15 +12461,16 @@ static struct snd_pci_quirk alc268_cfg_tbl[] = { ALC268_ACER_ASPIRE_ONE), SND_PCI_QUIRK(0x1028, 0x0253, "Dell OEM", ALC268_DELL), SND_PCI_QUIRK(0x1028, 0x02b0, "Dell Inspiron Mini9", ALC268_DELL), - SND_PCI_QUIRK(0x103c, 0x30cc, "TOSHIBA", ALC268_TOSHIBA), + SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3000, "HP TX25xx series", + ALC268_TOSHIBA), SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC268_3ST), - SND_PCI_QUIRK(0x1179, 0xff10, "TOSHIBA A205", ALC268_TOSHIBA), - SND_PCI_QUIRK(0x1179, 0xff50, "TOSHIBA A305", ALC268_TOSHIBA), - SND_PCI_QUIRK(0x1179, 0xff64, "TOSHIBA L305", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x1170, 0x0040, "ZEPTO", ALC268_ZEPTO), + SND_PCI_QUIRK_MASK(0x1179, 0xff00, 0xff00, "TOSHIBA A/Lx05", + ALC268_TOSHIBA), SND_PCI_QUIRK(0x14c0, 0x0025, "COMPAL IFL90/JFL-92", ALC268_TOSHIBA), SND_PCI_QUIRK(0x152d, 0x0763, "Diverse (CPR2000)", ALC268_ACER), SND_PCI_QUIRK(0x152d, 0x0771, "Quanta IL1", ALC267_QUANTA_IL1), - SND_PCI_QUIRK(0x1170, 0x0040, "ZEPTO", ALC268_ZEPTO), + SND_PCI_QUIRK(0x1854, 0x1775, "LG R510", ALC268_DELL), {} }; @@ -12083,7 +12488,7 @@ static struct alc_config_preset alc268_presets[] = { .channel_mode = alc268_modes, .input_mux = &alc268_capture_source, .unsol_event = alc267_quanta_il1_unsol_event, - .init_hook = alc267_quanta_il1_automute, + .init_hook = alc267_quanta_il1_init_hook, }, [ALC268_3ST] = { .mixers = { alc268_base_mixer, alc268_capture_alt_mixer, @@ -12101,7 +12506,7 @@ static struct alc_config_preset alc268_presets[] = { .input_mux = &alc268_capture_source, }, [ALC268_TOSHIBA] = { - .mixers = { alc268_base_mixer, alc268_capture_alt_mixer, + .mixers = { alc268_toshiba_mixer, alc268_capture_alt_mixer, alc268_beep_mixer }, .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, alc268_toshiba_verbs }, @@ -12115,7 +12520,7 @@ static struct alc_config_preset alc268_presets[] = { .channel_mode = alc268_modes, .input_mux = &alc268_capture_source, .unsol_event = alc268_toshiba_unsol_event, - .init_hook = alc268_toshiba_automute, + .init_hook = alc268_toshiba_init_hook, }, [ALC268_ACER] = { .mixers = { alc268_acer_mixer, alc268_capture_alt_mixer, @@ -12178,7 +12583,7 @@ static struct alc_config_preset alc268_presets[] = { .hp_nid = 0x02, .num_channel_mode = ARRAY_SIZE(alc268_modes), .channel_mode = alc268_modes, - .unsol_event = alc268_dell_unsol_event, + .unsol_event = alc_sku_unsol_event, .init_hook = alc268_dell_init_hook, .input_mux = &alc268_capture_source, }, @@ -12198,7 +12603,7 @@ static struct alc_config_preset alc268_presets[] = { .channel_mode = alc268_modes, .input_mux = &alc268_capture_source, .unsol_event = alc268_toshiba_unsol_event, - .init_hook = alc268_toshiba_automute + .init_hook = alc268_toshiba_init_hook }, #ifdef CONFIG_SND_DEBUG [ALC268_TEST] = { @@ -12236,8 +12641,8 @@ static int patch_alc268(struct hda_codec *codec) alc268_cfg_tbl); if (board_config < 0 || board_config >= ALC268_MODEL_LAST) { - printk(KERN_INFO "hda_codec: Unknown model for ALC268, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC268_AUTO; } @@ -12258,14 +12663,6 @@ static int patch_alc268(struct hda_codec *codec) if (board_config != ALC268_AUTO) setup_preset(spec, &alc268_presets[board_config]); - if (codec->vendor_id == 0x10ec0267) { - spec->stream_name_analog = "ALC267 Analog"; - spec->stream_name_digital = "ALC267 Digital"; - } else { - spec->stream_name_analog = "ALC268 Analog"; - spec->stream_name_digital = "ALC268 Digital"; - } - spec->stream_analog_playback = &alc268_pcm_analog_playback; spec->stream_analog_capture = &alc268_pcm_analog_capture; spec->stream_analog_alt_capture = &alc268_pcm_analog_alt_capture; @@ -12872,7 +13269,7 @@ static int alc269_auto_create_analog_input_ctls(struct alc_spec *spec, #define alc269_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc269_pcm_analog_playback alc880_pcm_analog_playback #define alc269_pcm_analog_capture alc880_pcm_analog_capture #define alc269_pcm_digital_playback alc880_pcm_digital_playback @@ -13092,8 +13489,8 @@ static int patch_alc269(struct hda_codec *codec) alc269_cfg_tbl); if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC269, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC269_AUTO; } @@ -13120,7 +13517,6 @@ static int patch_alc269(struct hda_codec *codec) if (board_config != ALC269_AUTO) setup_preset(spec, &alc269_presets[board_config]); - spec->stream_name_analog = "ALC269 Analog"; if (codec->subsystem_id == 0x17aa3bf8) { /* Due to a hardware problem on Lenovo Ideadpad, we need to * fix the sample rate of analog I/O to 44.1kHz @@ -13131,7 +13527,6 @@ static int patch_alc269(struct hda_codec *codec) spec->stream_analog_playback = &alc269_pcm_analog_playback; spec->stream_analog_capture = &alc269_pcm_analog_capture; } - spec->stream_name_digital = "ALC269 Digital"; spec->stream_digital_playback = &alc269_pcm_digital_playback; spec->stream_digital_capture = &alc269_pcm_digital_capture; @@ -13736,7 +14131,7 @@ static void alc861_toshiba_unsol_event(struct hda_codec *codec, alc861_toshiba_automute(codec); } -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc861_pcm_analog_playback alc880_pcm_analog_playback #define alc861_pcm_analog_capture alc880_pcm_analog_capture #define alc861_pcm_digital_playback alc880_pcm_digital_playback @@ -13920,7 +14315,6 @@ static void alc861_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x0e, 0x0f, 0x0b); for (i = 0; i < spec->autocfg.line_outs; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -14003,6 +14397,8 @@ static int alc861_parse_auto_config(struct hda_codec *codec) spec->num_adc_nids = ARRAY_SIZE(alc861_adc_nids); set_capture_mixer(spec); + alc_ssid_check(codec, 0x0e, 0x0f, 0x0b); + return 1; } @@ -14192,8 +14588,8 @@ static int patch_alc861(struct hda_codec *codec) alc861_cfg_tbl); if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC861, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC861_AUTO; } @@ -14220,11 +14616,9 @@ static int patch_alc861(struct hda_codec *codec) if (board_config != ALC861_AUTO) setup_preset(spec, &alc861_presets[board_config]); - spec->stream_name_analog = "ALC861 Analog"; spec->stream_analog_playback = &alc861_pcm_analog_playback; spec->stream_analog_capture = &alc861_pcm_analog_capture; - spec->stream_name_digital = "ALC861 Digital"; spec->stream_digital_playback = &alc861_pcm_digital_playback; spec->stream_digital_capture = &alc861_pcm_digital_capture; @@ -14260,7 +14654,7 @@ static hda_nid_t alc861vd_dac_nids[4] = { /* dac_nids for ALC660vd are in a different order - according to * Realtek's driver. - * This should probably tesult in a different mixer for 6stack models + * This should probably result in a different mixer for 6stack models * of ALC660vd codecs, but for now there is only 3stack mixer * - and it is the same as in 861vd. * adc_nids in ALC660vd are (is) the same as in 861vd @@ -14611,19 +15005,6 @@ static struct hda_verb alc861vd_lenovo_unsol_verbs[] = { {} }; -/* toggle speaker-output according to the hp-jack state */ -static void alc861vd_lenovo_hp_automute(struct hda_codec *codec) -{ - unsigned int present; - unsigned char bits; - - present = snd_hda_codec_read(codec, 0x1b, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - bits = present ? HDA_AMP_MUTE : 0; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, bits); -} - static void alc861vd_lenovo_mic_automute(struct hda_codec *codec) { unsigned int present; @@ -14636,9 +15017,13 @@ static void alc861vd_lenovo_mic_automute(struct hda_codec *codec) HDA_AMP_MUTE, bits); } -static void alc861vd_lenovo_automute(struct hda_codec *codec) +static void alc861vd_lenovo_init_hook(struct hda_codec *codec) { - alc861vd_lenovo_hp_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x1b; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); alc861vd_lenovo_mic_automute(codec); } @@ -14646,12 +15031,12 @@ static void alc861vd_lenovo_unsol_event(struct hda_codec *codec, unsigned int res) { switch (res >> 26) { - case ALC880_HP_EVENT: - alc861vd_lenovo_hp_automute(codec); - break; case ALC880_MIC_EVENT: alc861vd_lenovo_mic_automute(codec); break; + default: + alc_automute_amp_unsol_event(codec, res); + break; } } @@ -14701,27 +15086,20 @@ static struct hda_verb alc861vd_dallas_verbs[] = { }; /* toggle speaker-output according to the hp-jack state */ -static void alc861vd_dallas_automute(struct hda_codec *codec) +static void alc861vd_dallas_init_hook(struct hda_codec *codec) { - unsigned int present; - - present = snd_hda_codec_read(codec, 0x15, 0, - AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; - snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, - HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); -} + struct alc_spec *spec = codec->spec; -static void alc861vd_dallas_unsol_event(struct hda_codec *codec, unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc861vd_dallas_automute(codec); + spec->autocfg.hp_pins[0] = 0x15; + spec->autocfg.speaker_pins[0] = 0x14; + alc_automute_amp(codec); } #ifdef CONFIG_SND_HDA_POWER_SAVE #define alc861vd_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc861vd_pcm_analog_playback alc880_pcm_analog_playback #define alc861vd_pcm_analog_capture alc880_pcm_analog_capture #define alc861vd_pcm_digital_playback alc880_pcm_digital_playback @@ -14828,7 +15206,7 @@ static struct alc_config_preset alc861vd_presets[] = { .channel_mode = alc861vd_3stack_2ch_modes, .input_mux = &alc861vd_capture_source, .unsol_event = alc861vd_lenovo_unsol_event, - .init_hook = alc861vd_lenovo_automute, + .init_hook = alc861vd_lenovo_init_hook, }, [ALC861VD_DALLAS] = { .mixers = { alc861vd_dallas_mixer }, @@ -14838,8 +15216,8 @@ static struct alc_config_preset alc861vd_presets[] = { .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), .channel_mode = alc861vd_3stack_2ch_modes, .input_mux = &alc861vd_dallas_capture_source, - .unsol_event = alc861vd_dallas_unsol_event, - .init_hook = alc861vd_dallas_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc861vd_dallas_init_hook, }, [ALC861VD_HP] = { .mixers = { alc861vd_hp_mixer }, @@ -14850,8 +15228,8 @@ static struct alc_config_preset alc861vd_presets[] = { .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), .channel_mode = alc861vd_3stack_2ch_modes, .input_mux = &alc861vd_hp_capture_source, - .unsol_event = alc861vd_dallas_unsol_event, - .init_hook = alc861vd_dallas_automute, + .unsol_event = alc_automute_amp_unsol_event, + .init_hook = alc861vd_dallas_init_hook, }, [ALC660VD_ASUS_V1S] = { .mixers = { alc861vd_lenovo_mixer }, @@ -14866,7 +15244,7 @@ static struct alc_config_preset alc861vd_presets[] = { .channel_mode = alc861vd_3stack_2ch_modes, .input_mux = &alc861vd_capture_source, .unsol_event = alc861vd_lenovo_unsol_event, - .init_hook = alc861vd_lenovo_automute, + .init_hook = alc861vd_lenovo_init_hook, }, }; @@ -14884,7 +15262,6 @@ static void alc861vd_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x15, 0x1b, 0x14); for (i = 0; i <= HDA_SIDE; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -14901,7 +15278,7 @@ static void alc861vd_auto_init_hp_out(struct hda_codec *codec) hda_nid_t pin; pin = spec->autocfg.hp_pins[0]; - if (pin) /* connect to front and use dac 0 */ + if (pin) /* connect to front and use dac 0 */ alc861vd_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); pin = spec->autocfg.speaker_pins[0]; if (pin) @@ -15102,6 +15479,8 @@ static int alc861vd_parse_auto_config(struct hda_codec *codec) if (err < 0) return err; + alc_ssid_check(codec, 0x15, 0x1b, 0x14); + return 1; } @@ -15133,8 +15512,8 @@ static int patch_alc861vd(struct hda_codec *codec) alc861vd_cfg_tbl); if (board_config < 0 || board_config >= ALC861VD_MODEL_LAST) { - printk(KERN_INFO "hda_codec: Unknown model for ALC660VD/" - "ALC861VD, trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC861VD_AUTO; } @@ -15162,13 +15541,8 @@ static int patch_alc861vd(struct hda_codec *codec) setup_preset(spec, &alc861vd_presets[board_config]); if (codec->vendor_id == 0x10ec0660) { - spec->stream_name_analog = "ALC660-VD Analog"; - spec->stream_name_digital = "ALC660-VD Digital"; /* always turn on EAPD */ add_verb(spec, alc660vd_eapd_verbs); - } else { - spec->stream_name_analog = "ALC861VD Analog"; - spec->stream_name_digital = "ALC861VD Digital"; } spec->stream_analog_playback = &alc861vd_pcm_analog_playback; @@ -15282,6 +15656,38 @@ static struct hda_input_mux alc663_m51va_capture_source = { }, }; +#if 1 /* set to 0 for testing other input sources below */ +static struct hda_input_mux alc272_nc10_capture_source = { + .num_items = 2, + .items = { + { "Autoselect Mic", 0x0 }, + { "Internal Mic", 0x1 }, + }, +}; +#else +static struct hda_input_mux alc272_nc10_capture_source = { + .num_items = 16, + .items = { + { "Autoselect Mic", 0x0 }, + { "Internal Mic", 0x1 }, + { "In-0x02", 0x2 }, + { "In-0x03", 0x3 }, + { "In-0x04", 0x4 }, + { "In-0x05", 0x5 }, + { "In-0x06", 0x6 }, + { "In-0x07", 0x7 }, + { "In-0x08", 0x8 }, + { "In-0x09", 0x9 }, + { "In-0x0a", 0x0a }, + { "In-0x0b", 0x0b }, + { "In-0x0c", 0x0c }, + { "In-0x0d", 0x0d }, + { "In-0x0e", 0x0e }, + { "In-0x0f", 0x0f }, + }, +}; +#endif + /* * 2ch mode */ @@ -15421,10 +15827,8 @@ static struct snd_kcontrol_new alc662_lenovo_101e_mixer[] = { }; static struct snd_kcontrol_new alc662_eeepc_p701_mixer[] = { - HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), - - HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x02, 0x0, HDA_OUTPUT), - HDA_CODEC_MUTE("Line-Out Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT), + ALC262_HIPPO_MASTER_SWITCH, HDA_CODEC_VOLUME("e-Mic Boost", 0x18, 0, HDA_INPUT), HDA_CODEC_VOLUME("e-Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), @@ -15437,15 +15841,11 @@ static struct snd_kcontrol_new alc662_eeepc_p701_mixer[] = { }; static struct snd_kcontrol_new alc662_eeepc_ep20_mixer[] = { - HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x02, 0x0, HDA_OUTPUT), - HDA_CODEC_MUTE("Line-Out Playback Switch", 0x14, 0x0, HDA_OUTPUT), + ALC262_HIPPO_MASTER_SWITCH, + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), - HDA_BIND_MUTE("Surround Playback Switch", 0x03, 2, HDA_INPUT), HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT), HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT), - HDA_BIND_MUTE_MONO("Center Playback Switch", 0x04, 1, 2, HDA_INPUT), - HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x04, 2, 2, HDA_INPUT), - HDA_CODEC_MUTE("Speaker Playback Switch", 0x1b, 0x0, HDA_OUTPUT), HDA_BIND_MUTE("MuteCtrl Playback Switch", 0x0c, 2, HDA_INPUT), HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), @@ -15953,51 +16353,25 @@ static void alc662_eeepc_mic_automute(struct hda_codec *codec) static void alc662_eeepc_unsol_event(struct hda_codec *codec, unsigned int res) { - if ((res >> 26) == ALC880_HP_EVENT) - alc262_hippo1_automute( codec ); - if ((res >> 26) == ALC880_MIC_EVENT) alc662_eeepc_mic_automute(codec); + else + alc262_hippo_unsol_event(codec, res); } static void alc662_eeepc_inithook(struct hda_codec *codec) { - alc262_hippo1_automute( codec ); + alc262_hippo1_init_hook(codec); alc662_eeepc_mic_automute(codec); } -static void alc662_eeepc_ep20_automute(struct hda_codec *codec) -{ - unsigned int mute; - unsigned int present; - - snd_hda_codec_read(codec, 0x14, 0, AC_VERB_SET_PIN_SENSE, 0); - present = snd_hda_codec_read(codec, 0x14, 0, - AC_VERB_GET_PIN_SENSE, 0); - present = (present & 0x80000000) != 0; - if (present) { - /* mute internal speaker */ - snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, - HDA_AMP_MUTE, HDA_AMP_MUTE); - } else { - /* unmute internal speaker if necessary */ - mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0); - snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, - HDA_AMP_MUTE, mute); - } -} - -/* unsolicited event for HP jack sensing */ -static void alc662_eeepc_ep20_unsol_event(struct hda_codec *codec, - unsigned int res) -{ - if ((res >> 26) == ALC880_HP_EVENT) - alc662_eeepc_ep20_automute(codec); -} - static void alc662_eeepc_ep20_inithook(struct hda_codec *codec) { - alc662_eeepc_ep20_automute(codec); + struct alc_spec *spec = codec->spec; + + spec->autocfg.hp_pins[0] = 0x14; + spec->autocfg.speaker_pins[0] = 0x1b; + alc262_hippo_master_update(codec); } static void alc663_m51va_speaker_automute(struct hda_codec *codec) @@ -16331,35 +16705,9 @@ static void alc663_g50v_inithook(struct hda_codec *codec) alc662_eeepc_mic_automute(codec); } -/* bind hp and internal speaker mute (with plug check) */ -static int alc662_ecs_master_sw_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - long *valp = ucontrol->value.integer.value; - int change; - - change = snd_hda_codec_amp_update(codec, 0x1b, 0, HDA_OUTPUT, 0, - HDA_AMP_MUTE, - valp[0] ? 0 : HDA_AMP_MUTE); - change |= snd_hda_codec_amp_update(codec, 0x1b, 1, HDA_OUTPUT, 0, - HDA_AMP_MUTE, - valp[1] ? 0 : HDA_AMP_MUTE); - if (change) - alc262_hippo1_automute(codec); - return change; -} - static struct snd_kcontrol_new alc662_ecs_mixer[] = { HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT), - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Playback Switch", - .info = snd_hda_mixer_amp_switch_info, - .get = snd_hda_mixer_amp_switch_get, - .put = alc662_ecs_master_sw_put, - .private_value = HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT), - }, + ALC262_HIPPO_MASTER_SWITCH, HDA_CODEC_VOLUME("e-Mic/LineIn Boost", 0x18, 0, HDA_INPUT), HDA_CODEC_VOLUME("e-Mic/LineIn Playback Volume", 0x0b, 0x0, HDA_INPUT), @@ -16371,12 +16719,29 @@ static struct snd_kcontrol_new alc662_ecs_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc272_nc10_mixer[] = { + /* Master Playback automatically created from Speaker and Headphone */ + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + { } /* end */ +}; + #ifdef CONFIG_SND_HDA_POWER_SAVE #define alc662_loopbacks alc880_loopbacks #endif -/* pcm configuration: identiacal with ALC880 */ +/* pcm configuration: identical with ALC880 */ #define alc662_pcm_analog_playback alc880_pcm_analog_playback #define alc662_pcm_analog_capture alc880_pcm_analog_capture #define alc662_pcm_digital_playback alc880_pcm_digital_playback @@ -16404,6 +16769,9 @@ static const char *alc662_models[ALC662_MODEL_LAST] = { [ALC663_ASUS_MODE4] = "asus-mode4", [ALC663_ASUS_MODE5] = "asus-mode5", [ALC663_ASUS_MODE6] = "asus-mode6", + [ALC272_DELL] = "dell", + [ALC272_DELL_ZM1] = "dell-zm1", + [ALC272_SAMSUNG_NC10] = "samsung-nc10", [ALC662_AUTO] = "auto", }; @@ -16461,6 +16829,7 @@ static struct snd_pci_quirk alc662_cfg_tbl[] = { SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_ECS), SND_PCI_QUIRK(0x105b, 0x0d47, "Foxconn 45CMX/45GMX/45CMX-K", ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x144d, 0xca00, "Samsung NC10", ALC272_SAMSUNG_NC10), SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte 945GCM-S2L", ALC662_3ST_6ch_DIG), SND_PCI_QUIRK(0x1565, 0x820f, "Biostar TA780G M2+", ALC662_3ST_6ch_DIG), @@ -16551,7 +16920,7 @@ static struct alc_config_preset alc662_presets[] = { .num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes), .channel_mode = alc662_3ST_6ch_modes, .input_mux = &alc662_lenovo_101e_capture_source, - .unsol_event = alc662_eeepc_ep20_unsol_event, + .unsol_event = alc662_eeepc_unsol_event, .init_hook = alc662_eeepc_ep20_inithook, }, [ALC662_ECS] = { @@ -16732,6 +17101,18 @@ static struct alc_config_preset alc662_presets[] = { .unsol_event = alc663_m51va_unsol_event, .init_hook = alc663_m51va_inithook, }, + [ALC272_SAMSUNG_NC10] = { + .mixers = { alc272_nc10_mixer }, + .init_verbs = { alc662_init_verbs, + alc663_21jd_amic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc272_dac_nids), + .dac_nids = alc272_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc272_nc10_capture_source, + .unsol_event = alc663_mode4_unsol_event, + .init_hook = alc663_mode4_inithook, + }, }; @@ -16926,7 +17307,6 @@ static void alc662_auto_init_multi_out(struct hda_codec *codec) struct alc_spec *spec = codec->spec; int i; - alc_subsystem_id(codec, 0x15, 0x1b, 0x14); for (i = 0; i <= HDA_SIDE; i++) { hda_nid_t nid = spec->autocfg.line_out_pins[i]; int pin_type = get_pin_type(spec->autocfg.line_out_type); @@ -17023,6 +17403,8 @@ static int alc662_parse_auto_config(struct hda_codec *codec) if (err < 0) return err; + alc_ssid_check(codec, 0x15, 0x1b, 0x14); + return 1; } @@ -17055,8 +17437,8 @@ static int patch_alc662(struct hda_codec *codec) alc662_models, alc662_cfg_tbl); if (board_config < 0) { - printk(KERN_INFO "hda_codec: Unknown model for ALC662, " - "trying auto-probe from BIOS...\n"); + printk(KERN_INFO "hda_codec: Unknown model for %s, " + "trying auto-probe from BIOS...\n", codec->chip_name); board_config = ALC662_AUTO; } @@ -17083,17 +17465,6 @@ static int patch_alc662(struct hda_codec *codec) if (board_config != ALC662_AUTO) setup_preset(spec, &alc662_presets[board_config]); - if (codec->vendor_id == 0x10ec0663) { - spec->stream_name_analog = "ALC663 Analog"; - spec->stream_name_digital = "ALC663 Digital"; - } else if (codec->vendor_id == 0x10ec0272) { - spec->stream_name_analog = "ALC272 Analog"; - spec->stream_name_digital = "ALC272 Digital"; - } else { - spec->stream_name_analog = "ALC662 Analog"; - spec->stream_name_digital = "ALC662 Digital"; - } - spec->stream_analog_playback = &alc662_pcm_analog_playback; spec->stream_analog_capture = &alc662_pcm_analog_capture; diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 917bc5d3ac2..93e47c96a38 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -100,6 +100,7 @@ enum { STAC_HP_M4, STAC_HP_DV5, STAC_HP_HDX, + STAC_HP_DV4_1222NR, STAC_92HD71BXX_MODELS }; @@ -150,6 +151,7 @@ enum { STAC_D965_REF, STAC_D965_3ST, STAC_D965_5ST, + STAC_D965_5ST_NO_FP, STAC_DELL_3ST, STAC_DELL_BIOS, STAC_927X_MODELS @@ -192,6 +194,7 @@ struct sigmatel_spec { unsigned int gpio_dir; unsigned int gpio_data; unsigned int gpio_mute; + unsigned int gpio_led; /* stream */ unsigned int stream_delay; @@ -633,6 +636,40 @@ static int stac92xx_smux_enum_put(struct snd_kcontrol *kcontrol, return 0; } +static unsigned int stac92xx_vref_set(struct hda_codec *codec, + hda_nid_t nid, unsigned int new_vref) +{ + unsigned int error; + unsigned int pincfg; + pincfg = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + pincfg &= 0xff; + pincfg &= ~(AC_PINCTL_VREFEN | AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN); + pincfg |= new_vref; + + if (new_vref == AC_PINCTL_VREF_HIZ) + pincfg |= AC_PINCTL_OUT_EN; + else + pincfg |= AC_PINCTL_IN_EN; + + error = snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pincfg); + if (error < 0) + return error; + else + return 1; +} + +static unsigned int stac92xx_vref_get(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int vref; + vref = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + vref &= AC_PINCTL_VREFEN; + return vref; +} + static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); @@ -994,6 +1031,17 @@ static struct hda_verb stac9205_core_init[] = { .private_value = verb_read | (verb_write << 16), \ } +#define DC_BIAS(xname, idx, nid) \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = idx, \ + .info = stac92xx_dc_bias_info, \ + .get = stac92xx_dc_bias_get, \ + .put = stac92xx_dc_bias_put, \ + .private_value = nid, \ + } + static struct snd_kcontrol_new stac9200_mixer[] = { HDA_CODEC_VOLUME("Master Playback Volume", 0xb, 0, HDA_OUTPUT), HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT), @@ -1542,6 +1590,8 @@ static struct snd_pci_quirk stac9200_cfg_tbl[] = { /* SigmaTel reference board */ SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xfb30, + "SigmaTel",STAC_9205_REF), SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, "DFI LanParty", STAC_REF), /* Dell laptops have BIOS problem */ @@ -1836,6 +1886,7 @@ static unsigned int *stac92hd71bxx_brd_tbl[STAC_92HD71BXX_MODELS] = { [STAC_HP_M4] = NULL, [STAC_HP_DV5] = NULL, [STAC_HP_HDX] = NULL, + [STAC_HP_DV4_1222NR] = NULL, }; static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = { @@ -1847,6 +1898,7 @@ static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = { [STAC_HP_M4] = "hp-m4", [STAC_HP_DV5] = "hp-dv5", [STAC_HP_HDX] = "hp-hdx", + [STAC_HP_DV4_1222NR] = "hp-dv4-1222nr", }; static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = { @@ -1855,6 +1907,8 @@ static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = { "DFI LanParty", STAC_92HD71BXX_REF), SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, "DFI LanParty", STAC_92HD71BXX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30fb, + "HP dv4-1222nr", STAC_HP_DV4_1222NR), SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3080, "HP", STAC_HP_DV5), SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x30f0, @@ -2154,6 +2208,13 @@ static unsigned int d965_5st_pin_configs[14] = { 0x40000100, 0x40000100 }; +static unsigned int d965_5st_no_fp_pin_configs[14] = { + 0x40000100, 0x40000100, 0x0181304e, 0x01014010, + 0x01a19040, 0x01011012, 0x01016011, 0x40000100, + 0x40000100, 0x40000100, 0x40000100, 0x01442070, + 0x40000100, 0x40000100 +}; + static unsigned int dell_3st_pin_configs[14] = { 0x02211230, 0x02a11220, 0x01a19040, 0x01114210, 0x01111212, 0x01116211, 0x01813050, 0x01112214, @@ -2166,6 +2227,7 @@ static unsigned int *stac927x_brd_tbl[STAC_927X_MODELS] = { [STAC_D965_REF] = ref927x_pin_configs, [STAC_D965_3ST] = d965_3st_pin_configs, [STAC_D965_5ST] = d965_5st_pin_configs, + [STAC_D965_5ST_NO_FP] = d965_5st_no_fp_pin_configs, [STAC_DELL_3ST] = dell_3st_pin_configs, [STAC_DELL_BIOS] = NULL, }; @@ -2176,6 +2238,7 @@ static const char *stac927x_models[STAC_927X_MODELS] = { [STAC_D965_REF] = "ref", [STAC_D965_3ST] = "3stack", [STAC_D965_5ST] = "5stack", + [STAC_D965_5ST_NO_FP] = "5stack-no-fp", [STAC_DELL_3ST] = "dell-3stack", [STAC_DELL_BIOS] = "dell-bios", }; @@ -2535,7 +2598,8 @@ static int stac92xx_build_pcms(struct hda_codec *codec) return 0; } -static unsigned int stac92xx_get_vref(struct hda_codec *codec, hda_nid_t nid) +static unsigned int stac92xx_get_default_vref(struct hda_codec *codec, + hda_nid_t nid) { unsigned int pincap = snd_hda_query_pin_caps(codec, nid); pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; @@ -2589,15 +2653,108 @@ static int stac92xx_hp_switch_put(struct snd_kcontrol *kcontrol, return 1; } -#define stac92xx_io_switch_info snd_ctl_boolean_mono_info +static int stac92xx_dc_bias_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int i; + static char *texts[] = { + "Mic In", "Line In", "Line Out" + }; + + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = kcontrol->private_value; + + if (nid == spec->mic_switch || nid == spec->line_switch) + i = 3; + else + i = 2; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->value.enumerated.items = i; + uinfo->count = 1; + if (uinfo->value.enumerated.item >= i) + uinfo->value.enumerated.item = i-1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + + return 0; +} + +static int stac92xx_dc_bias_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int vref = stac92xx_vref_get(codec, nid); + + if (vref == stac92xx_get_default_vref(codec, nid)) + ucontrol->value.enumerated.item[0] = 0; + else if (vref == AC_PINCTL_VREF_GRD) + ucontrol->value.enumerated.item[0] = 1; + else if (vref == AC_PINCTL_VREF_HIZ) + ucontrol->value.enumerated.item[0] = 2; + + return 0; +} + +static int stac92xx_dc_bias_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int new_vref = 0; + unsigned int error; + hda_nid_t nid = kcontrol->private_value; + + if (ucontrol->value.enumerated.item[0] == 0) + new_vref = stac92xx_get_default_vref(codec, nid); + else if (ucontrol->value.enumerated.item[0] == 1) + new_vref = AC_PINCTL_VREF_GRD; + else if (ucontrol->value.enumerated.item[0] == 2) + new_vref = AC_PINCTL_VREF_HIZ; + else + return 0; + + if (new_vref != stac92xx_vref_get(codec, nid)) { + error = stac92xx_vref_set(codec, nid, new_vref); + return error; + } + + return 0; +} + +static int stac92xx_io_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[2]; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + if (kcontrol->private_value == spec->line_switch) + texts[0] = "Line In"; + else + texts[0] = "Mic In"; + texts[1] = "Line Out"; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->value.enumerated.items = 2; + uinfo->count = 1; + + if (uinfo->value.enumerated.item >= 2) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + + return 0; +} static int stac92xx_io_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct sigmatel_spec *spec = codec->spec; - int io_idx = kcontrol-> private_value & 0xff; + hda_nid_t nid = kcontrol->private_value; + int io_idx = (nid == spec->mic_switch) ? 1 : 0; - ucontrol->value.integer.value[0] = spec->io_switch[io_idx]; + ucontrol->value.enumerated.item[0] = spec->io_switch[io_idx]; return 0; } @@ -2605,9 +2762,9 @@ static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct sigmatel_spec *spec = codec->spec; - hda_nid_t nid = kcontrol->private_value >> 8; - int io_idx = kcontrol-> private_value & 0xff; - unsigned short val = !!ucontrol->value.integer.value[0]; + hda_nid_t nid = kcontrol->private_value; + int io_idx = (nid == spec->mic_switch) ? 1 : 0; + unsigned short val = !!ucontrol->value.enumerated.item[0]; spec->io_switch[io_idx] = val; @@ -2616,7 +2773,7 @@ static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ else { unsigned int pinctl = AC_PINCTL_IN_EN; if (io_idx) /* set VREF for mic */ - pinctl |= stac92xx_get_vref(codec, nid); + pinctl |= stac92xx_get_default_vref(codec, nid); stac92xx_auto_set_pinctl(codec, nid, pinctl); } @@ -2697,7 +2854,8 @@ enum { STAC_CTL_WIDGET_AMP_VOL, STAC_CTL_WIDGET_HP_SWITCH, STAC_CTL_WIDGET_IO_SWITCH, - STAC_CTL_WIDGET_CLFE_SWITCH + STAC_CTL_WIDGET_CLFE_SWITCH, + STAC_CTL_WIDGET_DC_BIAS }; static struct snd_kcontrol_new stac92xx_control_templates[] = { @@ -2709,6 +2867,7 @@ static struct snd_kcontrol_new stac92xx_control_templates[] = { STAC_CODEC_HP_SWITCH(NULL), STAC_CODEC_IO_SWITCH(NULL, 0), STAC_CODEC_CLFE_SWITCH(NULL, 0), + DC_BIAS(NULL, 0, 0), }; /* add dynamic controls */ @@ -2772,6 +2931,34 @@ static struct snd_kcontrol_new stac_input_src_temp = { .put = stac92xx_mux_enum_put, }; +static inline int stac92xx_add_jack_mode_control(struct hda_codec *codec, + hda_nid_t nid, int idx) +{ + int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int control = 0; + struct sigmatel_spec *spec = codec->spec; + char name[22]; + + if (!((get_defcfg_connect(def_conf)) & AC_JACK_PORT_FIXED)) { + if (stac92xx_get_default_vref(codec, nid) == AC_PINCTL_VREF_GRD + && nid == spec->line_switch) + control = STAC_CTL_WIDGET_IO_SWITCH; + else if (snd_hda_query_pin_caps(codec, nid) + & (AC_PINCAP_VREF_GRD << AC_PINCAP_VREF_SHIFT)) + control = STAC_CTL_WIDGET_DC_BIAS; + else if (nid == spec->mic_switch) + control = STAC_CTL_WIDGET_IO_SWITCH; + } + + if (control) { + strcpy(name, auto_pin_cfg_labels[idx]); + return stac92xx_add_control(codec->spec, control, + strcat(name, " Jack Mode"), nid); + } + + return 0; +} + static int stac92xx_add_input_source(struct sigmatel_spec *spec) { struct snd_kcontrol_new *knew; @@ -3134,7 +3321,9 @@ static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg) { struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid; int err; + int idx; err = create_multi_out_ctls(codec, cfg->line_outs, cfg->line_out_pins, spec->multiout.dac_nids, @@ -3151,20 +3340,13 @@ static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec, return err; } - if (spec->line_switch) { - err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH, - "Line In as Output Switch", - spec->line_switch << 8); - if (err < 0) - return err; - } - - if (spec->mic_switch) { - err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH, - "Mic as Output Switch", - (spec->mic_switch << 8) | 1); - if (err < 0) - return err; + for (idx = AUTO_PIN_MIC; idx <= AUTO_PIN_FRONT_LINE; idx++) { + nid = cfg->input_pins[idx]; + if (nid) { + err = stac92xx_add_jack_mode_control(codec, nid, idx); + if (err < 0) + return err; + } } return 0; @@ -3629,6 +3811,8 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out err = snd_hda_attach_beep_device(codec, nid); if (err < 0) return err; + /* IDT/STAC codecs have linear beep tone parameter */ + codec->beep->linear_tone = 1; /* if no beep switch is available, make its own one */ caps = query_amp_caps(codec, nid, HDA_OUTPUT); if (codec->beep && @@ -4072,14 +4256,19 @@ static int stac92xx_init(struct hda_codec *codec) unsigned int pinctl, conf; if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC) { /* for mic pins, force to initialize */ - pinctl = stac92xx_get_vref(codec, nid); + pinctl = stac92xx_get_default_vref(codec, nid); pinctl |= AC_PINCTL_IN_EN; stac92xx_auto_set_pinctl(codec, nid, pinctl); } else { pinctl = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0); /* if PINCTL already set then skip */ - if (!(pinctl & AC_PINCTL_IN_EN)) { + /* Also, if both INPUT and OUTPUT are set, + * it must be a BIOS bug; need to override, too + */ + if (!(pinctl & AC_PINCTL_IN_EN) || + (pinctl & AC_PINCTL_OUT_EN)) { + pinctl &= ~AC_PINCTL_OUT_EN; pinctl |= AC_PINCTL_IN_EN; stac92xx_auto_set_pinctl(codec, nid, pinctl); @@ -4520,17 +4709,19 @@ static int stac92xx_resume(struct hda_codec *codec) return 0; } - /* - * using power check for controlling mute led of HP HDX notebooks + * using power check for controlling mute led of HP notebooks * check for mute state only on Speakers (nid = 0x10) * * For this feature CONFIG_SND_HDA_POWER_SAVE is needed, otherwise * the LED is NOT working properly ! + * + * Changed name to reflect that it now works for any designated + * model, not just HP HDX. */ #ifdef CONFIG_SND_HDA_POWER_SAVE -static int stac92xx_hp_hdx_check_power_status(struct hda_codec *codec, +static int stac92xx_hp_check_power_status(struct hda_codec *codec, hda_nid_t nid) { struct sigmatel_spec *spec = codec->spec; @@ -4538,9 +4729,9 @@ static int stac92xx_hp_hdx_check_power_status(struct hda_codec *codec, if (nid == 0x10) { if (snd_hda_codec_amp_read(codec, nid, 0, HDA_OUTPUT, 0) & HDA_AMP_MUTE) - spec->gpio_data &= ~0x08; /* orange */ + spec->gpio_data &= ~spec->gpio_led; /* orange */ else - spec->gpio_data |= 0x08; /* white */ + spec->gpio_data |= spec->gpio_led; /* white */ stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, @@ -5186,6 +5377,15 @@ again: if (get_wcaps(codec, 0xa) & AC_WCAP_IN_AMP) snd_hda_sequence_write_cache(codec, unmute_init); + /* Some HP machines seem to have unstable codec communications + * especially with ATI fglrx driver. For recovering from the + * CORB/RIRB stall, allow the BUS reset and keep always sync + */ + if (spec->board_config == STAC_HP_DV5) { + codec->bus->sync_write = 1; + codec->bus->allow_bus_reset = 1; + } + spec->aloopback_ctl = stac92hd71bxx_loopback; spec->aloopback_mask = 0x50; spec->aloopback_shift = 0; @@ -5219,6 +5419,15 @@ again: spec->num_smuxes = 0; spec->num_dmuxes = 1; break; + case STAC_HP_DV4_1222NR: + spec->num_dmics = 1; + /* I don't know if it needs 1 or 2 smuxes - will wait for + * bug reports to fix if needed + */ + spec->num_smuxes = 1; + spec->num_dmuxes = 1; + spec->gpio_led = 0x01; + /* fallthrough */ case STAC_HP_DV5: snd_hda_codec_set_pincfg(codec, 0x0d, 0x90170010); stac92xx_auto_set_pinctl(codec, 0x0d, AC_PINCTL_OUT_EN); @@ -5227,22 +5436,21 @@ again: spec->num_dmics = 1; spec->num_dmuxes = 1; spec->num_smuxes = 1; - /* - * For controlling MUTE LED on HP HDX16/HDX18 notebooks, - * the CONFIG_SND_HDA_POWER_SAVE is needed to be set. - */ -#ifdef CONFIG_SND_HDA_POWER_SAVE /* orange/white mute led on GPIO3, orange=0, white=1 */ - spec->gpio_mask |= 0x08; - spec->gpio_dir |= 0x08; - spec->gpio_data |= 0x08; /* set to white */ + spec->gpio_led = 0x08; + break; + } +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (spec->gpio_led) { + spec->gpio_mask |= spec->gpio_led; + spec->gpio_dir |= spec->gpio_led; + spec->gpio_data |= spec->gpio_led; /* register check_power_status callback. */ codec->patch_ops.check_power_status = - stac92xx_hp_hdx_check_power_status; + stac92xx_hp_check_power_status; + } #endif - break; - }; spec->multiout.dac_nids = spec->dac_nids; if (spec->dinput_mux) @@ -5267,7 +5475,7 @@ again: codec->proc_widget_hook = stac92hd7x_proc_hook; return 0; -}; +} static int patch_stac922x(struct hda_codec *codec) { @@ -5422,7 +5630,7 @@ static int patch_stac927x(struct hda_codec *codec) /* correct the device field to SPDIF out */ snd_hda_codec_set_pincfg(codec, 0x21, 0x01442070); break; - }; + } /* configure the analog microphone on some laptops */ snd_hda_codec_set_pincfg(codec, 0x0c, 0x90a79130); /* correct the front output jack as a hp out */ @@ -5732,6 +5940,7 @@ static struct hda_codec_preset snd_hda_preset_sigmatel[] = { { .id = 0x83847661, .name = "CXD9872RD/K", .patch = patch_stac9872 }, { .id = 0x83847662, .name = "STAC9872AK", .patch = patch_stac9872 }, { .id = 0x83847664, .name = "CXD9872AKD", .patch = patch_stac9872 }, + { .id = 0x83847698, .name = "STAC9205", .patch = patch_stac9205 }, { .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 }, { .id = 0x838476a1, .name = "STAC9205D", .patch = patch_stac9205 }, { .id = 0x838476a2, .name = "STAC9204", .patch = patch_stac9205 }, diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index b25a5cc637d..8e004fb6961 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -205,7 +205,7 @@ struct via_spec { /* playback */ struct hda_multi_out multiout; - hda_nid_t extra_dig_out_nid; + hda_nid_t slave_dig_outs[2]; /* capture */ unsigned int num_adc_nids; @@ -731,21 +731,6 @@ static int via_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, return snd_hda_multi_out_dig_close(codec, &spec->multiout); } -/* setup SPDIF output stream */ -static void setup_dig_playback_stream(struct hda_codec *codec, hda_nid_t nid, - unsigned int stream_tag, unsigned int format) -{ - /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */ - if (codec->spdif_ctls & AC_DIG1_ENABLE) - snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, - codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff); - snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); - /* turn on again (if needed) */ - if (codec->spdif_ctls & AC_DIG1_ENABLE) - snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, - codec->spdif_ctls & 0xff); -} - static int via_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, struct hda_codec *codec, unsigned int stream_tag, @@ -753,19 +738,16 @@ static int via_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { struct via_spec *spec = codec->spec; - hda_nid_t nid; - - /* 1st or 2nd S/PDIF */ - if (substream->number == 0) - nid = spec->multiout.dig_out_nid; - else if (substream->number == 1) - nid = spec->extra_dig_out_nid; - else - return -1; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} - mutex_lock(&codec->spdif_mutex); - setup_dig_playback_stream(codec, nid, stream_tag, format); - mutex_unlock(&codec->spdif_mutex); +static int via_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); return 0; } @@ -842,7 +824,8 @@ static struct hda_pcm_stream vt1708_pcm_digital_playback = { .ops = { .open = via_dig_playback_pcm_open, .close = via_dig_playback_pcm_close, - .prepare = via_dig_playback_pcm_prepare + .prepare = via_dig_playback_pcm_prepare, + .cleanup = via_dig_playback_pcm_cleanup }, }; @@ -874,13 +857,6 @@ static int via_build_controls(struct hda_codec *codec) if (err < 0) return err; spec->multiout.share_spdif = 1; - - if (spec->extra_dig_out_nid) { - err = snd_hda_create_spdif_out_ctls(codec, - spec->extra_dig_out_nid); - if (err < 0) - return err; - } } if (spec->dig_in_nid) { err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); @@ -1013,10 +989,6 @@ static void via_unsol_event(struct hda_codec *codec, via_gpio_control(codec); } -static hda_nid_t slave_dig_outs[] = { - 0, -}; - static int via_init(struct hda_codec *codec) { struct via_spec *spec = codec->spec; @@ -1051,8 +1023,9 @@ static int via_init(struct hda_codec *codec) snd_hda_codec_write(codec, spec->autocfg.dig_in_pin, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN); - /* no slave outs */ - codec->slave_dig_outs = slave_dig_outs; + /* assign slave outs */ + if (spec->slave_dig_outs[0]) + codec->slave_dig_outs = spec->slave_dig_outs; return 0; } @@ -2134,7 +2107,8 @@ static struct hda_pcm_stream vt1708B_pcm_digital_playback = { .ops = { .open = via_dig_playback_pcm_open, .close = via_dig_playback_pcm_close, - .prepare = via_dig_playback_pcm_prepare + .prepare = via_dig_playback_pcm_prepare, + .cleanup = via_dig_playback_pcm_cleanup }, }; @@ -2589,14 +2563,15 @@ static struct hda_pcm_stream vt1708S_pcm_analog_capture = { }; static struct hda_pcm_stream vt1708S_pcm_digital_playback = { - .substreams = 2, + .substreams = 1, .channels_min = 2, .channels_max = 2, /* NID is set in via_build_pcms */ .ops = { .open = via_dig_playback_pcm_open, .close = via_dig_playback_pcm_close, - .prepare = via_dig_playback_pcm_prepare + .prepare = via_dig_playback_pcm_prepare, + .cleanup = via_dig_playback_pcm_cleanup }, }; @@ -2805,14 +2780,37 @@ static int vt1708S_auto_create_analog_input_ctls(struct via_spec *spec, return 0; } +/* fill out digital output widgets; one for master and one for slave outputs */ +static void fill_dig_outs(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->autocfg.dig_outs; i++) { + hda_nid_t nid; + int conn; + + nid = spec->autocfg.dig_out_pins[i]; + if (!nid) + continue; + conn = snd_hda_get_connections(codec, nid, &nid, 1); + if (conn < 1) + continue; + if (!spec->multiout.dig_out_nid) + spec->multiout.dig_out_nid = nid; + else { + spec->slave_dig_outs[0] = nid; + break; /* at most two dig outs */ + } + } +} + static int vt1708S_parse_auto_config(struct hda_codec *codec) { struct via_spec *spec = codec->spec; int err; - static hda_nid_t vt1708s_ignore[] = {0x21, 0}; - err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, - vt1708s_ignore); + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); if (err < 0) return err; err = vt1708S_auto_fill_dac_nids(spec, &spec->autocfg); @@ -2833,10 +2831,7 @@ static int vt1708S_parse_auto_config(struct hda_codec *codec) spec->multiout.max_channels = spec->multiout.num_dacs * 2; - if (spec->autocfg.dig_outs) - spec->multiout.dig_out_nid = VT1708S_DIGOUT_NID; - - spec->extra_dig_out_nid = 0x15; + fill_dig_outs(codec); if (spec->kctls.list) spec->mixers[spec->num_mixers++] = spec->kctls.list; @@ -3000,7 +2995,8 @@ static struct hda_pcm_stream vt1702_pcm_digital_playback = { .ops = { .open = via_dig_playback_pcm_open, .close = via_dig_playback_pcm_close, - .prepare = via_dig_playback_pcm_prepare + .prepare = via_dig_playback_pcm_prepare, + .cleanup = via_dig_playback_pcm_cleanup }, }; @@ -3128,10 +3124,8 @@ static int vt1702_parse_auto_config(struct hda_codec *codec) { struct via_spec *spec = codec->spec; int err; - static hda_nid_t vt1702_ignore[] = {0x1C, 0}; - err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, - vt1702_ignore); + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); if (err < 0) return err; err = vt1702_auto_fill_dac_nids(spec, &spec->autocfg); @@ -3152,10 +3146,7 @@ static int vt1702_parse_auto_config(struct hda_codec *codec) spec->multiout.max_channels = spec->multiout.num_dacs * 2; - if (spec->autocfg.dig_outs) - spec->multiout.dig_out_nid = VT1702_DIGOUT_NID; - - spec->extra_dig_out_nid = 0x1B; + fill_dig_outs(codec); if (spec->kctls.list) spec->mixers[spec->num_mixers++] = spec->kctls.list; diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile index f99fe089495..536eae2ccf9 100644 --- a/sound/pci/ice1712/Makefile +++ b/sound/pci/ice1712/Makefile @@ -5,7 +5,7 @@ snd-ice17xx-ak4xxx-objs := ak4xxx.o snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o -snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o +snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o # Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h index fdae6deba16..adc909ec125 100644 --- a/sound/pci/ice1712/ice1712.h +++ b/sound/pci/ice1712/ice1712.h @@ -335,6 +335,7 @@ struct snd_ice1712 { unsigned int force_rdma1:1; /* VT1720/4 - RDMA1 as non-spdif */ unsigned int midi_output:1; /* VT1720/4: MIDI output triggered */ unsigned int midi_input:1; /* VT1720/4: MIDI input triggered */ + unsigned int own_routing:1; /* VT1720/4: use own routing ctls */ unsigned int num_total_dacs; /* total DACs */ unsigned int num_total_adcs; /* total ADCs */ unsigned int cur_rate; /* current rate */ @@ -458,10 +459,17 @@ static inline int snd_ice1712_gpio_read_bits(struct snd_ice1712 *ice, return snd_ice1712_gpio_read(ice) & mask; } +/* route access functions */ +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift); +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift); + int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice); -int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *template, - const struct snd_ak4xxx_private *priv, struct snd_ice1712 *ice); +int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, + const struct snd_akm4xxx *template, + const struct snd_ak4xxx_private *priv, + struct snd_ice1712 *ice); void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice); int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice); diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 128510e77a7..36ade77cf37 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -49,6 +49,7 @@ #include "prodigy192.h" #include "prodigy_hifi.h" #include "juli.h" +#include "maya44.h" #include "phase.h" #include "wtm.h" #include "se.h" @@ -65,6 +66,7 @@ MODULE_SUPPORTED_DEVICE("{" PRODIGY192_DEVICE_DESC PRODIGY_HIFI_DEVICE_DESC JULI_DEVICE_DESC + MAYA44_DEVICE_DESC PHASE_DEVICE_DESC WTM_DEVICE_DESC SE_DEVICE_DESC @@ -626,7 +628,7 @@ static unsigned char stdclock_set_mclk(struct snd_ice1712 *ice, return 0; } -static void snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, +static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force) { unsigned long flags; @@ -634,17 +636,18 @@ static void snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, unsigned int i, old_rate; if (rate > ice->hw_rates->list[ice->hw_rates->count - 1]) - return; + return -EINVAL; + spin_lock_irqsave(&ice->reg_lock, flags); if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) || (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) { /* running? we cannot change the rate now... */ spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return -EBUSY; } if (!force && is_pro_rate_locked(ice)) { spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return (rate == ice->cur_rate) ? 0 : -EBUSY; } old_rate = ice->get_rate(ice); @@ -652,7 +655,7 @@ static void snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, ice->set_rate(ice, rate); else if (rate == ice->cur_rate) { spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return 0; } ice->cur_rate = rate; @@ -674,13 +677,15 @@ static void snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, } if (ice->spdif.ops.setup_rate) ice->spdif.ops.setup_rate(ice, rate); + + return 0; } static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); - int i, chs; + int i, chs, err; chs = params_channels(hw_params); mutex_lock(&ice->open_mutex); @@ -715,7 +720,11 @@ static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream, } } mutex_unlock(&ice->open_mutex); - snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); + + err = snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); + if (err < 0) + return err; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } @@ -848,20 +857,39 @@ static snd_pcm_uframes_t snd_vt1724_pcm_pointer(struct snd_pcm_substream *substr #endif } -static const struct vt1724_pcm_reg vt1724_playback_pro_reg = { +static const struct vt1724_pcm_reg vt1724_pdma0_reg = { .addr = VT1724_MT_PLAYBACK_ADDR, .size = VT1724_MT_PLAYBACK_SIZE, .count = VT1724_MT_PLAYBACK_COUNT, .start = VT1724_PDMA0_START, }; -static const struct vt1724_pcm_reg vt1724_capture_pro_reg = { +static const struct vt1724_pcm_reg vt1724_pdma4_reg = { + .addr = VT1724_MT_PDMA4_ADDR, + .size = VT1724_MT_PDMA4_SIZE, + .count = VT1724_MT_PDMA4_COUNT, + .start = VT1724_PDMA4_START, +}; + +static const struct vt1724_pcm_reg vt1724_rdma0_reg = { .addr = VT1724_MT_CAPTURE_ADDR, .size = VT1724_MT_CAPTURE_SIZE, .count = VT1724_MT_CAPTURE_COUNT, .start = VT1724_RDMA0_START, }; +static const struct vt1724_pcm_reg vt1724_rdma1_reg = { + .addr = VT1724_MT_RDMA1_ADDR, + .size = VT1724_MT_RDMA1_SIZE, + .count = VT1724_MT_RDMA1_COUNT, + .start = VT1724_RDMA1_START, +}; + +#define vt1724_playback_pro_reg vt1724_pdma0_reg +#define vt1724_playback_spdif_reg vt1724_pdma4_reg +#define vt1724_capture_pro_reg vt1724_rdma0_reg +#define vt1724_capture_spdif_reg vt1724_rdma1_reg + static const struct snd_pcm_hardware snd_vt1724_playback_pro = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -1077,20 +1105,6 @@ static int __devinit snd_vt1724_pcm_profi(struct snd_ice1712 *ice, int device) * SPDIF PCM */ -static const struct vt1724_pcm_reg vt1724_playback_spdif_reg = { - .addr = VT1724_MT_PDMA4_ADDR, - .size = VT1724_MT_PDMA4_SIZE, - .count = VT1724_MT_PDMA4_COUNT, - .start = VT1724_PDMA4_START, -}; - -static const struct vt1724_pcm_reg vt1724_capture_spdif_reg = { - .addr = VT1724_MT_RDMA1_ADDR, - .size = VT1724_MT_RDMA1_SIZE, - .count = VT1724_MT_RDMA1_COUNT, - .start = VT1724_RDMA1_START, -}; - /* update spdif control bits; call with reg_lock */ static void update_spdif_bits(struct snd_ice1712 *ice, unsigned int val) { @@ -1963,7 +1977,7 @@ static inline int digital_route_shift(int idx) return idx * 3; } -static int get_route_val(struct snd_ice1712 *ice, int shift) +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift) { unsigned long val; unsigned char eitem; @@ -1982,7 +1996,8 @@ static int get_route_val(struct snd_ice1712 *ice, int shift) return eitem; } -static int put_route_val(struct snd_ice1712 *ice, unsigned int val, int shift) +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift) { unsigned int old_val, nval; int change; @@ -2010,7 +2025,7 @@ static int snd_vt1724_pro_route_analog_get(struct snd_kcontrol *kcontrol, struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); ucontrol->value.enumerated.item[0] = - get_route_val(ice, analog_route_shift(idx)); + snd_ice1724_get_route_val(ice, analog_route_shift(idx)); return 0; } @@ -2019,8 +2034,9 @@ static int snd_vt1724_pro_route_analog_put(struct snd_kcontrol *kcontrol, { struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - return put_route_val(ice, ucontrol->value.enumerated.item[0], - analog_route_shift(idx)); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + analog_route_shift(idx)); } static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol, @@ -2029,7 +2045,7 @@ static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); ucontrol->value.enumerated.item[0] = - get_route_val(ice, digital_route_shift(idx)); + snd_ice1724_get_route_val(ice, digital_route_shift(idx)); return 0; } @@ -2038,11 +2054,13 @@ static int snd_vt1724_pro_route_spdif_put(struct snd_kcontrol *kcontrol, { struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - return put_route_val(ice, ucontrol->value.enumerated.item[0], - digital_route_shift(idx)); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + digital_route_shift(idx)); } -static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route __devinitdata = { +static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route __devinitdata = +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "H/W Playback Route", .info = snd_vt1724_pro_route_info, @@ -2109,6 +2127,7 @@ static struct snd_ice1712_card_info *card_tables[] __devinitdata = { snd_vt1724_prodigy_hifi_cards, snd_vt1724_prodigy192_cards, snd_vt1724_juli_cards, + snd_vt1724_maya44_cards, snd_vt1724_phase_cards, snd_vt1724_wtm_cards, snd_vt1724_se_cards, @@ -2246,8 +2265,10 @@ static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice, static void __devinit snd_vt1724_chip_reset(struct snd_ice1712 *ice) { outb(VT1724_RESET , ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ msleep(10); outb(0, ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ msleep(10); } @@ -2277,9 +2298,12 @@ static int __devinit snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice) if (snd_BUG_ON(!ice->pcm)) return -EIO; - err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice)); - if (err < 0) - return err; + if (!ice->own_routing) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice)); + if (err < 0) + return err; + } err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_spdif_switch, ice)); if (err < 0) @@ -2326,7 +2350,7 @@ static int __devinit snd_vt1724_build_controls(struct snd_ice1712 *ice) if (err < 0) return err; - if (ice->num_total_dacs > 0) { + if (!ice->own_routing && ice->num_total_dacs > 0) { struct snd_kcontrol_new tmp = snd_vt1724_mixer_pro_analog_route; tmp.count = ice->num_total_dacs; if (ice->vt1720 && tmp.count > 2) diff --git a/sound/pci/ice1712/maya44.c b/sound/pci/ice1712/maya44.c new file mode 100644 index 00000000000..3e1c20ae2f1 --- /dev/null +++ b/sound/pci/ice1712/maya44.c @@ -0,0 +1,779 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Maya44 cards + * + * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de> + * Based on the patches by Rainer Zimmermann <mail@lightshed.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/tlv.h> + +#include "ice1712.h" +#include "envy24ht.h" +#include "maya44.h" + +/* WM8776 register indexes */ +#define WM8776_REG_HEADPHONE_L 0x00 +#define WM8776_REG_HEADPHONE_R 0x01 +#define WM8776_REG_HEADPHONE_MASTER 0x02 +#define WM8776_REG_DAC_ATTEN_L 0x03 +#define WM8776_REG_DAC_ATTEN_R 0x04 +#define WM8776_REG_DAC_ATTEN_MASTER 0x05 +#define WM8776_REG_DAC_PHASE 0x06 +#define WM8776_REG_DAC_CONTROL 0x07 +#define WM8776_REG_DAC_MUTE 0x08 +#define WM8776_REG_DAC_DEEMPH 0x09 +#define WM8776_REG_DAC_IF_CONTROL 0x0a +#define WM8776_REG_ADC_IF_CONTROL 0x0b +#define WM8776_REG_MASTER_MODE_CONTROL 0x0c +#define WM8776_REG_POWERDOWN 0x0d +#define WM8776_REG_ADC_ATTEN_L 0x0e +#define WM8776_REG_ADC_ATTEN_R 0x0f +#define WM8776_REG_ADC_ALC1 0x10 +#define WM8776_REG_ADC_ALC2 0x11 +#define WM8776_REG_ADC_ALC3 0x12 +#define WM8776_REG_ADC_NOISE_GATE 0x13 +#define WM8776_REG_ADC_LIMITER 0x14 +#define WM8776_REG_ADC_MUX 0x15 +#define WM8776_REG_OUTPUT_MUX 0x16 +#define WM8776_REG_RESET 0x17 + +#define WM8776_NUM_REGS 0x18 + +/* clock ratio identifiers for snd_wm8776_set_rate() */ +#define WM8776_CLOCK_RATIO_128FS 0 +#define WM8776_CLOCK_RATIO_192FS 1 +#define WM8776_CLOCK_RATIO_256FS 2 +#define WM8776_CLOCK_RATIO_384FS 3 +#define WM8776_CLOCK_RATIO_512FS 4 +#define WM8776_CLOCK_RATIO_768FS 5 + +enum { WM_VOL_HP, WM_VOL_DAC, WM_VOL_ADC, WM_NUM_VOLS }; +enum { WM_SW_DAC, WM_SW_BYPASS, WM_NUM_SWITCHES }; + +struct snd_wm8776 { + unsigned char addr; + unsigned short regs[WM8776_NUM_REGS]; + unsigned char volumes[WM_NUM_VOLS][2]; + unsigned int switch_bits; +}; + +struct snd_maya44 { + struct snd_ice1712 *ice; + struct snd_wm8776 wm[2]; + struct mutex mutex; +}; + + +/* write the given register and save the data to the cache */ +static void wm8776_write(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, unsigned short val) +{ + /* + * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB + * of the address field + */ + snd_vt1724_write_i2c(ice, wm->addr, + (reg << 1) | ((val >> 8) & 1), + val & 0xff); + wm->regs[reg] = val; +} + +/* + * update the given register with and/or mask and save the data to the cache + */ +static int wm8776_write_bits(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, + unsigned short mask, unsigned short val) +{ + val |= wm->regs[reg] & ~mask; + if (val != wm->regs[reg]) { + wm8776_write(ice, wm, reg, val); + return 1; + } + return 0; +} + + +/* + * WM8776 volume controls + */ + +struct maya_vol_info { + unsigned int maxval; /* volume range: 0..maxval */ + unsigned char regs[2]; /* left and right registers */ + unsigned short mask; /* value mask */ + unsigned short offset; /* zero-value offset */ + unsigned short mute; /* mute bit */ + unsigned short update; /* update bits */ + unsigned char mux_bits[2]; /* extra bits for ADC mute */ +}; + +static struct maya_vol_info vol_info[WM_NUM_VOLS] = { + [WM_VOL_HP] = { + .maxval = 80, + .regs = { WM8776_REG_HEADPHONE_L, WM8776_REG_HEADPHONE_R }, + .mask = 0x7f, + .offset = 0x30, + .mute = 0x00, + .update = 0x180, /* update and zero-cross enable */ + }, + [WM_VOL_DAC] = { + .maxval = 255, + .regs = { WM8776_REG_DAC_ATTEN_L, WM8776_REG_DAC_ATTEN_R }, + .mask = 0xff, + .offset = 0x01, + .mute = 0x00, + .update = 0x100, /* zero-cross enable */ + }, + [WM_VOL_ADC] = { + .maxval = 91, + .regs = { WM8776_REG_ADC_ATTEN_L, WM8776_REG_ADC_ATTEN_R }, + .mask = 0xff, + .offset = 0xa5, + .mute = 0xa5, + .update = 0x100, /* update */ + .mux_bits = { 0x80, 0x40 }, /* ADCMUX bits */ + }, +}; + +/* + * dB tables + */ +/* headphone output: mute, -73..+6db (1db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_hp, -7400, 100, 1); +/* DAC output: mute, -127..0db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_dac, -12750, 50, 1); +/* ADC gain: mute, -21..+24db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(db_scale_adc, -2100, 50, 1); + +static int maya_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int idx = kcontrol->private_value; + struct maya_vol_info *vol = &vol_info[idx]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = vol->maxval; + return 0; +} + +static int maya_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = kcontrol->private_value; + + mutex_lock(&chip->mutex); + ucontrol->value.integer.value[0] = wm->volumes[idx][0]; + ucontrol->value.integer.value[1] = wm->volumes[idx][1]; + mutex_unlock(&chip->mutex); + return 0; +} + +static int maya_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = kcontrol->private_value; + struct maya_vol_info *vol = &vol_info[idx]; + unsigned int val, data; + int ch, changed = 0; + + mutex_lock(&chip->mutex); + for (ch = 0; ch < 2; ch++) { + val = ucontrol->value.integer.value[ch]; + if (val > vol->maxval) + val = vol->maxval; + if (val == wm->volumes[idx][ch]) + continue; + if (!val) + data = vol->mute; + else + data = (val - 1) + vol->offset; + data |= vol->update; + changed |= wm8776_write_bits(chip->ice, wm, vol->regs[ch], + vol->mask | vol->update, data); + if (vol->mux_bits[ch]) + wm8776_write_bits(chip->ice, wm, WM8776_REG_ADC_MUX, + vol->mux_bits[ch], + val ? 0 : vol->mux_bits[ch]); + wm->volumes[idx][ch] = val; + } + mutex_unlock(&chip->mutex); + return changed; +} + +/* + * WM8776 switch controls + */ + +#define COMPOSE_SW_VAL(idx, reg, mask) ((idx) | ((reg) << 8) | ((mask) << 16)) +#define GET_SW_VAL_IDX(val) ((val) & 0xff) +#define GET_SW_VAL_REG(val) (((val) >> 8) & 0xff) +#define GET_SW_VAL_MASK(val) (((val) >> 16) & 0xff) + +#define maya_sw_info snd_ctl_boolean_mono_info + +static int maya_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value); + + ucontrol->value.integer.value[0] = (wm->switch_bits >> idx) & 1; + return 0; +} + +static int maya_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + struct snd_wm8776 *wm = + &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)]; + unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value); + unsigned int mask, val; + int changed; + + mutex_lock(&chip->mutex); + mask = 1 << idx; + wm->switch_bits &= ~mask; + val = ucontrol->value.integer.value[0]; + if (val) + wm->switch_bits |= mask; + mask = GET_SW_VAL_MASK(kcontrol->private_value); + changed = wm8776_write_bits(chip->ice, wm, + GET_SW_VAL_REG(kcontrol->private_value), + mask, val ? mask : 0); + mutex_unlock(&chip->mutex); + return changed; +} + +/* + * GPIO pins (known ones for maya44) + */ +#define GPIO_PHANTOM_OFF 2 +#define GPIO_MIC_RELAY 4 +#define GPIO_SPDIF_IN_INV 5 +#define GPIO_MUST_BE_0 7 + +/* + * GPIO switch controls + */ + +#define COMPOSE_GPIO_VAL(shift, inv) ((shift) | ((inv) << 8)) +#define GET_GPIO_VAL_SHIFT(val) ((val) & 0xff) +#define GET_GPIO_VAL_INV(val) (((val) >> 8) & 1) + +static int maya_set_gpio_bits(struct snd_ice1712 *ice, unsigned int mask, + unsigned int bits) +{ + unsigned int data; + data = snd_ice1712_gpio_read(ice); + if ((data & mask) == bits) + return 0; + snd_ice1712_gpio_write(ice, (data & ~mask) | bits); + return 1; +} + +#define maya_gpio_sw_info snd_ctl_boolean_mono_info + +static int maya_gpio_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value); + unsigned int val; + + val = (snd_ice1712_gpio_read(chip->ice) >> shift) & 1; + if (GET_GPIO_VAL_INV(kcontrol->private_value)) + val = !val; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int maya_gpio_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value); + unsigned int val, mask; + int changed; + + mutex_lock(&chip->mutex); + mask = 1 << shift; + val = ucontrol->value.integer.value[0]; + if (GET_GPIO_VAL_INV(kcontrol->private_value)) + val = !val; + val = val ? mask : 0; + changed = maya_set_gpio_bits(chip->ice, mask, val); + mutex_unlock(&chip->mutex); + return changed; +} + +/* + * capture source selection + */ + +/* known working input slots (0-4) */ +#define MAYA_LINE_IN 1 /* in-2 */ +#define MAYA_MIC_IN 4 /* in-5 */ + +static void wm8776_select_input(struct snd_maya44 *chip, int idx, int line) +{ + wm8776_write_bits(chip->ice, &chip->wm[idx], WM8776_REG_ADC_MUX, + 0x1f, 1 << line); +} + +static int maya_rec_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { "Line", "Mic" }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = ARRAY_SIZE(texts); + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int maya_rec_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int sel; + + if (snd_ice1712_gpio_read(chip->ice) & (1 << GPIO_MIC_RELAY)) + sel = 1; + else + sel = 0; + ucontrol->value.enumerated.item[0] = sel; + return 0; +} + +static int maya_rec_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int sel = ucontrol->value.enumerated.item[0]; + int changed; + + mutex_lock(&chip->mutex); + changed = maya_set_gpio_bits(chip->ice, GPIO_MIC_RELAY, + sel ? GPIO_MIC_RELAY : 0); + wm8776_select_input(chip, 0, sel ? MAYA_MIC_IN : MAYA_LINE_IN); + mutex_unlock(&chip->mutex); + return changed; +} + +/* + * Maya44 routing switch settings have different meanings than the standard + * ice1724 switches as defined in snd_vt1724_pro_route_info (ice1724.c). + */ +static int maya_pb_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { + "PCM Out", /* 0 */ + "Input 1", "Input 2", "Input 3", "Input 4" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = ARRAY_SIZE(texts); + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int maya_pb_route_shift(int idx) +{ + static const unsigned char shift[10] = + { 8, 20, 0, 3, 11, 23, 14, 26, 17, 29 }; + return shift[idx % 10]; +} + +static int maya_pb_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + ucontrol->value.enumerated.item[0] = + snd_ice1724_get_route_val(chip->ice, maya_pb_route_shift(idx)); + return 0; +} + +static int maya_pb_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return snd_ice1724_put_route_val(chip->ice, + ucontrol->value.enumerated.item[0], + maya_pb_route_shift(idx)); +} + + +/* + * controls to be added + */ + +static struct snd_kcontrol_new maya_controls[] __devinitdata = { + { + .name = "Crossmix Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_hp }, + .private_value = WM_VOL_HP, + .count = 2, + }, + { + .name = "PCM Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_dac }, + .private_value = WM_VOL_DAC, + .count = 2, + }, + { + .name = "Line Capture Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = maya_vol_info, + .get = maya_vol_get, + .put = maya_vol_put, + .tlv = { .p = db_scale_adc }, + .private_value = WM_VOL_ADC, + .count = 2, + }, + { + .name = "PCM Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_sw_info, + .get = maya_sw_get, + .put = maya_sw_put, + .private_value = COMPOSE_SW_VAL(WM_SW_DAC, + WM8776_REG_OUTPUT_MUX, 0x01), + .count = 2, + }, + { + .name = "Bypass Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_sw_info, + .get = maya_sw_get, + .put = maya_sw_put, + .private_value = COMPOSE_SW_VAL(WM_SW_BYPASS, + WM8776_REG_OUTPUT_MUX, 0x04), + .count = 2, + }, + { + .name = "Capture Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_rec_src_info, + .get = maya_rec_src_get, + .put = maya_rec_src_put, + }, + { + .name = "Mic Phantom Power Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_gpio_sw_info, + .get = maya_gpio_sw_get, + .put = maya_gpio_sw_put, + .private_value = COMPOSE_GPIO_VAL(GPIO_PHANTOM_OFF, 1), + }, + { + .name = "SPDIF Capture Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = maya_gpio_sw_info, + .get = maya_gpio_sw_get, + .put = maya_gpio_sw_put, + .private_value = COMPOSE_GPIO_VAL(GPIO_SPDIF_IN_INV, 1), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Playback Route", + .info = maya_pb_route_info, + .get = maya_pb_route_get, + .put = maya_pb_route_put, + .count = 4, /* FIXME: do controls 5-9 have any meaning? */ + }, +}; + +static int __devinit maya44_add_controls(struct snd_ice1712 *ice) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(maya_controls); i++) { + err = snd_ctl_add(ice->card, snd_ctl_new1(&maya_controls[i], + ice->spec)); + if (err < 0) + return err; + } + return 0; +} + + +/* + * initialize a wm8776 chip + */ +static void __devinit wm8776_init(struct snd_ice1712 *ice, + struct snd_wm8776 *wm, unsigned int addr) +{ + static const unsigned short inits_wm8776[] = { + 0x02, 0x100, /* R2: headphone L+R muted + update */ + 0x05, 0x100, /* R5: DAC output L+R muted + update */ + 0x06, 0x000, /* R6: DAC output phase normal */ + 0x07, 0x091, /* R7: DAC enable zero cross detection, + normal output */ + 0x08, 0x000, /* R8: DAC soft mute off */ + 0x09, 0x000, /* R9: no deemph, DAC zero detect disabled */ + 0x0a, 0x022, /* R10: DAC I2C mode, std polarities, 24bit */ + 0x0b, 0x022, /* R11: ADC I2C mode, std polarities, 24bit, + highpass filter enabled */ + 0x0c, 0x042, /* R12: ADC+DAC slave, ADC+DAC 44,1kHz */ + 0x0d, 0x000, /* R13: all power up */ + 0x0e, 0x100, /* R14: ADC left muted, + enable zero cross detection */ + 0x0f, 0x100, /* R15: ADC right muted, + enable zero cross detection */ + /* R16: ALC...*/ + 0x11, 0x000, /* R17: disable ALC */ + /* R18: ALC...*/ + /* R19: noise gate...*/ + 0x15, 0x000, /* R21: ADC input mux init, mute all inputs */ + 0x16, 0x001, /* R22: output mux, select DAC */ + 0xff, 0xff + }; + + const unsigned short *ptr; + unsigned char reg; + unsigned short data; + + wm->addr = addr; + /* enable DAC output; mute bypass, aux & all inputs */ + wm->switch_bits = (1 << WM_SW_DAC); + + ptr = inits_wm8776; + while (*ptr != 0xff) { + reg = *ptr++; + data = *ptr++; + wm8776_write(ice, wm, reg, data); + } +} + + +/* + * change the rate on the WM8776 codecs. + * this assumes that the VT17xx's rate is changed by the calling function. + * NOTE: even though the WM8776's are running in slave mode and rate + * selection is automatic, we need to call snd_wm8776_set_rate() here + * to make sure some flags are set correctly. + */ +static void set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + struct snd_maya44 *chip = ice->spec; + unsigned int ratio, adc_ratio, val; + int i; + + switch (rate) { + case 192000: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 176400: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 96000: + ratio = WM8776_CLOCK_RATIO_256FS; + break; + case 88200: + ratio = WM8776_CLOCK_RATIO_384FS; + break; + case 48000: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 44100: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 32000: + ratio = WM8776_CLOCK_RATIO_768FS; + break; + case 0: + /* no hint - S/PDIF input is master, simply return */ + return; + default: + snd_BUG(); + return; + } + + /* + * this currently sets the same rate for ADC and DAC, but limits + * ADC rate to 256X (96kHz). For 256X mode (96kHz), this sets ADC + * oversampling to 64x, as recommended by WM8776 datasheet. + * Setting the rate is not really necessary in slave mode. + */ + adc_ratio = ratio; + if (adc_ratio < WM8776_CLOCK_RATIO_256FS) + adc_ratio = WM8776_CLOCK_RATIO_256FS; + + val = adc_ratio; + if (adc_ratio == WM8776_CLOCK_RATIO_256FS) + val |= 8; + val |= ratio << 4; + + mutex_lock(&chip->mutex); + for (i = 0; i < 2; i++) + wm8776_write_bits(ice, &chip->wm[i], + WM8776_REG_MASTER_MODE_CONTROL, + 0x180, val); + mutex_unlock(&chip->mutex); +} + +/* + * supported sample rates (to override the default one) + */ + +static unsigned int rates[] = { + 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000 +}; + +/* playback rates: 32..192 kHz */ +static struct snd_pcm_hw_constraint_list dac_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0 +}; + + +/* + * chip addresses on I2C bus + */ +static unsigned char wm8776_addr[2] __devinitdata = { + 0x34, 0x36, /* codec 0 & 1 */ +}; + +/* + * initialize the chip + */ +static int __devinit maya44_init(struct snd_ice1712 *ice) +{ + int i; + struct snd_maya44 *chip; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + mutex_init(&chip->mutex); + chip->ice = ice; + ice->spec = chip; + + /* initialise codecs */ + ice->num_total_dacs = 4; + ice->num_total_adcs = 4; + ice->akm_codecs = 0; + + for (i = 0; i < 2; i++) { + wm8776_init(ice, &chip->wm[i], wm8776_addr[i]); + wm8776_select_input(chip, i, MAYA_LINE_IN); + } + + /* set card specific rates */ + ice->hw_rates = &dac_rates; + + /* register change rate notifier */ + ice->gpio.set_pro_rate = set_rate; + + /* RDMA1 (2nd input channel) is used for ADC by default */ + ice->force_rdma1 = 1; + + /* have an own routing control */ + ice->own_routing = 1; + + return 0; +} + + +/* + * Maya44 boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static unsigned char maya44_eeprom[] __devinitdata = { + [ICE_EEP2_SYSCONF] = 0x45, + /* clock xin1=49.152MHz, mpu401, 2 stereo ADCs+DACs */ + [ICE_EEP2_ACLINK] = 0x80, + /* I2S */ + [ICE_EEP2_I2S] = 0xf8, + /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, + /* enable spdif out, spdif out supp, spdif-in, ext spdif out */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0xff, + [ICE_EEP2_GPIO_MASK] = 0/*0x9f*/, + [ICE_EEP2_GPIO_MASK1] = 0/*0xff*/, + [ICE_EEP2_GPIO_MASK2] = 0/*0x7f*/, + [ICE_EEP2_GPIO_STATE] = (1 << GPIO_PHANTOM_OFF) | + (1 << GPIO_SPDIF_IN_INV), + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_maya44_cards[] __devinitdata = { + { + .subvendor = VT1724_SUBDEVICE_MAYA44, + .name = "ESI Maya44", + .model = "maya44", + .chip_init = maya44_init, + .build_controls = maya44_add_controls, + .eeprom_size = sizeof(maya44_eeprom), + .eeprom_data = maya44_eeprom, + }, + { } /* terminator */ +}; diff --git a/sound/pci/ice1712/maya44.h b/sound/pci/ice1712/maya44.h new file mode 100644 index 00000000000..eafd03a8f4b --- /dev/null +++ b/sound/pci/ice1712/maya44.h @@ -0,0 +1,10 @@ +#ifndef __SOUND_MAYA44_H +#define __SOUND_MAYA44_H + +#define MAYA44_DEVICE_DESC "{ESI,Maya44}," + +#define VT1724_SUBDEVICE_MAYA44 0x34315441 /* Maya44 */ + +extern struct snd_ice1712_card_info snd_vt1724_maya44_cards[]; + +#endif /* __SOUND_MAYA44_H */ diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index 173bebf9f51..8aa5687f392 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -356,8 +356,6 @@ struct ichdev { unsigned int position; unsigned int pos_shift; unsigned int last_pos; - unsigned long last_pos_jiffies; - unsigned int jiffy_to_bytes; int frags; int lvi; int lvi_frag; @@ -844,7 +842,6 @@ static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: val = ICH_IOCE | ICH_STARTBM; ichdev->last_pos = ichdev->position; - ichdev->last_pos_jiffies = jiffies; break; case SNDRV_PCM_TRIGGER_SUSPEND: ichdev->suspended = 1; @@ -1048,7 +1045,6 @@ static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream) ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1; } snd_intel8x0_setup_periods(chip, ichdev); - ichdev->jiffy_to_bytes = (runtime->rate * 4 * ichdev->pos_shift) / HZ; return 0; } @@ -1073,19 +1069,23 @@ static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *subs ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb)) break; } while (timeout--); + ptr = ichdev->last_pos; if (ptr1 != 0) { ptr1 <<= ichdev->pos_shift; ptr = ichdev->fragsize1 - ptr1; ptr += position; - ichdev->last_pos = ptr; - ichdev->last_pos_jiffies = jiffies; - } else { - ptr1 = jiffies - ichdev->last_pos_jiffies; - if (ptr1) - ptr1 -= 1; - ptr = ichdev->last_pos + ptr1 * ichdev->jiffy_to_bytes; - ptr %= ichdev->size; + if (ptr < ichdev->last_pos) { + unsigned int pos_base, last_base; + pos_base = position / ichdev->fragsize1; + last_base = ichdev->last_pos / ichdev->fragsize1; + /* another sanity check; ptr1 can go back to full + * before the base position is updated + */ + if (pos_base == last_base) + ptr = ichdev->last_pos; + } } + ichdev->last_pos = ptr; spin_unlock(&chip->reg_lock); if (ptr >= ichdev->size) return 0; diff --git a/sound/pci/lx6464es/Makefile b/sound/pci/lx6464es/Makefile new file mode 100644 index 00000000000..eb04a6c73d8 --- /dev/null +++ b/sound/pci/lx6464es/Makefile @@ -0,0 +1,2 @@ +snd-lx6464es-objs := lx6464es.o lx_core.o +obj-$(CONFIG_SND_LX6464ES) += snd-lx6464es.o diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c new file mode 100644 index 00000000000..ccf1b38c88e --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.c @@ -0,0 +1,1159 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2008, 2009 Tim Blechmann <tim@klingt.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/info.h> + +#include "lx6464es.h" + +MODULE_AUTHOR("Tim Blechmann"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("digigram lx6464es"); +MODULE_SUPPORTED_DEVICE("{digigram lx6464es{}}"); + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram LX6464ES interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram LX6464ES interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific Digigram LX6464ES soundcards."); + +static const char card_name[] = "LX6464ES"; + + +#define PCI_DEVICE_ID_PLX_LX6464ES PCI_DEVICE_ID_PLX_9056 + +static struct pci_device_id snd_lx6464es_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES), + .subvendor = PCI_VENDOR_ID_DIGIGRAM, + .subdevice = PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_SERIAL_SUBSYSTEM + }, /* LX6464ES */ + { PCI_DEVICE(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES), + .subvendor = PCI_VENDOR_ID_DIGIGRAM, + .subdevice = PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_CAE_SERIAL_SUBSYSTEM + }, /* LX6464ES-CAE */ + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, snd_lx6464es_ids); + + + +/* PGO pour USERo dans le registre pci_0x06/loc_0xEC */ +#define CHIPSC_RESET_XILINX (1L<<16) + + +/* alsa callbacks */ +static struct snd_pcm_hardware lx_caps = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_3BE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_192000), + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 64, + .buffer_bytes_max = 64*2*3*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER, + .period_bytes_min = (2*2*MICROBLAZE_IBL_MIN*2), + .period_bytes_max = (4*64*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER), + .periods_min = 2, + .periods_max = MAX_STREAM_BUFFER, +}; + +static int lx_set_granularity(struct lx6464es *chip, u32 gran); + + +static int lx_hardware_open(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + int channels = runtime->channels; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_pcm_uframes_t period_size = runtime->period_size; + + snd_printd(LXP "allocating pipe for %d channels\n", channels); + err = lx_pipe_allocate(chip, 0, is_capture, channels); + if (err < 0) { + snd_printk(KERN_ERR LXP "allocating pipe failed\n"); + return err; + } + + err = lx_set_granularity(chip, period_size); + if (err < 0) { + snd_printk(KERN_ERR LXP "setting granularity to %ld failed\n", + period_size); + return err; + } + + return 0; +} + +static int lx_hardware_start(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_printd(LXP "setting stream format\n"); + err = lx_stream_set_format(chip, runtime, 0, is_capture); + if (err < 0) { + snd_printk(KERN_ERR LXP "setting stream format failed\n"); + return err; + } + + snd_printd(LXP "starting pipe\n"); + err = lx_pipe_start(chip, 0, is_capture); + if (err < 0) { + snd_printk(KERN_ERR LXP "starting pipe failed\n"); + return err; + } + + snd_printd(LXP "waiting for pipe to start\n"); + err = lx_pipe_wait_for_start(chip, 0, is_capture); + if (err < 0) { + snd_printk(KERN_ERR LXP "waiting for pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_hardware_stop(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_printd(LXP "pausing pipe\n"); + err = lx_pipe_pause(chip, 0, is_capture); + if (err < 0) { + snd_printk(KERN_ERR LXP "pausing pipe failed\n"); + return err; + } + + snd_printd(LXP "waiting for pipe to become idle\n"); + err = lx_pipe_wait_for_idle(chip, 0, is_capture); + if (err < 0) { + snd_printk(KERN_ERR LXP "waiting for pipe failed\n"); + return err; + } + + snd_printd(LXP "stopping pipe\n"); + err = lx_pipe_stop(chip, 0, is_capture); + if (err < 0) { + snd_printk(LXP "stopping pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_hardware_close(struct lx6464es *chip, + struct snd_pcm_substream *substream) +{ + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_printd(LXP "releasing pipe\n"); + err = lx_pipe_release(chip, 0, is_capture); + if (err < 0) { + snd_printk(LXP "releasing pipe failed\n"); + return err; + } + + return err; +} + + +static int lx_pcm_open(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + int board_rate; + + snd_printdd("->lx_pcm_open\n"); + mutex_lock(&chip->setup_mutex); + + /* copy the struct snd_pcm_hardware struct */ + runtime->hw = lx_caps; + +#if 0 + /* buffer-size should better be multiple of period-size */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + snd_printk(KERN_WARNING LXP "could not constrain periods\n"); + goto exit; + } +#endif + + /* the clock rate cannot be changed */ + board_rate = chip->board_sample_rate; + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + board_rate, board_rate); + + if (err < 0) { + snd_printk(KERN_WARNING LXP "could not constrain periods\n"); + goto exit; + } + + /* constrain period size */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + MICROBLAZE_IBL_MIN, + MICROBLAZE_IBL_MAX); + if (err < 0) { + snd_printk(KERN_WARNING LXP + "could not constrain period size\n"); + goto exit; + } + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); + + snd_pcm_set_sync(substream); + err = 0; + +exit: + runtime->private_data = chip; + + mutex_unlock(&chip->setup_mutex); + snd_printdd("<-lx_pcm_open, %d\n", err); + return err; +} + +static int lx_pcm_close(struct snd_pcm_substream *substream) +{ + int err = 0; + snd_printdd("->lx_pcm_close\n"); + return err; +} + +static snd_pcm_uframes_t lx_pcm_stream_pointer(struct snd_pcm_substream + *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + snd_pcm_uframes_t pos; + unsigned long flags; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + struct lx_stream *lx_stream = is_capture ? &chip->capture_stream : + &chip->playback_stream; + + snd_printdd("->lx_pcm_stream_pointer\n"); + + spin_lock_irqsave(&chip->lock, flags); + pos = lx_stream->frame_pos * substream->runtime->period_size; + spin_unlock_irqrestore(&chip->lock, flags); + + snd_printdd(LXP "stream_pointer at %ld\n", pos); + return pos; +} + +static int lx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + int err = 0; + const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_printdd("->lx_pcm_prepare\n"); + + mutex_lock(&chip->setup_mutex); + + if (chip->hardware_running[is_capture]) { + err = lx_hardware_stop(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to stop hardware. " + "Error code %d\n", err); + goto exit; + } + + err = lx_hardware_close(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to close hardware. " + "Error code %d\n", err); + goto exit; + } + } + + snd_printd(LXP "opening hardware\n"); + err = lx_hardware_open(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to open hardware. " + "Error code %d\n", err); + goto exit; + } + + err = lx_hardware_start(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to start hardware. " + "Error code %d\n", err); + goto exit; + } + + chip->hardware_running[is_capture] = 1; + + if (chip->board_sample_rate != substream->runtime->rate) { + if (!err) + chip->board_sample_rate = substream->runtime->rate; + } + +exit: + mutex_unlock(&chip->setup_mutex); + return err; +} + +static int lx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, int is_capture) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + int err = 0; + + snd_printdd("->lx_pcm_hw_params\n"); + + mutex_lock(&chip->setup_mutex); + + /* set dma buffer */ + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + + if (is_capture) + chip->capture_stream.stream = substream; + else + chip->playback_stream.stream = substream; + + mutex_unlock(&chip->setup_mutex); + return err; +} + +static int lx_pcm_hw_params_playback(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return lx_pcm_hw_params(substream, hw_params, 0); +} + +static int lx_pcm_hw_params_capture(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return lx_pcm_hw_params(substream, hw_params, 1); +} + +static int lx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + int err = 0; + int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + + snd_printdd("->lx_pcm_hw_free\n"); + mutex_lock(&chip->setup_mutex); + + if (chip->hardware_running[is_capture]) { + err = lx_hardware_stop(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to stop hardware. " + "Error code %d\n", err); + goto exit; + } + + err = lx_hardware_close(chip, substream); + if (err < 0) { + snd_printk(KERN_ERR LXP "failed to close hardware. " + "Error code %d\n", err); + goto exit; + } + + chip->hardware_running[is_capture] = 0; + } + + err = snd_pcm_lib_free_pages(substream); + + if (is_capture) + chip->capture_stream.stream = 0; + else + chip->playback_stream.stream = 0; + +exit: + mutex_unlock(&chip->setup_mutex); + return err; +} + +static void lx_trigger_start(struct lx6464es *chip, struct lx_stream *lx_stream) +{ + struct snd_pcm_substream *substream = lx_stream->stream; + const int is_capture = lx_stream->is_capture; + + int err; + + const u32 channels = substream->runtime->channels; + const u32 bytes_per_frame = channels * 3; + const u32 period_size = substream->runtime->period_size; + const u32 periods = substream->runtime->periods; + const u32 period_bytes = period_size * bytes_per_frame; + + dma_addr_t buf = substream->dma_buffer.addr; + int i; + + u32 needed, freed; + u32 size_array[5]; + + for (i = 0; i != periods; ++i) { + u32 buffer_index = 0; + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, + size_array); + snd_printdd(LXP "starting: needed %d, freed %d\n", + needed, freed); + + err = lx_buffer_give(chip, 0, is_capture, period_bytes, + lower_32_bits(buf), upper_32_bits(buf), + &buffer_index); + + snd_printdd(LXP "starting: buffer index %x on %p (%d bytes)\n", + buffer_index, (void *)buf, period_bytes); + buf += period_bytes; + } + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array); + snd_printdd(LXP "starting: needed %d, freed %d\n", needed, freed); + + snd_printd(LXP "starting: starting stream\n"); + err = lx_stream_start(chip, 0, is_capture); + if (err < 0) + snd_printk(KERN_ERR LXP "couldn't start stream\n"); + else + lx_stream->status = LX_STREAM_STATUS_RUNNING; + + lx_stream->frame_pos = 0; +} + +static void lx_trigger_stop(struct lx6464es *chip, struct lx_stream *lx_stream) +{ + const int is_capture = lx_stream->is_capture; + int err; + + snd_printd(LXP "stopping: stopping stream\n"); + err = lx_stream_stop(chip, 0, is_capture); + if (err < 0) + snd_printk(KERN_ERR LXP "couldn't stop stream\n"); + else + lx_stream->status = LX_STREAM_STATUS_FREE; + +} + +static void lx_trigger_tasklet_dispatch_stream(struct lx6464es *chip, + struct lx_stream *lx_stream) +{ + switch (lx_stream->status) { + case LX_STREAM_STATUS_SCHEDULE_RUN: + lx_trigger_start(chip, lx_stream); + break; + + case LX_STREAM_STATUS_SCHEDULE_STOP: + lx_trigger_stop(chip, lx_stream); + break; + + default: + break; + } +} + +static void lx_trigger_tasklet(unsigned long data) +{ + struct lx6464es *chip = (struct lx6464es *)data; + unsigned long flags; + + snd_printdd("->lx_trigger_tasklet\n"); + + spin_lock_irqsave(&chip->lock, flags); + lx_trigger_tasklet_dispatch_stream(chip, &chip->capture_stream); + lx_trigger_tasklet_dispatch_stream(chip, &chip->playback_stream); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int lx_pcm_trigger_dispatch(struct lx6464es *chip, + struct lx_stream *lx_stream, int cmd) +{ + int err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + lx_stream->status = LX_STREAM_STATUS_SCHEDULE_RUN; + break; + + case SNDRV_PCM_TRIGGER_STOP: + lx_stream->status = LX_STREAM_STATUS_SCHEDULE_STOP; + break; + + default: + err = -EINVAL; + goto exit; + } + tasklet_schedule(&chip->trigger_tasklet); + +exit: + return err; +} + + +static int lx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct lx6464es *chip = snd_pcm_substream_chip(substream); + const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct lx_stream *stream = is_capture ? &chip->capture_stream : + &chip->playback_stream; + + snd_printdd("->lx_pcm_trigger\n"); + + return lx_pcm_trigger_dispatch(chip, stream, cmd); +} + +static int snd_lx6464es_free(struct lx6464es *chip) +{ + snd_printdd("->snd_lx6464es_free\n"); + + lx_irq_disable(chip); + + if (chip->irq >= 0) + free_irq(chip->irq, chip); + + iounmap(chip->port_dsp_bar); + ioport_unmap(chip->port_plx_remapped); + + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); + + kfree(chip); + + return 0; +} + +static int snd_lx6464es_dev_free(struct snd_device *device) +{ + return snd_lx6464es_free(device->device_data); +} + +/* reset the dsp during initialization */ +static int __devinit lx_init_xilinx_reset(struct lx6464es *chip) +{ + int i; + u32 plx_reg = lx_plx_reg_read(chip, ePLX_CHIPSC); + + snd_printdd("->lx_init_xilinx_reset\n"); + + /* activate reset of xilinx */ + plx_reg &= ~CHIPSC_RESET_XILINX; + + lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg); + msleep(1); + + lx_plx_reg_write(chip, ePLX_MBOX3, 0); + msleep(1); + + plx_reg |= CHIPSC_RESET_XILINX; + lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg); + + /* deactivate reset of xilinx */ + for (i = 0; i != 100; ++i) { + u32 reg_mbox3; + msleep(10); + reg_mbox3 = lx_plx_reg_read(chip, ePLX_MBOX3); + if (reg_mbox3) { + snd_printd(LXP "xilinx reset done\n"); + snd_printdd(LXP "xilinx took %d loops\n", i); + break; + } + } + + /* todo: add some error handling? */ + + /* clear mr */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + /* le xilinx ES peut ne pas etre encore pret, on attend. */ + msleep(600); + + return 0; +} + +static int __devinit lx_init_xilinx_test(struct lx6464es *chip) +{ + u32 reg; + + snd_printdd("->lx_init_xilinx_test\n"); + + /* TEST if we have access to Xilinx/MicroBlaze */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + reg = lx_dsp_reg_read(chip, eReg_CSM); + + if (reg) { + snd_printk(KERN_ERR LXP "Problem: Reg_CSM %x.\n", reg); + + /* PCI9056_SPACE0_REMAP */ + lx_plx_reg_write(chip, ePLX_PCICR, 1); + + reg = lx_dsp_reg_read(chip, eReg_CSM); + if (reg) { + snd_printk(KERN_ERR LXP "Error: Reg_CSM %x.\n", reg); + return -EAGAIN; /* seems to be appropriate */ + } + } + + snd_printd(LXP "Xilinx/MicroBlaze access test successful\n"); + + return 0; +} + +/* initialize ethersound */ +static int __devinit lx_init_ethersound_config(struct lx6464es *chip) +{ + int i; + u32 orig_conf_es = lx_dsp_reg_read(chip, eReg_CONFES); + + u32 default_conf_es = (64 << IOCR_OUTPUTS_OFFSET) | + (64 << IOCR_INPUTS_OFFSET) | + (FREQ_RATIO_SINGLE_MODE << FREQ_RATIO_OFFSET); + + u32 conf_es = (orig_conf_es & CONFES_READ_PART_MASK) + | (default_conf_es & CONFES_WRITE_PART_MASK); + + snd_printdd("->lx_init_ethersound\n"); + + chip->freq_ratio = FREQ_RATIO_SINGLE_MODE; + + /* + * write it to the card ! + * this actually kicks the ES xilinx, the first time since poweron. + * the MAC address in the Reg_ADMACESMSB Reg_ADMACESLSB registers + * is not ready before this is done, and the bit 2 in Reg_CSES is set. + * */ + lx_dsp_reg_write(chip, eReg_CONFES, conf_es); + + for (i = 0; i != 1000; ++i) { + if (lx_dsp_reg_read(chip, eReg_CSES) & 4) { + snd_printd(LXP "ethersound initialized after %dms\n", + i); + goto ethersound_initialized; + } + msleep(1); + } + snd_printk(KERN_WARNING LXP + "ethersound could not be initialized after %dms\n", i); + return -ETIMEDOUT; + + ethersound_initialized: + snd_printd(LXP "ethersound initialized\n"); + return 0; +} + +static int __devinit lx_init_get_version_features(struct lx6464es *chip) +{ + u32 dsp_version; + + int err; + + snd_printdd("->lx_init_get_version_features\n"); + + err = lx_dsp_get_version(chip, &dsp_version); + + if (err == 0) { + u32 freq; + + snd_printk(LXP "DSP version: V%02d.%02d #%d\n", + (dsp_version>>16) & 0xff, (dsp_version>>8) & 0xff, + dsp_version & 0xff); + + /* later: what firmware version do we expect? */ + + /* retrieve Play/Rec features */ + /* done here because we may have to handle alternate + * DSP files. */ + /* later */ + + /* init the EtherSound sample rate */ + err = lx_dsp_get_clock_frequency(chip, &freq); + if (err == 0) + chip->board_sample_rate = freq; + snd_printd(LXP "actual clock frequency %d\n", freq); + } else { + snd_printk(KERN_ERR LXP "DSP corrupted \n"); + err = -EAGAIN; + } + + return err; +} + +static int lx_set_granularity(struct lx6464es *chip, u32 gran) +{ + int err = 0; + u32 snapped_gran = MICROBLAZE_IBL_MIN; + + snd_printdd("->lx_set_granularity\n"); + + /* blocksize is a power of 2 */ + while ((snapped_gran < gran) && + (snapped_gran < MICROBLAZE_IBL_MAX)) { + snapped_gran *= 2; + } + + if (snapped_gran == chip->pcm_granularity) + return 0; + + err = lx_dsp_set_granularity(chip, snapped_gran); + if (err < 0) { + snd_printk(KERN_WARNING LXP "could not set granularity\n"); + err = -EAGAIN; + } + + if (snapped_gran != gran) + snd_printk(LXP "snapped blocksize to %d\n", snapped_gran); + + snd_printd(LXP "set blocksize on board %d\n", snapped_gran); + chip->pcm_granularity = snapped_gran; + + return err; +} + +/* initialize and test the xilinx dsp chip */ +static int __devinit lx_init_dsp(struct lx6464es *chip) +{ + int err; + u8 mac_address[6]; + int i; + + snd_printdd("->lx_init_dsp\n"); + + snd_printd(LXP "initialize board\n"); + err = lx_init_xilinx_reset(chip); + if (err) + return err; + + snd_printd(LXP "testing board\n"); + err = lx_init_xilinx_test(chip); + if (err) + return err; + + snd_printd(LXP "initialize ethersound configuration\n"); + err = lx_init_ethersound_config(chip); + if (err) + return err; + + lx_irq_enable(chip); + + /** \todo the mac address should be ready by not, but it isn't, + * so we wait for it */ + for (i = 0; i != 1000; ++i) { + err = lx_dsp_get_mac(chip, mac_address); + if (err) + return err; + if (mac_address[0] || mac_address[1] || mac_address[2] || + mac_address[3] || mac_address[4] || mac_address[5]) + goto mac_ready; + msleep(1); + } + return -ETIMEDOUT; + +mac_ready: + snd_printd(LXP "mac address ready read after: %dms\n", i); + snd_printk(LXP "mac address: %02X.%02X.%02X.%02X.%02X.%02X\n", + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]); + + err = lx_init_get_version_features(chip); + if (err) + return err; + + lx_set_granularity(chip, MICROBLAZE_IBL_DEFAULT); + + chip->playback_mute = 0; + + return err; +} + +static struct snd_pcm_ops lx_ops_playback = { + .open = lx_pcm_open, + .close = lx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = lx_pcm_prepare, + .hw_params = lx_pcm_hw_params_playback, + .hw_free = lx_pcm_hw_free, + .trigger = lx_pcm_trigger, + .pointer = lx_pcm_stream_pointer, +}; + +static struct snd_pcm_ops lx_ops_capture = { + .open = lx_pcm_open, + .close = lx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = lx_pcm_prepare, + .hw_params = lx_pcm_hw_params_capture, + .hw_free = lx_pcm_hw_free, + .trigger = lx_pcm_trigger, + .pointer = lx_pcm_stream_pointer, +}; + +static int __devinit lx_pcm_create(struct lx6464es *chip) +{ + int err; + struct snd_pcm *pcm; + + u32 size = 64 * /* channels */ + 3 * /* 24 bit samples */ + MAX_STREAM_BUFFER * /* periods */ + MICROBLAZE_IBL_MAX * /* frames per period */ + 2; /* duplex */ + + size = PAGE_ALIGN(size); + + /* hardcoded device name & channel count */ + err = snd_pcm_new(chip->card, (char *)card_name, 0, + 1, 1, &pcm); + + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &lx_ops_playback); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &lx_ops_capture); + + pcm->info_flags = 0; + strcpy(pcm->name, card_name); + + err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + size, size); + if (err < 0) + return err; + + chip->pcm = pcm; + chip->capture_stream.is_capture = 1; + + return 0; +} + +static int lx_control_playback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int lx_control_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lx6464es *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->playback_mute; + return 0; +} + +static int lx_control_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct lx6464es *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int current_value = chip->playback_mute; + + if (current_value != ucontrol->value.integer.value[0]) { + lx_level_unmute(chip, 0, !current_value); + chip->playback_mute = !current_value; + changed = 1; + } + return changed; +} + +static struct snd_kcontrol_new lx_control_playback_switch __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = lx_control_playback_info, + .get = lx_control_playback_get, + .put = lx_control_playback_put +}; + + + +static void lx_proc_levels_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + u32 levels[64]; + int err; + int i, j; + struct lx6464es *chip = entry->private_data; + + snd_iprintf(buffer, "capture levels:\n"); + err = lx_level_peaks(chip, 1, 64, levels); + if (err < 0) + return; + + for (i = 0; i != 8; ++i) { + for (j = 0; j != 8; ++j) + snd_iprintf(buffer, "%08x ", levels[i*8+j]); + snd_iprintf(buffer, "\n"); + } + + snd_iprintf(buffer, "\nplayback levels:\n"); + + err = lx_level_peaks(chip, 0, 64, levels); + if (err < 0) + return; + + for (i = 0; i != 8; ++i) { + for (j = 0; j != 8; ++j) + snd_iprintf(buffer, "%08x ", levels[i*8+j]); + snd_iprintf(buffer, "\n"); + } + + snd_iprintf(buffer, "\n"); +} + +static int __devinit lx_proc_create(struct snd_card *card, struct lx6464es *chip) +{ + struct snd_info_entry *entry; + int err = snd_card_proc_new(card, "levels", &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, chip, lx_proc_levels_read); + return 0; +} + + +static int __devinit snd_lx6464es_create(struct snd_card *card, + struct pci_dev *pci, + struct lx6464es **rchip) +{ + struct lx6464es *chip; + int err; + + static struct snd_device_ops ops = { + .dev_free = snd_lx6464es_dev_free, + }; + + snd_printdd("->snd_lx6464es_create\n"); + + *rchip = NULL; + + /* enable PCI device */ + err = pci_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(pci); + + /* check if we can restrict PCI DMA transfers to 32 bits */ + err = pci_set_dma_mask(pci, DMA_32BIT_MASK); + if (err < 0) { + snd_printk(KERN_ERR "architecture does not support " + "32bit PCI busmaster DMA\n"); + pci_disable_device(pci); + return -ENXIO; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + err = -ENOMEM; + goto alloc_failed; + } + + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + /* initialize synchronization structs */ + spin_lock_init(&chip->lock); + spin_lock_init(&chip->msg_lock); + mutex_init(&chip->setup_mutex); + tasklet_init(&chip->trigger_tasklet, lx_trigger_tasklet, + (unsigned long)chip); + tasklet_init(&chip->tasklet_capture, lx_tasklet_capture, + (unsigned long)chip); + tasklet_init(&chip->tasklet_playback, lx_tasklet_playback, + (unsigned long)chip); + + /* request resources */ + err = pci_request_regions(pci, card_name); + if (err < 0) + goto request_regions_failed; + + /* plx port */ + chip->port_plx = pci_resource_start(pci, 1); + chip->port_plx_remapped = ioport_map(chip->port_plx, + pci_resource_len(pci, 1)); + + /* dsp port */ + chip->port_dsp_bar = pci_ioremap_bar(pci, 2); + + err = request_irq(pci->irq, lx_interrupt, IRQF_SHARED, + card_name, chip); + if (err) { + snd_printk(KERN_ERR LXP "unable to grab IRQ %d\n", pci->irq); + goto request_irq_failed; + } + chip->irq = pci->irq; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) + goto device_new_failed; + + err = lx_init_dsp(chip); + if (err < 0) { + snd_printk(KERN_ERR LXP "error during DSP initialization\n"); + return err; + } + + err = lx_pcm_create(chip); + if (err < 0) + return err; + + err = lx_proc_create(card, chip); + if (err < 0) + return err; + + err = snd_ctl_add(card, snd_ctl_new1(&lx_control_playback_switch, + chip)); + if (err < 0) + return err; + + snd_card_set_dev(card, &pci->dev); + + *rchip = chip; + return 0; + +device_new_failed: + free_irq(pci->irq, chip); + +request_irq_failed: + pci_release_regions(pci); + +request_regions_failed: + kfree(chip); + +alloc_failed: + pci_disable_device(pci); + + return err; +} + +static int __devinit snd_lx6464es_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct lx6464es *chip; + int err; + + snd_printdd("->snd_lx6464es_probe\n"); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_lx6464es_create(card, pci, &chip); + if (err < 0) { + snd_printk(KERN_ERR LXP "error during snd_lx6464es_create\n"); + goto out_free; + } + + strcpy(card->driver, "lx6464es"); + strcpy(card->shortname, "Digigram LX6464ES"); + sprintf(card->longname, "%s at 0x%lx, 0x%p, irq %i", + card->shortname, chip->port_plx, + chip->port_dsp_bar, chip->irq); + + err = snd_card_register(card); + if (err < 0) + goto out_free; + + snd_printdd(LXP "initialization successful\n"); + pci_set_drvdata(pci, card); + dev++; + return 0; + +out_free: + snd_card_free(card); + return err; + +} + +static void __devexit snd_lx6464es_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + + +static struct pci_driver driver = { + .name = "Digigram LX6464ES", + .id_table = snd_lx6464es_ids, + .probe = snd_lx6464es_probe, + .remove = __devexit_p(snd_lx6464es_remove), +}; + + +/* module initialization */ +static int __init mod_init(void) +{ + return pci_register_driver(&driver); +} + +static void __exit mod_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(mod_init); +module_exit(mod_exit); diff --git a/sound/pci/lx6464es/lx6464es.h b/sound/pci/lx6464es/lx6464es.h new file mode 100644 index 00000000000..012c010c8c8 --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.h @@ -0,0 +1,114 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2009 Tim Blechmann <tim@klingt.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef LX6464ES_H +#define LX6464ES_H + +#include <linux/spinlock.h> +#include <asm/atomic.h> + +#include <sound/core.h> +#include <sound/pcm.h> + +#include "lx_core.h" + +#define LXP "LX6464ES: " + +enum { + ES_cmd_free = 0, /* no command executing */ + ES_cmd_processing = 1, /* execution of a read/write command */ + ES_read_pending = 2, /* a asynchron read command is pending */ + ES_read_finishing = 3, /* a read command has finished waiting (set by + * Interrupt or CancelIrp) */ +}; + +enum lx_stream_status { + LX_STREAM_STATUS_FREE, +/* LX_STREAM_STATUS_OPEN, */ + LX_STREAM_STATUS_SCHEDULE_RUN, +/* LX_STREAM_STATUS_STARTED, */ + LX_STREAM_STATUS_RUNNING, + LX_STREAM_STATUS_SCHEDULE_STOP, +/* LX_STREAM_STATUS_STOPPED, */ +/* LX_STREAM_STATUS_PAUSED */ +}; + + +struct lx_stream { + struct snd_pcm_substream *stream; + snd_pcm_uframes_t frame_pos; + enum lx_stream_status status; /* free, open, running, draining + * pause */ + int is_capture:1; +}; + + +struct lx6464es { + struct snd_card *card; + struct pci_dev *pci; + int irq; + + spinlock_t lock; /* interrupt spinlock */ + struct mutex setup_mutex; /* mutex used in hw_params, open + * and close */ + + struct tasklet_struct trigger_tasklet; /* trigger tasklet */ + struct tasklet_struct tasklet_capture; + struct tasklet_struct tasklet_playback; + + /* ports */ + unsigned long port_plx; /* io port (size=256) */ + void __iomem *port_plx_remapped; /* remapped plx port */ + void __iomem *port_dsp_bar; /* memory port (32-bit, + * non-prefetchable, + * size=8K) */ + + /* messaging */ + spinlock_t msg_lock; /* message spinlock */ + atomic_t send_message_locked; + struct lx_rmh rmh; + + /* configuration */ + uint freq_ratio : 2; + uint playback_mute : 1; + uint hardware_running[2]; + u32 board_sample_rate; /* sample rate read from + * board */ + u32 sample_rate; /* our sample rate */ + u16 pcm_granularity; /* board blocksize */ + + /* dma */ + struct snd_dma_buffer capture_dma_buf; + struct snd_dma_buffer playback_dma_buf; + + /* pcm */ + struct snd_pcm *pcm; + + /* streams */ + struct lx_stream capture_stream; + struct lx_stream playback_stream; +}; + + +#endif /* LX6464ES_H */ diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c new file mode 100644 index 00000000000..5812780d6e8 --- /dev/null +++ b/sound/pci/lx6464es/lx_core.c @@ -0,0 +1,1444 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann <tim@klingt.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +/* #define RMH_DEBUG 1 */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include "lx6464es.h" +#include "lx_core.h" + +/* low-level register access */ + +static const unsigned long dsp_port_offsets[] = { + 0, + 0x400, + 0x401, + 0x402, + 0x403, + 0x404, + 0x405, + 0x406, + 0x407, + 0x408, + 0x409, + 0x40a, + 0x40b, + 0x40c, + + 0x410, + 0x411, + 0x412, + 0x413, + 0x414, + 0x415, + 0x416, + + 0x420, + 0x430, + 0x431, + 0x432, + 0x433, + 0x434, + 0x440 +}; + +static void __iomem *lx_dsp_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_dsp_bar; + return base_address + dsp_port_offsets[port]*4; +} + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_dsp_register(chip, port); + return ioread32(address); +} + +void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, u32 len) +{ + void __iomem *address = lx_dsp_register(chip, port); + memcpy_fromio(data, address, len*sizeof(u32)); +} + + +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data) +{ + void __iomem *address = lx_dsp_register(chip, port); + iowrite32(data, address); +} + +void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, const u32 *data, + u32 len) +{ + void __iomem *address = lx_dsp_register(chip, port); + memcpy_toio(address, data, len*sizeof(u32)); +} + + +static const unsigned long plx_port_offsets[] = { + 0x04, + 0x40, + 0x44, + 0x48, + 0x4c, + 0x50, + 0x54, + 0x58, + 0x5c, + 0x64, + 0x68, + 0x6C +}; + +static void __iomem *lx_plx_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_plx_remapped; + return base_address + plx_port_offsets[port]; +} + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_plx_register(chip, port); + return ioread32(address); +} + +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data) +{ + void __iomem *address = lx_plx_register(chip, port); + iowrite32(data, address); +} + +u32 lx_plx_mbox_read(struct lx6464es *chip, int mbox_nr) +{ + int index; + + switch (mbox_nr) { + case 1: + index = ePLX_MBOX1; break; + case 2: + index = ePLX_MBOX2; break; + case 3: + index = ePLX_MBOX3; break; + case 4: + index = ePLX_MBOX4; break; + case 5: + index = ePLX_MBOX5; break; + case 6: + index = ePLX_MBOX6; break; + case 7: + index = ePLX_MBOX7; break; + case 0: /* reserved for HF flags */ + snd_BUG(); + default: + return 0xdeadbeef; + } + + return lx_plx_reg_read(chip, index); +} + +int lx_plx_mbox_write(struct lx6464es *chip, int mbox_nr, u32 value) +{ + int index = -1; + + switch (mbox_nr) { + case 1: + index = ePLX_MBOX1; break; + case 3: + index = ePLX_MBOX3; break; + case 4: + index = ePLX_MBOX4; break; + case 5: + index = ePLX_MBOX5; break; + case 6: + index = ePLX_MBOX6; break; + case 7: + index = ePLX_MBOX7; break; + case 0: /* reserved for HF flags */ + case 2: /* reserved for Pipe States + * the DSP keeps an image of it */ + snd_BUG(); + return -EBADRQC; + } + + lx_plx_reg_write(chip, index, value); + return 0; +} + + +/* rmh */ + +#ifdef CONFIG_SND_DEBUG +#define CMD_NAME(a) a +#else +#define CMD_NAME(a) NULL +#endif + +#define Reg_CSM_MR 0x00000002 +#define Reg_CSM_MC 0x00000001 + +struct dsp_cmd_info { + u32 dcCodeOp; /* Op Code of the command (usually 1st 24-bits + * word).*/ + u16 dcCmdLength; /* Command length in words of 24 bits.*/ + u16 dcStatusType; /* Status type: 0 for fixed length, 1 for + * random. */ + u16 dcStatusLength; /* Status length (if fixed).*/ + char *dcOpName; +}; + +/* + Initialization and control data for the Microblaze interface + - OpCode: + the opcode field of the command set at the proper offset + - CmdLength + the number of command words + - StatusType + offset in the status registers: 0 means that the return value may be + different from 0, and must be read + - StatusLength + the number of status words (in addition to the return value) +*/ + +static struct dsp_cmd_info dsp_commands[] = +{ + { (CMD_00_INFO_DEBUG << OPCODE_OFFSET) , 1 /*custom*/ + , 1 , 0 /**/ , CMD_NAME("INFO_DEBUG") }, + { (CMD_01_GET_SYS_CFG << OPCODE_OFFSET) , 1 /**/ + , 1 , 2 /**/ , CMD_NAME("GET_SYS_CFG") }, + { (CMD_02_SET_GRANULARITY << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_GRANULARITY") }, + { (CMD_03_SET_TIMER_IRQ << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_TIMER_IRQ") }, + { (CMD_04_GET_EVENT << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /*up to 10*/ , CMD_NAME("GET_EVENT") }, + { (CMD_05_GET_PIPES << OPCODE_OFFSET) , 1 /**/ + , 1 , 2 /*up to 4*/ , CMD_NAME("GET_PIPES") }, + { (CMD_06_ALLOCATE_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /**/ , CMD_NAME("ALLOCATE_PIPE") }, + { (CMD_07_RELEASE_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /**/ , CMD_NAME("RELEASE_PIPE") }, + { (CMD_08_ASK_BUFFERS << OPCODE_OFFSET) , 1 /**/ + , 1 , MAX_STREAM_BUFFER , CMD_NAME("ASK_BUFFERS") }, + { (CMD_09_STOP_PIPE << OPCODE_OFFSET) , 1 /**/ + , 0 , 0 /*up to 2*/ , CMD_NAME("STOP_PIPE") }, + { (CMD_0A_GET_PIPE_SPL_COUNT << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /*up to 2*/ , CMD_NAME("GET_PIPE_SPL_COUNT") }, + { (CMD_0B_TOGGLE_PIPE_STATE << OPCODE_OFFSET) , 1 /*up to 5*/ + , 1 , 0 /**/ , CMD_NAME("TOGGLE_PIPE_STATE") }, + { (CMD_0C_DEF_STREAM << OPCODE_OFFSET) , 1 /*up to 4*/ + , 1 , 0 /**/ , CMD_NAME("DEF_STREAM") }, + { (CMD_0D_SET_MUTE << OPCODE_OFFSET) , 3 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_MUTE") }, + { (CMD_0E_GET_STREAM_SPL_COUNT << OPCODE_OFFSET) , 1/**/ + , 1 , 2 /**/ , CMD_NAME("GET_STREAM_SPL_COUNT") }, + { (CMD_0F_UPDATE_BUFFER << OPCODE_OFFSET) , 3 /*up to 4*/ + , 0 , 1 /**/ , CMD_NAME("UPDATE_BUFFER") }, + { (CMD_10_GET_BUFFER << OPCODE_OFFSET) , 1 /**/ + , 1 , 4 /**/ , CMD_NAME("GET_BUFFER") }, + { (CMD_11_CANCEL_BUFFER << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /*up to 4*/ , CMD_NAME("CANCEL_BUFFER") }, + { (CMD_12_GET_PEAK << OPCODE_OFFSET) , 1 /**/ + , 1 , 1 /**/ , CMD_NAME("GET_PEAK") }, + { (CMD_13_SET_STREAM_STATE << OPCODE_OFFSET) , 1 /**/ + , 1 , 0 /**/ , CMD_NAME("SET_STREAM_STATE") }, +}; + +static void lx_message_init(struct lx_rmh *rmh, enum cmd_mb_opcodes cmd) +{ + snd_BUG_ON(cmd >= CMD_14_INVALID); + + rmh->cmd[0] = dsp_commands[cmd].dcCodeOp; + rmh->cmd_len = dsp_commands[cmd].dcCmdLength; + rmh->stat_len = dsp_commands[cmd].dcStatusLength; + rmh->dsp_stat = dsp_commands[cmd].dcStatusType; + rmh->cmd_idx = cmd; + memset(&rmh->cmd[1], 0, (REG_CRM_NUMBER - 1) * sizeof(u32)); + +#ifdef CONFIG_SND_DEBUG + memset(rmh->stat, 0, REG_CRM_NUMBER * sizeof(u32)); +#endif +#ifdef RMH_DEBUG + rmh->cmd_idx = cmd; +#endif +} + +#ifdef RMH_DEBUG +#define LXRMH "lx6464es rmh: " +static void lx_message_dump(struct lx_rmh *rmh) +{ + u8 idx = rmh->cmd_idx; + int i; + + snd_printk(LXRMH "command %s\n", dsp_commands[idx].dcOpName); + + for (i = 0; i != rmh->cmd_len; ++i) + snd_printk(LXRMH "\tcmd[%d] %08x\n", i, rmh->cmd[i]); + + for (i = 0; i != rmh->stat_len; ++i) + snd_printk(LXRMH "\tstat[%d]: %08x\n", i, rmh->stat[i]); + snd_printk("\n"); +} +#else +static inline void lx_message_dump(struct lx_rmh *rmh) +{} +#endif + + + +/* sleep 500 - 100 = 400 times 100us -> the timeout is >= 40 ms */ +#define XILINX_TIMEOUT_MS 40 +#define XILINX_POLL_NO_SLEEP 100 +#define XILINX_POLL_ITERATIONS 150 + +#if 0 /* not used now */ +static int lx_message_send(struct lx6464es *chip, struct lx_rmh *rmh) +{ + u32 reg = ED_DSP_TIMED_OUT; + int dwloop; + int answer_received; + + if (lx_dsp_reg_read(chip, eReg_CSM) & (Reg_CSM_MC | Reg_CSM_MR)) { + snd_printk(KERN_ERR LXP "PIOSendMessage eReg_CSM %x\n", reg); + return -EBUSY; + } + + /* write command */ + lx_dsp_reg_writebuf(chip, eReg_CRM1, rmh->cmd, rmh->cmd_len); + + snd_BUG_ON(atomic_read(&chip->send_message_locked) != 0); + atomic_set(&chip->send_message_locked, 1); + + /* MicoBlaze gogogo */ + lx_dsp_reg_write(chip, eReg_CSM, Reg_CSM_MC); + + /* wait for interrupt to answer */ + for (dwloop = 0; dwloop != XILINX_TIMEOUT_MS; ++dwloop) { + answer_received = atomic_read(&chip->send_message_locked); + if (answer_received == 0) + break; + msleep(1); + } + + if (answer_received == 0) { + /* in Debug mode verify Reg_CSM_MR */ + snd_BUG_ON(!(lx_dsp_reg_read(chip, eReg_CSM) & Reg_CSM_MR)); + + /* command finished, read status */ + if (rmh->dsp_stat == 0) + reg = lx_dsp_reg_read(chip, eReg_CRM1); + else + reg = 0; + } else { + int i; + snd_printk(KERN_WARNING LXP "TIMEOUT lx_message_send! " + "Interrupts disabled?\n"); + + /* attente bit Reg_CSM_MR */ + for (i = 0; i != XILINX_POLL_ITERATIONS; i++) { + if ((lx_dsp_reg_read(chip, eReg_CSM) & Reg_CSM_MR)) { + if (rmh->dsp_stat == 0) + reg = lx_dsp_reg_read(chip, eReg_CRM1); + else + reg = 0; + goto polling_successful; + } + + if (i > XILINX_POLL_NO_SLEEP) + msleep(1); + } + snd_printk(KERN_WARNING LXP "TIMEOUT lx_message_send! " + "polling failed\n"); + +polling_successful: + atomic_set(&chip->send_message_locked, 0); + } + + if ((reg & ERROR_VALUE) == 0) { + /* read response */ + if (rmh->stat_len) { + snd_BUG_ON(rmh->stat_len >= (REG_CRM_NUMBER-1)); + + lx_dsp_reg_readbuf(chip, eReg_CRM2, rmh->stat, + rmh->stat_len); + } + } else + snd_printk(KERN_WARNING LXP "lx_message_send: error_value %x\n", + reg); + + /* clear Reg_CSM_MR */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + switch (reg) { + case ED_DSP_TIMED_OUT: + snd_printk(KERN_WARNING LXP "lx_message_send: dsp timeout\n"); + return -ETIMEDOUT; + + case ED_DSP_CRASHED: + snd_printk(KERN_WARNING LXP "lx_message_send: dsp crashed\n"); + return -EAGAIN; + } + + lx_message_dump(rmh); + return 0; +} +#endif /* not used now */ + +static int lx_message_send_atomic(struct lx6464es *chip, struct lx_rmh *rmh) +{ + u32 reg = ED_DSP_TIMED_OUT; + int dwloop; + + if (lx_dsp_reg_read(chip, eReg_CSM) & (Reg_CSM_MC | Reg_CSM_MR)) { + snd_printk(KERN_ERR LXP "PIOSendMessage eReg_CSM %x\n", reg); + return -EBUSY; + } + + /* write command */ + lx_dsp_reg_writebuf(chip, eReg_CRM1, rmh->cmd, rmh->cmd_len); + + /* MicoBlaze gogogo */ + lx_dsp_reg_write(chip, eReg_CSM, Reg_CSM_MC); + + /* wait for interrupt to answer */ + for (dwloop = 0; dwloop != XILINX_TIMEOUT_MS * 1000; ++dwloop) { + if (lx_dsp_reg_read(chip, eReg_CSM) & Reg_CSM_MR) { + if (rmh->dsp_stat == 0) + reg = lx_dsp_reg_read(chip, eReg_CRM1); + else + reg = 0; + goto polling_successful; + } else + udelay(1); + } + snd_printk(KERN_WARNING LXP "TIMEOUT lx_message_send_atomic! " + "polling failed\n"); + +polling_successful: + if ((reg & ERROR_VALUE) == 0) { + /* read response */ + if (rmh->stat_len) { + snd_BUG_ON(rmh->stat_len >= (REG_CRM_NUMBER-1)); + lx_dsp_reg_readbuf(chip, eReg_CRM2, rmh->stat, + rmh->stat_len); + } + } else + snd_printk(LXP "rmh error: %08x\n", reg); + + /* clear Reg_CSM_MR */ + lx_dsp_reg_write(chip, eReg_CSM, 0); + + switch (reg) { + case ED_DSP_TIMED_OUT: + snd_printk(KERN_WARNING LXP "lx_message_send: dsp timeout\n"); + return -ETIMEDOUT; + + case ED_DSP_CRASHED: + snd_printk(KERN_WARNING LXP "lx_message_send: dsp crashed\n"); + return -EAGAIN; + } + + lx_message_dump(rmh); + + return reg; +} + + +/* low-level dsp access */ +int __devinit lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version) +{ + u16 ret; + unsigned long flags; + + spin_lock_irqsave(&chip->msg_lock, flags); + + lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG); + ret = lx_message_send_atomic(chip, &chip->rmh); + + *rdsp_version = chip->rmh.stat[1]; + spin_unlock_irqrestore(&chip->msg_lock, flags); + return ret; +} + +int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq) +{ + u16 ret = 0; + unsigned long flags; + u32 freq_raw = 0; + u32 freq = 0; + u32 frequency = 0; + + spin_lock_irqsave(&chip->msg_lock, flags); + + lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG); + ret = lx_message_send_atomic(chip, &chip->rmh); + + if (ret == 0) { + freq_raw = chip->rmh.stat[0] >> FREQ_FIELD_OFFSET; + freq = freq_raw & XES_FREQ_COUNT8_MASK; + + if ((freq < XES_FREQ_COUNT8_48_MAX) || + (freq > XES_FREQ_COUNT8_44_MIN)) + frequency = 0; /* unknown */ + else if (freq >= XES_FREQ_COUNT8_44_MAX) + frequency = 44100; + else + frequency = 48000; + } + + spin_unlock_irqrestore(&chip->msg_lock, flags); + + *rfreq = frequency * chip->freq_ratio; + + return ret; +} + +int lx_dsp_get_mac(struct lx6464es *chip, u8 *mac_address) +{ + u32 macmsb, maclsb; + + macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; + maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; + + /* todo: endianess handling */ + mac_address[5] = ((u8 *)(&maclsb))[0]; + mac_address[4] = ((u8 *)(&maclsb))[1]; + mac_address[3] = ((u8 *)(&maclsb))[2]; + mac_address[2] = ((u8 *)(&macmsb))[0]; + mac_address[1] = ((u8 *)(&macmsb))[1]; + mac_address[0] = ((u8 *)(&macmsb))[2]; + + return 0; +} + + +int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chip->msg_lock, flags); + + lx_message_init(&chip->rmh, CMD_02_SET_GRANULARITY); + chip->rmh.cmd[0] |= gran; + + ret = lx_message_send_atomic(chip, &chip->rmh); + spin_unlock_irqrestore(&chip->msg_lock, flags); + return ret; +} + +int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chip->msg_lock, flags); + + lx_message_init(&chip->rmh, CMD_04_GET_EVENT); + chip->rmh.stat_len = 9; /* we don't necessarily need the full length */ + + ret = lx_message_send_atomic(chip, &chip->rmh); + + if (!ret) + memcpy(data, chip->rmh.stat, chip->rmh.stat_len * sizeof(u32)); + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return ret; +} + +#define CSES_TIMEOUT 100 /* microseconds */ +#define CSES_CE 0x0001 +#define CSES_BROADCAST 0x0002 +#define CSES_UPDATE_LDSV 0x0004 + +int lx_dsp_es_check_pipeline(struct lx6464es *chip) +{ + int i; + + for (i = 0; i != CSES_TIMEOUT; ++i) { + /* + * le bit CSES_UPDATE_LDSV est à1 dés que le macprog + * est pret. il re-passe à0 lorsque le premier read a + * été fait. pour l'instant on retire le test car ce bit + * passe a 1 environ 200 à400 ms aprés que le registre + * confES àété écrit (kick du xilinx ES). + * + * On ne teste que le bit CE. + * */ + + u32 cses = lx_dsp_reg_read(chip, eReg_CSES); + + if ((cses & CSES_CE) == 0) + return 0; + + udelay(1); + } + + return -ETIMEDOUT; +} + + +#define PIPE_INFO_TO_CMD(capture, pipe) \ + ((u32)((u32)(pipe) | ((capture) ? ID_IS_CAPTURE : 0L)) << ID_OFFSET) + + + +/* low-level pipe handling */ +int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture, + int channels) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_06_ALLOCATE_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= channels; + + err = lx_message_send_atomic(chip, &chip->rmh); + spin_unlock_irqrestore(&chip->msg_lock, flags); + + if (err != 0) + snd_printk(KERN_ERR "lx6464es: could not allocate pipe\n"); + + return err; +} + +int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_07_RELEASE_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + spin_unlock_irqrestore(&chip->msg_lock, flags); + + return err; +} + +int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_needed, u32 *r_freed, u32 *size_array) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + +#ifdef CONFIG_SND_DEBUG + if (size_array) + memset(size_array, 0, sizeof(u32)*MAX_STREAM_BUFFER); +#endif + + *r_needed = 0; + *r_freed = 0; + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_08_ASK_BUFFERS); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (!err) { + int i; + for (i = 0; i < MAX_STREAM_BUFFER; ++i) { + u32 stat = chip->rmh.stat[i]; + if (stat & (BF_EOB << BUFF_FLAGS_OFFSET)) { + /* finished */ + *r_freed += 1; + if (size_array) + size_array[i] = stat & MASK_DATA_SIZE; + } else if ((stat & (BF_VALID << BUFF_FLAGS_OFFSET)) + == 0) + /* free */ + *r_needed += 1; + } + +#if 0 + snd_printdd(LXP "CMD_08_ASK_BUFFERS: needed %d, freed %d\n", + *r_needed, *r_freed); + for (i = 0; i < MAX_STREAM_BUFFER; ++i) { + for (i = 0; i != chip->rmh.stat_len; ++i) + snd_printdd(" stat[%d]: %x, %x\n", i, + chip->rmh.stat[i], + chip->rmh.stat[i] & MASK_DATA_SIZE); + } +#endif + } + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + + +int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_09_STOP_PIPE); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +static int lx_pipe_toggle_state(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0B_TOGGLE_PIPE_STATE); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + + +int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err; + + err = lx_pipe_wait_for_idle(chip, pipe, is_capture); + if (err < 0) + return err; + + err = lx_pipe_toggle_state(chip, pipe, is_capture); + + return err; +} + +int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture) +{ + int err = 0; + + err = lx_pipe_wait_for_start(chip, pipe, is_capture); + if (err < 0) + return err; + + err = lx_pipe_toggle_state(chip, pipe, is_capture); + + return err; +} + + +int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *rsample_count) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.stat_len = 2; /* need all words here! */ + + err = lx_message_send_atomic(chip, &chip->rmh); /* don't sleep! */ + + if (err != 0) + snd_printk(KERN_ERR + "lx6464es: could not query pipe's sample count\n"); + else { + *rsample_count = ((u64)(chip->rmh.stat[0] & MASK_SPL_COUNT_HI) + << 24) /* hi part */ + + chip->rmh.stat[1]; /* lo part */ + } + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err != 0) + snd_printk(KERN_ERR "lx6464es: could not query pipe's state\n"); + else + *rstate = (chip->rmh.stat[0] >> PSTATE_OFFSET) & 0x0F; + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +static int lx_pipe_wait_for_state(struct lx6464es *chip, u32 pipe, + int is_capture, u16 state) +{ + int i; + + /* max 2*PCMOnlyGranularity = 2*1024 at 44100 = < 50 ms: + * timeout 50 ms */ + for (i = 0; i != 50; ++i) { + u16 current_state; + int err = lx_pipe_state(chip, pipe, is_capture, ¤t_state); + + if (err < 0) + return err; + + if (current_state == state) + return 0; + + mdelay(1); + } + + return -ETIMEDOUT; +} + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_RUN); +} + +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_IDLE); +} + +/* low-level stream handling */ +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_13_SET_STREAM_STATE); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= state; + + err = lx_message_send_atomic(chip, &chip->rmh); + spin_unlock_irqrestore(&chip->msg_lock, flags); + + return err; +} + +int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime, + u32 pipe, int is_capture) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + u32 channels = runtime->channels; + + if (runtime->channels != channels) + snd_printk(KERN_ERR LXP "channel count mismatch: %d vs %d", + runtime->channels, channels); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0C_DEF_STREAM); + + chip->rmh.cmd[0] |= pipe_cmd; + + if (runtime->sample_bits == 16) + /* 16 bit format */ + chip->rmh.cmd[0] |= (STREAM_FMT_16b << STREAM_FMT_OFFSET); + + if (snd_pcm_format_little_endian(runtime->format)) + /* little endian/intel format */ + chip->rmh.cmd[0] |= (STREAM_FMT_intel << STREAM_FMT_OFFSET); + + chip->rmh.cmd[0] |= channels-1; + + err = lx_message_send_atomic(chip, &chip->rmh); + spin_unlock_irqrestore(&chip->msg_lock, flags); + + return err; +} + +int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture, + int *rstate) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + *rstate = (chip->rmh.stat[0] & SF_START) ? START_STATE : PAUSE_STATE; + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *r_bytepos) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT); + + chip->rmh.cmd[0] |= pipe_cmd; + + err = lx_message_send_atomic(chip, &chip->rmh); + + *r_bytepos = ((u64) (chip->rmh.stat[0] & MASK_SPL_COUNT_HI) + << 32) /* hi part */ + + chip->rmh.stat[1]; /* lo part */ + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +/* low-level buffer handling */ +int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi, + u32 *r_buffer_index) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0F_UPDATE_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= BF_NOTIFY_EOB; /* request interrupt notification */ + + /* todo: pause request, circular buffer */ + + chip->rmh.cmd[1] = buffer_size & MASK_DATA_SIZE; + chip->rmh.cmd[2] = buf_address_lo; + + if (buf_address_hi) { + chip->rmh.cmd_len = 4; + chip->rmh.cmd[3] = buf_address_hi; + chip->rmh.cmd[0] |= BF_64BITS_ADR; + } + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) { + *r_buffer_index = chip->rmh.stat[0]; + goto done; + } + + if (err == EB_RBUFFERS_TABLE_OVERFLOW) + snd_printk(LXP "lx_buffer_give EB_RBUFFERS_TABLE_OVERFLOW\n"); + + if (err == EB_INVALID_STREAM) + snd_printk(LXP "lx_buffer_give EB_INVALID_STREAM\n"); + + if (err == EB_CMD_REFUSED) + snd_printk(LXP "lx_buffer_give EB_CMD_REFUSED\n"); + + done: + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_buffer_size) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= MASK_BUFFER_ID; /* ask for the current buffer: the + * microblaze will seek for it */ + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) + *r_buffer_size = chip->rmh.stat[0] & MASK_DATA_SIZE; + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_index) +{ + int err; + unsigned long flags; + + u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe); + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER); + + chip->rmh.cmd[0] |= pipe_cmd; + chip->rmh.cmd[0] |= buffer_index; + + err = lx_message_send_atomic(chip, &chip->rmh); + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + + +/* low-level gain/peak handling + * + * \todo: can we unmute capture/playback channels independently? + * + * */ +int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute) +{ + int err; + unsigned long flags; + + /* bit set to 1: channel muted */ + u64 mute_mask = unmute ? 0 : 0xFFFFFFFFFFFFFFFFLLU; + + spin_lock_irqsave(&chip->msg_lock, flags); + lx_message_init(&chip->rmh, CMD_0D_SET_MUTE); + + chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, 0); + + chip->rmh.cmd[1] = (u32)(mute_mask >> (u64)32); /* hi part */ + chip->rmh.cmd[2] = (u32)(mute_mask & (u64)0xFFFFFFFF); /* lo part */ + + snd_printk("mute %x %x %x\n", chip->rmh.cmd[0], chip->rmh.cmd[1], + chip->rmh.cmd[2]); + + err = lx_message_send_atomic(chip, &chip->rmh); + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +static u32 peak_map[] = { + 0x00000109, /* -90.308dB */ + 0x0000083B, /* -72.247dB */ + 0x000020C4, /* -60.205dB */ + 0x00008273, /* -48.030dB */ + 0x00020756, /* -36.005dB */ + 0x00040C37, /* -30.001dB */ + 0x00081385, /* -24.002dB */ + 0x00101D3F, /* -18.000dB */ + 0x0016C310, /* -15.000dB */ + 0x002026F2, /* -12.001dB */ + 0x002D6A86, /* -9.000dB */ + 0x004026E6, /* -6.004dB */ + 0x005A9DF6, /* -3.000dB */ + 0x0065AC8B, /* -2.000dB */ + 0x00721481, /* -1.000dB */ + 0x007FFFFF, /* FS */ +}; + +int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels, + u32 *r_levels) +{ + int err = 0; + unsigned long flags; + int i; + spin_lock_irqsave(&chip->msg_lock, flags); + + for (i = 0; i < channels; i += 4) { + u32 s0, s1, s2, s3; + + lx_message_init(&chip->rmh, CMD_12_GET_PEAK); + chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, i); + + err = lx_message_send_atomic(chip, &chip->rmh); + + if (err == 0) { + s0 = peak_map[chip->rmh.stat[0] & 0x0F]; + s1 = peak_map[(chip->rmh.stat[0] >> 4) & 0xf]; + s2 = peak_map[(chip->rmh.stat[0] >> 8) & 0xf]; + s3 = peak_map[(chip->rmh.stat[0] >> 12) & 0xf]; + } else + s0 = s1 = s2 = s3 = 0; + + r_levels[0] = s0; + r_levels[1] = s1; + r_levels[2] = s2; + r_levels[3] = s3; + + r_levels += 4; + } + + spin_unlock_irqrestore(&chip->msg_lock, flags); + return err; +} + +/* interrupt handling */ +#define PCX_IRQ_NONE 0 +#define IRQCS_ACTIVE_PCIDB 0x00002000L /* Bit nø 13 */ +#define IRQCS_ENABLE_PCIIRQ 0x00000100L /* Bit nø 08 */ +#define IRQCS_ENABLE_PCIDB 0x00000200L /* Bit nø 09 */ + +static u32 lx_interrupt_test_ack(struct lx6464es *chip) +{ + u32 irqcs = lx_plx_reg_read(chip, ePLX_IRQCS); + + /* Test if PCI Doorbell interrupt is active */ + if (irqcs & IRQCS_ACTIVE_PCIDB) { + u32 temp; + irqcs = PCX_IRQ_NONE; + + while ((temp = lx_plx_reg_read(chip, ePLX_L2PCIDB))) { + /* RAZ interrupt */ + irqcs |= temp; + lx_plx_reg_write(chip, ePLX_L2PCIDB, temp); + } + + return irqcs; + } + return PCX_IRQ_NONE; +} + +static int lx_interrupt_ack(struct lx6464es *chip, u32 *r_irqsrc, + int *r_async_pending, int *r_async_escmd) +{ + u32 irq_async; + u32 irqsrc = lx_interrupt_test_ack(chip); + + if (irqsrc == PCX_IRQ_NONE) + return 0; + + *r_irqsrc = irqsrc; + + irq_async = irqsrc & MASK_SYS_ASYNC_EVENTS; /* + EtherSound response + * (set by xilinx) + EOB */ + + if (irq_async & MASK_SYS_STATUS_ESA) { + irq_async &= ~MASK_SYS_STATUS_ESA; + *r_async_escmd = 1; + } + + if (irqsrc & MASK_SYS_STATUS_CMD_DONE) + /* xilinx command notification */ + atomic_set(&chip->send_message_locked, 0); + + if (irq_async) { + /* snd_printd("interrupt: async event pending\n"); */ + *r_async_pending = 1; + } + + return 1; +} + +static int lx_interrupt_handle_async_events(struct lx6464es *chip, u32 irqsrc, + int *r_freq_changed, + u64 *r_notified_in_pipe_mask, + u64 *r_notified_out_pipe_mask) +{ + int err; + u32 stat[9]; /* answer from CMD_04_GET_EVENT */ + + /* On peut optimiser pour ne pas lire les evenements vides + * les mots de réponse sont dans l'ordre suivant : + * Stat[0] mot de status général + * Stat[1] fin de buffer OUT pF + * Stat[2] fin de buffer OUT pf + * Stat[3] fin de buffer IN pF + * Stat[4] fin de buffer IN pf + * Stat[5] underrun poid fort + * Stat[6] underrun poid faible + * Stat[7] overrun poid fort + * Stat[8] overrun poid faible + * */ + + u64 orun_mask; + u64 urun_mask; +#if 0 + int has_underrun = (irqsrc & MASK_SYS_STATUS_URUN) ? 1 : 0; + int has_overrun = (irqsrc & MASK_SYS_STATUS_ORUN) ? 1 : 0; +#endif + int eb_pending_out = (irqsrc & MASK_SYS_STATUS_EOBO) ? 1 : 0; + int eb_pending_in = (irqsrc & MASK_SYS_STATUS_EOBI) ? 1 : 0; + + *r_freq_changed = (irqsrc & MASK_SYS_STATUS_FREQ) ? 1 : 0; + + err = lx_dsp_read_async_events(chip, stat); + if (err < 0) + return err; + + if (eb_pending_in) { + *r_notified_in_pipe_mask = ((u64)stat[3] << 32) + + stat[4]; + snd_printdd(LXP "interrupt: EOBI pending %llx\n", + *r_notified_in_pipe_mask); + } + if (eb_pending_out) { + *r_notified_out_pipe_mask = ((u64)stat[1] << 32) + + stat[2]; + snd_printdd(LXP "interrupt: EOBO pending %llx\n", + *r_notified_out_pipe_mask); + } + + orun_mask = ((u64)stat[7] << 32) + stat[8]; + urun_mask = ((u64)stat[5] << 32) + stat[6]; + + /* todo: handle xrun notification */ + + return err; +} + +static int lx_interrupt_request_new_buffer(struct lx6464es *chip, + struct lx_stream *lx_stream) +{ + struct snd_pcm_substream *substream = lx_stream->stream; + int is_capture = lx_stream->is_capture; + int err; + unsigned long flags; + + const u32 channels = substream->runtime->channels; + const u32 bytes_per_frame = channels * 3; + const u32 period_size = substream->runtime->period_size; + const u32 period_bytes = period_size * bytes_per_frame; + const u32 pos = lx_stream->frame_pos; + const u32 next_pos = ((pos+1) == substream->runtime->periods) ? + 0 : pos + 1; + + dma_addr_t buf = substream->dma_buffer.addr + pos * period_bytes; + u32 buf_hi = 0; + u32 buf_lo = 0; + u32 buffer_index = 0; + + u32 needed, freed; + u32 size_array[MAX_STREAM_BUFFER]; + + snd_printdd("->lx_interrupt_request_new_buffer\n"); + + spin_lock_irqsave(&chip->lock, flags); + + err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array); + snd_printdd(LXP "interrupt: needed %d, freed %d\n", needed, freed); + + unpack_pointer(buf, &buf_lo, &buf_hi); + err = lx_buffer_give(chip, 0, is_capture, period_bytes, buf_lo, buf_hi, + &buffer_index); + snd_printdd(LXP "interrupt: gave buffer index %x on %p (%d bytes)\n", + buffer_index, (void *)buf, period_bytes); + + lx_stream->frame_pos = next_pos; + spin_unlock_irqrestore(&chip->lock, flags); + + return err; +} + +void lx_tasklet_playback(unsigned long data) +{ + struct lx6464es *chip = (struct lx6464es *)data; + struct lx_stream *lx_stream = &chip->playback_stream; + int err; + + snd_printdd("->lx_tasklet_playback\n"); + + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + snd_printk(KERN_ERR LXP + "cannot request new buffer for playback\n"); + + snd_pcm_period_elapsed(lx_stream->stream); +} + +void lx_tasklet_capture(unsigned long data) +{ + struct lx6464es *chip = (struct lx6464es *)data; + struct lx_stream *lx_stream = &chip->capture_stream; + int err; + + snd_printdd("->lx_tasklet_capture\n"); + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + snd_printk(KERN_ERR LXP + "cannot request new buffer for capture\n"); + + snd_pcm_period_elapsed(lx_stream->stream); +} + + + +static int lx_interrupt_handle_audio_transfer(struct lx6464es *chip, + u64 notified_in_pipe_mask, + u64 notified_out_pipe_mask) +{ + int err = 0; + + if (notified_in_pipe_mask) { + snd_printdd(LXP "requesting audio transfer for capture\n"); + tasklet_hi_schedule(&chip->tasklet_capture); + } + + if (notified_out_pipe_mask) { + snd_printdd(LXP "requesting audio transfer for playback\n"); + tasklet_hi_schedule(&chip->tasklet_playback); + } + + return err; +} + + +irqreturn_t lx_interrupt(int irq, void *dev_id) +{ + struct lx6464es *chip = dev_id; + int async_pending, async_escmd; + u32 irqsrc; + + spin_lock(&chip->lock); + + snd_printdd("**************************************************\n"); + + if (!lx_interrupt_ack(chip, &irqsrc, &async_pending, &async_escmd)) { + spin_unlock(&chip->lock); + snd_printdd("IRQ_NONE\n"); + return IRQ_NONE; /* this device did not cause the interrupt */ + } + + if (irqsrc & MASK_SYS_STATUS_CMD_DONE) + goto exit; + +#if 0 + if (irqsrc & MASK_SYS_STATUS_EOBI) + snd_printdd(LXP "interrupt: EOBI\n"); + + if (irqsrc & MASK_SYS_STATUS_EOBO) + snd_printdd(LXP "interrupt: EOBO\n"); + + if (irqsrc & MASK_SYS_STATUS_URUN) + snd_printdd(LXP "interrupt: URUN\n"); + + if (irqsrc & MASK_SYS_STATUS_ORUN) + snd_printdd(LXP "interrupt: ORUN\n"); +#endif + + if (async_pending) { + u64 notified_in_pipe_mask = 0; + u64 notified_out_pipe_mask = 0; + int freq_changed; + int err; + + /* handle async events */ + err = lx_interrupt_handle_async_events(chip, irqsrc, + &freq_changed, + ¬ified_in_pipe_mask, + ¬ified_out_pipe_mask); + if (err) + snd_printk(KERN_ERR LXP + "error handling async events\n"); + + err = lx_interrupt_handle_audio_transfer(chip, + notified_in_pipe_mask, + notified_out_pipe_mask + ); + if (err) + snd_printk(KERN_ERR LXP + "error during audio transfer\n"); + } + + if (async_escmd) { +#if 0 + /* backdoor for ethersound commands + * + * for now, we do not need this + * + * */ + + snd_printdd("lx6464es: interrupt requests escmd handling\n"); +#endif + } + +exit: + spin_unlock(&chip->lock); + return IRQ_HANDLED; /* this device caused the interrupt */ +} + + +static void lx_irq_set(struct lx6464es *chip, int enable) +{ + u32 reg = lx_plx_reg_read(chip, ePLX_IRQCS); + + /* enable/disable interrupts + * + * Set the Doorbell and PCI interrupt enable bits + * + * */ + if (enable) + reg |= (IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + else + reg &= ~(IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + lx_plx_reg_write(chip, ePLX_IRQCS, reg); +} + +void lx_irq_enable(struct lx6464es *chip) +{ + snd_printdd("->lx_irq_enable\n"); + lx_irq_set(chip, 1); +} + +void lx_irq_disable(struct lx6464es *chip) +{ + snd_printdd("->lx_irq_disable\n"); + lx_irq_set(chip, 0); +} diff --git a/sound/pci/lx6464es/lx_core.h b/sound/pci/lx6464es/lx_core.h new file mode 100644 index 00000000000..6bd9cbbbc68 --- /dev/null +++ b/sound/pci/lx6464es/lx_core.h @@ -0,0 +1,242 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann <tim@klingt.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef LX_CORE_H +#define LX_CORE_H + +#include <linux/interrupt.h> + +#include "lx_defs.h" + +#define REG_CRM_NUMBER 12 + +struct lx6464es; + +/* low-level register access */ + +/* dsp register access */ +enum { + eReg_BASE, + eReg_CSM, + eReg_CRM1, + eReg_CRM2, + eReg_CRM3, + eReg_CRM4, + eReg_CRM5, + eReg_CRM6, + eReg_CRM7, + eReg_CRM8, + eReg_CRM9, + eReg_CRM10, + eReg_CRM11, + eReg_CRM12, + + eReg_ICR, + eReg_CVR, + eReg_ISR, + eReg_RXHTXH, + eReg_RXMTXM, + eReg_RHLTXL, + eReg_RESETDSP, + + eReg_CSUF, + eReg_CSES, + eReg_CRESMSB, + eReg_CRESLSB, + eReg_ADMACESMSB, + eReg_ADMACESLSB, + eReg_CONFES, + + eMaxPortLx +}; + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port); +void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, u32 len); +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data); +void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, const u32 *data, + u32 len); + +/* plx register access */ +enum { + ePLX_PCICR, + + ePLX_MBOX0, + ePLX_MBOX1, + ePLX_MBOX2, + ePLX_MBOX3, + ePLX_MBOX4, + ePLX_MBOX5, + ePLX_MBOX6, + ePLX_MBOX7, + + ePLX_L2PCIDB, + ePLX_IRQCS, + ePLX_CHIPSC, + + eMaxPort +}; + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port); +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data); + +/* rhm */ +struct lx_rmh { + u16 cmd_len; /* length of the command to send (WORDs) */ + u16 stat_len; /* length of the status received (WORDs) */ + u16 dsp_stat; /* status type, RMP_SSIZE_XXX */ + u16 cmd_idx; /* index of the command */ + u32 cmd[REG_CRM_NUMBER]; + u32 stat[REG_CRM_NUMBER]; +}; + + +/* low-level dsp access */ +int __devinit lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version); +int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq); +int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran); +int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data); +int lx_dsp_get_mac(struct lx6464es *chip, u8 *mac_address); + + +/* low-level pipe handling */ +int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture, + int channels); +int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *rsample_count); +int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate); +int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture); + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture); + +/* low-level stream handling */ +int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime, + u32 pipe, int is_capture); +int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture, + int *rstate); +int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *r_bytepos); + +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state); + +static inline int lx_stream_start(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + snd_printdd("->lx_stream_start\n"); + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_RUN); +} + +static inline int lx_stream_pause(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + snd_printdd("->lx_stream_pause\n"); + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_PAUSE); +} + +static inline int lx_stream_stop(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + snd_printdd("->lx_stream_stop\n"); + return lx_stream_set_state(chip, pipe, is_capture, SSTATE_STOP); +} + +/* low-level buffer handling */ +int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_needed, u32 *r_freed, u32 *size_array); +int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi, + u32 *r_buffer_index); +int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture, + u32 *r_buffer_size); +int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture, + u32 buffer_index); + +/* low-level gain/peak handling */ +int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute); +int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels, + u32 *r_levels); + + +/* interrupt handling */ +irqreturn_t lx_interrupt(int irq, void *dev_id); +void lx_irq_enable(struct lx6464es *chip); +void lx_irq_disable(struct lx6464es *chip); + +void lx_tasklet_capture(unsigned long data); +void lx_tasklet_playback(unsigned long data); + + +/* Stream Format Header Defines (for LIN and IEEE754) */ +#define HEADER_FMT_BASE HEADER_FMT_BASE_LIN +#define HEADER_FMT_BASE_LIN 0xFED00000 +#define HEADER_FMT_BASE_FLOAT 0xFAD00000 +#define HEADER_FMT_MONO 0x00000080 /* bit 23 in header_lo. WARNING: old + * bit 22 is ignored in float + * format */ +#define HEADER_FMT_INTEL 0x00008000 +#define HEADER_FMT_16BITS 0x00002000 +#define HEADER_FMT_24BITS 0x00004000 +#define HEADER_FMT_UPTO11 0x00000200 /* frequency is less or equ. to 11k. + * */ +#define HEADER_FMT_UPTO32 0x00000100 /* frequency is over 11k and less + * then 32k.*/ + + +#define BIT_FMP_HEADER 23 +#define BIT_FMP_SD 22 +#define BIT_FMP_MULTICHANNEL 19 + +#define START_STATE 1 +#define PAUSE_STATE 0 + + + + + +/* from PcxAll_e.h */ +/* Start/Pause condition for pipes (PCXStartPipe, PCXPausePipe) */ +#define START_PAUSE_IMMEDIATE 0 +#define START_PAUSE_ON_SYNCHRO 1 +#define START_PAUSE_ON_TIME_CODE 2 + + +/* Pipe / Stream state */ +#define START_STATE 1 +#define PAUSE_STATE 0 + +static inline void unpack_pointer(dma_addr_t ptr, u32 *r_low, u32 *r_high) +{ + *r_low = (u32)(ptr & 0xffffffff); +#if BITS_PER_LONG == 32 + *r_high = 0; +#else + *r_high = (u32)((u64)ptr>>32); +#endif +} + +#endif /* LX_CORE_H */ diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h new file mode 100644 index 00000000000..49d36bdd512 --- /dev/null +++ b/sound/pci/lx6464es/lx_defs.h @@ -0,0 +1,376 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * adapted upstream headers + * + * Copyright (c) 2009 Tim Blechmann <tim@klingt.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef LX_DEFS_H +#define LX_DEFS_H + +/* code adapted from ethersound.h */ +#define XES_FREQ_COUNT8_MASK 0x00001FFF /* compteur 25MHz entre 8 ech. */ +#define XES_FREQ_COUNT8_44_MIN 0x00001288 /* 25M / + * [ 44k - ( 44.1k + 48k ) / 2 ] + * * 8 */ +#define XES_FREQ_COUNT8_44_MAX 0x000010F0 /* 25M / [ ( 44.1k + 48k ) / 2 ] + * * 8 */ +#define XES_FREQ_COUNT8_48_MAX 0x00000F08 /* 25M / + * [ 48k + ( 44.1k + 48k ) / 2 ] + * * 8 */ + +/* code adapted from LXES_registers.h */ + +#define IOCR_OUTPUTS_OFFSET 0 /* (rw) offset for the number of OUTs in the + * ConfES register. */ +#define IOCR_INPUTS_OFFSET 8 /* (rw) offset for the number of INs in the + * ConfES register. */ +#define FREQ_RATIO_OFFSET 19 /* (rw) offset for frequency ratio in the + * ConfES register. */ +#define FREQ_RATIO_SINGLE_MODE 0x01 /* value for single mode frequency ratio: + * sample rate = frequency rate. */ + +#define CONFES_READ_PART_MASK 0x00070000 +#define CONFES_WRITE_PART_MASK 0x00F80000 + +/* code adapted from if_drv_mb.h */ + +#define MASK_SYS_STATUS_ERROR (1L << 31) /* events that lead to a PCI irq if + * not yet pending */ +#define MASK_SYS_STATUS_URUN (1L << 30) +#define MASK_SYS_STATUS_ORUN (1L << 29) +#define MASK_SYS_STATUS_EOBO (1L << 28) +#define MASK_SYS_STATUS_EOBI (1L << 27) +#define MASK_SYS_STATUS_FREQ (1L << 26) +#define MASK_SYS_STATUS_ESA (1L << 25) /* reserved, this is set by the + * XES */ +#define MASK_SYS_STATUS_TIMER (1L << 24) + +#define MASK_SYS_ASYNC_EVENTS (MASK_SYS_STATUS_ERROR | \ + MASK_SYS_STATUS_URUN | \ + MASK_SYS_STATUS_ORUN | \ + MASK_SYS_STATUS_EOBO | \ + MASK_SYS_STATUS_EOBI | \ + MASK_SYS_STATUS_FREQ | \ + MASK_SYS_STATUS_ESA) + +#define MASK_SYS_PCI_EVENTS (MASK_SYS_ASYNC_EVENTS | \ + MASK_SYS_STATUS_TIMER) + +#define MASK_SYS_TIMER_COUNT 0x0000FFFF + +#define MASK_SYS_STATUS_EOT_PLX (1L << 22) /* event that remains + * internal: reserved fo end + * of plx dma */ +#define MASK_SYS_STATUS_XES (1L << 21) /* event that remains + * internal: pending XES + * IRQ */ +#define MASK_SYS_STATUS_CMD_DONE (1L << 20) /* alternate command + * management: notify driver + * instead of polling */ + + +#define MAX_STREAM_BUFFER 5 /* max amount of stream buffers. */ + +#define MICROBLAZE_IBL_MIN 32 +#define MICROBLAZE_IBL_DEFAULT 128 +#define MICROBLAZE_IBL_MAX 512 +/* #define MASK_GRANULARITY (2*MICROBLAZE_IBL_MAX-1) */ + + + +/* command opcodes, see reference for details */ + +/* + the capture bit position in the object_id field in driver commands + depends upon the number of managed channels. For now, 64 IN + 64 OUT are + supported. HOwever, the communication protocol forsees 1024 channels, hence + bit 10 indicates a capture (input) object). +*/ +#define ID_IS_CAPTURE (1L << 10) +#define ID_OFFSET 13 /* object ID is at the 13th bit in the + * 1st command word.*/ +#define ID_CH_MASK 0x3F +#define OPCODE_OFFSET 24 /* offset of the command opcode in the first + * command word.*/ + +enum cmd_mb_opcodes { + CMD_00_INFO_DEBUG = 0x00, + CMD_01_GET_SYS_CFG = 0x01, + CMD_02_SET_GRANULARITY = 0x02, + CMD_03_SET_TIMER_IRQ = 0x03, + CMD_04_GET_EVENT = 0x04, + CMD_05_GET_PIPES = 0x05, + + CMD_06_ALLOCATE_PIPE = 0x06, + CMD_07_RELEASE_PIPE = 0x07, + CMD_08_ASK_BUFFERS = 0x08, + CMD_09_STOP_PIPE = 0x09, + CMD_0A_GET_PIPE_SPL_COUNT = 0x0a, + CMD_0B_TOGGLE_PIPE_STATE = 0x0b, + + CMD_0C_DEF_STREAM = 0x0c, + CMD_0D_SET_MUTE = 0x0d, + CMD_0E_GET_STREAM_SPL_COUNT = 0x0e, + CMD_0F_UPDATE_BUFFER = 0x0f, + CMD_10_GET_BUFFER = 0x10, + CMD_11_CANCEL_BUFFER = 0x11, + CMD_12_GET_PEAK = 0x12, + CMD_13_SET_STREAM_STATE = 0x13, + CMD_14_INVALID = 0x14, +}; + +/* pipe states */ +enum pipe_state_t { + PSTATE_IDLE = 0, /* the pipe is not processed in the XES_IRQ + * (free or stopped, or paused). */ + PSTATE_RUN = 1, /* sustained play/record state. */ + PSTATE_PURGE = 2, /* the ES channels are now off, render pipes do + * not DMA, record pipe do a last DMA. */ + PSTATE_ACQUIRE = 3, /* the ES channels are now on, render pipes do + * not yet increase their sample count, record + * pipes do not DMA. */ + PSTATE_CLOSING = 4, /* the pipe is releasing, and may not yet + * receive an "alloc" command. */ +}; + +/* stream states */ +enum stream_state_t { + SSTATE_STOP = 0x00, /* setting to stop resets the stream spl + * count.*/ + SSTATE_RUN = (0x01 << 0), /* start DMA and spl count handling. */ + SSTATE_PAUSE = (0x01 << 1), /* pause DMA and spl count handling. */ +}; + +/* buffer flags */ +enum buffer_flags { + BF_VALID = 0x80, /* set if the buffer is valid, clear if free.*/ + BF_CURRENT = 0x40, /* set if this is the current buffer (there is + * always a current buffer).*/ + BF_NOTIFY_EOB = 0x20, /* set if this buffer must cause a PCI event + * when finished.*/ + BF_CIRCULAR = 0x10, /* set if buffer[1] must be copied to buffer[0] + * by the end of this buffer.*/ + BF_64BITS_ADR = 0x08, /* set if the hi part of the address is valid.*/ + BF_xx = 0x04, /* future extension.*/ + BF_EOB = 0x02, /* set if finished, but not yet free.*/ + BF_PAUSE = 0x01, /* pause stream at buffer end.*/ + BF_ZERO = 0x00, /* no flags (init).*/ +}; + +/** +* Stream Flags definitions +*/ +enum stream_flags { + SF_ZERO = 0x00000000, /* no flags (stream invalid). */ + SF_VALID = 0x10000000, /* the stream has a valid DMA_conf + * info (setstreamformat). */ + SF_XRUN = 0x20000000, /* the stream is un x-run state. */ + SF_START = 0x40000000, /* the DMA is running.*/ + SF_ASIO = 0x80000000, /* ASIO.*/ +}; + + +#define MASK_SPL_COUNT_HI 0x00FFFFFF /* 4 MSBits are status bits */ +#define PSTATE_OFFSET 28 /* 4 MSBits are status bits */ + + +#define MASK_STREAM_HAS_MAPPING (1L << 12) +#define MASK_STREAM_IS_ASIO (1L << 9) +#define STREAM_FMT_OFFSET 10 /* the stream fmt bits start at the 10th + * bit in the command word. */ + +#define STREAM_FMT_16b 0x02 +#define STREAM_FMT_intel 0x01 + +#define FREQ_FIELD_OFFSET 15 /* offset of the freq field in the response + * word */ + +#define BUFF_FLAGS_OFFSET 24 /* offset of the buffer flags in the + * response word. */ +#define MASK_DATA_SIZE 0x00FFFFFF /* this must match the field size of + * datasize in the buffer_t structure. */ + +#define MASK_BUFFER_ID 0xFF /* the cancel command awaits a buffer ID, + * may be 0xFF for "current". */ + + +/* code adapted from PcxErr_e.h */ + +/* Bits masks */ + +#define ERROR_MASK 0x8000 + +#define SOURCE_MASK 0x7800 + +#define E_SOURCE_BOARD 0x4000 /* 8 >> 1 */ +#define E_SOURCE_DRV 0x2000 /* 4 >> 1 */ +#define E_SOURCE_API 0x1000 /* 2 >> 1 */ +/* Error tools */ +#define E_SOURCE_TOOLS 0x0800 /* 1 >> 1 */ +/* Error pcxaudio */ +#define E_SOURCE_AUDIO 0x1800 /* 3 >> 1 */ +/* Error virtual pcx */ +#define E_SOURCE_VPCX 0x2800 /* 5 >> 1 */ +/* Error dispatcher */ +#define E_SOURCE_DISPATCHER 0x3000 /* 6 >> 1 */ +/* Error from CobraNet firmware */ +#define E_SOURCE_COBRANET 0x3800 /* 7 >> 1 */ + +#define E_SOURCE_USER 0x7800 + +#define CLASS_MASK 0x0700 + +#define CODE_MASK 0x00FF + +/* Bits values */ + +/* Values for the error/warning bit */ +#define ERROR_VALUE 0x8000 +#define WARNING_VALUE 0x0000 + +/* Class values */ +#define E_CLASS_GENERAL 0x0000 +#define E_CLASS_INVALID_CMD 0x0100 +#define E_CLASS_INVALID_STD_OBJECT 0x0200 +#define E_CLASS_RSRC_IMPOSSIBLE 0x0300 +#define E_CLASS_WRONG_CONTEXT 0x0400 +#define E_CLASS_BAD_SPECIFIC_PARAMETER 0x0500 +#define E_CLASS_REAL_TIME_ERROR 0x0600 +#define E_CLASS_DIRECTSHOW 0x0700 +#define E_CLASS_FREE 0x0700 + + +/* Complete DRV error code for the general class */ +#define ED_GN (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_GENERAL) +#define ED_CONCURRENCY (ED_GN | 0x01) +#define ED_DSP_CRASHED (ED_GN | 0x02) +#define ED_UNKNOWN_BOARD (ED_GN | 0x03) +#define ED_NOT_INSTALLED (ED_GN | 0x04) +#define ED_CANNOT_OPEN_SVC_MANAGER (ED_GN | 0x05) +#define ED_CANNOT_READ_REGISTRY (ED_GN | 0x06) +#define ED_DSP_VERSION_MISMATCH (ED_GN | 0x07) +#define ED_UNAVAILABLE_FEATURE (ED_GN | 0x08) +#define ED_CANCELLED (ED_GN | 0x09) +#define ED_NO_RESPONSE_AT_IRQA (ED_GN | 0x10) +#define ED_INVALID_ADDRESS (ED_GN | 0x11) +#define ED_DSP_CORRUPTED (ED_GN | 0x12) +#define ED_PENDING_OPERATION (ED_GN | 0x13) +#define ED_NET_ALLOCATE_MEMORY_IMPOSSIBLE (ED_GN | 0x14) +#define ED_NET_REGISTER_ERROR (ED_GN | 0x15) +#define ED_NET_THREAD_ERROR (ED_GN | 0x16) +#define ED_NET_OPEN_ERROR (ED_GN | 0x17) +#define ED_NET_CLOSE_ERROR (ED_GN | 0x18) +#define ED_NET_NO_MORE_PACKET (ED_GN | 0x19) +#define ED_NET_NO_MORE_BUFFER (ED_GN | 0x1A) +#define ED_NET_SEND_ERROR (ED_GN | 0x1B) +#define ED_NET_RECEIVE_ERROR (ED_GN | 0x1C) +#define ED_NET_WRONG_MSG_SIZE (ED_GN | 0x1D) +#define ED_NET_WAIT_ERROR (ED_GN | 0x1E) +#define ED_NET_EEPROM_ERROR (ED_GN | 0x1F) +#define ED_INVALID_RS232_COM_NUMBER (ED_GN | 0x20) +#define ED_INVALID_RS232_INIT (ED_GN | 0x21) +#define ED_FILE_ERROR (ED_GN | 0x22) +#define ED_INVALID_GPIO_CMD (ED_GN | 0x23) +#define ED_RS232_ALREADY_OPENED (ED_GN | 0x24) +#define ED_RS232_NOT_OPENED (ED_GN | 0x25) +#define ED_GPIO_ALREADY_OPENED (ED_GN | 0x26) +#define ED_GPIO_NOT_OPENED (ED_GN | 0x27) +#define ED_REGISTRY_ERROR (ED_GN | 0x28) /* <- NCX */ +#define ED_INVALID_SERVICE (ED_GN | 0x29) /* <- NCX */ + +#define ED_READ_FILE_ALREADY_OPENED (ED_GN | 0x2a) /* <- Decalage + * pour RCX + * (old 0x28) + * */ +#define ED_READ_FILE_INVALID_COMMAND (ED_GN | 0x2b) /* ~ */ +#define ED_READ_FILE_INVALID_PARAMETER (ED_GN | 0x2c) /* ~ */ +#define ED_READ_FILE_ALREADY_CLOSED (ED_GN | 0x2d) /* ~ */ +#define ED_READ_FILE_NO_INFORMATION (ED_GN | 0x2e) /* ~ */ +#define ED_READ_FILE_INVALID_HANDLE (ED_GN | 0x2f) /* ~ */ +#define ED_READ_FILE_END_OF_FILE (ED_GN | 0x30) /* ~ */ +#define ED_READ_FILE_ERROR (ED_GN | 0x31) /* ~ */ + +#define ED_DSP_CRASHED_EXC_DSPSTACK_OVERFLOW (ED_GN | 0x32) /* <- Decalage pour + * PCX (old 0x14) */ +#define ED_DSP_CRASHED_EXC_SYSSTACK_OVERFLOW (ED_GN | 0x33) /* ~ */ +#define ED_DSP_CRASHED_EXC_ILLEGAL (ED_GN | 0x34) /* ~ */ +#define ED_DSP_CRASHED_EXC_TIMER_REENTRY (ED_GN | 0x35) /* ~ */ +#define ED_DSP_CRASHED_EXC_FATAL_ERROR (ED_GN | 0x36) /* ~ */ + +#define ED_FLASH_PCCARD_NOT_PRESENT (ED_GN | 0x37) + +#define ED_NO_CURRENT_CLOCK (ED_GN | 0x38) + +/* Complete DRV error code for real time class */ +#define ED_RT (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_REAL_TIME_ERROR) +#define ED_DSP_TIMED_OUT (ED_RT | 0x01) +#define ED_DSP_CHK_TIMED_OUT (ED_RT | 0x02) +#define ED_STREAM_OVERRUN (ED_RT | 0x03) +#define ED_DSP_BUSY (ED_RT | 0x04) +#define ED_DSP_SEMAPHORE_TIME_OUT (ED_RT | 0x05) +#define ED_BOARD_TIME_OUT (ED_RT | 0x06) +#define ED_XILINX_ERROR (ED_RT | 0x07) +#define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) + +/* Complete BOARD error code for the invaid standard object class */ +#define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ + E_CLASS_INVALID_STD_OBJECT) +#define EB_INVALID_EFFECT (EB_ISO | 0x00) +#define EB_INVALID_PIPE (EB_ISO | 0x40) +#define EB_INVALID_STREAM (EB_ISO | 0x80) +#define EB_INVALID_AUDIO (EB_ISO | 0xC0) + +/* Complete BOARD error code for impossible resource allocation class */ +#define EB_RI (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_RSRC_IMPOSSIBLE) +#define EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE (EB_RI | 0x01) +#define EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE (EB_RI | 0x02) + +#define EB_ALLOCATE_MEM_STREAM_IMPOSSIBLE \ + EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE +#define EB_ALLOCATE_MEM_PIPE_IMPOSSIBLE \ + EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE + +#define EB_ALLOCATE_DIFFERED_CMD_IMPOSSIBLE (EB_RI | 0x03) +#define EB_TOO_MANY_DIFFERED_CMD (EB_RI | 0x04) +#define EB_RBUFFERS_TABLE_OVERFLOW (EB_RI | 0x05) +#define EB_ALLOCATE_EFFECTS_IMPOSSIBLE (EB_RI | 0x08) +#define EB_ALLOCATE_EFFECT_POS_IMPOSSIBLE (EB_RI | 0x09) +#define EB_RBUFFER_NOT_AVAILABLE (EB_RI | 0x0A) +#define EB_ALLOCATE_CONTEXT_LIII_IMPOSSIBLE (EB_RI | 0x0B) +#define EB_STATUS_DIALOG_IMPOSSIBLE (EB_RI | 0x1D) +#define EB_CONTROL_CMD_IMPOSSIBLE (EB_RI | 0x1E) +#define EB_STATUS_SEND_IMPOSSIBLE (EB_RI | 0x1F) +#define EB_ALLOCATE_PIPE_IMPOSSIBLE (EB_RI | 0x40) +#define EB_ALLOCATE_STREAM_IMPOSSIBLE (EB_RI | 0x80) +#define EB_ALLOCATE_AUDIO_IMPOSSIBLE (EB_RI | 0xC0) + +/* Complete BOARD error code for wrong call context class */ +#define EB_WCC (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_WRONG_CONTEXT) +#define EB_CMD_REFUSED (EB_WCC | 0x00) +#define EB_START_STREAM_REFUSED (EB_WCC | 0xFC) +#define EB_SPC_REFUSED (EB_WCC | 0xFD) +#define EB_CSN_REFUSED (EB_WCC | 0xFE) +#define EB_CSE_REFUSED (EB_WCC | 0xFF) + + + + +#endif /* LX_DEFS_H */ diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c index c262049961e..3b5ca70c9d4 100644 --- a/sound/pci/oxygen/oxygen_pcm.c +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -487,10 +487,14 @@ static int oxygen_hw_free(struct snd_pcm_substream *substream) { struct oxygen *chip = snd_pcm_substream_chip(substream); unsigned int channel = oxygen_substream_channel(substream); + unsigned int channel_mask = 1 << channel; spin_lock_irq(&chip->reg_lock); - chip->interrupt_mask &= ~(1 << channel); + chip->interrupt_mask &= ~channel_mask; oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + + oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); spin_unlock_irq(&chip->reg_lock); return snd_pcm_lib_free_pages(substream); diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c index bc5ce11c8b1..bf971f7cfdc 100644 --- a/sound/pci/oxygen/virtuoso.c +++ b/sound/pci/oxygen/virtuoso.c @@ -113,8 +113,8 @@ */ /* - * Xonar Essence STX - * ----------------- + * Xonar Essence ST (Deluxe)/STX + * ----------------------------- * * CMI8788: * @@ -180,6 +180,8 @@ enum { MODEL_DX, MODEL_HDAV, /* without daughterboard */ MODEL_HDAV_H6, /* with H6 daughterboard */ + MODEL_ST, + MODEL_ST_H6, MODEL_STX, }; @@ -188,8 +190,10 @@ static struct pci_device_id xonar_ids[] __devinitdata = { { OXYGEN_PCI_SUBID(0x1043, 0x8275), .driver_data = MODEL_DX }, { OXYGEN_PCI_SUBID(0x1043, 0x82b7), .driver_data = MODEL_D2X }, { OXYGEN_PCI_SUBID(0x1043, 0x8314), .driver_data = MODEL_HDAV }, + { OXYGEN_PCI_SUBID(0x1043, 0x8327), .driver_data = MODEL_DX }, { OXYGEN_PCI_SUBID(0x1043, 0x834f), .driver_data = MODEL_D1 }, { OXYGEN_PCI_SUBID(0x1043, 0x835c), .driver_data = MODEL_STX }, + { OXYGEN_PCI_SUBID(0x1043, 0x835d), .driver_data = MODEL_ST }, { OXYGEN_PCI_SUBID_BROKEN_EEPROM }, { } }; @@ -210,9 +214,9 @@ MODULE_DEVICE_TABLE(pci, xonar_ids); #define GPIO_DX_FRONT_PANEL 0x0002 #define GPIO_DX_INPUT_ROUTE 0x0100 -#define GPIO_HDAV_DB_MASK 0x0030 -#define GPIO_HDAV_DB_H6 0x0000 -#define GPIO_HDAV_DB_XX 0x0020 +#define GPIO_DB_MASK 0x0030 +#define GPIO_DB_H6 0x0000 +#define GPIO_DB_XX 0x0020 #define GPIO_ST_HP_REAR 0x0002 #define GPIO_ST_HP 0x0080 @@ -530,7 +534,7 @@ static void xonar_hdav_init(struct oxygen *chip) snd_component_add(chip->card, "CS5381"); } -static void xonar_stx_init(struct oxygen *chip) +static void xonar_st_init(struct oxygen *chip) { struct xonar_data *data = chip->model_data; @@ -539,12 +543,11 @@ static void xonar_stx_init(struct oxygen *chip) OXYGEN_2WIRE_INTERRUPT_MASK | OXYGEN_2WIRE_SPEED_FAST); + if (chip->model.private_data == MODEL_ST_H6) + chip->model.dac_channels = 8; data->anti_pop_delay = 100; - data->dacs = 1; + data->dacs = chip->model.private_data == MODEL_ST_H6 ? 4 : 1; data->output_enable_bit = GPIO_DX_OUTPUT_ENABLE; - data->ext_power_reg = OXYGEN_GPI_DATA; - data->ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; - data->ext_power_bit = GPI_DX_EXT_POWER; data->pcm1796_oversampling = PCM1796_OS_64; pcm1796_init(chip); @@ -560,6 +563,17 @@ static void xonar_stx_init(struct oxygen *chip) snd_component_add(chip->card, "CS5381"); } +static void xonar_stx_init(struct oxygen *chip) +{ + struct xonar_data *data = chip->model_data; + + data->ext_power_reg = OXYGEN_GPI_DATA; + data->ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->ext_power_bit = GPI_DX_EXT_POWER; + + xonar_st_init(chip); +} + static void xonar_disable_output(struct oxygen *chip) { struct xonar_data *data = chip->model_data; @@ -1021,7 +1035,8 @@ static const struct oxygen_model model_xonar_hdav = { .model_data_size = sizeof(struct xonar_data), .device_config = PLAYBACK_0_TO_I2S | PLAYBACK_1_TO_SPDIF | - CAPTURE_0_FROM_I2S_2, + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF, .dac_channels = 8, .dac_volume_min = 255 - 2*60, .dac_volume_max = 255, @@ -1034,7 +1049,7 @@ static const struct oxygen_model model_xonar_hdav = { static const struct oxygen_model model_xonar_st = { .longname = "Asus Virtuoso 100", .chip = "AV200", - .init = xonar_stx_init, + .init = xonar_st_init, .control_filter = xonar_st_control_filter, .mixer_init = xonar_st_mixer_init, .cleanup = xonar_st_cleanup, @@ -1067,6 +1082,7 @@ static int __devinit get_xonar_model(struct oxygen *chip, [MODEL_D2] = &model_xonar_d2, [MODEL_D2X] = &model_xonar_d2, [MODEL_HDAV] = &model_xonar_hdav, + [MODEL_ST] = &model_xonar_st, [MODEL_STX] = &model_xonar_st, }; static const char *const names[] = { @@ -1076,6 +1092,8 @@ static int __devinit get_xonar_model(struct oxygen *chip, [MODEL_D2X] = "Xonar D2X", [MODEL_HDAV] = "Xonar HDAV1.3", [MODEL_HDAV_H6] = "Xonar HDAV1.3+H6", + [MODEL_ST] = "Xonar Essence ST", + [MODEL_ST_H6] = "Xonar Essence ST+H6", [MODEL_STX] = "Xonar Essence STX", }; unsigned int model = id->driver_data; @@ -1092,21 +1110,27 @@ static int __devinit get_xonar_model(struct oxygen *chip, chip->model.init = xonar_dx_init; break; case MODEL_HDAV: - oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, - GPIO_HDAV_DB_MASK); - switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & - GPIO_HDAV_DB_MASK) { - case GPIO_HDAV_DB_H6: + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); + switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) { + case GPIO_DB_H6: model = MODEL_HDAV_H6; break; - case GPIO_HDAV_DB_XX: + case GPIO_DB_XX: snd_printk(KERN_ERR "unknown daughterboard\n"); return -ENODEV; } break; + case MODEL_ST: + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); + switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) { + case GPIO_DB_H6: + model = MODEL_ST_H6; + break; + } + break; case MODEL_STX: - oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, - GPIO_HDAV_DB_MASK); + chip->model.init = xonar_stx_init; + oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK); break; } diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 6f1034417a0..235a71e5ac8 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -507,41 +507,19 @@ static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip); */ static struct pci_device_id snd_riptide_ids[] = { - { - .vendor = 0x127a,.device = 0x4310, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - { - .vendor = 0x127a,.device = 0x4320, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - { - .vendor = 0x127a,.device = 0x4330, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - { - .vendor = 0x127a,.device = 0x4340, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, + { PCI_DEVICE(0x127a, 0x4310) }, + { PCI_DEVICE(0x127a, 0x4320) }, + { PCI_DEVICE(0x127a, 0x4330) }, + { PCI_DEVICE(0x127a, 0x4340) }, {0,}, }; #ifdef SUPPORT_JOYSTICK static struct pci_device_id snd_riptide_joystick_ids[] __devinitdata = { - { - .vendor = 0x127a,.device = 0x4312, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - { - .vendor = 0x127a,.device = 0x4322, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - {.vendor = 0x127a,.device = 0x4332, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, - {.vendor = 0x127a,.device = 0x4342, - .subvendor = PCI_ANY_ID,.subdevice = PCI_ANY_ID, - }, + { PCI_DEVICE(0x127a, 0x4312) }, + { PCI_DEVICE(0x127a, 0x4322) }, + { PCI_DEVICE(0x127a, 0x4332) }, + { PCI_DEVICE(0x127a, 0x4342) }, {0,}, }; #endif @@ -889,7 +867,7 @@ static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, spin_lock_irqsave(&cif->lock, irqflags); while (i++ < CMDIF_TIMEOUT && !IS_READY(cif->hwport)) udelay(10); - if (i >= CMDIF_TIMEOUT) { + if (i > CMDIF_TIMEOUT) { err = -EBUSY; goto errout; } @@ -907,8 +885,10 @@ static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm, WRITE_PORT_ULONG(cmdport->data1, cmd); /* write cmd */ if ((flags & RESP) && ret) { while (!IS_DATF(cmdport) && - time++ < CMDIF_TIMEOUT) + time < CMDIF_TIMEOUT) { udelay(10); + time++; + } if (time < CMDIF_TIMEOUT) { /* read response */ ret->retlongs[0] = READ_PORT_ULONG(cmdport->data1); @@ -1207,12 +1187,79 @@ static int riptide_resume(struct pci_dev *pci) } #endif +static int try_to_load_firmware(struct cmdif *cif, struct snd_riptide *chip) +{ + union firmware_version firmware = { .ret = CMDRET_ZERO }; + int i, timeout, err; + + for (i = 0; i < 2; i++) { + WRITE_PORT_ULONG(cif->hwport->port[i].data1, 0); + WRITE_PORT_ULONG(cif->hwport->port[i].data2, 0); + } + SET_GRESET(cif->hwport); + udelay(100); + UNSET_GRESET(cif->hwport); + udelay(100); + + for (timeout = 100000; --timeout; udelay(10)) { + if (IS_READY(cif->hwport) && !IS_GERR(cif->hwport)) + break; + } + if (!timeout) { + snd_printk(KERN_ERR + "Riptide: device not ready, audio status: 0x%x " + "ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + return -EIO; + } else { + snd_printdd + ("Riptide: audio status: 0x%x ready: %d gerr: %d\n", + READ_AUDIO_STATUS(cif->hwport), + IS_READY(cif->hwport), IS_GERR(cif->hwport)); + } + + SEND_GETV(cif, &firmware.ret); + snd_printdd("Firmware version: ASIC: %d CODEC %d AUXDSP %d PROG %d\n", + firmware.firmware.ASIC, firmware.firmware.CODEC, + firmware.firmware.AUXDSP, firmware.firmware.PROG); + + for (i = 0; i < FIRMWARE_VERSIONS; i++) { + if (!memcmp(&firmware_versions[i], &firmware, sizeof(firmware))) + break; + } + if (i >= FIRMWARE_VERSIONS) + return 0; /* no match */ + + if (!chip) + return 1; /* OK */ + + snd_printdd("Writing Firmware\n"); + if (!chip->fw_entry) { + err = request_firmware(&chip->fw_entry, "riptide.hex", + &chip->pci->dev); + if (err) { + snd_printk(KERN_ERR + "Riptide: Firmware not available %d\n", err); + return -EIO; + } + } + err = loadfirmware(cif, chip->fw_entry->data, chip->fw_entry->size); + if (err) { + snd_printk(KERN_ERR + "Riptide: Could not load firmware %d\n", err); + return err; + } + + chip->firmware = firmware; + + return 1; /* OK */ +} + static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip) { - int timeout, tries; union cmdret rptr = CMDRET_ZERO; - union firmware_version firmware; - int i, j, err, has_firmware; + int err, tries; if (!cif) return -EINVAL; @@ -1225,75 +1272,11 @@ static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip) cif->is_reset = 0; tries = RESET_TRIES; - has_firmware = 0; - while (has_firmware == 0 && tries-- > 0) { - for (i = 0; i < 2; i++) { - WRITE_PORT_ULONG(cif->hwport->port[i].data1, 0); - WRITE_PORT_ULONG(cif->hwport->port[i].data2, 0); - } - SET_GRESET(cif->hwport); - udelay(100); - UNSET_GRESET(cif->hwport); - udelay(100); - - for (timeout = 100000; --timeout; udelay(10)) { - if (IS_READY(cif->hwport) && !IS_GERR(cif->hwport)) - break; - } - if (timeout == 0) { - snd_printk(KERN_ERR - "Riptide: device not ready, audio status: 0x%x ready: %d gerr: %d\n", - READ_AUDIO_STATUS(cif->hwport), - IS_READY(cif->hwport), IS_GERR(cif->hwport)); - return -EIO; - } else { - snd_printdd - ("Riptide: audio status: 0x%x ready: %d gerr: %d\n", - READ_AUDIO_STATUS(cif->hwport), - IS_READY(cif->hwport), IS_GERR(cif->hwport)); - } - - SEND_GETV(cif, &rptr); - for (i = 0; i < 4; i++) - firmware.ret.retwords[i] = rptr.retwords[i]; - - snd_printdd - ("Firmware version: ASIC: %d CODEC %d AUXDSP %d PROG %d\n", - firmware.firmware.ASIC, firmware.firmware.CODEC, - firmware.firmware.AUXDSP, firmware.firmware.PROG); - - for (j = 0; j < FIRMWARE_VERSIONS; j++) { - has_firmware = 1; - for (i = 0; i < 4; i++) { - if (firmware_versions[j].ret.retwords[i] != - firmware.ret.retwords[i]) - has_firmware = 0; - } - if (has_firmware) - break; - } - - if (chip != NULL && has_firmware == 0) { - snd_printdd("Writing Firmware\n"); - if (!chip->fw_entry) { - if ((err = - request_firmware(&chip->fw_entry, - "riptide.hex", - &chip->pci->dev)) != 0) { - snd_printk(KERN_ERR - "Riptide: Firmware not available %d\n", - err); - return -EIO; - } - } - err = loadfirmware(cif, chip->fw_entry->data, - chip->fw_entry->size); - if (err) - snd_printk(KERN_ERR - "Riptide: Could not load firmware %d\n", - err); - } - } + do { + err = try_to_load_firmware(cif, chip); + if (err < 0) + return err; + } while (!err && --tries); SEND_SACR(cif, 0, AC97_RESET); SEND_RACR(cif, AC97_RESET, &rptr); @@ -1335,11 +1318,6 @@ static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip) SET_AIE(cif->hwport); SET_AIACK(cif->hwport); cif->is_reset = 1; - if (chip) { - for (i = 0; i < 4; i++) - chip->firmware.ret.retwords[i] = - firmware.ret.retwords[i]; - } return 0; } @@ -1454,7 +1432,7 @@ static int snd_riptide_trigger(struct snd_pcm_substream *substream, int cmd) SEND_GPOS(cif, 0, data->id, &rptr); udelay(1); } while (i != rptr.retlongs[1] && j++ < MAX_WRITE_RETRY); - if (j >= MAX_WRITE_RETRY) + if (j > MAX_WRITE_RETRY) snd_printk(KERN_ERR "Riptide: Could not stop stream!"); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: @@ -1783,7 +1761,7 @@ snd_riptide_codec_write(struct snd_ac97 *ac97, unsigned short reg, SEND_SACR(cif, val, reg); SEND_RACR(cif, reg, &rptr); } while (rptr.retwords[1] != val && i++ < MAX_WRITE_RETRY); - if (i == MAX_WRITE_RETRY) + if (i > MAX_WRITE_RETRY) snd_printdd("Write AC97 reg failed\n"); } @@ -2036,14 +2014,12 @@ static int __devinit snd_riptide_mixer(struct snd_riptide *chip) } #ifdef SUPPORT_JOYSTICK -static int have_joystick; -static struct pci_dev *riptide_gameport_pci; -static struct gameport *riptide_gameport; static int __devinit snd_riptide_joystick_probe(struct pci_dev *pci, const struct pci_device_id *id) { static int dev; + struct gameport *gameport; if (dev >= SNDRV_CARDS) return -ENODEV; @@ -2052,36 +2028,33 @@ snd_riptide_joystick_probe(struct pci_dev *pci, const struct pci_device_id *id) return -ENOENT; } - if (joystick_port[dev]) { - riptide_gameport = gameport_allocate_port(); - if (riptide_gameport) { - if (!request_region - (joystick_port[dev], 8, "Riptide gameport")) { - snd_printk(KERN_WARNING - "Riptide: cannot grab gameport 0x%x\n", - joystick_port[dev]); - gameport_free_port(riptide_gameport); - riptide_gameport = NULL; - } else { - riptide_gameport_pci = pci; - riptide_gameport->io = joystick_port[dev]; - gameport_register_port(riptide_gameport); - } - } + if (!joystick_port[dev++]) + return 0; + + gameport = gameport_allocate_port(); + if (!gameport) + return -ENOMEM; + if (!request_region(joystick_port[dev], 8, "Riptide gameport")) { + snd_printk(KERN_WARNING + "Riptide: cannot grab gameport 0x%x\n", + joystick_port[dev]); + gameport_free_port(gameport); + return -EBUSY; } - dev++; + + gameport->io = joystick_port[dev]; + gameport_register_port(gameport); + pci_set_drvdata(pci, gameport); return 0; } static void __devexit snd_riptide_joystick_remove(struct pci_dev *pci) { - if (riptide_gameport) { - if (riptide_gameport_pci == pci) { - release_region(riptide_gameport->io, 8); - riptide_gameport_pci = NULL; - gameport_unregister_port(riptide_gameport); - riptide_gameport = NULL; - } + struct gameport *gameport = pci_get_drvdata(pci); + if (gameport) { + release_region(gameport->io, 8); + gameport_unregister_port(gameport); + pci_set_drvdata(pci, NULL); } } #endif @@ -2092,8 +2065,8 @@ snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) static int dev; struct snd_card *card; struct snd_riptide *chip; - unsigned short addr; - int err = 0; + unsigned short val; + int err; if (dev >= SNDRV_CARDS) return -ENODEV; @@ -2105,60 +2078,63 @@ snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); if (err < 0) return err; - if ((err = snd_riptide_create(card, pci, &chip)) < 0) { - snd_card_free(card); - return err; - } + err = snd_riptide_create(card, pci, &chip); + if (err < 0) + goto error; card->private_data = chip; - if ((err = snd_riptide_pcm(chip, 0, NULL)) < 0) { - snd_card_free(card); - return err; - } - if ((err = snd_riptide_mixer(chip)) < 0) { - snd_card_free(card); - return err; - } - pci_write_config_word(chip->pci, PCI_EXT_Legacy_Mask, LEGACY_ENABLE_ALL - | (opl3_port[dev] ? LEGACY_ENABLE_FM : 0) + err = snd_riptide_pcm(chip, 0, NULL); + if (err < 0) + goto error; + err = snd_riptide_mixer(chip); + if (err < 0) + goto error; + + val = LEGACY_ENABLE_ALL; + if (opl3_port[dev]) + val |= LEGACY_ENABLE_FM; #ifdef SUPPORT_JOYSTICK - | (joystick_port[dev] ? LEGACY_ENABLE_GAMEPORT : - 0) + if (joystick_port[dev]) + val |= LEGACY_ENABLE_GAMEPORT; #endif - | (mpu_port[dev] - ? (LEGACY_ENABLE_MPU_INT | LEGACY_ENABLE_MPU) : - 0) - | ((chip->irq << 4) & 0xF0)); - if ((addr = mpu_port[dev]) != 0) { - pci_write_config_word(chip->pci, PCI_EXT_MPU_Base, addr); - if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_RIPTIDE, - addr, 0, chip->irq, 0, - &chip->rmidi)) < 0) + if (mpu_port[dev]) + val |= LEGACY_ENABLE_MPU_INT | LEGACY_ENABLE_MPU; + val |= (chip->irq << 4) & 0xf0; + pci_write_config_word(chip->pci, PCI_EXT_Legacy_Mask, val); + if (mpu_port[dev]) { + val = mpu_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_MPU_Base, val); + err = snd_mpu401_uart_new(card, 0, MPU401_HW_RIPTIDE, + val, 0, chip->irq, 0, + &chip->rmidi); + if (err < 0) snd_printk(KERN_WARNING "Riptide: Can't Allocate MPU at 0x%x\n", - addr); + val); else - chip->mpuaddr = addr; + chip->mpuaddr = val; } - if ((addr = opl3_port[dev]) != 0) { - pci_write_config_word(chip->pci, PCI_EXT_FM_Base, addr); - if ((err = snd_opl3_create(card, addr, addr + 2, - OPL3_HW_RIPTIDE, 0, - &chip->opl3)) < 0) + if (opl3_port[dev]) { + val = opl3_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_FM_Base, val); + err = snd_opl3_create(card, val, val + 2, + OPL3_HW_RIPTIDE, 0, &chip->opl3); + if (err < 0) snd_printk(KERN_WARNING "Riptide: Can't Allocate OPL3 at 0x%x\n", - addr); + val); else { - chip->opladdr = addr; - if ((err = - snd_opl3_hwdep_new(chip->opl3, 0, 1, NULL)) < 0) + chip->opladdr = val; + err = snd_opl3_hwdep_new(chip->opl3, 0, 1, NULL); + if (err < 0) snd_printk(KERN_WARNING "Riptide: Can't Allocate OPL3-HWDEP\n"); } } #ifdef SUPPORT_JOYSTICK - if ((addr = joystick_port[dev]) != 0) { - pci_write_config_word(chip->pci, PCI_EXT_Game_Base, addr); - chip->gameaddr = addr; + if (joystick_port[dev]) { + val = joystick_port[dev]; + pci_write_config_word(chip->pci, PCI_EXT_Game_Base, val); + chip->gameaddr = val; } #endif @@ -2176,13 +2152,16 @@ snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) chip->opladdr); #endif snd_riptide_proc_init(chip); - if ((err = snd_card_register(card)) < 0) { - snd_card_free(card); - return err; - } + err = snd_card_register(card); + if (err < 0) + goto error; pci_set_drvdata(pci, card); dev++; return 0; + + error: + snd_card_free(card); + return err; } static void __devexit snd_card_riptide_remove(struct pci_dev *pci) @@ -2214,14 +2193,11 @@ static struct pci_driver joystick_driver = { static int __init alsa_card_riptide_init(void) { int err; - if ((err = pci_register_driver(&driver)) < 0) + err = pci_register_driver(&driver); + if (err < 0) return err; #if defined(SUPPORT_JOYSTICK) - if (pci_register_driver(&joystick_driver) < 0) { - have_joystick = 0; - snd_printk(KERN_INFO "no joystick found\n"); - } else - have_joystick = 1; + pci_register_driver(&joystick_driver); #endif return 0; } @@ -2230,8 +2206,7 @@ static void __exit alsa_card_riptide_exit(void) { pci_unregister_driver(&driver); #if defined(SUPPORT_JOYSTICK) - if (have_joystick) - pci_unregister_driver(&joystick_driver); + pci_unregister_driver(&joystick_driver); #endif } diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 314e73531bd..3da5c029f93 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -28,6 +28,7 @@ #include <linux/pci.h> #include <linux/firmware.h> #include <linux/moduleparam.h> +#include <linux/math64.h> #include <sound/core.h> #include <sound/control.h> @@ -402,9 +403,9 @@ MODULE_FIRMWARE("digiface_firmware_rev11.bin"); #define HDSP_DMA_AREA_BYTES ((HDSP_MAX_CHANNELS+1) * HDSP_CHANNEL_BUFFER_BYTES) #define HDSP_DMA_AREA_KILOBYTES (HDSP_DMA_AREA_BYTES/1024) -/* use hotplug firmeare loader? */ +/* use hotplug firmware loader? */ #if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE) -#if !defined(HDSP_USE_HWDEP_LOADER) && !defined(CONFIG_SND_HDSP) +#if !defined(HDSP_USE_HWDEP_LOADER) #define HDSP_FW_LOADER #endif #endif @@ -1047,7 +1048,6 @@ static int hdsp_set_interrupt_interval(struct hdsp *s, unsigned int frames) static void hdsp_set_dds_value(struct hdsp *hdsp, int rate) { u64 n; - u32 r; if (rate >= 112000) rate /= 4; @@ -1055,7 +1055,7 @@ static void hdsp_set_dds_value(struct hdsp *hdsp, int rate) rate /= 2; n = DDS_NUMERATOR; - div64_32(&n, rate, &r); + n = div_u64(n, rate); /* n should be less than 2^32 for being written to FREQ register */ snd_BUG_ON(n >> 32); /* HDSP_freqReg and HDSP_resetPointer are the same, so keep the DDS @@ -3097,7 +3097,6 @@ static int snd_hdsp_get_adat_sync_check(struct snd_kcontrol *kcontrol, struct sn static int hdsp_dds_offset(struct hdsp *hdsp) { u64 n; - u32 r; unsigned int dds_value = hdsp->dds_value; int system_sample_rate = hdsp->system_sample_rate; @@ -3109,7 +3108,7 @@ static int hdsp_dds_offset(struct hdsp *hdsp) * dds_value = n / rate * rate = n / dds_value */ - div64_32(&n, dds_value, &r); + n = div_u64(n, dds_value); if (system_sample_rate >= 112000) n *= 4; else if (system_sample_rate >= 56000) diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index bac2dc0c5d8..0dce331a2a3 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -29,6 +29,7 @@ #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/pci.h> +#include <linux/math64.h> #include <asm/io.h> #include <sound/core.h> @@ -831,7 +832,6 @@ static int hdspm_set_interrupt_interval(struct hdspm * s, unsigned int frames) static void hdspm_set_dds_value(struct hdspm *hdspm, int rate) { u64 n; - u32 r; if (rate >= 112000) rate /= 4; @@ -844,7 +844,7 @@ static void hdspm_set_dds_value(struct hdspm *hdspm, int rate) */ /* n = 104857600000000ULL; */ /* = 2^20 * 10^8 */ n = 110100480000000ULL; /* Value checked for AES32 and MADI */ - div64_32(&n, rate, &r); + n = div_u64(n, rate); /* n should be less than 2^32 for being written to FREQ register */ snd_BUG_ON(n >> 32); hdspm_write(hdspm, HDSPM_freqReg, (u32)n); diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 809b233dd4a..1ef58c51c21 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1687,7 +1687,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol, return change; } -static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -9450, 150, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1); static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control __devinitdata = { .name = "PCM Playback Volume", diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c index 80df9b1f651..2cc0eda4f20 100644 --- a/sound/ppc/awacs.c +++ b/sound/ppc/awacs.c @@ -477,7 +477,7 @@ static int snd_pmac_awacs_put_master_amp(struct snd_kcontrol *kcontrol, #define AMP_CH_SPK 0 #define AMP_CH_HD 1 -static struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] __devinitdata = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PC Speaker Playback Volume", .info = snd_pmac_awacs_info_volume_amp, @@ -514,7 +514,7 @@ static struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] __initdata = { }, }; -static struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Playback Switch", .info = snd_pmac_boolean_stereo_info, @@ -523,7 +523,7 @@ static struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw __initdata = { .private_value = AMP_CH_HD, }; -static struct snd_kcontrol_new snd_pmac_awacs_amp_spk_sw __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_amp_spk_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PC Speaker Playback Switch", .info = snd_pmac_boolean_stereo_info, @@ -595,46 +595,46 @@ static int snd_pmac_screamer_mic_boost_put(struct snd_kcontrol *kcontrol, /* * lists of mixer elements */ -static struct snd_kcontrol_new snd_pmac_awacs_mixers[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers[] __devinitdata = { AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0), AWACS_VOLUME("Master Capture Volume", 0, 4, 0), /* AWACS_SWITCH("Unknown Playback Switch", 6, SHIFT_PAROUT0, 0), */ }; -static struct snd_kcontrol_new snd_pmac_screamer_mixers_beige[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mixers_beige[] __devinitdata = { AWACS_VOLUME("Master Playback Volume", 2, 6, 1), AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_LINE, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mixers_lo[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mixers_lo[] __devinitdata = { AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), }; -static struct snd_kcontrol_new snd_pmac_screamer_mixers_imac[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mixers_imac[] __devinitdata = { AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mixers_g4agp[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mixers_g4agp[] __devinitdata = { AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), AWACS_VOLUME("Master Playback Volume", 5, 6, 1), AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), }; -static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac7500[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac7500[] __devinitdata = { AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), }; -static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac5500[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac5500[] __devinitdata = { AWACS_VOLUME("Headphone Playback Volume", 2, 6, 1), }; -static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] __devinitdata = { AWACS_VOLUME("Master Playback Volume", 2, 6, 1), AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), }; @@ -642,34 +642,34 @@ static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] __initdata = { /* FIXME: is this correct order? * screamer (powerbook G3 pismo) seems to have different bits... */ -static struct snd_kcontrol_new snd_pmac_awacs_mixers2[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers2[] __devinitdata = { AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0), AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mixers2[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mixers2[] __devinitdata = { AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0), }; -static struct snd_kcontrol_new snd_pmac_awacs_mixers2_pmac5500[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mixers2_pmac5500[] __devinitdata = { AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), }; -static struct snd_kcontrol_new snd_pmac_awacs_master_sw __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_master_sw __devinitdata = AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1); -static struct snd_kcontrol_new snd_pmac_awacs_master_sw_imac __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_master_sw_imac __devinitdata = AWACS_SWITCH("Line out Playback Switch", 1, SHIFT_HDMUTE, 1); -static struct snd_kcontrol_new snd_pmac_awacs_master_sw_pmac5500 __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_master_sw_pmac5500 __devinitdata = AWACS_SWITCH("Headphone Playback Switch", 1, SHIFT_HDMUTE, 1); -static struct snd_kcontrol_new snd_pmac_awacs_mic_boost[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_mic_boost[] __devinitdata = { AWACS_SWITCH("Mic Boost Capture Switch", 0, SHIFT_GAINLINE, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] __initdata = { +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] __devinitdata = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mic Boost Capture Volume", .info = snd_pmac_screamer_mic_boost_info, @@ -678,34 +678,34 @@ static struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] __initdata = { }, }; -static struct snd_kcontrol_new snd_pmac_awacs_mic_boost_pmac7500[] __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_mic_boost_pmac7500[] __devinitdata = { AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_beige[] __initdata = +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_beige[] __devinitdata = { AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), AWACS_SWITCH("CD Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), }; -static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_imac[] __initdata = +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_imac[] __devinitdata = { AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), AWACS_SWITCH("Mic Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), }; -static struct snd_kcontrol_new snd_pmac_awacs_speaker_vol[] __initdata = { +static struct snd_kcontrol_new snd_pmac_awacs_speaker_vol[] __devinitdata = { AWACS_VOLUME("PC Speaker Playback Volume", 4, 6, 1), }; -static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw __devinitdata = AWACS_SWITCH("PC Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1); -static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac1 __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac1 __devinitdata = AWACS_SWITCH("PC Speaker Playback Switch", 1, SHIFT_PAROUT1, 1); -static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac2 __initdata = +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac2 __devinitdata = AWACS_SWITCH("PC Speaker Playback Switch", 1, SHIFT_PAROUT1, 0); @@ -872,7 +872,7 @@ static void snd_pmac_awacs_update_automute(struct snd_pmac *chip, int do_notify) /* * initialize chip */ -int __init +int __devinit snd_pmac_awacs_init(struct snd_pmac *chip) { int pm7500 = IS_PM7500; diff --git a/sound/ppc/beep.c b/sound/ppc/beep.c index 89f5c328acf..a9d350789f5 100644 --- a/sound/ppc/beep.c +++ b/sound/ppc/beep.c @@ -215,7 +215,7 @@ static struct snd_kcontrol_new snd_pmac_beep_mixer = { }; /* Initialize beep stuff */ -int __init snd_pmac_attach_beep(struct snd_pmac *chip) +int __devinit snd_pmac_attach_beep(struct snd_pmac *chip) { struct pmac_beep *beep; struct input_dev *input_dev; diff --git a/sound/ppc/burgundy.c b/sound/ppc/burgundy.c index 45a76297c38..16ed240e423 100644 --- a/sound/ppc/burgundy.c +++ b/sound/ppc/burgundy.c @@ -46,12 +46,12 @@ snd_pmac_burgundy_extend_wait(struct snd_pmac *chip) timeout = 50; while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) udelay(1); - if (! timeout) + if (timeout < 0) printk(KERN_DEBUG "burgundy_extend_wait: timeout #1\n"); timeout = 50; while ((in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) udelay(1); - if (! timeout) + if (timeout < 0) printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n"); } @@ -468,7 +468,7 @@ static int snd_pmac_burgundy_put_switch_b(struct snd_kcontrol *kcontrol, /* * Burgundy mixers */ -static struct snd_kcontrol_new snd_pmac_burgundy_mixers[] __initdata = { +static struct snd_kcontrol_new snd_pmac_burgundy_mixers[] __devinitdata = { BURGUNDY_VOLUME_W("Master Playback Volume", 0, MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8), BURGUNDY_VOLUME_W("CD Capture Volume", 0, @@ -496,7 +496,7 @@ static struct snd_kcontrol_new snd_pmac_burgundy_mixers[] __initdata = { */ BURGUNDY_SWITCH_B("PCM Capture Switch", 0, MASK_ADDR_BURGUNDY_HOSTIFEH, 0x01, 0, 0) }; -static struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] __initdata = { +static struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] __devinitdata = { BURGUNDY_VOLUME_W("Line in Capture Volume", 0, MASK_ADDR_BURGUNDY_VOLLINE, 16), BURGUNDY_VOLUME_W("Mic Capture Volume", 0, @@ -522,7 +522,7 @@ static struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] __initdata = { BURGUNDY_SWITCH_B("Mic Boost Capture Switch", 0, MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) }; -static struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] __initdata = { +static struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] __devinitdata = { BURGUNDY_VOLUME_W("Line in Capture Volume", 0, MASK_ADDR_BURGUNDY_VOLMIC, 16), BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0, @@ -538,33 +538,33 @@ static struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] __initdata = { /* BURGUNDY_SWITCH_B("Line in Boost Capture Switch", 0, * MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) */ }; -static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_imac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_imac __devinitdata = BURGUNDY_SWITCH_B("Master Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_OUTPUT_LEFT | BURGUNDY_LINEOUT_LEFT | BURGUNDY_HP_LEFT, BURGUNDY_OUTPUT_RIGHT | BURGUNDY_LINEOUT_RIGHT | BURGUNDY_HP_RIGHT, 1); -static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_pmac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_pmac __devinitdata = BURGUNDY_SWITCH_B("Master Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_OUTPUT_INTERN | BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); -static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_imac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_imac __devinitdata = BURGUNDY_SWITCH_B("PC Speaker Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); -static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_pmac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_pmac __devinitdata = BURGUNDY_SWITCH_B("PC Speaker Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_OUTPUT_INTERN, 0, 0); -static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_imac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_imac __devinitdata = BURGUNDY_SWITCH_B("Line out Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_LINEOUT_LEFT, BURGUNDY_LINEOUT_RIGHT, 1); -static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_pmac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_pmac __devinitdata = BURGUNDY_SWITCH_B("Line out Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); -static struct snd_kcontrol_new snd_pmac_burgundy_hp_sw_imac __initdata = +static struct snd_kcontrol_new snd_pmac_burgundy_hp_sw_imac __devinitdata = BURGUNDY_SWITCH_B("Headphone Playback Switch", 0, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, BURGUNDY_HP_LEFT, BURGUNDY_HP_RIGHT, 1); @@ -618,7 +618,7 @@ static void snd_pmac_burgundy_update_automute(struct snd_pmac *chip, int do_noti /* * initialize burgundy */ -int __init snd_pmac_burgundy_init(struct snd_pmac *chip) +int __devinit snd_pmac_burgundy_init(struct snd_pmac *chip) { int imac = machine_is_compatible("iMac"); int i, err; diff --git a/sound/ppc/daca.c b/sound/ppc/daca.c index f8d478c2da6..24200b7bdac 100644 --- a/sound/ppc/daca.c +++ b/sound/ppc/daca.c @@ -244,7 +244,7 @@ static void daca_cleanup(struct snd_pmac *chip) } /* exported */ -int __init snd_pmac_daca_init(struct snd_pmac *chip) +int __devinit snd_pmac_daca_init(struct snd_pmac *chip) { int i, err; struct pmac_daca *mix; diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c index a5afb2682e7..835fa19ed46 100644 --- a/sound/ppc/keywest.c +++ b/sound/ppc/keywest.c @@ -33,10 +33,6 @@ static struct pmac_keywest *keywest_ctx; -#ifndef i2c_device_name -#define i2c_device_name(x) ((x)->name) -#endif - static int keywest_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -56,7 +52,7 @@ static int keywest_attach_adapter(struct i2c_adapter *adapter) if (! keywest_ctx) return -EINVAL; - if (strncmp(i2c_device_name(adapter), "mac-io", 6)) + if (strncmp(adapter->name, "mac-io", 6)) return 0; /* ignored */ memset(&info, 0, sizeof(struct i2c_board_info)); @@ -109,7 +105,7 @@ void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c) } } -int __init snd_pmac_tumbler_post_init(void) +int __devinit snd_pmac_tumbler_post_init(void) { int err; @@ -124,7 +120,7 @@ int __init snd_pmac_tumbler_post_init(void) } /* exported */ -int __init snd_pmac_keywest_init(struct pmac_keywest *i2c) +int __devinit snd_pmac_keywest_init(struct pmac_keywest *i2c) { int err; diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c index 9b4e9c31669..7bc492ee77e 100644 --- a/sound/ppc/pmac.c +++ b/sound/ppc/pmac.c @@ -702,7 +702,7 @@ static struct snd_pcm_ops snd_pmac_capture_ops = { .pointer = snd_pmac_capture_pointer, }; -int __init snd_pmac_pcm_new(struct snd_pmac *chip) +int __devinit snd_pmac_pcm_new(struct snd_pmac *chip) { struct snd_pcm *pcm; int err; @@ -908,7 +908,7 @@ static int snd_pmac_dev_free(struct snd_device *device) * check the machine support byteswap (little-endian) */ -static void __init detect_byte_swap(struct snd_pmac *chip) +static void __devinit detect_byte_swap(struct snd_pmac *chip) { struct device_node *mio; @@ -934,7 +934,7 @@ static void __init detect_byte_swap(struct snd_pmac *chip) /* * detect a sound chip */ -static int __init snd_pmac_detect(struct snd_pmac *chip) +static int __devinit snd_pmac_detect(struct snd_pmac *chip) { struct device_node *sound; struct device_node *dn; @@ -1143,7 +1143,7 @@ static int pmac_hp_detect_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new auto_mute_controls[] __initdata = { +static struct snd_kcontrol_new auto_mute_controls[] __devinitdata = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Auto Mute Switch", .info = snd_pmac_boolean_mono_info, @@ -1158,7 +1158,7 @@ static struct snd_kcontrol_new auto_mute_controls[] __initdata = { }, }; -int __init snd_pmac_add_automute(struct snd_pmac *chip) +int __devinit snd_pmac_add_automute(struct snd_pmac *chip) { int err; chip->auto_mute = 1; @@ -1175,7 +1175,7 @@ int __init snd_pmac_add_automute(struct snd_pmac *chip) /* * create and detect a pmac chip record */ -int __init snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return) +int __devinit snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return) { struct snd_pmac *chip; struct device_node *np; diff --git a/sound/ppc/snd_ps3.c b/sound/ppc/snd_ps3.c index f361c26506a..53c81a54761 100644 --- a/sound/ppc/snd_ps3.c +++ b/sound/ppc/snd_ps3.c @@ -18,81 +18,31 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> #include <linux/init.h> -#include <linux/slab.h> -#include <linux/io.h> #include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include <sound/asound.h> +#include <sound/control.h> #include <sound/core.h> #include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/asound.h> #include <sound/memalloc.h> +#include <sound/pcm.h> #include <sound/pcm_params.h> -#include <sound/control.h> -#include <linux/dmapool.h> -#include <linux/dma-mapping.h> -#include <asm/firmware.h> + #include <asm/dma.h> +#include <asm/firmware.h> #include <asm/lv1call.h> #include <asm/ps3.h> #include <asm/ps3av.h> -#include "snd_ps3_reg.h" #include "snd_ps3.h" - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("PS3 sound driver"); -MODULE_AUTHOR("Sony Computer Entertainment Inc."); - -/* module entries */ -static int __init snd_ps3_init(void); -static void __exit snd_ps3_exit(void); - -/* ALSA snd driver ops */ -static int snd_ps3_pcm_open(struct snd_pcm_substream *substream); -static int snd_ps3_pcm_close(struct snd_pcm_substream *substream); -static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream); -static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream, - int cmd); -static snd_pcm_uframes_t snd_ps3_pcm_pointer(struct snd_pcm_substream - *substream); -static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params); -static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream); - - -/* ps3_system_bus_driver entries */ -static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev); -static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev); - -/* address setup */ -static int snd_ps3_map_mmio(void); -static void snd_ps3_unmap_mmio(void); -static int snd_ps3_allocate_irq(void); -static void snd_ps3_free_irq(void); -static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start); - -/* interrupt handler */ -static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id); - - -/* set sampling rate/format */ -static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream); -/* take effect parameter change */ -static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card); -/* initialize avsetting and take it effect */ -static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card); -/* setup dma */ -static int snd_ps3_program_dma(struct snd_ps3_card_info *card, - enum snd_ps3_dma_filltype filltype); -static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card); - -static dma_addr_t v_to_bus(struct snd_ps3_card_info *, void *vaddr, int ch); +#include "snd_ps3_reg.h" -module_init(snd_ps3_init); -module_exit(snd_ps3_exit); - /* * global */ @@ -165,25 +115,13 @@ static const struct snd_pcm_hardware snd_ps3_pcm_hw = { .fifo_size = PS3_AUDIO_FIFO_SIZE }; -static struct snd_pcm_ops snd_ps3_pcm_spdif_ops = -{ - .open = snd_ps3_pcm_open, - .close = snd_ps3_pcm_close, - .prepare = snd_ps3_pcm_prepare, - .ioctl = snd_pcm_lib_ioctl, - .trigger = snd_ps3_pcm_trigger, - .pointer = snd_ps3_pcm_pointer, - .hw_params = snd_ps3_pcm_hw_params, - .hw_free = snd_ps3_pcm_hw_free -}; - static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card, int count, int force_stop) { int dma_ch, done, retries, stop_forced = 0; uint32_t status; - for (dma_ch = 0; dma_ch < 8; dma_ch ++) { + for (dma_ch = 0; dma_ch < 8; dma_ch++) { retries = count; do { status = read_reg(PS3_AUDIO_KICK(dma_ch)) & @@ -259,9 +197,7 @@ static void snd_ps3_kick_dma(struct snd_ps3_card_info *card) /* * convert virtual addr to ioif bus addr. */ -static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, - void * paddr, - int ch) +static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, void *paddr, int ch) { return card->dma_start_bus_addr[ch] + (paddr - card->dma_start_vaddr[ch]); @@ -321,7 +257,7 @@ static int snd_ps3_program_dma(struct snd_ps3_card_info *card, spin_lock_irqsave(&card->dma_lock, irqsave); for (ch = 0; ch < 2; ch++) { start_vaddr = card->dma_next_transfer_vaddr[0]; - for (stage = 0; stage < fill_stages; stage ++) { + for (stage = 0; stage < fill_stages; stage++) { dma_ch = stage * 2 + ch; if (silent) dma_addr = card->null_buffer_start_dma_addr; @@ -372,6 +308,71 @@ static int snd_ps3_program_dma(struct snd_ps3_card_info *card, } /* + * Interrupt handler + */ +static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id) +{ + + uint32_t port_intr; + int underflow_occured = 0; + struct snd_ps3_card_info *card = dev_id; + + if (!card->running) { + update_reg(PS3_AUDIO_AX_IS, 0); + update_reg(PS3_AUDIO_INTR_0, 0); + return IRQ_HANDLED; + } + + port_intr = read_reg(PS3_AUDIO_AX_IS); + /* + *serial buffer empty detected (every 4 times), + *program next dma and kick it + */ + if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0)); + if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, port_intr); + underflow_occured = 1; + } + if (card->silent) { + /* we are still in silent time */ + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + card->silent--; + } else { + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_RUNNING); + snd_ps3_kick_dma(card); + snd_pcm_period_elapsed(card->substream); + } + } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * serial out underflow, but buffer empty not detected. + * in this case, fill fifo with 0 to recover. After + * filling dummy data, serial automatically start to + * consume them and then will generate normal buffer + * empty interrupts. + * If both buffer underflow and buffer empty are occured, + * it is better to do nomal data transfer than empty one + */ + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + } + /* clear interrupt cause */ + return IRQ_HANDLED; +}; + +/* * audio mute on/off * mute_on : 0 output enabled * 1 mute @@ -382,6 +383,142 @@ static int snd_ps3_mute(int mute_on) } /* + * av setting + * NOTE: calling this function may generate audio interrupt. + */ +static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card) +{ + int ret, retries, i; + pr_debug("%s: start\n", __func__); + + ret = ps3av_set_audio_mode(card->avs.avs_audio_ch, + card->avs.avs_audio_rate, + card->avs.avs_audio_width, + card->avs.avs_audio_format, + card->avs.avs_audio_source); + /* + * Reset the following unwanted settings: + */ + + /* disable all 3wire buffers */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(3)), + 0); + wmb(); /* ensure the hardware sees the change */ + /* wait for actually stopped */ + retries = 1000; + while ((read_reg(PS3_AUDIO_AO_3WMCTRL) & + (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) && + --retries) { + udelay(1); + } + + /* reset buffer pointer */ + for (i = 0; i < 4; i++) { + update_reg(PS3_AUDIO_AO_3WCTRL(i), + PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET); + udelay(10); + } + wmb(); /* ensure the hardware actually start resetting */ + + /* enable 3wire#0 buffer */ + update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0)); + + + /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */ + update_mask_reg(PS3_AUDIO_AO_3WCTRL(0), + ~PS3_AUDIO_AO_3WCTRL_ASODF, + PS3_AUDIO_AO_3WCTRL_ASODF_LSB); + update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0), + ~PS3_AUDIO_AO_SPDCTRL_SPODF, + PS3_AUDIO_AO_SPDCTRL_SPODF_LSB); + /* ensure all the setting above is written back to register */ + wmb(); + /* avsetting driver altered AX_IE, caller must reset it if you want */ + pr_debug("%s: end\n", __func__); + return ret; +} + +/* + * set sampling rate according to the substream + */ +static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + struct snd_ps3_avsetting_info avs; + int ret; + + avs = card->avs; + + pr_debug("%s: called freq=%d width=%d\n", __func__, + substream->runtime->rate, + snd_pcm_format_width(substream->runtime->format)); + + pr_debug("%s: before freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + /* sample rate */ + switch (substream->runtime->rate) { + case 44100: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K; + break; + case 48000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + break; + case 88200: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K; + break; + case 96000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K; + break; + default: + pr_info("%s: invalid rate %d\n", __func__, + substream->runtime->rate); + return 1; + } + + /* width */ + switch (snd_pcm_format_width(substream->runtime->format)) { + case 16: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + break; + case 24: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24; + break; + default: + pr_info("%s: invalid width %d\n", __func__, + snd_pcm_format_width(substream->runtime->format)); + return 1; + } + + memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8); + + if (memcmp(&card->avs, &avs, sizeof(avs))) { + pr_debug("%s: after freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + card->avs = avs; + snd_ps3_change_avsetting(card); + ret = 0; + } else + ret = 1; + + /* check CS non-audio bit and mute accordingly */ + if (avs.avs_cs_info[0] & 0x02) + ps3av_audio_mute_analog(1); /* mute if non-audio */ + else + ps3av_audio_mute_analog(0); + + return ret; +} + +/* * PCM operators */ static int snd_ps3_pcm_open(struct snd_pcm_substream *substream) @@ -406,6 +543,13 @@ static int snd_ps3_pcm_open(struct snd_pcm_substream *substream) return 0; }; +static int snd_ps3_pcm_close(struct snd_pcm_substream *substream) +{ + /* mute on */ + snd_ps3_mute(1); + return 0; +}; + static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -417,6 +561,13 @@ static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, return 0; }; +static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream) +{ + int ret; + ret = snd_pcm_lib_free_pages(substream); + return ret; +}; + static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream, unsigned int delay_ms) { @@ -556,202 +707,6 @@ static snd_pcm_uframes_t snd_ps3_pcm_pointer( return ret; }; -static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream) -{ - int ret; - ret = snd_pcm_lib_free_pages(substream); - return ret; -}; - -static int snd_ps3_pcm_close(struct snd_pcm_substream *substream) -{ - /* mute on */ - snd_ps3_mute(1); - return 0; -}; - -static void snd_ps3_audio_fixup(struct snd_ps3_card_info *card) -{ - /* - * avsetting driver seems to never change the followings - * so, init them here once - */ - - /* no dma interrupt needed */ - write_reg(PS3_AUDIO_INTR_EN_0, 0); - - /* use every 4 buffer empty interrupt */ - update_mask_reg(PS3_AUDIO_AX_IC, - PS3_AUDIO_AX_IC_AASOIMD_MASK, - PS3_AUDIO_AX_IC_AASOIMD_EVERY4); - - /* enable 3wire clocks */ - update_mask_reg(PS3_AUDIO_AO_3WMCTRL, - ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED | - PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED), - 0); - update_reg(PS3_AUDIO_AO_3WMCTRL, - PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT); -} - -/* - * av setting - * NOTE: calling this function may generate audio interrupt. - */ -static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card) -{ - int ret, retries, i; - pr_debug("%s: start\n", __func__); - - ret = ps3av_set_audio_mode(card->avs.avs_audio_ch, - card->avs.avs_audio_rate, - card->avs.avs_audio_width, - card->avs.avs_audio_format, - card->avs.avs_audio_source); - /* - * Reset the following unwanted settings: - */ - - /* disable all 3wire buffers */ - update_mask_reg(PS3_AUDIO_AO_3WMCTRL, - ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) | - PS3_AUDIO_AO_3WMCTRL_ASOEN(1) | - PS3_AUDIO_AO_3WMCTRL_ASOEN(2) | - PS3_AUDIO_AO_3WMCTRL_ASOEN(3)), - 0); - wmb(); /* ensure the hardware sees the change */ - /* wait for actually stopped */ - retries = 1000; - while ((read_reg(PS3_AUDIO_AO_3WMCTRL) & - (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) | - PS3_AUDIO_AO_3WMCTRL_ASORUN(1) | - PS3_AUDIO_AO_3WMCTRL_ASORUN(2) | - PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) && - --retries) { - udelay(1); - } - - /* reset buffer pointer */ - for (i = 0; i < 4; i++) { - update_reg(PS3_AUDIO_AO_3WCTRL(i), - PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET); - udelay(10); - } - wmb(); /* ensure the hardware actually start resetting */ - - /* enable 3wire#0 buffer */ - update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0)); - - - /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */ - update_mask_reg(PS3_AUDIO_AO_3WCTRL(0), - ~PS3_AUDIO_AO_3WCTRL_ASODF, - PS3_AUDIO_AO_3WCTRL_ASODF_LSB); - update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0), - ~PS3_AUDIO_AO_SPDCTRL_SPODF, - PS3_AUDIO_AO_SPDCTRL_SPODF_LSB); - /* ensure all the setting above is written back to register */ - wmb(); - /* avsetting driver altered AX_IE, caller must reset it if you want */ - pr_debug("%s: end\n", __func__); - return ret; -} - -static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card) -{ - int ret; - pr_debug("%s: start\n", __func__); - card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2; - card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; - card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; - card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM; - card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; - memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8); - - ret = snd_ps3_change_avsetting(card); - - snd_ps3_audio_fixup(card); - - /* to start to generate SPDIF signal, fill data */ - snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); - snd_ps3_kick_dma(card); - pr_debug("%s: end\n", __func__); - return ret; -} - -/* - * set sampling rate according to the substream - */ -static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream) -{ - struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); - struct snd_ps3_avsetting_info avs; - int ret; - - avs = card->avs; - - pr_debug("%s: called freq=%d width=%d\n", __func__, - substream->runtime->rate, - snd_pcm_format_width(substream->runtime->format)); - - pr_debug("%s: before freq=%d width=%d\n", __func__, - card->avs.avs_audio_rate, card->avs.avs_audio_width); - - /* sample rate */ - switch (substream->runtime->rate) { - case 44100: - avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K; - break; - case 48000: - avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; - break; - case 88200: - avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K; - break; - case 96000: - avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K; - break; - default: - pr_info("%s: invalid rate %d\n", __func__, - substream->runtime->rate); - return 1; - } - - /* width */ - switch (snd_pcm_format_width(substream->runtime->format)) { - case 16: - avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; - break; - case 24: - avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24; - break; - default: - pr_info("%s: invalid width %d\n", __func__, - snd_pcm_format_width(substream->runtime->format)); - return 1; - } - - memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8); - - if (memcmp(&card->avs, &avs, sizeof(avs))) { - pr_debug("%s: after freq=%d width=%d\n", __func__, - card->avs.avs_audio_rate, card->avs.avs_audio_width); - - card->avs = avs; - snd_ps3_change_avsetting(card); - ret = 0; - } else - ret = 1; - - /* check CS non-audio bit and mute accordingly */ - if (avs.avs_cs_info[0] & 0x02) - ps3av_audio_mute_analog(1); /* mute if non-audio */ - else - ps3av_audio_mute_analog(0); - - return ret; -} - /* * SPDIF status bits controls */ @@ -798,28 +753,39 @@ static struct snd_kcontrol_new spdif_ctls[] = { { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), .info = snd_ps3_spdif_mask_info, .get = snd_ps3_spdif_cmask_get, }, { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK), + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), .info = snd_ps3_spdif_mask_info, .get = snd_ps3_spdif_pmask_get, }, { .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), .info = snd_ps3_spdif_mask_info, .get = snd_ps3_spdif_default_get, .put = snd_ps3_spdif_default_put, }, }; +static struct snd_pcm_ops snd_ps3_pcm_spdif_ops = { + .open = snd_ps3_pcm_open, + .close = snd_ps3_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ps3_pcm_hw_params, + .hw_free = snd_ps3_pcm_hw_free, + .prepare = snd_ps3_pcm_prepare, + .trigger = snd_ps3_pcm_trigger, + .pointer = snd_ps3_pcm_pointer, +}; + -static int snd_ps3_map_mmio(void) +static int __devinit snd_ps3_map_mmio(void) { the_card.mapped_mmio_vaddr = ioremap(the_card.ps3_dev->m_region->bus_addr, @@ -841,7 +807,7 @@ static void snd_ps3_unmap_mmio(void) the_card.mapped_mmio_vaddr = NULL; } -static int snd_ps3_allocate_irq(void) +static int __devinit snd_ps3_allocate_irq(void) { int ret; u64 lpar_addr, lpar_size; @@ -899,7 +865,7 @@ static void snd_ps3_free_irq(void) ps3_irq_plug_destroy(the_card.irq_no); } -static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) +static void __devinit snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) { uint64_t val; int ret; @@ -915,7 +881,53 @@ static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) ret); } -static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev) +static void __devinit snd_ps3_audio_fixup(struct snd_ps3_card_info *card) +{ + /* + * avsetting driver seems to never change the followings + * so, init them here once + */ + + /* no dma interrupt needed */ + write_reg(PS3_AUDIO_INTR_EN_0, 0); + + /* use every 4 buffer empty interrupt */ + update_mask_reg(PS3_AUDIO_AX_IC, + PS3_AUDIO_AX_IC_AASOIMD_MASK, + PS3_AUDIO_AX_IC_AASOIMD_EVERY4); + + /* enable 3wire clocks */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED | + PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED), + 0); + update_reg(PS3_AUDIO_AO_3WMCTRL, + PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT); +} + +static int __devinit snd_ps3_init_avsetting(struct snd_ps3_card_info *card) +{ + int ret; + pr_debug("%s: start\n", __func__); + card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2; + card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM; + card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; + memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8); + + ret = snd_ps3_change_avsetting(card); + + snd_ps3_audio_fixup(card); + + /* to start to generate SPDIF signal, fill data */ + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + pr_debug("%s: end\n", __func__); + return ret; +} + +static int __devinit snd_ps3_driver_probe(struct ps3_system_bus_device *dev) { int i, ret; u64 lpar_addr, lpar_size; @@ -1020,11 +1032,12 @@ static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev) * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2 * PAGE_SIZE is enogh */ - if (!(the_card.null_buffer_start_vaddr = - dma_alloc_coherent(&the_card.ps3_dev->core, - PAGE_SIZE, - &the_card.null_buffer_start_dma_addr, - GFP_KERNEL))) { + the_card.null_buffer_start_vaddr = + dma_alloc_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + &the_card.null_buffer_start_dma_addr, + GFP_KERNEL); + if (!the_card.null_buffer_start_vaddr) { pr_info("%s: nullbuffer alloc failed\n", __func__); goto clean_preallocate; } @@ -1115,71 +1128,6 @@ static struct ps3_system_bus_driver snd_ps3_bus_driver_info = { /* - * Interrupt handler - */ -static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id) -{ - - uint32_t port_intr; - int underflow_occured = 0; - struct snd_ps3_card_info *card = dev_id; - - if (!card->running) { - update_reg(PS3_AUDIO_AX_IS, 0); - update_reg(PS3_AUDIO_INTR_0, 0); - return IRQ_HANDLED; - } - - port_intr = read_reg(PS3_AUDIO_AX_IS); - /* - *serial buffer empty detected (every 4 times), - *program next dma and kick it - */ - if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) { - write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0)); - if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { - write_reg(PS3_AUDIO_AX_IS, port_intr); - underflow_occured = 1; - } - if (card->silent) { - /* we are still in silent time */ - snd_ps3_program_dma(card, - (underflow_occured) ? - SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL : - SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); - snd_ps3_kick_dma(card); - card->silent --; - } else { - snd_ps3_program_dma(card, - (underflow_occured) ? - SND_PS3_DMA_FILLTYPE_FIRSTFILL : - SND_PS3_DMA_FILLTYPE_RUNNING); - snd_ps3_kick_dma(card); - snd_pcm_period_elapsed(card->substream); - } - } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { - write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0)); - /* - * serial out underflow, but buffer empty not detected. - * in this case, fill fifo with 0 to recover. After - * filling dummy data, serial automatically start to - * consume them and then will generate normal buffer - * empty interrupts. - * If both buffer underflow and buffer empty are occured, - * it is better to do nomal data transfer than empty one - */ - snd_ps3_program_dma(card, - SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); - snd_ps3_kick_dma(card); - snd_ps3_program_dma(card, - SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); - snd_ps3_kick_dma(card); - } - /* clear interrupt cause */ - return IRQ_HANDLED; -}; - -/* * module/subsystem initialize/terminate */ static int __init snd_ps3_init(void) @@ -1197,10 +1145,15 @@ static int __init snd_ps3_init(void) return ret; } +module_init(snd_ps3_init); static void __exit snd_ps3_exit(void) { ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info); } +module_exit(snd_ps3_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PS3 sound driver"); +MODULE_AUTHOR("Sony Computer Entertainment Inc."); MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND); diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 40222fcc087..08e584d1453 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -838,7 +838,7 @@ static int snapper_put_capture_source(struct snd_kcontrol *kcontrol, /* */ -static struct snd_kcontrol_new tumbler_mixers[] __initdata = { +static struct snd_kcontrol_new tumbler_mixers[] __devinitdata = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Volume", .info = tumbler_info_master_volume, @@ -862,7 +862,7 @@ static struct snd_kcontrol_new tumbler_mixers[] __initdata = { }, }; -static struct snd_kcontrol_new snapper_mixers[] __initdata = { +static struct snd_kcontrol_new snapper_mixers[] __devinitdata = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Volume", .info = tumbler_info_master_volume, @@ -895,7 +895,7 @@ static struct snd_kcontrol_new snapper_mixers[] __initdata = { }, }; -static struct snd_kcontrol_new tumbler_hp_sw __initdata = { +static struct snd_kcontrol_new tumbler_hp_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Playback Switch", .info = snd_pmac_boolean_mono_info, @@ -903,7 +903,7 @@ static struct snd_kcontrol_new tumbler_hp_sw __initdata = { .put = tumbler_put_mute_switch, .private_value = TUMBLER_MUTE_HP, }; -static struct snd_kcontrol_new tumbler_speaker_sw __initdata = { +static struct snd_kcontrol_new tumbler_speaker_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PC Speaker Playback Switch", .info = snd_pmac_boolean_mono_info, @@ -911,7 +911,7 @@ static struct snd_kcontrol_new tumbler_speaker_sw __initdata = { .put = tumbler_put_mute_switch, .private_value = TUMBLER_MUTE_AMP, }; -static struct snd_kcontrol_new tumbler_lineout_sw __initdata = { +static struct snd_kcontrol_new tumbler_lineout_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line Out Playback Switch", .info = snd_pmac_boolean_mono_info, @@ -919,7 +919,7 @@ static struct snd_kcontrol_new tumbler_lineout_sw __initdata = { .put = tumbler_put_mute_switch, .private_value = TUMBLER_MUTE_LINE, }; -static struct snd_kcontrol_new tumbler_drc_sw __initdata = { +static struct snd_kcontrol_new tumbler_drc_sw __devinitdata = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "DRC Switch", .info = snd_pmac_boolean_mono_info, @@ -1269,7 +1269,7 @@ static void tumbler_resume(struct snd_pmac *chip) #endif /* initialize tumbler */ -static int __init tumbler_init(struct snd_pmac *chip) +static int __devinit tumbler_init(struct snd_pmac *chip) { int irq; struct pmac_tumbler *mix = chip->mixer_data; @@ -1339,7 +1339,7 @@ static void tumbler_cleanup(struct snd_pmac *chip) } /* exported */ -int __init snd_pmac_tumbler_init(struct snd_pmac *chip) +int __devinit snd_pmac_tumbler_init(struct snd_pmac *chip) { int i, err; struct pmac_tumbler *mix; diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3d2bb6fc6dc..d3e786a9a0a 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -32,7 +32,9 @@ source "sound/soc/fsl/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" +source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/txx9/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 0237879fd41..6f1e28de23c 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -10,4 +10,6 @@ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ +obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index a608d7009db..e720d5e6f04 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -41,3 +41,11 @@ config SND_AT32_SOC_PLAYPAQ_SLAVE and FRAME signals on the PlayPaq. Unless you want to play with the AT32 as the SSC master, you probably want to say N here, as this will give you better sound quality. + +config SND_AT91_SOC_AFEB9260 + tristate "SoC Audio support for AFEB9260 board" + depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC + select SND_ATMEL_SOC_SSC + select SND_SOC_TLV320AIC23 + help + Say Y here to support sound on AFEB9260 board. diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index f54a7cc68e6..e7ea56bd5f8 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -13,3 +13,4 @@ snd-soc-playpaq-objs := playpaq_wm8510.o obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o +obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c index 70657534e6b..9eb610c2ba9 100644 --- a/sound/soc/atmel/playpaq_wm8510.c +++ b/sound/soc/atmel/playpaq_wm8510.c @@ -117,7 +117,7 @@ static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( * Find actual rate, compare to requested rate */ actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); - pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n", + pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n", rate, actual_rate); diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c new file mode 100644 index 00000000000..23349de2731 --- /dev/null +++ b/sound/soc/atmel/snd-soc-afeb9260.c @@ -0,0 +1,203 @@ +/* + * afeb9260.c -- SoC audio for AFEB9260 + * + * Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <linux/atmel-ssc.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> + +#include "../codecs/tlv320aic23.h" +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + +#define CODEC_CLOCK 12000000 + +static int afeb9260_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S| + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return err; + } + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return err; + } + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static struct snd_soc_ops afeb9260_ops = { + .hw_params = afeb9260_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int afeb9260_tlv320aic23_init(struct snd_soc_codec *codec) +{ + + /* Add afeb9260 specific widgets */ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* Set up afeb9260 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link afeb9260_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai = &atmel_ssc_dai[0], + .codec_dai = &tlv320aic23_dai, + .init = afeb9260_tlv320aic23_init, + .ops = &afeb9260_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_machine_afeb9260 = { + .name = "AFEB9260", + .platform = &atmel_soc_platform, + .dai_link = &afeb9260_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device afeb9260_snd_devdata = { + .card = &snd_soc_machine_afeb9260, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *afeb9260_snd_device; + +static int __init afeb9260_soc_init(void) +{ + int err; + struct device *dev; + struct atmel_ssc_info *ssc_p = afeb9260_dai.cpu_dai->private_data; + struct ssc_device *ssc = NULL; + + if (!(machine_is_afeb9260())) + return -ENODEV; + + ssc = ssc_request(0); + if (IS_ERR(ssc)) { + printk(KERN_ERR "ASoC: Failed to request SSC 0\n"); + err = PTR_ERR(ssc); + ssc = NULL; + goto err_ssc; + } + ssc_p->ssc = ssc; + + afeb9260_snd_device = platform_device_alloc("soc-audio", -1); + if (!afeb9260_snd_device) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(afeb9260_snd_device, &afeb9260_snd_devdata); + afeb9260_snd_devdata.dev = &afeb9260_snd_device->dev; + err = platform_device_add(afeb9260_snd_device); + if (err) + goto err1; + + dev = &afeb9260_snd_device->dev; + + return 0; +err1: + platform_device_del(afeb9260_snd_device); + platform_device_put(afeb9260_snd_device); +err_ssc: + return err; + +} + +static void __exit afeb9260_soc_exit(void) +{ + platform_device_unregister(afeb9260_snd_device); +} + +module_init(afeb9260_soc_init); +module_exit(afeb9260_soc_exit); + +MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>"); +MODULE_DESCRIPTION("ALSA SoC for AFEB9260"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index 8a935f2d176..b1ed423fabd 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -31,6 +31,15 @@ #include "bf5xx-sport.h" #include "bf5xx-ac97.h" +/* Anomaly notes: + * 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT + * contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1, + * while the max AC97 data size is 13*16. The DIV is always larger + * than data size. AD73311 and ad2602 are not running in TDM mode. + * AD1836 and AD73322 depend on external RFS/TFS only. So, this + * anomaly does not affect blackfin sound drivers. +*/ + static int *cmd_count; static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index 96482441967..af06904bab0 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -50,6 +50,7 @@ struct bf5xx_i2s_port { u16 tcr2; u16 rcr2; int counter; + int configured; }; static struct bf5xx_i2s_port bf5xx_i2s; @@ -168,7 +169,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, break; } - if (bf5xx_i2s.counter == 1) { + if (!bf5xx_i2s.configured) { /* * TX and RX are not independent,they are enabled at the * same time, even if only one side is running. So, we @@ -177,6 +178,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, * * CPU DAI:slave mode. */ + bf5xx_i2s.configured = 1; ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, bf5xx_i2s.rcr2, 0, 0); if (ret) { @@ -200,6 +202,9 @@ static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream, { pr_debug("%s enter\n", __func__); bf5xx_i2s.counter--; + /* No active stream, SPORT is allowed to be configured again. */ + if (!bf5xx_i2s.counter) + bf5xx_i2s.configured = 0; } static int bf5xx_i2s_probe(struct platform_device *pdev, @@ -244,8 +249,7 @@ static int bf5xx_i2s_suspend(struct snd_soc_dai *dai) return 0; } -static int bf5xx_i2s_resume(struct platform_device *pdev, - struct snd_soc_dai *dai) +static int bf5xx_i2s_resume(struct snd_soc_dai *dai) { int ret; struct sport_device *sport = diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c index b7953c8cf83..469ce7fab20 100644 --- a/sound/soc/blackfin/bf5xx-sport.c +++ b/sound/soc/blackfin/bf5xx-sport.c @@ -190,7 +190,7 @@ static inline int sport_hook_rx_dummy(struct sport_device *sport) desc = get_dma_next_desc_ptr(sport->dma_rx_chan); /* Copy the descriptor which will be damaged to backup */ temp_desc = *desc; - desc->x_count = 0xa; + desc->x_count = sport->dummy_count / 2; desc->y_count = 0; desc->next_desc_addr = sport->dummy_rx_desc; local_irq_restore(flags); @@ -309,7 +309,7 @@ static inline int sport_hook_tx_dummy(struct sport_device *sport) desc = get_dma_next_desc_ptr(sport->dma_tx_chan); /* Store the descriptor which will be damaged */ temp_desc = *desc; - desc->x_count = 0xa; + desc->x_count = sport->dummy_count / 2; desc->y_count = 0; desc->next_desc_addr = sport->dummy_tx_desc; local_irq_restore(flags); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b6c7f7a01cb..bbc97fd7664 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -18,7 +18,9 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_PCM3008 + select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C + select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC3X if I2C @@ -35,8 +37,12 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C + select SND_SOC_WM8940 if I2C + select SND_SOC_WM8960 if I2C select SND_SOC_WM8971 if I2C + select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C + select SND_SOC_WM9081 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS @@ -86,9 +92,15 @@ config SND_SOC_L3 config SND_SOC_PCM3008 tristate +config SND_SOC_SPDIF + tristate + config SND_SOC_SSM2602 tristate +config SND_SOC_STAC9766 + tristate + config SND_SOC_TLV320AIC23 tristate @@ -138,12 +150,24 @@ config SND_SOC_WM8900 config SND_SOC_WM8903 tristate +config SND_SOC_WM8940 + tristate + +config SND_SOC_WM8960 + tristate + config SND_SOC_WM8971 tristate +config SND_SOC_WM8988 + tristate + config SND_SOC_WM8990 tristate +config SND_SOC_WM9081 + tristate + config SND_SOC_WM9705 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f2653803ede..8b7530546f4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -6,7 +6,9 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o +snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o @@ -23,8 +25,12 @@ snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8940-objs := wm8940.o +snd-soc-wm8960-objs := wm8960.o snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o @@ -37,7 +43,9 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o +obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o @@ -55,7 +63,11 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o +obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o +obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index b0d4af145b8..932299bb5d1 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -53,13 +53,13 @@ struct snd_soc_dai ac97_dai = { .channels_min = 1, .channels_max = 2, .rates = STD_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "AC97 Capture", .channels_min = 1, .channels_max = 2, .rates = STD_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &ac97_dai_ops, }; EXPORT_SYMBOL_GPL(ac97_dai); diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c index ddb3b08ac23..d7440a982d2 100644 --- a/sound/soc/codecs/ad1980.c +++ b/sound/soc/codecs/ad1980.c @@ -137,13 +137,13 @@ struct snd_soc_dai ad1980_dai = { .channels_min = 2, .channels_max = 6, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .formats = SND_SOC_STD_AC97_FMTS, }, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .formats = SND_SOC_STD_AC97_FMTS, }, }; EXPORT_SYMBOL_GPL(ad1980_dai); diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 7fa09a38762..a32b8226c8a 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -18,7 +18,7 @@ * - The machine driver's 'startup' function must call * cs4270_set_dai_sysclk() with the value of MCLK. * - Only I2S and left-justified modes are supported - * - Power management is not supported + * - Power management is supported */ #include <linux/module.h> @@ -27,6 +27,7 @@ #include <sound/soc.h> #include <sound/initval.h> #include <linux/i2c.h> +#include <linux/delay.h> #include "cs4270.h" @@ -56,6 +57,7 @@ #define CS4270_FIRSTREG 0x01 #define CS4270_LASTREG 0x08 #define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) +#define CS4270_I2C_INCR 0x80 /* Bit masks for the CS4270 registers */ #define CS4270_CHIPID_ID 0xF0 @@ -64,6 +66,8 @@ #define CS4270_PWRCTL_PDN_ADC 0x20 #define CS4270_PWRCTL_PDN_DAC 0x02 #define CS4270_PWRCTL_PDN 0x01 +#define CS4270_PWRCTL_PDN_ALL \ + (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN) #define CS4270_MODE_SPEED_MASK 0x30 #define CS4270_MODE_1X 0x00 #define CS4270_MODE_2X 0x10 @@ -109,6 +113,7 @@ struct cs4270_private { unsigned int mclk; /* Input frequency of the MCLK pin */ unsigned int mode; /* The mode (I2S or left-justified) */ unsigned int slave_mode; + unsigned int manual_mute; }; /** @@ -295,7 +300,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec) s32 length; length = i2c_smbus_read_i2c_block_data(i2c_client, - CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache); + CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache); if (length != CS4270_NUMREGS) { dev_err(codec->dev, "i2c read failure, addr=0x%x\n", @@ -453,7 +458,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, } /** - * cs4270_mute - enable/disable the CS4270 external mute + * cs4270_dai_mute - enable/disable the CS4270 external mute * @dai: the SOC DAI * @mute: 0 = disable mute, 1 = enable mute * @@ -462,21 +467,52 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, * board does not have the MUTEA or MUTEB pins connected to such circuitry, * then this function will do nothing. */ -static int cs4270_mute(struct snd_soc_dai *dai, int mute) +static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; + struct cs4270_private *cs4270 = codec->private_data; int reg6; reg6 = snd_soc_read(codec, CS4270_MUTE); if (mute) reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; - else + else { reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); + reg6 |= cs4270->manual_mute; + } return snd_soc_write(codec, CS4270_MUTE, reg6); } +/** + * cs4270_soc_put_mute - put callback for the 'Master Playback switch' + * alsa control. + * @kcontrol: mixer control + * @ucontrol: control element information + * + * This function basically passes the arguments on to the generic + * snd_soc_put_volsw() function and saves the mute information in + * our private data structure. This is because we want to prevent + * cs4270_dai_mute() neglecting the user's decision to manually + * mute the codec's output. + * + * Returns 0 for success. + */ +static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs4270_private *cs4270 = codec->private_data; + int left = !ucontrol->value.integer.value[0]; + int right = !ucontrol->value.integer.value[1]; + + cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) | + (right ? CS4270_MUTE_DAC_B : 0); + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + /* A list of non-DAPM controls that the CS4270 supports */ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_DOUBLE_R("Master Playback Volume", @@ -486,7 +522,9 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0), SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1), SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0), - SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0) + SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1), + SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1, + snd_soc_get_volsw, cs4270_soc_put_mute), }; /* @@ -506,7 +544,7 @@ static struct snd_soc_dai_ops cs4270_dai_ops = { .hw_params = cs4270_hw_params, .set_sysclk = cs4270_set_dai_sysclk, .set_fmt = cs4270_set_dai_fmt, - .digital_mute = cs4270_mute, + .digital_mute = cs4270_dai_mute, }; struct snd_soc_dai cs4270_dai = { @@ -753,6 +791,57 @@ static struct i2c_device_id cs4270_id[] = { }; MODULE_DEVICE_TABLE(i2c, cs4270_id); +#ifdef CONFIG_PM + +/* This suspend/resume implementation can handle both - a simple standby + * where the codec remains powered, and a full suspend, where the voltage + * domain the codec is connected to is teared down and/or any other hardware + * reset condition is asserted. + * + * The codec's own power saving features are enabled in the suspend callback, + * and all registers are written back to the hardware when resuming. + */ + +static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cs4270_private *cs4270 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs4270->codec; + int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; + + return snd_soc_write(codec, CS4270_PWRCTL, reg); +} + +static int cs4270_i2c_resume(struct i2c_client *client) +{ + struct cs4270_private *cs4270 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs4270->codec; + int reg; + + /* In case the device was put to hard reset during sleep, we need to + * wait 500ns here before any I2C communication. */ + ndelay(500); + + /* first restore the entire register cache ... */ + for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) { + u8 val = snd_soc_read(codec, reg); + + if (i2c_smbus_write_byte_data(client, reg, val)) { + dev_err(codec->dev, "i2c write failed\n"); + return -EIO; + } + } + + /* ... then disable the power-down bits */ + reg = snd_soc_read(codec, CS4270_PWRCTL); + reg &= ~CS4270_PWRCTL_PDN_ALL; + + return snd_soc_write(codec, CS4270_PWRCTL, reg); +} +#else +#define cs4270_i2c_suspend NULL +#define cs4270_i2c_resume NULL +#endif /* CONFIG_PM */ + /* * cs4270_i2c_driver - I2C device identification * @@ -767,6 +856,8 @@ static struct i2c_driver cs4270_i2c_driver = { .id_table = cs4270_id, .probe = cs4270_i2c_probe, .remove = cs4270_i2c_remove, + .suspend = cs4270_i2c_suspend, + .resume = cs4270_i2c_resume, }; /* diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c new file mode 100644 index 00000000000..218b33adad9 --- /dev/null +++ b/sound/soc/codecs/spdif_transciever.c @@ -0,0 +1,71 @@ +/* + * ALSA SoC SPDIF DIT driver + * + * This driver is used by controllers which can operate in DIT (SPDI/F) where + * no codec is needed. This file provides stub codec that can be used + * in these configurations. TI DaVinci Audio controller uses this driver. + * + * Author: Steve Chen, <schen@mvista.com> + * Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com> + * Copyright: (C) 2009 Texas Instruments, India + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <sound/soc.h> +#include <sound/pcm.h> + +#include "spdif_transciever.h" + +#define STUB_RATES SNDRV_PCM_RATE_8000_96000 +#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_dai dit_stub_dai = { + .name = "DIT", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int spdif_dit_probe(struct platform_device *pdev) +{ + dit_stub_dai.dev = &pdev->dev; + return snd_soc_register_dai(&dit_stub_dai); +} + +static int spdif_dit_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&dit_stub_dai); + return 0; +} + +static struct platform_driver spdif_dit_driver = { + .probe = spdif_dit_probe, + .remove = spdif_dit_remove, + .driver = { + .name = "spdif-dit", + .owner = THIS_MODULE, + }, +}; + +static int __init dit_modinit(void) +{ + return platform_driver_register(&spdif_dit_driver); +} + +static void __exit dit_exit(void) +{ + platform_driver_unregister(&spdif_dit_driver); +} + +module_init(dit_modinit); +module_exit(dit_exit); + diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h new file mode 100644 index 00000000000..296f2eb6c4e --- /dev/null +++ b/sound/soc/codecs/spdif_transciever.h @@ -0,0 +1,17 @@ +/* + * ALSA SoC DIT/DIR driver header + * + * Author: Steve Chen, <schen@mvista.com> + * Copyright: (C) 2008 MontaVista Software, Inc., <source@mvista.com> + * + * 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. + */ + +#ifndef CODEC_STUBS_H +#define CODEC_STUBS_H + +extern struct snd_soc_dai dit_stub_dai; + +#endif /* CODEC_STUBS_H */ diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 87f606c7682..c550750c79c 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -336,15 +336,17 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, master_runtime->sample_bits, master_runtime->rate); - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - master_runtime->sample_bits, - master_runtime->sample_bits); + if (master_runtime->rate != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + if (master_runtime->sample_bits != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); ssm2602->slave_substream = substream; } else @@ -372,6 +374,7 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; + /* deactivate */ if (!codec->active) ssm2602_write(codec, SSM2602_ACTIVE, 0); @@ -497,11 +500,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, return 0; } -#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ - SNDRV_PCM_RATE_96000) +#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c new file mode 100644 index 00000000000..8ad4b7b3e3b --- /dev/null +++ b/sound/soc/codecs/stac9766.c @@ -0,0 +1,463 @@ +/* + * stac9766.c -- ALSA SoC STAC9766 codec support + * + * Copyright 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Features:- + * + * o Support for AC97 Codec, S/PDIF + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/soc-of-simple.h> + +#include "stac9766.h" + +#define STAC9766_VERSION "0.10" + +/* + * STAC9766 register cache + */ +static const u16 stac9766_reg[] = { + 0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* e */ + 0x8808, 0x8808, 0x8808, 0x8808, /* 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 1e */ + 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */ + 0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */ + 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */ + 0x0000, 0x2000, 0x0000, 0x0100, /* 3e */ + 0x0000, 0x0000, 0x0080, 0x0000, /* 46 */ + 0x0000, 0x0000, 0x0003, 0xffff, /* 4e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */ + 0x4000, 0x0000, 0x0000, 0x0000, /* 5e */ + 0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */ + 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */ +}; + +static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; +static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; +static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; +static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; +static const char *stac9766_record_all_mux[] = {"All analog", + "Analog plus DAC"}; +static const char *stac9766_boost1[] = {"0dB", "10dB"}; +static const char *stac9766_boost2[] = {"0dB", "20dB"}; +static const char *stac9766_stereo_mic[] = {"Off", "On"}; + +static const struct soc_enum stac9766_record_enum = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux); +static const struct soc_enum stac9766_mono_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux); +static const struct soc_enum stac9766_mic_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux); +static const struct soc_enum stac9766_SPDIF_enum = + SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux); +static const struct soc_enum stac9766_popbypass_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux); +static const struct soc_enum stac9766_record_all_enum = + SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, + stac9766_record_all_mux); +static const struct soc_enum stac9766_boost1_enum = + SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */ +static const struct soc_enum stac9766_boost2_enum = + SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */ +static const struct soc_enum stac9766_stereo_mic_enum = + SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic); + +static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0); +static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250); +static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0); +static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200); + +static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { + SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1), + SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1, + master_tlv), + SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1), + SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1, + master_tlv), + SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1), + + SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), + SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1), + + + SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv), + SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1), + SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv), + SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), + + SOC_ENUM("Mic Boost1", stac9766_boost1_enum), + SOC_ENUM("Mic Boost2", stac9766_boost2_enum), + SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv), + SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum), + + SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), + SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1), + SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), + SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), + + SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1), + SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), + SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1), + SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), + + SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum), + SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum), + SOC_ENUM("Record All Mux", stac9766_record_all_enum), + SOC_ENUM("Record Mux", stac9766_record_enum), + SOC_ENUM("Mono Mux", stac9766_mono_enum), + SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum), +}; + +static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + if (reg > AC97_STAC_PAGE0) { + stac9766_ac97_write(codec, AC97_INT_PAGING, 0); + soc_ac97_ops.write(codec->ac97, reg, val); + stac9766_ac97_write(codec, AC97_INT_PAGING, 1); + return 0; + } + if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + return -EIO; + + soc_ac97_ops.write(codec->ac97, reg, val); + cache[reg / 2] = val; + return 0; +} + +static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 val = 0, *cache = codec->reg_cache; + + if (reg > AC97_STAC_PAGE0) { + stac9766_ac97_write(codec, AC97_INT_PAGING, 0); + val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0); + stac9766_ac97_write(codec, AC97_INT_PAGING, 1); + return val; + } + if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + return -EIO; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || + reg == AC97_VENDOR_ID2) { + + val = soc_ac97_ops.read(codec->ac97, reg); + return val; + } + return cache[reg / 2]; +} + +static int ac97_analog_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg, vra; + + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + + vra |= 0x1; /* enable variable rate audio */ + + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return stac9766_ac97_write(codec, reg, runtime->rate); +} + +static int ac97_digital_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg, vra; + + stac9766_ac97_write(codec, AC97_SPDIF, 0x2002); + + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + vra |= 0x5; /* Enable VRA and SPDIF out */ + + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + + reg = AC97_PCM_FRONT_DAC_RATE; + + return stac9766_ac97_write(codec, reg, runtime->rate); +} + +static int ac97_digital_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned short vra; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + vra &= !0x04; + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + break; + } + return 0; +} + +static int stac9766_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + case SND_SOC_BIAS_PREPARE: /* partial On */ + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* disable everything including AC link */ + stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +static int stac9766_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (soc_ac97_ops.warm_reset) + soc_ac97_ops.warm_reset(codec->ac97); + if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) + return -EIO; + return 0; +} + +static int stac9766_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int stac9766_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 id, reset; + + reset = 0; + /* give the codec an AC97 warm reset to start the link */ +reset: + if (reset > 5) { + printk(KERN_ERR "stac9766 failed to resume"); + return -EIO; + } + codec->ac97->bus->ops->warm_reset(codec->ac97); + id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2); + if (id != 0x4c13) { + stac9766_reset(codec, 0); + reset++; + goto reset; + } + stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + stac9766_set_bias_level(codec, SND_SOC_BIAS_ON); + + return 0; +} + +static struct snd_soc_dai_ops stac9766_dai_ops_analog = { + .prepare = ac97_analog_prepare, +}; + +static struct snd_soc_dai_ops stac9766_dai_ops_digital = { + .prepare = ac97_digital_prepare, + .trigger = ac97_digital_trigger, +}; + +struct snd_soc_dai stac9766_dai[] = { +{ + .name = "stac9766 analog", + .id = 0, + .ac97_control = 1, + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_analog, +}, +{ + .name = "stac9766 IEC958", + .id = 1, + .ac97_control = 1, + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 IEC958", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_digital, +} +}; +EXPORT_SYMBOL_GPL(stac9766_dai); + +static int stac9766_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION); + + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->card->codec == NULL) + return -ENOMEM; + codec = socdev->card->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(stac9766_reg); + codec->reg_cache_step = 2; + + codec->name = "STAC9766"; + codec->owner = THIS_MODULE; + codec->dai = stac9766_dai; + codec->num_dai = ARRAY_SIZE(stac9766_dai); + codec->write = stac9766_ac97_write; + codec->read = stac9766_ac97_read; + codec->set_bias_level = stac9766_set_bias_level; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) + goto codec_err; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + stac9766_reset(codec, 0); + ret = stac9766_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); + goto reset_err; + } + + stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_add_controls(codec, stac9766_snd_ac97_controls, + ARRAY_SIZE(stac9766_snd_ac97_controls)); + + ret = snd_soc_init_card(socdev); + if (ret < 0) + goto reset_err; + return 0; + +reset_err: + snd_soc_free_pcms(socdev); +pcm_err: + snd_soc_free_ac97_codec(codec); +codec_err: + kfree(codec->private_data); +cache_err: + kfree(socdev->card->codec); + socdev->card->codec = NULL; + return ret; +} + +static int stac9766_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec == NULL) + return 0; + + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_stac9766 = { + .probe = stac9766_codec_probe, + .remove = stac9766_codec_remove, + .suspend = stac9766_codec_suspend, + .resume = stac9766_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766); + +MODULE_DESCRIPTION("ASoC stac9766 driver"); +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stac9766.h b/sound/soc/codecs/stac9766.h new file mode 100644 index 00000000000..65642eb8393 --- /dev/null +++ b/sound/soc/codecs/stac9766.h @@ -0,0 +1,21 @@ +/* + * stac9766.h -- STAC9766 Soc Audio driver + */ + +#ifndef _STAC9766_H +#define _STAC9766_H + +#define AC97_STAC_PAGE0 0x1000 +#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A) +#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E) +#define AC97_STAC_STEREO_MIC 0x78 + +/* STAC9766 DAI ID's */ +#define STAC9766_DAI_AC97_ANALOG 0 +#define STAC9766_DAI_AC97_DIGITAL 1 + +extern struct snd_soc_dai stac9766_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_stac9766; + + +#endif diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index c3f4afb5d01..0b8dcb5cd72 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -86,7 +86,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, */ if ((reg < 0 || reg > 9) && (reg != 15)) { - printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg); + printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg); return -1; } @@ -98,7 +98,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, if (codec->hw_write(codec->control_data, data, 2) == 2) return 0; - printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__, + printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__, value, reg); return -EIO; @@ -273,14 +273,14 @@ static const unsigned short sr_valid_mask[] = { * Every divisor is a factor of 11*12 */ #define SR_MULT (11*12) -#define A(x) (x) ? (SR_MULT/x) : 0 +#define A(x) (SR_MULT/x) static const unsigned char sr_adc_mult_table[] = { - A(2), A(2), A(12), A(12), A(0), A(0), A(3), A(1), - A(2), A(2), A(11), A(11), A(0), A(0), A(0), A(1) + A(2), A(2), A(12), A(12), 0, 0, A(3), A(1), + A(2), A(2), A(11), A(11), 0, 0, 0, A(1) }; static const unsigned char sr_dac_mult_table[] = { - A(2), A(12), A(2), A(12), A(0), A(0), A(3), A(1), - A(2), A(11), A(2), A(11), A(0), A(0), A(0), A(1) + A(2), A(12), A(2), A(12), 0, 0, A(3), A(1), + A(2), A(11), A(2), A(11), 0, 0, 0, A(1) }; static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc, @@ -523,6 +523,8 @@ static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_I2S: iface_reg |= TLV320AIC23_FOR_I2S; break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_LRP_ON; case SND_SOC_DAIFMT_DSP_B: iface_reg |= TLV320AIC23_FOR_DSP; break; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index df7c8c281d2..4dbb853eef5 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -115,6 +115,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* REG_VIBRA_PWM_SET (0x47) */ 0x00, /* REG_ANAMIC_GAIN (0x48) */ 0x00, /* REG_MISC_SET_2 (0x49) */ + 0x00, /* REG_SW_SHADOW (0x4A) - Shadow, non HW register */ }; /* codec private data */ @@ -125,6 +126,17 @@ struct twl4030_priv { struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; + + unsigned int configured; + unsigned int rate; + unsigned int sample_bits; + unsigned int channels; + + unsigned int sysclk; + + /* Headset output state handling */ + unsigned int hsl_enabled; + unsigned int hsr_enabled; }; /* @@ -161,7 +173,11 @@ static int twl4030_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { twl4030_write_reg_cache(codec, reg, value); - return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); + if (likely(reg < TWL4030_REG_SW_SHADOW)) + return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, + reg); + else + return 0; } static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) @@ -188,6 +204,7 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) static void twl4030_init_chip(struct snd_soc_codec *codec) { + u8 *cache = codec->reg_cache; int i; /* clear CODECPDZ prior to setting register defaults */ @@ -195,7 +212,7 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) /* set all audio section registers to reasonable defaults */ for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) - twl4030_write(codec, i, twl4030_reg[i]); + twl4030_write(codec, i, cache[i]); } @@ -232,7 +249,7 @@ static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute) TWL4030_REG_PRECKL_CTL); reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL); twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PRECKL_GAIN), + reg_val & (~TWL4030_PRECKR_GAIN), TWL4030_REG_PRECKR_CTL); /* Disable PLL */ @@ -316,104 +333,60 @@ static void twl4030_power_down(struct snd_soc_codec *codec) } /* Earpiece */ -static const char *twl4030_earpiece_texts[] = - {"Off", "DACL1", "DACL2", "DACR1"}; - -static const unsigned int twl4030_earpiece_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_earpiece_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_EAR_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_earpiece_texts), - twl4030_earpiece_texts, - twl4030_earpiece_values); - -static const struct snd_kcontrol_new twl4030_dapm_earpiece_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_earpiece_enum); +static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0), +}; /* PreDrive Left */ -static const char *twl4030_predrivel_texts[] = - {"Off", "DACL1", "DACL2", "DACR2"}; - -static const unsigned int twl4030_predrivel_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_predrivel_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDL_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_predrivel_texts), - twl4030_predrivel_texts, - twl4030_predrivel_values); - -static const struct snd_kcontrol_new twl4030_dapm_predrivel_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_predrivel_enum); +static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0), +}; /* PreDrive Right */ -static const char *twl4030_predriver_texts[] = - {"Off", "DACR1", "DACR2", "DACL2"}; - -static const unsigned int twl4030_predriver_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_predriver_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDR_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_predriver_texts), - twl4030_predriver_texts, - twl4030_predriver_values); - -static const struct snd_kcontrol_new twl4030_dapm_predriver_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_predriver_enum); +static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0), +}; /* Headset Left */ -static const char *twl4030_hsol_texts[] = - {"Off", "DACL1", "DACL2"}; - -static const struct soc_enum twl4030_hsol_enum = - SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 1, - ARRAY_SIZE(twl4030_hsol_texts), - twl4030_hsol_texts); - -static const struct snd_kcontrol_new twl4030_dapm_hsol_control = -SOC_DAPM_ENUM("Route", twl4030_hsol_enum); +static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0), +}; /* Headset Right */ -static const char *twl4030_hsor_texts[] = - {"Off", "DACR1", "DACR2"}; - -static const struct soc_enum twl4030_hsor_enum = - SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 4, - ARRAY_SIZE(twl4030_hsor_texts), - twl4030_hsor_texts); - -static const struct snd_kcontrol_new twl4030_dapm_hsor_control = -SOC_DAPM_ENUM("Route", twl4030_hsor_enum); +static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0), +}; /* Carkit Left */ -static const char *twl4030_carkitl_texts[] = - {"Off", "DACL1", "DACL2"}; - -static const struct soc_enum twl4030_carkitl_enum = - SOC_ENUM_SINGLE(TWL4030_REG_PRECKL_CTL, 1, - ARRAY_SIZE(twl4030_carkitl_texts), - twl4030_carkitl_texts); - -static const struct snd_kcontrol_new twl4030_dapm_carkitl_control = -SOC_DAPM_ENUM("Route", twl4030_carkitl_enum); +static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0), +}; /* Carkit Right */ -static const char *twl4030_carkitr_texts[] = - {"Off", "DACR1", "DACR2"}; - -static const struct soc_enum twl4030_carkitr_enum = - SOC_ENUM_SINGLE(TWL4030_REG_PRECKR_CTL, 1, - ARRAY_SIZE(twl4030_carkitr_texts), - twl4030_carkitr_texts); - -static const struct snd_kcontrol_new twl4030_dapm_carkitr_control = -SOC_DAPM_ENUM("Route", twl4030_carkitr_enum); +static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0), +}; /* Handsfree Left */ static const char *twl4030_handsfreel_texts[] = - {"Voice", "DACL1", "DACL2", "DACR2"}; + {"Voice", "AudioL1", "AudioL2", "AudioR2"}; static const struct soc_enum twl4030_handsfreel_enum = SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0, @@ -423,9 +396,13 @@ static const struct soc_enum twl4030_handsfreel_enum = static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control = SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum); +/* Handsfree Left virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 0, 1, 0); + /* Handsfree Right */ static const char *twl4030_handsfreer_texts[] = - {"Voice", "DACR1", "DACR2", "DACL2"}; + {"Voice", "AudioR1", "AudioR2", "AudioL2"}; static const struct soc_enum twl4030_handsfreer_enum = SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0, @@ -435,37 +412,48 @@ static const struct soc_enum twl4030_handsfreer_enum = static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); -/* Left analog microphone selection */ -static const char *twl4030_analoglmic_texts[] = - {"Off", "Main mic", "Headset mic", "AUXL", "Carkit mic"}; +/* Handsfree Right virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 1, 1, 0); -static const unsigned int twl4030_analoglmic_values[] = - {0x0, 0x1, 0x2, 0x4, 0x8}; +/* Vibra */ +/* Vibra audio path selection */ +static const char *twl4030_vibra_texts[] = + {"AudioL1", "AudioR1", "AudioL2", "AudioR2"}; -static const struct soc_enum twl4030_analoglmic_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICL, 0, 0xf, - ARRAY_SIZE(twl4030_analoglmic_texts), - twl4030_analoglmic_texts, - twl4030_analoglmic_values); +static const struct soc_enum twl4030_vibra_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 2, + ARRAY_SIZE(twl4030_vibra_texts), + twl4030_vibra_texts); -static const struct snd_kcontrol_new twl4030_dapm_analoglmic_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_analoglmic_enum); +static const struct snd_kcontrol_new twl4030_dapm_vibra_control = +SOC_DAPM_ENUM("Route", twl4030_vibra_enum); -/* Right analog microphone selection */ -static const char *twl4030_analogrmic_texts[] = - {"Off", "Sub mic", "AUXR"}; +/* Vibra path selection: local vibrator (PWM) or audio driven */ +static const char *twl4030_vibrapath_texts[] = + {"Local vibrator", "Audio"}; -static const unsigned int twl4030_analogrmic_values[] = - {0x0, 0x1, 0x4}; +static const struct soc_enum twl4030_vibrapath_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 4, + ARRAY_SIZE(twl4030_vibrapath_texts), + twl4030_vibrapath_texts); -static const struct soc_enum twl4030_analogrmic_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICR, 0, 0x5, - ARRAY_SIZE(twl4030_analogrmic_texts), - twl4030_analogrmic_texts, - twl4030_analogrmic_values); +static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control = +SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum); -static const struct snd_kcontrol_new twl4030_dapm_analogrmic_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_analogrmic_enum); +/* Left analog microphone selection */ +static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = { + SOC_DAPM_SINGLE("Main mic", TWL4030_REG_ANAMICL, 0, 1, 0), + SOC_DAPM_SINGLE("Headset mic", TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("AUXL", TWL4030_REG_ANAMICL, 2, 1, 0), + SOC_DAPM_SINGLE("Carkit mic", TWL4030_REG_ANAMICL, 3, 1, 0), +}; + +/* Right analog microphone selection */ +static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = { + SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0), + SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 2, 1, 0), +}; /* TX1 L/R Analog/Digital microphone selection */ static const char *twl4030_micpathtx1_texts[] = @@ -507,6 +495,10 @@ static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control = static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control = SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0); +/* Analog bypass for Voice */ +static const struct snd_kcontrol_new twl4030_dapm_abypassv_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0); + /* Digital bypass gain, 0 mutes the bypass */ static const unsigned int twl4030_dapm_dbypass_tlv[] = { TLV_DB_RANGE_HEAD(2), @@ -526,6 +518,18 @@ static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control = TWL4030_REG_ATX2ARXPGA, 0, 7, 0, twl4030_dapm_dbypass_tlv); +/* + * Voice Sidetone GAIN volume control: + * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB) + */ +static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1); + +/* Digital bypass voice: sidetone (VUL -> VDL)*/ +static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_VSTPGA, 0, 0x29, 0, + twl4030_dapm_dbypassv_tlv); + static int micpath_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -556,63 +560,143 @@ static int micpath_event(struct snd_soc_dapm_widget *w, return 0; } -static int handsfree_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp) { - struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value; unsigned char hs_ctl; - hs_ctl = twl4030_read_reg_cache(w->codec, e->reg); + hs_ctl = twl4030_read_reg_cache(codec, reg); - if (hs_ctl & TWL4030_HF_CTL_REF_EN) { + if (ramp) { + /* HF ramp-up */ + hs_ctl |= TWL4030_HF_CTL_REF_EN; + twl4030_write(codec, reg, hs_ctl); + udelay(10); hs_ctl |= TWL4030_HF_CTL_RAMP_EN; - twl4030_write(w->codec, e->reg, hs_ctl); + twl4030_write(codec, reg, hs_ctl); + udelay(40); hs_ctl |= TWL4030_HF_CTL_LOOP_EN; - twl4030_write(w->codec, e->reg, hs_ctl); hs_ctl |= TWL4030_HF_CTL_HB_EN; - twl4030_write(w->codec, e->reg, hs_ctl); + twl4030_write(codec, reg, hs_ctl); } else { - hs_ctl &= ~(TWL4030_HF_CTL_RAMP_EN | TWL4030_HF_CTL_LOOP_EN - | TWL4030_HF_CTL_HB_EN); - twl4030_write(w->codec, e->reg, hs_ctl); + /* HF ramp-down */ + hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN; + hs_ctl &= ~TWL4030_HF_CTL_HB_EN; + twl4030_write(codec, reg, hs_ctl); + hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN; + twl4030_write(codec, reg, hs_ctl); + udelay(40); + hs_ctl &= ~TWL4030_HF_CTL_REF_EN; + twl4030_write(codec, reg, hs_ctl); } +} +static int handsfreelpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 0); + break; + } return 0; } -static int headsetl_event(struct snd_soc_dapm_widget *w, +static int handsfreerpga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 0); + break; + } + return 0; +} + +static void headset_ramp(struct snd_soc_codec *codec, int ramp) +{ unsigned char hs_gain, hs_pop; + struct twl4030_priv *twl4030 = codec->private_data; + /* Base values for ramp delay calculation: 2^19 - 2^26 */ + unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, + 8388608, 16777216, 33554432, 67108864}; - /* Save the current volume */ - hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET); - hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET); + hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET); + hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); - switch (event) { - case SND_SOC_DAPM_POST_PMU: - /* Do the anti-pop/bias ramp enable according to the TRM */ + if (ramp) { + /* Headset ramp-up according to the TRM */ hs_pop |= TWL4030_VMID_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); - /* Is this needed? Can we just use whatever gain here? */ - twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, - (hs_gain & (~0x0f)) | 0x0a); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); hs_pop |= TWL4030_RAMP_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); - - /* Restore the original volume */ - twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain); - break; - case SND_SOC_DAPM_POST_PMD: - /* Do the anti-pop/bias ramp disable according to the TRM */ + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } else { + /* Headset ramp-down _not_ according to + * the TRM, but in a way that it is working */ hs_pop &= ~TWL4030_RAMP_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Wait ramp delay time + 1, so the VMID can settle */ + mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] / + twl4030->sysclk) + 1); /* Bypass the reg_cache to mute the headset */ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain & (~0x0f), TWL4030_REG_HS_GAIN_SET); + hs_pop &= ~TWL4030_VMID_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } +} + +static int headsetlpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct twl4030_priv *twl4030 = w->codec->private_data; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsr_enabled) + headset_ramp(w->codec, 1); + + twl4030->hsl_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsr_enabled) + headset_ramp(w->codec, 0); + + twl4030->hsl_enabled = 0; + break; + } + return 0; +} + +static int headsetrpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct twl4030_priv *twl4030 = w->codec->private_data; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsl_enabled) + headset_ramp(w->codec, 1); + + twl4030->hsr_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsl_enabled) + headset_ramp(w->codec, 0); + + twl4030->hsr_enabled = 0; break; } return 0; @@ -624,7 +708,7 @@ static int bypass_event(struct snd_soc_dapm_widget *w, struct soc_mixer_control *m = (struct soc_mixer_control *)w->kcontrols->private_value; struct twl4030_priv *twl4030 = w->codec->private_data; - unsigned char reg; + unsigned char reg, misc; reg = twl4030_read_reg_cache(w->codec, m->reg); @@ -636,14 +720,34 @@ static int bypass_event(struct snd_soc_dapm_widget *w, else twl4030->bypass_state &= ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL)); + } else if (m->reg == TWL4030_REG_VDL_APGA_CTL) { + /* Analog voice bypass */ + if (reg & (1 << m->shift)) + twl4030->bypass_state |= (1 << 4); + else + twl4030->bypass_state &= ~(1 << 4); + } else if (m->reg == TWL4030_REG_VSTPGA) { + /* Voice digital bypass */ + if (reg) + twl4030->bypass_state |= (1 << 5); + else + twl4030->bypass_state &= ~(1 << 5); } else { /* Digital bypass */ if (reg & (0x7 << m->shift)) - twl4030->bypass_state |= (1 << (m->shift ? 5 : 4)); + twl4030->bypass_state |= (1 << (m->shift ? 7 : 6)); else - twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4)); + twl4030->bypass_state &= ~(1 << (m->shift ? 7 : 6)); } + /* Enable master analog loopback mode if any analog switch is enabled*/ + misc = twl4030_read_reg_cache(w->codec, TWL4030_REG_MISC_SET_1); + if (twl4030->bypass_state & 0x1F) + misc |= TWL4030_FMLOOP_EN; + else + misc &= ~TWL4030_FMLOOP_EN; + twl4030_write(w->codec, TWL4030_REG_MISC_SET_1, misc); + if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) { if (twl4030->bypass_state) twl4030_codec_mute(w->codec, 0); @@ -810,6 +914,48 @@ static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, return err; } +/* Codec operation modes */ +static const char *twl4030_op_modes_texts[] = { + "Option 2 (voice/audio)", "Option 1 (audio)" +}; + +static const struct soc_enum twl4030_op_modes_enum = + SOC_ENUM_SINGLE(TWL4030_REG_CODEC_MODE, 0, + ARRAY_SIZE(twl4030_op_modes_texts), + twl4030_op_modes_texts); + +int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl4030_priv *twl4030 = codec->private_data; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned short val; + unsigned short mask, bitmask; + + if (twl4030->configured) { + printk(KERN_ERR "twl4030 operation mode cannot be " + "changed on-the-fly\n"); + return -EBUSY; + } + + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) + ; + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + val = ucontrol->value.enumerated.item[0] << e->shift_l; + mask = (bitmask - 1) << e->shift_l; + if (e->shift_l != e->shift_r) { + if (ucontrol->value.enumerated.item[1] > e->max - 1) + return -EINVAL; + val |= ucontrol->value.enumerated.item[1] << e->shift_r; + mask |= (bitmask - 1) << e->shift_r; + } + + return snd_soc_update_bits(codec, e->reg, mask, val); +} + /* * FGAIN volume control: * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) @@ -824,6 +970,12 @@ static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1); static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0); /* + * Voice Downlink GAIN volume control: + * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB) + */ +static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1); + +/* * Analog playback gain * -24 dB to 12 dB in 2 dB steps */ @@ -864,7 +1016,32 @@ static const struct soc_enum twl4030_rampdelay_enum = ARRAY_SIZE(twl4030_rampdelay_texts), twl4030_rampdelay_texts); +/* Vibra H-bridge direction mode */ +static const char *twl4030_vibradirmode_texts[] = { + "Vibra H-bridge direction", "Audio data MSB", +}; + +static const struct soc_enum twl4030_vibradirmode_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 5, + ARRAY_SIZE(twl4030_vibradirmode_texts), + twl4030_vibradirmode_texts); + +/* Vibra H-bridge direction */ +static const char *twl4030_vibradir_texts[] = { + "Positive polarity", "Negative polarity", +}; + +static const struct soc_enum twl4030_vibradir_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 1, + ARRAY_SIZE(twl4030_vibradir_texts), + twl4030_vibradir_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { + /* Codec operation mode control */ + SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum, + snd_soc_get_enum_double, + snd_soc_put_twl4030_opmode_enum_double), + /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, @@ -893,6 +1070,16 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, 1, 1, 0), + /* Common voice downlink gain controls */ + SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume", + TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv), + + SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume", + TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv), + + SOC_SINGLE("DAC Voice Analog Downlink Switch", + TWL4030_REG_VDL_APGA_CTL, 1, 1, 0), + /* Separate output gain controls */ SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume", TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL, @@ -920,6 +1107,9 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { 0, 3, 5, 0, input_gain_tlv), SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), + + SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), + SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum), }; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { @@ -947,26 +1137,19 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("CARKITR"), SND_SOC_DAPM_OUTPUT("HFL"), SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("VIBRA"), /* DACs */ - SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback", + SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback", + SND_SOC_DAPM_DAC("DAC Left1", "Left Front HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback", + SND_SOC_DAPM_DAC("DAC Right2", "Right Rear HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", + SND_SOC_DAPM_DAC("DAC Left2", "Left Rear HiFi Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback", SND_SOC_NOPM, 0, 0), - - /* Analog PGAs */ - SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXL1_APGA", TWL4030_REG_ARXL1_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXR2_APGA", TWL4030_REG_ARXR2_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL, - 0, 0, NULL, 0), /* Analog bypasses */ SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0, @@ -981,6 +1164,9 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0, &twl4030_dapm_abypassl2_control, bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Voice Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassv_control, + bypass_event, SND_SOC_DAPM_POST_REG), /* Digital bypasses */ SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0, @@ -989,43 +1175,88 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SWITCH_E("Right Digital Loopback", SND_SOC_NOPM, 0, 0, &twl4030_dapm_dbypassr_control, bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Voice Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassv_control, bypass_event, + SND_SOC_DAPM_POST_REG), - SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 1, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 2, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 3, 0, NULL, 0), - - /* Output MUX controls */ + /* Digital mixers, power control for the physical DACs */ + SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer", + TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0), + + /* Analog mixers, power control for the physical PGAs */ + SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", + TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", + TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", + TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", + TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer", + TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0), + + /* Output MIXER controls */ /* Earpiece */ - SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_earpiece_control), + SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_earpiece_controls[0], + ARRAY_SIZE(twl4030_dapm_earpiece_controls)), /* PreDrivL/R */ - SND_SOC_DAPM_VALUE_MUX("PredriveL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_predrivel_control), - SND_SOC_DAPM_VALUE_MUX("PredriveR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_predriver_control), + SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predrivel_controls[0], + ARRAY_SIZE(twl4030_dapm_predrivel_controls)), + SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predriver_controls[0], + ARRAY_SIZE(twl4030_dapm_predriver_controls)), /* HeadsetL/R */ - SND_SOC_DAPM_MUX_E("HeadsetL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_hsol_control, headsetl_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_hsor_control), + SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsol_controls[0], + ARRAY_SIZE(twl4030_dapm_hsol_controls)), + SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetlpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsor_controls[0], + ARRAY_SIZE(twl4030_dapm_hsor_controls)), + SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetrpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* CarkitL/R */ - SND_SOC_DAPM_MUX("CarkitL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_carkitl_control), - SND_SOC_DAPM_MUX("CarkitR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_carkitr_control), + SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitl_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitl_controls)), + SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitr_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitr_controls)), + + /* Output MUX controls */ /* HandsfreeL/R */ - SND_SOC_DAPM_MUX_E("HandsfreeL Mux", TWL4030_REG_HFL_CTL, 5, 0, - &twl4030_dapm_handsfreel_control, handsfree_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_MUX_E("HandsfreeR Mux", TWL4030_REG_HFR_CTL, 5, 0, - &twl4030_dapm_handsfreer_control, handsfree_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreel_control), + SND_SOC_DAPM_SWITCH("HandsfreeL Switch", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreelmute_control), + SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreelpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0, + &twl4030_dapm_handsfreer_control), + SND_SOC_DAPM_SWITCH("HandsfreeR Switch", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreermute_control), + SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreerpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* Vibra */ + SND_SOC_DAPM_MUX("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0, + &twl4030_dapm_vibra_control), + SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_vibrapath_control), /* Introducing four virtual ADC, since TWL4030 have four channel for capture */ @@ -1050,11 +1281,15 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD| SND_SOC_DAPM_POST_REG), - /* Analog input muxes with switch for the capture amplifiers */ - SND_SOC_DAPM_VALUE_MUX("Analog Left Capture Route", - TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_control), - SND_SOC_DAPM_VALUE_MUX("Analog Right Capture Route", - TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_control), + /* Analog input mixers for the capture amplifiers */ + SND_SOC_DAPM_MIXER("Analog Left Capture Route", + TWL4030_REG_ANAMICL, 4, 0, + &twl4030_dapm_analoglmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analoglmic_controls)), + SND_SOC_DAPM_MIXER("Analog Right Capture Route", + TWL4030_REG_ANAMICR, 4, 0, + &twl4030_dapm_analogrmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analogrmic_controls)), SND_SOC_DAPM_PGA("ADC Physical Left", TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0), @@ -1073,62 +1308,86 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { }; static const struct snd_soc_dapm_route intercon[] = { - {"Analog L1 Playback Mixer", NULL, "DAC Left1"}, - {"Analog R1 Playback Mixer", NULL, "DAC Right1"}, - {"Analog L2 Playback Mixer", NULL, "DAC Left2"}, - {"Analog R2 Playback Mixer", NULL, "DAC Right2"}, - - {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"}, - {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"}, - {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"}, - {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"}, + {"Digital L1 Playback Mixer", NULL, "DAC Left1"}, + {"Digital R1 Playback Mixer", NULL, "DAC Right1"}, + {"Digital L2 Playback Mixer", NULL, "DAC Left2"}, + {"Digital R2 Playback Mixer", NULL, "DAC Right2"}, + {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, + + {"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"}, + {"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"}, + {"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"}, + {"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"}, + {"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"}, /* Internal playback routings */ /* Earpiece */ - {"Earpiece Mux", "DACL1", "ARXL1_APGA"}, - {"Earpiece Mux", "DACL2", "ARXL2_APGA"}, - {"Earpiece Mux", "DACR1", "ARXR1_APGA"}, + {"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"}, /* PreDrivL */ - {"PredriveL Mux", "DACL1", "ARXL1_APGA"}, - {"PredriveL Mux", "DACL2", "ARXL2_APGA"}, - {"PredriveL Mux", "DACR2", "ARXR2_APGA"}, + {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"}, /* PreDrivR */ - {"PredriveR Mux", "DACR1", "ARXR1_APGA"}, - {"PredriveR Mux", "DACR2", "ARXR2_APGA"}, - {"PredriveR Mux", "DACL2", "ARXL2_APGA"}, + {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"}, /* HeadsetL */ - {"HeadsetL Mux", "DACL1", "ARXL1_APGA"}, - {"HeadsetL Mux", "DACL2", "ARXL2_APGA"}, + {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"HeadsetL PGA", NULL, "HeadsetL Mixer"}, /* HeadsetR */ - {"HeadsetR Mux", "DACR1", "ARXR1_APGA"}, - {"HeadsetR Mux", "DACR2", "ARXR2_APGA"}, + {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"HeadsetR PGA", NULL, "HeadsetR Mixer"}, /* CarkitL */ - {"CarkitL Mux", "DACL1", "ARXL1_APGA"}, - {"CarkitL Mux", "DACL2", "ARXL2_APGA"}, + {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, /* CarkitR */ - {"CarkitR Mux", "DACR1", "ARXR1_APGA"}, - {"CarkitR Mux", "DACR2", "ARXR2_APGA"}, + {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, /* HandsfreeL */ - {"HandsfreeL Mux", "DACL1", "ARXL1_APGA"}, - {"HandsfreeL Mux", "DACL2", "ARXL2_APGA"}, - {"HandsfreeL Mux", "DACR2", "ARXR2_APGA"}, + {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, + {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeL Switch", "Switch", "HandsfreeL Mux"}, + {"HandsfreeL PGA", NULL, "HandsfreeL Switch"}, /* HandsfreeR */ - {"HandsfreeR Mux", "DACR1", "ARXR1_APGA"}, - {"HandsfreeR Mux", "DACR2", "ARXR2_APGA"}, - {"HandsfreeR Mux", "DACL2", "ARXL2_APGA"}, + {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, + {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeR Switch", "Switch", "HandsfreeR Mux"}, + {"HandsfreeR PGA", NULL, "HandsfreeR Switch"}, + /* Vibra */ + {"Vibra Mux", "AudioL1", "DAC Left1"}, + {"Vibra Mux", "AudioR1", "DAC Right1"}, + {"Vibra Mux", "AudioL2", "DAC Left2"}, + {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ - {"OUTL", NULL, "ARXL2_APGA"}, - {"OUTR", NULL, "ARXR2_APGA"}, - {"EARPIECE", NULL, "Earpiece Mux"}, - {"PREDRIVEL", NULL, "PredriveL Mux"}, - {"PREDRIVER", NULL, "PredriveR Mux"}, - {"HSOL", NULL, "HeadsetL Mux"}, - {"HSOR", NULL, "HeadsetR Mux"}, - {"CARKITL", NULL, "CarkitL Mux"}, - {"CARKITR", NULL, "CarkitR Mux"}, - {"HFL", NULL, "HandsfreeL Mux"}, - {"HFR", NULL, "HandsfreeR Mux"}, + {"OUTL", NULL, "Analog L2 Playback Mixer"}, + {"OUTR", NULL, "Analog R2 Playback Mixer"}, + {"EARPIECE", NULL, "Earpiece Mixer"}, + {"PREDRIVEL", NULL, "PredriveL Mixer"}, + {"PREDRIVER", NULL, "PredriveR Mixer"}, + {"HSOL", NULL, "HeadsetL PGA"}, + {"HSOR", NULL, "HeadsetR PGA"}, + {"CARKITL", NULL, "CarkitL Mixer"}, + {"CARKITR", NULL, "CarkitR Mixer"}, + {"HFL", NULL, "HandsfreeL PGA"}, + {"HFR", NULL, "HandsfreeR PGA"}, + {"Vibra Route", "Audio", "Vibra Mux"}, + {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ {"Analog Left Capture Route", "Main mic", "MAINMIC"}, @@ -1168,18 +1427,22 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"}, {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"}, {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"}, + {"Voice Analog Loopback", "Switch", "Analog Left Capture Route"}, {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"}, {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"}, {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"}, {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"}, + {"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"}, /* Digital bypass routes */ {"Right Digital Loopback", "Volume", "TX1 Capture Route"}, {"Left Digital Loopback", "Volume", "TX1 Capture Route"}, + {"Voice Digital Loopback", "Volume", "TX2 Capture Route"}, - {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"}, - {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"}, + {"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"}, + {"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"}, + {"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"}, }; @@ -1226,6 +1489,58 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec, return 0; } +static void twl4030_constraints(struct twl4030_priv *twl4030, + struct snd_pcm_substream *mst_substream) +{ + struct snd_pcm_substream *slv_substream; + + /* Pick the stream, which need to be constrained */ + if (mst_substream == twl4030->master_substream) + slv_substream = twl4030->slave_substream; + else if (mst_substream == twl4030->slave_substream) + slv_substream = twl4030->master_substream; + else /* This should not happen.. */ + return; + + /* Set the constraints according to the already configured stream */ + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + twl4030->rate, + twl4030->rate); + + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + twl4030->sample_bits, + twl4030->sample_bits); + + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + twl4030->channels, + twl4030->channels); +} + +/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for + * capture has to be enabled/disabled. */ +static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(codec, TWL4030_REG_OPTION, reg); +} + static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1234,26 +1549,25 @@ static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; struct twl4030_priv *twl4030 = codec->private_data; - /* If we already have a playback or capture going then constrain - * this substream to match it. - */ if (twl4030->master_substream) { - struct snd_pcm_runtime *master_runtime; - master_runtime = twl4030->master_substream->runtime; - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - master_runtime->sample_bits, - master_runtime->sample_bits); - twl4030->slave_substream = substream; - } else + /* The DAI has one configuration for playback and capture, so + * if the DAI has been already configured then constrain this + * substream to match it. */ + if (twl4030->configured) + twl4030_constraints(twl4030, twl4030->master_substream); + } else { + if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & + TWL4030_OPTION_1)) { + /* In option2 4 channel is not supported, set the + * constraint for the first stream for channels, the + * second stream will 'inherit' this cosntraint */ + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2, 2); + } twl4030->master_substream = substream; + } return 0; } @@ -1270,6 +1584,17 @@ static void twl4030_shutdown(struct snd_pcm_substream *substream, twl4030->master_substream = twl4030->slave_substream; twl4030->slave_substream = NULL; + + /* If all streams are closed, or the remaining stream has not yet + * been configured than set the DAI as not configured. */ + if (!twl4030->master_substream) + twl4030->configured = 0; + else if (!twl4030->master_substream->runtime->channels) + twl4030->configured = 0; + + /* If the closing substream had 4 channel, do the necessary cleanup */ + if (substream->runtime->channels == 4) + twl4030_tdm_enable(codec, substream->stream, 0); } static int twl4030_hw_params(struct snd_pcm_substream *substream, @@ -1282,8 +1607,24 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, struct twl4030_priv *twl4030 = codec->private_data; u8 mode, old_mode, format, old_format; - if (substream == twl4030->slave_substream) - /* Ignoring hw_params for slave substream */ + /* If the substream has 4 channel, do the necessary setup */ + if (params_channels(params) == 4) { + u8 format, mode; + + format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); + + /* Safety check: are we in the correct operating mode and + * the interface is in TDM mode? */ + if ((mode & TWL4030_OPTION_1) && + ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM)) + twl4030_tdm_enable(codec, substream->stream, 1); + else + return -EINVAL; + } + + if (twl4030->configured) + /* Ignoring hw_params for already configured DAI */ return 0; /* bit rate */ @@ -1363,6 +1704,21 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, /* set CODECPDZ afterwards */ twl4030_codec_enable(codec, 1); } + + /* Store the important parameters for the DAI configuration and set + * the DAI as configured */ + twl4030->configured = 1; + twl4030->rate = params_rate(params); + twl4030->sample_bits = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + twl4030->channels = params_channels(params); + + /* If both playback and capture streams are open, and one of them + * is setting the hw parameters right now (since we are here), set + * constraints to the other stream to match the current one. */ + if (twl4030->slave_substream) + twl4030_constraints(twl4030, substream); + return 0; } @@ -1370,17 +1726,21 @@ static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; + struct twl4030_priv *twl4030 = codec->private_data; u8 infreq; switch (freq) { case 19200000: infreq = TWL4030_APLL_INFREQ_19200KHZ; + twl4030->sysclk = 19200; break; case 26000000: infreq = TWL4030_APLL_INFREQ_26000KHZ; + twl4030->sysclk = 26000; break; case 38400000: infreq = TWL4030_APLL_INFREQ_38400KHZ; + twl4030->sysclk = 38400; break; default: printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", @@ -1424,6 +1784,9 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_I2S: format |= TWL4030_AIF_FORMAT_CODEC; break; + case SND_SOC_DAIFMT_DSP_A: + format |= TWL4030_AIF_FORMAT_TDM; + break; default: return -EINVAL; } @@ -1443,6 +1806,180 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R + * (VTXL, VTXR) for uplink has to be enabled/disabled. */ +static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(codec, TWL4030_REG_OPTION, reg); +} + +static int twl4030_voice_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u8 infreq; + u8 mode; + + /* If the system master clock is not 26MHz, the voice PCM interface is + * not avilable. + */ + infreq = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL) + & TWL4030_APLL_INFREQ; + + if (infreq != TWL4030_APLL_INFREQ_26000KHZ) { + printk(KERN_ERR "TWL4030 voice startup: " + "MCLK is not 26MHz, call set_sysclk() on init\n"); + return -EINVAL; + } + + /* If the codec mode is not option2, the voice PCM interface is not + * avilable. + */ + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) + & TWL4030_OPT_MODE; + + if (mode != TWL4030_OPTION_2) { + printk(KERN_ERR "TWL4030 voice startup: " + "the codec mode is not option2\n"); + return -EINVAL; + } + + return 0; +} + +static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 0); +} + +static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u8 old_mode, mode; + + /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 1); + + /* bit rate */ + old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) + & ~(TWL4030_CODECPDZ); + mode = old_mode; + + switch (params_rate(params)) { + case 8000: + mode &= ~(TWL4030_SEL_16K); + break; + case 16000: + mode |= TWL4030_SEL_16K; + break; + default: + printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n", + params_rate(params)); + return -EINVAL; + } + + if (mode != old_mode) { + /* change rate and set CODECPDZ */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_codec_enable(codec, 1); + } + + return 0; +} + +static int twl4030_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 infreq; + + switch (freq) { + case 26000000: + infreq = TWL4030_APLL_INFREQ_26000KHZ; + break; + default: + printk(KERN_ERR "TWL4030 voice set sysclk: unknown rate %d\n", + freq); + return -EINVAL; + } + + infreq |= TWL4030_APLL_EN; + twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq); + + return 0; +} + +static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 old_format, format; + + /* get format */ + old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF); + format = old_format; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFM: + format &= ~(TWL4030_VIF_SLAVE_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + format |= TWL4030_VIF_SLAVE_EN; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + format &= ~(TWL4030_VIF_FORMAT); + break; + case SND_SOC_DAIFMT_NB_IF: + format |= TWL4030_VIF_FORMAT; + break; + default: + return -EINVAL; + } + + if (format != old_format) { + /* change format and set CODECPDZ */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_VOICE_IF, format); + twl4030_codec_enable(codec, 1); + } + + return 0; +} + #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) @@ -1454,21 +1991,47 @@ static struct snd_soc_dai_ops twl4030_dai_ops = { .set_fmt = twl4030_set_dai_fmt, }; -struct snd_soc_dai twl4030_dai = { +static struct snd_soc_dai_ops twl4030_dai_voice_ops = { + .startup = twl4030_voice_startup, + .shutdown = twl4030_voice_shutdown, + .hw_params = twl4030_voice_hw_params, + .set_sysclk = twl4030_voice_set_dai_sysclk, + .set_fmt = twl4030_voice_set_dai_fmt, +}; + +struct snd_soc_dai twl4030_dai[] = { +{ .name = "twl4030", .playback = { - .stream_name = "Playback", + .stream_name = "HiFi Playback", .channels_min = 2, - .channels_max = 2, + .channels_max = 4, .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000, .formats = TWL4030_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 2, - .channels_max = 2, + .channels_max = 4, .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, .ops = &twl4030_dai_ops, +}, +{ + .name = "twl4030 Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &twl4030_dai_voice_ops, +}, }; EXPORT_SYMBOL_GPL(twl4030_dai); @@ -1500,6 +2063,8 @@ static int twl4030_resume(struct platform_device *pdev) static int twl4030_init(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->card->codec; + struct twl4030_setup_data *setup = socdev->codec_data; + struct twl4030_priv *twl4030 = codec->private_data; int ret = 0; printk(KERN_INFO "TWL4030 Audio Codec init \n"); @@ -1509,14 +2074,31 @@ static int twl4030_init(struct snd_soc_device *socdev) codec->read = twl4030_read_reg_cache; codec->write = twl4030_write; codec->set_bias_level = twl4030_set_bias_level; - codec->dai = &twl4030_dai; - codec->num_dai = 1; + codec->dai = twl4030_dai; + codec->num_dai = ARRAY_SIZE(twl4030_dai), codec->reg_cache_size = sizeof(twl4030_reg); codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; + /* Configuration for headset ramp delay from setup data */ + if (setup) { + unsigned char hs_pop; + + if (setup->sysclk) + twl4030->sysclk = setup->sysclk; + else + twl4030->sysclk = 26000; + + hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + hs_pop &= ~TWL4030_RAMP_DELAY; + hs_pop |= (setup->ramp_delay_value << 2); + twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } else { + twl4030->sysclk = 26000; + } + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1604,13 +2186,13 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030); static int __init twl4030_modinit(void) { - return snd_soc_register_dai(&twl4030_dai); + return snd_soc_register_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai)); } module_init(twl4030_modinit); static void __exit twl4030_exit(void) { - snd_soc_unregister_dai(&twl4030_dai); + snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai)); } module_exit(twl4030_exit); diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index cb63765db1d..fe5f395d9e4 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -92,8 +92,9 @@ #define TWL4030_REG_VIBRA_PWM_SET 0x47 #define TWL4030_REG_ANAMIC_GAIN 0x48 #define TWL4030_REG_MISC_SET_2 0x49 +#define TWL4030_REG_SW_SHADOW 0x4A -#define TWL4030_CACHEREGNUM (TWL4030_REG_MISC_SET_2 + 1) +#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1) /* Bitfield Definitions */ @@ -110,9 +111,22 @@ #define TWL4030_APLL_RATE_44100 0x90 #define TWL4030_APLL_RATE_48000 0xA0 #define TWL4030_APLL_RATE_96000 0xE0 -#define TWL4030_SEL_16K 0x04 +#define TWL4030_SEL_16K 0x08 #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01 +#define TWL4030_OPTION_1 (1 << 0) +#define TWL4030_OPTION_2 (0 << 0) + +/* TWL4030_OPTION (0x02) Fields */ + +#define TWL4030_ATXL1_EN (1 << 0) +#define TWL4030_ATXR1_EN (1 << 1) +#define TWL4030_ATXL2_VTXL_EN (1 << 2) +#define TWL4030_ATXR2_VTXR_EN (1 << 3) +#define TWL4030_ARXL1_VRX_EN (1 << 4) +#define TWL4030_ARXR1_EN (1 << 5) +#define TWL4030_ARXL2_EN (1 << 6) +#define TWL4030_ARXR2_EN (1 << 7) /* TWL4030_REG_MICBIAS_CTL (0x04) Fields */ @@ -171,6 +185,17 @@ #define TWL4030_CLK256FS_EN 0x02 #define TWL4030_AIF_EN 0x01 +/* VOICE_IF (0x0F) Fields */ + +#define TWL4030_VIF_SLAVE_EN 0x80 +#define TWL4030_VIF_DIN_EN 0x40 +#define TWL4030_VIF_DOUT_EN 0x20 +#define TWL4030_VIF_SWAP 0x10 +#define TWL4030_VIF_FORMAT 0x08 +#define TWL4030_VIF_TRI_EN 0x04 +#define TWL4030_VIF_SUB_EN 0x02 +#define TWL4030_VIF_EN 0x01 + /* EAR_CTL (0x21) */ #define TWL4030_EAR_GAIN 0x30 @@ -236,7 +261,19 @@ #define TWL4030_SMOOTH_ANAVOL_EN 0x02 #define TWL4030_DIGMIC_LR_SWAP_EN 0x01 -extern struct snd_soc_dai twl4030_dai; +/* TWL4030_REG_SW_SHADOW (0x4A) Fields */ +#define TWL4030_HFL_EN 0x01 +#define TWL4030_HFR_EN 0x02 + +#define TWL4030_DAI_HIFI 0 +#define TWL4030_DAI_VOICE 1 + +extern struct snd_soc_dai twl4030_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_twl4030; +struct twl4030_setup_data { + unsigned int ramp_delay_value; + unsigned int sysclk; +}; + #endif /* End of __TWL4030_AUDIO_H__ */ diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index ddefb8f8014..269b108e1de 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -101,7 +101,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg, pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value); if (reg >= UDA134X_REGS_NUM) { - printk(KERN_ERR "%s unkown register: reg: %d", + printk(KERN_ERR "%s unkown register: reg: %u", __func__, reg); return -EINVAL; } @@ -296,7 +296,7 @@ static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct uda134x_priv *uda134x = codec->private_data; - pr_debug("%s clk_id: %d, freq: %d, dir: %d\n", __func__, + pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__, clk_id, freq, dir); /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 0275321ff8a..e7348d341b7 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1108,7 +1108,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, if (ret < 0) return ret; dev_dbg(wm8350->dev, - "FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d", + "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d", freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div, fll_div.ratio); diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h index d11bd9288cf..d088eb4b88b 100644 --- a/sound/soc/codecs/wm8350.h +++ b/sound/soc/codecs/wm8350.h @@ -13,6 +13,7 @@ #define _WM8350_H #include <sound/soc.h> +#include <linux/mfd/wm8350/audio.h> extern struct snd_soc_dai wm8350_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8350; diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 510efa60400..502eefac1ec 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -954,7 +954,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, factors->outdiv *= 2; if (factors->outdiv > 32) { dev_err(wm8400->wm8400->dev, - "Unsupported FLL output frequency %dHz\n", + "Unsupported FLL output frequency %uHz\n", Fout); return -EINVAL; } @@ -1003,7 +1003,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, factors->k = K / 10; dev_dbg(wm8400->wm8400->dev, - "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n", + "FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n", Fref, Fout, factors->n, factors->k, factors->fratio, factors->outdiv); @@ -1473,8 +1473,8 @@ static int wm8400_codec_probe(struct platform_device *dev) codec = &priv->codec; codec->private_data = priv; - codec->control_data = dev->dev.driver_data; - priv->wm8400 = dev->dev.driver_data; + codec->control_data = dev_get_drvdata(&dev->dev); + priv->wm8400 = dev_get_drvdata(&dev->dev); ret = regulator_bulk_get(priv->wm8400->dev, ARRAY_SIZE(power), &power[0]); diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 6a4cea09c45..c8b8dba8589 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -298,7 +298,7 @@ static void pll_factors(unsigned int target, unsigned int source) if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "WM8510 N value %d outwith recommended range!d\n", + "WM8510 N value %u outwith recommended range!d\n", Ndiv); pll_div.n = Ndiv; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 9f6be3d31ac..86c4b24db81 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -415,7 +415,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target, unsigned int K, Ndiv, Nmod; int i; - pr_debug("wm8580: PLL %dHz->%dHz\n", source, target); + pr_debug("wm8580: PLL %uHz->%uHz\n", source, target); /* Scale the output frequency up; the PLL should run in the * region of 90-100MHz. @@ -447,7 +447,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 5) || (Ndiv > 13)) { printk(KERN_ERR - "WM8580 N=%d outside supported range\n", Ndiv); + "WM8580 N=%u outside supported range\n", Ndiv); return -EINVAL; } diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index e043e3f6000..7a205876ef4 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -666,14 +666,14 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) codec->hw_write = (hw_write_t)wm8731_spi_write; codec->dev = &spi->dev; - spi->dev.driver_data = wm8731; + dev_set_drvdata(&spi->dev, wm8731); return wm8731_register(wm8731); } static int __devexit wm8731_spi_remove(struct spi_device *spi) { - struct wm8731_priv *wm8731 = spi->dev.driver_data; + struct wm8731_priv *wm8731 = dev_get_drvdata(&spi->dev); wm8731_unregister(wm8731); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index a6e8f3f7f05..d28eeaceb85 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -703,7 +703,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "wm8753: unsupported N = %d\n", Ndiv); + "wm8753: unsupported N = %u\n", Ndiv); pll_div->n = Ndiv; Nmod = target % source; @@ -1822,14 +1822,14 @@ static int __devinit wm8753_spi_probe(struct spi_device *spi) codec->hw_write = (hw_write_t)wm8753_spi_write; codec->dev = &spi->dev; - spi->dev.driver_data = wm8753; + dev_set_drvdata(&spi->dev, wm8753); return wm8753_register(wm8753); } static int __devexit wm8753_spi_remove(struct spi_device *spi) { - struct wm8753_priv *wm8753 = spi->dev.driver_data; + struct wm8753_priv *wm8753 = dev_get_drvdata(&spi->dev); wm8753_unregister(wm8753); return 0; } diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 46c5ea1ff92..3c78945244b 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -778,11 +778,11 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, } if (target > 100000000) - printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d" - " Fout=%d\n", target, Fref, Fout); + printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u" + " Fout=%u\n", target, Fref, Fout); if (div > 32) { printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " - "Fref=%d, Fout=%d, target=%d\n", + "Fref=%u, Fout=%u, target=%u\n", div, Fref, Fout, target); return -EINVAL; } diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 8cf571f1a80..e8d2e3e14c4 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -217,7 +217,6 @@ struct wm8903_priv { int sysclk; /* Reference counts */ - int charge_pump_users; int class_w_users; int playback_active; int capture_active; @@ -373,6 +372,15 @@ static void wm8903_reset(struct snd_soc_codec *codec) #define WM8903_OUTPUT_INT 0x2 #define WM8903_OUTPUT_IN 0x1 +static int wm8903_cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + WARN_ON(event != SND_SOC_DAPM_POST_PMU); + mdelay(4); + + return 0; +} + /* * Event for headphone and line out amplifier power changes. Special * power up/down sequences are required in order to maximise pop/click @@ -382,19 +390,20 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8903_priv *wm8903 = codec->private_data; - struct i2c_client *i2c = codec->control_data; u16 val; u16 reg; + u16 dcs_reg; + u16 dcs_bit; int shift; - u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0); switch (w->reg) { case WM8903_POWER_MANAGEMENT_2: reg = WM8903_ANALOGUE_HP_0; + dcs_bit = 0 + w->shift; break; case WM8903_POWER_MANAGEMENT_3: reg = WM8903_ANALOGUE_LINEOUT_0; + dcs_bit = 2 + w->shift; break; default: BUG(); @@ -419,18 +428,6 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, /* Short the output */ val &= ~(WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); - - wm8903->charge_pump_users++; - - dev_dbg(&i2c->dev, "Charge pump use count now %d\n", - wm8903->charge_pump_users); - - if (wm8903->charge_pump_users == 1) { - dev_dbg(&i2c->dev, "Enabling charge pump\n"); - wm8903_write(codec, WM8903_CHARGE_PUMP_0, - cp_reg | WM8903_CP_ENA); - mdelay(4); - } } if (event & SND_SOC_DAPM_POST_PMU) { @@ -446,6 +443,11 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, val |= (WM8903_OUTPUT_OUT << shift); wm8903_write(codec, reg, val); + /* Enable the DC servo */ + dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg |= dcs_bit; + wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Remove the short */ val |= (WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); @@ -458,25 +460,17 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, val &= ~(WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); + /* Disable the DC servo */ + dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg &= ~dcs_bit; + wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Then disable the intermediate and output stages */ val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | WM8903_OUTPUT_IN) << shift); wm8903_write(codec, reg, val); } - if (event & SND_SOC_DAPM_POST_PMD) { - wm8903->charge_pump_users--; - - dev_dbg(&i2c->dev, "Charge pump use count now %d\n", - wm8903->charge_pump_users); - - if (wm8903->charge_pump_users == 0) { - dev_dbg(&i2c->dev, "Disabling charge pump\n"); - wm8903_write(codec, WM8903_CHARGE_PUMP_0, - cp_reg & ~WM8903_CP_ENA); - } - } - return 0; } @@ -539,6 +533,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, /* ALSA can only do steps of .01dB */ static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0); static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); @@ -657,6 +652,16 @@ static const struct soc_enum rinput_inv_enum = SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text); +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static const struct soc_enum lsidetone_enum = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text); + +static const struct soc_enum rsidetone_enum = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text); + static const struct snd_kcontrol_new wm8903_snd_controls[] = { /* Input PGAs - No TLV since the scale depends on PGA mode */ @@ -700,6 +705,9 @@ SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, SOC_ENUM("ADC Companding Mode", adc_companding), SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8, + 12, 0, digital_sidetone_tlv), + /* DAC */ SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), @@ -762,6 +770,12 @@ static const struct snd_kcontrol_new rinput_mux = static const struct snd_kcontrol_new rinput_inv_mux = SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); +static const struct snd_kcontrol_new lsidetone_mux = + SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum); + +static const struct snd_kcontrol_new rsidetone_mux = + SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); + static const struct snd_kcontrol_new left_output_mixer[] = { SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), @@ -828,6 +842,9 @@ SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), + SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), @@ -844,26 +861,29 @@ SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, 1, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, 0, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, + wm8903_cp_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), }; static const struct snd_soc_dapm_route intercon[] = { @@ -909,7 +929,19 @@ static const struct snd_soc_dapm_route intercon[] = { { "Right Input PGA", NULL, "Right Input Mode Mux" }, { "ADCL", NULL, "Left Input PGA" }, + { "ADCL", NULL, "CLK_DSP" }, { "ADCR", NULL, "Right Input PGA" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "DACL", NULL, "DACL Sidetone" }, + { "DACL", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Sidetone" }, + { "DACR", NULL, "CLK_DSP" }, { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, @@ -951,6 +983,11 @@ static const struct snd_soc_dapm_route intercon[] = { { "ROP", NULL, "Right Speaker PGA" }, { "RON", NULL, "Right Speaker PGA" }, + + { "Left Headphone Output PGA", NULL, "Charge Pump" }, + { "Right Headphone Output PGA", NULL, "Charge Pump" }, + { "Left Line Output PGA", NULL, "Charge Pump" }, + { "Right Line Output PGA", NULL, "Charge Pump" }, }; static int wm8903_add_widgets(struct snd_soc_codec *codec) @@ -985,6 +1022,11 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); + /* Change DC servo dither level in startup sequence */ + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); + wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); + wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); + wm8903_run_sequence(codec, 0); wm8903_sync_reg_cache(codec, codec->reg_cache); @@ -1215,22 +1257,18 @@ static struct { int div; } bclk_divs[] = { { 10, 0 }, - { 15, 1 }, { 20, 2 }, { 30, 3 }, { 40, 4 }, { 50, 5 }, - { 55, 6 }, { 60, 7 }, { 80, 8 }, { 100, 9 }, - { 110, 10 }, { 120, 11 }, { 160, 12 }, { 200, 13 }, { 220, 14 }, { 240, 15 }, - { 250, 16 }, { 300, 17 }, { 320, 18 }, { 440, 19 }, @@ -1277,14 +1315,8 @@ static int wm8903_startup(struct snd_pcm_substream *substream, if (wm8903->master_substream) { master_runtime = wm8903->master_substream->runtime; - dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", - master_runtime->sample_bits, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); + dev_dbg(&i2c->dev, "Constraining to %d bits\n", + master_runtime->sample_bits); snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, @@ -1523,6 +1555,7 @@ struct snd_soc_dai wm8903_dai = { .formats = WM8903_FORMATS, }, .ops = &wm8903_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(wm8903_dai); diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c new file mode 100644 index 00000000000..b8e17d6bc1f --- /dev/null +++ b/sound/soc/codecs/wm8940.c @@ -0,0 +1,955 @@ +/* + * wm8940.c -- WM8940 ALSA Soc Audio driver + * + * Author: Jonathan Cameron <jic23@cam.ac.uk> + * + * Based on wm8510.c + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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. + * + * Not currently handled: + * Notch filter control + * AUXMode (inverting vs mixer) + * No means to obtain current gain if alc enabled. + * No use made of gpio + * Fast VMID discharge for power down + * Soft Start + * DLR and ALR Swaps not enabled + * Digital Sidetone not supported + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8940.h" + +struct wm8940_priv { + unsigned int sysclk; + u16 reg_cache[WM8940_CACHEREGNUM]; + struct snd_soc_codec codec; +}; + +static u16 wm8940_reg_defaults[] = { + 0x8940, /* Soft Reset */ + 0x0000, /* Power 1 */ + 0x0000, /* Power 2 */ + 0x0000, /* Power 3 */ + 0x0010, /* Interface Control */ + 0x0000, /* Companding Control */ + 0x0140, /* Clock Control */ + 0x0000, /* Additional Controls */ + 0x0000, /* GPIO Control */ + 0x0002, /* Auto Increment Control */ + 0x0000, /* DAC Control */ + 0x00FF, /* DAC Volume */ + 0, + 0, + 0x0100, /* ADC Control */ + 0x00FF, /* ADC Volume */ + 0x0000, /* Notch Filter 1 Control 1 */ + 0x0000, /* Notch Filter 1 Control 2 */ + 0x0000, /* Notch Filter 2 Control 1 */ + 0x0000, /* Notch Filter 2 Control 2 */ + 0x0000, /* Notch Filter 3 Control 1 */ + 0x0000, /* Notch Filter 3 Control 2 */ + 0x0000, /* Notch Filter 4 Control 1 */ + 0x0000, /* Notch Filter 4 Control 2 */ + 0x0032, /* DAC Limit Control 1 */ + 0x0000, /* DAC Limit Control 2 */ + 0, + 0, + 0, + 0, + 0, + 0, + 0x0038, /* ALC Control 1 */ + 0x000B, /* ALC Control 2 */ + 0x0032, /* ALC Control 3 */ + 0x0000, /* Noise Gate */ + 0x0041, /* PLLN */ + 0x000C, /* PLLK1 */ + 0x0093, /* PLLK2 */ + 0x00E9, /* PLLK3 */ + 0, + 0, + 0x0030, /* ALC Control 4 */ + 0, + 0x0002, /* Input Control */ + 0x0050, /* PGA Gain */ + 0, + 0x0002, /* ADC Boost Control */ + 0, + 0x0002, /* Output Control */ + 0x0000, /* Speaker Mixer Control */ + 0, + 0, + 0, + 0x0079, /* Speaker Volume */ + 0, + 0x0000, /* Mono Mixer Control */ +}; + +static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) + return -1; + + return cache[reg]; +} + +static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) + return -1; + + cache[reg] = value; + + return 0; +} + +static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + u8 data[3] = { reg, + (value & 0xff00) >> 8, + (value & 0x00ff) + }; + + wm8940_write_reg_cache(codec, reg, value); + + ret = codec->hw_write(codec->control_data, data, 3); + + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + return 0; +} + +static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const struct soc_enum wm8940_adc_companding_enum += SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding); +static const struct soc_enum wm8940_dac_companding_enum += SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding); + +static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"}; +static const struct soc_enum wm8940_alc_mode_enum += SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text); + +static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"}; +static const struct soc_enum wm8940_mic_bias_level_enum += SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text); + +static const char *wm8940_filter_mode_text[] = {"Audio", "Application"}; +static const struct soc_enum wm8940_filter_mode_enum += SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text); + +static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1); +static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0); +static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1); +static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0); + +static const struct snd_kcontrol_new wm8940_snd_controls[] = { + SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL, + 6, 1, 0), + SOC_ENUM("DAC Companding", wm8940_dac_companding_enum), + SOC_ENUM("ADC Companding", wm8940_adc_companding_enum), + + SOC_ENUM("ALC Mode", wm8940_alc_mode_enum), + SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0), + SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1, + 3, 7, 1, wm8940_alc_max_tlv), + SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1, + 0, 7, 0, wm8940_alc_min_tlv), + SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2, + 0, 14, 0, wm8940_alc_tar_tlv), + SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0), + SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0), + SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE, + 3, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE, + 0, 7, 0), + + SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0), + SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2, + 4, 9, 1, wm8940_lim_thresh_tlv), + SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2, + 0, 12, 0, wm8940_lim_boost_tlv), + + SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0), + SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN, + 0, 63, 0, wm8940_pga_vol_tlv), + SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum), + SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST, + 8, 1, 0, wm8940_capture_boost_vol_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL, + 0, 63, 0, wm8940_spk_vol_tlv), + SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL, + 8, 1, 1, wm8940_att_tlv), + SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0), + + SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1), + SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX, + 7, 1, 1, wm8940_att_tlv), + + SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0), + SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum), + SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0), + SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0), + SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0), + SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0), + SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0), +}; + +static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1); +static const struct snd_kcontrol_new wm8940_input_boost_controls[] = { + SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1), + SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST, + 0, 7, 0, wm8940_boost_vol_tlv), + SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST, + 4, 7, 0, wm8940_boost_vol_tlv), +}; + +static const struct snd_kcontrol_new wm8940_micpga_controls[] = { + SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0), + SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0), + SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0, + &wm8940_speaker_mixer_controls[0], + ARRAY_SIZE(wm8940_speaker_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0, + &wm8940_mono_mixer_controls[0], + ARRAY_SIZE(wm8940_mono_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0), + + SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUTP"), + SND_SOC_DAPM_OUTPUT("SPKOUTN"), + + SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0), + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0), + SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0, + &wm8940_micpga_controls[0], + ARRAY_SIZE(wm8940_micpga_controls)), + SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0, + &wm8940_input_boost_controls[0], + ARRAY_SIZE(wm8940_input_boost_controls)), + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0), + + SND_SOC_DAPM_INPUT("MICN"), + SND_SOC_DAPM_INPUT("MICP"), + SND_SOC_DAPM_INPUT("AUX"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + {"Mic PGA", "AUX Switch", "AUX"}, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +static int wm8940_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(codec, wm8940_dapm_widgets, + ARRAY_SIZE(wm8940_dapm_widgets)); + if (ret) + goto error_ret; + ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + if (ret) + goto error_ret; + ret = snd_soc_dapm_new_widgets(codec); + +error_ret: + return ret; +} + +#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0); + +static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67; + u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + wm8940_write(codec, WM8940_CLOCK, clk); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= (2 << 3); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= (1 << 3); + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= (3 << 3); + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= (3 << 3) | (1 << 7); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= (1 << 7); + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= (1 << 8); + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= (1 << 8) | (1 << 7); + break; + } + + wm8940_write(codec, WM8940_IFACE, iface); + + return 0; +} + +static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F; + u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1; + u16 companding = wm8940_read_reg_cache(codec, + WM8940_COMPANDINGCTL) & 0xFFDF; + int ret; + + /* LoutR control */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE + && params_channels(params) == 2) + iface |= (1 << 9); + + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + addcntrl |= (0x5 << 1); + break; + case SNDRV_PCM_RATE_11025: + addcntrl |= (0x4 << 1); + break; + case SNDRV_PCM_RATE_16000: + addcntrl |= (0x3 << 1); + break; + case SNDRV_PCM_RATE_22050: + addcntrl |= (0x2 << 1); + break; + case SNDRV_PCM_RATE_32000: + addcntrl |= (0x1 << 1); + break; + case SNDRV_PCM_RATE_44100: + case SNDRV_PCM_RATE_48000: + break; + } + ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl); + if (ret) + goto error_ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + companding = companding | (1 << 5); + break; + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= (1 << 5); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= (2 << 5); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= (3 << 5); + break; + } + ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding); + if (ret) + goto error_ret; + ret = wm8940_write(codec, WM8940_IFACE, iface); + +error_ret: + return ret; +} + +static int wm8940_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf; + + if (mute) + mute_reg |= 0x40; + + return wm8940_write(codec, WM8940_DAC, mute_reg); +} + +static int wm8940_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0; + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* Enable thermal shutdown */ + val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); + ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2); + if (ret) + break; + /* set vmid to 75k */ + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_PREPARE: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_STANDBY: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* set vmid to 300k for standby */ + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2); + break; + case SND_SOC_BIAS_OFF: + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg); + break; + } + + return ret; +} + +struct pll_ { + unsigned int pre_scale:2; + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + /* The left shift ist to avoid accuracy loss when right shifting */ + Ndiv = target / source; + + if (Ndiv > 12) { + source <<= 1; + /* Multiply by 2 */ + pll_div.pre_scale = 0; + Ndiv = target / source; + } else if (Ndiv < 3) { + source >>= 2; + /* Divide by 4 */ + pll_div.pre_scale = 3; + Ndiv = target / source; + } else if (Ndiv < 6) { + source >>= 1; + /* divide by 2 */ + pll_div.pre_scale = 2; + Ndiv = target / source; + } else + pll_div.pre_scale = 1; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8940 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +/* Untested at the moment */ +static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + /* Turn off PLL */ + reg = wm8940_read_reg_cache(codec, WM8940_POWER1); + wm8940_write(codec, WM8940_POWER1, reg & 0x1df); + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); + wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff); + /* Pll power down */ + wm8940_write(codec, WM8940_PLLN, (1 << 7)); + return 0; + } + + /* Pll is followed by a frequency divide by 4 */ + pll_factors(freq_out*4, freq_in); + if (pll_div.k) + wm8940_write(codec, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n | (1 << 6)); + else /* No factional component */ + wm8940_write(codec, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n); + wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18); + wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff); + wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff); + /* Enable the PLL */ + reg = wm8940_read_reg_cache(codec, WM8940_POWER1); + wm8940_write(codec, WM8940_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); + wm8940_write(codec, WM8940_CLOCK, reg | 0x100); + + return 0; +} + +static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8940_priv *wm8940 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8940->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + int ret = 0; + + switch (div_id) { + case WM8940_BCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3; + ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2)); + break; + case WM8940_MCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F; + ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5)); + break; + case WM8940_OPCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF; + ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4)); + break; + } + return ret; +} + +#define WM8940_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8940_dai_ops = { + .hw_params = wm8940_i2s_hw_params, + .set_sysclk = wm8940_set_dai_sysclk, + .digital_mute = wm8940_mute, + .set_fmt = wm8940_set_dai_fmt, + .set_clkdiv = wm8940_set_dai_clkdiv, + .set_pll = wm8940_set_dai_pll, +}; + +struct snd_soc_dai wm8940_dai = { + .name = "WM8940", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .ops = &wm8940_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8940_dai); + +static int wm8940_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int wm8940_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + int ret; + u8 data[3]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware + * Could use auto incremented writes to speed this up + */ + for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) { + data[0] = i; + data[1] = (cache[i] & 0xFF00) >> 8; + data[2] = cache[i] & 0x00FF; + ret = codec->hw_write(codec->control_data, data, 3); + if (ret < 0) + goto error_ret; + else if (ret != 3) { + ret = -EIO; + goto error_ret; + } + } + ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto error_ret; + ret = wm8940_set_bias_level(codec, codec->suspend_bias_level); + +error_ret: + return ret; +} + +static struct snd_soc_codec *wm8940_codec; + +static int wm8940_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + + int ret = 0; + + if (wm8940_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8940_codec; + codec = wm8940_codec; + + mutex_init(&codec->mutex); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + ret = snd_soc_add_controls(codec, wm8940_snd_controls, + ARRAY_SIZE(wm8940_snd_controls)); + if (ret) + goto error_free_pcms; + ret = wm8940_add_widgets(codec); + if (ret) + goto error_free_pcms; + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto error_free_pcms; + } + + return ret; + +error_free_pcms: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8940_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8940 = { + .probe = wm8940_probe, + .remove = wm8940_remove, + .suspend = wm8940_suspend, + .resume = wm8940_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940); + +static int wm8940_register(struct wm8940_priv *wm8940) +{ + struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data; + struct snd_soc_codec *codec = &wm8940->codec; + int ret; + u16 reg; + if (wm8940_codec) { + dev_err(codec->dev, "Another WM8940 is registered\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8940; + codec->name = "WM8940"; + codec->owner = THIS_MODULE; + codec->read = wm8940_read_reg_cache; + codec->write = wm8940_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8940_set_bias_level; + codec->dai = &wm8940_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults); + codec->reg_cache = &wm8940->reg_cache; + + memcpy(codec->reg_cache, wm8940_reg_defaults, + sizeof(wm8940_reg_defaults)); + + ret = wm8940_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8940_dai.dev = codec->dev; + + wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = wm8940_write(codec, WM8940_POWER1, 0x180); + if (ret < 0) + return ret; + + if (!pdata) + dev_warn(codec->dev, "No platform data supplied\n"); + else { + reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); + ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi); + if (ret < 0) + return ret; + } + + + wm8940_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8940_dai); + if (ret) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void wm8940_unregister(struct wm8940_priv *wm8940) +{ + wm8940_set_bias_level(&wm8940->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8940_dai); + snd_soc_unregister_codec(&wm8940->codec); + kfree(wm8940); + wm8940_codec = NULL; +} + +static int wm8940_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8940_priv *wm8940; + struct snd_soc_codec *codec; + + wm8940 = kzalloc(sizeof *wm8940, GFP_KERNEL); + if (wm8940 == NULL) + return -ENOMEM; + + codec = &wm8940->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + i2c_set_clientdata(i2c, wm8940); + codec->control_data = i2c; + codec->dev = &i2c->dev; + + return wm8940_register(wm8940); +} + +static int __devexit wm8940_i2c_remove(struct i2c_client *client) +{ + struct wm8940_priv *wm8940 = i2c_get_clientdata(client); + + wm8940_unregister(wm8940); + + return 0; +} + +static const struct i2c_device_id wm8940_i2c_id[] = { + { "wm8940", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id); + +static struct i2c_driver wm8940_i2c_driver = { + .driver = { + .name = "WM8940 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8940_i2c_probe, + .remove = __devexit_p(wm8940_i2c_remove), + .id_table = wm8940_i2c_id, +}; + +static int __init wm8940_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8940_i2c_driver); + if (ret) + printk(KERN_ERR "Failed to register WM8940 I2C driver: %d\n", + ret); + return ret; +} +module_init(wm8940_modinit); + +static void __exit wm8940_exit(void) +{ + i2c_del_driver(&wm8940_i2c_driver); +} +module_exit(wm8940_exit); + +MODULE_DESCRIPTION("ASoC WM8940 driver"); +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8940.h b/sound/soc/codecs/wm8940.h new file mode 100644 index 00000000000..8410eed3ef8 --- /dev/null +++ b/sound/soc/codecs/wm8940.h @@ -0,0 +1,104 @@ +/* + * wm8940.h -- WM8940 Soc Audio 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. + */ + +#ifndef _WM8940_H +#define _WM8940_H + +struct wm8940_setup_data { + /* Vref to analogue output resistance */ +#define WM8940_VROI_1K 0 +#define WM8940_VROI_30K 1 + unsigned int vroi:1; +}; +extern struct snd_soc_dai wm8940_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8940; + +/* WM8940 register space */ +#define WM8940_SOFTRESET 0x00 +#define WM8940_POWER1 0x01 +#define WM8940_POWER2 0x02 +#define WM8940_POWER3 0x03 +#define WM8940_IFACE 0x04 +#define WM8940_COMPANDINGCTL 0x05 +#define WM8940_CLOCK 0x06 +#define WM8940_ADDCNTRL 0x07 +#define WM8940_GPIO 0x08 +#define WM8940_CTLINT 0x09 +#define WM8940_DAC 0x0A +#define WM8940_DACVOL 0x0B + +#define WM8940_ADC 0x0E +#define WM8940_ADCVOL 0x0F +#define WM8940_NOTCH1 0x10 +#define WM8940_NOTCH2 0x11 +#define WM8940_NOTCH3 0x12 +#define WM8940_NOTCH4 0x13 +#define WM8940_NOTCH5 0x14 +#define WM8940_NOTCH6 0x15 +#define WM8940_NOTCH7 0x16 +#define WM8940_NOTCH8 0x17 +#define WM8940_DACLIM1 0x18 +#define WM8940_DACLIM2 0x19 + +#define WM8940_ALC1 0x20 +#define WM8940_ALC2 0x21 +#define WM8940_ALC3 0x22 +#define WM8940_NOISEGATE 0x23 +#define WM8940_PLLN 0x24 +#define WM8940_PLLK1 0x25 +#define WM8940_PLLK2 0x26 +#define WM8940_PLLK3 0x27 + +#define WM8940_ALC4 0x2A + +#define WM8940_INPUTCTL 0x2C +#define WM8940_PGAGAIN 0x2D + +#define WM8940_ADCBOOST 0x2F + +#define WM8940_OUTPUTCTL 0x31 +#define WM8940_SPKMIX 0x32 + +#define WM8940_SPKVOL 0x36 + +#define WM8940_MONOMIX 0x38 + +#define WM8940_CACHEREGNUM 0x57 + + +/* Clock divider Id's */ +#define WM8940_BCLKDIV 0 +#define WM8940_MCLKDIV 1 +#define WM8940_OPCLKDIV 2 + +/* MCLK clock dividers */ +#define WM8940_MCLKDIV_1 0 +#define WM8940_MCLKDIV_1_5 1 +#define WM8940_MCLKDIV_2 2 +#define WM8940_MCLKDIV_3 3 +#define WM8940_MCLKDIV_4 4 +#define WM8940_MCLKDIV_6 5 +#define WM8940_MCLKDIV_8 6 +#define WM8940_MCLKDIV_12 7 + +/* BCLK clock dividers */ +#define WM8940_BCLKDIV_1 0 +#define WM8940_BCLKDIV_2 1 +#define WM8940_BCLKDIV_4 2 +#define WM8940_BCLKDIV_8 3 +#define WM8940_BCLKDIV_16 4 +#define WM8940_BCLKDIV_32 5 + +/* PLL Out Dividers */ +#define WM8940_OPCLKDIV_1 0 +#define WM8940_OPCLKDIV_2 1 +#define WM8940_OPCLKDIV_3 2 +#define WM8940_OPCLKDIV_4 3 + +#endif /* _WM8940_H */ + diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c new file mode 100644 index 00000000000..e224d8add17 --- /dev/null +++ b/sound/soc/codecs/wm8960.c @@ -0,0 +1,969 @@ +/* + * wm8960.c -- WM8960 ALSA SoC Audio driver + * + * Author: Liam Girdwood + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8960.h" + +#define AUDIO_NAME "wm8960" + +struct snd_soc_codec_device soc_codec_dev_wm8960; + +/* R25 - Power 1 */ +#define WM8960_VREF 0x40 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 + +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0000, 0x0000, + 0x0000, 0x0008, 0x0000, 0x000a, + 0x01c0, 0x0000, 0x00ff, 0x00ff, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x007b, 0x0100, 0x0032, + 0x0000, 0x00c3, 0x00c3, 0x01c0, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0100, 0x0100, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0040, 0x0000, + 0x0000, 0x0050, 0x0050, 0x0000, + 0x0002, 0x0037, 0x004d, 0x0080, + 0x0008, 0x0031, 0x0026, 0x00e9, +}; + +struct wm8960_priv { + u16 reg_cache[WM8960_CACHEREGNUM]; + struct snd_soc_codec codec; +}; + +/* + * read wm8960 register cache + */ +static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8960_RESET) + return 0; + if (reg >= WM8960_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8960 register cache + */ +static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8960_CACHEREGNUM) + return; + cache[reg] = value; +} + +static inline unsigned int wm8960_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return wm8960_read_reg_cache(codec, reg); +} + +/* + * write to the WM8960 register space + */ +static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8960 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8960_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), +}; + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, adc_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[1]), +SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[2]), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, + 0, 127, 0), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, + { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, + { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "LINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static int wm8960_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7; + + if (mute) + wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8); + else + wm8960_write(codec, WM8960_DACCTL1, mute_reg); + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_data *pdata = codec->dev->platform_data; + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Set VMID to 2x50k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg &= ~0x180; + reg |= 0x80; + wm8960_write(codec, WM8960_POWER1, reg); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Discharge HP output */ + reg = WM8960_DISOP; + if (pdata) + reg |= pdata->dres << 4; + wm8960_write(codec, WM8960_APOP2, reg); + + msleep(400); + + wm8960_write(codec, WM8960_APOP2, 0); + + /* Enable & ramp VMID at 2x50k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg |= 0x80; + wm8960_write(codec, WM8960_POWER1, reg); + msleep(100); + + /* Enable VREF */ + wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF); + + /* Disable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg &= ~0x180; + reg |= 0x100; + wm8960_write(codec, WM8960_POWER1, reg); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + wm8960_write(codec, WM8960_POWER1, 0); + msleep(600); + + wm8960_write(codec, WM8960_APOP1, 0); + break; + } + + codec->bias_level = level; + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. */ + wm8960_write(codec, WM8960_CLOCK1, + wm8960_read(codec, WM8960_CLOCK1) & ~1); + wm8960_write(codec, WM8960_POWER2, + wm8960_read(codec, WM8960_POWER2) & ~1); + + if (!freq_in || !freq_out) + return 0; + + reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); + wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); + wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); + } + wm8960_write(codec, WM8960_PLL1, reg); + + /* Turn it on */ + wm8960_write(codec, WM8960_POWER2, + wm8960_read(codec, WM8960_POWER2) | 1); + msleep(250); + wm8960_write(codec, WM8960_CLOCK1, + wm8960_read(codec, WM8960_CLOCK1) | 1); + + return 0; +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKSEL: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_SYSCLKDIV: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = wm8960_read(codec, WM8960_PLL1) & 0x03f; + wm8960_write(codec, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f; + wm8960_write(codec, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd; + wm8960_write(codec, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define WM8960_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8960_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, +}; + +struct snd_soc_dai wm8960_dai = { + .name = "WM8960", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .ops = &wm8960_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8960_dai); + +static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8960_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8960_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +static struct snd_soc_codec *wm8960_codec; + +static int wm8960_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8960_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8960_codec; + codec = wm8960_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8960_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8960 = { + .probe = wm8960_probe, + .remove = wm8960_remove, + .suspend = wm8960_suspend, + .resume = wm8960_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); + +static int wm8960_register(struct wm8960_priv *wm8960) +{ + struct wm8960_data *pdata = wm8960->codec.dev->platform_data; + struct snd_soc_codec *codec = &wm8960->codec; + int ret; + u16 reg; + + if (wm8960_codec) { + dev_err(codec->dev, "Another WM8960 is registered\n"); + return -EINVAL; + } + + if (!pdata) { + dev_warn(codec->dev, "No platform data supplied\n"); + } else { + if (pdata->dres > WM8960_DRES_MAX) { + dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); + pdata->dres = 0; + } + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8960; + codec->name = "WM8960"; + codec->owner = THIS_MODULE; + codec->read = wm8960_read_reg_cache; + codec->write = wm8960_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8960_set_bias_level; + codec->dai = &wm8960_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8960_CACHEREGNUM; + codec->reg_cache = &wm8960->reg_cache; + + memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); + + ret = wm8960_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8960_dai.dev = codec->dev; + + wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch the update bits */ + reg = wm8960_read(codec, WM8960_LINVOL); + wm8960_write(codec, WM8960_LINVOL, reg | 0x100); + reg = wm8960_read(codec, WM8960_RINVOL); + wm8960_write(codec, WM8960_RINVOL, reg | 0x100); + reg = wm8960_read(codec, WM8960_LADC); + wm8960_write(codec, WM8960_LADC, reg | 0x100); + reg = wm8960_read(codec, WM8960_RADC); + wm8960_write(codec, WM8960_RADC, reg | 0x100); + reg = wm8960_read(codec, WM8960_LDAC); + wm8960_write(codec, WM8960_LDAC, reg | 0x100); + reg = wm8960_read(codec, WM8960_RDAC); + wm8960_write(codec, WM8960_RDAC, reg | 0x100); + reg = wm8960_read(codec, WM8960_LOUT1); + wm8960_write(codec, WM8960_LOUT1, reg | 0x100); + reg = wm8960_read(codec, WM8960_ROUT1); + wm8960_write(codec, WM8960_ROUT1, reg | 0x100); + reg = wm8960_read(codec, WM8960_LOUT2); + wm8960_write(codec, WM8960_LOUT2, reg | 0x100); + reg = wm8960_read(codec, WM8960_ROUT2); + wm8960_write(codec, WM8960_ROUT2, reg | 0x100); + + wm8960_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8960_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void wm8960_unregister(struct wm8960_priv *wm8960) +{ + wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8960_dai); + snd_soc_unregister_codec(&wm8960->codec); + kfree(wm8960); + wm8960_codec = NULL; +} + +static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8960_priv *wm8960; + struct snd_soc_codec *codec; + + wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + codec = &wm8960->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8960); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8960_register(wm8960); +} + +static __devexit int wm8960_i2c_remove(struct i2c_client *client) +{ + struct wm8960_priv *wm8960 = i2c_get_clientdata(client); + wm8960_unregister(wm8960); + return 0; +} + +static const struct i2c_device_id wm8960_i2c_id[] = { + { "wm8960", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); + +static struct i2c_driver wm8960_i2c_driver = { + .driver = { + .name = "WM8960 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8960_i2c_probe, + .remove = __devexit_p(wm8960_i2c_remove), + .id_table = wm8960_i2c_id, +}; + +static int __init wm8960_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8960_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm8960_modinit); + +static void __exit wm8960_exit(void) +{ + i2c_del_driver(&wm8960_i2c_driver); +} +module_exit(wm8960_exit); + + +MODULE_DESCRIPTION("ASoC WM8960 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h new file mode 100644 index 00000000000..c9af56c9d9d --- /dev/null +++ b/sound/soc/codecs/wm8960.h @@ -0,0 +1,127 @@ +/* + * wm8960.h -- WM8960 Soc Audio 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. + */ + +#ifndef _WM8960_H +#define _WM8960_H + +/* WM8960 register space */ + + +#define WM8960_CACHEREGNUM 56 + +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX 0x22 + +#define WM8960_ROUTMIX 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLL1 0x34 +#define WM8960_PLL2 0x35 +#define WM8960_PLL3 0x36 +#define WM8960_PLL4 0x37 + + +/* + * WM8960 Clock dividers + */ +#define WM8960_SYSCLKDIV 0 +#define WM8960_DACDIV 1 +#define WM8960_OPCLKDIV 2 +#define WM8960_DCLKDIV 3 +#define WM8960_TOCLKSEL 4 +#define WM8960_SYSCLKSEL 5 + +#define WM8960_SYSCLK_DIV_1 (0 << 1) +#define WM8960_SYSCLK_DIV_2 (2 << 1) + +#define WM8960_SYSCLK_MCLK (0 << 0) +#define WM8960_SYSCLK_PLL (1 << 0) + +#define WM8960_DAC_DIV_1 (0 << 3) +#define WM8960_DAC_DIV_1_5 (1 << 3) +#define WM8960_DAC_DIV_2 (2 << 3) +#define WM8960_DAC_DIV_3 (3 << 3) +#define WM8960_DAC_DIV_4 (4 << 3) +#define WM8960_DAC_DIV_5_5 (5 << 3) +#define WM8960_DAC_DIV_6 (6 << 3) + +#define WM8960_DCLK_DIV_1_5 (0 << 6) +#define WM8960_DCLK_DIV_2 (1 << 6) +#define WM8960_DCLK_DIV_3 (2 << 6) +#define WM8960_DCLK_DIV_4 (3 << 6) +#define WM8960_DCLK_DIV_6 (4 << 6) +#define WM8960_DCLK_DIV_8 (5 << 6) +#define WM8960_DCLK_DIV_12 (6 << 6) +#define WM8960_DCLK_DIV_16 (7 << 6) + +#define WM8960_TOCLK_F19 (0 << 1) +#define WM8960_TOCLK_F21 (1 << 1) + +#define WM8960_OPCLK_DIV_1 (0 << 0) +#define WM8960_OPCLK_DIV_2 (1 << 0) +#define WM8960_OPCLK_DIV_3 (2 << 0) +#define WM8960_OPCLK_DIV_4 (3 << 0) +#define WM8960_OPCLK_DIV_5_5 (4 << 0) +#define WM8960_OPCLK_DIV_6 (5 << 0) + +extern struct snd_soc_dai wm8960_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8960; + +#define WM8960_DRES_400R 0 +#define WM8960_DRES_200R 1 +#define WM8960_DRES_600R 2 +#define WM8960_DRES_150R 3 +#define WM8960_DRES_MAX 3 + +struct wm8960_data { + int dres; +}; + +#endif diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c new file mode 100644 index 00000000000..c05f71803aa --- /dev/null +++ b/sound/soc/codecs/wm8988.c @@ -0,0 +1,1097 @@ +/* + * wm8988.c -- WM8988 ALSA SoC audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2005 Openedhand Ltd. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8988.h" + +/* + * wm8988 register cache + * We can't read the WM8988 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8988_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +/* codec private data */ +struct wm8988_priv { + unsigned int sysclk; + struct snd_soc_codec codec; + struct snd_pcm_hw_constraint_list *sysclk_constraints; + u16 reg_cache[WM8988_NUM_REG]; +}; + + +/* + * read wm8988 register cache + */ +static inline unsigned int wm8988_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8988_NUM_REG) + return -1; + return cache[reg]; +} + +/* + * write wm8988 register cache + */ +static inline void wm8988_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8988_NUM_REG) + return; + cache[reg] = value; +} + +static int wm8988_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8988_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8988_reset(c) wm8988_write(c, WM8988_RESET, 0) + +/* + * WM8988 Controls + */ + +static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"}; +static const struct soc_enum bass_boost = + SOC_ENUM_SINGLE(WM8988_BASS, 7, 2, bass_boost_txt); + +static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const struct soc_enum bass_filter = + SOC_ENUM_SINGLE(WM8988_BASS, 6, 2, bass_filter_txt); + +static const char *treble_txt[] = {"8kHz", "4kHz"}; +static const struct soc_enum treble = + SOC_ENUM_SINGLE(WM8988_TREBLE, 6, 2, treble_txt); + +static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"}; +static const struct soc_enum stereo_3d_lc = + SOC_ENUM_SINGLE(WM8988_3D, 5, 2, stereo_3d_lc_txt); + +static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"}; +static const struct soc_enum stereo_3d_uc = + SOC_ENUM_SINGLE(WM8988_3D, 6, 2, stereo_3d_uc_txt); + +static const char *stereo_3d_func_txt[] = {"Capture", "Playback"}; +static const struct soc_enum stereo_3d_func = + SOC_ENUM_SINGLE(WM8988_3D, 7, 2, stereo_3d_func_txt); + +static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"}; +static const struct soc_enum alc_func = + SOC_ENUM_SINGLE(WM8988_ALC1, 7, 4, alc_func_txt); + +static const char *ng_type_txt[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const struct soc_enum ng_type = + SOC_ENUM_SINGLE(WM8988_NGATE, 1, 2, ng_type_txt); + +static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const struct soc_enum deemph = + SOC_ENUM_SINGLE(WM8988_ADCDAC, 1, 4, deemph_txt); + +static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const struct soc_enum adcpol = + SOC_ENUM_SINGLE(WM8988_ADCDAC, 5, 4, adcpol_txt); + +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new wm8988_snd_controls[] = { + +SOC_ENUM("Bass Boost", bass_boost), +SOC_ENUM("Bass Filter", bass_filter), +SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0), +SOC_ENUM("Treble Cut-off", treble), + +SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", stereo_3d_lc), +SOC_ENUM("3D Upper Cut-off", stereo_3d_uc), +SOC_ENUM("3D Mode", stereo_3d_func), + +SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", alc_func), +SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", ng_type), +SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC, + 0, 255, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL, + 0, 63, 0, pga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1), + +SOC_ENUM("Playback De-emphasis", deemph), + +SOC_ENUM("Capture Polarity", adcpol), +SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv), + +SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1, + bypass_tlv), + +SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V, + WM8988_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V, + 0, 127, 0, out_tlv), + +SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V, + WM8988_ROUT2V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V, + 0, 127, 0, out_tlv), + +}; + +/* + * DAPM Controls + */ + +static int wm8988_lrc_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 adctl2 = wm8988_read_reg_cache(codec, WM8988_ADCTL2); + + /* Use the DAC to gate LRC if active, otherwise use ADC */ + if (wm8988_read_reg_cache(codec, WM8988_PWR2) & 0x180) + adctl2 &= ~0x4; + else + adctl2 |= 0x4; + + return wm8988_write(codec, WM8988_ADCTL2, adctl2); +} + +static const char *wm8988_line_texts[] = { + "Line 1", "Line 2", "PGA", "Differential"}; + +static const unsigned int wm8988_line_values[] = { + 0, 1, 3, 4}; + +static const struct soc_enum wm8988_lline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_left_line_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum); + +static const struct soc_enum wm8988_rline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_right_line_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0), +}; + +static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"}; +static const unsigned int wm8988_pga_val[] = { 0, 1, 3 }; + +/* Left PGA Mux */ +static const struct soc_enum wm8988_lpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_left_pga_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lpga_enum); + +/* Right PGA Mux */ +static const struct soc_enum wm8988_rpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_right_pga_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_rpga_enum); + +/* Differential Mux */ +static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"}; +static const struct soc_enum diffmux = + SOC_ENUM_SINGLE(WM8988_ADCIN, 8, 2, wm8988_diff_sel); +static const struct snd_kcontrol_new wm8988_diffmux_controls = + SOC_DAPM_ENUM("Route", diffmux); + +/* Mono ADC Mux */ +static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const struct soc_enum monomux = + SOC_ENUM_SINGLE(WM8988_ADCIN, 6, 4, wm8988_mono_mux); +static const struct snd_kcontrol_new wm8988_monomux_controls = + SOC_DAPM_ENUM("Route", monomux); + +static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8988_PWR1, 1, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8988_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0, + &wm8988_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0, + &wm8988_right_pga_controls), + + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_right_line_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0), + + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_left_mixer_controls[0], + ARRAY_SIZE(wm8988_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_right_mixer_controls[0], + ARRAY_SIZE(wm8988_right_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0), + + SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left PGA Mux", "Line 1", "LINPUT1" }, + { "Left PGA Mux", "Line 2", "LINPUT2" }, + { "Left PGA Mux", "Differential", "Differential Mux" }, + + { "Right PGA Mux", "Line 1", "RINPUT1" }, + { "Right PGA Mux", "Line 2", "RINPUT2" }, + { "Right PGA Mux", "Differential", "Differential Mux" }, + + { "Differential Mux", "Line 1", "LINPUT1" }, + { "Differential Mux", "Line 1", "RINPUT1" }, + { "Differential Mux", "Line 2", "LINPUT2" }, + { "Differential Mux", "Line 2", "RINPUT2" }, + + { "Left ADC Mux", "Stereo", "Left PGA Mux" }, + { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" }, + { "Left ADC Mux", "Digital Mono", "Left PGA Mux" }, + + { "Right ADC Mux", "Stereo", "Right PGA Mux" }, + { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" }, + { "Right ADC Mux", "Digital Mono", "Right PGA Mux" }, + + { "Left ADC", NULL, "Left ADC Mux" }, + { "Right ADC", NULL, "Right ADC Mux" }, + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left Mixer", "Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, + { "Left Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Right Mixer", "Left Playback Switch", "Left DAC" }, + { "Right Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Right Mixer", "Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Left Out 1", NULL, "Left Mixer" }, + { "LOUT1", NULL, "Left Out 1" }, + { "Right Out 1", NULL, "Right Mixer" }, + { "ROUT1", NULL, "Right Out 1" }, + + { "Left Out 2", NULL, "Left Mixer" }, + { "LOUT2", NULL, "Left Out 2" }, + { "Right Out 2", NULL, "Right Mixer" }, + { "ROUT2", NULL, "Right Out 2" }, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + return -EINVAL; +} + +/* The set of rates we can generate from the above for each SYSCLK */ + +static unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static unsigned int rates_112896[] = { + 8000, 11025, 22050, 44100, +}; + +static struct snd_pcm_hw_constraint_list constraints_112896 = { + .count = ARRAY_SIZE(rates_112896), + .list = rates_112896, +}; + +static unsigned int rates_12[] = { + 8000, 11025, 12000, 16000, 22050, 2400, 32000, 41100, 48000, + 48000, 88235, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12 = { + .count = ARRAY_SIZE(rates_12), + .list = rates_12, +}; + +/* + * Note that this should be called from init rather than from hw_params. + */ +static int wm8988_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8988_priv *wm8988 = codec->private_data; + + switch (freq) { + case 11289600: + case 18432000: + case 22579200: + case 36864000: + wm8988->sysclk_constraints = &constraints_112896; + wm8988->sysclk = freq; + return 0; + + case 12288000: + case 16934400: + case 24576000: + case 33868800: + wm8988->sysclk_constraints = &constraints_12288; + wm8988->sysclk = freq; + return 0; + + case 12000000: + case 24000000: + wm8988->sysclk_constraints = &constraints_12; + wm8988->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8988_write(codec, WM8988_IFACE, iface); + return 0; +} + +static int wm8988_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8988_priv *wm8988 = codec->private_data; + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8988->sysclk) { + dev_err(codec->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + wm8988->sysclk_constraints); + + return 0; +} + +static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8988_priv *wm8988 = codec->private_data; + u16 iface = wm8988_read_reg_cache(codec, WM8988_IFACE) & 0x1f3; + u16 srate = wm8988_read_reg_cache(codec, WM8988_SRATE) & 0x180; + int coeff; + + coeff = get_coeff(wm8988->sysclk, params_rate(params)); + if (coeff < 0) { + coeff = get_coeff(wm8988->sysclk / 2, params_rate(params)); + srate |= 0x40; + } + if (coeff < 0) { + dev_err(codec->dev, + "Unable to configure sample rate %dHz with %dHz MCLK\n", + params_rate(params), wm8988->sysclk); + return coeff; + } + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8988_write(codec, WM8988_IFACE, iface); + if (coeff >= 0) + wm8988_write(codec, WM8988_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8988_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8988_read_reg_cache(codec, WM8988_ADCDAC) & 0xfff7; + + if (mute) + wm8988_write(codec, WM8988_ADCDAC, mute_reg | 0x8); + else + wm8988_write(codec, WM8988_ADCDAC, mute_reg); + return 0; +} + +static int wm8988_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8988_read_reg_cache(codec, WM8988_PWR1) & ~0x1c1; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VREF, VMID=2x50k, digital enabled */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x00c0); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* VREF, VMID=2x5k */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x1c1); + + /* Charge caps */ + msleep(100); + } + + /* VREF, VMID=2*500k, digital stopped */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x0141); + break; + + case SND_SOC_BIAS_OFF: + wm8988_write(codec, WM8988_PWR1, 0x0000); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8988_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8988_ops = { + .startup = wm8988_pcm_startup, + .hw_params = wm8988_pcm_hw_params, + .set_fmt = wm8988_set_dai_fmt, + .set_sysclk = wm8988_set_dai_sysclk, + .digital_mute = wm8988_mute, +}; + +struct snd_soc_dai wm8988_dai = { + .name = "WM8988", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .ops = &wm8988_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8988_dai); + +static int wm8988_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8988_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < WM8988_NUM_REG; i++) { + if (i == WM8988_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec *wm8988_codec; + +static int wm8988_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8988_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8988_codec; + codec = wm8988_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8988_snd_controls, + ARRAY_SIZE(wm8988_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8988_dapm_widgets, + ARRAY_SIZE(wm8988_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8988_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8988 = { + .probe = wm8988_probe, + .remove = wm8988_remove, + .suspend = wm8988_suspend, + .resume = wm8988_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8988); + +static int wm8988_register(struct wm8988_priv *wm8988) +{ + struct snd_soc_codec *codec = &wm8988->codec; + int ret; + u16 reg; + + if (wm8988_codec) { + dev_err(codec->dev, "Another WM8988 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8988; + codec->name = "WM8988"; + codec->owner = THIS_MODULE; + codec->read = wm8988_read_reg_cache; + codec->write = wm8988_write; + codec->dai = &wm8988_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8988->reg_cache); + codec->reg_cache = &wm8988->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8988_set_bias_level; + + memcpy(codec->reg_cache, wm8988_reg, + sizeof(wm8988_reg)); + + ret = wm8988_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + /* set the update bits (we always update left then right) */ + reg = wm8988_read_reg_cache(codec, WM8988_RADC); + wm8988_write(codec, WM8988_RADC, reg | 0x100); + reg = wm8988_read_reg_cache(codec, WM8988_RDAC); + wm8988_write(codec, WM8988_RDAC, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_ROUT1V); + wm8988_write(codec, WM8988_ROUT1V, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_ROUT2V); + wm8988_write(codec, WM8988_ROUT2V, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_RINVOL); + wm8988_write(codec, WM8988_RINVOL, reg | 0x0100); + + wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_STANDBY); + + wm8988_dai.dev = codec->dev; + + wm8988_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8988_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm8988); + return ret; +} + +static void wm8988_unregister(struct wm8988_priv *wm8988) +{ + wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8988_dai); + snd_soc_unregister_codec(&wm8988->codec); + kfree(wm8988); + wm8988_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int wm8988_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8988_priv *wm8988; + struct snd_soc_codec *codec; + + wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + codec = &wm8988->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8988); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8988_register(wm8988); +} + +static int wm8988_i2c_remove(struct i2c_client *client) +{ + struct wm8988_priv *wm8988 = i2c_get_clientdata(client); + wm8988_unregister(wm8988); + return 0; +} + +static const struct i2c_device_id wm8988_i2c_id[] = { + { "wm8988", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id); + +static struct i2c_driver wm8988_i2c_driver = { + .driver = { + .name = "WM8988", + .owner = THIS_MODULE, + }, + .probe = wm8988_i2c_probe, + .remove = wm8988_i2c_remove, + .id_table = wm8988_i2c_id, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) +static int wm8988_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} + +static int __devinit wm8988_spi_probe(struct spi_device *spi) +{ + struct wm8988_priv *wm8988; + struct snd_soc_codec *codec; + + wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + codec = &wm8988->codec; + codec->hw_write = (hw_write_t)wm8988_spi_write; + codec->control_data = spi; + codec->dev = &spi->dev; + + spi->dev.driver_data = wm8988; + + return wm8988_register(wm8988); +} + +static int __devexit wm8988_spi_remove(struct spi_device *spi) +{ + struct wm8988_priv *wm8988 = spi->dev.driver_data; + + wm8988_unregister(wm8988); + + return 0; +} + +static struct spi_driver wm8988_spi_driver = { + .driver = { + .name = "wm8988", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8988_spi_probe, + .remove = __devexit_p(wm8988_spi_remove), +}; +#endif + +static int __init wm8988_modinit(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8988_i2c_driver); + if (ret != 0) + pr_err("WM8988: Unable to register I2C driver: %d\n", ret); +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8988_spi_driver); + if (ret != 0) + pr_err("WM8988: Unable to register SPI driver: %d\n", ret); +#endif + return ret; +} +module_init(wm8988_modinit); + +static void __exit wm8988_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8988_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8988_spi_driver); +#endif +} +module_exit(wm8988_exit); + + +MODULE_DESCRIPTION("ASoC WM8988 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8988.h b/sound/soc/codecs/wm8988.h new file mode 100644 index 00000000000..4552d37fdd4 --- /dev/null +++ b/sound/soc/codecs/wm8988.h @@ -0,0 +1,60 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on WM8753.h + * + * 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. + * + */ + +#ifndef _WM8988_H +#define _WM8988_H + +/* WM8988 register space */ + +#define WM8988_LINVOL 0x00 +#define WM8988_RINVOL 0x01 +#define WM8988_LOUT1V 0x02 +#define WM8988_ROUT1V 0x03 +#define WM8988_ADCDAC 0x05 +#define WM8988_IFACE 0x07 +#define WM8988_SRATE 0x08 +#define WM8988_LDAC 0x0a +#define WM8988_RDAC 0x0b +#define WM8988_BASS 0x0c +#define WM8988_TREBLE 0x0d +#define WM8988_RESET 0x0f +#define WM8988_3D 0x10 +#define WM8988_ALC1 0x11 +#define WM8988_ALC2 0x12 +#define WM8988_ALC3 0x13 +#define WM8988_NGATE 0x14 +#define WM8988_LADC 0x15 +#define WM8988_RADC 0x16 +#define WM8988_ADCTL1 0x17 +#define WM8988_ADCTL2 0x18 +#define WM8988_PWR1 0x19 +#define WM8988_PWR2 0x1a +#define WM8988_ADCTL3 0x1b +#define WM8988_ADCIN 0x1f +#define WM8988_LADCIN 0x20 +#define WM8988_RADCIN 0x21 +#define WM8988_LOUTM1 0x22 +#define WM8988_LOUTM2 0x23 +#define WM8988_ROUTM1 0x24 +#define WM8988_ROUTM2 0x25 +#define WM8988_LOUT2V 0x28 +#define WM8988_ROUT2V 0x29 +#define WM8988_LPPB 0x43 +#define WM8988_NUM_REG 0x44 + +#define WM8988_SYSCLK 0 + +extern struct snd_soc_dai wm8988_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8988; + +#endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index c518c3e5aa3..d029818350e 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -729,7 +729,7 @@ SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0, inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /* AINLMUX */ -SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0, +SND_SOC_DAPM_MUX_E("AINLMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0, &wm8990_dapm_ainlmux_controls, inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), @@ -740,7 +740,7 @@ SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0, inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /* AINRMUX */ -SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0, +SND_SOC_DAPM_MUX_E("AINRMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0, &wm8990_dapm_ainrmux_controls, inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), @@ -848,40 +848,40 @@ static const struct snd_soc_dapm_route audio_map[] = { {"LIN12 PGA", "LIN2 Switch", "LIN2"}, /* LIN34 PGA */ {"LIN34 PGA", "LIN3 Switch", "LIN3"}, - {"LIN34 PGA", "LIN4 Switch", "LIN4"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, /* INMIXL */ {"INMIXL", "Record Left Volume", "LOMIX"}, {"INMIXL", "LIN2 Volume", "LIN2"}, {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, - /* AILNMUX */ - {"AILNMUX", "INMIXL Mix", "INMIXL"}, - {"AILNMUX", "DIFFINL Mix", "LIN12PGA"}, - {"AILNMUX", "DIFFINL Mix", "LIN34PGA"}, - {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, - {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* AINLMUX */ + {"AINLMUX", "INMIXL Mix", "INMIXL"}, + {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AINLMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AINLMUX", "RXVOICE Mix", "RIN4/RXP"}, /* ADC */ - {"Left ADC", NULL, "AILNMUX"}, + {"Left ADC", NULL, "AINLMUX"}, /* RIN12 PGA */ {"RIN12 PGA", "RIN1 Switch", "RIN1"}, {"RIN12 PGA", "RIN2 Switch", "RIN2"}, /* RIN34 PGA */ {"RIN34 PGA", "RIN3 Switch", "RIN3"}, - {"RIN34 PGA", "RIN4 Switch", "RIN4"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, /* INMIXL */ {"INMIXR", "Record Right Volume", "ROMIX"}, {"INMIXR", "RIN2 Volume", "RIN2"}, {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, - /* AIRNMUX */ - {"AIRNMUX", "INMIXR Mix", "INMIXR"}, - {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"}, - {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"}, - {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"}, - {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* AINRMUX */ + {"AINRMUX", "INMIXR Mix", "INMIXR"}, + {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AINRMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AINRMUX", "RXVOICE Mix", "RIN4/RXP"}, /* ADC */ - {"Right ADC", NULL, "AIRNMUX"}, + {"Right ADC", NULL, "AINRMUX"}, /* LOMIX */ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, @@ -922,7 +922,7 @@ static const struct snd_soc_dapm_route audio_map[] = { {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, /* OUT3MIX */ - {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"}, + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, /* OUT4MIX */ @@ -949,7 +949,7 @@ static const struct snd_soc_dapm_route audio_map[] = { /* Output Pins */ {"LON", NULL, "LONMIX"}, {"LOP", NULL, "LOPMIX"}, - {"OUT", NULL, "OUT3MIX"}, + {"OUT3", NULL, "OUT3MIX"}, {"LOUT", NULL, "LOUT PGA"}, {"SPKN", NULL, "SPKMIX"}, {"ROUT", NULL, "ROUT PGA"}, @@ -998,7 +998,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "WM8990 N value outwith recommended range! N = %d\n", Ndiv); + "WM8990 N value outwith recommended range! N = %u\n", Ndiv); pll_div->n = Ndiv; Nmod = target % source; diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c new file mode 100644 index 00000000000..86fc57e25f9 --- /dev/null +++ b/sound/soc/codecs/wm9081.c @@ -0,0 +1,1534 @@ +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009 Wolfson Microelectronics plc + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include <sound/wm9081.h> +#include "wm9081.h" + +static u16 wm9081_reg_defaults[] = { + 0x0000, /* R0 - Software Reset */ + 0x0000, /* R1 */ + 0x00B9, /* R2 - Analogue Lineout */ + 0x00B9, /* R3 - Analogue Speaker PGA */ + 0x0001, /* R4 - VMID Control */ + 0x0068, /* R5 - Bias Control 1 */ + 0x0000, /* R6 */ + 0x0000, /* R7 - Analogue Mixer */ + 0x0000, /* R8 - Anti Pop Control */ + 0x01DB, /* R9 - Analogue Speaker 1 */ + 0x0018, /* R10 - Analogue Speaker 2 */ + 0x0180, /* R11 - Power Management */ + 0x0000, /* R12 - Clock Control 1 */ + 0x0038, /* R13 - Clock Control 2 */ + 0x4000, /* R14 - Clock Control 3 */ + 0x0000, /* R15 */ + 0x0000, /* R16 - FLL Control 1 */ + 0x0200, /* R17 - FLL Control 2 */ + 0x0000, /* R18 - FLL Control 3 */ + 0x0204, /* R19 - FLL Control 4 */ + 0x0000, /* R20 - FLL Control 5 */ + 0x0000, /* R21 */ + 0x0000, /* R22 - Audio Interface 1 */ + 0x0002, /* R23 - Audio Interface 2 */ + 0x0008, /* R24 - Audio Interface 3 */ + 0x0022, /* R25 - Audio Interface 4 */ + 0x0000, /* R26 - Interrupt Status */ + 0x0006, /* R27 - Interrupt Status Mask */ + 0x0000, /* R28 - Interrupt Polarity */ + 0x0000, /* R29 - Interrupt Control */ + 0x00C0, /* R30 - DAC Digital 1 */ + 0x0008, /* R31 - DAC Digital 2 */ + 0x09AF, /* R32 - DRC 1 */ + 0x4201, /* R33 - DRC 2 */ + 0x0000, /* R34 - DRC 3 */ + 0x0000, /* R35 - DRC 4 */ + 0x0000, /* R36 */ + 0x0000, /* R37 */ + 0x0000, /* R38 - Write Sequencer 1 */ + 0x0000, /* R39 - Write Sequencer 2 */ + 0x0002, /* R40 - MW Slave 1 */ + 0x0000, /* R41 */ + 0x0000, /* R42 - EQ 1 */ + 0x0000, /* R43 - EQ 2 */ + 0x0FCA, /* R44 - EQ 3 */ + 0x0400, /* R45 - EQ 4 */ + 0x00B8, /* R46 - EQ 5 */ + 0x1EB5, /* R47 - EQ 6 */ + 0xF145, /* R48 - EQ 7 */ + 0x0B75, /* R49 - EQ 8 */ + 0x01C5, /* R50 - EQ 9 */ + 0x169E, /* R51 - EQ 10 */ + 0xF829, /* R52 - EQ 11 */ + 0x07AD, /* R53 - EQ 12 */ + 0x1103, /* R54 - EQ 13 */ + 0x1C58, /* R55 - EQ 14 */ + 0xF373, /* R56 - EQ 15 */ + 0x0A54, /* R57 - EQ 16 */ + 0x0558, /* R58 - EQ 17 */ + 0x0564, /* R59 - EQ 18 */ + 0x0559, /* R60 - EQ 19 */ + 0x4000, /* R61 - EQ 20 */ +}; + +static struct { + int ratio; + int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +struct wm9081_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM9081_MAX_REGISTER + 1]; + int sysclk_source; + int mclk_rate; + int sysclk_rate; + int fs; + int bclk; + int master; + int fll_fref; + int fll_fout; + struct wm9081_retune_mobile_config *retune; +}; + +static int wm9081_reg_is_volatile(int reg) +{ + switch (reg) { + default: + return 0; + } +} + +static unsigned int wm9081_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > WM9081_MAX_REGISTER); + return cache[reg]; +} + +static unsigned int wm9081_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg > WM9081_MAX_REGISTER); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm9081_read(struct snd_soc_codec *codec, unsigned int reg) +{ + if (wm9081_reg_is_volatile(reg)) + return wm9081_read_hw(codec, reg); + else + return wm9081_read_reg_cache(codec, reg); +} + +static int wm9081_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[3]; + + BUG_ON(reg > WM9081_MAX_REGISTER); + + if (!wm9081_reg_is_volatile(reg)) + cache[reg] = value; + + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static int wm9081_reset(struct snd_soc_codec *codec) +{ + return wm9081_write(codec, WM9081_SOFTWARE_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0); +static unsigned int drc_max_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0); + +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const char *drc_high_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "1/16", + "0", +}; + +static const struct soc_enum drc_high = + SOC_ENUM_SINGLE(WM9081_DRC_3, 3, 6, drc_high_text); + +static const char *drc_low_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "0", +}; + +static const struct soc_enum drc_low = + SOC_ENUM_SINGLE(WM9081_DRC_3, 0, 5, drc_low_text); + +static const char *drc_atk_text[] = { + "181us", + "181us", + "363us", + "726us", + "1.45ms", + "2.9ms", + "5.8ms", + "11.6ms", + "23.2ms", + "46.4ms", + "92.8ms", + "185.6ms", +}; + +static const struct soc_enum drc_atk = + SOC_ENUM_SINGLE(WM9081_DRC_2, 12, 12, drc_atk_text); + +static const char *drc_dcy_text[] = { + "186ms", + "372ms", + "743ms", + "1.49s", + "2.97s", + "5.94s", + "11.89s", + "23.78s", + "47.56s", +}; + +static const struct soc_enum drc_dcy = + SOC_ENUM_SINGLE(WM9081_DRC_2, 8, 9, drc_dcy_text); + +static const char *drc_qr_dcy_text[] = { + "0.725ms", + "1.45ms", + "5.8ms", +}; + +static const struct soc_enum drc_qr_dcy = + SOC_ENUM_SINGLE(WM9081_DRC_2, 4, 3, drc_qr_dcy_text); + +static const char *dac_deemph_text[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static const struct soc_enum dac_deemph = + SOC_ENUM_SINGLE(WM9081_DAC_DIGITAL_2, 1, 4, dac_deemph_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static const struct soc_enum speaker_mode = + SOC_ENUM_SINGLE(WM9081_ANALOGUE_SPEAKER_2, 6, 2, speaker_mode_text); + +static int speaker_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg; + + reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + if (reg & WM9081_SPK_MODE) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +/* + * Stop any attempts to change speaker mode while the speaker is enabled. + * + * We also have some special anti-pop controls dependant on speaker + * mode which must be changed along with the mode. + */ +static int speaker_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg_pwr = wm9081_read(codec, WM9081_POWER_MANAGEMENT); + unsigned int reg2 = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + + /* Are we changing anything? */ + if (ucontrol->value.integer.value[0] == + ((reg2 & WM9081_SPK_MODE) != 0)) + return 0; + + /* Don't try to change modes while enabled */ + if (reg_pwr & WM9081_SPK_ENA) + return -EINVAL; + + if (ucontrol->value.integer.value[0]) { + /* Class AB */ + reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL); + reg2 |= WM9081_SPK_MODE; + } else { + /* Class D */ + reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL; + reg2 &= ~WM9081_SPK_MODE; + } + + wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2); + + return 0; +} + +static const struct snd_kcontrol_new wm9081_snd_controls[] = { +SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv), +SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv), + +SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv), + +SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1), +SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0), +SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv), + +SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0), +SOC_ENUM("DRC High Slope", drc_high), +SOC_ENUM("DRC Low Slope", drc_low), +SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv), +SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv), +SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv), +SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv), +SOC_ENUM("DRC Attack", drc_atk), +SOC_ENUM("DRC Decay", drc_dcy), +SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0), +SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv), +SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy), +SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv), + +SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0), + +SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0), +SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0), +SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0, + out_tlv), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put), +}; + +static const struct snd_kcontrol_new wm9081_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv), +}; + +static const struct snd_kcontrol_new mixer[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0), +SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0), +}; + +static int speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = wm9081_read(codec, WM9081_POWER_MANAGEMENT); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + reg |= WM9081_SPK_ENA; + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~WM9081_SPK_ENA; + break; + } + + wm9081_write(codec, WM9081_POWER_MANAGEMENT, reg); + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + while ((Fref / div) > 13500000) { + div *= 2; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + fll_div->fll_clk_ref_div = div / 2; + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 0; + target = Fout * 2; + while (target < 90000000) { + div++; + target *= 2; + if (div > 7) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + fll_div->fll_outdiv = div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, + unsigned int Fref, unsigned int Fout) +{ + struct wm9081_priv *wm9081 = codec->private_data; + u16 reg1, reg4, reg5; + struct _fll_div fll_div; + int ret; + int clk_sys_reg; + + /* Any change? */ + if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout) + return 0; + + /* Disable the FLL */ + if (Fout == 0) { + dev_dbg(codec->dev, "FLL disabled\n"); + wm9081->fll_fref = 0; + wm9081->fll_fout = 0; + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + reg5 = wm9081_read(codec, WM9081_FLL_CONTROL_5); + reg5 &= ~WM9081_FLL_CLK_SRC_MASK; + + switch (fll_id) { + case WM9081_SYSCLK_FLL_MCLK: + reg5 |= 0x1; + break; + + default: + dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Disable CLK_SYS while we reconfigure */ + clk_sys_reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, + clk_sys_reg & ~WM9081_CLK_SYS_ENA); + + /* Any FLL configuration change requires that the FLL be + * disabled first. */ + reg1 = wm9081_read(codec, WM9081_FLL_CONTROL_1); + reg1 &= ~WM9081_FLL_ENA; + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + + /* Apply the configuration */ + if (fll_div.k) + reg1 |= WM9081_FLL_FRAC_MASK; + else + reg1 &= ~WM9081_FLL_FRAC_MASK; + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + + wm9081_write(codec, WM9081_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT)); + wm9081_write(codec, WM9081_FLL_CONTROL_3, fll_div.k); + + reg4 = wm9081_read(codec, WM9081_FLL_CONTROL_4); + reg4 &= ~WM9081_FLL_N_MASK; + reg4 |= fll_div.n << WM9081_FLL_N_SHIFT; + wm9081_write(codec, WM9081_FLL_CONTROL_4, reg4); + + reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK; + reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT; + wm9081_write(codec, WM9081_FLL_CONTROL_5, reg5); + + /* Enable the FLL */ + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA); + + /* Then bring CLK_SYS up again if it was disabled */ + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg); + + dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); + + wm9081->fll_fref = Fref; + wm9081->fll_fout = Fout; + + return 0; +} + +static int configure_clock(struct snd_soc_codec *codec) +{ + struct wm9081_priv *wm9081 = codec->private_data; + int new_sysclk, i, target; + unsigned int reg; + int ret = 0; + int mclkdiv = 0; + int fll = 0; + + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + if (wm9081->mclk_rate > 12225000) { + mclkdiv = 1; + wm9081->sysclk_rate = wm9081->mclk_rate / 2; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, 0, 0); + break; + + case WM9081_SYSCLK_FLL_MCLK: + /* If we have a sample rate calculate a CLK_SYS that + * gives us a suitable DAC configuration, plus BCLK. + * Ideally we would check to see if we can clock + * directly from MCLK and only use the FLL if this is + * not the case, though care must be taken with free + * running mode. + */ + if (wm9081->master && wm9081->bclk) { + /* Make sure we can generate CLK_SYS and BCLK + * and that we've got 3MHz for optimal + * performance. */ + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + target = wm9081->fs * clk_sys_rates[i].ratio; + new_sysclk = target; + if (target >= wm9081->bclk && + target > 3000000) + break; + } + } else if (wm9081->fs) { + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + new_sysclk = clk_sys_rates[i].ratio + * wm9081->fs; + if (new_sysclk > 3000000) + break; + } + } else { + new_sysclk = 12288000; + } + + ret = wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, + wm9081->mclk_rate, new_sysclk); + if (ret == 0) { + wm9081->sysclk_rate = new_sysclk; + + /* Switch SYSCLK over to FLL */ + fll = 1; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + break; + + default: + return -EINVAL; + } + + reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_1); + if (mclkdiv) + reg |= WM9081_MCLKDIV2; + else + reg &= ~WM9081_MCLKDIV2; + wm9081_write(codec, WM9081_CLOCK_CONTROL_1, reg); + + reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + if (fll) + reg |= WM9081_CLK_SRC_SEL; + else + reg &= ~WM9081_CLK_SRC_SEL; + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, reg); + + dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate); + + return ret; +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm9081_priv *wm9081 = codec->private_data; + + /* This should be done on init() for bypass paths */ + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + dev_dbg(codec->dev, "Using %dHz MCLK\n", wm9081->mclk_rate); + break; + case WM9081_SYSCLK_FLL_MCLK: + dev_dbg(codec->dev, "Using %dHz MCLK with FLL\n", + wm9081->mclk_rate); + break; + default: + dev_err(codec->dev, "System clock not configured\n"); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + configure_clock(codec); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable the FLL if it's running */ + wm9081_set_fll(codec, 0, 0, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1"), +SND_SOC_DAPM_INPUT("IN2"), + +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM9081_POWER_MANAGEMENT, 0, 0), + +SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, + mixer, ARRAY_SIZE(mixer)), + +SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0, + speaker_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("LINEOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM9081_CLOCK_CONTROL_3, 0, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM9081_CLOCK_CONTROL_3, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0), +}; + + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "CLK_SYS" }, + { "DAC", NULL, "CLK_DSP" }, + + { "Mixer", "IN1 Switch", "IN1" }, + { "Mixer", "IN2 Switch", "IN2" }, + { "Mixer", "Playback Switch", "DAC" }, + + { "LINEOUT PGA", NULL, "Mixer" }, + { "LINEOUT PGA", NULL, "TOCLK" }, + { "LINEOUT PGA", NULL, "CLK_SYS" }, + + { "LINEOUT", NULL, "LINEOUT PGA" }, + + { "Speaker PGA", NULL, "Mixer" }, + { "Speaker PGA", NULL, "TOCLK" }, + { "Speaker PGA", NULL, "CLK_SYS" }, + + { "SPKN", NULL, "Speaker PGA" }, + { "SPKP", NULL, "Speaker PGA" }, +}; + +static int wm9081_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*40k */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg &= ~WM9081_VMID_SEL_MASK; + reg |= 0x2; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Normal bias current */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_STBY_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + break; + + case SND_SOC_BIAS_STANDBY: + /* Initial cold start */ + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Disable LINEOUT discharge */ + reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg &= ~WM9081_LINEOUT_DISCH; + wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + + /* Select startup bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + + /* VMID 2*4k; Soft VMID ramp enable */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg |= WM9081_VMID_RAMP | 0x6; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + mdelay(100); + + /* Normal bias enable & soft start off */ + reg |= WM9081_BIAS_ENA; + reg &= ~WM9081_VMID_RAMP; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Standard bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_BIAS_SRC; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + } + + /* VMID 2*240k */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_VMID_SEL_MASK; + reg |= 0x40; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Standby bias current on */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_STBY_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_BIAS_SRC; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + + /* Disable VMID and biases with soft ramping */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA); + reg |= WM9081_VMID_RAMP; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Actively discharge LINEOUT */ + reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg |= WM9081_LINEOUT_DISCH; + wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + unsigned int aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + + aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | + WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + wm9081->master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif2 |= WM9081_LRCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif2 |= WM9081_BCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif2 |= WM9081_LRCLK_DIR | WM9081_BCLK_DIR; + wm9081->master = 1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif2 |= WM9081_AIF_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif2 |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif2 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif2 |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif2 |= WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif2 |= WM9081_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + + return 0; +} + +static int wm9081_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + int ret, i, best, best_val, cur_val; + unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; + + clk_ctrl2 = wm9081_read(codec, WM9081_CLOCK_CONTROL_2); + clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK); + + aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + + aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + aif2 &= ~WM9081_AIF_WL_MASK; + + aif3 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_3); + aif3 &= ~WM9081_BCLK_DIV_MASK; + + aif4 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_4); + aif4 &= ~WM9081_LRCLK_RATE_MASK; + + /* What BCLK do we need? */ + wm9081->fs = params_rate(params); + wm9081->bclk = 2 * wm9081->fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wm9081->bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wm9081->bclk *= 20; + aif2 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wm9081->bclk *= 24; + aif2 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wm9081->bclk *= 32; + aif2 |= 0xc; + break; + default: + return -EINVAL; + } + + if (aif1 & WM9081_AIFDAC_TDM_MODE_MASK) { + int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >> + WM9081_AIFDAC_TDM_MODE_SHIFT) + 1; + wm9081->bclk *= slots; + } + + dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk); + + ret = configure_clock(codec); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio) + - wm9081->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm9081->sysclk_rate / + clk_sys_rates[i].ratio) - wm9081->fs);; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate + << WM9081_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm9081->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm9081->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clk_ctrl2 |= (sample_rates[best].sample_rate + << WM9081_SAMPLE_RATE_SHIFT); + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div) + - wm9081->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm9081->bclk); + aif3 |= bclk_divs[best].bclk_div; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs); + aif4 |= wm9081->bclk / wm9081->fs; + + /* Apply a ReTune Mobile configuration if it's in use */ + if (wm9081->retune) { + struct wm9081_retune_mobile_config *retune = wm9081->retune; + struct wm9081_retune_mobile_setting *s; + int eq1; + + best = 0; + best_val = abs(retune->configs[0].rate - wm9081->fs); + for (i = 0; i < retune->num_configs; i++) { + cur_val = abs(retune->configs[i].rate - wm9081->fs); + if (cur_val < best_val) { + best_val = cur_val; + best = i; + } + } + s = &retune->configs[best]; + + dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n", + s->name, s->rate); + + /* If the EQ is enabled then disable it while we write out */ + eq1 = wm9081_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA; + if (eq1 & WM9081_EQ_ENA) + wm9081_write(codec, WM9081_EQ_1, 0); + + /* Write out the other values */ + for (i = 1; i < ARRAY_SIZE(s->config); i++) + wm9081_write(codec, WM9081_EQ_1 + i, s->config[i]); + + eq1 |= (s->config[0] & ~WM9081_EQ_ENA); + wm9081_write(codec, WM9081_EQ_1, eq1); + } + + wm9081_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_3, aif3); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_4, aif4); + + return 0; +} + +static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm9081_read(codec, WM9081_DAC_DIGITAL_2); + + if (mute) + reg |= WM9081_DAC_MUTE; + else + reg &= ~WM9081_DAC_MUTE; + + wm9081_write(codec, WM9081_DAC_DIGITAL_2, reg); + + return 0; +} + +static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + + switch (clk_id) { + case WM9081_SYSCLK_MCLK: + case WM9081_SYSCLK_FLL_MCLK: + wm9081->sysclk_source = clk_id; + wm9081->mclk_rate = freq; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int mask, int slots) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + + aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); + + if (slots < 1 || slots > 4) + return -EINVAL; + + aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT; + + switch (mask) { + case 1: + break; + case 2: + aif1 |= 0x10; + break; + case 4: + aif1 |= 0x20; + break; + case 8: + aif1 |= 0x30; + break; + default: + return -EINVAL; + } + + wm9081_write(codec, WM9081_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +#define WM9081_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM9081_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm9081_dai_ops = { + .hw_params = wm9081_hw_params, + .set_sysclk = wm9081_set_sysclk, + .set_fmt = wm9081_set_dai_fmt, + .digital_mute = wm9081_digital_mute, + .set_tdm_slot = wm9081_set_tdm_slot, +}; + +/* We report two channels because the CODEC processes a stereo signal, even + * though it is only capable of handling a mono output. + */ +struct snd_soc_dai wm9081_dai = { + .name = "WM9081", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9081_RATES, + .formats = WM9081_FORMATS, + }, + .ops = &wm9081_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm9081_dai); + + +static struct snd_soc_codec *wm9081_codec; + +static int wm9081_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct wm9081_priv *wm9081; + int ret = 0; + + if (wm9081_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm9081_codec; + codec = wm9081_codec; + wm9081 = codec->private_data; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm9081_snd_controls, + ARRAY_SIZE(wm9081_snd_controls)); + if (!wm9081->retune) { + dev_dbg(codec->dev, + "No ReTune Mobile data, using normal EQ\n"); + snd_soc_add_controls(codec, wm9081_eq_controls, + ARRAY_SIZE(wm9081_eq_controls)); + } + + snd_soc_dapm_new_controls(codec, wm9081_dapm_widgets, + ARRAY_SIZE(wm9081_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm9081_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int wm9081_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm9081_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 *reg_cache = codec->reg_cache; + int i; + + for (i = 0; i < codec->reg_cache_size; i++) { + if (i == WM9081_SOFTWARE_RESET) + continue; + + wm9081_write(codec, i, reg_cache[i]); + } + + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm9081_suspend NULL +#define wm9081_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_wm9081 = { + .probe = wm9081_probe, + .remove = wm9081_remove, + .suspend = wm9081_suspend, + .resume = wm9081_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9081); + +static int wm9081_register(struct wm9081_priv *wm9081) +{ + struct snd_soc_codec *codec = &wm9081->codec; + int ret; + u16 reg; + + if (wm9081_codec) { + dev_err(codec->dev, "Another WM9081 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm9081; + codec->name = "WM9081"; + codec->owner = THIS_MODULE; + codec->read = wm9081_read; + codec->write = wm9081_write; + codec->dai = &wm9081_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm9081->reg_cache); + codec->reg_cache = &wm9081->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm9081_set_bias_level; + + memcpy(codec->reg_cache, wm9081_reg_defaults, + sizeof(wm9081_reg_defaults)); + + reg = wm9081_read_hw(codec, WM9081_SOFTWARE_RESET); + if (reg != 0x9081) { + dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg); + ret = -EINVAL; + goto err; + } + + ret = wm9081_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Enable zero cross by default */ + reg = wm9081_read(codec, WM9081_ANALOGUE_LINEOUT); + wm9081_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC); + reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_PGA); + wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_PGA, + reg | WM9081_SPKPGAZC); + + wm9081_dai.dev = codec->dev; + + wm9081_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm9081_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm9081); + return ret; +} + +static void wm9081_unregister(struct wm9081_priv *wm9081) +{ + wm9081_set_bias_level(&wm9081->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm9081_dai); + snd_soc_unregister_codec(&wm9081->codec); + kfree(wm9081); + wm9081_codec = NULL; +} + +static __devinit int wm9081_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9081_priv *wm9081; + struct snd_soc_codec *codec; + + wm9081 = kzalloc(sizeof(struct wm9081_priv), GFP_KERNEL); + if (wm9081 == NULL) + return -ENOMEM; + + codec = &wm9081->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + wm9081->retune = i2c->dev.platform_data; + + i2c_set_clientdata(i2c, wm9081); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm9081_register(wm9081); +} + +static __devexit int wm9081_i2c_remove(struct i2c_client *client) +{ + struct wm9081_priv *wm9081 = i2c_get_clientdata(client); + wm9081_unregister(wm9081); + return 0; +} + +static const struct i2c_device_id wm9081_i2c_id[] = { + { "wm9081", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id); + +static struct i2c_driver wm9081_i2c_driver = { + .driver = { + .name = "wm9081", + .owner = THIS_MODULE, + }, + .probe = wm9081_i2c_probe, + .remove = __devexit_p(wm9081_i2c_remove), + .id_table = wm9081_i2c_id, +}; + +static int __init wm9081_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm9081_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM9081 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm9081_modinit); + +static void __exit wm9081_exit(void) +{ + i2c_del_driver(&wm9081_i2c_driver); +} +module_exit(wm9081_exit); + + +MODULE_DESCRIPTION("ASoC WM9081 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9081.h b/sound/soc/codecs/wm9081.h new file mode 100644 index 00000000000..42d3bc75702 --- /dev/null +++ b/sound/soc/codecs/wm9081.h @@ -0,0 +1,787 @@ +#ifndef WM9081_H +#define WM9081_H + +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009 Wolfson Microelectronics plc + * + * 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. + */ + +#include <sound/soc.h> + +extern struct snd_soc_dai wm9081_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm9081; + +/* + * SYSCLK sources + */ +#define WM9081_SYSCLK_MCLK 1 /* Use MCLK without FLL */ +#define WM9081_SYSCLK_FLL_MCLK 2 /* Use MCLK, enabling FLL if required */ + +/* + * Register values. + */ +#define WM9081_SOFTWARE_RESET 0x00 +#define WM9081_ANALOGUE_LINEOUT 0x02 +#define WM9081_ANALOGUE_SPEAKER_PGA 0x03 +#define WM9081_VMID_CONTROL 0x04 +#define WM9081_BIAS_CONTROL_1 0x05 +#define WM9081_ANALOGUE_MIXER 0x07 +#define WM9081_ANTI_POP_CONTROL 0x08 +#define WM9081_ANALOGUE_SPEAKER_1 0x09 +#define WM9081_ANALOGUE_SPEAKER_2 0x0A +#define WM9081_POWER_MANAGEMENT 0x0B +#define WM9081_CLOCK_CONTROL_1 0x0C +#define WM9081_CLOCK_CONTROL_2 0x0D +#define WM9081_CLOCK_CONTROL_3 0x0E +#define WM9081_FLL_CONTROL_1 0x10 +#define WM9081_FLL_CONTROL_2 0x11 +#define WM9081_FLL_CONTROL_3 0x12 +#define WM9081_FLL_CONTROL_4 0x13 +#define WM9081_FLL_CONTROL_5 0x14 +#define WM9081_AUDIO_INTERFACE_1 0x16 +#define WM9081_AUDIO_INTERFACE_2 0x17 +#define WM9081_AUDIO_INTERFACE_3 0x18 +#define WM9081_AUDIO_INTERFACE_4 0x19 +#define WM9081_INTERRUPT_STATUS 0x1A +#define WM9081_INTERRUPT_STATUS_MASK 0x1B +#define WM9081_INTERRUPT_POLARITY 0x1C +#define WM9081_INTERRUPT_CONTROL 0x1D +#define WM9081_DAC_DIGITAL_1 0x1E +#define WM9081_DAC_DIGITAL_2 0x1F +#define WM9081_DRC_1 0x20 +#define WM9081_DRC_2 0x21 +#define WM9081_DRC_3 0x22 +#define WM9081_DRC_4 0x23 +#define WM9081_WRITE_SEQUENCER_1 0x26 +#define WM9081_WRITE_SEQUENCER_2 0x27 +#define WM9081_MW_SLAVE_1 0x28 +#define WM9081_EQ_1 0x2A +#define WM9081_EQ_2 0x2B +#define WM9081_EQ_3 0x2C +#define WM9081_EQ_4 0x2D +#define WM9081_EQ_5 0x2E +#define WM9081_EQ_6 0x2F +#define WM9081_EQ_7 0x30 +#define WM9081_EQ_8 0x31 +#define WM9081_EQ_9 0x32 +#define WM9081_EQ_10 0x33 +#define WM9081_EQ_11 0x34 +#define WM9081_EQ_12 0x35 +#define WM9081_EQ_13 0x36 +#define WM9081_EQ_14 0x37 +#define WM9081_EQ_15 0x38 +#define WM9081_EQ_16 0x39 +#define WM9081_EQ_17 0x3A +#define WM9081_EQ_18 0x3B +#define WM9081_EQ_19 0x3C +#define WM9081_EQ_20 0x3D + +#define WM9081_REGISTER_COUNT 55 +#define WM9081_MAX_REGISTER 0x3D + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9081_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R2 (0x02) - Analogue Lineout + */ +#define WM9081_LINEOUT_MUTE 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_MASK 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_SHIFT 7 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_WIDTH 1 /* LINEOUT_MUTE */ +#define WM9081_LINEOUTZC 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_MASK 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_SHIFT 6 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_WIDTH 1 /* LINEOUTZC */ +#define WM9081_LINEOUT_VOL_MASK 0x003F /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_SHIFT 0 /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_WIDTH 6 /* LINEOUT_VOL - [5:0] */ + +/* + * R3 (0x03) - Analogue Speaker PGA + */ +#define WM9081_SPKPGA_MUTE 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_MASK 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_SHIFT 7 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_WIDTH 1 /* SPKPGA_MUTE */ +#define WM9081_SPKPGAZC 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_MASK 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_SHIFT 6 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_WIDTH 1 /* SPKPGAZC */ +#define WM9081_SPKPGA_VOL_MASK 0x003F /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_SHIFT 0 /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_WIDTH 6 /* SPKPGA_VOL - [5:0] */ + +/* + * R4 (0x04) - VMID Control + */ +#define WM9081_VMID_BUF_ENA 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_MASK 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_SHIFT 5 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9081_VMID_RAMP 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_MASK 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_SHIFT 3 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_WIDTH 1 /* VMID_RAMP */ +#define WM9081_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_FAST_ST 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_MASK 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_SHIFT 0 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_WIDTH 1 /* VMID_FAST_ST */ + +/* + * R5 (0x05) - Bias Control 1 + */ +#define WM9081_BIAS_SRC 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_MASK 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_SHIFT 6 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_WIDTH 1 /* BIAS_SRC */ +#define WM9081_STBY_BIAS_LVL 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_MASK 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_SHIFT 5 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_WIDTH 1 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_ENA 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_MASK 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_SHIFT 4 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_WIDTH 1 /* STBY_BIAS_ENA */ +#define WM9081_BIAS_LVL_MASK 0x000C /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_SHIFT 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_WIDTH 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_ENA 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_MASK 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_SHIFT 1 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_MASK 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_SHIFT 0 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ + +/* + * R7 (0x07) - Analogue Mixer + */ +#define WM9081_DAC_SEL 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_MASK 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_SHIFT 4 /* DAC_SEL */ +#define WM9081_DAC_SEL_WIDTH 1 /* DAC_SEL */ +#define WM9081_IN2_VOL 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_MASK 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_SHIFT 3 /* IN2_VOL */ +#define WM9081_IN2_VOL_WIDTH 1 /* IN2_VOL */ +#define WM9081_IN2_ENA 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_MASK 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_SHIFT 2 /* IN2_ENA */ +#define WM9081_IN2_ENA_WIDTH 1 /* IN2_ENA */ +#define WM9081_IN1_VOL 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_MASK 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_SHIFT 1 /* IN1_VOL */ +#define WM9081_IN1_VOL_WIDTH 1 /* IN1_VOL */ +#define WM9081_IN1_ENA 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_MASK 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_SHIFT 0 /* IN1_ENA */ +#define WM9081_IN1_ENA_WIDTH 1 /* IN1_ENA */ + +/* + * R8 (0x08) - Anti Pop Control + */ +#define WM9081_LINEOUT_DISCH 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_MASK 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_SHIFT 2 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_WIDTH 1 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_VROI 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_MASK 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_SHIFT 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_WIDTH 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_CLAMP 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_MASK 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_SHIFT 0 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_WIDTH 1 /* LINEOUT_CLAMP */ + +/* + * R9 (0x09) - Analogue Speaker 1 + */ +#define WM9081_SPK_DCGAIN_MASK 0x0038 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_SHIFT 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_WIDTH 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_ACGAIN_MASK 0x0007 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_SHIFT 0 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_WIDTH 3 /* SPK_ACGAIN - [2:0] */ + +/* + * R10 (0x0A) - Analogue Speaker 2 + */ +#define WM9081_SPK_MODE 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_MASK 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_SHIFT 6 /* SPK_MODE */ +#define WM9081_SPK_MODE_WIDTH 1 /* SPK_MODE */ +#define WM9081_SPK_INV_MUTE 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_MASK 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_SHIFT 4 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_WIDTH 1 /* SPK_INV_MUTE */ +#define WM9081_OUT_SPK_CTRL 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_MASK 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_SHIFT 3 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_WIDTH 1 /* OUT_SPK_CTRL */ + +/* + * R11 (0x0B) - Power Management + */ +#define WM9081_TSHUT_ENA 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_MASK 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_SHIFT 8 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9081_TSENSE_ENA 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_MASK 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_SHIFT 7 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_WIDTH 1 /* TSENSE_ENA */ +#define WM9081_TEMP_SHUT 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_MASK 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_SHIFT 6 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM9081_LINEOUT_ENA 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_MASK 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_SHIFT 4 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_WIDTH 1 /* LINEOUT_ENA */ +#define WM9081_SPKPGA_ENA 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_MASK 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_SHIFT 2 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_WIDTH 1 /* SPKPGA_ENA */ +#define WM9081_SPK_ENA 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_MASK 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_SHIFT 1 /* SPK_ENA */ +#define WM9081_SPK_ENA_WIDTH 1 /* SPK_ENA */ +#define WM9081_DAC_ENA 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_MASK 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_SHIFT 0 /* DAC_ENA */ +#define WM9081_DAC_ENA_WIDTH 1 /* DAC_ENA */ + +/* + * R12 (0x0C) - Clock Control 1 + */ +#define WM9081_CLK_OP_DIV_MASK 0x1C00 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_SHIFT 10 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_WIDTH 3 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_TO_DIV_MASK 0x0300 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_SHIFT 8 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [9:8] */ +#define WM9081_MCLKDIV2 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_MASK 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_SHIFT 7 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R13 (0x0D) - Clock Control 2 + */ +#define WM9081_CLK_SYS_RATE_MASK 0x00F0 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_SHIFT 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R14 (0x0E) - Clock Control 3 + */ +#define WM9081_CLK_SRC_SEL 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_MASK 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_SHIFT 13 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_WIDTH 1 /* CLK_SRC_SEL */ +#define WM9081_CLK_OP_ENA 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_MASK 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_SHIFT 5 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_WIDTH 1 /* CLK_OP_ENA */ +#define WM9081_CLK_TO_ENA 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_MASK 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_SHIFT 2 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_WIDTH 1 /* CLK_TO_ENA */ +#define WM9081_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_SYS_ENA 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_MASK 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_SHIFT 0 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ + +/* + * R16 (0x10) - FLL Control 1 + */ +#define WM9081_FLL_HOLD 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_SHIFT 3 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_WIDTH 1 /* FLL_HOLD */ +#define WM9081_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM9081_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM9081_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R17 (0x11) - FLL Control 2 + */ +#define WM9081_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R18 (0x12) - FLL Control 3 + */ +#define WM9081_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM9081_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM9081_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R19 (0x13) - FLL Control 4 + */ +#define WM9081_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM9081_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R20 (0x14) - FLL Control 5 + */ +#define WM9081_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */ + +/* + * R22 (0x16) - Audio Interface 1 + */ +#define WM9081_AIFDAC_CHAN 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_MASK 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_SHIFT 6 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_WIDTH 1 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_TDM_SLOT_MASK 0x0030 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_SHIFT 4 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_WIDTH 2 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_MODE_MASK 0x000C /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_SHIFT 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_WIDTH 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM9081_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM9081_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R23 (0x17) - Audio Interface 2 + */ +#define WM9081_AIF_TRIS 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_MASK 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_SHIFT 9 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM9081_DAC_DAT_INV 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_MASK 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_SHIFT 8 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_WIDTH 1 /* DAC_DAT_INV */ +#define WM9081_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM9081_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM9081_LRCLK_DIR 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_MASK 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_SHIFT 5 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM9081_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM9081_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R24 (0x18) - Audio Interface 3 + */ +#define WM9081_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R25 (0x19) - Audio Interface 4 + */ +#define WM9081_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R26 (0x1A) - Interrupt Status + */ +#define WM9081_WSEQ_BUSY_EINT 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_MASK 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_SHIFT 2 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM9081_TSHUT_EINT 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_MASK 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_SHIFT 0 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_WIDTH 1 /* TSHUT_EINT */ + +/* + * R27 (0x1B) - Interrupt Status Mask + */ +#define WM9081_IM_WSEQ_BUSY_EINT 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_MASK 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT 2 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_TSHUT_EINT 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_MASK 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_SHIFT 0 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_WIDTH 1 /* IM_TSHUT_EINT */ + +/* + * R28 (0x1C) - Interrupt Polarity + */ +#define WM9081_TSHUT_INV 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_MASK 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_SHIFT 0 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_WIDTH 1 /* TSHUT_INV */ + +/* + * R29 (0x1D) - Interrupt Control + */ +#define WM9081_IRQ_POL 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_MASK 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_SHIFT 15 /* IRQ_POL */ +#define WM9081_IRQ_POL_WIDTH 1 /* IRQ_POL */ +#define WM9081_IRQ_OP_CTRL 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_MASK 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_SHIFT 0 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_WIDTH 1 /* IRQ_OP_CTRL */ + +/* + * R30 (0x1E) - DAC Digital 1 + */ +#define WM9081_DAC_VOL_MASK 0x00FF /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_SHIFT 0 /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_WIDTH 8 /* DAC_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital 2 + */ +#define WM9081_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM9081_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R32 (0x20) - DRC 1 + */ +#define WM9081_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM9081_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM9081_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_FF_DLY 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_MASK 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_SHIFT 5 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_WIDTH 1 /* DRC_FF_DLY */ +#define WM9081_DRC_QR 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_MASK 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_SHIFT 2 /* DRC_QR */ +#define WM9081_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM9081_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ + +/* + * R33 (0x21) - DRC 2 + */ +#define WM9081_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R34 (0x22) - DRC 3 + */ +#define WM9081_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R35 (0x23) - DRC 4 + */ +#define WM9081_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R38 (0x26) - Write Sequencer 1 + */ +#define WM9081_WSEQ_ENA 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9081_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9081_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9081_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9081_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R39 (0x27) - Write Sequencer 2 + */ +#define WM9081_WSEQ_CURRENT_INDEX_MASK 0x07F0 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R40 (0x28) - MW Slave 1 + */ +#define WM9081_SPI_CFG 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_MASK 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_SHIFT 5 /* SPI_CFG */ +#define WM9081_SPI_CFG_WIDTH 1 /* SPI_CFG */ +#define WM9081_SPI_4WIRE 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_MASK 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_SHIFT 4 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */ +#define WM9081_ARA_ENA 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_MASK 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_SHIFT 3 /* ARA_ENA */ +#define WM9081_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM9081_AUTO_INC 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_MASK 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_SHIFT 1 /* AUTO_INC */ +#define WM9081_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R42 (0x2A) - EQ 1 + */ +#define WM9081_EQ_B1_GAIN_MASK 0xF800 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_SHIFT 11 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B2_GAIN_MASK 0x07C0 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_SHIFT 6 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B4_GAIN_MASK 0x003E /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_SHIFT 1 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM9081_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R43 (0x2B) - EQ 2 + */ +#define WM9081_EQ_B3_GAIN_MASK 0xF800 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_SHIFT 11 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B5_GAIN_MASK 0x07C0 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_SHIFT 6 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [10:6] */ + +/* + * R44 (0x2C) - EQ 3 + */ +#define WM9081_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R45 (0x2D) - EQ 4 + */ +#define WM9081_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R46 (0x2E) - EQ 5 + */ +#define WM9081_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R47 (0x2F) - EQ 6 + */ +#define WM9081_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R48 (0x30) - EQ 7 + */ +#define WM9081_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R49 (0x31) - EQ 8 + */ +#define WM9081_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R50 (0x32) - EQ 9 + */ +#define WM9081_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R51 (0x33) - EQ 10 + */ +#define WM9081_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R52 (0x34) - EQ 11 + */ +#define WM9081_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R53 (0x35) - EQ 12 + */ +#define WM9081_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R54 (0x36) - EQ 13 + */ +#define WM9081_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R55 (0x37) - EQ 14 + */ +#define WM9081_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R56 (0x38) - EQ 15 + */ +#define WM9081_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R57 (0x39) - EQ 16 + */ +#define WM9081_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R58 (0x3A) - EQ 17 + */ +#define WM9081_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R59 (0x3B) - EQ 18 + */ +#define WM9081_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R60 (0x3C) - EQ 19 + */ +#define WM9081_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R61 (0x3D) - EQ 20 + */ +#define WM9081_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + + +#endif diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index c2d1a7a18fa..fa88b463e71 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -282,14 +282,14 @@ struct snd_soc_dai wm9705_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9705_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SND_SOC_STD_AC97_FMTS, }, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9705_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SND_SOC_STD_AC97_FMTS, }, .ops = &wm9705_dai_ops, }, diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 765cf1e7369..1fd4e88f50c 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -534,13 +534,13 @@ struct snd_soc_dai wm9712_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9712_dai_ops_hifi, }, { @@ -550,7 +550,7 @@ struct snd_soc_dai wm9712_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9712_dai_ops_aux, } }; @@ -585,6 +585,8 @@ static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) } soc_ac97_ops.reset(codec->ac97); + if (soc_ac97_ops.warm_reset) + soc_ac97_ops.warm_reset(codec->ac97); if (ac97_read(codec, 0) != wm9712_reg[0]) goto err; return 0; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 523bad077fa..abed37acf78 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -189,6 +189,26 @@ SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), }; +static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 status, rate; + + BUG_ON(event != SND_SOC_DAPM_PRE_PMD); + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000; + rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF; + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00); + ac97_write(codec, AC97_EXTENDED_MID, status); + + return 0; +} + + /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path using the current @@ -400,7 +420,8 @@ SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), -SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), +SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1, + wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), @@ -689,7 +710,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source) Ndiv = target / source; if ((Ndiv < 5) || (Ndiv > 12)) printk(KERN_WARNING - "WM9713 PLL N value %d out of recommended range!\n", + "WM9713 PLL N value %u out of recommended range!\n", Ndiv); pll_div->n = Ndiv; @@ -936,21 +957,6 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static void wm9713_voiceshutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_codec *codec = dai->codec; - u16 status, rate; - - /* Gracefully shut down the voice interface. */ - status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; - rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF; - ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200); - schedule_timeout_interruptible(msecs_to_jiffies(1)); - ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00); - ac97_write(codec, AC97_EXTENDED_MID, status); -} - static int ac97_hifi_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1019,7 +1025,6 @@ static struct snd_soc_dai_ops wm9713_dai_ops_aux = { static struct snd_soc_dai_ops wm9713_dai_ops_voice = { .hw_params = wm9713_pcm_hw_params, - .shutdown = wm9713_voiceshutdown, .set_clkdiv = wm9713_set_dai_clkdiv, .set_pll = wm9713_set_dai_pll, .set_fmt = wm9713_set_dai_fmt, @@ -1035,13 +1040,13 @@ struct snd_soc_dai wm9713_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9713_dai_ops_hifi, }, { @@ -1051,7 +1056,7 @@ struct snd_soc_dai wm9713_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9713_dai_ops_aux, }, { @@ -1069,6 +1074,7 @@ struct snd_soc_dai wm9713_dai[] = { .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .ops = &wm9713_dai_ops_voice, + .symmetric_rates = 1, }, }; EXPORT_SYMBOL_GPL(wm9713_dai); diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index bd7392c9657..411a710be66 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -10,13 +10,14 @@ config SND_DAVINCI_SOC_I2S tristate config SND_DAVINCI_SOC_EVM - tristate "SoC Audio support for DaVinci EVM" - depends on SND_DAVINCI_SOC && MACH_DAVINCI_EVM + tristate "SoC Audio support for DaVinci DM6446 or DM355 EVM" + depends on SND_DAVINCI_SOC + depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM select SND_DAVINCI_SOC_I2S select SND_SOC_TLV320AIC3X help Say Y if you want to add support for SoC audio on TI - DaVinci EVM platform. + DaVinci DM6446 or DM355 EVM platforms. config SND_DAVINCI_SOC_SFFSDR tristate "SoC Audio support for SFFSDR" diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 9b90b347007..58fd1cbedd8 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -20,7 +20,11 @@ #include <sound/soc-dapm.h> #include <asm/dma.h> -#include <mach/hardware.h> +#include <asm/mach-types.h> + +#include <mach/asp.h> +#include <mach/edma.h> +#include <mach/mux.h> #include "../codecs/tlv320aic3x.h" #include "davinci-pcm.h" @@ -150,7 +154,7 @@ static struct snd_soc_card snd_soc_card_evm = { /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { - .i2c_bus = 0, + .i2c_bus = 1, .i2c_address = 0x1b, }; @@ -161,36 +165,73 @@ static struct snd_soc_device evm_snd_devdata = { .codec_data = &evm_aic3x_setup, }; +/* DM6446 EVM uses ASP0; line-out is a pair of RCA jacks */ static struct resource evm_snd_resources[] = { { - .start = DAVINCI_MCBSP_BASE, - .end = DAVINCI_MCBSP_BASE + SZ_8K - 1, + .start = DAVINCI_ASP0_BASE, + .end = DAVINCI_ASP0_BASE + SZ_8K - 1, .flags = IORESOURCE_MEM, }, }; static struct evm_snd_platform_data evm_snd_data = { - .tx_dma_ch = DM644X_DMACH_MCBSP_TX, - .rx_dma_ch = DM644X_DMACH_MCBSP_RX, + .tx_dma_ch = DAVINCI_DMA_ASP0_TX, + .rx_dma_ch = DAVINCI_DMA_ASP0_RX, +}; + +/* DM335 EVM uses ASP1; line-out is a stereo mini-jack */ +static struct resource dm335evm_snd_resources[] = { + { + .start = DAVINCI_ASP1_BASE, + .end = DAVINCI_ASP1_BASE + SZ_8K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct evm_snd_platform_data dm335evm_snd_data = { + .tx_dma_ch = DAVINCI_DMA_ASP1_TX, + .rx_dma_ch = DAVINCI_DMA_ASP1_RX, }; static struct platform_device *evm_snd_device; static int __init evm_init(void) { + struct resource *resources; + unsigned num_resources; + struct evm_snd_platform_data *data; + int index; int ret; - evm_snd_device = platform_device_alloc("soc-audio", 0); + if (machine_is_davinci_evm()) { + davinci_cfg_reg(DM644X_MCBSP); + + resources = evm_snd_resources; + num_resources = ARRAY_SIZE(evm_snd_resources); + data = &evm_snd_data; + index = 0; + } else if (machine_is_davinci_dm355_evm()) { + /* we don't use ASP1 IRQs, or we'd need to mux them ... */ + davinci_cfg_reg(DM355_EVT8_ASP1_TX); + davinci_cfg_reg(DM355_EVT9_ASP1_RX); + + resources = dm335evm_snd_resources; + num_resources = ARRAY_SIZE(dm335evm_snd_resources); + data = &dm335evm_snd_data; + index = 1; + } else + return -EINVAL; + + evm_snd_device = platform_device_alloc("soc-audio", index); if (!evm_snd_device) return -ENOMEM; platform_set_drvdata(evm_snd_device, &evm_snd_devdata); evm_snd_devdata.dev = &evm_snd_device->dev; - platform_device_add_data(evm_snd_device, &evm_snd_data, - sizeof(evm_snd_data)); + platform_device_add_data(evm_snd_device, data, sizeof(*data)); - ret = platform_device_add_resources(evm_snd_device, evm_snd_resources, - ARRAY_SIZE(evm_snd_resources)); + ret = platform_device_add_resources(evm_snd_device, resources, + num_resources); if (ret) { platform_device_put(evm_snd_device); return ret; diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index ffdb9439d3d..b1ea52fc83c 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -24,6 +24,26 @@ #include "davinci-pcm.h" + +/* + * NOTE: terminology here is confusing. + * + * - This driver supports the "Audio Serial Port" (ASP), + * found on dm6446, dm355, and other DaVinci chips. + * + * - But it labels it a "Multi-channel Buffered Serial Port" + * (McBSP) as on older chips like the dm642 ... which was + * backward-compatible, possibly explaining that confusion. + * + * - OMAP chips have a controller called McBSP, which is + * incompatible with the DaVinci flavor of McBSP. + * + * - Newer DaVinci chips have a controller called McASP, + * incompatible with ASP and with either McBSP. + * + * In short: this uses ASP to implement I2S, not McBSP. + * And it won't be the only DaVinci implemention of I2S. + */ #define DAVINCI_MCBSP_DRR_REG 0x00 #define DAVINCI_MCBSP_DXR_REG 0x04 #define DAVINCI_MCBSP_SPCR_REG 0x08 @@ -421,7 +441,7 @@ static int davinci_i2s_probe(struct platform_device *pdev, { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; - struct snd_soc_dai *cpu_dai = card->dai_link[pdev->id].cpu_dai; + struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai; struct davinci_mcbsp_dev *dev; struct resource *mem, *ioarea; struct evm_snd_platform_data *pdata; @@ -448,7 +468,7 @@ static int davinci_i2s_probe(struct platform_device *pdev, cpu_dai->private_data = dev; - dev->clk = clk_get(&pdev->dev, "McBSPCLK"); + dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) { ret = -ENODEV; goto err_free_mem; @@ -483,7 +503,7 @@ static void davinci_i2s_remove(struct platform_device *pdev, { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; - struct snd_soc_dai *cpu_dai = card->dai_link[pdev->id].cpu_dai; + struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai; struct davinci_mcbsp_dev *dev = cpu_dai->private_data; struct resource *mem; diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 7af3b5b3a53..a0599658848 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -22,6 +22,7 @@ #include <sound/soc.h> #include <asm/dma.h> +#include <mach/edma.h> #include "davinci-pcm.h" @@ -51,7 +52,7 @@ struct davinci_runtime_data { spinlock_t lock; int period; /* current DMA period */ int master_lch; /* Master DMA channel */ - int slave_lch; /* Slave DMA channel */ + int slave_lch; /* linked parameter RAM reload slot */ struct davinci_pcm_dma_params *params; /* DMA params */ }; @@ -90,18 +91,18 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream) dst_bidx = data_type; } - davinci_set_dma_src_params(lch, src, INCR, W8BIT); - davinci_set_dma_dest_params(lch, dst, INCR, W8BIT); - davinci_set_dma_src_index(lch, src_bidx, 0); - davinci_set_dma_dest_index(lch, dst_bidx, 0); - davinci_set_dma_transfer_params(lch, data_type, count, 1, 0, ASYNC); + edma_set_src(lch, src, INCR, W8BIT); + edma_set_dest(lch, dst, INCR, W8BIT); + edma_set_src_index(lch, src_bidx, 0); + edma_set_dest_index(lch, dst_bidx, 0); + edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC); prtd->period++; if (unlikely(prtd->period >= runtime->periods)) prtd->period = 0; } -static void davinci_pcm_dma_irq(int lch, u16 ch_status, void *data) +static void davinci_pcm_dma_irq(unsigned lch, u16 ch_status, void *data) { struct snd_pcm_substream *substream = data; struct davinci_runtime_data *prtd = substream->runtime->private_data; @@ -125,7 +126,7 @@ static int davinci_pcm_dma_request(struct snd_pcm_substream *substream) struct davinci_runtime_data *prtd = substream->runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct davinci_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data; - int tcc = TCC_ANY; + struct edmacc_param p_ram; int ret; if (!dma_data) @@ -134,22 +135,34 @@ static int davinci_pcm_dma_request(struct snd_pcm_substream *substream) prtd->params = dma_data; /* Request master DMA channel */ - ret = davinci_request_dma(prtd->params->channel, prtd->params->name, + ret = edma_alloc_channel(prtd->params->channel, davinci_pcm_dma_irq, substream, - &prtd->master_lch, &tcc, EVENTQ_0); - if (ret) + EVENTQ_0); + if (ret < 0) return ret; + prtd->master_lch = ret; - /* Request slave DMA channel */ - ret = davinci_request_dma(PARAM_ANY, "Link", - NULL, NULL, &prtd->slave_lch, &tcc, EVENTQ_0); - if (ret) { - davinci_free_dma(prtd->master_lch); + /* Request parameter RAM reload slot */ + ret = edma_alloc_slot(EDMA_SLOT_ANY); + if (ret < 0) { + edma_free_channel(prtd->master_lch); return ret; } - - /* Link slave DMA channel in loopback */ - davinci_dma_link_lch(prtd->slave_lch, prtd->slave_lch); + prtd->slave_lch = ret; + + /* Issue transfer completion IRQ when the channel completes a + * transfer, then always reload from the same slot (by a kind + * of loopback link). The completion IRQ handler will update + * the reload slot with a new buffer. + * + * REVISIT save p_ram here after setting up everything except + * the buffer and its length (ccnt) ... use it as a template + * so davinci_pcm_enqueue_dma() takes less time in IRQ. + */ + edma_read_slot(prtd->slave_lch, &p_ram); + p_ram.opt |= TCINTEN | EDMA_TCC(prtd->master_lch); + p_ram.link_bcntrld = prtd->slave_lch << 5; + edma_write_slot(prtd->slave_lch, &p_ram); return 0; } @@ -165,12 +178,12 @@ static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - davinci_start_dma(prtd->master_lch); + edma_start(prtd->master_lch); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - davinci_stop_dma(prtd->master_lch); + edma_stop(prtd->master_lch); break; default: ret = -EINVAL; @@ -185,14 +198,14 @@ static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static int davinci_pcm_prepare(struct snd_pcm_substream *substream) { struct davinci_runtime_data *prtd = substream->runtime->private_data; - struct paramentry_descriptor temp; + struct edmacc_param temp; prtd->period = 0; davinci_pcm_enqueue_dma(substream); - /* Get slave channel dma params for master channel startup */ - davinci_get_dma_params(prtd->slave_lch, &temp); - davinci_set_dma_params(prtd->master_lch, &temp); + /* Copy self-linked parameter RAM entry into master channel */ + edma_read_slot(prtd->slave_lch, &temp); + edma_write_slot(prtd->master_lch, &temp); return 0; } @@ -208,7 +221,7 @@ davinci_pcm_pointer(struct snd_pcm_substream *substream) spin_lock(&prtd->lock); - davinci_dma_getposition(prtd->master_lch, &src, &dst); + edma_get_position(prtd->master_lch, &src, &dst); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) count = src - runtime->dma_addr; else @@ -253,10 +266,10 @@ static int davinci_pcm_close(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct davinci_runtime_data *prtd = runtime->private_data; - davinci_dma_unlink_lch(prtd->slave_lch, prtd->slave_lch); + edma_unlink(prtd->slave_lch); - davinci_free_dma(prtd->slave_lch); - davinci_free_dma(prtd->master_lch); + edma_free_slot(prtd->slave_lch); + edma_free_channel(prtd->master_lch); kfree(prtd); diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 9fc90828337..5dbebf82249 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,5 +1,8 @@ config SND_SOC_OF_SIMPLE tristate + +config SND_MPC52xx_DMA + tristate # ASoC platform support for the Freescale MPC8610 SOC. This compiles drivers # for the SSI and the Elo DMA controller. You will still need to select @@ -22,7 +25,34 @@ config SND_SOC_MPC8610_HPCD config SND_SOC_MPC5200_I2S tristate "Freescale MPC5200 PSC in I2S mode driver" depends on PPC_MPC52xx && PPC_BESTCOMM - select SND_SOC_OF_SIMPLE + select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD help Say Y here to support the MPC5200 PSCs in I2S mode. + +config SND_SOC_MPC5200_AC97 + tristate "Freescale MPC5200 PSC in AC97 mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select AC97_BUS + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in AC97 mode. + +config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE && BROKEN + select SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 + baseboard. + +config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA && BROKEN + select SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. + diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index f85134c8638..a83a73967ec 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -10,5 +10,12 @@ snd-soc-fsl-ssi-objs := fsl_ssi.o snd-soc-fsl-dma-objs := fsl_dma.o obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o +# MPC5200 Platform Support +obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o +obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o + +# MPC5200 Machine Support +obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o +obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c new file mode 100644 index 00000000000..85b0e756950 --- /dev/null +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -0,0 +1,90 @@ +/* + * Efika driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" +#include "../codecs/stac9766.h" + +static struct snd_soc_device device; +static struct snd_soc_card card; + +static struct snd_soc_dai_link efika_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai = &stac9766_dai[STAC9766_DAI_AC97_ANALOG], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL], +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai = &stac9766_dai[STAC9766_DAI_AC97_DIGITAL], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF], +}, +}; + +static __init int efika_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!machine_is_compatible("bplan,efika")) + return -ENODEV; + + card.platform = &mpc5200_audio_dma_platform; + card.name = "Efika"; + card.dai_link = efika_fabric_dai; + card.num_links = ARRAY_SIZE(efika_fabric_dai); + + device.card = &card; + device.codec_dev = &soc_codec_dev_stac9766; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("efika_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &device); + device.dev = &pdev->dev; + + rc = platform_device_add(pdev); + if (rc) { + pr_err("efika_fabric_init: platform_device_add() failed\n"); + return -ENODEV; + } + return 0; +} + +module_init(efika_fabric_init); + + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 3711d8454d9..93f0f38a32c 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -375,18 +375,14 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, struct snd_pcm_runtime *first_runtime = ssi_private->first_stream->runtime; - if (!first_runtime->rate || !first_runtime->sample_bits) { + if (!first_runtime->sample_bits) { dev_err(substream->pcm->card->dev, - "set sample rate and size in %s stream first\n", + "set sample size in %s stream first\n", substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? "capture" : "playback"); return -EAGAIN; } - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - first_runtime->rate, first_runtime->rate); - /* If we're in synchronous mode, then we need to constrain * the sample size as well. We don't support independent sample * rates in asynchronous mode. @@ -674,7 +670,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) ssi_private->dev = ssi_info->dev; ssi_private->asynchronous = ssi_info->asynchronous; - ssi_private->dev->driver_data = fsl_ssi_dai; + dev_set_drvdata(ssi_private->dev, fsl_ssi_dai); /* Initialize the the device_attribute structure */ dev_attr->attr.name = "ssi-stats"; @@ -693,6 +689,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) fsl_ssi_dai->name = ssi_private->name; fsl_ssi_dai->id = ssi_info->id; fsl_ssi_dai->dev = ssi_info->dev; + fsl_ssi_dai->symmetric_rates = 1; ret = snd_soc_register_dai(fsl_ssi_dai); if (ret != 0) { diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c new file mode 100644 index 00000000000..efec33a1c5b --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.c @@ -0,0 +1,564 @@ +/* + * Freescale MPC5200 PSC DMA + * ALSA SoC Platform driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker + */ + +#include <linux/module.h> +#include <linux/of_device.h> + +#include <sound/soc.h> + +#include <sysdev/bestcomm/bestcomm.h> +#include <sysdev/bestcomm/gen_bd.h> +#include <asm/mpc52xx_psc.h> + +#include "mpc5200_dma.h" + +/* + * Interrupt handlers + */ +static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma) +{ + struct psc_dma *psc_dma = _psc_dma; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_dma->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_dma->stats.overrun_count++; + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + return IRQ_HANDLED; +} + +/** + * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->period_next_pt; + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next_pt += s->period_bytes; + if (s->period_next_pt >= s->period_end) + s->period_next_pt = s->period_start; +} + +static void psc_dma_bcom_enqueue_tx(struct psc_dma_stream *s) +{ + while (s->appl_ptr < s->runtime->control->appl_ptr) { + + if (bcom_queue_full(s->bcom_task)) + return; + + s->appl_ptr += s->period_size; + + psc_dma_bcom_enqueue_next_buffer(s); + } +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_dma_bcom_irq_tx(int irq, void *_psc_dma_stream) +{ + struct psc_dma_stream *s = _psc_dma_stream; + + spin_lock(&s->psc_dma->lock); + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + } + psc_dma_bcom_enqueue_tx(s); + spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +static irqreturn_t psc_dma_bcom_irq_rx(int irq, void *_psc_dma_stream) +{ + struct psc_dma_stream *s = _psc_dma_stream; + + spin_lock(&s->psc_dma->lock); + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + + psc_dma_bcom_enqueue_next_buffer(s); + } + spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +static int psc_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_dma_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_dma_stream *s; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; + unsigned long flags; + int i; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + dev_dbg(psc_dma->dev, "psc_dma_trigger(substream=%p, cmd=%i)" + " stream_id=%i\n", + substream, cmd, substream->pstr->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_start = virt_to_phys(runtime->dma_area); + s->period_end = s->period_start + + (s->period_bytes * runtime->periods); + s->period_next_pt = s->period_start; + s->period_current_pt = s->period_start; + s->period_size = runtime->period_size; + s->active = 1; + + /* track appl_ptr so that we have a better chance of detecting + * end of stream and not over running it. + */ + s->runtime = runtime; + s->appl_ptr = s->runtime->control->appl_ptr - + (runtime->period_size * runtime->periods); + + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ + spin_lock_irqsave(&psc_dma->lock, flags); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + bcom_gen_bd_rx_reset(s->bcom_task); + for (i = 0; i < runtime->periods; i++) + if (!bcom_queue_full(s->bcom_task)) + psc_dma_bcom_enqueue_next_buffer(s); + } else { + bcom_gen_bd_tx_reset(s->bcom_task); + psc_dma_bcom_enqueue_tx(s); + } + + bcom_enable(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + s->active = 0; + + spin_lock_irqsave(&psc_dma->lock, flags); + bcom_disable(s->bcom_task); + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + break; + + default: + dev_dbg(psc_dma->dev, "invalid command\n"); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_dma->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_dma->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, psc_dma->imr | imr); + + return 0; +} + + +/* --------------------------------------------------------------------- + * The PSC DMA 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 512, +}; + +static int psc_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; + int rc; + + dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware); + + rc = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) { + dev_err(substream->pcm->card->dev, "invalid buffer size\n"); + return rc; + } + + s->stream = substream; + return 0; +} + +static int psc_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; + + dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + if (!psc_dma->playback.active && + !psc_dma->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ + } + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + count = s->period_current_pt - s->period_start; + + return bytes_to_frames(substream->runtime, count); +} + +static int +psc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static struct snd_pcm_ops psc_dma_ops = { + .open = psc_dma_open, + .close = psc_dma_close, + .hw_free = psc_dma_hw_free, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_dma_pointer, + .trigger = psc_dma_trigger, + .hw_params = psc_dma_hw_params, +}; + +static u64 psc_dma_dmamask = 0xffffffff; +static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + size_t size = psc_dma_hardware.buffer_bytes_max; + int rc = 0; + + dev_dbg(rtd->socdev->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &psc_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[0].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[0].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[1].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[1].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + if (rtd->socdev->card->codec->ac97) + rtd->socdev->card->codec->ac97->private_data = psc_dma; + + return 0; + + capture_alloc_err: + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + + return -ENOMEM; +} + +static void psc_dma_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->socdev->dev, "psc_dma_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +struct snd_soc_platform mpc5200_audio_dma_platform = { + .name = "mpc5200-psc-audio", + .pcm_ops = &psc_dma_ops, + .pcm_new = &psc_dma_new, + .pcm_free = &psc_dma_free, +}; +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_platform); + +int mpc5200_audio_dma_create(struct of_device *op) +{ + phys_addr_t fifo; + struct psc_dma *psc_dma; + struct resource res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->node, 0); + if (of_address_to_resource(op->node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, 1 + res.end - res.start); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); + if (!psc_dma) { + iounmap(regs); + return -ENOMEM; + } + + /* Get the PSC ID */ + prop = of_get_property(op->node, "cell-index", &size); + if (!prop || size < sizeof *prop) + return -ENODEV; + + spin_lock_init(&psc_dma->lock); + psc_dma->id = be32_to_cpu(*prop); + psc_dma->irq = irq; + psc_dma->psc_regs = regs; + psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; + psc_dma->dev = &op->dev; + psc_dma->playback.psc_dma = psc_dma; + psc_dma->capture.psc_dma = psc_dma; + snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id); + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo); + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_dma); + return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + /* reset receiver */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX); + /* reset transmitter */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX); + /* reset error */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT); + /* reset mode */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1); + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_dma->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); + out_8(&psc_dma->fifo_regs->rfcntl, 0x4); + out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); + out_8(&psc_dma->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_dma->playback.irq = + bcom_get_task_irq(psc_dma->playback.bcom_task); + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + + rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); + rc |= request_irq(psc_dma->capture.irq, + &psc_dma_bcom_irq_rx, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); + rc |= request_irq(psc_dma->playback.irq, + &psc_dma_bcom_irq_tx, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); + if (rc) { + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, + &psc_dma->capture); + free_irq(psc_dma->playback.irq, + &psc_dma->playback); + return -ENODEV; + } + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); + + /* Tell the ASoC OF helpers about it */ + return snd_soc_register_platform(&mpc5200_audio_dma_platform); +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +int mpc5200_audio_dma_destroy(struct of_device *op) +{ + struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n"); + + snd_soc_unregister_platform(&mpc5200_audio_dma_platform); + + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + + /* Release irqs */ + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); + + iounmap(psc_dma->psc_regs); + kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy); + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h new file mode 100644 index 00000000000..2000803f06a --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.h @@ -0,0 +1,80 @@ +/* + * Freescale MPC5200 Audio DMA driver + */ + +#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ +#define __SOUND_SOC_FSL_MPC5200_DMA_H__ + +#define PSC_STREAM_NAME_LEN 32 + +/** + * psc_ac97_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_dma: pointer back to parent psc_dma data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_start: physical address of start of DMA region + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + */ +struct psc_dma_stream { + struct snd_pcm_runtime *runtime; + snd_pcm_uframes_t appl_ptr; + + int active; + struct psc_dma *psc_dma; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + dma_addr_t period_start; + dma_addr_t period_end; + dma_addr_t period_next_pt; + dma_addr_t period_current_pt; + int period_bytes; + int period_size; +}; + +/** + * psc_dma - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_dma { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + spinlock_t lock; + u32 sicr; + uint sysclk; + int imr; + int id; + unsigned int slots; + + /* per-stream data */ + struct psc_dma_stream playback; + struct psc_dma_stream capture; + + /* Statistics */ + struct { + unsigned long overrun_count; + unsigned long underrun_count; + } stats; +}; + +int mpc5200_audio_dma_create(struct of_device *op); +int mpc5200_audio_dma_destroy(struct of_device *op); + +extern struct snd_soc_platform mpc5200_audio_dma_platform; + +#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c new file mode 100644 index 00000000000..794a247b3eb --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -0,0 +1,329 @@ +/* + * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. + * + * Copyright (C) 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/time.h> +#include <asm/delay.h> +#include <asm/mpc52xx_psc.h> + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" + +#define DRV_NAME "mpc5200-psc-ac97" + +/* ALSA only supports a single AC97 device so static is recommend here */ +static struct psc_dma *psc_dma; + +static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + int status; + unsigned int val; + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); + return -ENODEV; + } + /* Send the read */ + out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); + + /* Wait for the answer */ + status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_DATA_VAL), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + + return (unsigned short) val; +} + +static void psc_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int status; + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); + return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +} + +static void psc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +} + +static void psc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Do a cold reset */ + out_8(®s->op1, MPC52xx_PSC_OP_RES); + udelay(10); + out_8(®s->op0, MPC52xx_PSC_OP_RES); + udelay(50); + psc_ac97_warm_reset(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = psc_ac97_read, + .write = psc_ac97_write, + .reset = psc_ac97_cold_reset, + .warm_reset = psc_ac97_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i channels=%i" + " rate=%i format=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params), + params_channels(params), params_rate(params), + params_format(params)); + + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (params_channels(params) == 1) + psc_dma->slots |= 0x00000100; + else + psc_dma->slots |= 0x00000300; + } else { + if (params_channels(params) == 1) + psc_dma->slots |= 0x01000000; + else + psc_dma->slots |= 0x03000000; + } + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + + return 0; +} + +static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + + if (params_channels(params) == 1) + out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000); + else + out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000); + + return 0; +} + +static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + psc_dma->slots &= 0xFFFF0000; + else + psc_dma->slots &= 0x0000FFFF; + + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + } + return 0; +} + +static int psc_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Go */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + return 0; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_ac97_dai_template: template CPU Digital Audio Interface + */ +static struct snd_soc_dai_ops psc_ac97_analog_ops = { + .hw_params = psc_ac97_hw_analog_params, + .trigger = psc_ac97_trigger, +}; + +static struct snd_soc_dai_ops psc_ac97_digital_ops = { + .hw_params = psc_ac97_hw_digital_params, +}; + +struct snd_soc_dai psc_ac97_dai[] = { +{ + .name = "AC97", + .ac97_control = 1, + .probe = psc_ac97_probe, + .playback = { + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .ops = &psc_ac97_analog_ops, +}, +{ + .name = "SPDIF", + .ac97_control = 1, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, + }, + .ops = &psc_ac97_digital_ops, +} }; +EXPORT_SYMBOL_GPL(psc_ac97_dai); + + + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int __devinit psc_ac97_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + int rc, i; + struct snd_ac97 ac97; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++) + psc_ac97_dai[i].dev = &op->dev; + + rc = snd_soc_register_dais(psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + ac97.private_data = psc_dma; + + for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++) + psc_ac97_dai[i].private_data = psc_dma; + + psc_dma->imr = 0; + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + + /* Configure the serial interface mode to AC97 */ + psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97; + out_be32(®s->sicr, psc_dma->sicr); + + /* No slots active */ + out_be32(®s->ac97_slots, 0x00000000); + + return 0; +} + +static int __devexit psc_ac97_of_remove(struct of_device *op) +{ + return mpc5200_audio_dma_destroy(op); +} + +/* Match table for of_platform binding */ +static struct of_device_id psc_ac97_match[] __devinitdata = { + { .compatible = "fsl,mpc5200-psc-ac97", }, + { .compatible = "fsl,mpc5200b-psc-ac97", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_ac97_match); + +static struct of_platform_driver psc_ac97_driver = { + .match_table = psc_ac97_match, + .probe = psc_ac97_of_probe, + .remove = __devexit_p(psc_ac97_of_remove), + .driver = { + .name = "mpc5200-psc-ac97", + .owner = THIS_MODULE, + }, +}; + +/* --------------------------------------------------------------------- + * Module setup and teardown; simply register the of_platform driver + * for the PSC in AC97 mode. + */ +static int __init psc_ac97_init(void) +{ + return of_register_platform_driver(&psc_ac97_driver); +} +module_init(psc_ac97_init); + +static void __exit psc_ac97_exit(void) +{ + of_unregister_platform_driver(&psc_ac97_driver); +} +module_exit(psc_ac97_exit); + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION("mpc5200 AC97 module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc5200_psc_ac97.h b/sound/soc/fsl/mpc5200_psc_ac97.h new file mode 100644 index 00000000000..4bc18c35c36 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.h @@ -0,0 +1,15 @@ +/* + * Freescale MPC5200 PSC in AC97 mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + */ + +#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ +#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ + +extern struct snd_soc_dai psc_ac97_dai[]; + +#define MPC5200_AC97_NORMAL 0 +#define MPC5200_AC97_SPDIF 1 + +#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 1111c710118..ce8de90fb94 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -3,31 +3,21 @@ * ALSA SoC Digital Audio Interface (DAI) driver * * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker */ -#include <linux/init.h> #include <linux/module.h> -#include <linux/interrupt.h> -#include <linux/device.h> -#include <linux/delay.h> #include <linux/of_device.h> #include <linux/of_platform.h> -#include <linux/dma-mapping.h> -#include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> -#include <sound/soc-of-simple.h> -#include <sysdev/bestcomm/bestcomm.h> -#include <sysdev/bestcomm/gen_bd.h> #include <asm/mpc52xx_psc.h> -MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); -MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); -MODULE_LICENSE("GPL"); +#include "mpc5200_psc_i2s.h" +#include "mpc5200_dma.h" /** * PSC_I2S_RATES: sample rates supported by the I2S @@ -44,191 +34,17 @@ MODULE_LICENSE("GPL"); * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode */ #define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ - SNDRV_PCM_FMTBIT_S32_BE) - -/** - * psc_i2s_stream - Data specific to a single stream (playback or capture) - * @active: flag indicating if the stream is active - * @psc_i2s: pointer back to parent psc_i2s data structure - * @bcom_task: bestcomm task structure - * @irq: irq number for bestcomm task - * @period_start: physical address of start of DMA region - * @period_end: physical address of end of DMA region - * @period_next_pt: physical address of next DMA buffer to enqueue - * @period_bytes: size of DMA period in bytes - */ -struct psc_i2s_stream { - int active; - struct psc_i2s *psc_i2s; - struct bcom_task *bcom_task; - int irq; - struct snd_pcm_substream *stream; - dma_addr_t period_start; - dma_addr_t period_end; - dma_addr_t period_next_pt; - dma_addr_t period_current_pt; - int period_bytes; -}; - -/** - * psc_i2s - Private driver data - * @name: short name for this device ("PSC0", "PSC1", etc) - * @psc_regs: pointer to the PSC's registers - * @fifo_regs: pointer to the PSC's FIFO registers - * @irq: IRQ of this PSC - * @dev: struct device pointer - * @dai: the CPU DAI for this device - * @sicr: Base value used in serial interface control register; mode is ORed - * with this value. - * @playback: Playback stream context data - * @capture: Capture stream context data - */ -struct psc_i2s { - char name[32]; - struct mpc52xx_psc __iomem *psc_regs; - struct mpc52xx_psc_fifo __iomem *fifo_regs; - unsigned int irq; - struct device *dev; - struct snd_soc_dai dai; - spinlock_t lock; - u32 sicr; - - /* per-stream data */ - struct psc_i2s_stream playback; - struct psc_i2s_stream capture; - - /* Statistics */ - struct { - int overrun_count; - int underrun_count; - } stats; -}; - -/* - * Interrupt handlers - */ -static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) -{ - struct psc_i2s *psc_i2s = _psc_i2s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; - u16 isr; - - isr = in_be16(®s->mpc52xx_psc_isr); - - /* Playback underrun error */ - if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) - psc_i2s->stats.underrun_count++; - - /* Capture overrun error */ - if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) - psc_i2s->stats.overrun_count++; - - out_8(®s->command, 4 << 4); /* reset the error status */ - - return IRQ_HANDLED; -} - -/** - * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer - * @s: pointer to stream private data structure - * - * Enqueues another audio period buffer into the bestcomm queue. - * - * Note: The routine must only be called when there is space available in - * the queue. Otherwise the enqueue will fail and the audio ring buffer - * will get out of sync - */ -static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) -{ - struct bcom_bd *bd; - - /* Prepare and enqueue the next buffer descriptor */ - bd = bcom_prepare_next_buffer(s->bcom_task); - bd->status = s->period_bytes; - bd->data[0] = s->period_next_pt; - bcom_submit_next_buffer(s->bcom_task, NULL); - - /* Update for next period */ - s->period_next_pt += s->period_bytes; - if (s->period_next_pt >= s->period_end) - s->period_next_pt = s->period_start; -} - -/* Bestcomm DMA irq handler */ -static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) -{ - struct psc_i2s_stream *s = _psc_i2s_stream; - - /* For each finished period, dequeue the completed period buffer - * and enqueue a new one in it's place. */ - while (bcom_buffer_done(s->bcom_task)) { - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); - s->period_current_pt += s->period_bytes; - if (s->period_current_pt >= s->period_end) - s->period_current_pt = s->period_start; - psc_i2s_bcom_enqueue_next_buffer(s); - bcom_enable(s->bcom_task); - } - - /* If the stream is active, then also inform the PCM middle layer - * of the period finished event. */ - if (s->active) - snd_pcm_period_elapsed(s->stream); - - return IRQ_HANDLED; -} - -/** - * psc_i2s_startup: create a new substream - * - * This is the first function called when a stream is opened. - * - * If this is the first stream open, then grab the IRQ and program most of - * the PSC registers. - */ -static int psc_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - int rc; - - dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); - - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { - /* Setup the IRQs */ - rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, - "psc-i2s-status", psc_i2s); - rc |= request_irq(psc_i2s->capture.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-capture", &psc_i2s->capture); - rc |= request_irq(psc_i2s->playback.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-playback", &psc_i2s->playback); - if (rc) { - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, - &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, - &psc_i2s->playback); - return -ENODEV; - } - } - - return 0; -} + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) static int psc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; u32 mode; - dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" " periods=%i buffer_size=%i buffer_bytes=%i\n", __func__, substream, params_period_size(params), params_period_bytes(params), params_periods(params), @@ -248,175 +64,15 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, mode = MPC52xx_PSC_SICR_SIM_CODEC_32; break; default: - dev_dbg(psc_i2s->dev, "invalid format\n"); - return -EINVAL; - } - out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode); - - snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); - - return 0; -} - -static int psc_i2s_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - snd_pcm_set_runtime_buffer(substream, NULL); - return 0; -} - -/** - * psc_i2s_trigger: start and stop the DMA transfer. - * - * This function is called by ALSA to start, stop, pause, and resume the DMA - * transfer of data. - */ -static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct psc_i2s_stream *s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; - u16 imr; - u8 psc_cmd; - unsigned long flags; - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" - " stream_id=%i\n", - substream, cmd, substream->pstr->stream); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - s->period_bytes = frames_to_bytes(runtime, - runtime->period_size); - s->period_start = virt_to_phys(runtime->dma_area); - s->period_end = s->period_start + - (s->period_bytes * runtime->periods); - s->period_next_pt = s->period_start; - s->period_current_pt = s->period_start; - s->active = 1; - - /* First; reset everything */ - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - out_8(®s->command, MPC52xx_PSC_RST_RX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); - } else { - out_8(®s->command, MPC52xx_PSC_RST_TX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); - } - - /* Next, fill up the bestcomm bd queue and enable DMA. - * This will begin filling the PSC's fifo. */ - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - bcom_gen_bd_rx_reset(s->bcom_task); - else - bcom_gen_bd_tx_reset(s->bcom_task); - while (!bcom_queue_full(s->bcom_task)) - psc_i2s_bcom_enqueue_next_buffer(s); - bcom_enable(s->bcom_task); - - /* Due to errata in the i2s mode; need to line up enabling - * the transmitter with a transition on the frame sync - * line */ - - spin_lock_irqsave(&psc_i2s->lock, flags); - /* first make sure it is low */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) - ; - /* then wait for the transition to high */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) - ; - /* Finally, enable the PSC. - * Receiver must always be enabled; even when we only want - * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ - psc_cmd = MPC52xx_PSC_RX_ENABLE; - if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) - psc_cmd |= MPC52xx_PSC_TX_ENABLE; - out_8(®s->command, psc_cmd); - spin_unlock_irqrestore(&psc_i2s->lock, flags); - - break; - - case SNDRV_PCM_TRIGGER_STOP: - /* Turn off the PSC */ - s->active = 0; - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - if (!psc_i2s->playback.active) { - out_8(®s->command, 2 << 4); /* reset rx */ - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - } - } else { - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - if (!psc_i2s->capture.active) - out_8(®s->command, 2 << 4); /* reset rx */ - } - - bcom_disable(s->bcom_task); - while (!bcom_queue_empty(s->bcom_task)) - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); - - break; - - default: - dev_dbg(psc_i2s->dev, "invalid command\n"); + dev_dbg(psc_dma->dev, "invalid format\n"); return -EINVAL; } - - /* Update interrupt enable settings */ - imr = 0; - if (psc_i2s->playback.active) - imr |= MPC52xx_PSC_IMR_TXEMP; - if (psc_i2s->capture.active) - imr |= MPC52xx_PSC_IMR_ORERR; - out_be16(®s->isr_imr.imr, imr); + out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode); return 0; } /** - * psc_i2s_shutdown: shutdown the data transfer on a stream - * - * Shutdown the PSC if there are no other substreams open. - */ -static void psc_i2s_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - - dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); - - /* - * If this is the last active substream, disable the PSC and release - * the IRQ. - */ - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { - - /* Disable all interrupts and reset the PSC */ - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ - - /* Release irqs */ - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, &psc_i2s->playback); - } -} - -/** * psc_i2s_set_sysclk: set the clock frequency and direction * * This function is called by the machine driver to tell us what the clock @@ -433,8 +89,8 @@ static void psc_i2s_shutdown(struct snd_pcm_substream *substream, static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { - struct psc_i2s *psc_i2s = cpu_dai->private_data; - dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + struct psc_dma *psc_dma = cpu_dai->private_data; + dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", cpu_dai, dir); return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; } @@ -452,8 +108,8 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, */ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) { - struct psc_i2s *psc_i2s = cpu_dai->private_data; - dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + struct psc_dma *psc_dma = cpu_dai->private_data; + dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", cpu_dai, format); return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; } @@ -469,16 +125,13 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) * psc_i2s_dai_template: template CPU Digital Audio Interface */ static struct snd_soc_dai_ops psc_i2s_dai_ops = { - .startup = psc_i2s_startup, .hw_params = psc_i2s_hw_params, - .hw_free = psc_i2s_hw_free, - .shutdown = psc_i2s_shutdown, - .trigger = psc_i2s_trigger, .set_sysclk = psc_i2s_set_sysclk, .set_fmt = psc_i2s_set_fmt, }; -static struct snd_soc_dai psc_i2s_dai_template = { +struct snd_soc_dai psc_i2s_dai[] = {{ + .name = "I2S", .playback = { .channels_min = 2, .channels_max = 2, @@ -492,223 +145,8 @@ static struct snd_soc_dai psc_i2s_dai_template = { .formats = PSC_I2S_FORMATS, }, .ops = &psc_i2s_dai_ops, -}; - -/* --------------------------------------------------------------------- - * The PSC I2S 'ASoC platform' driver - * - * Can be referenced by an 'ASoC machine' driver - * This driver only deals with the audio bus; it doesn't have any - * interaction with the attached codec - */ - -static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_BATCH, - .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, - .rate_min = 8000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, - .period_bytes_max = 1024 * 1024, - .period_bytes_min = 32, - .periods_min = 2, - .periods_max = 256, - .buffer_bytes_max = 2 * 1024 * 1024, - .fifo_size = 0, -}; - -static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); - - s->stream = substream; - return 0; -} - -static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - s->stream = NULL; - return 0; -} - -static snd_pcm_uframes_t -psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - dma_addr_t count; - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - count = s->period_current_pt - s->period_start; - - return bytes_to_frames(substream->runtime, count); -} - -static struct snd_pcm_ops psc_i2s_pcm_ops = { - .open = psc_i2s_pcm_open, - .close = psc_i2s_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .pointer = psc_i2s_pcm_pointer, -}; - -static u64 psc_i2s_pcm_dmamask = 0xffffffff; -static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, - struct snd_pcm *pcm) -{ - struct snd_soc_pcm_runtime *rtd = pcm->private_data; - size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; - int rc = 0; - - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", - card, dai, pcm); - - if (!card->dev->dma_mask) - card->dev->dma_mask = &psc_i2s_pcm_dmamask; - if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = 0xffffffff; - - if (pcm->streams[0].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[0].substream->dma_buffer); - if (rc) - goto playback_alloc_err; - } - - if (pcm->streams[1].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[1].substream->dma_buffer); - if (rc) - goto capture_alloc_err; - } - - return 0; - - capture_alloc_err: - if (pcm->streams[0].substream) - snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); - playback_alloc_err: - dev_err(card->dev, "Cannot allocate buffer(s)\n"); - return -ENOMEM; -} - -static void psc_i2s_pcm_free(struct snd_pcm *pcm) -{ - struct snd_soc_pcm_runtime *rtd = pcm->private_data; - struct snd_pcm_substream *substream; - int stream; - - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (substream) { - snd_dma_free_pages(&substream->dma_buffer); - substream->dma_buffer.area = NULL; - substream->dma_buffer.addr = 0; - } - } -} - -struct snd_soc_platform psc_i2s_pcm_soc_platform = { - .name = "mpc5200-psc-audio", - .pcm_ops = &psc_i2s_pcm_ops, - .pcm_new = &psc_i2s_pcm_new, - .pcm_free = &psc_i2s_pcm_free, -}; - -/* --------------------------------------------------------------------- - * Sysfs attributes for debugging - */ - -static ssize_t psc_i2s_status_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); - - return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x " - "tfnum=%i tfstat=0x%.4x\n", - in_be16(&psc_i2s->psc_regs->sr_csr.status), - in_be32(&psc_i2s->psc_regs->sicr), - in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff, - in_be16(&psc_i2s->fifo_regs->rfstat), - in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff, - in_be16(&psc_i2s->fifo_regs->tfstat)); -} - -static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name) -{ - if (strcmp(name, "playback_underrun") == 0) - return &psc_i2s->stats.underrun_count; - if (strcmp(name, "capture_overrun") == 0) - return &psc_i2s->stats.overrun_count; - - return NULL; -} - -static ssize_t psc_i2s_stat_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); - int *attrib; - - attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); - if (!attrib) - return 0; - - return sprintf(buf, "%i\n", *attrib); -} - -static ssize_t psc_i2s_stat_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); - int *attrib; - - attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); - if (!attrib) - return 0; - - *attrib = simple_strtoul(buf, NULL, 0); - return count; -} - -static DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL); -static DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show, - psc_i2s_stat_store); -static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, - psc_i2s_stat_store); +} }; +EXPORT_SYMBOL_GPL(psc_i2s_dai); /* --------------------------------------------------------------------- * OF platform bus binding code: @@ -718,150 +156,65 @@ static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, static int __devinit psc_i2s_of_probe(struct of_device *op, const struct of_device_id *match) { - phys_addr_t fifo; - struct psc_i2s *psc_i2s; - struct resource res; - int size, psc_id, irq, rc; - const __be32 *prop; - void __iomem *regs; - - dev_dbg(&op->dev, "probing psc i2s device\n"); - - /* Get the PSC ID */ - prop = of_get_property(op->node, "cell-index", &size); - if (!prop || size < sizeof *prop) - return -ENODEV; - psc_id = be32_to_cpu(*prop); - - /* Fetch the registers and IRQ of the PSC */ - irq = irq_of_parse_and_map(op->node, 0); - if (of_address_to_resource(op->node, 0, &res)) { - dev_err(&op->dev, "Missing reg property\n"); - return -ENODEV; - } - regs = ioremap(res.start, 1 + res.end - res.start); - if (!regs) { - dev_err(&op->dev, "Could not map registers\n"); - return -ENODEV; - } + int rc; + struct psc_dma *psc_dma; + struct mpc52xx_psc __iomem *regs; - /* Allocate and initialize the driver private data */ - psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL); - if (!psc_i2s) { - iounmap(regs); - return -ENOMEM; - } - spin_lock_init(&psc_i2s->lock); - psc_i2s->irq = irq; - psc_i2s->psc_regs = regs; - psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs; - psc_i2s->dev = &op->dev; - psc_i2s->playback.psc_i2s = psc_i2s; - psc_i2s->capture.psc_i2s = psc_i2s; - snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1); - - /* Fill out the CPU DAI structure */ - memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai); - psc_i2s->dai.private_data = psc_i2s; - psc_i2s->dai.name = psc_i2s->name; - psc_i2s->dai.id = psc_id; - - /* Find the address of the fifo data registers and setup the - * DMA tasks */ - fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); - psc_i2s->capture.bcom_task = - bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); - psc_i2s->playback.bcom_task = - bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); - if (!psc_i2s->capture.bcom_task || - !psc_i2s->playback.bcom_task) { - dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); - iounmap(regs); - kfree(psc_i2s); - return -ENODEV; + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_register_dais(psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); + return 0; } - /* Disable all interrupts and reset the PSC */ - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */ - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */ - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; /* Configure the serial interface mode; defaulting to CODEC8 mode */ - psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL; - if (of_get_property(op->node, "fsl,cellslave", NULL)) - psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | - MPC52xx_PSC_SICR_GENCLK; - out_be32(&psc_i2s->psc_regs->sicr, - psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + out_be32(&psc_dma->psc_regs->sicr, + psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); /* Check for the codec handle. If it is not present then we * are done */ if (!of_get_property(op->node, "codec-handle", NULL)) return 0; - /* Set up mode register; - * First write: RxRdy (FIFO Alarm) generates rx FIFO irq - * Second write: register Normal mode for non loopback - */ - out_8(&psc_i2s->psc_regs->mode, 0); - out_8(&psc_i2s->psc_regs->mode, 0); - - /* Set the TX and RX fifo alarm thresholds */ - out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100); - out_8(&psc_i2s->fifo_regs->rfcntl, 0x4); - out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100); - out_8(&psc_i2s->fifo_regs->tfcntl, 0x7); - - /* Lookup the IRQ numbers */ - psc_i2s->playback.irq = - bcom_get_task_irq(psc_i2s->playback.bcom_task); - psc_i2s->capture.irq = - bcom_get_task_irq(psc_i2s->capture.bcom_task); - - /* Save what we've done so it can be found again later */ - dev_set_drvdata(&op->dev, psc_i2s); - - /* Register the SYSFS files */ - rc = device_create_file(psc_i2s->dev, &dev_attr_status); - rc |= device_create_file(psc_i2s->dev, &dev_attr_capture_overrun); - rc |= device_create_file(psc_i2s->dev, &dev_attr_playback_underrun); - if (rc) - dev_info(psc_i2s->dev, "error creating sysfs files\n"); - - snd_soc_register_platform(&psc_i2s_pcm_soc_platform); - - /* Tell the ASoC OF helpers about it */ - of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node, - &psc_i2s->dai); + /* Due to errata in the dma mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + + /* Go */ + out_8(&psc_dma->psc_regs->command, + MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); return 0; + } static int __devexit psc_i2s_of_remove(struct of_device *op) { - struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev); - - dev_dbg(&op->dev, "psc_i2s_remove()\n"); - - snd_soc_unregister_platform(&psc_i2s_pcm_soc_platform); - - bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task); - bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task); - - iounmap(psc_i2s->psc_regs); - iounmap(psc_i2s->fifo_regs); - kfree(psc_i2s); - dev_set_drvdata(&op->dev, NULL); - - return 0; + return mpc5200_audio_dma_destroy(op); } /* Match table for of_platform binding */ static struct of_device_id psc_i2s_match[] __devinitdata = { { .compatible = "fsl,mpc5200-psc-i2s", }, + { .compatible = "fsl,mpc5200b-psc-i2s", }, {} }; MODULE_DEVICE_TABLE(of, psc_i2s_match); @@ -892,4 +245,7 @@ static void __exit psc_i2s_exit(void) } module_exit(psc_i2s_exit); +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_psc_i2s.h b/sound/soc/fsl/mpc5200_psc_i2s.h new file mode 100644 index 00000000000..ce55e070fdf --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.h @@ -0,0 +1,12 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + */ + +#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ +#define __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ + +extern struct snd_soc_dai psc_i2s_dai[]; + +#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ */ diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c new file mode 100644 index 00000000000..8766f7a3893 --- /dev/null +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -0,0 +1,90 @@ +/* + * Phytec pcm030 driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" +#include "../codecs/wm9712.h" + +static struct snd_soc_device device; +static struct snd_soc_card card; + +static struct snd_soc_dai_link pcm030_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL], +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF], +}, +}; + +static __init int pcm030_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!machine_is_compatible("phytec,pcm030")) + return -ENODEV; + + card.platform = &mpc5200_audio_dma_platform; + card.name = "pcm030"; + card.dai_link = pcm030_fabric_dai; + card.num_links = ARRAY_SIZE(pcm030_fabric_dai); + + device.card = &card; + device.codec_dev = &soc_codec_dev_wm9712; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("pcm030_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &device); + device.dev = &pdev->dev; + + rc = platform_device_add(pdev); + if (rc) { + pr_err("pcm030_fabric_init: platform_device_add() failed\n"); + return -ENODEV; + } + return 0; +} + +module_init(pcm030_fabric_init); + + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 675732e724d..b771238662b 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -39,6 +39,14 @@ config SND_OMAP_SOC_OMAP2EVM help Say Y if you want to add support for SoC audio on the omap2evm board. +config SND_OMAP_SOC_OMAP3EVM + tristate "SoC Audio support for OMAP3EVM board" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for SoC audio on the omap3evm board. + config SND_OMAP_SOC_SDP3430 tristate "SoC Audio support for Texas Instruments SDP3430" depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0c9e4ac3766..a37f4986238 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -10,6 +10,7 @@ snd-soc-n810-objs := n810.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o snd-soc-omap2evm-objs := omap2evm.o +snd-soc-omap3evm-objs := omap3evm.o snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o @@ -18,6 +19,7 @@ obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o obj-$(CONFIG_MACH_OMAP2EVM) += snd-soc-omap2evm.o +obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 91ef17992de..b60b1dfbc43 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -383,10 +383,9 @@ static int __init n810_soc_init(void) clk_set_parent(sys_clkout2_src, func96m_clk); clk_set_rate(sys_clkout2, 12000000); - if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) - BUG(); - if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0) - BUG(); + BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) || + (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0)); + gpio_direction_output(N810_HEADSET_AMP_GPIO, 0); gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 91261428384..a5d46a7b196 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -215,8 +215,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; - int wlen, channels; + int wlen, channels, wpf; unsigned long port; + unsigned int format; if (cpu_class_is_omap1()) { dma = omap1_dma_reqs[bus_id][substream->stream]; @@ -244,18 +245,24 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, return 0; } - channels = params_channels(params); + format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + wpf = channels = params_channels(params); switch (channels) { case 2: - /* Use dual-phase frames */ - regs->rcr2 |= RPHASE; - regs->xcr2 |= XPHASE; + if (format == SND_SOC_DAIFMT_I2S) { + /* Use dual-phase frames */ + regs->rcr2 |= RPHASE; + regs->xcr2 |= XPHASE; + /* Set 1 word per (McBSP) frame for phase1 and phase2 */ + wpf--; + regs->rcr2 |= RFRLEN2(wpf - 1); + regs->xcr2 |= XFRLEN2(wpf - 1); + } case 1: - /* Set 1 word per (McBSP) frame */ - regs->rcr2 |= RFRLEN2(1 - 1); - regs->rcr1 |= RFRLEN1(1 - 1); - regs->xcr2 |= XFRLEN2(1 - 1); - regs->xcr1 |= XFRLEN1(1 - 1); + case 4: + /* Set word per (McBSP) frame for phase1 */ + regs->rcr1 |= RFRLEN1(wpf - 1); + regs->xcr1 |= XFRLEN1(wpf - 1); break; default: /* Unsupported number of channels */ @@ -277,11 +284,12 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } /* Set FS period and length in terms of bit clock periods */ - switch (mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + switch (format) { case SND_SOC_DAIFMT_I2S: - regs->srgr2 |= FPER(wlen * 2 - 1); + regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(wlen - 1); break; + case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(0); @@ -326,6 +334,13 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_DSP_A: + /* 1-bit data delay */ + regs->rcr2 |= RDATDLY(1); + regs->xcr2 |= XDATDLY(1); + /* Invert FS polarity configuration */ + temp_fmt ^= SND_SOC_DAIFMT_NB_IF; + break; case SND_SOC_DAIFMT_DSP_B: /* 0-bit data delay */ regs->rcr2 |= RDATDLY(0); @@ -492,13 +507,13 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .id = (link_id), \ .playback = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 07cf7f46b58..6454e15f7d2 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -87,8 +87,10 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data; int err = 0; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma_data) - return -ENODEV; + return 0; snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); @@ -134,6 +136,11 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) struct omap_pcm_dma_data *dma_data = prtd->dma_data; struct omap_dma_channel_params dma_params; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->dma_data) + return 0; + memset(&dma_params, 0, sizeof(dma_params)); /* * Note: Regardless of interface data formats supported by OMAP McBSP diff --git a/sound/soc/omap/omap2evm.c b/sound/soc/omap/omap2evm.c index 0c2322dcf02..027e1a40f8a 100644 --- a/sound/soc/omap/omap2evm.c +++ b/sound/soc/omap/omap2evm.c @@ -86,7 +86,7 @@ static struct snd_soc_dai_link omap2evm_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap2evm_ops, }; diff --git a/sound/soc/omap/omap3beagle.c b/sound/soc/omap/omap3beagle.c index fd24a4acd2f..b0cff9f33b7 100644 --- a/sound/soc/omap/omap3beagle.c +++ b/sound/soc/omap/omap3beagle.c @@ -41,23 +41,33 @@ static int omap3beagle_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int fmt; int ret; + switch (params_channels(params)) { + case 2: /* Stereo I2S mode */ + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + case 4: /* Four channel TDM mode */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + default: + return -EINVAL; + } + /* Set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; } /* Set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration\n"); return ret; @@ -83,7 +93,7 @@ static struct snd_soc_dai_link omap3beagle_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3beagle_ops, }; diff --git a/sound/soc/omap/omap3evm.c b/sound/soc/omap/omap3evm.c new file mode 100644 index 00000000000..9114c263077 --- /dev/null +++ b/sound/soc/omap/omap3evm.c @@ -0,0 +1,147 @@ +/* + * omap3evm.c -- ALSA SoC support for OMAP3 EVM + * + * Author: Anuj Aggarwal <anuj.aggarwal@ti.com> + * + * Based on sound/soc/omap/beagle.c by Steve Sakoman + * + * Copyright (C) 2008 Texas Instruments, Incorporated + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +static int omap3evm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops omap3evm_ops = { + .hw_params = omap3evm_hw_params, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link omap3evm_dai = { + .name = "TWL4030", + .stream_name = "TWL4030", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .ops = &omap3evm_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_omap3evm = { + .name = "omap3evm", + .platform = &omap_soc_platform, + .dai_link = &omap3evm_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device omap3evm_snd_devdata = { + .card = &snd_soc_omap3evm, + .codec_dev = &soc_codec_dev_twl4030, +}; + +static struct platform_device *omap3evm_snd_device; + +static int __init omap3evm_soc_init(void) +{ + int ret; + + if (!machine_is_omap3evm()) { + pr_err("Not OMAP3 EVM!\n"); + return -ENODEV; + } + pr_info("OMAP3 EVM SoC init\n"); + + omap3evm_snd_device = platform_device_alloc("soc-audio", -1); + if (!omap3evm_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(omap3evm_snd_device, &omap3evm_snd_devdata); + omap3evm_snd_devdata.dev = &omap3evm_snd_device->dev; + *(unsigned int *)omap3evm_dai.cpu_dai->private_data = 1; + + ret = platform_device_add(omap3evm_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(omap3evm_snd_device); + + return ret; +} + +static void __exit omap3evm_soc_exit(void) +{ + platform_device_unregister(omap3evm_snd_device); +} + +module_init(omap3evm_soc_init); +module_exit(omap3evm_soc_exit); + +MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>"); +MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM"); +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index fe282d4ef42..ad219aaf7cb 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -228,14 +228,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = { .name = "PCM1773", .stream_name = "HiFi Out", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_out_ops, .init = omap3pandora_out_init, }, { .name = "TWL4030", .stream_name = "Line/Mic In", .cpu_dai = &omap_mcbsp_dai[1], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_in_ops, .init = omap3pandora_in_init, } diff --git a/sound/soc/omap/overo.c b/sound/soc/omap/overo.c index a72dc4e159e..ec4f8fd8b3a 100644 --- a/sound/soc/omap/overo.c +++ b/sound/soc/omap/overo.c @@ -83,7 +83,7 @@ static struct snd_soc_dai_link overo_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &overo_ops, }; diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index 10f1c867f11..b719e5db4f5 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -84,6 +84,49 @@ static struct snd_soc_ops sdp3430_ops = { .hw_params = sdp3430_hw_params, }; +static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops sdp3430_voice_ops = { + .hw_params = sdp3430_hw_voice_params, +}; + /* Headset jack */ static struct snd_soc_jack hs_jack; @@ -192,28 +235,58 @@ static int sdp3430_twl4030_init(struct snd_soc_codec *codec) return ret; } +static int sdp3430_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + + /* Digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link sdp3430_dai = { - .name = "TWL4030", - .stream_name = "TWL4030", - .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, - .init = sdp3430_twl4030_init, - .ops = &sdp3430_ops, +static struct snd_soc_dai_link sdp3430_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = sdp3430_twl4030_init, + .ops = &sdp3430_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = sdp3430_twl4030_voice_init, + .ops = &sdp3430_voice_ops, + }, }; /* Audio machine driver */ static struct snd_soc_card snd_soc_sdp3430 = { .name = "SDP3430", .platform = &omap_soc_platform, - .dai_link = &sdp3430_dai, - .num_links = 1, + .dai_link = sdp3430_dai, + .num_links = ARRAY_SIZE(sdp3430_dai), +}; + +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 3, + .sysclk = 26000, }; /* Audio subsystem */ static struct snd_soc_device sdp3430_snd_devdata = { .card = &snd_soc_sdp3430, .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, }; static struct platform_device *sdp3430_snd_device; @@ -236,7 +309,8 @@ static int __init sdp3430_soc_init(void) platform_set_drvdata(sdp3430_snd_device, &sdp3430_snd_devdata); sdp3430_snd_devdata.dev = &sdp3430_snd_device->dev; - *(unsigned int *)sdp3430_dai.cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ ret = platform_device_add(sdp3430_snd_device); if (ret) diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index ad8a10fe629..dcd163a4ee9 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -89,13 +89,13 @@ config SND_PXA2XX_SOC_E800 Toshiba e800 PDA config SND_PXA2XX_SOC_EM_X270 - tristate "SoC Audio support for CompuLab EM-x270" + tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300" depends on SND_PXA2XX_SOC && MACH_EM_X270 select SND_PXA2XX_SOC_AC97 select SND_SOC_WM9712 help Say Y if you want to add support for SoC audio on - CompuLab EM-x270. + CompuLab EM-x270, eXeda and CM-X300 machines. config SND_PXA2XX_SOC_PALM27X bool "SoC Audio support for Palm T|X, T5 and LifeDrive" @@ -134,3 +134,12 @@ config SND_PXA2XX_SOC_MIOA701 help Say Y if you want to add support for SoC audio on the MIO A701. + +config SND_PXA2XX_SOC_IMOTE2 + tristate "SoC Audio support for IMote 2" + depends on SND_PXA2XX_SOC && MACH_INTELMOTE2 + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8940 + help + Say Y if you want to add support for SoC audio on the + IMote 2. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 4b90c3ccae4..6e096b48033 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-imote2-objs := imote2.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o @@ -35,3 +36,4 @@ obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o +obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c index 949be9c2a01..f4756e4025f 100644 --- a/sound/soc/pxa/em-x270.c +++ b/sound/soc/pxa/em-x270.c @@ -1,7 +1,7 @@ /* - * em-x270.c -- SoC audio for EM-X270 + * SoC audio driver for EM-X270, eXeda and CM-X300 * - * Copyright 2007 CompuLab, Ltd. + * Copyright 2007, 2009 CompuLab, Ltd. * * Author: Mike Rapoport <mike@compulab.co.il> * @@ -68,7 +68,8 @@ static int __init em_x270_init(void) { int ret; - if (!machine_is_em_x270()) + if (!(machine_is_em_x270() || machine_is_exeda() + || machine_is_cm_x300())) return -ENODEV; em_x270_snd_device = platform_device_alloc("soc-audio", -1); @@ -95,5 +96,5 @@ module_exit(em_x270_exit); /* Module information */ MODULE_AUTHOR("Mike Rapoport"); -MODULE_DESCRIPTION("ALSA SoC EM-X270"); +MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c new file mode 100644 index 00000000000..405587a0116 --- /dev/null +++ b/sound/soc/pxa/imote2.c @@ -0,0 +1,114 @@ + +#include <linux/module.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> + +#include "../codecs/wm8940.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-pcm.h" + +static int imote2_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int clk = 0; + int ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* CPU should be clock master */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk, + SND_SOC_CLOCK_OUT); + + return ret; +} + +static struct snd_soc_ops imote2_asoc_ops = { + .hw_params = imote2_asoc_hw_params, +}; + +static struct snd_soc_dai_link imote2_dai = { + .name = "WM8940", + .stream_name = "WM8940", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8940_dai, + .ops = &imote2_asoc_ops, +}; + +static struct snd_soc_card snd_soc_imote2 = { + .name = "Imote2", + .platform = &pxa2xx_soc_platform, + .dai_link = &imote2_dai, + .num_links = 1, +}; + +static struct snd_soc_device imote2_snd_devdata = { + .card = &snd_soc_imote2, + .codec_dev = &soc_codec_dev_wm8940, +}; + +static struct platform_device *imote2_snd_device; + +static int __init imote2_asoc_init(void) +{ + int ret; + + if (!machine_is_intelmote2()) + return -ENODEV; + imote2_snd_device = platform_device_alloc("soc-audio", -1); + if (!imote2_snd_device) + return -ENOMEM; + + platform_set_drvdata(imote2_snd_device, &imote2_snd_devdata); + imote2_snd_devdata.dev = &imote2_snd_device->dev; + ret = platform_device_add(imote2_snd_device); + if (ret) + platform_device_put(imote2_snd_device); + + return ret; +} +module_init(imote2_asoc_init); + +static void __exit imote2_asoc_exit(void) +{ + platform_device_unregister(imote2_snd_device); +} +module_exit(imote2_asoc_exit); + +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_DESCRIPTION("ALSA SoC Imote 2"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 0625c342a1c..326955dea36 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -106,7 +106,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */ acds = PXA_SSP_CLK_AUDIO_DIV_16; break; - case 32: + default: /* 32 */ /* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */ acds = PXA_SSP_CLK_AUDIO_DIV_8; } @@ -118,7 +118,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_4; break; - case 32: + default: /* 32 */ /* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; } @@ -130,7 +130,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } @@ -142,7 +142,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } @@ -154,19 +154,20 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } break; case 96000: + default: acps = 12235000; switch (width) { case 16: /* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; break; - case 32: + default: /* 32 */ /* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; div4 = PXA_SSP_CLK_SCDB_1; @@ -183,7 +184,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 286be31545d..19c45409d94 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -50,139 +50,6 @@ struct ssp_priv { #endif }; -#define PXA2xx_SSP1_BASE 0x41000000 -#define PXA27x_SSP2_BASE 0x41700000 -#define PXA27x_SSP3_BASE 0x41900000 -#define PXA3xx_SSP4_BASE 0x41a00000 - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_out = { - .name = "SSP1 PCM Mono out", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(14), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_in = { - .name = "SSP1 PCM Mono in", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(13), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_out = { - .name = "SSP1 PCM Stereo out", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(14), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_in = { - .name = "SSP1 PCM Stereo in", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(13), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_out = { - .name = "SSP2 PCM Mono out", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(16), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_in = { - .name = "SSP2 PCM Mono in", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(15), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_out = { - .name = "SSP2 PCM Stereo out", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(16), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_in = { - .name = "SSP2 PCM Stereo in", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(15), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_out = { - .name = "SSP3 PCM Mono out", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_in = { - .name = "SSP3 PCM Mono in", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_out = { - .name = "SSP3 PCM Stereo out", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_in = { - .name = "SSP3 PCM Stereo in", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_out = { - .name = "SSP4 PCM Mono out", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_in = { - .name = "SSP4 PCM Mono in", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_out = { - .name = "SSP4 PCM Stereo out", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_in = { - .name = "SSP4 PCM Stereo in", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - static void dump_registers(struct ssp_device *ssp) { dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n", @@ -194,25 +61,33 @@ static void dump_registers(struct ssp_device *ssp) ssp_read_reg(ssp, SSACD)); } -static struct pxa2xx_pcm_dma_params *ssp_dma_params[4][4] = { - { - &pxa_ssp1_pcm_mono_out, &pxa_ssp1_pcm_mono_in, - &pxa_ssp1_pcm_stereo_out, &pxa_ssp1_pcm_stereo_in, - }, - { - &pxa_ssp2_pcm_mono_out, &pxa_ssp2_pcm_mono_in, - &pxa_ssp2_pcm_stereo_out, &pxa_ssp2_pcm_stereo_in, - }, - { - &pxa_ssp3_pcm_mono_out, &pxa_ssp3_pcm_mono_in, - &pxa_ssp3_pcm_stereo_out, &pxa_ssp3_pcm_stereo_in, - }, - { - &pxa_ssp4_pcm_mono_out, &pxa_ssp4_pcm_mono_in, - &pxa_ssp4_pcm_stereo_out, &pxa_ssp4_pcm_stereo_in, - }, +struct pxa2xx_pcm_dma_data { + struct pxa2xx_pcm_dma_params params; + char name[20]; }; +static struct pxa2xx_pcm_dma_params * +ssp_get_dma_params(struct ssp_device *ssp, int width4, int out) +{ + struct pxa2xx_pcm_dma_data *dma; + + dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL); + if (dma == NULL) + return NULL; + + snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id, + width4 ? "32-bit" : "16-bit", out ? "out" : "in"); + + dma->params.name = dma->name; + dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx); + dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) : + (DCMD_INCTRGADDR | DCMD_FLOWSRC)) | + (width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; + dma->params.dev_addr = ssp->phys_base + SSDR; + + return &dma->params; +} + static int pxa_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -227,6 +102,11 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, clk_enable(priv->dev.ssp->clk); ssp_disable(&priv->dev); } + + if (cpu_dai->dma_data) { + kfree(cpu_dai->dma_data); + cpu_dai->dma_data = NULL; + } return ret; } @@ -241,6 +121,11 @@ static void pxa_ssp_shutdown(struct snd_pcm_substream *substream, ssp_disable(&priv->dev); clk_disable(priv->dev.ssp->clk); } + + if (cpu_dai->dma_data) { + kfree(cpu_dai->dma_data); + cpu_dai->dma_data = NULL; + } } #ifdef CONFIG_PM @@ -323,7 +208,7 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); dev_dbg(&ssp->pdev->dev, - "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d\n", + "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n", cpu_dai->id, clk_id, freq); switch (clk_id) { @@ -472,7 +357,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, ssacd |= (0x6 << 4); dev_dbg(&ssp->pdev->dev, - "Using SSACDD %x to supply %dHz\n", + "Using SSACDD %x to supply %uHz\n", val, freq_out); break; } @@ -589,7 +474,10 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_NB_IF: break; case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(3); + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; break; default: return -EINVAL; @@ -606,7 +494,13 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_NB_NF: sspsp |= SSPSP_SFRMP; break; + case SND_SOC_DAIFMT_NB_IF: + break; case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; break; default: return -EINVAL; @@ -644,25 +538,23 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct ssp_priv *priv = cpu_dai->private_data; struct ssp_device *ssp = priv->dev.ssp; - int dma = 0, chn = params_channels(params); + int chn = params_channels(params); u32 sscr0; u32 sspsp; int width = snd_pcm_format_physical_width(params_format(params)); int ttsa = ssp_read_reg(ssp, SSTSA) & 0xf; - /* select correct DMA params */ - if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) - dma = 1; /* capture DMA offset is 1,3 */ + /* generate correct DMA params */ + if (cpu_dai->dma_data) + kfree(cpu_dai->dma_data); + /* Network mode with one active slot (ttsa == 1) can be used * to force 16-bit frame width on the wire (for S16_LE), even * with two channels. Use 16-bit DMA transfers for this case. */ - if (((chn == 2) && (ttsa != 1)) || (width == 32)) - dma += 2; /* 32-bit DMA offset is 2, 16-bit is 0 */ - - cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma]; - - dev_dbg(&ssp->pdev->dev, "pxa_ssp_hw_params: dma %d\n", dma); + cpu_dai->dma_data = ssp_get_dma_params(ssp, + ((chn == 2) && (ttsa != 1)) || (width == 32), + substream->stream == SNDRV_PCM_STREAM_PLAYBACK); /* we can only change the settings if the port is not in use */ if (ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 2f4b6e489b7..4743e262895 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -106,10 +106,8 @@ static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream, if (IS_ERR(clk_i2s)) return PTR_ERR(clk_i2s); - if (!cpu_dai->active) { - SACR0 |= SACR0_RST; + if (!cpu_dai->active) SACR0 = 0; - } return 0; } @@ -178,9 +176,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, /* is port used by another stream */ if (!(SACR0 & SACR0_ENB)) { - SACR0 = 0; - SACR1 = 0; if (pxa_i2s.master) SACR0 |= SACR0_BCKD; @@ -226,6 +222,10 @@ static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SACR1 &= ~SACR1_DRPL; + else + SACR1 &= ~SACR1_DREC; SACR0 |= SACR0_ENB; break; case SNDRV_PCM_TRIGGER_RESUME: @@ -252,21 +252,16 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, SAIMR &= ~SAIMR_RFS; } - if (SACR1 & (SACR1_DREC | SACR1_DRPL)) { + if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) { SACR0 &= ~SACR0_ENB; pxa_i2s_wait(); clk_disable(clk_i2s); } - - clk_put(clk_i2s); } #ifdef CONFIG_PM static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai) { - if (!dai->active) - return 0; - /* store registers */ pxa_i2s.sacr0 = SACR0; pxa_i2s.sacr1 = SACR1; @@ -281,16 +276,14 @@ static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai) static int pxa2xx_i2s_resume(struct snd_soc_dai *dai) { - if (!dai->active) - return 0; - pxa_i2s_wait(); - SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB; + SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB; SACR1 = pxa_i2s.sacr1; SAIMR = pxa_i2s.saimr; SADIV = pxa_i2s.sadiv; - SACR0 |= SACR0_ENB; + + SACR0 = pxa_i2s.sacr0; return 0; } @@ -329,6 +322,7 @@ struct snd_soc_dai pxa_i2s_dai = { .rates = PXA2XX_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .ops = &pxa_i2s_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(pxa_i2s_dai); @@ -346,6 +340,19 @@ static int pxa2xx_i2s_probe(struct platform_device *dev) if (ret != 0) clk_put(clk_i2s); + /* + * PXA Developer's Manual: + * If SACR0[ENB] is toggled in the middle of a normal operation, + * the SACR0[RST] bit must also be set and cleared to reset all + * I2S controller registers. + */ + SACR0 = SACR0_RST; + SACR0 = 0; + /* Make sure RPL and REC are disabled */ + SACR1 = SACR1_DRPL | SACR1_DREC; + /* Along with FIFO servicing */ + SAIMR &= ~(SAIMR_RFS | SAIMR_TFS); + return ret; } diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 289fadf60b1..906709e6dd5 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -345,9 +345,11 @@ static void lm4857_write_regs(void) static int lm4857_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - int reg = kcontrol->private_value & 0xFF; - int shift = (kcontrol->private_value >> 8) & 0x0F; - int mask = (kcontrol->private_value >> 16) & 0xFF; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; pr_debug("Entered %s\n", __func__); @@ -358,9 +360,11 @@ static int lm4857_get_reg(struct snd_kcontrol *kcontrol, static int lm4857_set_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - int reg = kcontrol->private_value & 0xFF; - int shift = (kcontrol->private_value >> 8) & 0x0F; - int mask = (kcontrol->private_value >> 16) & 0xFF; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; if (((lm4857_regs[reg] >> shift) & mask) == ucontrol->value.integer.value[0]) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index ab680aac3fc..1a283170ca9 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -37,6 +37,20 @@ #include "s3c-i2s-v2.h" +#undef S3C_IIS_V2_SUPPORTED + +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifdef CONFIG_PLAT_S3C64XX +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifndef S3C_IIS_V2_SUPPORTED +#error Unsupported CPU model +#endif + #define S3C2412_I2S_DEBUG_CON 0 static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) @@ -75,7 +89,7 @@ static inline void dbg_showcon(const char *fn, u32 con) /* Turn on or off the transmission path. */ -void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) +static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) { void __iomem *regs = i2s->regs; u32 fic, con, mod; @@ -105,7 +119,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(con, regs + S3C2412_IISCON); @@ -132,7 +148,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(mod, regs + S3C2412_IISMOD); @@ -143,9 +161,8 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) dbg_showcon(__func__, con); pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); } -EXPORT_SYMBOL_GPL(s3c2412_snd_txctrl); -void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) +static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) { void __iomem *regs = i2s->regs; u32 fic, con, mod; @@ -175,7 +192,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(mod, regs + S3C2412_IISMOD); @@ -199,7 +217,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(con, regs + S3C2412_IISCON); @@ -209,7 +228,6 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) fic = readl(regs + S3C2412_IISFIC); pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); } -EXPORT_SYMBOL_GPL(s3c2412_snd_rxctrl); /* * Wait for the LR signal to allow synchronisation to the L/R clock @@ -266,7 +284,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, */ #define IISMOD_MASTER_MASK (1 << 11) #define IISMOD_SLAVE (1 << 11) -#define IISMOD_MASTER (0x0) +#define IISMOD_MASTER (0 << 11) #endif switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -281,7 +299,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= IISMOD_MASTER; break; default: - pr_debug("unknwon master/slave format\n"); + pr_err("unknwon master/slave format\n"); return -EINVAL; } @@ -298,7 +316,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= S3C2412_IISMOD_SDF_IIS; break; default: - pr_debug("Unknown data format\n"); + pr_err("Unknown data format\n"); return -EINVAL; } @@ -327,6 +345,7 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: iismod |= S3C2412_IISMOD_8BIT; @@ -335,6 +354,25 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod &= ~S3C2412_IISMOD_8BIT; break; } +#endif + +#ifdef CONFIG_PLAT_S3C64XX + iismod &= ~0x606; + /* Sample size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + /* 8 bit sample, 16fs BCLK */ + iismod |= 0x2004; + break; + case SNDRV_PCM_FORMAT_S16_LE: + /* 16 bit sample, 32fs BCLK */ + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* 24 bit sample, 48fs BCLK */ + iismod |= 0x4002; + break; + } +#endif writel(iismod, i2s->regs + S3C2412_IISMOD); pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); @@ -489,6 +527,8 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, unsigned int best_rate = 0; unsigned int best_deviation = INT_MAX; + pr_debug("Input clock rate %ldHz\n", clkrate); + if (fstab == NULL) fstab = iis_fs_tab; @@ -507,7 +547,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, actual = clkrate / (fsdiv * div); deviation = actual - rate; - printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n", + printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n", fsdiv, div, actual, deviation); deviation = abs(deviation); @@ -523,7 +563,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, break; } - printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n", + printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n", best_fs, best_div, best_rate); info->fs_div = best_fs; @@ -539,12 +579,31 @@ int s3c_i2sv2_probe(struct platform_device *pdev, unsigned long base) { struct device *dev = &pdev->dev; + unsigned int iismod; i2s->dev = dev; /* record our i2s structure for later use in the callbacks */ dai->private_data = i2s; + if (!base) { + struct resource *res = platform_get_resource(pdev, + IORESOURCE_MEM, + 0); + if (!res) { + dev_err(dev, "Unable to get register resource\n"); + return -ENXIO; + } + + if (!request_mem_region(res->start, resource_size(res), + "s3c64xx-i2s-v4")) { + dev_err(dev, "Unable to request register region\n"); + return -EBUSY; + } + + base = res->start; + } + i2s->regs = ioremap(base, 0x100); if (i2s->regs == NULL) { dev_err(dev, "cannot ioremap registers\n"); @@ -560,12 +619,16 @@ int s3c_i2sv2_probe(struct platform_device *pdev, clk_enable(i2s->iis_pclk); + /* Mark ourselves as in TXRX mode so we can run through our cleanup + * process without warnings. */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + iismod |= S3C2412_IISMOD_MODE_TXRX; + writel(iismod, i2s->regs + S3C2412_IISMOD); s3c2412_snd_txctrl(i2s, 0); s3c2412_snd_rxctrl(i2s, 0); return 0; } - EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); #ifdef CONFIG_PM diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index b7e0b3f0bfc..168a088ba76 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -120,7 +120,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk"); if (s3c2412_i2s.iis_cclk == NULL) { - pr_debug("failed to get i2sclk clock\n"); + pr_err("failed to get i2sclk clock\n"); iounmap(s3c2412_i2s.regs); return -ENODEV; } diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 33c5de7e255..3c06c401d0f 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -108,48 +108,19 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, return 0; } - -unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai) +struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai) { struct s3c_i2sv2_info *i2s = to_info(dai); - return clk_get_rate(i2s->iis_cclk); + return i2s->iis_cclk; } -EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate); +EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock); static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - struct device *dev = &pdev->dev; - struct s3c_i2sv2_info *i2s; - int ret; - - dev_dbg(dev, "%s: probing dai %d\n", __func__, pdev->id); - - if (pdev->id < 0 || pdev->id > ARRAY_SIZE(s3c64xx_i2s)) { - dev_err(dev, "id %d out of range\n", pdev->id); - return -EINVAL; - } - - i2s = &s3c64xx_i2s[pdev->id]; - - ret = s3c_i2sv2_probe(pdev, dai, i2s, - pdev->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0); - if (ret) - return ret; - - i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; - i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; - - i2s->iis_cclk = clk_get(dev, "audio-bus"); - if (IS_ERR(i2s->iis_cclk)) { - dev_err(dev, "failed to get audio-bus"); - iounmap(i2s->regs); - return -ENODEV; - } - /* configure GPIO for i2s port */ - switch (pdev->id) { + switch (dai->id) { case 0: s3c_gpio_cfgpin(S3C64XX_GPD(0), S3C64XX_GPD0_I2S0_CLK); s3c_gpio_cfgpin(S3C64XX_GPD(1), S3C64XX_GPD1_I2S0_CDCLK); @@ -175,41 +146,122 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define S3C64XX_I2S_FMTS \ - (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE) + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE) static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { .set_sysclk = s3c64xx_i2s_set_sysclk, }; -struct snd_soc_dai s3c64xx_i2s_dai = { - .name = "s3c64xx-i2s", - .id = 0, - .probe = s3c64xx_i2s_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = S3C64XX_I2S_RATES, - .formats = S3C64XX_I2S_FMTS, +struct snd_soc_dai s3c64xx_i2s_dai[] = { + { + .name = "s3c64xx-i2s", + .id = 0, + .probe = s3c64xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .ops = &s3c64xx_i2s_dai_ops, + .symmetric_rates = 1, }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = S3C64XX_I2S_RATES, - .formats = S3C64XX_I2S_FMTS, + { + .name = "s3c64xx-i2s", + .id = 1, + .probe = s3c64xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .ops = &s3c64xx_i2s_dai_ops, + .symmetric_rates = 1, }, - .ops = &s3c64xx_i2s_dai_ops, }; EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai); +static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) +{ + struct s3c_i2sv2_info *i2s; + struct snd_soc_dai *dai; + int ret; + + if (pdev->id >= ARRAY_SIZE(s3c64xx_i2s)) { + dev_err(&pdev->dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + i2s = &s3c64xx_i2s[pdev->id]; + dai = &s3c64xx_i2s_dai[pdev->id]; + dai->dev = &pdev->dev; + + i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; + i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; + + i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus\n"); + ret = PTR_ERR(i2s->iis_cclk); + goto err; + } + + ret = s3c_i2sv2_probe(pdev, dai, i2s, 0); + if (ret) + goto err_clk; + + ret = s3c_i2sv2_register_dai(dai); + if (ret != 0) + goto err_i2sv2; + + return 0; + +err_i2sv2: + /* Not implemented for I2Sv2 core yet */ +err_clk: + clk_put(i2s->iis_cclk); +err: + return ret; +} + +static __devexit int s3c64xx_iis_dev_remove(struct platform_device *pdev) +{ + dev_err(&pdev->dev, "Device removal not yet supported\n"); + return 0; +} + +static struct platform_driver s3c64xx_iis_driver = { + .probe = s3c64xx_iis_dev_probe, + .remove = s3c64xx_iis_dev_remove, + .driver = { + .name = "s3c64xx-iis", + .owner = THIS_MODULE, + }, +}; + static int __init s3c64xx_i2s_init(void) { - return s3c_i2sv2_register_dai(&s3c64xx_i2s_dai); + return platform_driver_register(&s3c64xx_iis_driver); } module_init(s3c64xx_i2s_init); static void __exit s3c64xx_i2s_exit(void) { - snd_soc_unregister_dai(&s3c64xx_i2s_dai); + platform_driver_unregister(&s3c64xx_iis_driver); } module_exit(s3c64xx_i2s_exit); @@ -217,6 +269,3 @@ module_exit(s3c64xx_i2s_exit); MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); MODULE_DESCRIPTION("S3C64XX I2S SoC Interface"); MODULE_LICENSE("GPL"); - - - diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index b7ffe3c38b6..02148cee261 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -15,6 +15,8 @@ #ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H #define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__ +struct clk; + #include "s3c-i2s-v2.h" #define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK @@ -24,8 +26,8 @@ #define S3C64XX_CLKSRC_PCLK (0) #define S3C64XX_CLKSRC_MUX (1) -extern struct snd_soc_dai s3c64xx_i2s_dai; +extern struct snd_soc_dai s3c64xx_i2s_dai[]; -extern unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *cpu_dai); +extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai); #endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig new file mode 100644 index 00000000000..c74eb3d4a47 --- /dev/null +++ b/sound/soc/s6000/Kconfig @@ -0,0 +1,19 @@ +config SND_S6000_SOC + tristate "SoC Audio for the Stretch s6000 family" + depends on XTENSA_VARIANT_S6000 + help + Say Y or M if you want to add support for codecs attached to + s6000 family chips. You will also need to select the platform + to support below. + +config SND_S6000_SOC_I2S + tristate + +config SND_S6000_SOC_S6IPCAM + tristate "SoC Audio support for Stretch 6105 IP Camera" + depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105 + select SND_S6000_SOC_I2S + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on the + Stretch s6105 IP Camera Reference Design. diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile new file mode 100644 index 00000000000..7a613612e01 --- /dev/null +++ b/sound/soc/s6000/Makefile @@ -0,0 +1,11 @@ +# s6000 Platform Support +snd-soc-s6000-objs := s6000-pcm.o +snd-soc-s6000-i2s-objs := s6000-i2s.o + +obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o +obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o + +# s6105 Machine Support +snd-soc-s6ipcam-objs := s6105-ipcam.o + +obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c new file mode 100644 index 00000000000..c5cda187eca --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.c @@ -0,0 +1,629 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch S6000 family + * + * Author: Daniel Gloeckner, <dg@emlix.com> + * Copyright: (C) 2009 emlix GmbH <info@emlix.com> + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "s6000-i2s.h" +#include "s6000-pcm.h" + +struct s6000_i2s_dev { + dma_addr_t sifbase; + u8 __iomem *scbbase; + unsigned int wide; + unsigned int channel_in; + unsigned int channel_out; + unsigned int lines_in; + unsigned int lines_out; + struct s6000_pcm_dma_params dma_params; +}; + +#define S6_I2S_INTERRUPT_STATUS 0x00 +#define S6_I2S_INT_OVERRUN 1 +#define S6_I2S_INT_UNDERRUN 2 +#define S6_I2S_INT_ALIGNMENT 4 +#define S6_I2S_INTERRUPT_ENABLE 0x04 +#define S6_I2S_INTERRUPT_RAW 0x08 +#define S6_I2S_INTERRUPT_CLEAR 0x0C +#define S6_I2S_INTERRUPT_SET 0x10 +#define S6_I2S_MODE 0x20 +#define S6_I2S_DUAL 0 +#define S6_I2S_WIDE 1 +#define S6_I2S_TX_DEFAULT 0x24 +#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c)) +#define S6_I2S_IN 0 +#define S6_I2S_OUT 1 +#define S6_I2S_UNUSED 2 +#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c)) +#define S6_I2S_DIV_MASK 0x001fff +#define S6_I2S_16BIT 0x000000 +#define S6_I2S_20BIT 0x002000 +#define S6_I2S_24BIT 0x004000 +#define S6_I2S_32BIT 0x006000 +#define S6_I2S_BITS_MASK 0x006000 +#define S6_I2S_MEM_16BIT 0x000000 +#define S6_I2S_MEM_32BIT 0x008000 +#define S6_I2S_MEM_MASK 0x008000 +#define S6_I2S_CHANNELS_SHIFT 16 +#define S6_I2S_CHANNELS_MASK 0x030000 +#define S6_I2S_SCK_IN 0x000000 +#define S6_I2S_SCK_OUT 0x040000 +#define S6_I2S_SCK_DIR 0x040000 +#define S6_I2S_WS_IN 0x000000 +#define S6_I2S_WS_OUT 0x080000 +#define S6_I2S_WS_DIR 0x080000 +#define S6_I2S_LEFT_FIRST 0x000000 +#define S6_I2S_RIGHT_FIRST 0x100000 +#define S6_I2S_FIRST 0x100000 +#define S6_I2S_CUR_SCK 0x200000 +#define S6_I2S_CUR_WS 0x400000 +#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c)) +#define S6_I2S_DISABLE_IF 0x02 +#define S6_I2S_ENABLE_IF 0x03 +#define S6_I2S_IS_BUSY 0x04 +#define S6_I2S_DMA_ACTIVE 0x08 +#define S6_I2S_IS_ENABLED 0x10 + +#define S6_I2S_NUM_LINES 4 + +#define S6_I2S_SIF_PORT0 0x0000000 +#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */ + +static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val) +{ + writel(val, dev->scbbase + reg); +} + +static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg) +{ + return readl(dev->scbbase + reg); +} + +static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg, + u32 mask, u32 val) +{ + val ^= s6_i2s_read_reg(dev, reg) & ~mask; + s6_i2s_write_reg(dev, reg, val); +} + +static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel) +{ + int i, j, cur, prev; + + /* + * Wait for WCLK to toggle 5 times before enabling the channel + * s6000 Family Datasheet 3.6.4: + * "At least two cycles of WS must occur between commands + * to disable or enable the interface" + */ + j = 0; + prev = ~S6_I2S_CUR_WS; + for (i = 1000000; --i && j < 6; ) { + cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel)) + & S6_I2S_CUR_WS; + if (prev != cur) { + prev = cur; + j++; + } + } + if (j < 6) + printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n"); + + s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF); +} + +static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel) +{ + s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF); +} + +static void s6000_i2s_start(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data; + int channel; + + channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + dev->channel_out : dev->channel_in; + + s6000_i2s_start_channel(dev, channel); +} + +static void s6000_i2s_stop(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data; + int channel; + + channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + dev->channel_out : dev->channel_in; + + s6000_i2s_stop_channel(dev, channel); +} + +static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + int after) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after) + s6000_i2s_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!after) + s6000_i2s_stop(substream); + } + return 0; +} + +static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev) +{ + unsigned int pending; + pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW); + pending &= S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN; + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending); + + return pending; +} + +static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai) +{ + struct s6000_i2s_dev *dev = cpu_dai->private_data; + unsigned int errors; + unsigned int ret; + + errors = s6000_i2s_int_sources(dev); + if (likely(!errors)) + return 0; + + ret = 0; + if (errors & S6_I2S_INT_ALIGNMENT) + printk(KERN_ERR "s6000-i2s: WCLK misaligned\n"); + if (errors & S6_I2S_INT_UNDERRUN) + ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK; + if (errors & S6_I2S_INT_OVERRUN) + ret |= 1 << SNDRV_PCM_STREAM_CAPTURE; + return ret; +} + +static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev) +{ + int channel; + int n = 50; + for (channel = 0; channel < 2; channel++) { + while (--n >= 0) { + int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel)); + if ((v & S6_I2S_IS_ENABLED) + || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY))) + break; + udelay(20); + } + } + if (n < 0) + printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces"); +} + +static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s6000_i2s_dev *dev = cpu_dai->private_data; + u32 w; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + w = S6_I2S_SCK_IN | S6_I2S_WS_IN; + break; + case SND_SOC_DAIFMT_CBS_CFM: + w = S6_I2S_SCK_OUT | S6_I2S_WS_IN; + break; + case SND_SOC_DAIFMT_CBM_CFS: + w = S6_I2S_SCK_IN | S6_I2S_WS_OUT; + break; + case SND_SOC_DAIFMT_CBS_CFS: + w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + w |= S6_I2S_LEFT_FIRST; + break; + case SND_SOC_DAIFMT_NB_IF: + w |= S6_I2S_RIGHT_FIRST; + break; + default: + return -EINVAL; + } + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0), + S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1), + S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); + + return 0; +} + +static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct s6000_i2s_dev *dev = dai->private_data; + + if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2) + return -EINVAL; + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id), + S6_I2S_DIV_MASK, div / 2 - 1); + return 0; +} + +static int s6000_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev = dai->private_data; + int interf; + u32 w = 0; + + if (dev->wide) + interf = 0; + else { + w |= (((params_channels(params) - 2) / 2) + << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK; + interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ? dev->channel_out : dev->channel_in; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT; + break; + default: + printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n", + params_format(params)); + return -EINVAL; + } + + if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf)) + & S6_I2S_IS_ENABLED) { + printk(KERN_ERR "s6000-i2s: interface already enabled\n"); + return -EBUSY; + } + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf), + S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK, + w); + + return 0; +} + +static int s6000_i2s_dai_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev = dai->private_data; + struct s6000_snd_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + dev->wide = pdata->wide; + dev->channel_in = pdata->channel_in; + dev->channel_out = pdata->channel_out; + dev->lines_in = pdata->lines_in; + dev->lines_out = pdata->lines_out; + + s6_i2s_write_reg(dev, S6_I2S_MODE, + dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL); + + if (dev->wide) { + int i; + + if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES) + return -EINVAL; + + dev->channel_in = 0; + dev->channel_out = 1; + dai->capture.channels_min = 2 * dev->lines_in; + dai->capture.channels_max = dai->capture.channels_min; + dai->playback.channels_min = 2 * dev->lines_out; + dai->playback.channels_max = dai->playback.channels_min; + + for (i = 0; i < dev->lines_out; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT); + + for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), + S6_I2S_UNUSED); + + for (; i < S6_I2S_NUM_LINES; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN); + } else { + unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED}; + + if (dev->lines_in > 1 || dev->lines_out > 1) + return -EINVAL; + + dai->capture.channels_min = 2 * dev->lines_in; + dai->capture.channels_max = 8 * dev->lines_in; + dai->playback.channels_min = 2 * dev->lines_out; + dai->playback.channels_max = 8 * dev->lines_out; + + if (dev->lines_in) + cfg[dev->channel_in] = S6_I2S_IN; + if (dev->lines_out) + cfg[dev->channel_out] = S6_I2S_OUT; + + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]); + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]); + } + + if (dev->lines_out) { + if (dev->lines_in) { + if (!dev->dma_params.dma_out) + return -ENODEV; + } else { + dev->dma_params.dma_out = dev->dma_params.dma_in; + dev->dma_params.dma_in = 0; + } + } + dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ? + S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); + dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ? + S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); + dev->dma_params.same_rate = pdata->same_rate | pdata->wide; + return 0; +} + +#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \ + SNDRV_PCM_RATE_8000_192000) +#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops s6000_i2s_dai_ops = { + .set_fmt = s6000_i2s_set_dai_fmt, + .set_clkdiv = s6000_i2s_set_clkdiv, + .hw_params = s6000_i2s_hw_params, +}; + +struct snd_soc_dai s6000_i2s_dai = { + .name = "s6000-i2s", + .id = 0, + .probe = s6000_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 8, + .formats = S6000_I2S_FORMATS, + .rates = S6000_I2S_RATES, + .rate_min = 0, + .rate_max = 1562500, + }, + .capture = { + .channels_min = 2, + .channels_max = 8, + .formats = S6000_I2S_FORMATS, + .rates = S6000_I2S_RATES, + .rate_min = 0, + .rate_max = 1562500, + }, + .ops = &s6000_i2s_dai_ops, +} +EXPORT_SYMBOL_GPL(s6000_i2s_dai); + +static int __devinit s6000_i2s_probe(struct platform_device *pdev) +{ + struct s6000_i2s_dev *dev; + struct resource *scbmem, *sifmem, *region, *dma1, *dma2; + u8 __iomem *mmio; + int ret; + + scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!scbmem) { + dev_err(&pdev->dev, "no mem resource?\n"); + ret = -ENODEV; + goto err_release_none; + } + + region = request_mem_region(scbmem->start, + scbmem->end - scbmem->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S SCB region already claimed\n"); + ret = -EBUSY; + goto err_release_none; + } + + mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1); + if (!mmio) { + dev_err(&pdev->dev, "can't ioremap SCB region\n"); + ret = -ENOMEM; + goto err_release_scb; + } + + sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!sifmem) { + dev_err(&pdev->dev, "no second mem resource?\n"); + ret = -ENODEV; + goto err_release_map; + } + + region = request_mem_region(sifmem->start, + sifmem->end - sifmem->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S SIF region already claimed\n"); + ret = -EBUSY; + goto err_release_map; + } + + dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dma1) { + dev_err(&pdev->dev, "no dma resource?\n"); + ret = -ENODEV; + goto err_release_sif; + } + + region = request_mem_region(dma1->start, dma1->end - dma1->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_sif; + } + + dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (dma2) { + region = request_mem_region(dma2->start, + dma2->end - dma2->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, + "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_dma1; + } + } + + dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_release_dma2; + } + + s6000_i2s_dai.dev = &pdev->dev; + s6000_i2s_dai.private_data = dev; + s6000_i2s_dai.dma_data = &dev->dma_params; + + dev->sifbase = sifmem->start; + dev->scbbase = mmio; + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, + S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN); + + s6000_i2s_stop_channel(dev, 0); + s6000_i2s_stop_channel(dev, 1); + s6000_i2s_wait_disabled(dev); + + dev->dma_params.check_xrun = s6000_i2s_check_xrun; + dev->dma_params.trigger = s6000_i2s_trigger; + dev->dma_params.dma_in = dma1->start; + dev->dma_params.dma_out = dma2 ? dma2->start : 0; + dev->dma_params.irq = platform_get_irq(pdev, 0); + if (dev->dma_params.irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_release_dev; + } + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, + S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN); + + ret = snd_soc_register_dai(&s6000_i2s_dai); + if (ret) + goto err_release_dev; + + return 0; + +err_release_dev: + kfree(dev); +err_release_dma2: + if (dma2) + release_mem_region(dma2->start, dma2->end - dma2->start + 1); +err_release_dma1: + release_mem_region(dma1->start, dma1->end - dma1->start + 1); +err_release_sif: + release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1); +err_release_map: + iounmap(mmio); +err_release_scb: + release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1); +err_release_none: + return ret; +} + +static void __devexit s6000_i2s_remove(struct platform_device *pdev) +{ + struct s6000_i2s_dev *dev = s6000_i2s_dai.private_data; + struct resource *region; + void __iomem *mmio = dev->scbbase; + + snd_soc_unregister_dai(&s6000_i2s_dai); + + s6000_i2s_stop_channel(dev, 0); + s6000_i2s_stop_channel(dev, 1); + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); + s6000_i2s_dai.private_data = 0; + kfree(dev); + + region = platform_get_resource(pdev, IORESOURCE_DMA, 0); + release_mem_region(region->start, region->end - region->start + 1); + + region = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (region) + release_mem_region(region->start, + region->end - region->start + 1); + + region = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(region->start, (region->end - region->start) + 1); + + iounmap(mmio); + region = platform_get_resource(pdev, IORESOURCE_IO, 0); + release_mem_region(region->start, (region->end - region->start) + 1); +} + +static struct platform_driver s6000_i2s_driver = { + .probe = s6000_i2s_probe, + .remove = __devexit_p(s6000_i2s_remove), + .driver = { + .name = "s6000-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init s6000_i2s_init(void) +{ + return platform_driver_register(&s6000_i2s_driver); +} +module_init(s6000_i2s_init); + +static void __exit s6000_i2s_exit(void) +{ + platform_driver_unregister(&s6000_i2s_driver); +} +module_exit(s6000_i2s_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.h new file mode 100644 index 00000000000..2375fdfe6db --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.h @@ -0,0 +1,25 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch s6000 family + * + * Author: Daniel Gloeckner, <dg@emlix.com> + * Copyright: (C) 2009 emlix GmbH <info@emlix.com> + * + * 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. + */ + +#ifndef _S6000_I2S_H +#define _S6000_I2S_H + +extern struct snd_soc_dai s6000_i2s_dai; + +struct s6000_snd_platform_data { + int lines_in; + int lines_out; + int channel_in; + int channel_out; + int wide; + int same_rate; +}; +#endif diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c new file mode 100644 index 00000000000..83b8028e209 --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.c @@ -0,0 +1,497 @@ +/* + * ALSA PCM interface for the Stetch s6000 family + * + * Author: Daniel Gloeckner, <dg@emlix.com> + * Copyright: (C) 2009 emlix GmbH <info@emlix.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <variant/dmac.h> + +#include "s6000-pcm.h" + +#define S6_PCM_PREALLOCATE_SIZE (96 * 1024) +#define S6_PCM_PREALLOCATE_MAX (2048 * 1024) + +static struct snd_pcm_hardware s6000_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \ + SNDRV_PCM_RATE_8000_192000), + .rate_min = 0, + .rate_max = 1562500, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = 0x7ffffff0, + .period_bytes_min = 16, + .period_bytes_max = 0xfffff0, + .periods_min = 2, + .periods_max = 1024, /* no limit */ + .fifo_size = 0, +}; + +struct s6000_runtime_data { + spinlock_t lock; + int period; /* current DMA period */ +}; + +static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + int channel; + unsigned int period_size; + unsigned int dma_offset; + dma_addr_t dma_pos; + dma_addr_t src, dst; + + period_size = snd_pcm_lib_period_bytes(substream); + dma_offset = prtd->period * period_size; + dma_pos = runtime->dma_addr + dma_offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + src = dma_pos; + dst = par->sif_out; + channel = par->dma_out; + } else { + src = par->sif_in; + dst = dma_pos; + channel = par->dma_in; + } + + if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel), + DMA_INDEX_CHNL(channel))) + return; + + if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) { + printk(KERN_ERR "s6000-pcm: fifo full\n"); + return; + } + + BUG_ON(period_size & 15); + s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel), + src, dst, period_size); + + prtd->period++; + if (unlikely(prtd->period >= runtime->periods)) + prtd->period = 0; +} + +static irqreturn_t s6000_pcm_irq(int irq, void *data) +{ + struct snd_pcm *pcm = data; + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + struct s6000_runtime_data *prtd; + unsigned int has_xrun; + int i, ret = IRQ_NONE; + u32 channel[2] = { + [SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out, + [SNDRV_PCM_STREAM_CAPTURE] = params->dma_in + }; + + has_xrun = params->check_xrun(runtime->dai->cpu_dai); + + for (i = 0; i < ARRAY_SIZE(channel); ++i) { + struct snd_pcm_substream *substream = pcm->streams[i].substream; + unsigned int pending; + + if (!channel[i]) + continue; + + if (unlikely(has_xrun & (1 << i)) && + substream->runtime && + snd_pcm_running(substream)) { + dev_dbg(pcm->dev, "xrun\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + } + + pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i])); + + if (pending & 1) { + ret = IRQ_HANDLED; + if (likely(substream->runtime && + snd_pcm_running(substream))) { + snd_pcm_period_elapsed(substream); + dev_dbg(pcm->dev, "period elapsed %x %x\n", + s6dmac_cur_src(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i])), + s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i]))); + prtd = substream->runtime->private_data; + spin_lock(&prtd->lock); + s6000_pcm_enqueue_dma(substream); + spin_unlock(&prtd->lock); + } + } + + if (unlikely(pending & ~7)) { + if (pending & (1 << 3)) + printk(KERN_WARNING + "s6000-pcm: DMA %x Underflow\n", + channel[i]); + if (pending & (1 << 4)) + printk(KERN_WARNING + "s6000-pcm: DMA %x Overflow\n", + channel[i]); + if (pending & 0x1e0) + printk(KERN_WARNING + "s6000-pcm: DMA %x Master Error " + "(mask %x)\n", + channel[i], pending >> 5); + + } + } + + return ret; +} + +static int s6000_pcm_start(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + unsigned long flags; + int srcinc; + u32 dma; + + spin_lock_irqsave(&prtd->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + srcinc = 1; + dma = par->dma_out; + } else { + srcinc = 0; + dma = par->dma_in; + } + s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma), + 1 /* priority 1 (0 is max) */, + 0 /* peripheral requests w/o xfer length mode */, + srcinc /* source address increment */, + srcinc^1 /* destination address increment */, + 0 /* chunksize 0 (skip impossible on this dma) */, + 0 /* source skip after chunk (impossible) */, + 0 /* destination skip after chunk (impossible) */, + 4 /* 16 byte burst size */, + -1 /* don't conserve bandwidth */, + 0 /* low watermark irq descriptor theshold */, + 0 /* disable hardware timestamps */, + 1 /* enable channel */); + + s6000_pcm_enqueue_dma(substream); + s6000_pcm_enqueue_dma(substream); + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int s6000_pcm_stop(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + unsigned long flags; + u32 channel; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + channel = par->dma_out; + else + channel = par->dma_in; + + s6dmac_set_terminal_count(DMA_MASK_DMAC(channel), + DMA_INDEX_CHNL(channel), 0); + + spin_lock_irqsave(&prtd->lock, flags); + + s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel)); + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + int ret; + + ret = par->trigger(substream, cmd, 0); + if (ret < 0) + return ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = s6000_pcm_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = s6000_pcm_stop(substream); + break; + default: + ret = -EINVAL; + } + if (ret < 0) + return ret; + + return par->trigger(substream, cmd, 1); +} + +static int s6000_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + + prtd->period = 0; + + return 0; +} + +static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + unsigned long flags; + unsigned int offset; + dma_addr_t count; + + spin_lock_irqsave(&prtd->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out), + DMA_INDEX_CHNL(par->dma_out)); + else + count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in), + DMA_INDEX_CHNL(par->dma_in)); + + count -= runtime->dma_addr; + + spin_unlock_irqrestore(&prtd->lock, flags); + + offset = bytes_to_frames(runtime, count); + if (unlikely(offset >= runtime->buffer_size)) + offset = 0; + + return offset; +} + +static int s6000_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware); + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16); + if (ret < 0) + return ret; + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); + if (ret < 0) + return ret; + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + if (par->same_rate) { + int rate; + spin_lock(&par->lock); /* needed? */ + rate = par->rate; + spin_unlock(&par->lock); + if (rate != -1) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_RATE, + rate, rate); + if (ret < 0) + return ret; + } + } + + prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + + return 0; +} + +static int s6000_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + + return 0; +} + +static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + int ret; + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) { + printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n"); + return ret; + } + + if (par->same_rate) { + spin_lock(&par->lock); + if (par->rate == -1 || + !(par->in_use & ~(1 << substream->stream))) { + par->rate = params_rate(hw_params); + par->in_use |= 1 << substream->stream; + } else if (params_rate(hw_params) != par->rate) { + snd_pcm_lib_free_pages(substream); + par->in_use &= ~(1 << substream->stream); + ret = -EBUSY; + } + spin_unlock(&par->lock); + } + return ret; +} + +static int s6000_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + + spin_lock(&par->lock); + par->in_use &= ~(1 << substream->stream); + if (!par->in_use) + par->rate = -1; + spin_unlock(&par->lock); + + return snd_pcm_lib_free_pages(substream); +} + +static struct snd_pcm_ops s6000_pcm_ops = { + .open = s6000_pcm_open, + .close = s6000_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = s6000_pcm_hw_params, + .hw_free = s6000_pcm_hw_free, + .trigger = s6000_pcm_trigger, + .prepare = s6000_pcm_prepare, + .pointer = s6000_pcm_pointer, +}; + +static void s6000_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + + free_irq(params->irq, pcm); + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static u64 s6000_pcm_dmamask = DMA_32BIT_MASK; + +static int s6000_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + int res; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &s6000_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (params->dma_in) { + s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in), + DMA_INDEX_CHNL(params->dma_in)); + s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in), + DMA_INDEX_CHNL(params->dma_in)); + } + + if (params->dma_out) { + s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out), + DMA_INDEX_CHNL(params->dma_out)); + s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out), + DMA_INDEX_CHNL(params->dma_out)); + } + + res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED, + s6000_soc_platform.name, pcm); + if (res) { + printk(KERN_ERR "s6000-pcm couldn't get IRQ\n"); + return res; + } + + res = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, + card->dev, + S6_PCM_PREALLOCATE_SIZE, + S6_PCM_PREALLOCATE_MAX); + if (res) + printk(KERN_WARNING "s6000-pcm: preallocation failed\n"); + + spin_lock_init(¶ms->lock); + params->in_use = 0; + params->rate = -1; + return 0; +} + +struct snd_soc_platform s6000_soc_platform = { + .name = "s6000-audio", + .pcm_ops = &s6000_pcm_ops, + .pcm_new = s6000_pcm_new, + .pcm_free = s6000_pcm_free, +}; +EXPORT_SYMBOL_GPL(s6000_soc_platform); + +static int __init s6000_pcm_init(void) +{ + return snd_soc_register_platform(&s6000_soc_platform); +} +module_init(s6000_pcm_init); + +static void __exit s6000_pcm_exit(void) +{ + snd_soc_unregister_platform(&s6000_soc_platform); +} +module_exit(s6000_pcm_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.h new file mode 100644 index 00000000000..96f23f6f52b --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.h @@ -0,0 +1,35 @@ +/* + * ALSA PCM interface for the Stretch s6000 family + * + * Author: Daniel Gloeckner, <dg@emlix.com> + * Copyright: (C) 2009 emlix GmbH <info@emlix.com> + * + * 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. + */ + +#ifndef _S6000_PCM_H +#define _S6000_PCM_H + +struct snd_soc_dai; +struct snd_pcm_substream; + +struct s6000_pcm_dma_params { + unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai); + int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after); + dma_addr_t sif_in; + dma_addr_t sif_out; + u32 dma_in; + u32 dma_out; + int irq; + int same_rate; + + spinlock_t lock; + int in_use; + int rate; +}; + +extern struct snd_soc_platform s6000_soc_platform; + +#endif diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c new file mode 100644 index 00000000000..b5f95f9781c --- /dev/null +++ b/sound/soc/s6000/s6105-ipcam.c @@ -0,0 +1,244 @@ +/* + * ASoC driver for Stretch s6105 IP camera platform + * + * Author: Daniel Gloeckner, <dg@emlix.com> + * Copyright: (C) 2009 emlix GmbH <info@emlix.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <variant/dmac.h> + +#include "../codecs/tlv320aic3x.h" +#include "s6000-pcm.h" +#include "s6000-i2s.h" + +#define S6105_CAM_CODEC_CLOCK 12288000 + +static int s6105_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + /* set the codec system clock */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops s6105_ops = { + .hw_params = s6105_hw_params, +}; + +/* s6105 machine dapm widgets */ +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out Differential", NULL), + SND_SOC_DAPM_LINE("Audio Out Stereo", NULL), + SND_SOC_DAPM_LINE("Audio In", NULL), +}; + +/* s6105 machine audio_mapnections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Audio Out connected to HPLOUT, HPLCOM, HPROUT */ + {"Audio Out Differential", NULL, "HPLOUT"}, + {"Audio Out Differential", NULL, "HPLCOM"}, + {"Audio Out Stereo", NULL, "HPLOUT"}, + {"Audio Out Stereo", NULL, "HPROUT"}, + + /* Audio In connected to LINE1L, LINE1R */ + {"LINE1L", NULL, "Audio In"}, + {"LINE1R", NULL, "Audio In"}, +}; + +static int output_type_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item) { + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT"); + } else { + strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM"); + } + return 0; +} + +static int output_type_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kcontrol->private_value; + return 0; +} + +static int output_type_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + unsigned int val = (ucontrol->value.enumerated.item[0] != 0); + char *differential = "Audio Out Differential"; + char *stereo = "Audio Out Stereo"; + + if (kcontrol->private_value == val) + return 0; + kcontrol->private_value = val; + snd_soc_dapm_disable_pin(codec, val ? differential : stereo); + snd_soc_dapm_sync(codec); + snd_soc_dapm_enable_pin(codec, val ? stereo : differential); + snd_soc_dapm_sync(codec); + + return 1; +} + +static const struct snd_kcontrol_new audio_out_mux = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Output Mux", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = output_type_info, + .get = output_type_get, + .put = output_type_put, + .private_value = 1 /* default to stereo */ +}; + +/* Logic for a aic3x as connected on the s6105 ip camera ref design */ +static int s6105_aic3x_init(struct snd_soc_codec *codec) +{ + /* Add s6105 specific widgets */ + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); + + /* Set up s6105 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* not present */ + snd_soc_dapm_nc_pin(codec, "MONO_LOUT"); + snd_soc_dapm_nc_pin(codec, "LINE2L"); + snd_soc_dapm_nc_pin(codec, "LINE2R"); + + /* not connected */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); /* LINE2L on this chip */ + snd_soc_dapm_nc_pin(codec, "MIC3R"); /* LINE2R on this chip */ + snd_soc_dapm_nc_pin(codec, "LLOUT"); + snd_soc_dapm_nc_pin(codec, "RLOUT"); + snd_soc_dapm_nc_pin(codec, "HPRCOM"); + + /* always connected */ + snd_soc_dapm_enable_pin(codec, "Audio In"); + + /* must correspond to audio_out_mux.private_value initializer */ + snd_soc_dapm_disable_pin(codec, "Audio Out Differential"); + snd_soc_dapm_sync(codec); + snd_soc_dapm_enable_pin(codec, "Audio Out Stereo"); + + snd_soc_dapm_sync(codec); + + snd_ctl_add(codec->card, snd_ctl_new1(&audio_out_mux, codec)); + + return 0; +} + +/* s6105 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link s6105_dai = { + .name = "TLV320AIC31", + .stream_name = "AIC31", + .cpu_dai = &s6000_i2s_dai, + .codec_dai = &aic3x_dai, + .init = s6105_aic3x_init, + .ops = &s6105_ops, +}; + +/* s6105 audio machine driver */ +static struct snd_soc_card snd_soc_card_s6105 = { + .name = "Stretch IP Camera", + .platform = &s6000_soc_platform, + .dai_link = &s6105_dai, + .num_links = 1, +}; + +/* s6105 audio private data */ +static struct aic3x_setup_data s6105_aic3x_setup = { + .i2c_bus = 0, + .i2c_address = 0x18, +}; + +/* s6105 audio subsystem */ +static struct snd_soc_device s6105_snd_devdata = { + .card = &snd_soc_card_s6105, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &s6105_aic3x_setup, +}; + +static struct s6000_snd_platform_data __initdata s6105_snd_data = { + .wide = 0, + .channel_in = 0, + .channel_out = 1, + .lines_in = 1, + .lines_out = 1, + .same_rate = 1, +}; + +static struct platform_device *s6105_snd_device; + +static int __init s6105_init(void) +{ + int ret; + + s6105_snd_device = platform_device_alloc("soc-audio", -1); + if (!s6105_snd_device) + return -ENOMEM; + + platform_set_drvdata(s6105_snd_device, &s6105_snd_devdata); + s6105_snd_devdata.dev = &s6105_snd_device->dev; + platform_device_add_data(s6105_snd_device, &s6105_snd_data, + sizeof(s6105_snd_data)); + + ret = platform_device_add(s6105_snd_device); + if (ret) + platform_device_put(s6105_snd_device); + + return ret; +} + +static void __exit s6105_exit(void) +{ + platform_device_unregister(s6105_snd_device); +} + +module_init(s6105_init); +module_exit(s6105_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c index 56fa0872abb..b378096cadb 100644 --- a/sound/soc/sh/ssi.c +++ b/sound/soc/sh/ssi.c @@ -145,7 +145,7 @@ static int ssi_hw_params(struct snd_pcm_substream *substream, recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); - pr_debug("bits: %d channels: %d\n", bits, channels); + pr_debug("bits: %u channels: %u\n", bits, channels); ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | CR_SWL_MASK); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 99712f652d0..1d70829464e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -113,6 +113,35 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec) } #endif +static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_card *card = socdev->card; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + int ret; + + if (codec_dai->symmetric_rates || cpu_dai->symmetric_rates || + machine->symmetric_rates) { + dev_dbg(card->dev, "Symmetry forces %dHz rate\n", + machine->rate); + + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + machine->rate, + machine->rate); + if (ret < 0) { + dev_err(card->dev, + "Unable to apply rate symmetry constraint: %d\n", ret); + return ret; + } + } + + return 0; +} + /* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls @@ -221,6 +250,13 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto machine_err; } + /* Symmetry only applies if we've already got an active stream. */ + if (cpu_dai->active || codec_dai->active) { + ret = soc_pcm_apply_symmetry(substream); + if (ret != 0) + goto machine_err; + } + pr_debug("asoc: %s <-> %s info:\n", codec_dai->name, cpu_dai->name); pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates); pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min, @@ -263,7 +299,6 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_card *card = container_of(work, struct snd_soc_card, delayed_work.work); - struct snd_soc_device *socdev = card->socdev; struct snd_soc_codec *codec = card->codec; struct snd_soc_dai *codec_dai; int i; @@ -279,27 +314,10 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { - - /* Reduce power if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D1 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - } - codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); - - /* Fall into standby if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D3 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); - } } } mutex_unlock(&pcm_mutex); @@ -363,10 +381,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); - - if (codec->active == 0 && codec_dai->pop_wait == 0) - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); } mutex_unlock(&pcm_mutex); @@ -431,36 +445,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) cancel_delayed_work(&card->delayed_work); } - /* do we need to power up codec */ - if (codec->bias_level != SND_SOC_BIAS_ON) { - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); - - snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON); - snd_soc_dai_digital_mute(codec_dai, 0); - - } else { - /* codec already powered - power on widgets */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_stream_event(codec, + codec_dai->playback.stream_name, + SND_SOC_DAPM_STREAM_START); + else + snd_soc_dapm_stream_event(codec, + codec_dai->capture.stream_name, + SND_SOC_DAPM_STREAM_START); - snd_soc_dai_digital_mute(codec_dai, 0); - } + snd_soc_dai_digital_mute(codec_dai, 0); out: mutex_unlock(&pcm_mutex); @@ -521,6 +515,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, } } + machine->rate = params_rate(params); + out: mutex_unlock(&pcm_mutex); return ret; @@ -632,6 +628,12 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_codec *codec = card->codec; int i; + /* If the initialization of this soc device failed, there is no codec + * associated with it. Just bail out in this case. + */ + if (!codec) + return 0; + /* Due to the resume being scheduled into a workqueue we could * suspend before that's finished - wait for it to complete. */ @@ -954,6 +956,9 @@ static int soc_remove(struct platform_device *pdev) struct snd_soc_platform *platform = card->platform; struct snd_soc_codec_device *codec_dev = socdev->codec_dev; + if (!card->instantiated) + return 0; + run_delayed_work(&card->delayed_work); if (platform->remove) @@ -1331,6 +1336,7 @@ int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid) return ret; } + codec->socdev = socdev; codec->card->dev = socdev->dev; codec->card->private_data = codec; strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver)); @@ -1383,6 +1389,9 @@ int snd_soc_init_card(struct snd_soc_device *socdev) snprintf(codec->card->longname, sizeof(codec->card->longname), "%s (%s)", card->name, codec->name); + /* Make sure all DAPM widgets are instantiated */ + snd_soc_dapm_new_widgets(codec); + ret = snd_card_register(codec->card); if (ret < 0) { printk(KERN_ERR "asoc: failed to register soundcard for %s\n", @@ -1741,7 +1750,7 @@ int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, { int max = kcontrol->private_value; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; @@ -1771,7 +1780,7 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; @@ -1878,7 +1887,7 @@ int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; int max = mc->max; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; @@ -2062,7 +2071,7 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_sysclk) return dai->ops->set_sysclk(dai, clk_id, freq, dir); else return -EINVAL; @@ -2082,7 +2091,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) { - if (dai->ops->set_clkdiv) + if (dai->ops && dai->ops->set_clkdiv) return dai->ops->set_clkdiv(dai, div_id, div); else return -EINVAL; @@ -2101,7 +2110,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, unsigned int freq_in, unsigned int freq_out) { - if (dai->ops->set_pll) + if (dai->ops && dai->ops->set_pll) return dai->ops->set_pll(dai, pll_id, freq_in, freq_out); else return -EINVAL; @@ -2117,7 +2126,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); */ int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - if (dai->ops->set_fmt) + if (dai->ops && dai->ops->set_fmt) return dai->ops->set_fmt(dai, fmt); else return -EINVAL; @@ -2136,7 +2145,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int mask, int slots) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_tdm_slot) return dai->ops->set_tdm_slot(dai, mask, slots); else return -EINVAL; @@ -2152,7 +2161,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); */ int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_tristate) return dai->ops->set_tristate(dai, tristate); else return -EINVAL; @@ -2168,7 +2177,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); */ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute) { - if (dai->ops->digital_mute) + if (dai->ops && dai->ops->digital_mute) return dai->ops->digital_mute(dai, mute); else return -EINVAL; @@ -2349,6 +2358,39 @@ void snd_soc_unregister_platform(struct snd_soc_platform *platform) } EXPORT_SYMBOL_GPL(snd_soc_unregister_platform); +static u64 codec_format_map[] = { + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE, + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE, + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE, + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE, + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE, + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3BE, + SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_U24_3BE, + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE, + SNDRV_PCM_FMTBIT_U20_3LE | SNDRV_PCM_FMTBIT_U20_3BE, + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE, + SNDRV_PCM_FMTBIT_U18_3LE | SNDRV_PCM_FMTBIT_U18_3BE, + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE, + SNDRV_PCM_FMTBIT_FLOAT64_LE | SNDRV_PCM_FMTBIT_FLOAT64_BE, + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE + | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, +}; + +/* Fix up the DAI formats for endianness: codecs don't actually see + * the endianness of the data but we're using the CPU format + * definitions which do need to include endianness so we ensure that + * codec DAIs always have both big and little endian variants set. + */ +static void fixup_codec_formats(struct snd_soc_pcm_stream *stream) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(codec_format_map); i++) + if (stream->formats & codec_format_map[i]) + stream->formats |= codec_format_map[i]; +} + /** * snd_soc_register_codec - Register a codec with the ASoC core * @@ -2356,6 +2398,8 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_platform); */ int snd_soc_register_codec(struct snd_soc_codec *codec) { + int i; + if (!codec->name) return -EINVAL; @@ -2365,6 +2409,11 @@ int snd_soc_register_codec(struct snd_soc_codec *codec) INIT_LIST_HEAD(&codec->list); + for (i = 0; i < codec->num_dai; i++) { + fixup_codec_formats(&codec->dai[i].playback); + fixup_codec_formats(&codec->dai[i].capture); + } + mutex_lock(&client_mutex); list_add(&codec->list, &codec_list); snd_soc_instantiate_cards(); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 735903a7467..21c69074aa1 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -12,7 +12,7 @@ * Features: * o Changes power status of internal codec blocks depending on the * dynamic configuration of codec internal audio paths and active - * DAC's/ADC's. + * DACs/ADCs. * o Platform power domain - can support external components i.e. amps and * mic/meadphone insertion events. * o Automatic Mic Bias support @@ -52,23 +52,21 @@ /* dapm power sequences - make this per codec in the future */ static int dapm_up_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_dac, - snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_pga, - snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post + snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias, + snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux, + snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, + snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, + snd_soc_dapm_post }; static int dapm_down_seq[] = { snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer, snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_post + snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply, + snd_soc_dapm_post }; -static int dapm_status = 1; -module_param(dapm_status, int, 0); -MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries"); - static void pop_wait(u32 pop_time) { if (pop_time) @@ -96,6 +94,48 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL); } +/** + * snd_soc_dapm_set_bias_level - set the bias level for the system + * @socdev: audio device + * @level: level to configure + * + * Configure the bias (power) levels for the SoC audio device. + * + * Returns 0 for success else error. + */ +static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, + enum snd_soc_bias_level level) +{ + struct snd_soc_card *card = socdev->card; + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + dev_dbg(socdev->dev, "Setting full bias\n"); + break; + case SND_SOC_BIAS_PREPARE: + dev_dbg(socdev->dev, "Setting bias prepare\n"); + break; + case SND_SOC_BIAS_STANDBY: + dev_dbg(socdev->dev, "Setting standby bias\n"); + break; + case SND_SOC_BIAS_OFF: + dev_dbg(socdev->dev, "Setting bias off\n"); + break; + default: + dev_err(socdev->dev, "Setting invalid bias %d\n", level); + return -EINVAL; + } + + if (card->set_bias_level) + ret = card->set_bias_level(card, level); + if (ret == 0 && codec->set_bias_level) + ret = codec->set_bias_level(codec, level); + + return ret; +} + /* set up initial codec paths */ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct snd_soc_dapm_path *p, int i) @@ -165,6 +205,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_dac: case snd_soc_dapm_micbias: case snd_soc_dapm_vmid: + case snd_soc_dapm_supply: p->connect = 1; break; /* does effect routing - dynamically connected */ @@ -179,7 +220,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, } } -/* connect mux widget to it's interconnecting audio paths */ +/* connect mux widget to its interconnecting audio paths */ static int dapm_connect_mux(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, struct snd_soc_dapm_path *path, const char *control_name, @@ -202,7 +243,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec, return -ENODEV; } -/* connect mixer widget to it's interconnecting audio paths */ +/* connect mixer widget to its interconnecting audio paths */ static int dapm_connect_mixer(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, struct snd_soc_dapm_path *path, const char *control_name) @@ -357,8 +398,9 @@ static int dapm_new_mixer(struct snd_soc_codec *codec, path->long_name); ret = snd_ctl_add(codec->card, path->kcontrol); if (ret < 0) { - printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n", - path->long_name); + printk(KERN_ERR "asoc: failed to add dapm kcontrol %s: %d\n", + path->long_name, + ret); kfree(path->long_name); path->long_name = NULL; return ret; @@ -434,6 +476,9 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) struct snd_soc_dapm_path *path; int con = 0; + if (widget->id == snd_soc_dapm_supply) + return 0; + if (widget->id == snd_soc_dapm_adc && widget->active) return 1; @@ -470,6 +515,9 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) struct snd_soc_dapm_path *path; int con = 0; + if (widget->id == snd_soc_dapm_supply) + return 0; + /* active stream ? */ if (widget->id == snd_soc_dapm_dac && widget->active) return 1; @@ -521,84 +569,12 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(dapm_reg_event); -/* - * Scan a single DAPM widget for a complete audio path and update the - * power status appropriately. +/* Standard power change method, used to apply power changes to most + * widgets. */ -static int dapm_power_widget(struct snd_soc_codec *codec, int event, - struct snd_soc_dapm_widget *w) +static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w) { - int in, out, power_change, power, ret; - - /* vmid - no action */ - if (w->id == snd_soc_dapm_vmid) - return 0; - - /* active ADC */ - if (w->id == snd_soc_dapm_adc && w->active) { - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - w->power = (in != 0) ? 1 : 0; - dapm_update_bits(w); - return 0; - } - - /* active DAC */ - if (w->id == snd_soc_dapm_dac && w->active) { - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - w->power = (out != 0) ? 1 : 0; - dapm_update_bits(w); - return 0; - } - - /* pre and post event widgets */ - if (w->id == snd_soc_dapm_pre) { - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - return 0; - } - if (w->id == snd_soc_dapm_post) { - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - return 0; - } - - /* all other widgets */ - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - power = (out != 0 && in != 0) ? 1 : 0; - power_change = (w->power == power) ? 0 : 1; - w->power = power; - - if (!power_change) - return 0; + int ret; /* call any power change event handlers */ if (w->event) @@ -607,7 +583,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, w->name, w->event_flags); /* power up pre event */ - if (power && w->event && + if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); if (ret < 0) @@ -615,7 +591,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } /* power down pre event */ - if (!power && w->event && + if (!w->power && w->event && (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); if (ret < 0) @@ -623,17 +599,17 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !power) - dapm_set_pga(w, power); + if (w->id == snd_soc_dapm_pga && !w->power) + dapm_set_pga(w, w->power); dapm_update_bits(w); /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && power) - dapm_set_pga(w, power); + if (w->id == snd_soc_dapm_pga && w->power) + dapm_set_pga(w, w->power); /* power up post event */ - if (power && w->event && + if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMU); @@ -642,7 +618,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } /* power down post event */ - if (!power && w->event && + if (!w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMD)) { ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); if (ret < 0) @@ -652,6 +628,116 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return 0; } +/* Generic check to see if a widget should be powered. + */ +static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) +{ + int in, out; + + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + return out != 0 && in != 0; +} + +/* Check to see if an ADC has power */ +static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) +{ + int in; + + if (w->active) { + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + return in != 0; + } else { + return dapm_generic_check_power(w); + } +} + +/* Check to see if a DAC has power */ +static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) +{ + int out; + + if (w->active) { + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + return out != 0; + } else { + return dapm_generic_check_power(w); + } +} + +/* Check to see if a power supply is needed */ +static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *path; + int power = 0; + + /* Check if one of our outputs is connected */ + list_for_each_entry(path, &w->sinks, list_source) { + if (path->sink && path->sink->power_check && + path->sink->power_check(path->sink)) { + power = 1; + break; + } + } + + dapm_clear_walk(w->codec); + + return power; +} + +/* + * Scan a single DAPM widget for a complete audio path and update the + * power status appropriately. + */ +static int dapm_power_widget(struct snd_soc_codec *codec, int event, + struct snd_soc_dapm_widget *w) +{ + int ret; + + switch (w->id) { + case snd_soc_dapm_pre: + if (!w->event) + return 0; + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + return ret; + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + return ret; + } + return 0; + + case snd_soc_dapm_post: + if (!w->event) + return 0; + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + return ret; + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + } + return 0; + + default: + return dapm_generic_apply_power(w); + } +} + /* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- @@ -663,31 +749,102 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, */ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { + struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; - int i, c = 1, *seq = NULL, ret = 0; - - /* do we have a sequenced stream event */ - if (event == SND_SOC_DAPM_STREAM_START) { - c = ARRAY_SIZE(dapm_up_seq); - seq = dapm_up_seq; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - c = ARRAY_SIZE(dapm_down_seq); - seq = dapm_down_seq; + int ret = 0; + int i, power; + int sys_power = 0; + + INIT_LIST_HEAD(&codec->up_list); + INIT_LIST_HEAD(&codec->down_list); + + /* Check which widgets we need to power and store them in + * lists indicating if they should be powered up or down. + */ + list_for_each_entry(w, &codec->dapm_widgets, list) { + switch (w->id) { + case snd_soc_dapm_pre: + list_add_tail(&codec->down_list, &w->power_list); + break; + case snd_soc_dapm_post: + list_add_tail(&codec->up_list, &w->power_list); + break; + + default: + if (!w->power_check) + continue; + + power = w->power_check(w); + if (power) + sys_power = 1; + + if (w->power == power) + continue; + + if (power) + list_add_tail(&w->power_list, &codec->up_list); + else + list_add_tail(&w->power_list, + &codec->down_list); + + w->power = power; + break; + } } - for (i = 0; i < c; i++) { - list_for_each_entry(w, &codec->dapm_widgets, list) { + /* If we're changing to all on or all off then prepare */ + if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || + (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_PREPARE); + if (ret != 0) + pr_err("Failed to prepare bias: %d\n", ret); + } + /* Power down widgets first; try to avoid amplifying pops. */ + for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { + list_for_each_entry(w, &codec->down_list, power_list) { /* is widget in stream order */ - if (seq && seq[i] && w->id != seq[i]) + if (w->id != dapm_down_seq[i]) continue; ret = dapm_power_widget(codec, event, w); if (ret != 0) - return ret; + pr_err("Failed to power down %s: %d\n", + w->name, ret); } } + /* Now power up. */ + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { + list_for_each_entry(w, &codec->up_list, power_list) { + /* is widget in stream order */ + if (w->id != dapm_up_seq[i]) + continue; + + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power up %s: %d\n", + w->name, ret); + } + } + + /* If we just powered the last thing off drop to standby bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_STANDBY); + if (ret != 0) + pr_err("Failed to apply standby bias: %d\n", ret); + } + + /* If we just powered up then move to active bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_ON); + if (ret != 0) + pr_err("Failed to apply active bias: %d\n", ret); + } + return 0; } @@ -723,6 +880,7 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) case snd_soc_dapm_pga: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: if (w->name) { in = is_connected_input_ep(w); dapm_clear_walk(w->codec); @@ -851,6 +1009,7 @@ static ssize_t dapm_widget_show(struct device *dev, case snd_soc_dapm_pga: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: if (w->name) count += sprintf(buf + count, "%s: %s\n", w->name, w->power ? "On":"Off"); @@ -883,16 +1042,12 @@ static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL); int snd_soc_dapm_sys_add(struct device *dev) { - if (!dapm_status) - return 0; return device_create_file(dev, &dev_attr_dapm_widget); } static void snd_soc_dapm_sys_remove(struct device *dev) { - if (dapm_status) { - device_remove_file(dev, &dev_attr_dapm_widget); - } + device_remove_file(dev, &dev_attr_dapm_widget); } /* free all dapm widgets and resources */ @@ -1015,6 +1170,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: + case snd_soc_dapm_supply: list_add(&path->list, &codec->dapm_paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); @@ -1108,15 +1264,22 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + w->power_check = dapm_generic_check_power; dapm_new_mixer(codec, w); break; case snd_soc_dapm_mux: case snd_soc_dapm_value_mux: + w->power_check = dapm_generic_check_power; dapm_new_mux(codec, w); break; case snd_soc_dapm_adc: + w->power_check = dapm_adc_check_power; + break; case snd_soc_dapm_dac: + w->power_check = dapm_dac_check_power; + break; case snd_soc_dapm_pga: + w->power_check = dapm_generic_check_power; dapm_new_pga(codec, w); break; case snd_soc_dapm_input: @@ -1126,6 +1289,10 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_supply: + w->power_check = dapm_supply_check_power; case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: @@ -1626,35 +1793,11 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); /** - * snd_soc_dapm_set_bias_level - set the bias level for the system - * @socdev: audio device - * @level: level to configure - * - * Configure the bias (power) levels for the SoC audio device. - * - * Returns 0 for success else error. - */ -int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, - enum snd_soc_bias_level level) -{ - struct snd_soc_card *card = socdev->card; - struct snd_soc_codec *codec = socdev->card->codec; - int ret = 0; - - if (card->set_bias_level) - ret = card->set_bias_level(card, level); - if (ret == 0 && codec->set_bias_level) - ret = codec->set_bias_level(codec, level); - - return ret; -} - -/** * snd_soc_dapm_enable_pin - enable pin. * @codec: SoC codec * @pin: pin name * - * Enables input/output pin and it's parents or children widgets iff there is + * Enables input/output pin and its parents or children widgets iff there is * a valid audio route and active audio stream. * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching. @@ -1670,7 +1813,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); * @codec: SoC codec * @pin: pin name * - * Disables input/output pin and it's parents or children widgets. + * Disables input/output pin and its parents or children widgets. * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching. */ diff --git a/sound/soc/txx9/Kconfig b/sound/soc/txx9/Kconfig new file mode 100644 index 00000000000..ebc9327eae7 --- /dev/null +++ b/sound/soc/txx9/Kconfig @@ -0,0 +1,29 @@ +## +## TXx9 ACLC +## +config SND_SOC_TXX9ACLC + tristate "SoC Audio for TXx9" + depends on HAS_TXX9_ACLC && TXX9_DMAC + help + This option enables support for the AC Link Controllers in TXx9 SoC. + +config HAS_TXX9_ACLC + bool + +config SND_SOC_TXX9ACLC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_TXX9ACLC_GENERIC + tristate "Generic TXx9 ACLC sound machine" + depends on SND_SOC_TXX9ACLC + select SND_SOC_TXX9ACLC_AC97 + select SND_SOC_AC97_CODEC + help + This is a generic AC97 sound machine for use in TXx9 based systems. diff --git a/sound/soc/txx9/Makefile b/sound/soc/txx9/Makefile new file mode 100644 index 00000000000..551f16c0c4f --- /dev/null +++ b/sound/soc/txx9/Makefile @@ -0,0 +1,11 @@ +# Platform +snd-soc-txx9aclc-objs := txx9aclc.o +snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o +obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o + +# Machine +snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c new file mode 100644 index 00000000000..0f83bdb9b16 --- /dev/null +++ b/sound/soc/txx9/txx9aclc-ac97.c @@ -0,0 +1,255 @@ +/* + * TXx9 ACLC AC97 driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include "txx9aclc.h" + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_48000 + +#ifdef __BIG_ENDIAN +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE +#else +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE +#endif + +static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq); + +/* REVISIT: How to find txx9aclc_soc_device from snd_ac97? */ +static struct txx9aclc_soc_device *txx9aclc_soc_dev; + +static int txx9aclc_regready(struct txx9aclc_soc_device *dev) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + + return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY; +} + +/* AC97 controller reads codec register */ +static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + u32 dat; + + if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num))) + return 0xffff; + reg |= ac97->num << 7; + dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ; + __raw_writel(dat, base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + dev_err(dev->soc_dev.dev, "ac97 read timeout (reg %#x)\n", reg); + dat = 0xffff; + goto done; + } + dat = __raw_readl(base + ACREGACC); + if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) { + dev_err(dev->soc_dev.dev, "reg mismatch %x with %x\n", + dat, reg); + dat = 0xffff; + goto done; + } + dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff; +done: + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + return dat; +} + +/* AC97 controller writes to codec register */ +static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + + __raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) | + (val << ACREGACC_DAT_SHIFT), + base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { + dev_err(dev->soc_dev.dev, + "ac97 write timeout (reg %#x)\n", reg); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); +} + +static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY; + + __raw_writel(ACCTL_ENLINK, base + ACCTLDIS); + mmiowb(); + udelay(1); + __raw_writel(ACCTL_ENLINK, base + ACCTLEN); + /* wait for primary codec ready status */ + __raw_writel(ready, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, + (__raw_readl(base + ACINTSTS) & ready) == ready, + HZ)) { + dev_err(&ac97->dev, "primary codec is not ready " + "(status %#x)\n", + __raw_readl(base + ACINTSTS)); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTSTS); + __raw_writel(ready, base + ACINTDIS); +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = txx9aclc_ac97_read, + .write = txx9aclc_ac97_write, + .reset = txx9aclc_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id) +{ + struct txx9aclc_plat_drvdata *drvdata = dev_id; + void __iomem *base = drvdata->base; + + __raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS); + wake_up(&ac97_waitq); + return IRQ_HANDLED; +} + +static int txx9aclc_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + + dev->aclc_pdev = to_platform_device(dai->dev); + txx9aclc_soc_dev = dev; + return 0; +} + +static void txx9aclc_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct platform_device *aclc_pdev = to_platform_device(dai->dev); + struct txx9aclc_plat_drvdata *drvdata = platform_get_drvdata(aclc_pdev); + + /* disable AC-link */ + __raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS); + txx9aclc_soc_dev = NULL; +} + +struct snd_soc_dai txx9aclc_ac97_dai = { + .name = "txx9aclc_ac97", + .ac97_control = 1, + .probe = txx9aclc_ac97_probe, + .remove = txx9aclc_ac97_remove, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, +}; +EXPORT_SYMBOL_GPL(txx9aclc_ac97_dai); + +static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev) +{ + struct txx9aclc_plat_drvdata *drvdata; + struct resource *r; + int err; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EBUSY; + + if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), + dev_name(&pdev->dev))) + return -EBUSY; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + drvdata->physbase = r->start; + if (sizeof(drvdata->physbase) > sizeof(r->start) && + r->start >= TXX9_DIRECTMAP_BASE && + r->start < TXX9_DIRECTMAP_BASE + 0x400000) + drvdata->physbase |= 0xf00000000ull; + drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!drvdata->base) + return -EBUSY; + err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq, + IRQF_DISABLED, dev_name(&pdev->dev), drvdata); + if (err < 0) + return err; + + txx9aclc_ac97_dai.dev = &pdev->dev; + return snd_soc_register_dai(&txx9aclc_ac97_dai); +} + +static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&txx9aclc_ac97_dai); + return 0; +} + +static struct platform_driver txx9aclc_ac97_driver = { + .probe = txx9aclc_ac97_dev_probe, + .remove = __devexit_p(txx9aclc_ac97_dev_remove), + .driver = { + .name = "txx9aclc-ac97", + .owner = THIS_MODULE, + }, +}; + +static int __init txx9aclc_ac97_init(void) +{ + return platform_driver_register(&txx9aclc_ac97_driver); +} + +static void __exit txx9aclc_ac97_exit(void) +{ + platform_driver_unregister(&txx9aclc_ac97_driver); +} + +module_init(txx9aclc_ac97_init); +module_exit(txx9aclc_ac97_exit); + +MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); +MODULE_DESCRIPTION("TXx9 ACLC AC97 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc-generic.c b/sound/soc/txx9/txx9aclc-generic.c new file mode 100644 index 00000000000..3175de9a92c --- /dev/null +++ b/sound/soc/txx9/txx9aclc-generic.c @@ -0,0 +1,98 @@ +/* + * Generic TXx9 ACLC machine driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This is a very generic AC97 sound machine driver for boards which + * have (AC97) audio at ACLC (e.g. RBTX49XX boards). + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include "../codecs/ac97.h" +#include "txx9aclc.h" + +static struct snd_soc_dai_link txx9aclc_generic_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &txx9aclc_ac97_dai, + .codec_dai = &ac97_dai, +}; + +static struct snd_soc_card txx9aclc_generic_card = { + .name = "Generic TXx9 ACLC Audio", + .platform = &txx9aclc_soc_platform, + .dai_link = &txx9aclc_generic_dai, + .num_links = 1, +}; + +static struct txx9aclc_soc_device txx9aclc_generic_soc_device = { + .soc_dev = { + .card = &txx9aclc_generic_card, + .codec_dev = &soc_codec_dev_ac97, + }, +}; + +static int __init txx9aclc_generic_probe(struct platform_device *pdev) +{ + struct txx9aclc_soc_device *dev = &txx9aclc_generic_soc_device; + struct platform_device *soc_pdev; + int ret; + + soc_pdev = platform_device_alloc("soc-audio", -1); + if (!soc_pdev) + return -ENOMEM; + platform_set_drvdata(soc_pdev, &dev->soc_dev); + dev->soc_dev.dev = &soc_pdev->dev; + ret = platform_device_add(soc_pdev); + if (ret) { + platform_device_put(soc_pdev); + return ret; + } + platform_set_drvdata(pdev, soc_pdev); + return 0; +} + +static int __exit txx9aclc_generic_remove(struct platform_device *pdev) +{ + struct platform_device *soc_pdev = platform_get_drvdata(pdev); + + platform_device_unregister(soc_pdev); + return 0; +} + +static struct platform_driver txx9aclc_generic_driver = { + .remove = txx9aclc_generic_remove, + .driver = { + .name = "txx9aclc-generic", + .owner = THIS_MODULE, + }, +}; + +static int __init txx9aclc_generic_init(void) +{ + return platform_driver_probe(&txx9aclc_generic_driver, + txx9aclc_generic_probe); +} + +static void __exit txx9aclc_generic_exit(void) +{ + platform_driver_unregister(&txx9aclc_generic_driver); +} + +module_init(txx9aclc_generic_init); +module_exit(txx9aclc_generic_exit); + +MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); +MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c new file mode 100644 index 00000000000..938a58a5a24 --- /dev/null +++ b/sound/soc/txx9/txx9aclc.c @@ -0,0 +1,430 @@ +/* + * Generic TXx9 ACLC platform driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "txx9aclc.h" + +static const struct snd_pcm_hardware txx9aclc_pcm_hardware = { + /* + * REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + * needs more works for noncoherent MIPS. + */ + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE, +#ifdef __BIG_ENDIAN + .formats = SNDRV_PCM_FMTBIT_S16_BE, +#else + .formats = SNDRV_PCM_FMTBIT_S16_LE, +#endif + .period_bytes_min = 1024, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 4096, + .buffer_bytes_max = 32 * 1024, +}; + +static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_device *socdev = rtd->socdev; + struct snd_pcm_runtime *runtime = substream->runtime; + struct txx9aclc_dmadata *dmadata = runtime->private_data; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + dev_dbg(socdev->dev, + "runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd " + "runtime->min_align %ld\n", + (unsigned long)runtime->dma_area, + (unsigned long)runtime->dma_addr, runtime->dma_bytes, + runtime->min_align); + dev_dbg(socdev->dev, + "periods %d period_bytes %d stream %d\n", + params_periods(params), params_period_bytes(params), + substream->stream); + + dmadata->substream = substream; + dmadata->pos = 0; + return 0; +} + +static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct txx9aclc_dmadata *dmadata = runtime->private_data; + + dmadata->dma_addr = runtime->dma_addr; + dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + dmadata->period_bytes = snd_pcm_lib_period_bytes(substream); + + if (dmadata->buffer_bytes == dmadata->period_bytes) { + dmadata->frag_bytes = dmadata->period_bytes >> 1; + dmadata->frags = 2; + } else { + dmadata->frag_bytes = dmadata->period_bytes; + dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes; + } + dmadata->frag_count = 0; + dmadata->pos = 0; + return 0; +} + +static void txx9aclc_dma_complete(void *arg) +{ + struct txx9aclc_dmadata *dmadata = arg; + unsigned long flags; + + /* dma completion handler cannot submit new operations */ + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count >= 0) { + dmadata->dmacount--; + BUG_ON(dmadata->dmacount < 0); + tasklet_schedule(&dmadata->tasklet); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static struct dma_async_tx_descriptor * +txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr) +{ + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)), + dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1)); + sg_dma_address(&sg) = buf_dma_addr; + desc = chan->device->device_prep_slave_sg(chan, &sg, 1, + dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_TO_DEVICE : DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return NULL; + } + desc->callback = txx9aclc_dma_complete; + desc->callback_param = dmadata; + desc->tx_submit(desc); + return desc; +} + +#define NR_DMA_CHAIN 2 + +static void txx9aclc_dma_tasklet(unsigned long data) +{ + struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data; + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct snd_pcm_substream *substream = dmadata->substream; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + int i; + unsigned long flags; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count < 0) { + struct txx9aclc_soc_device *dev = + container_of(dmadata, struct txx9aclc_soc_device, + dmadata[substream->stream]); + struct txx9aclc_plat_drvdata *drvdata = + txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + chan->device->device_terminate_all(chan); + /* first time */ + for (i = 0; i < NR_DMA_CHAIN; i++) { + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + i * dmadata->frag_bytes); + if (!desc) + return; + } + dmadata->dmacount = NR_DMA_CHAIN; + chan->device->device_issue_pending(chan); + spin_lock_irqsave(&dmadata->dma_lock, flags); + __raw_writel(ctlbit, base + ACCTLEN); + dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return; + } + BUG_ON(dmadata->dmacount >= NR_DMA_CHAIN); + while (dmadata->dmacount < NR_DMA_CHAIN) { + dmadata->dmacount++; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + + dmadata->frag_count * dmadata->frag_bytes); + if (!desc) + return; + chan->device->device_issue_pending(chan); + + spin_lock_irqsave(&dmadata->dma_lock, flags); + dmadata->frag_count++; + dmadata->frag_count %= dmadata->frags; + dmadata->pos += dmadata->frag_bytes; + dmadata->pos %= dmadata->buffer_bytes; + if ((dmadata->frag_count * dmadata->frag_bytes) % + dmadata->period_bytes == 0) + snd_pcm_period_elapsed(substream); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct txx9aclc_soc_device *dev = + container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + unsigned long flags; + int ret = 0; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dmadata->frag_count = -1; + tasklet_schedule(&dmadata->tasklet); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + __raw_writel(ctlbit, base + ACCTLDIS); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + __raw_writel(ctlbit, base + ACCTLEN); + break; + default: + ret = -EINVAL; + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return ret; +} + +static snd_pcm_uframes_t +txx9aclc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, dmadata->pos); +} + +static int txx9aclc_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct txx9aclc_soc_device *dev = + container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream]; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware); + if (ret) + return ret; + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + substream->runtime->private_data = dmadata; + return 0; +} + +static int txx9aclc_pcm_close(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct dma_chan *chan = dmadata->dma_chan; + + dmadata->frag_count = -1; + chan->device->device_terminate_all(chan); + return 0; +} + +static struct snd_pcm_ops txx9aclc_pcm_ops = { + .open = txx9aclc_pcm_open, + .close = txx9aclc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = txx9aclc_pcm_hw_params, + .hw_free = txx9aclc_pcm_hw_free, + .prepare = txx9aclc_pcm_prepare, + .trigger = txx9aclc_pcm_trigger, + .pointer = txx9aclc_pcm_pointer, +}; + +static void txx9aclc_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 64 * 1024, 4 * 1024 * 1024); +} + +static bool filter(struct dma_chan *chan, void *param) +{ + struct txx9aclc_dmadata *dmadata = param; + char devname[20 + 2]; /* FIXME: old BUS_ID_SIZE + 2 */ + + snprintf(devname, sizeof(devname), "%s.%d", dmadata->dma_res->name, + (int)dmadata->dma_res->start); + if (strcmp(dev_name(chan->device->dev), devname) == 0) { + chan->private = &dmadata->dma_slave; + return true; + } + return false; +} + +static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, + struct txx9aclc_dmadata *dmadata) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + struct txx9dmac_slave *ds = &dmadata->dma_slave; + dma_cap_mask_t mask; + + spin_lock_init(&dmadata->dma_lock); + + ds->reg_width = sizeof(u32); + if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ds->tx_reg = drvdata->physbase + ACAUDODAT; + ds->rx_reg = 0; + } else { + ds->tx_reg = 0; + ds->rx_reg = drvdata->physbase + ACAUDIDAT; + } + + /* Try to grab a DMA channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dmadata->dma_chan = dma_request_channel(mask, filter, dmadata); + if (!dmadata->dma_chan) { + dev_err(dev->soc_dev.dev, + "DMA channel for %s is not available\n", + dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "playback" : "capture"); + return -EBUSY; + } + tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet, + (unsigned long)dmadata); + return 0; +} + +static int txx9aclc_pcm_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + struct resource *r; + int i; + int ret; + + dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK; + dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE; + for (i = 0; i < 2; i++) { + r = platform_get_resource(dev->aclc_pdev, IORESOURCE_DMA, i); + if (!r) { + ret = -EBUSY; + goto exit; + } + dev->dmadata[i].dma_res = r; + ret = txx9aclc_dma_init(dev, &dev->dmadata[i]); + if (ret) + goto exit; + } + return 0; + +exit: + for (i = 0; i < 2; i++) { + if (dev->dmadata[i].dma_chan) + dma_release_channel(dev->dmadata[i].dma_chan); + dev->dmadata[i].dma_chan = NULL; + } + return ret; +} + +static int txx9aclc_pcm_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + int i; + + /* disable all FIFO DMAs */ + __raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS); + /* dummy R/W to clear pending DMAREQ if any */ + __raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT); + + for (i = 0; i < 2; i++) { + struct txx9aclc_dmadata *dmadata = &dev->dmadata[i]; + struct dma_chan *chan = dmadata->dma_chan; + if (chan) { + dmadata->frag_count = -1; + chan->device->device_terminate_all(chan); + dma_release_channel(chan); + } + dev->dmadata[i].dma_chan = NULL; + } + return 0; +} + +struct snd_soc_platform txx9aclc_soc_platform = { + .name = "txx9aclc-audio", + .probe = txx9aclc_pcm_probe, + .remove = txx9aclc_pcm_remove, + .pcm_ops = &txx9aclc_pcm_ops, + .pcm_new = txx9aclc_pcm_new, + .pcm_free = txx9aclc_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(txx9aclc_soc_platform); + +static int __init txx9aclc_soc_platform_init(void) +{ + return snd_soc_register_platform(&txx9aclc_soc_platform); +} + +static void __exit txx9aclc_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&txx9aclc_soc_platform); +} + +module_init(txx9aclc_soc_platform_init); +module_exit(txx9aclc_soc_platform_exit); + +MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); +MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc.h b/sound/soc/txx9/txx9aclc.h new file mode 100644 index 00000000000..6769aab41b3 --- /dev/null +++ b/sound/soc/txx9/txx9aclc.h @@ -0,0 +1,83 @@ +/* + * TXx9 SoC AC Link Controller + * + * 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. + */ + +#ifndef __TXX9ACLC_H +#define __TXX9ACLC_H + +#include <linux/interrupt.h> +#include <asm/txx9/dmac.h> + +#define ACCTLEN 0x00 /* control enable */ +#define ACCTLDIS 0x04 /* control disable */ +#define ACCTL_ENLINK 0x00000001 /* enable/disable AC-link */ +#define ACCTL_AUDODMA 0x00000100 /* AUDODMA enable/disable */ +#define ACCTL_AUDIDMA 0x00001000 /* AUDIDMA enable/disable */ +#define ACCTL_AUDOEHLT 0x00010000 /* AUDO error halt + enable/disable */ +#define ACCTL_AUDIEHLT 0x00100000 /* AUDI error halt + enable/disable */ +#define ACREGACC 0x08 /* codec register access */ +#define ACREGACC_DAT_SHIFT 0 /* data field */ +#define ACREGACC_REG_SHIFT 16 /* address field */ +#define ACREGACC_CODECID_SHIFT 24 /* CODEC ID field */ +#define ACREGACC_READ 0x80000000 /* CODEC read */ +#define ACREGACC_WRITE 0x00000000 /* CODEC write */ +#define ACINTSTS 0x10 /* interrupt status */ +#define ACINTMSTS 0x14 /* interrupt masked status */ +#define ACINTEN 0x18 /* interrupt enable */ +#define ACINTDIS 0x1c /* interrupt disable */ +#define ACINT_CODECRDY(n) (0x00000001 << (n)) /* CODECn ready */ +#define ACINT_REGACCRDY 0x00000010 /* ACREGACC ready */ +#define ACINT_AUDOERR 0x00000100 /* AUDO underrun error */ +#define ACINT_AUDIERR 0x00001000 /* AUDI overrun error */ +#define ACDMASTS 0x80 /* DMA request status */ +#define ACDMA_AUDO 0x00000001 /* AUDODMA pending */ +#define ACDMA_AUDI 0x00000010 /* AUDIDMA pending */ +#define ACAUDODAT 0xa0 /* audio out data */ +#define ACAUDIDAT 0xb0 /* audio in data */ +#define ACREVID 0xfc /* revision ID */ + +struct txx9aclc_dmadata { + struct resource *dma_res; + struct txx9dmac_slave dma_slave; + struct dma_chan *dma_chan; + struct tasklet_struct tasklet; + spinlock_t dma_lock; + int stream; /* SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE */ + struct snd_pcm_substream *substream; + unsigned long pos; + dma_addr_t dma_addr; + unsigned long buffer_bytes; + unsigned long period_bytes; + unsigned long frag_bytes; + int frags; + int frag_count; + int dmacount; +}; + +struct txx9aclc_plat_drvdata { + void __iomem *base; + u64 physbase; +}; + +struct txx9aclc_soc_device { + struct snd_soc_device soc_dev; + struct platform_device *aclc_pdev; /* for ioresources, drvdata */ + struct txx9aclc_dmadata dmadata[2]; +}; + +static inline struct txx9aclc_plat_drvdata *txx9aclc_get_plat_drvdata( + struct txx9aclc_soc_device *sdev) +{ + return platform_get_drvdata(sdev->aclc_pdev); +} + +extern struct snd_soc_platform txx9aclc_soc_platform; +extern struct snd_soc_dai txx9aclc_ac97_dai; + +#endif /* __TXX9ACLC_H */ diff --git a/sound/synth/Makefile b/sound/synth/Makefile index e99fd76caa1..11eb06ac2ec 100644 --- a/sound/synth/Makefile +++ b/sound/synth/Makefile @@ -5,16 +5,8 @@ snd-util-mem-objs := util_mem.o -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - # Toplevel Module Dependency obj-$(CONFIG_SND_EMU10K1) += snd-util-mem.o obj-$(CONFIG_SND_TRIDENT) += snd-util-mem.o -obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-util-mem.o -obj-$(call sequencer,$(CONFIG_SND)) += emux/ +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-util-mem.o +obj-$(CONFIG_SND_SEQUENCER) += emux/ diff --git a/sound/synth/emux/Makefile b/sound/synth/emux/Makefile index b69035240cf..328594e6152 100644 --- a/sound/synth/emux/Makefile +++ b/sound/synth/emux/Makefile @@ -7,14 +7,6 @@ snd-emux-synth-objs := emux.o emux_synth.o emux_seq.o emux_nrpn.o \ emux_effect.o emux_proc.o emux_hwdep.o soundfont.o \ $(if $(CONFIG_SND_SEQUENCER_OSS),emux_oss.o) -# -# this function returns: -# "m" - CONFIG_SND_SEQUENCER is m -# <empty string> - CONFIG_SND_SEQUENCER is undefined -# otherwise parameter #1 value -# -sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) - # Toplevel Module Dependencies -obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-emux-synth.o -obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-emux-synth.o +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emux-synth.o +obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emux-synth.o diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c index b13ce767ac7..8f9b60c5d74 100644 --- a/sound/usb/caiaq/audio.c +++ b/sound/usb/caiaq/audio.c @@ -42,10 +42,10 @@ (stream << 1) | (~(i / (dev->n_streams * BYTES_PER_SAMPLE_USB)) & 1) static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = SNDRV_PCM_FMTBIT_S24_3BE, - .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000), .rate_min = 44100, .rate_max = 0, /* will overwrite later */ @@ -68,7 +68,7 @@ activate_substream(struct snd_usb_caiaqdev *dev, dev->sub_capture[sub->number] = sub; } -static void +static void deactivate_substream(struct snd_usb_caiaqdev *dev, struct snd_pcm_substream *sub) { @@ -118,7 +118,7 @@ static int stream_start(struct snd_usb_caiaqdev *dev) return -EPIPE; } } - + return 0; } @@ -129,7 +129,7 @@ static void stream_stop(struct snd_usb_caiaqdev *dev) debug("%s(%p)\n", __func__, dev); if (!dev->streaming) return; - + dev->streaming = 0; for (i = 0; i < N_URBS; i++) { @@ -154,7 +154,7 @@ static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream) debug("%s(%p)\n", __func__, substream); if (all_substreams_zero(dev->sub_playback) && all_substreams_zero(dev->sub_capture)) { - /* when the last client has stopped streaming, + /* when the last client has stopped streaming, * all sample rates are allowed again */ stream_stop(dev); dev->pcm_info.rates = dev->samplerates; @@ -194,30 +194,31 @@ static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; debug("%s(%p)\n", __func__, substream); - + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { dev->period_out_count[index] = BYTES_PER_SAMPLE + 1; dev->audio_out_buf_pos[index] = BYTES_PER_SAMPLE + 1; } else { - dev->period_in_count[index] = BYTES_PER_SAMPLE; - dev->audio_in_buf_pos[index] = BYTES_PER_SAMPLE; + int in_pos = (dev->spec.data_alignment == 2) ? 0 : 2; + dev->period_in_count[index] = BYTES_PER_SAMPLE + in_pos; + dev->audio_in_buf_pos[index] = BYTES_PER_SAMPLE + in_pos; } if (dev->streaming) return 0; - + /* the first client that opens a stream defines the sample rate * setting for all subsequent calls, until the last client closed. */ for (i=0; i < ARRAY_SIZE(rates); i++) if (runtime->rate == rates[i]) dev->pcm_info.rates = 1 << i; - + snd_pcm_limit_hw_rates(runtime); bytes_per_sample = BYTES_PER_SAMPLE; if (dev->spec.data_alignment == 2) bytes_per_sample++; - + bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE) * bytes_per_sample * CHANNELS_PER_STREAM * dev->n_streams; @@ -232,7 +233,7 @@ static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) ret = stream_start(dev); if (ret) return ret; - + dev->output_running = 0; wait_event_timeout(dev->prepare_wait_queue, dev->output_running, HZ); if (!dev->output_running) { @@ -273,7 +274,7 @@ snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub) return SNDRV_PCM_POS_XRUN; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) - return bytes_to_frames(sub->runtime, + return bytes_to_frames(sub->runtime, dev->audio_out_buf_pos[index]); else return bytes_to_frames(sub->runtime, @@ -291,7 +292,7 @@ static struct snd_pcm_ops snd_usb_caiaq_ops = { .trigger = snd_usb_caiaq_pcm_trigger, .pointer = snd_usb_caiaq_pcm_pointer }; - + static void check_for_elapsed_periods(struct snd_usb_caiaqdev *dev, struct snd_pcm_substream **subs) { @@ -333,7 +334,7 @@ static void read_in_urb_mode0(struct snd_usb_caiaqdev *dev, struct snd_pcm_runtime *rt = sub->runtime; char *audio_buf = rt->dma_area; int sz = frames_to_bytes(rt, rt->buffer_size); - audio_buf[dev->audio_in_buf_pos[stream]++] + audio_buf[dev->audio_in_buf_pos[stream]++] = usb_buf[i]; dev->period_in_count[stream]++; if (dev->audio_in_buf_pos[stream] == sz) @@ -354,14 +355,14 @@ static void read_in_urb_mode2(struct snd_usb_caiaqdev *dev, for (i = 0; i < iso->actual_length;) { if (i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == 0) { - for (stream = 0; - stream < dev->n_streams; + for (stream = 0; + stream < dev->n_streams; stream++, i++) { if (dev->first_packet) continue; check_byte = MAKE_CHECKBYTE(dev, stream, i); - + if ((usb_buf[i] & 0x3f) != check_byte) dev->input_panic = 1; @@ -410,21 +411,21 @@ static void read_in_urb(struct snd_usb_caiaqdev *dev, } if ((dev->input_panic || dev->output_panic) && !dev->warned) { - debug("streaming error detected %s %s\n", + debug("streaming error detected %s %s\n", dev->input_panic ? "(input)" : "", dev->output_panic ? "(output)" : ""); dev->warned = 1; } } -static void fill_out_urb(struct snd_usb_caiaqdev *dev, - struct urb *urb, +static void fill_out_urb(struct snd_usb_caiaqdev *dev, + struct urb *urb, const struct usb_iso_packet_descriptor *iso) { unsigned char *usb_buf = urb->transfer_buffer + iso->offset; struct snd_pcm_substream *sub; int stream, i; - + for (i = 0; i < iso->length;) { for (stream = 0; stream < dev->n_streams; stream++, i++) { sub = dev->sub_playback[stream]; @@ -444,7 +445,7 @@ static void fill_out_urb(struct snd_usb_caiaqdev *dev, /* fill in the check bytes */ if (dev->spec.data_alignment == 2 && - i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == + i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == (dev->n_streams * CHANNELS_PER_STREAM)) for (stream = 0; stream < dev->n_streams; stream++, i++) usb_buf[i] = MAKE_CHECKBYTE(dev, stream, i); @@ -453,7 +454,7 @@ static void fill_out_urb(struct snd_usb_caiaqdev *dev, static void read_completed(struct urb *urb) { - struct snd_usb_caiaq_cb_info *info = urb->context; + struct snd_usb_caiaq_cb_info *info = urb->context; struct snd_usb_caiaqdev *dev; struct urb *out; int frame, len, send_it = 0, outframe = 0; @@ -478,7 +479,7 @@ static void read_completed(struct urb *urb) out->iso_frame_desc[outframe].length = len; out->iso_frame_desc[outframe].actual_length = 0; out->iso_frame_desc[outframe].offset = BYTES_PER_FRAME * frame; - + if (len > 0) { spin_lock(&dev->spinlock); fill_out_urb(dev, out, &out->iso_frame_desc[outframe]); @@ -497,14 +498,14 @@ static void read_completed(struct urb *urb) out->transfer_flags = URB_ISO_ASAP; usb_submit_urb(out, GFP_ATOMIC); } - + /* re-submit inbound urb */ for (frame = 0; frame < FRAMES_PER_URB; frame++) { urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame; urb->iso_frame_desc[frame].length = BYTES_PER_FRAME; urb->iso_frame_desc[frame].actual_length = 0; } - + urb->number_of_packets = FRAMES_PER_URB; urb->transfer_flags = URB_ISO_ASAP; usb_submit_urb(urb, GFP_ATOMIC); @@ -528,7 +529,7 @@ static struct urb **alloc_urbs(struct snd_usb_caiaqdev *dev, int dir, int *ret) struct usb_device *usb_dev = dev->chip.dev; unsigned int pipe; - pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ? + pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ? usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) : usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE); @@ -547,25 +548,25 @@ static struct urb **alloc_urbs(struct snd_usb_caiaqdev *dev, int dir, int *ret) return urbs; } - urbs[i]->transfer_buffer = + urbs[i]->transfer_buffer = kmalloc(FRAMES_PER_URB * BYTES_PER_FRAME, GFP_KERNEL); if (!urbs[i]->transfer_buffer) { log("unable to kmalloc() transfer buffer, OOM!?\n"); *ret = -ENOMEM; return urbs; } - + for (frame = 0; frame < FRAMES_PER_URB; frame++) { - struct usb_iso_packet_descriptor *iso = + struct usb_iso_packet_descriptor *iso = &urbs[i]->iso_frame_desc[frame]; - + iso->offset = BYTES_PER_FRAME * frame; iso->length = BYTES_PER_FRAME; } - + urbs[i]->dev = usb_dev; urbs[i]->pipe = pipe; - urbs[i]->transfer_buffer_length = FRAMES_PER_URB + urbs[i]->transfer_buffer_length = FRAMES_PER_URB * BYTES_PER_FRAME; urbs[i]->context = &dev->data_cb_info[i]; urbs[i]->interval = 1; @@ -589,7 +590,7 @@ static void free_urbs(struct urb **urbs) for (i = 0; i < N_URBS; i++) { if (!urbs[i]) continue; - + usb_kill_urb(urbs[i]); kfree(urbs[i]->transfer_buffer); usb_free_urb(urbs[i]); @@ -602,11 +603,11 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) { int i, ret; - dev->n_audio_in = max(dev->spec.num_analog_audio_in, - dev->spec.num_digital_audio_in) / + dev->n_audio_in = max(dev->spec.num_analog_audio_in, + dev->spec.num_digital_audio_in) / CHANNELS_PER_STREAM; dev->n_audio_out = max(dev->spec.num_analog_audio_out, - dev->spec.num_digital_audio_out) / + dev->spec.num_digital_audio_out) / CHANNELS_PER_STREAM; dev->n_streams = max(dev->n_audio_in, dev->n_audio_out); @@ -619,7 +620,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) return -EINVAL; } - ret = snd_pcm_new(dev->chip.card, dev->product_name, 0, + ret = snd_pcm_new(dev->chip.card, dev->product_name, 0, dev->n_audio_out, dev->n_audio_in, &dev->pcm); if (ret < 0) { @@ -632,7 +633,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) memset(dev->sub_playback, 0, sizeof(dev->sub_playback)); memset(dev->sub_capture, 0, sizeof(dev->sub_capture)); - + memcpy(&dev->pcm_info, &snd_usb_caiaq_pcm_hardware, sizeof(snd_usb_caiaq_pcm_hardware)); @@ -651,9 +652,9 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) break; } - snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usb_caiaq_ops); - snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, + snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_caiaq_ops); snd_pcm_lib_preallocate_pages_for_all(dev->pcm, @@ -662,7 +663,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); dev->data_cb_info = - kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS, + kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS, GFP_KERNEL); if (!dev->data_cb_info) @@ -672,14 +673,14 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) dev->data_cb_info[i].dev = dev; dev->data_cb_info[i].index = i; } - + dev->data_urbs_in = alloc_urbs(dev, SNDRV_PCM_STREAM_CAPTURE, &ret); if (ret < 0) { kfree(dev->data_cb_info); free_urbs(dev->data_urbs_in); return ret; } - + dev->data_urbs_out = alloc_urbs(dev, SNDRV_PCM_STREAM_PLAYBACK, &ret); if (ret < 0) { kfree(dev->data_cb_info); diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index 515de1cd2a3..0e5db719de2 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -35,7 +35,7 @@ #include "input.h" MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); -MODULE_DESCRIPTION("caiaq USB audio, version 1.3.14"); +MODULE_DESCRIPTION("caiaq USB audio, version 1.3.17"); MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{Native Instruments, RigKontrol2}," "{Native Instruments, RigKontrol3}," @@ -79,7 +79,7 @@ static struct usb_device_id snd_usb_id_table[] = { { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = USB_VID_NATIVEINSTRUMENTS, - .idProduct = USB_PID_RIGKONTROL2 + .idProduct = USB_PID_RIGKONTROL2 }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, @@ -197,7 +197,7 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *dev, if (buffer && len > 0) memcpy(dev->ep1_out_buf+1, buffer, len); - + dev->ep1_out_buf[0] = command; return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), dev->ep1_out_buf, len+1, &actual_len, 200); @@ -208,7 +208,7 @@ int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, { int ret; char tmp[5]; - + switch (rate) { case 44100: tmp[0] = SAMPLERATE_44100; break; case 48000: tmp[0] = SAMPLERATE_48000; break; @@ -237,12 +237,12 @@ int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, if (ret) return ret; - - if (!wait_event_timeout(dev->ep1_wait_queue, + + if (!wait_event_timeout(dev->ep1_wait_queue, dev->audio_parm_answer >= 0, HZ)) return -EPIPE; - - if (dev->audio_parm_answer != 1) + + if (dev->audio_parm_answer != 1) debug("unable to set the device's audio params\n"); else dev->bpp = bpp; @@ -250,8 +250,8 @@ int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, return dev->audio_parm_answer == 1 ? 0 : -EINVAL; } -int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, - int digital, int analog, int erp) +int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *dev, + int digital, int analog, int erp) { char tmp[3] = { digital, analog, erp }; return snd_usb_caiaq_send_command(dev, EP1_CMD_AUTO_MSG, @@ -262,7 +262,7 @@ static void __devinit setup_card(struct snd_usb_caiaqdev *dev) { int ret; char val[4]; - + /* device-specific startup specials */ switch (dev->chip.usb_id) { case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): @@ -314,7 +314,7 @@ static void __devinit setup_card(struct snd_usb_caiaqdev *dev) dev->control_state, 1); break; } - + if (dev->spec.num_analog_audio_out + dev->spec.num_analog_audio_in + dev->spec.num_digital_audio_out + @@ -323,7 +323,7 @@ static void __devinit setup_card(struct snd_usb_caiaqdev *dev) if (ret < 0) log("Unable to set up audio system (ret=%d)\n", ret); } - + if (dev->spec.num_midi_in + dev->spec.num_midi_out > 0) { ret = snd_usb_caiaq_midi_init(dev); @@ -363,7 +363,7 @@ static int create_card(struct usb_device* usb_dev, struct snd_card **cardp) if (devnum >= SNDRV_CARDS) return -ENODEV; - err = snd_card_create(index[devnum], id[devnum], THIS_MODULE, + err = snd_card_create(index[devnum], id[devnum], THIS_MODULE, sizeof(struct snd_usb_caiaqdev), &card); if (err < 0) return err; @@ -382,11 +382,11 @@ static int create_card(struct usb_device* usb_dev, struct snd_card **cardp) static int __devinit init_card(struct snd_usb_caiaqdev *dev) { - char *c; + char *c, usbpath[32]; struct usb_device *usb_dev = dev->chip.dev; struct snd_card *card = dev->chip.card; int err, len; - + if (usb_set_interface(usb_dev, 0, 1) != 0) { log("can't set alt interface.\n"); return -EIO; @@ -395,19 +395,19 @@ static int __devinit init_card(struct snd_usb_caiaqdev *dev) usb_init_urb(&dev->ep1_in_urb); usb_init_urb(&dev->midi_out_urb); - usb_fill_bulk_urb(&dev->ep1_in_urb, usb_dev, + usb_fill_bulk_urb(&dev->ep1_in_urb, usb_dev, usb_rcvbulkpipe(usb_dev, 0x1), - dev->ep1_in_buf, EP1_BUFSIZE, + dev->ep1_in_buf, EP1_BUFSIZE, usb_ep1_command_reply_dispatch, dev); - usb_fill_bulk_urb(&dev->midi_out_urb, usb_dev, + usb_fill_bulk_urb(&dev->midi_out_urb, usb_dev, usb_sndbulkpipe(usb_dev, 0x1), - dev->midi_out_buf, EP1_BUFSIZE, + dev->midi_out_buf, EP1_BUFSIZE, snd_usb_caiaq_midi_output_done, dev); - + init_waitqueue_head(&dev->ep1_wait_queue); init_waitqueue_head(&dev->prepare_wait_queue); - + if (usb_submit_urb(&dev->ep1_in_urb, GFP_KERNEL) != 0) return -EIO; @@ -420,47 +420,52 @@ static int __devinit init_card(struct snd_usb_caiaqdev *dev) usb_string(usb_dev, usb_dev->descriptor.iManufacturer, dev->vendor_name, CAIAQ_USB_STR_LEN); - + usb_string(usb_dev, usb_dev->descriptor.iProduct, dev->product_name, CAIAQ_USB_STR_LEN); - - usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, - dev->serial, CAIAQ_USB_STR_LEN); - - /* terminate serial string at first white space occurence */ - c = strchr(dev->serial, ' '); - if (c) - *c = '\0'; - - strcpy(card->driver, MODNAME); - strcpy(card->shortname, dev->product_name); - - len = snprintf(card->longname, sizeof(card->longname), - "%s %s (serial %s, ", - dev->vendor_name, dev->product_name, dev->serial); - - if (len < sizeof(card->longname) - 2) - len += usb_make_path(usb_dev, card->longname + len, - sizeof(card->longname) - len); - - card->longname[len++] = ')'; - card->longname[len] = '\0'; + + strlcpy(card->driver, MODNAME, sizeof(card->driver)); + strlcpy(card->shortname, dev->product_name, sizeof(card->shortname)); + strlcpy(card->mixername, dev->product_name, sizeof(card->mixername)); + + /* if the id was not passed as module option, fill it with a shortened + * version of the product string which does not contain any + * whitespaces */ + + if (*card->id == '\0') { + char id[sizeof(card->id)]; + + memset(id, 0, sizeof(id)); + + for (c = card->shortname, len = 0; + *c && len < sizeof(card->id); c++) + if (*c != ' ') + id[len++] = *c; + + snd_card_set_id(card, id); + } + + usb_make_path(usb_dev, usbpath, sizeof(usbpath)); + snprintf(card->longname, sizeof(card->longname), + "%s %s (%s)", + dev->vendor_name, dev->product_name, usbpath); + setup_card(dev); return 0; } -static int __devinit snd_probe(struct usb_interface *intf, +static int __devinit snd_probe(struct usb_interface *intf, const struct usb_device_id *id) { int ret; struct snd_card *card; struct usb_device *device = interface_to_usbdev(intf); - + ret = create_card(device, &card); - + if (ret < 0) return ret; - + usb_set_intfdata(intf, card); ret = init_card(caiaqdev(card)); if (ret < 0) { @@ -468,7 +473,7 @@ static int __devinit snd_probe(struct usb_interface *intf, snd_card_free(card); return ret; } - + return 0; } @@ -489,10 +494,10 @@ static void snd_disconnect(struct usb_interface *intf) snd_usb_caiaq_input_free(dev); #endif snd_usb_caiaq_audio_free(dev); - + usb_kill_urb(&dev->ep1_in_urb); usb_kill_urb(&dev->midi_out_urb); - + snd_card_free(card); usb_reset_device(interface_to_usbdev(intf)); } diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 4cce1ad7493..ece73514854 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -81,7 +81,6 @@ struct snd_usb_caiaqdev { char vendor_name[CAIAQ_USB_STR_LEN]; char product_name[CAIAQ_USB_STR_LEN]; - char serial[CAIAQ_USB_STR_LEN]; int n_streams, n_audio_in, n_audio_out; int streaming, first_packet, output_running; diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index 8fa8cd88d76..538e8c00d31 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -40,7 +40,7 @@ static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *subst if (!dev) return; - + dev->midi_receive_substream = up ? substream : NULL; } @@ -64,18 +64,18 @@ static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *dev, struct snd_rawmidi_substream *substream) { int len, ret; - + dev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE; dev->midi_out_buf[1] = 0; /* port */ len = snd_rawmidi_transmit(substream, dev->midi_out_buf + 3, EP1_BUFSIZE - 3); - + if (len <= 0) return; - + dev->midi_out_buf[2] = len; dev->midi_out_urb.transfer_buffer_length = len+3; - + ret = usb_submit_urb(&dev->midi_out_urb, GFP_ATOMIC); if (ret < 0) log("snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed," @@ -88,7 +88,7 @@ static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *dev, static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) { struct snd_usb_caiaqdev *dev = substream->rmidi->private_data; - + if (up) { dev->midi_out_substream = substream; if (!dev->midi_out_active) @@ -113,12 +113,12 @@ static struct snd_rawmidi_ops snd_usb_caiaq_midi_input = .trigger = snd_usb_caiaq_midi_input_trigger, }; -void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, +void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, int port, const char *buf, int len) { if (!dev->midi_receive_substream) return; - + snd_rawmidi_receive(dev->midi_receive_substream, buf, len); } @@ -142,16 +142,16 @@ int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) if (device->spec.num_midi_out > 0) { rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; - snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_usb_caiaq_midi_output); } if (device->spec.num_midi_in > 0) { rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; - snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usb_caiaq_midi_input); } - + device->rmidi = rmidi; return 0; @@ -160,7 +160,7 @@ int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) void snd_usb_caiaq_midi_output_done(struct urb* urb) { struct snd_usb_caiaqdev *dev = urb->context; - + dev->midi_out_active = 0; if (urb->status != 0) return; diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index 823296d7d57..c7b902358b7 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -627,6 +627,7 @@ static int prepare_playback_urb(struct snd_usb_substream *subs, subs->hwptr_done += offs; if (subs->hwptr_done >= runtime->buffer_size) subs->hwptr_done -= runtime->buffer_size; + runtime->delay += offs; spin_unlock_irqrestore(&subs->lock, flags); urb->transfer_buffer_length = offs * stride; if (period_elapsed) @@ -636,12 +637,22 @@ static int prepare_playback_urb(struct snd_usb_substream *subs, /* * process after playback data complete - * - nothing to do + * - decrease the delay count again */ static int retire_playback_urb(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *urb) { + unsigned long flags; + int stride = runtime->frame_bits >> 3; + int processed = urb->transfer_buffer_length / stride; + + spin_lock_irqsave(&subs->lock, flags); + if (processed > runtime->delay) + runtime->delay = 0; + else + runtime->delay -= processed; + spin_unlock_irqrestore(&subs->lock, flags); return 0; } @@ -1520,6 +1531,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) subs->hwptr_done = 0; subs->transfer_done = 0; subs->phase = 0; + runtime->delay = 0; /* clear urbs (to be sure) */ deactivate_urbs(subs, 0, 1); @@ -3279,6 +3291,25 @@ static int snd_usb_cm106_boot_quirk(struct usb_device *dev) return snd_usb_cm106_write_int_reg(dev, 2, 0x8004); } +/* + * C-Media CM6206 is based on CM106 with two additional + * registers that are not documented in the data sheet. + * Values here are chosen based on sniffing USB traffic + * under Windows. + */ +static int snd_usb_cm6206_boot_quirk(struct usb_device *dev) +{ + int err, reg; + int val[] = {0x200c, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000}; + + for (reg = 0; reg < ARRAY_SIZE(val); reg++) { + err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]); + if (err < 0) + return err; + } + + return err; +} /* * Setup quirks @@ -3347,7 +3378,7 @@ static int snd_usb_create_quirk(struct snd_usb_audio *chip, [QUIRK_MIDI_YAMAHA] = snd_usb_create_midi_interface, [QUIRK_MIDI_MIDIMAN] = snd_usb_create_midi_interface, [QUIRK_MIDI_NOVATION] = snd_usb_create_midi_interface, - [QUIRK_MIDI_RAW] = snd_usb_create_midi_interface, + [QUIRK_MIDI_FASTLANE] = snd_usb_create_midi_interface, [QUIRK_MIDI_EMAGIC] = snd_usb_create_midi_interface, [QUIRK_MIDI_CME] = snd_usb_create_midi_interface, [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, @@ -3565,6 +3596,12 @@ static void *snd_usb_audio_probe(struct usb_device *dev, goto __err_val; } + /* C-Media CM6206 / CM106-Like Sound Device */ + if (id == USB_ID(0x0d8c, 0x0102)) { + if (snd_usb_cm6206_boot_quirk(dev) < 0) + goto __err_val; + } + /* * found a config. now register to ALSA */ diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 36e4f7a29ad..8e7f78941ba 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -153,7 +153,7 @@ enum quirk_type { QUIRK_MIDI_YAMAHA, QUIRK_MIDI_MIDIMAN, QUIRK_MIDI_NOVATION, - QUIRK_MIDI_RAW, + QUIRK_MIDI_FASTLANE, QUIRK_MIDI_EMAGIC, QUIRK_MIDI_CME, QUIRK_MIDI_US122L, diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c index 26bad373fe6..2fb35cc22a3 100644 --- a/sound/usb/usbmidi.c +++ b/sound/usb/usbmidi.c @@ -1778,8 +1778,18 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, umidi->usb_protocol_ops = &snd_usbmidi_novation_ops; err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); break; - case QUIRK_MIDI_RAW: + case QUIRK_MIDI_FASTLANE: umidi->usb_protocol_ops = &snd_usbmidi_raw_ops; + /* + * Interface 1 contains isochronous endpoints, but with the same + * numbers as in interface 0. Since it is interface 1 that the + * USB core has most recently seen, these descriptors are now + * associated with the endpoint numbers. This will foul up our + * attempts to submit bulk/interrupt URBs to the endpoints in + * interface 0, so we have to make sure that the USB core looks + * again at interface 0 by calling usb_set_interface() on it. + */ + usb_set_interface(umidi->chip->dev, 0, 0); err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); break; case QUIRK_MIDI_EMAGIC: diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h index 647ef502965..f6f201eb24c 100644 --- a/sound/usb/usbquirks.h +++ b/sound/usb/usbquirks.h @@ -1470,6 +1470,41 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + /* Edirol M-16DX */ + /* FIXME: This quirk gives a good-working capture stream but the + * playback seems problematic because of lacking of sync + * with capture stream. It needs to sync with the capture + * clock. As now, you'll get frequent sound distortions + * via the playback. + */ + USB_DEVICE(0x0582, 0x00c4), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ /* BOSS GT-10 */ USB_DEVICE(0x0582, 0x00da), .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { @@ -1868,7 +1903,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .data = & (const struct snd_usb_audio_quirk[]) { { .ifnum = 0, - .type = QUIRK_MIDI_RAW + .type = QUIRK_MIDI_FASTLANE }, { .ifnum = 1, @@ -1951,6 +1986,14 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + USB_DEVICE(0x0ccd, 0x0028), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "Aureon5.1MkII", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ USB_DEVICE(0x0ccd, 0x0035), .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { .vendor_name = "Miditech", |