diff options
Diffstat (limited to 'sound')
42 files changed, 2696 insertions, 516 deletions
diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c index f0ebc971c686..1dd66ddffcaf 100644 --- a/sound/aoa/codecs/tas.c +++ b/sound/aoa/codecs/tas.c @@ -897,6 +897,15 @@ static int tas_create(struct i2c_adapter *adapter, client = i2c_new_device(adapter, &info); if (!client) return -ENODEV; + /* + * We know the driver is already loaded, so the device should be + * already bound. If not it means binding failed, and then there + * is no point in keeping the device instantiated. + */ + if (!client->driver) { + i2c_unregister_device(client); + return -ENODEV; + } /* * Let i2c-core delete that device on driver removal. diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index dc78272fc39f..dbb05b772558 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -504,6 +504,10 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, int err; aaci_pcm_hw_free(substream); + if (aacirun->pcm_open) { + snd_ac97_pcm_close(aacirun->pcm); + aacirun->pcm_open = 0; + } err = devdma_hw_alloc(NULL, substream, params_buffer_bytes(params)); @@ -517,7 +521,7 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, else err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params), params_channels(params), - aacirun->pcm->r[1].slots); + aacirun->pcm->r[0].slots); if (err) goto out; diff --git a/sound/core/hrtimer.c b/sound/core/hrtimer.c index 34c7d48f5061..7f4d744ae40a 100644 --- a/sound/core/hrtimer.c +++ b/sound/core/hrtimer.c @@ -37,14 +37,22 @@ static unsigned int resolution; struct snd_hrtimer { struct snd_timer *timer; struct hrtimer hrt; + atomic_t running; }; static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) { struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt); struct snd_timer *t = stime->timer; + + if (!atomic_read(&stime->running)) + return HRTIMER_NORESTART; + hrtimer_forward_now(hrt, ns_to_ktime(t->sticks * resolution)); snd_timer_interrupt(stime->timer, t->sticks); + + if (!atomic_read(&stime->running)) + return HRTIMER_NORESTART; return HRTIMER_RESTART; } @@ -58,6 +66,7 @@ static int snd_hrtimer_open(struct snd_timer *t) hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); stime->timer = t; stime->hrt.function = snd_hrtimer_callback; + atomic_set(&stime->running, 0); t->private_data = stime; return 0; } @@ -78,16 +87,18 @@ static int snd_hrtimer_start(struct snd_timer *t) { struct snd_hrtimer *stime = t->private_data; + atomic_set(&stime->running, 0); + hrtimer_cancel(&stime->hrt); hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution), HRTIMER_MODE_REL); + atomic_set(&stime->running, 1); return 0; } static int snd_hrtimer_stop(struct snd_timer *t) { struct snd_hrtimer *stime = t->private_data; - - hrtimer_cancel(&stime->hrt); + atomic_set(&stime->running, 0); return 0; } diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 473247c8e6d3..70d6f25ba526 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -248,7 +248,8 @@ static int assign_substream(struct snd_rawmidi *rmidi, int subdevice, list_for_each_entry(substream, &s->substreams, list) { if (substream->opened) { if (stream == SNDRV_RAWMIDI_STREAM_INPUT || - !(mode & SNDRV_RAWMIDI_LFLG_APPEND)) + !(mode & SNDRV_RAWMIDI_LFLG_APPEND) || + !substream->append) continue; } if (subdevice < 0 || subdevice == substream->number) { @@ -266,17 +267,21 @@ static int open_substream(struct snd_rawmidi *rmidi, { int err; - err = snd_rawmidi_runtime_create(substream); - if (err < 0) - return err; - err = substream->ops->open(substream); - if (err < 0) - return err; - substream->opened = 1; - if (substream->use_count++ == 0) - substream->active_sensing = 1; - if (mode & SNDRV_RAWMIDI_LFLG_APPEND) - substream->append = 1; + if (substream->use_count == 0) { + err = snd_rawmidi_runtime_create(substream); + if (err < 0) + return err; + err = substream->ops->open(substream); + if (err < 0) { + snd_rawmidi_runtime_free(substream); + return err; + } + substream->opened = 1; + substream->active_sensing = 0; + if (mode & SNDRV_RAWMIDI_LFLG_APPEND) + substream->append = 1; + } + substream->use_count++; rmidi->streams[substream->stream].substream_opened++; return 0; } @@ -297,27 +302,27 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, SNDRV_RAWMIDI_STREAM_INPUT, mode, &sinput); if (err < 0) - goto __error; + return err; } if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { err = assign_substream(rmidi, subdevice, SNDRV_RAWMIDI_STREAM_OUTPUT, mode, &soutput); if (err < 0) - goto __error; + return err; } if (sinput) { err = open_substream(rmidi, sinput, mode); if (err < 0) - goto __error; + return err; } if (soutput) { err = open_substream(rmidi, soutput, mode); if (err < 0) { if (sinput) close_substream(rmidi, sinput, 0); - goto __error; + return err; } } @@ -325,13 +330,6 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, rfile->input = sinput; rfile->output = soutput; return 0; - - __error: - if (sinput && sinput->runtime) - snd_rawmidi_runtime_free(sinput); - if (soutput && soutput->runtime) - snd_rawmidi_runtime_free(soutput); - return err; } /* called from sound/core/seq/seq_midi.c */ diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 4d26146a62cc..90356b880dbb 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -236,6 +236,7 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info memset(¶ms, 0, sizeof(params)); params.avail_min = 1; params.buffer_size = output_buffer_size; + params.no_active_sensing = 1; if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms)) < 0) { snd_rawmidi_kernel_release(&msynth->output_rfile); return err; diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index e497525bc11b..db7ff29ee9a1 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -609,7 +609,7 @@ static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, /* alloc virtual 'dma' area */ if (runtime->dma_area) vfree(runtime->dma_area); - runtime->dma_area = vmalloc(size); + runtime->dma_area = vmalloc_user(size); if (runtime->dma_area == NULL) return -ENOMEM; runtime->dma_bytes = size; diff --git a/sound/pci/cs46xx/cs46xx_lib.h b/sound/pci/cs46xx/cs46xx_lib.h index 4eb55aa33612..b5189495d58a 100644 --- a/sound/pci/cs46xx/cs46xx_lib.h +++ b/sound/pci/cs46xx/cs46xx_lib.h @@ -35,7 +35,7 @@ #ifdef CONFIG_SND_CS46XX_NEW_DSP -#define CS46XX_MIN_PERIOD_SIZE 1 +#define CS46XX_MIN_PERIOD_SIZE 64 #define CS46XX_MAX_PERIOD_SIZE 1024*1024 #else #define CS46XX_MIN_PERIOD_SIZE 2048 diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 175f07a381ba..7ea92f58ffd4 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1839,6 +1839,12 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) if (!bdl_pos_adj[chip->dev_index]) return 1; /* no delayed ack */ + if (azx_dev->period_bytes == 0) { + printk(KERN_WARNING + "hda-intel: Divide by zero was avoided " + "in azx_dev->period_bytes.\n"); + return 0; + } if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) return 0; /* NG - it's below the period boundary */ return 1; /* OK, it's fine */ @@ -2392,6 +2398,11 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, } } + /* disable 64bit DMA address for Teradici */ + /* it does not work with device 6549:1200 subsys e4a2:040b */ + if (chip->driver_type == AZX_DRIVER_TERA) + gcap &= ~ICH6_GCAP_64OK; + /* allow 64bit DMA address if supported by H/W */ if ((gcap & ICH6_GCAP_64OK) && !pci_set_dma_mask(pci, DMA_BIT_MASK(64))) pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64)); diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 30eeb304351c..713bf692bc27 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -16876,6 +16876,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(0x1179, 0xff6e, "Toshiba NB200", ALC663_ASUS_MODE4), SND_PCI_QUIRK(0x144d, 0xca00, "Samsung NC10", ALC272_SAMSUNG_NC10), SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte 945GCM-S2L", ALC662_3ST_6ch_DIG), diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 6990cfcb6a38..285b1e24e995 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -1817,6 +1817,8 @@ static struct snd_pci_quirk stac92hd73xx_cfg_tbl[] = { "Dell Studio 17", STAC_DELL_M6_DMIC), SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02be, "Dell Studio 1555", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02bd, + "Dell Studio 1557", STAC_DELL_M6_DMIC), {} /* terminator */ }; diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index cc84a831eb21..56d1bd8b6118 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -643,7 +643,7 @@ static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) { /* running? we cannot change the rate now... */ spin_unlock_irqrestore(&ice->reg_lock, flags); - return -EBUSY; + return ((rate == ice->cur_rate) && !force) ? 0 : -EBUSY; } if (!force && is_pro_rate_locked(ice)) { spin_unlock_irqrestore(&ice->reg_lock, flags); diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index a83d1968a845..32f98535c0b4 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -1161,13 +1161,15 @@ static long snd_mixart_BA0_read(struct snd_info_entry *entry, void *file_private unsigned long count, unsigned long pos) { struct mixart_mgr *mgr = entry->private_data; + unsigned long maxsize; - count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ - if(count <= 0) + if (pos >= MIXART_BA0_SIZE) return 0; - if(pos + count > MIXART_BA0_SIZE) - count = (long)(MIXART_BA0_SIZE - pos); - if(copy_to_user_fromio(buf, MIXART_MEM( mgr, pos ), count)) + maxsize = MIXART_BA0_SIZE - pos; + if (count > maxsize) + count = maxsize; + count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ + if (copy_to_user_fromio(buf, MIXART_MEM(mgr, pos), count)) return -EFAULT; return count; } @@ -1180,13 +1182,15 @@ static long snd_mixart_BA1_read(struct snd_info_entry *entry, void *file_private unsigned long count, unsigned long pos) { struct mixart_mgr *mgr = entry->private_data; + unsigned long maxsize; - count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ - if(count <= 0) + if (pos > MIXART_BA1_SIZE) return 0; - if(pos + count > MIXART_BA1_SIZE) - count = (long)(MIXART_BA1_SIZE - pos); - if(copy_to_user_fromio(buf, MIXART_REG( mgr, pos ), count)) + maxsize = MIXART_BA1_SIZE - pos; + if (count > maxsize) + count = maxsize; + count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ + if (copy_to_user_fromio(buf, MIXART_REG(mgr, pos), count)) return -EFAULT; return count; } diff --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c index c1eb923f2ac9..09b2b2a36df5 100644 --- a/sound/pci/oxygen/oxygen_io.c +++ b/sound/pci/oxygen/oxygen_io.c @@ -215,17 +215,8 @@ EXPORT_SYMBOL(oxygen_write_spi); void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data) { - unsigned long timeout; - /* should not need more than about 300 us */ - timeout = jiffies + msecs_to_jiffies(1); - do { - if (!(oxygen_read16(chip, OXYGEN_2WIRE_BUS_STATUS) - & OXYGEN_2WIRE_BUSY)) - break; - udelay(1); - cond_resched(); - } while (time_after_eq(timeout, jiffies)); + msleep(1); oxygen_write8(chip, OXYGEN_2WIRE_MAP, map); oxygen_write8(chip, OXYGEN_2WIRE_DATA, data); diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index acfa4760da49..91683a349035 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1626,7 +1626,7 @@ static int snd_via8233_dxs_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct via82xx *chip = snd_kcontrol_chip(kcontrol); - unsigned int idx = snd_ctl_get_ioff(kcontrol, &ucontrol->id); + unsigned int idx = kcontrol->id.subdevice; ucontrol->value.integer.value[0] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][0]; ucontrol->value.integer.value[1] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][1]; @@ -1646,7 +1646,7 @@ static int snd_via8233_dxs_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct via82xx *chip = snd_kcontrol_chip(kcontrol); - unsigned int idx = snd_ctl_get_ioff(kcontrol, &ucontrol->id); + unsigned int idx = kcontrol->id.subdevice; unsigned long port = chip->port + 0x10 * idx; unsigned char val; int i, change = 0; @@ -1705,11 +1705,12 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control __devinitdata = }; static struct snd_kcontrol_new snd_via8233_dxs_volume_control __devinitdata = { - .name = "VIA DXS Playback Volume", - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .device = 0, + /* .subdevice set later */ + .name = "PCM Playback Volume", .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), - .count = 4, .info = snd_via8233_dxs_volume_info, .get = snd_via8233_dxs_volume_get, .put = snd_via8233_dxs_volume_put, @@ -1936,10 +1937,18 @@ static int __devinit snd_via8233_init_misc(struct via82xx *chip) } else /* Using DXS when PCM emulation is enabled is really weird */ { - /* Standalone DXS controls */ - err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_dxs_volume_control, chip)); - if (err < 0) - return err; + for (i = 0; i < 4; ++i) { + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1( + &snd_via8233_dxs_volume_control, chip); + if (!kctl) + return -ENOMEM; + kctl->id.subdevice = i; + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } } } /* select spdif data slot 10/11 */ diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c b/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c index d057e6489643..5cfa608823f7 100644 --- a/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c +++ b/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c @@ -51,7 +51,7 @@ static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t s return 0; /* already enough large */ vfree(runtime->dma_area); } - runtime->dma_area = vmalloc_32(size); + runtime->dma_area = vmalloc_32_user(size); if (! runtime->dma_area) return -ENOMEM; runtime->dma_bytes = size; diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c index 835fa19ed461..bb6819aab133 100644 --- a/sound/ppc/keywest.c +++ b/sound/ppc/keywest.c @@ -59,6 +59,18 @@ static int keywest_attach_adapter(struct i2c_adapter *adapter) strlcpy(info.type, "keywest", I2C_NAME_SIZE); info.addr = keywest_ctx->addr; keywest_ctx->client = i2c_new_device(adapter, &info); + if (!keywest_ctx->client) + return -ENODEV; + /* + * We know the driver is already loaded, so the device should be + * already bound. If not it means binding failed, and then there + * is no point in keeping the device instantiated. + */ + if (!keywest_ctx->client->driver) { + i2c_unregister_device(keywest_ctx->client); + keywest_ctx->client = NULL; + return -ENODEV; + } /* * Let i2c-core delete that device on driver removal. diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4362689dd639..dd28cad09934 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_CS42888 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -90,6 +91,9 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_CS42888 + tristate + config SND_SOC_L3 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8add6333d7c5..8fd4da08392c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak5702-objs := ak5702.o snd-soc-cs4270-objs := cs4270.o +snd-soc-cs42888-objs := cs42888.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o @@ -50,6 +51,7 @@ obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK5702) += snd-soc-ak5702.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_CS42888) += snd-soc-cs42888.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 diff --git a/sound/soc/codecs/cs42888.c b/sound/soc/codecs/cs42888.c new file mode 100644 index 000000000000..e9288cc9a1ca --- /dev/null +++ b/sound/soc/codecs/cs42888.c @@ -0,0 +1,1196 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#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/spi/spi.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.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/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "cs42888.h" + +#define CS42888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* CS42888 registers addresses */ +#define CS42888_CHIPID 0x01 /* Chip ID */ +#define CS42888_PWRCTL 0x02 /* Power Control */ +#define CS42888_MODE 0x03 /* Functional Mode */ +#define CS42888_FORMAT 0x04 /* Interface Formats */ +#define CS42888_ADCCTL 0x05 /* ADC Control */ +#define CS42888_TRANS 0x06 /* Transition Control */ +#define CS42888_MUTE 0x07 /* Mute Control */ +#define CS42888_VOLAOUT1 0x08 /* Volume Control AOUT1*/ +#define CS42888_VOLAOUT2 0x09 /* Volume Control AOUT2*/ +#define CS42888_VOLAOUT3 0x0A /* Volume Control AOUT3*/ +#define CS42888_VOLAOUT4 0x0B /* Volume Control AOUT4*/ +#define CS42888_VOLAOUT5 0x0C /* Volume Control AOUT5*/ +#define CS42888_VOLAOUT6 0x0D /* Volume Control AOUT6*/ +#define CS42888_VOLAOUT7 0x0E /* Volume Control AOUT7*/ +#define CS42888_VOLAOUT8 0x0F /* Volume Control AOUT8*/ +#define CS42888_DACINV 0x10 /* DAC Channel Invert */ +#define CS42888_VOLAIN1 0x11 /* Volume Control AIN1 */ +#define CS42888_VOLAIN2 0x12 /* Volume Control AIN2 */ +#define CS42888_VOLAIN3 0x13 /* Volume Control AIN3 */ +#define CS42888_VOLAIN4 0x14 /* Volume Control AIN4 */ +#define CS42888_ADCINV 0x17 /* ADC Channel Invert */ +#define CS42888_STATUSCTL 0x18 /* Status Control */ +#define CS42888_STATUS 0x19 /* Status */ +#define CS42888_STATUSMASK 0x1A /* Status Mask */ + +#define CS42888_FIRSTREG 0x01 +#define CS42888_LASTREG 0x1A +#define CS42888_NUMREGS (CS42888_LASTREG - CS42888_FIRSTREG + 1) +#define CS42888_I2C_INCR 0x80 + +/* Bit masks for the CS42888 registers */ +#define CS42888_CHIPID_ID_MASK 0xF0 +#define CS42888_CHIPID_REV 0x0F +#define CS42888_PWRCTL_PDN_ADC2_OFFSET 6 +#define CS42888_PWRCTL_PDN_ADC1_OFFSET 5 +#define CS42888_PWRCTL_PDN_DAC4_OFFSET 4 +#define CS42888_PWRCTL_PDN_DAC3_OFFSET 3 +#define CS42888_PWRCTL_PDN_DAC2_OFFSET 2 +#define CS42888_PWRCTL_PDN_DAC1_OFFSET 1 +#define CS42888_PWRCTL_PDN_OFFSET 0 +#define CS42888_PWRCTL_PDN_ADC2_MASK (1 << CS42888_PWRCTL_PDN_ADC2_OFFSET) +#define CS42888_PWRCTL_PDN_ADC1_MASK (1 << CS42888_PWRCTL_PDN_ADC1_OFFSET) +#define CS42888_PWRCTL_PDN_DAC4_MASK (1 << CS42888_PWRCTL_PDN_DAC4_OFFSET) +#define CS42888_PWRCTL_PDN_DAC3_MASK (1 << CS42888_PWRCTL_PDN_DAC3_OFFSET) +#define CS42888_PWRCTL_PDN_DAC2_MASK (1 << CS42888_PWRCTL_PDN_DAC2_OFFSET) +#define CS42888_PWRCTL_PDN_DAC1_MASK (1 << CS42888_PWRCTL_PDN_DAC1_OFFSET) +#define CS42888_PWRCTL_PDN_MASK (1 << CS42888_PWRCTL_PDN_OFFSET) + +#define CS42888_MODE_SPEED_MASK 0xF0 +#define CS42888_MODE_1X 0x00 +#define CS42888_MODE_2X 0x50 +#define CS42888_MODE_4X 0xA0 +#define CS42888_MODE_SLAVE 0xF0 +#define CS42888_MODE_DIV_MASK 0x0E +#define CS42888_MODE_DIV1 0x00 +#define CS42888_MODE_DIV2 0x02 +#define CS42888_MODE_DIV3 0x04 +#define CS42888_MODE_DIV4 0x06 +#define CS42888_MODE_DIV5 0x08 + +#define CS42888_FORMAT_FREEZE_OFFSET 7 +#define CS42888_FORMAT_AUX_DIF_OFFSET 6 +#define CS42888_FORMAT_DAC_DIF_OFFSET 3 +#define CS42888_FORMAT_ADC_DIF_OFFSET 0 +#define CS42888_FORMAT_FREEZE_MASK (1 << CS42888_FORMAT_FREEZE_OFFSET) +#define CS42888_FORMAT_AUX_DIF_MASK (1 << CS42888_FORMAT_AUX_DIF_OFFSET) +#define CS42888_FORMAT_DAC_DIF_MASK (7 << CS42888_FORMAT_DAC_DIF_OFFSET) +#define CS42888_FORMAT_ADC_DIF_MASK (7 << CS42888_FORMAT_ADC_DIF_OFFSET) + +#define CS42888_TRANS_DAC_SNGVOL_OFFSET 7 +#define CS42888_TRANS_DAC_SZC_OFFSET 5 +#define CS42888_TRANS_AMUTE_OFFSET 4 +#define CS42888_TRANS_MUTE_ADC_SP_OFFSET 3 +#define CS42888_TRANS_ADC_SNGVOL_OFFSET 2 +#define CS42888_TRANS_ADC_SZC_OFFSET 0 +#define CS42888_TRANS_DAC_SNGVOL_MASK (1 << CS42888_TRANS_DAC_SNGVOL_OFFSET) +#define CS42888_TRANS_DAC_SZC_MASK (3 << CS42888_TRANS_DAC_SZC_OFFSET) +#define CS42888_TRANS_AMUTE_MASK (1 << CS42888_TRANS_AMUTE_OFFSET) +#define CS42888_TRANS_MUTE_ADC_SP_MASK (1 << CS42888_TRANS_MUTE_ADC_SP_OFFSET) +#define CS42888_TRANS_ADC_SNGVOL_MASK (1 << CS42888_TRANS_ADC_SNGVOL_OFFSET) +#define CS42888_TRANS_ADC_SZC_MASK (3 << CS42888_TRANS_ADC_SZC_OFFSET) + +#define CS42888_MUTE_AOUT8 (0x1 << 7) +#define CS42888_MUTE_AOUT7 (0x1 << 6) +#define CS42888_MUTE_AOUT6 (0x1 << 5) +#define CS42888_MUTE_AOUT5 (0x1 << 4) +#define CS42888_MUTE_AOUT4 (0x1 << 3) +#define CS42888_MUTE_AOUT3 (0x1 << 2) +#define CS42888_MUTE_AOUT2 (0x1 << 1) +#define CS42888_MUTE_AOUT1 (0x1 << 0) +#define CS42888_MUTE_ALL (CS42888_MUTE_AOUT1 | CS42888_MUTE_AOUT2 | \ + CS42888_MUTE_AOUT3 | CS42888_MUTE_AOUT4 | \ + CS42888_MUTE_AOUT5 | CS42888_MUTE_AOUT6 | \ + CS42888_MUTE_AOUT7 | CS42888_MUTE_AOUT8) + +#define DIF_LEFT_J 0 +#define DIF_I2S 1 +#define DIF_RIGHT_J 2 +#define DIF_TDM 6 + +/* Private data for the CS42888 */ +struct cs42888_private { + struct snd_soc_codec codec; + u8 reg_cache[CS42888_NUMREGS]; + 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; + struct regulator *regulator_vsd; +}; + +static struct i2c_client *cs42888_i2c_client; + +int cs42888_read_reg(unsigned int reg, u8 *value) +{ + s32 retval; + retval = i2c_smbus_read_byte_data(cs42888_i2c_client, reg); + if (retval < 0) { + pr_err("%s:read reg errorr:reg=%x,val=%x\n", + __func__, reg, retval); + return -1; + } else { + *value = (u8) retval; + return 0; + } +} + +int cs42888_write_reg(unsigned int reg, u8 value) +{ + if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value) < 0) { + pr_err("%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} +/** + * cs42888_fill_cache - pre-fill the CS42888 register cache. + * @codec: the codec for this CS42888 + * + * This function fills in the CS42888 register cache by reading the register + * values from the hardware. + * + * This CS42888 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS42888 never updates + * the register values, so we won't have a cache coherency problem. + * + * We use the auto-increment feature of the CS42888 to read all registers in + * one shot. + */ +static int cs42888_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS42888_FIRSTREG | CS42888_I2C_INCR, CS42888_NUMREGS, cache); + + if (length != CS42888_NUMREGS) { + dev_err(codec->dev, "i2c read failure, addr=0x%x\n", + i2c_client->addr); + return -EIO; + } + + return 0; +} + +/** + * cs42888_read_reg_cache - read from the CS42888 register cache. + * @codec: the codec for this CS42888 + * @reg: the register to read + * + * This function returns the value for a given register. It reads only from + * the register cache, not the hardware itself. + * + * This CS42888 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS42888 never updates + * the register values, so we won't have a cache coherency problem. + */ +static u8 cs42888_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG)) + return -EIO; + + return cache[reg - CS42888_FIRSTREG]; +} + +/** + * cs42888_i2c_write - write to a CS42888 register via the I2C bus. + * @codec: the codec for this CS42888 + * @reg: the register to write + * @value: the value to write to the register + * + * This function writes the given value to the given CS42888 register, and + * also updates the register cache. + * + * Note that we don't use the hw_write function pointer of snd_soc_codec. + * That's because it's too clunky: the hw_write_t prototype does not match + * i2c_smbus_write_byte_data(), and it's just another layer of overhead. + */ +static int cs42888_i2c_write(struct snd_soc_codec *codec, unsigned int reg, + u8 value) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG)) + return -EIO; + + /* Only perform an I2C operation if the new value is different */ + if (cache[reg - CS42888_FIRSTREG] != value) { + if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value) + < 0) { + dev_err(codec->dev, "i2c write failed\n"); + return -EIO; + } + + /* We've written to the hardware, so update the cache */ + cache[reg - CS42888_FIRSTREG] = value; + } + + return 0; +} + +#ifdef CS42888_DEBUG +static void dump_reg(struct snd_soc_codec *codec) +{ + int i, reg; + int ret; + printk(KERN_DEBUG "dump begin\n"); + printk(KERN_DEBUG "reg value in cache\n"); + for (i = 0; i < CS42888_NUMREGS; i++) + printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]); + + printk(KERN_DEBUG "real reg value\n"); + ret = cs42888_fill_cache(codec); + if (ret < 0) { + pr_err("failed to fill register cache\n"); + return ret; + } + for (i = 0; i < CS42888_NUMREGS; i++) + printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]); + + printk(KERN_DEBUG "dump end\n"); +} +#else +static void dump_reg(struct snd_soc_codec *codec) +{ +} +#endif + +/* -127.5dB to 0dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +/* -64dB to 24dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 1); + +static int cs42888_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int ret; + u16 val; + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + val = cs42888_read_reg_cache(codec, reg); + ret = cs42888_i2c_write(codec, reg, val); + + val = cs42888_read_reg_cache(codec, reg2); + ret = cs42888_i2c_write(codec, reg2, val); + return 0; +} + +int cs42888_info_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max-min; + return 0; +} + +int cs42888_get_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int min = mc->min; + int val = cs42888_read_reg_cache(codec, reg); + + ucontrol->value.integer.value[0] = + ((signed char)(val))-min; + + val = cs42888_read_reg_cache(codec, reg2); + ucontrol->value.integer.value[1] = + ((signed char)(val))-min; + return 0; +} + +int cs42888_put_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int min = mc->min; + unsigned short val; + int ret; + + val = (ucontrol->value.integer.value[0]+min); + ret = cs42888_i2c_write(codec, reg, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + val = ((ucontrol->value.integer.value[1]+min)); + ret = cs42888_i2c_write(codec, reg2, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + return 0; +} + +#define SOC_CS42888_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \ + xinvert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, \ + .put = cs42888_out_vu, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, \ + .rreg = reg_right, \ + .shift = xshift, \ + .max = xmax, \ + .invert = xinvert} \ +} + +#define SOC_CS42888_DOUBLE_R_S8_TLV(xname, reg_left, reg_right, xmin, xmax, \ + tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = cs42888_info_volsw_s8, .get = cs42888_get_volsw_s8, \ + .put = cs42888_put_volsw_s8, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, \ + .rreg = reg_right, \ + .min = xmin, \ + .max = xmax} \ +} + +static const char *cs42888_adcfilter[] = { "None", "High Pass" }; +static const char *cs42888_dacinvert[] = { "Disabled", "Enabled" }; +static const char *cs42888_adcinvert[] = { "Disabled", "Enabled" }; +static const char *cs42888_dacamute[] = { "Disabled", "AutoMute" }; +static const char *cs42888_dac_sngvol[] = { "Disabled", "Enabled" }; +static const char *cs42888_dac_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; +static const char *cs42888_mute_adc[] = { "UnMute", "Mute" }; +static const char *cs42888_adc_sngvol[] = { "Disabled", "Enabled" }; +static const char *cs42888_adc_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; +static const char *cs42888_dac_dem[] = { "No-De-Emphasis", "De-Emphasis" }; +static const char *cs42888_adc_single[] = { "Differential", "Single-Ended" }; + +static const struct soc_enum cs42888_enum[] = { + SOC_ENUM_SINGLE(CS42888_ADCCTL, 7, 2, cs42888_adcfilter), + SOC_ENUM_DOUBLE(CS42888_DACINV, 0, 1, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 2, 3, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 4, 5, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 6, 7, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_ADCINV, 0, 1, 2, cs42888_adcinvert), + SOC_ENUM_DOUBLE(CS42888_ADCINV, 2, 3, 2, cs42888_adcinvert), + SOC_ENUM_SINGLE(CS42888_TRANS, 4, 2, cs42888_dacamute), + SOC_ENUM_SINGLE(CS42888_TRANS, 7, 2, cs42888_dac_sngvol), + SOC_ENUM_SINGLE(CS42888_TRANS, 5, 4, cs42888_dac_szc), + SOC_ENUM_SINGLE(CS42888_TRANS, 3, 2, cs42888_mute_adc), + SOC_ENUM_SINGLE(CS42888_TRANS, 2, 2, cs42888_adc_sngvol), + SOC_ENUM_SINGLE(CS42888_TRANS, 0, 4, cs42888_adc_szc), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 5, 2, cs42888_dac_dem), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 4, 2, cs42888_adc_single), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 3, 2, cs42888_adc_single), +}; + +static const struct snd_kcontrol_new cs42888_snd_controls[] = { +SOC_CS42888_DOUBLE_R_TLV("DAC1 Playback Volume", + CS42888_VOLAOUT1, + CS42888_VOLAOUT2, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC2 Playback Volume", + CS42888_VOLAOUT3, + CS42888_VOLAOUT4, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC3 Playback Volume", + CS42888_VOLAOUT5, + CS42888_VOLAOUT6, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC4 Playback Volume", + CS42888_VOLAOUT7, + CS42888_VOLAOUT8, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_S8_TLV("ADC1 Capture Volume", + CS42888_VOLAIN1, + CS42888_VOLAIN2, + -128, 48, adc_tlv), +SOC_CS42888_DOUBLE_R_S8_TLV("ADC2 Capture Volume", + CS42888_VOLAIN3, + CS42888_VOLAIN4, + -128, 48, adc_tlv), +SOC_ENUM("ADC High-Pass Filter Switch", cs42888_enum[0]), +SOC_ENUM("DAC1 Invert Switch", cs42888_enum[1]), +SOC_ENUM("DAC2 Invert Switch", cs42888_enum[2]), +SOC_ENUM("DAC3 Invert Switch", cs42888_enum[3]), +SOC_ENUM("DAC4 Invert Switch", cs42888_enum[4]), +SOC_ENUM("ADC1 Invert Switch", cs42888_enum[5]), +SOC_ENUM("ADC2 Invert Switch", cs42888_enum[6]), +SOC_ENUM("DAC Auto Mute Switch", cs42888_enum[7]), +SOC_ENUM("DAC Single Volume Control Switch", cs42888_enum[8]), +SOC_ENUM("DAC Soft Ramp and Zero Cross Control Switch", cs42888_enum[9]), +SOC_ENUM("Mute ADC Serial Port Switch", cs42888_enum[10]), +SOC_ENUM("ADC Single Volume Control Switch", cs42888_enum[11]), +SOC_ENUM("ADC Soft Ramp and Zero Cross Control Switch", cs42888_enum[12]), +SOC_ENUM("DAC Deemphasis Switch", cs42888_enum[13]), +SOC_ENUM("ADC1 Single Ended Mode Switch", cs42888_enum[14]), +SOC_ENUM("ADC2 Single Ended Mode Switch", cs42888_enum[15]), +}; + + +static const struct snd_soc_dapm_widget cs42888_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", CS42888_PWRCTL, 1, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", CS42888_PWRCTL, 2, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", CS42888_PWRCTL, 3, 1), +SND_SOC_DAPM_DAC("DAC4", "Playback", CS42888_PWRCTL, 4, 1), + +SND_SOC_DAPM_OUTPUT("AOUT1L"), +SND_SOC_DAPM_OUTPUT("AOUT1R"), +SND_SOC_DAPM_OUTPUT("AOUT2L"), +SND_SOC_DAPM_OUTPUT("AOUT2R"), +SND_SOC_DAPM_OUTPUT("AOUT3L"), +SND_SOC_DAPM_OUTPUT("AOUT3R"), +SND_SOC_DAPM_OUTPUT("AOUT4L"), +SND_SOC_DAPM_OUTPUT("AOUT4R"), + +SND_SOC_DAPM_ADC("ADC1", "Capture", CS42888_PWRCTL, 5, 1), +SND_SOC_DAPM_ADC("ADC2", "Capture", CS42888_PWRCTL, 6, 1), + +SND_SOC_DAPM_INPUT("AIN1L"), +SND_SOC_DAPM_INPUT("AIN1R"), +SND_SOC_DAPM_INPUT("AIN2L"), +SND_SOC_DAPM_INPUT("AIN2R"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, +}; + + +static int ca42888_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, cs42888_dapm_widgets, + ARRAY_SIZE(cs42888_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/** + * struct cs42888_mode_ratios - clock ratio tables + * @ratio: the ratio of MCLK to the sample rate + * @speed_mode: the Speed Mode bits to set in the Mode Control register for + * this ratio + * @mclk: the Ratio Select bits to set in the Mode Control register for this + * ratio + * + * The data for this chart is taken from Table 10 of the CS42888 reference + * manual. + * + * This table is used to determine how to program the Functional Mode register. + * It is also used by cs42888_set_dai_sysclk() to tell ALSA which sampling + * rates the CS42888 currently supports. + * + * @speed_mode is the corresponding bit pattern to be written to the + * MODE bits of the Mode Control Register + * + * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of + * the Mode Control Register. + * + */ +struct cs42888_mode_ratios { + unsigned int ratio; + u8 speed_mode; + u8 mclk; +}; + +static struct cs42888_mode_ratios cs42888_mode_ratios[] = { + {64, CS42888_MODE_4X, CS42888_MODE_DIV1}, + {96, CS42888_MODE_4X, CS42888_MODE_DIV2}, + {128, CS42888_MODE_2X, CS42888_MODE_DIV1}, + {192, CS42888_MODE_2X, CS42888_MODE_DIV2}, + {256, CS42888_MODE_1X, CS42888_MODE_DIV1}, + {384, CS42888_MODE_2X, CS42888_MODE_DIV4}, + {512, CS42888_MODE_1X, CS42888_MODE_DIV3}, + {768, CS42888_MODE_1X, CS42888_MODE_DIV4}, + {1024, CS42888_MODE_1X, CS42888_MODE_DIV5} +}; + +/* The number of MCLK/LRCK ratios supported by the CS42888 */ +#define NUM_MCLK_RATIOS ARRAY_SIZE(cs42888_mode_ratios) + +/** + * cs42888_set_dai_sysclk - determine the CS42888 samples rates. + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * This function is used to tell the codec driver what the input MCLK + * frequency is. + * + */ +static int cs42888_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 cs42888_private *cs42888 = codec->private_data; + + cs42888->mclk = freq; + + return 0; +} + +/** + * cs42888_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @format: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + * + * Currently, this function only supports SND_SOC_DAIFMT_I2S and + * SND_SOC_DAIFMT_LEFT_J. The CS42888 codec also supports right-justified + * data for playback only, but ASoC currently does not support different + * formats for playback vs. record. + */ +static int cs42888_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42888_private *cs42888 = codec->private_data; + int ret = 0; + u8 val; + val = cs42888_read_reg_cache(codec, CS42888_FORMAT); + val &= ~CS42888_FORMAT_DAC_DIF_MASK; + val &= ~CS42888_FORMAT_ADC_DIF_MASK; + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val |= DIF_LEFT_J << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_LEFT_J << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + case SND_SOC_DAIFMT_I2S: + val |= DIF_I2S << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_I2S << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= DIF_RIGHT_J << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_RIGHT_J << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + default: + dev_err(codec->dev, "invalid dai format\n"); + ret = -EINVAL; + return ret; + } + + ret = cs42888_i2c_write(codec, CS42888_FORMAT, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + val = cs42888_read_reg_cache(codec, CS42888_MODE); + /* set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42888->slave_mode = 1; + val &= ~CS42888_MODE_SPEED_MASK; + val |= CS42888_MODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42888->slave_mode = 0; + break; + default: + /* all other modes are unsupported by the hardware */ + ret = -EINVAL; + return ret; + } + + ret = cs42888_i2c_write(codec, CS42888_MODE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + return ret; +} + +/** + * cs42888_hw_params - program the CS42888 with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + * + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static int cs42888_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 cs42888_private *cs42888 = codec->private_data; + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + u8 val; + + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs42888->mclk / rate; /* MCLK/LRCK ratio */ + + for (i = 0; i < NUM_MCLK_RATIOS; i++) { + if (cs42888_mode_ratios[i].ratio == ratio) + break; + } + + if (i == NUM_MCLK_RATIOS) { + /* We did not find a matching ratio */ + dev_err(codec->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + if (!cs42888->slave_mode) { + val = cs42888_read_reg_cache(codec, CS42888_MODE); + val &= ~CS42888_MODE_SPEED_MASK; + val |= cs42888_mode_ratios[i].speed_mode; + val &= ~CS42888_MODE_DIV_MASK; + val |= cs42888_mode_ratios[i].mclk; + } else { + val = cs42888_read_reg_cache(codec, CS42888_MODE); + val &= ~CS42888_MODE_SPEED_MASK; + val |= CS42888_MODE_SLAVE; + } + ret = cs42888_i2c_write(codec, CS42888_MODE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + /* Out of low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val &= ~CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + /* Unmute all the channels */ + val = cs42888_read_reg_cache(codec, CS42888_MUTE); + val &= ~CS42888_MUTE_ALL; + ret = cs42888_i2c_write(codec, CS42888_MUTE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + ret = cs42888_fill_cache(codec); + if (ret < 0) { + pr_err("failed to fill register cache\n"); + return ret; + } + + return ret; +} + +/** + * cs42888_shutdown - cs42888 enters into low power mode again. + * @substream: the audio stream + * @dai: the SOC DAI (ignored) + * + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static void cs42888_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; + int ret; + u8 val; + + /* Mute all the channels */ + val = cs42888_read_reg_cache(codec, CS42888_MUTE); + val |= CS42888_MUTE_ALL; + ret = cs42888_i2c_write(codec, CS42888_MUTE, val); + if (ret < 0) + pr_err("i2c write failed\n"); + + /* Enter low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val |= CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) + pr_err("i2c write failed\n"); +} + +/* + * cs42888_codec - global variable to store codec for the ASoC probe function + * + * If struct i2c_driver had a private_data field, we wouldn't need to use + * cs42888_codec. This is the only way to pass the codec structure from + * cs42888_i2c_probe() to cs42888_probe(). Unfortunately, there is no good + * way to synchronize these two functions. cs42888_i2c_probe() can be called + * multiple times before cs42888_probe() is called even once. So for now, we + * also only allow cs42888_i2c_probe() to be run once. That means that we do + * not support more than one cs42888 device in the system, at least for now. + */ +static struct snd_soc_codec *cs42888_codec; + +static struct snd_soc_dai_ops cs42888_dai_ops = { + .set_fmt = cs42888_set_dai_fmt, + .set_sysclk = cs42888_set_dai_sysclk, + .hw_params = cs42888_hw_params, + .shutdown = cs42888_shutdown, +}; + +struct snd_soc_dai cs42888_dai = { + .name = "CS42888", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_176400), + .formats = CS42888_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_176400), + .formats = CS42888_FORMATS, + }, + .ops = &cs42888_dai_ops, +}; +EXPORT_SYMBOL_GPL(cs42888_dai); + +/** + * cs42888_probe - ASoC probe function + * @pdev: platform device + * + * This function is called when ASoC has all the pieces it needs to + * instantiate a sound driver. + */ +static int cs42888_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = cs42888_codec; + int ret; + + /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ + socdev->card->codec = 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\n"); + return ret; + } + + /* Add the non-DAPM controls */ + ret = snd_soc_add_controls(codec, cs42888_snd_controls, + ARRAY_SIZE(cs42888_snd_controls)); + if (ret < 0) { + dev_err(codec->dev, "failed to add controls\n"); + goto error_free_pcms; + } + + /* Add DAPM controls */ + ca42888_add_widgets(codec); + + /* And finally, register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + goto error_free_pcms; + } + + return 0; + +error_free_pcms: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return ret; +} + +/** + * cs42888_remove - ASoC remove function + * @pdev: platform device + * + * This function is the counterpart to cs42888_probe(). + */ +static int cs42888_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; +}; + + +/** + * cs42888_i2c_probe - initialize the I2C interface of the CS42888 + * @i2c_client: the I2C client object + * @id: the I2C device ID (ignored) + * + * This function is called whenever the I2C subsystem finds a device that + * matches the device ID given via a prior call to i2c_add_driver(). + */ +static int cs42888_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct cs42888_private *cs42888; + int ret; + struct regulator *regulator_vsd; + u8 val; + + if (cs42888_codec) { + dev_err(&i2c_client->dev, + "Multiple CS42888 devices not supported\n"); + return -ENOMEM; + } + + cs42888_i2c_client = i2c_client; + + /* Allocate enough space for the snd_soc_codec structure + and our private data together. */ + cs42888 = kzalloc(sizeof(struct cs42888_private), GFP_KERNEL); + if (!cs42888) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + + /* hold on reset */ + gpio_cs42888_pdwn(1); + + regulator_vsd = regulator_get(&i2c_client->dev, "VSD"); + if (!IS_ERR(regulator_vsd)) + cs42888->regulator_vsd = regulator_vsd; + + if (cs42888->regulator_vsd) { + regulator_set_voltage(cs42888->regulator_vsd, + 2800000, 2800000); + if (regulator_enable(cs42888->regulator_vsd) != 0) { + pr_err("%s:VSD set voltage error\n", __func__); + } else { + dev_dbg(&i2c_client->dev, + "%s:io set voltage ok\n", __func__); + } + } + + msleep(1); + /* out of reset state */ + gpio_cs42888_pdwn(0); + + /* Verify that we have a CS42888 */ + ret = cs42888_read_reg(CS42888_CHIPID, &val); + if (ret < 0) { + pr_err("Device with ID register %x is not a CS42888", val); + return -ENODEV; + } + /* The top four bits of the chip ID should be 0000. */ + if ((val & CS42888_CHIPID_ID_MASK) != 0x00) { + dev_err(&i2c_client->dev, "device is not a CS42888\n"); + return -ENODEV; + } + + dev_info(&i2c_client->dev, "found device at i2c address %X\n", + i2c_client->addr); + dev_info(&i2c_client->dev, "hardware revision %X\n", val & 0xF); + + codec = &cs42888->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c_client, cs42888); + codec->control_data = i2c_client; + + codec->dev = &i2c_client->dev; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = cs42888; + codec->name = "CS42888"; + codec->owner = THIS_MODULE; + codec->read = cs42888_read_reg_cache; + codec->write = cs42888_i2c_write; + codec->dai = &cs42888_dai; + codec->num_dai = 1; + codec->reg_cache = cs42888->reg_cache; + codec->reg_cache_size = ARRAY_SIZE(cs42888->reg_cache); + + /* The I2C interface is set up, so pre-fill our register cache */ + ret = cs42888_fill_cache(codec); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to fill register cache\n"); + goto error_free_codec; + } + + /* Enter low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val |= CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) { + dev_err(&i2c_client->dev, "i2c write failed\n"); + return ret; + } + + /* Disable auto-mute */ + val = cs42888_read_reg_cache(codec, CS42888_TRANS); + val &= ~CS42888_TRANS_AMUTE_MASK; + ret = cs42888_i2c_write(codec, CS42888_TRANS, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + cs42888_dai.dev = &i2c_client->dev; + + cs42888_codec = codec; + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to register codec: %d\n", ret); + goto error_free_codec; + } + + ret = snd_soc_register_dai(&cs42888_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_codec; + } + + return 0; + +error_codec: + snd_soc_unregister_codec(codec); +error_free_codec: + kfree(cs42888); + cs42888_codec = NULL; + cs42888_dai.dev = NULL; + + return ret; +} + +/** + * cs42888_i2c_remove - remove an I2C device + * @i2c_client: the I2C client object + * + * This function is the counterpart to cs42888_i2c_probe(). + */ +static int cs42888_i2c_remove(struct i2c_client *i2c_client) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(i2c_client); + + snd_soc_unregister_dai(&cs42888_dai); + snd_soc_unregister_codec(&cs42888->codec); + kfree(cs42888); + cs42888_codec = NULL; + cs42888_dai.dev = NULL; + + return 0; +} + +/* + * cs42888_i2c_id - I2C device IDs supported by this driver + */ +static struct i2c_device_id cs42888_i2c_id[] = { + {"cs42888", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42888_i2c_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 cs42888_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs42888->codec; + int reg = snd_soc_read(codec, CS42888_PWRCTL) | CS42888_PWRCTL_PDN_MASK; + + return snd_soc_write(codec, CS42888_PWRCTL, reg); +} + +static int cs42888_i2c_resume(struct i2c_client *client) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs42888->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 = CS42888_FIRSTREG; reg <= CS42888_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, CS42888_PWRCTL); + reg &= ~CS42888_PWRCTL_PDN_MASK; + + return snd_soc_write(codec, CS42888_PWRCTL, reg); +} +#else +#define cs42888_i2c_suspend NULL +#define cs42888_i2c_resume NULL +#endif /* CONFIG_PM */ + +/* + * cs42888_i2c_driver - I2C device identification + * + * This structure tells the I2C subsystem how to identify and support a + * given I2C device type. + */ +static struct i2c_driver cs42888_i2c_driver = { + .driver = { + .name = "cs42888", + .owner = THIS_MODULE, + }, + .id_table = cs42888_i2c_id, + .probe = cs42888_i2c_probe, + .remove = cs42888_i2c_remove, + .suspend = cs42888_i2c_suspend, + .resume = cs42888_i2c_resume, +}; + +/* + * ASoC codec device structure + * + * Assign this variable to the codec_dev field of the machine driver's + * snd_soc_device structure. + */ +struct snd_soc_codec_device soc_codec_device_cs42888 = { + .probe = cs42888_probe, + .remove = cs42888_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_cs42888); + +static int __init cs42888_init(void) +{ + pr_info("Cirrus Logic CS42888 ALSA SoC Codec Driver\n"); + + return i2c_add_driver(&cs42888_i2c_driver); +} +module_init(cs42888_init); + +static void __exit cs42888_exit(void) +{ + i2c_del_driver(&cs42888_i2c_driver); +} +module_exit(cs42888_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Cirrus Logic CS42888 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42888.h b/sound/soc/codecs/cs42888.h new file mode 100644 index 000000000000..a2c8562c17c5 --- /dev/null +++ b/sound/soc/codecs/cs42888.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _CS42888_H +#define _CS42888_H + +/* + * The ASoC codec DAI structure for the CS42888. Assign this structure to + * the .codec_dai field of your machine driver's snd_soc_dai_link structure. + */ +extern struct snd_soc_dai cs42888_dai; + +/* + * The ASoC codec device structure for the CS42888. Assign this structure + * to the .codec_dev field of your machine driver's snd_soc_device + * structure. + */ +extern struct snd_soc_codec_device soc_codec_device_cs42888; + +extern void gpio_cs42888_pdwn(int pdwn); +#endif diff --git a/sound/soc/codecs/mxs-adc-codec.c b/sound/soc/codecs/mxs-adc-codec.c index 246dab352342..f8f10619731a 100644 --- a/sound/soc/codecs/mxs-adc-codec.c +++ b/sound/soc/codecs/mxs-adc-codec.c @@ -48,6 +48,8 @@ #define BF(value, field) (((value) << BP_##field) & BM_##field) #endif +#define BM_RTC_PERSISTENT0_RELEASE_GND BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG) + #define REGS_RTC_BASE (IO_ADDRESS(RTC_PHYS_ADDR)) struct mxs_codec_priv { @@ -252,6 +254,47 @@ static int dac_put_volsw(struct snd_kcontrol *kcontrol, return 0; } +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Prepare powering up HP and SPEAKER output */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMU: + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + break; + case SND_SOC_DAPM_POST_PMD: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR); + break; + } + return 0; +} + +static int adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMD: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR); + break; + } + return 0; +} + static const char *mxs_codec_adc_input_sel[] = { "Mic", "Line In 1", "Head Phone", "Line In 2" }; @@ -282,8 +325,6 @@ static const struct snd_kcontrol_new mxs_snd_controls[] = { SOC_DOUBLE_R("DAC Playback Switch", DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1), SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1), - SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1), - SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1), /* Capture Volume */ SOC_DOUBLE_R("ADC Capture Volume", @@ -310,10 +351,10 @@ SOC_DAPM_ENUM("Route", mxs_codec_enum[2]); static const struct snd_soc_dapm_widget mxs_codec_widgets[] = { - SND_SOC_DAPM_ADC("Left ADC", "Left Capture", DAC_PWRDN_L, 8, 1), - SND_SOC_DAPM_ADC("Right ADC", "Right Capture", DAC_PWRDN_H, 0, 1), + SND_SOC_DAPM_ADC_E("ADC", "Capture", DAC_PWRDN_L, 8, 1, adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1), SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, &mxs_left_adc_controls), @@ -321,7 +362,10 @@ static const struct snd_soc_dapm_widget mxs_codec_widgets[] = { &mxs_right_adc_controls), SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0, &mxs_hp_controls), - + SND_SOC_DAPM_PGA_E("HP AMP", DAC_PWRDN_L, 0, 1, NULL, 0, pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA("SPEAKER AMP", DAC_PWRDN_H, 8, 1, NULL, 0), SND_SOC_DAPM_INPUT("LINE1L"), SND_SOC_DAPM_INPUT("LINE1R"), SND_SOC_DAPM_INPUT("LINE2L"), @@ -348,20 +392,23 @@ static const struct snd_soc_dapm_route intercon[] = { {"Right ADC Mux", "Head Phone", "HPR"}, /* ADC */ - {"Left ADC", NULL, "Left ADC Mux"}, - {"Right ADC", NULL, "Right ADC Mux"}, + {"ADC", NULL, "Left ADC Mux"}, + {"ADC", NULL, "Right ADC Mux"}, /* HP Mux */ {"HP Mux", "DAC Out", "DAC"}, {"HP Mux", "Line In 1", "LINE1L"}, {"HP Mux", "Line In 1", "LINE1R"}, + /* HP amp */ + {"HP AMP", NULL, "HP Mux"}, /* HP output */ - {"HPR", NULL, "HP MUX"}, - {"HPL", NULL, "HP MUX"}, + {"HPR", NULL, "HP AMP"}, + {"HPL", NULL, "HP AMP"}, /* Speaker amp */ - {"SPEAKER", NULL, "DAC"}, + {"SPEAKER AMP", NULL, "DAC"}, + {"SPEAKER", NULL, "SPEAKER AMP"}, }; static int mxs_codec_add_widgets(struct snd_soc_codec *codec) @@ -488,29 +535,84 @@ static int mxs_codec_hw_params(struct snd_pcm_substream *substream, static int mxs_codec_dig_mute(struct snd_soc_dai *dai, int mute) { + int l, r; + int ll, rr; + u32 reg, reg1, reg2; u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT; - u32 reg1 = 0; - u32 reg = 0; if (mute) { - reg1 = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); - reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(0x7f) | \ - BF_AUDIOOUT_HPVOL_VOL_RIGHT(0x7f); - __raw_writel(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + reg = __raw_readl(REGS_AUDIOOUT_BASE + \ + HW_AUDIOOUT_DACVOLUME); + + reg1 = reg & ~BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + reg1 = reg1 & ~BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + + l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + + /* fade out dac vol */ + while ((l > DAC_VOLUME_MIN) || (r > DAC_VOLUME_MIN)) { + l -= 0x8; + r -= 0x8; + ll = l > DAC_VOLUME_MIN ? l : DAC_VOLUME_MIN; + rr = r > DAC_VOLUME_MIN ? r : DAC_VOLUME_MIN; + reg2 = reg1 | BF_AUDIOOUT_DACVOLUME_VOLUME_LEFT(ll) + | BF_AUDIOOUT_DACVOLUME_VOLUME_RIGHT(rr); + __raw_writel(reg2, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + msleep(1); + } __raw_writel(dac_mask, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); - __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); - - __raw_writel(reg1, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); - } else { + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + reg = reg | dac_mask; + __raw_writel(reg, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + } else __raw_writel(dac_mask, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR); - __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + + return 0; +} + +static int mxs_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + pr_debug("dapm level %d\n", level); + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + if (codec->bias_level == SND_SOC_BIAS_ON) + break; + break; + + case SND_SOC_BIAS_PREPARE: /* partial On */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE) + break; + /* Set Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + break; + + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + if (codec->bias_level == SND_SOC_BIAS_STANDBY) + break; + /* Unset Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + break; + + case SND_SOC_BIAS_OFF: /* Off, without power */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + break; + /* Unset Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + break; } + codec->bias_level = level; return 0; } @@ -533,6 +635,58 @@ static void mxs_codec_dac_set_vag(void) __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); } +static bool mxs_codec_dac_is_capless() +{ + if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN) + & BM_AUDIOOUT_PWRDN_CAPLESS) == 0) + return false; + else + return true; +} +static void mxs_codec_dac_arm_short_cm(bool bShort) +{ + __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_CM), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_CM_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + if (bShort) + __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_CM), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); +} +static void mxs_codec_dac_arm_short_lr(bool bShort) +{ + __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_LR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + if (bShort) + __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_LR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); +} +static void mxs_codec_dac_set_short_trip_level(u8 u8level) +{ + __raw_writel(__raw_readl(REGS_AUDIOOUT_BASE + + HW_AUDIOOUT_ANACTRL) + & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJL) + & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJR) + | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJL) + | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); +} +static void mxs_codec_dac_arm_short(bool bLatchCM, bool bLatchLR) +{ + if (bLatchCM) { + if (mxs_codec_dac_is_capless()) + mxs_codec_dac_arm_short_cm(true); + } else + mxs_codec_dac_arm_short_cm(false); + + if (bLatchLR) + mxs_codec_dac_arm_short_lr(true); + else + mxs_codec_dac_arm_short_lr(false); + +} static void mxs_codec_dac_power_on(struct mxs_codec_priv *mxs_adc) { @@ -542,17 +696,13 @@ mxs_codec_dac_power_on(struct mxs_codec_priv *mxs_adc) __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_CLR); - /* Set capless mode */ - __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, REGS_AUDIOOUT_BASE - + HW_AUDIOOUT_PWRDN_CLR); - /* 16 bit word length */ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); - /* Powerup DAC */ - __raw_writel(BM_AUDIOOUT_PWRDN_DAC, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + /* Arm headphone LR short protect */ + mxs_codec_dac_set_short_trip_level(0); + mxs_codec_dac_arm_short(false, true); /* Update DAC volume over zero crossings */ __raw_writel(BM_AUDIOOUT_DACVOLUME_EN_ZCD, @@ -566,30 +716,26 @@ mxs_codec_dac_power_on(struct mxs_codec_priv *mxs_adc) __raw_writel(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); - /* Prepare powering up HP output */ - __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); - __raw_writel(BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG), - REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); - __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); - __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + /* Mute HP output */ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); - __raw_writel(BM_AUDIOOUT_PWRDN_SPEAKER, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); /* Mute speaker amp */ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* Enable the audioout */ + __raw_writel(BM_AUDIOOUT_CTRL_RUN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); } static void mxs_codec_dac_power_down(struct mxs_codec_priv *mxs_adc) { + /* Disable the audioout */ + __raw_writel(BM_AUDIOOUT_CTRL_RUN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); /* Disable class AB */ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); @@ -858,7 +1004,8 @@ static int mxs_codec_probe(struct platform_device *pdev) snd_soc_free_pcms(socdev); return ret; } - + /* Set default bias level*/ + mxs_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } @@ -982,6 +1129,8 @@ static int __init mxs_codec_audio_probe(struct platform_device *pdev) codec->private_data = mxs_adc; codec->read = mxs_codec_read; codec->write = mxs_codec_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = mxs_codec_set_bias_level; codec->dai = &mxs_codec_dai; codec->num_dai = 1; codec->reg_cache_size = sizeof(mxs_audio_regs) >> 1; diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index a63b65cdc04d..dbe96a8e1d3b 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -200,39 +200,6 @@ static void dump_reg(struct snd_soc_codec *codec) } #endif -static int dac_mux_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - struct snd_soc_codec *codec = widget->codec; - unsigned int reg; - - if (ucontrol->value.enumerated.item[0]) { - reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); - reg |= SGTL5000_INT_OSC_EN; - sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); - - if (codec->bias_level != SND_SOC_BIAS_ON) { - sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - sgtl5000_set_bias_level(codec, SND_SOC_BIAS_ON); - } else - snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - - reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_CTRL); - reg &= ~(SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE); - sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg); - } else { - reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); - reg &= ~SGTL5000_INT_OSC_EN; - sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); - - snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - } - return 0; -} - static const char *adc_mux_text[] = { "MIC_IN", "LINE_IN" }; @@ -250,16 +217,8 @@ SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text); static const struct snd_kcontrol_new adc_mux = SOC_DAPM_ENUM("ADC Mux", adc_enum); -static const struct snd_kcontrol_new dac_mux = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "DAC Mux", - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE - | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = snd_soc_info_enum_double, - .get = snd_soc_dapm_get_enum_double, - .put = dac_mux_put, - .private_value = (unsigned long)&dac_enum, -}; +static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("DAC Mux", dac_enum); static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINE_IN"), @@ -783,7 +742,7 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, avoid pops. */ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); if (reg & SGTL5000_VAG_POWERUP) - delay = 400; + delay = 600; reg &= ~SGTL5000_VAG_POWERUP; reg |= SGTL5000_DAC_POWERUP; reg |= SGTL5000_HP_POWERUP; diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 0b8dcb5cd729..1365de0f7aec 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -625,11 +625,10 @@ static int tlv320aic23_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - int i; u16 reg; /* Sync reg_cache with the hardware */ - for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) { + for (reg = 0; reg < TLV320AIC23_RESET; reg++) { u16 val = tlv320aic23_read_reg_cache(codec, reg); tlv320aic23_write(codec, reg, val); } diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 921932ab2d3a..7b3963461234 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -595,6 +595,7 @@ static const struct snd_soc_dapm_route audio_map[] = { /* Mono Capture mixer-mux */ {"Capture Right Mixer", "Stereo", "Capture Right Mux"}, + {"Capture Left Mixer", "Stereo", "Capture Left Mux"}, {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"}, {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"}, {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"}, @@ -1265,6 +1266,13 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); + + /* wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); */ + /* + * Force the enable of the MICBIAS, otherwise the microphone will not + * work. + */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00e0); break; case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ @@ -1702,6 +1710,12 @@ static int wm8753_register(struct wm8753_priv *wm8753) wm8753_codec = codec; + /* Configure bclk as mclk/4 */ + reg = wm8753_read_reg_cache(codec, WM8753_SRATE2); + reg &= ~WM8753_BCLK_DIV_MASK; + reg |= WM8753_BCLK_DIV_4; + wm8753_write(codec, WM8753_SRATE2, reg); + for (i = 0; i < ARRAY_SIZE(wm8753_dai); i++) wm8753_dai[i].dev = codec->dev; diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h index 57b2ba244040..40c55ad713aa 100644 --- a/sound/soc/codecs/wm8753.h +++ b/sound/soc/codecs/wm8753.h @@ -99,6 +99,7 @@ #define WM8753_PCM_DIV_8 (7 << 6) /* BCLK clock dividers */ +#define WM8753_BCLK_DIV_MASK (7 << 3) #define WM8753_BCLK_DIV_1 (0 << 3) #define WM8753_BCLK_DIV_2 (1 << 3) #define WM8753_BCLK_DIV_4 (2 << 3) diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 1fd4e88f50cf..e9123f54202d 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -464,7 +464,8 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, { u16 *cache = codec->reg_cache; - soc_ac97_ops.write(codec->ac97, reg, val); + if (reg < 0x7c) + soc_ac97_ops.write(codec->ac97, reg, val); reg = reg >> 1; if (reg < (ARRAY_SIZE(wm9712_reg))) cache[reg] = val; diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 43b6d8e3b10c..afefe2c5585c 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -79,4 +79,14 @@ config SND_SOC_IMX_3STACK_BLUETOOTH help Say Y if you want to add support for Soc audio on IMX 3STACK with the BLUETOOTH + +config SND_SOC_IMX_3STACK_CS42888 + tristate "SoC Audio support for IMX - CS42888" + select SND_MXC_SOC_ESAI + select SND_SOC_CS42888 + help + Say Y if you want to add support for Soc audio on IMX 3STACK + with the CS42888 + + endif diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index 284be7777de7..7cc8d1f32674 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -24,3 +24,6 @@ snd-soc-imx-3stack-ak5702-objs := imx-3stack-ak5702.o obj-$(CONFIG_SND_SOC_IMX_3STACK_AK5702) += snd-soc-imx-3stack-ak5702.o snd-soc-imx-3stack-bt-objs := imx-3stack-bt.o obj-$(CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH) += snd-soc-imx-3stack-bt.o +snd-soc-imx-3stack-cs42888-objs := imx-3stack-cs42888.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_CS42888) += snd-soc-imx-3stack-cs42888.o + diff --git a/sound/soc/imx/imx-3stack-ak4647.c b/sound/soc/imx/imx-3stack-ak4647.c index 6d6a98fd02e0..56c7a498e5b1 100644 --- a/sound/soc/imx/imx-3stack-ak4647.c +++ b/sound/soc/imx/imx-3stack-ak4647.c @@ -363,8 +363,6 @@ static int __init imx_3stack_ak4647_probe(struct platform_device *pdev) imx_3stack_dai.cpu_dai = ak4647_cpu_dai; - /* Configure audio port 3 */ - gpio_activate_audio_ports(); imx_3stack_init_dam(dev_data->src_port, dev_data->ext_port); ret = request_irq(dev_data->intr_id_hp, imx_headphone_detect_handler, 0, @@ -388,7 +386,6 @@ err: static int __devexit imx_3stack_ak4647_remove(struct platform_device *pdev) { struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data; - gpio_inactivate_audio_ports(); free_irq(dev_data->intr_id_hp, NULL); driver_remove_file(pdev->dev.driver, &driver_attr_headphone); return 0; diff --git a/sound/soc/imx/imx-3stack-ak5702.c b/sound/soc/imx/imx-3stack-ak5702.c index 7603c0f0ae19..734399434aa9 100644 --- a/sound/soc/imx/imx-3stack-ak5702.c +++ b/sound/soc/imx/imx-3stack-ak5702.c @@ -1,7 +1,7 @@ /* * imx-3stack-ak5702.c -- SoC audio for imx_3stack * - * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -171,13 +171,11 @@ static int __devinit imx_3stack_ak5702_probe(struct platform_device *pdev) setup->i2c_address = 0x13; imx_3stack_snd_devdata.codec_data = setup; - gpio_activate_esai_ports(); return 0; } static int imx_3stack_ak5702_remove(struct platform_device *pdev) { - gpio_deactivate_esai_ports(); return 0; } diff --git a/sound/soc/imx/imx-3stack-cs42888.c b/sound/soc/imx/imx-3stack-cs42888.c new file mode 100644 index 000000000000..935a4e544029 --- /dev/null +++ b/sound/soc/imx/imx-3stack-cs42888.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/soc-dai.h> + +#include <mach/hardware.h> +#include <mach/clock.h> + +#include "imx-pcm.h" +#include "imx-esai.h" +#include "../codecs/cs42888.h" + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +#include <linux/mxc_asrc.h> +#endif + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +static unsigned int asrc_rates[] = { + 0, + 8000, + 11025, + 16000, + 22050, + 32000, + 44100, + 48000, + 64000, + 88200, + 96000, + 176400, + 192000, +}; + +struct asrc_esai { + unsigned int cpu_dai_rates; + unsigned int codec_dai_rates; + enum asrc_pair_index asrc_index; + unsigned int output_sample_rate; +}; + +static struct asrc_esai asrc_esai_data; + +#endif + +struct imx_3stack_pcm_state { + int lr_clk_active; +}; + +static struct imx_3stack_pcm_state clk_state; + +static int imx_3stack_startup(struct snd_pcm_substream *substream) +{ + clk_state.lr_clk_active++; +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate >= 32000) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + asrc_esai_data.cpu_dai_rates = cpu_dai->playback.rates; + asrc_esai_data.codec_dai_rates = codec_dai->playback.rates; + cpu_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + codec_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + } +#endif + + return 0; +} + +static void imx_3stack_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate >= 32000) { + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + codec_dai->playback.rates = asrc_esai_data.codec_dai_rates; + cpu_dai->playback.rates = asrc_esai_data.cpu_dai_rates; + asrc_release_pair(asrc_esai_data.asrc_index); + } +#endif + + clk_state.lr_clk_active--; +} + +static int imx_3stack_surround_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_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + unsigned int rate = params_rate(params); + u32 dai_format; + unsigned int mclk_freq = 0, lrclk_ratio = 0; + unsigned int channel = params_channels(params); + struct imx_esai *esai_mode = (struct imx_esai *)cpu_dai->private_data; + if (clk_state.lr_clk_active > 1) + return 0; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate >= 32000) { + unsigned int asrc_input_rate = rate; + struct mxc_runtime_data *pcm_data = + substream->runtime->private_data; + struct asrc_config config; + int retVal = 0;; + + retVal = asrc_req_pair(channel, &asrc_esai_data.asrc_index); + if (retVal < 0) { + pr_err("Fail to request asrc pair\n"); + return -1; + } + + config.pair = asrc_esai_data.asrc_index; + config.channel_num = channel; + config.input_sample_rate = asrc_input_rate; + config.output_sample_rate = asrc_esai_data.output_sample_rate; + config.inclk = INCLK_NONE; + config.word_width = 32; + config.outclk = OUTCLK_ESAI_TX; + retVal = asrc_config_pair(&config); + if (retVal < 0) { + pr_err("Fail to config asrc\n"); + asrc_release_pair(asrc_esai_data.asrc_index); + return retVal; + } + rate = asrc_esai_data.output_sample_rate; + pcm_data->asrc_index = asrc_esai_data.asrc_index; + pcm_data->asrc_enable = 1; + } +#endif + + switch (rate) { + case 32000: + lrclk_ratio = 3; + mclk_freq = 12288000; + break; + case 48000: + lrclk_ratio = 3; + mclk_freq = 12288000; + break; + case 64000: + lrclk_ratio = 1; + mclk_freq = 12288000; + break; + case 96000: + lrclk_ratio = 1; + mclk_freq = 12288000; + break; + case 128000: + lrclk_ratio = 1; + mclk_freq = 12288000; + break; + case 44100: + lrclk_ratio = 3; + mclk_freq = 11289600; + break; + case 88200: + lrclk_ratio = 1; + mclk_freq = 11289600; + break; + case 176400: + lrclk_ratio = 0; + mclk_freq = 11289600; + break; + case 192000: + lrclk_ratio = 0; + mclk_freq = 12288000; + break; + default: + pr_info("Rate not support.\n"); + return -EINVAL;; + } + + dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + esai_mode->sync_mode = 0; + esai_mode->network_mode = 1; + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, channel == 1 ? 0x1 : 0x3, 2); + /* set the ESAI system clock as output */ + snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL, + mclk_freq, SND_SOC_CLOCK_OUT); + /* set the ratio */ + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1); + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 0); + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio); + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1); + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 0); + snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio); + + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + /* set codec Master clock */ + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops imx_3stack_surround_ops = { + .startup = imx_3stack_startup, + .shutdown = imx_3stack_shutdown, + .hw_params = imx_3stack_surround_hw_params, +}; + +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "AOUT1L"}, + {"Line Out Jack", NULL, "AOUT1R"}, + {"Line Out Jack", NULL, "AOUT2L"}, + {"Line Out Jack", NULL, "AOUT2R"}, + {"Line Out Jack", NULL, "AOUT3L"}, + {"Line Out Jack", NULL, "AOUT3R"}, + {"Line Out Jack", NULL, "AOUT4L"}, + {"Line Out Jack", NULL, "AOUT4R"}, + {"AIN1L", NULL, "Line In Jack"}, + {"AIN1R", NULL, "Line In Jack"}, + {"AIN2L", NULL, "Line In Jack"}, + {"AIN2R", NULL, "Line In Jack"}, +}; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +static int asrc_func; + +static const char *asrc_function[] = + { "disable", "32KHz", "44.1KHz", + "48KHz", "64KHz", "88.2KHz", "96KHz", "176.4KHz", "192KHz" +}; + +static const struct soc_enum asrc_enum[] = { + SOC_ENUM_SINGLE_EXT(9, asrc_function), +}; + +static int asrc_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = asrc_func; + return 0; +} + +static int asrc_set_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (asrc_func == ucontrol->value.enumerated.item[0]) + return 0; + + asrc_func = ucontrol->value.enumerated.item[0]; + asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4]; + + return 1; +} + +static const struct snd_kcontrol_new asrc_controls[] = { + SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate, + asrc_set_rate), +}; + +#endif + +static int imx_3stack_cs42888_init(struct snd_soc_codec *codec) +{ + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + int i; + int ret; + for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&asrc_controls[i], codec, NULL)); + if (ret < 0) + return ret; + } + asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4]; +#endif + + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "cs42888", + .stream_name = "cs42888", + .codec_dai = &cs42888_dai, + .init = imx_3stack_cs42888_init, + .ops = &imx_3stack_surround_ops, +}; + +static int imx_3stack_card_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + kfree(socdev->codec_data); + return 0; +} + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, + .remove = imx_3stack_card_remove, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_device_cs42888, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + */ +static int __devinit imx_3stack_cs42888_probe(struct platform_device *pdev) +{ + imx_3stack_dai.cpu_dai = &imx_esai_dai[2]; + imx_3stack_dai.cpu_dai->dev = &pdev->dev; + + return 0; +} + +static int __devexit imx_3stack_cs42888_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver imx_3stack_cs42888_driver = { + .probe = imx_3stack_cs42888_probe, + .remove = __devexit_p(imx_3stack_cs42888_remove), + .driver = { + .name = "imx-3stack-cs42888", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_asoc_init(void) +{ + int ret; + ret = platform_driver_register(&imx_3stack_cs42888_driver); + if (ret < 0) + goto exit; + + imx_3stack_snd_device = platform_device_alloc("soc-audio", 1); + if (!imx_3stack_snd_device) + goto err_device_alloc; + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + if (0 == ret && snd_soc_card_imx_3stack.codec != NULL) + goto exit; + + platform_device_unregister(imx_3stack_snd_device); +err_device_alloc: + platform_driver_unregister(&imx_3stack_cs42888_driver); +exit: + return ret; +} + +static void __exit imx_3stack_asoc_exit(void) +{ + platform_driver_unregister(&imx_3stack_cs42888_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_asoc_init); +module_exit(imx_3stack_asoc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC cs42888 imx_3stack"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-wm8580.c b/sound/soc/imx/imx-3stack-wm8580.c index 631571ddb5ef..f3f9f85ca0f0 100644 --- a/sound/soc/imx/imx-3stack-wm8580.c +++ b/sound/soc/imx/imx-3stack-wm8580.c @@ -1,7 +1,7 @@ /* * imx-3stack-wm8580.c -- SoC 5.1 audio for imx_3stack * - * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -69,9 +69,6 @@ struct imx_3stack_pcm_state { int lr_clk_active; }; -extern void gpio_activate_esai_ports(void); -extern void gpio_deactivate_esai_ports(void); - static struct imx_3stack_pcm_state clk_state; static int imx_3stack_startup(struct snd_pcm_substream *substream) @@ -374,20 +371,17 @@ static int __devinit imx_3stack_wm8580_probe(struct platform_device *pdev) struct wm8580_setup_data *setup; imx_3stack_dai.cpu_dai = &imx_esai_dai[2]; + imx_3stack_dai.cpu_dai->dev = &pdev->dev; setup = kzalloc(sizeof(struct wm8580_setup_data), GFP_KERNEL); setup->spi = 1; imx_3stack_snd_devdata.codec_data = setup; - /* Configure audio port 3 */ - gpio_activate_esai_ports(); - return 0; } static int __devexit imx_3stack_wm8580_remove(struct platform_device *pdev) { - gpio_deactivate_esai_ports(); return 0; } diff --git a/sound/soc/imx/imx-ccwmx51-wm8753.c b/sound/soc/imx/imx-ccwmx51-wm8753.c index a12285fb1a8d..e92df8914b1c 100644 --- a/sound/soc/imx/imx-ccwmx51-wm8753.c +++ b/sound/soc/imx/imx-ccwmx51-wm8753.c @@ -54,14 +54,47 @@ static int imx_ccwmx51_audio_hw_params(struct snd_pcm_substream *substream, 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; + unsigned int rate = params_rate(params); struct imx_ccwmx51_priv *priv = &card_priv; struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data; int ret = 0; + unsigned int pll_out = 0; unsigned int channels = params_channels(params); u32 dai_format; - snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, priv->sysclk, 0); + switch (rate) { + case 8000: + pll_out = 12288000; + break; + case 11025: + pll_out = 11289600; + break; + case 16000: + pll_out = 12288000; + break; + case 22050: + pll_out = 11289600; + break; + case 32000: + pll_out = 12288000; + break; + case 44100: + pll_out = 11289600; + break; + case 48000: + pll_out = 12288000; + break; + case 88200: + pll_out = 11289600; + break; + case 96000: + pll_out = 12288000; + break; + default: + pr_info("Rate not supported.\n"); + return -EINVAL;; + } #if WM8753_SSI_MASTER dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | @@ -94,6 +127,9 @@ static int imx_ccwmx51_audio_hw_params(struct snd_pcm_substream *substream, /* set the SSI system clock as input (unused) */ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN); + priv->sysclk = pll_out; + snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, priv->sysclk, 0); + snd_soc_dai_set_pll(codec_dai, WM8753_MCLK, 13000000, pll_out); return 0; } diff --git a/sound/soc/imx/imx-esai.c b/sound/soc/imx/imx-esai.c index 71dd62cff509..0c6b234f29ec 100644 --- a/sound/soc/imx/imx-esai.c +++ b/sound/soc/imx/imx-esai.c @@ -1,5 +1,5 @@ /* - * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -35,292 +35,9 @@ #include "imx-esai.h" #include "imx-pcm.h" -/*#define IMX_ESAI_DUMP 1*/ - -#ifdef IMX_ESAI_DUMP -#define ESAI_DUMP() \ - do {pr_info("dump @ %s\n", __func__); \ - pr_info("ecr %x\n", __raw_readl(ESAI_ECR)); \ - pr_info("esr %x\n", __raw_readl(ESAI_ESR)); \ - pr_info("tfcr %x\n", __raw_readl(ESAI_TFCR)); \ - pr_info("tfsr %x\n", __raw_readl(ESAI_TFSR)); \ - pr_info("rfcr %x\n", __raw_readl(ESAI_RFCR)); \ - pr_info("rfsr %x\n", __raw_readl(ESAI_RFSR)); \ - pr_info("tsr %x\n", __raw_readl(ESAI_TSR)); \ - pr_info("saisr %x\n", __raw_readl(ESAI_SAISR)); \ - pr_info("saicr %x\n", __raw_readl(ESAI_SAICR)); \ - pr_info("tcr %x\n", __raw_readl(ESAI_TCR)); \ - pr_info("tccr %x\n", __raw_readl(ESAI_TCCR)); \ - pr_info("rcr %x\n", __raw_readl(ESAI_RCR)); \ - pr_info("rccr %x\n", __raw_readl(ESAI_RCCR)); \ - pr_info("tsma %x\n", __raw_readl(ESAI_TSMA)); \ - pr_info("tsmb %x\n", __raw_readl(ESAI_TSMB)); \ - pr_info("rsma %x\n", __raw_readl(ESAI_RSMA)); \ - pr_info("rsmb %x\n", __raw_readl(ESAI_RSMB)); \ - pr_info("prrc %x\n", __raw_readl(ESAI_PRRC)); \ - pr_info("pcrc %x\n", __raw_readl(ESAI_PCRC)); } while (0); -#else -#define ESAI_DUMP() -#endif - -#define ESAI_IO_BASE_ADDR IO_ADDRESS(ESAI_BASE_ADDR) - -#define ESAI_ETDR (ESAI_IO_BASE_ADDR + 0x00) -#define ESAI_ERDR (ESAI_IO_BASE_ADDR + 0x04) -#define ESAI_ECR (ESAI_IO_BASE_ADDR + 0x08) -#define ESAI_ESR (ESAI_IO_BASE_ADDR + 0x0C) -#define ESAI_TFCR (ESAI_IO_BASE_ADDR + 0x10) -#define ESAI_TFSR (ESAI_IO_BASE_ADDR + 0x14) -#define ESAI_RFCR (ESAI_IO_BASE_ADDR + 0x18) -#define ESAI_RFSR (ESAI_IO_BASE_ADDR + 0x1C) -#define ESAI_TX0 (ESAI_IO_BASE_ADDR + 0x80) -#define ESAI_TX1 (ESAI_IO_BASE_ADDR + 0x84) -#define ESAI_TX2 (ESAI_IO_BASE_ADDR + 0x88) -#define ESAI_TX3 (ESAI_IO_BASE_ADDR + 0x8C) -#define ESAI_TX4 (ESAI_IO_BASE_ADDR + 0x90) -#define ESAI_TX5 (ESAI_IO_BASE_ADDR + 0x94) -#define ESAI_TSR (ESAI_IO_BASE_ADDR + 0x98) -#define ESAI_RX0 (ESAI_IO_BASE_ADDR + 0xA0) -#define ESAI_RX1 (ESAI_IO_BASE_ADDR + 0xA4) -#define ESAI_RX2 (ESAI_IO_BASE_ADDR + 0xA8) -#define ESAI_RX3 (ESAI_IO_BASE_ADDR + 0xAC) -#define ESAI_SAISR (ESAI_IO_BASE_ADDR + 0xCC) -#define ESAI_SAICR (ESAI_IO_BASE_ADDR + 0xD0) -#define ESAI_TCR (ESAI_IO_BASE_ADDR + 0xD4) -#define ESAI_TCCR (ESAI_IO_BASE_ADDR + 0xD8) -#define ESAI_RCR (ESAI_IO_BASE_ADDR + 0xDC) -#define ESAI_RCCR (ESAI_IO_BASE_ADDR + 0xE0) -#define ESAI_TSMA (ESAI_IO_BASE_ADDR + 0xE4) -#define ESAI_TSMB (ESAI_IO_BASE_ADDR + 0xE8) -#define ESAI_RSMA (ESAI_IO_BASE_ADDR + 0xEC) -#define ESAI_RSMB (ESAI_IO_BASE_ADDR + 0xF0) -#define ESAI_PRRC (ESAI_IO_BASE_ADDR + 0xF8) -#define ESAI_PCRC (ESAI_IO_BASE_ADDR + 0xFC) - -#define ESAI_ECR_ETI (1 << 19) -#define ESAI_ECR_ETO (1 << 18) -#define ESAI_ECR_ERI (1 << 17) -#define ESAI_ECR_ERO (1 << 16) -#define ESAI_ECR_ERST (1 << 1) -#define ESAI_ECR_ESAIEN (1 << 0) - -#define ESAI_ESR_TINIT (1 << 10) -#define ESAI_ESR_RFF (1 << 9) -#define ESAI_ESR_TFE (1 << 8) -#define ESAI_ESR_TLS (1 << 7) -#define ESAI_ESR_TDE (1 << 6) -#define ESAI_ESR_TED (1 << 5) -#define ESAI_ESR_TD (1 << 4) -#define ESAI_ESR_RLS (1 << 3) -#define ESAI_ESR_RDE (1 << 2) -#define ESAI_ESR_RED (1 << 1) -#define ESAI_ESR_RD (1 << 0) - -#define ESAI_TFCR_TIEN (1 << 19) -#define ESAI_TFCR_TE5 (1 << 7) -#define ESAI_TFCR_TE4 (1 << 6) -#define ESAI_TFCR_TE3 (1 << 5) -#define ESAI_TFCR_TE2 (1 << 4) -#define ESAI_TFCR_TE1 (1 << 3) -#define ESAI_TFCR_TE0 (1 << 2) -#define ESAI_TFCR_TFR (1 << 1) -#define ESAI_TFCR_TFEN (1 << 0) -#define ESAI_TFCR_TE(x) ((0x3f >> (6 - ((x + 1) >> 1))) << 2) -#define ESAI_TFCR_TE_MASK 0xfff03 -#define ESAI_TFCR_TFWM(x) ((x - 1) << 8) -#define ESAI_TFCR_TWA_MASK 0xf8ffff - -#define ESAI_RFCR_REXT (1 << 19) -#define ESAI_RFCR_RE3 (1 << 5) -#define ESAI_RFCR_RE2 (1 << 4) -#define ESAI_RFCR_RE1 (1 << 3) -#define ESAI_RFCR_RE0 (1 << 2) -#define ESAI_RFCR_RFR (1 << 1) -#define ESAI_RFCR_RFEN (1 << 0) -#define ESAI_RFCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 3) -#define ESAI_RFCR_RE_MASK 0xfffc3 -#define ESAI_RFCR_RFWM(x) ((x-1) << 8) -#define ESAI_RFCR_RWA_MASK 0xf8ffff - -#define ESAI_WORD_LEN_32 (0x00 << 16) -#define ESAI_WORD_LEN_28 (0x01 << 16) -#define ESAI_WORD_LEN_24 (0x02 << 16) -#define ESAI_WORD_LEN_20 (0x03 << 16) -#define ESAI_WORD_LEN_16 (0x04 << 16) -#define ESAI_WORD_LEN_12 (0x05 << 16) -#define ESAI_WORD_LEN_8 (0x06 << 16) -#define ESAI_WORD_LEN_4 (0x07 << 16) - -#define ESAI_SAISR_TODFE (1 << 17) -#define ESAI_SAISR_TEDE (1 << 16) -#define ESAI_SAISR_TDE (1 << 15) -#define ESAI_SAISR_TUE (1 << 14) -#define ESAI_SAISR_TFS (1 << 13) -#define ESAI_SAISR_RODF (1 << 10) -#define ESAI_SAISR_REDF (1 << 9) -#define ESAI_SAISR_RDF (1 << 8) -#define ESAI_SAISR_ROE (1 << 7) -#define ESAI_SAISR_RFS (1 << 6) -#define ESAI_SAISR_IF2 (1 << 2) -#define ESAI_SAISR_IF1 (1 << 1) -#define ESAI_SAISR_IF0 (1 << 0) - -#define ESAI_SAICR_ALC (1 << 8) -#define ESAI_SAICR_TEBE (1 << 7) -#define ESAI_SAICR_SYNC (1 << 6) -#define ESAI_SAICR_OF2 (1 << 2) -#define ESAI_SAICR_OF1 (1 << 1) -#define ESAI_SAICR_OF0 (1 << 0) - -#define ESAI_TCR_TLIE (1 << 23) -#define ESAI_TCR_TIE (1 << 22) -#define ESAI_TCR_TEDIE (1 << 21) -#define ESAI_TCR_TEIE (1 << 20) -#define ESAI_TCR_TPR (1 << 19) -#define ESAI_TCR_PADC (1 << 17) -#define ESAI_TCR_TFSR (1 << 16) -#define ESAI_TCR_TFSL (1 << 15) -#define ESAI_TCR_TWA (1 << 7) -#define ESAI_TCR_TSHFD_MSB (0 << 6) -#define ESAI_TCR_TSHFD_LSB (1 << 6) -#define ESAI_TCR_TE5 (1 << 5) -#define ESAI_TCR_TE4 (1 << 4) -#define ESAI_TCR_TE3 (1 << 3) -#define ESAI_TCR_TE2 (1 << 2) -#define ESAI_TCR_TE1 (1 << 1) -#define ESAI_TCR_TE0 (1 << 0) -#define ESAI_TCR_TE(x) (0x3f >> (6 - ((x + 1) >> 1))) - -#define ESAI_TCR_TSWS_MASK 0xff83ff -#define ESAI_TCR_TSWS_STL8_WDL8 (0x00 << 10) -#define ESAI_TCR_TSWS_STL12_WDL8 (0x04 << 10) -#define ESAI_TCR_TSWS_STL12_WDL12 (0x01 << 10) -#define ESAI_TCR_TSWS_STL16_WDL8 (0x08 << 10) -#define ESAI_TCR_TSWS_STL16_WDL12 (0x05 << 10) -#define ESAI_TCR_TSWS_STL16_WDL16 (0x02 << 10) -#define ESAI_TCR_TSWS_STL20_WDL8 (0x0c << 10) -#define ESAI_TCR_TSWS_STL20_WDL12 (0x09 << 10) -#define ESAI_TCR_TSWS_STL20_WDL16 (0x06 << 10) -#define ESAI_TCR_TSWS_STL20_WDL20 (0x03 << 10) -#define ESAI_TCR_TSWS_STL24_WDL8 (0x10 << 10) -#define ESAI_TCR_TSWS_STL24_WDL12 (0x0d << 10) -#define ESAI_TCR_TSWS_STL24_WDL16 (0x0a << 10) -#define ESAI_TCR_TSWS_STL24_WDL20 (0x07 << 10) -#define ESAI_TCR_TSWS_STL24_WDL24 (0x1e << 10) -#define ESAI_TCR_TSWS_STL32_WDL8 (0x18 << 10) -#define ESAI_TCR_TSWS_STL32_WDL12 (0x15 << 10) -#define ESAI_TCR_TSWS_STL32_WDL16 (0x12 << 10) -#define ESAI_TCR_TSWS_STL32_WDL20 (0x0f << 10) -#define ESAI_TCR_TSWS_STL32_WDL24 (0x1f << 10) - -#define ESAI_TCR_TMOD_MASK 0xfffcff -#define ESAI_TCR_TMOD_NORMAL (0x00 << 8) -#define ESAI_TCR_TMOD_ONDEMAND (0x01 << 8) -#define ESAI_TCR_TMOD_NETWORK (0x01 << 8) -#define ESAI_TCR_TMOD_RESERVED (0x02 << 8) -#define ESAI_TCR_TMOD_AC97 (0x03 << 8) - -#define ESAI_TCCR_THCKD (1 << 23) -#define ESAI_TCCR_TFSD (1 << 22) -#define ESAI_TCCR_TCKD (1 << 21) -#define ESAI_TCCR_THCKP (1 << 20) -#define ESAI_TCCR_TFSP (1 << 19) -#define ESAI_TCCR_TCKP (1 << 18) - -#define ESAI_TCCR_TPSR_MASK 0xfffeff -#define ESAI_TCCR_TPSR_BYPASS (1 << 8) -#define ESAI_TCCR_TPSR_DIV8 (0 << 8) - -#define ESAI_TCCR_TFP_MASK 0xfc3fff -#define ESAI_TCCR_TFP(x) ((x & 0xf) << 14) - -#define ESAI_TCCR_TDC_MASK 0xffc1ff -#define ESAI_TCCR_TDC(x) (((x) & 0x1f) << 9) - -#define ESAI_TCCR_TPM_MASK 0xffff00 -#define ESAI_TCCR_TPM(x) (x & 0xff) - -#define ESAI_RCR_RLIE (1 << 23) -#define ESAI_RCR_RIE (1 << 22) -#define ESAI_RCR_REDIE (1 << 21) -#define ESAI_RCR_REIE (1 << 20) -#define ESAI_RCR_RPR (1 << 19) -#define ESAI_RCR_RFSR (1 << 16) -#define ESAI_RCR_RFSL (1 << 15) -#define ESAI_RCR_RWA (1 << 7) -#define ESAI_RCR_RSHFD_MSB (0 << 6) -#define ESAI_RCR_RSHFD_LSB (1 << 6) -#define ESAI_RCR_RE3 (1 << 3) -#define ESAI_RCR_RE2 (1 << 2) -#define ESAI_RCR_RE1 (1 << 1) -#define ESAI_RCR_RE0 (1 << 0) -#define ESAI_RCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 1) - -#define ESAI_RCR_RSWS_MASK 0xff83ff -#define ESAI_RCR_RSWS_STL8_WDL8 (0x00 << 10) -#define ESAI_RCR_RSWS_STL12_WDL8 (0x04 << 10) -#define ESAI_RCR_RSWS_STL12_WDL12 (0x01 << 10) -#define ESAI_RCR_RSWS_STL16_WDL8 (0x08 << 10) -#define ESAI_RCR_RSWS_STL16_WDL12 (0x05 << 10) -#define ESAI_RCR_RSWS_STL16_WDL16 (0x02 << 10) -#define ESAI_RCR_RSWS_STL20_WDL8 (0x0c << 10) -#define ESAI_RCR_RSWS_STL20_WDL12 (0x09 << 10) -#define ESAI_RCR_RSWS_STL20_WDL16 (0x06 << 10) -#define ESAI_RCR_RSWS_STL20_WDL20 (0x03 << 10) -#define ESAI_RCR_RSWS_STL24_WDL8 (0x10 << 10) -#define ESAI_RCR_RSWS_STL24_WDL12 (0x0d << 10) -#define ESAI_RCR_RSWS_STL24_WDL16 (0x0a << 10) -#define ESAI_RCR_RSWS_STL24_WDL20 (0x07 << 10) -#define ESAI_RCR_RSWS_STL24_WDL24 (0x1e << 10) -#define ESAI_RCR_RSWS_STL32_WDL8 (0x18 << 10) -#define ESAI_RCR_RSWS_STL32_WDL12 (0x15 << 10) -#define ESAI_RCR_RSWS_STL32_WDL16 (0x12 << 10) -#define ESAI_RCR_RSWS_STL32_WDL20 (0x0f << 10) -#define ESAI_RCR_RSWS_STL32_WDL24 (0x1f << 10) - -#define ESAI_RCR_RMOD_MASK 0xfffcff -#define ESAI_RCR_RMOD_NORMAL (0x00 << 8) -#define ESAI_RCR_RMOD_ONDEMAND (0x01 << 8) -#define ESAI_RCR_RMOD_NETWORK (0x01 << 8) -#define ESAI_RCR_RMOD_RESERVED (0x02 << 8) -#define ESAI_RCR_RMOD_AC97 (0x03 << 8) - -#define ESAI_RCCR_RHCKD (1 << 23) -#define ESAI_RCCR_RFSD (1 << 22) -#define ESAI_RCCR_RCKD (1 << 21) -#define ESAI_RCCR_RHCKP (1 << 20) -#define ESAI_RCCR_RFSP (1 << 19) -#define ESAI_RCCR_RCKP (1 << 18) - -#define ESAI_RCCR_RPSR_MASK 0xfffeff -#define ESAI_RCCR_RPSR_BYPASS (1 << 8) -#define ESAI_RCCR_RPSR_DIV8 (0 << 8) - -#define ESAI_RCCR_RFP_MASK 0xfc3fff -#define ESAI_RCCR_RFP(x) ((x & 0xf) << 14) - -#define ESAI_RCCR_RDC_MASK 0xffc1ff -#define ESAI_RCCR_RDC(x) (((x) & 0x1f) << 9) - -#define ESAI_RCCR_RPM_MASK 0xffff00 -#define ESAI_RCCR_RPM(x) (x & 0xff) - -#define ESAI_GPIO_ESAI 0xfff - -/* ESAI clock source */ -#define ESAI_CLK_FSYS 0 -#define ESAI_CLK_EXTAL 1 - -/* ESAI clock divider */ -#define ESAI_TX_DIV_PSR 0 -#define ESAI_TX_DIV_PM 1 -#define ESAI_TX_DIV_FP 2 -#define ESAI_RX_DIV_PSR 3 -#define ESAI_RX_DIV_PM 4 -#define ESAI_RX_DIV_FP 5 - static int imx_esai_txrx_state; static struct imx_esai imx_esai_priv[3]; +static void __iomem *esai_ioaddr; static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) @@ -341,10 +58,8 @@ static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, ~(ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD); } else { - if (cpu_dai->id & IMX_DAI_ESAI_TX) tccr |= ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD; - if (cpu_dai->id & IMX_DAI_ESAI_RX) rccr |= ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD; if (clk_id == ESAI_CLK_FSYS) { @@ -353,14 +68,10 @@ static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, if (cpu_dai->id & IMX_DAI_ESAI_RX) ecr &= ~(ESAI_ECR_ERI | ESAI_ECR_ERO); } else if (clk_id == ESAI_CLK_EXTAL) { - if (cpu_dai->id & IMX_DAI_ESAI_TX) { ecr |= ESAI_ECR_ETI; ecr &= ~ESAI_ECR_ETO; - } - if (cpu_dai->id & IMX_DAI_ESAI_RX) { ecr |= ESAI_ECR_ERI; ecr &= ~ESAI_ECR_ERO; - } } } @@ -386,7 +97,10 @@ static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, switch (div_id) { case ESAI_TX_DIV_PSR: tccr &= ESAI_TCCR_TPSR_MASK; - tccr |= div; + if (div) + tccr |= ESAI_TCCR_TPSR_BYPASS; + else + tccr &= ~ESAI_TCCR_TPSR_DIV8; break; case ESAI_TX_DIV_PM: tccr &= ESAI_TCCR_TPM_MASK; @@ -398,7 +112,10 @@ static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, break; case ESAI_RX_DIV_PSR: rccr &= ESAI_RCCR_RPSR_MASK; - rccr |= div; + if (div) + rccr |= ESAI_RCCR_RPSR_BYPASS; + else + rccr &= ~ESAI_RCCR_RPSR_DIV8; break; case ESAI_RX_DIV_PM: rccr &= ESAI_RCCR_RPM_MASK; @@ -691,7 +408,7 @@ static int imx_esai_hw_rx_params(struct snd_pcm_substream *substream, switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: rfcr |= ESAI_WORD_LEN_16; - rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL16_WDL16; + rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL16; break; } @@ -729,6 +446,7 @@ static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { u32 reg, tfcr = 0, rfcr = 0; + u32 temp; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { tfcr = __raw_readl(ESAI_TFCR); @@ -748,6 +466,9 @@ static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd, reg |= ESAI_TCR_TE(substream->runtime->channels); __raw_writel(reg, ESAI_TCR); } else { + temp = __raw_readl(ESAI_TCR); + temp &= ~ESAI_TCR_TPR; + __raw_writel(temp, ESAI_TCR); rfcr |= ESAI_RFCR_RFEN; __raw_writel(rfcr, ESAI_RFCR); reg &= ~ESAI_RCR_RPR; @@ -783,7 +504,6 @@ static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd, default: return -EINVAL; } - ESAI_DUMP(); return 0; } @@ -932,14 +652,53 @@ struct snd_soc_dai imx_esai_dai[] = { EXPORT_SYMBOL_GPL(imx_esai_dai); +static int imx_esai_dev_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mxc_esai_platform_data *plat_data = pdev->dev.platform_data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + esai_ioaddr = ioremap(res->start, res->end - res->start + 1); + + if (plat_data->activate_esai_ports) + plat_data->activate_esai_ports(); + + snd_soc_register_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai)); + return 0; +} + +static int __devexit imx_esai_dev_remove(struct platform_device *pdev) +{ + + struct mxc_esai_platform_data *plat_data = pdev->dev.platform_data; + iounmap(esai_ioaddr); + if (plat_data->deactivate_esai_ports) + plat_data->deactivate_esai_ports(); + + snd_soc_unregister_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai)); + return 0; +} + + +static struct platform_driver imx_esai_driver = { + .probe = imx_esai_dev_probe, + .remove = __devexit_p(imx_esai_dev_remove), + .driver = { + .name = "mxc_esai", + }, +}; + static int __init imx_esai_init(void) { - return snd_soc_register_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai)); + return platform_driver_register(&imx_esai_driver); } static void __exit imx_esai_exit(void) { - snd_soc_unregister_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai)); + platform_driver_unregister(&imx_esai_driver); } module_init(imx_esai_init); diff --git a/sound/soc/imx/imx-esai.h b/sound/soc/imx/imx-esai.h index 58ad601c119f..6e2cce0eff4a 100644 --- a/sound/soc/imx/imx-esai.h +++ b/sound/soc/imx/imx-esai.h @@ -1,7 +1,7 @@ /* * imx-esai.h -- ESAI driver header file for Freescale IMX * - * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -16,6 +16,290 @@ #ifndef _MXC_ESAI_H #define _MXC_ESAI_H +/*#define IMX_ESAI_DUMP 1*/ + +#ifdef IMX_ESAI_DUMP +#define ESAI_DUMP() \ + do {pr_info("dump @ %s\n", __func__); \ + pr_info("ecr %x\n", __raw_readl(ESAI_ECR)); \ + pr_info("esr %x\n", __raw_readl(ESAI_ESR)); \ + pr_info("tfcr %x\n", __raw_readl(ESAI_TFCR)); \ + pr_info("tfsr %x\n", __raw_readl(ESAI_TFSR)); \ + pr_info("rfcr %x\n", __raw_readl(ESAI_RFCR)); \ + pr_info("rfsr %x\n", __raw_readl(ESAI_RFSR)); \ + pr_info("tsr %x\n", __raw_readl(ESAI_TSR)); \ + pr_info("saisr %x\n", __raw_readl(ESAI_SAISR)); \ + pr_info("saicr %x\n", __raw_readl(ESAI_SAICR)); \ + pr_info("tcr %x\n", __raw_readl(ESAI_TCR)); \ + pr_info("tccr %x\n", __raw_readl(ESAI_TCCR)); \ + pr_info("rcr %x\n", __raw_readl(ESAI_RCR)); \ + pr_info("rccr %x\n", __raw_readl(ESAI_RCCR)); \ + pr_info("tsma %x\n", __raw_readl(ESAI_TSMA)); \ + pr_info("tsmb %x\n", __raw_readl(ESAI_TSMB)); \ + pr_info("rsma %x\n", __raw_readl(ESAI_RSMA)); \ + pr_info("rsmb %x\n", __raw_readl(ESAI_RSMB)); \ + pr_info("prrc %x\n", __raw_readl(ESAI_PRRC)); \ + pr_info("pcrc %x\n", __raw_readl(ESAI_PCRC)); } while (0); +#else +#define ESAI_DUMP() +#endif + +#define ESAI_IO_BASE_ADDR (esai_ioaddr) + +#define ESAI_ETDR (ESAI_IO_BASE_ADDR + 0x00) +#define ESAI_ERDR (ESAI_IO_BASE_ADDR + 0x04) +#define ESAI_ECR (ESAI_IO_BASE_ADDR + 0x08) +#define ESAI_ESR (ESAI_IO_BASE_ADDR + 0x0C) +#define ESAI_TFCR (ESAI_IO_BASE_ADDR + 0x10) +#define ESAI_TFSR (ESAI_IO_BASE_ADDR + 0x14) +#define ESAI_RFCR (ESAI_IO_BASE_ADDR + 0x18) +#define ESAI_RFSR (ESAI_IO_BASE_ADDR + 0x1C) +#define ESAI_TX0 (ESAI_IO_BASE_ADDR + 0x80) +#define ESAI_TX1 (ESAI_IO_BASE_ADDR + 0x84) +#define ESAI_TX2 (ESAI_IO_BASE_ADDR + 0x88) +#define ESAI_TX3 (ESAI_IO_BASE_ADDR + 0x8C) +#define ESAI_TX4 (ESAI_IO_BASE_ADDR + 0x90) +#define ESAI_TX5 (ESAI_IO_BASE_ADDR + 0x94) +#define ESAI_TSR (ESAI_IO_BASE_ADDR + 0x98) +#define ESAI_RX0 (ESAI_IO_BASE_ADDR + 0xA0) +#define ESAI_RX1 (ESAI_IO_BASE_ADDR + 0xA4) +#define ESAI_RX2 (ESAI_IO_BASE_ADDR + 0xA8) +#define ESAI_RX3 (ESAI_IO_BASE_ADDR + 0xAC) +#define ESAI_SAISR (ESAI_IO_BASE_ADDR + 0xCC) +#define ESAI_SAICR (ESAI_IO_BASE_ADDR + 0xD0) +#define ESAI_TCR (ESAI_IO_BASE_ADDR + 0xD4) +#define ESAI_TCCR (ESAI_IO_BASE_ADDR + 0xD8) +#define ESAI_RCR (ESAI_IO_BASE_ADDR + 0xDC) +#define ESAI_RCCR (ESAI_IO_BASE_ADDR + 0xE0) +#define ESAI_TSMA (ESAI_IO_BASE_ADDR + 0xE4) +#define ESAI_TSMB (ESAI_IO_BASE_ADDR + 0xE8) +#define ESAI_RSMA (ESAI_IO_BASE_ADDR + 0xEC) +#define ESAI_RSMB (ESAI_IO_BASE_ADDR + 0xF0) +#define ESAI_PRRC (ESAI_IO_BASE_ADDR + 0xF8) +#define ESAI_PCRC (ESAI_IO_BASE_ADDR + 0xFC) + +#define ESAI_ECR_ETI (1 << 19) +#define ESAI_ECR_ETO (1 << 18) +#define ESAI_ECR_ERI (1 << 17) +#define ESAI_ECR_ERO (1 << 16) +#define ESAI_ECR_ERST (1 << 1) +#define ESAI_ECR_ESAIEN (1 << 0) + +#define ESAI_ESR_TINIT (1 << 10) +#define ESAI_ESR_RFF (1 << 9) +#define ESAI_ESR_TFE (1 << 8) +#define ESAI_ESR_TLS (1 << 7) +#define ESAI_ESR_TDE (1 << 6) +#define ESAI_ESR_TED (1 << 5) +#define ESAI_ESR_TD (1 << 4) +#define ESAI_ESR_RLS (1 << 3) +#define ESAI_ESR_RDE (1 << 2) +#define ESAI_ESR_RED (1 << 1) +#define ESAI_ESR_RD (1 << 0) + +#define ESAI_TFCR_TIEN (1 << 19) +#define ESAI_TFCR_TE5 (1 << 7) +#define ESAI_TFCR_TE4 (1 << 6) +#define ESAI_TFCR_TE3 (1 << 5) +#define ESAI_TFCR_TE2 (1 << 4) +#define ESAI_TFCR_TE1 (1 << 3) +#define ESAI_TFCR_TE0 (1 << 2) +#define ESAI_TFCR_TFR (1 << 1) +#define ESAI_TFCR_TFEN (1 << 0) +#define ESAI_TFCR_TE(x) ((0x3f >> (6 - ((x + 1) >> 1))) << 2) +#define ESAI_TFCR_TE_MASK 0xfff03 +#define ESAI_TFCR_TFWM(x) ((x - 1) << 8) +#define ESAI_TFCR_TWA_MASK 0xf8ffff + +#define ESAI_RFCR_REXT (1 << 19) +#define ESAI_RFCR_RE3 (1 << 5) +#define ESAI_RFCR_RE2 (1 << 4) +#define ESAI_RFCR_RE1 (1 << 3) +#define ESAI_RFCR_RE0 (1 << 2) +#define ESAI_RFCR_RFR (1 << 1) +#define ESAI_RFCR_RFEN (1 << 0) +#define ESAI_RFCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 2) +#define ESAI_RFCR_RE_MASK 0xfffc3 +#define ESAI_RFCR_RFWM(x) ((x-1) << 8) +#define ESAI_RFCR_RWA_MASK 0xf8ffff + +#define ESAI_WORD_LEN_32 (0x00 << 16) +#define ESAI_WORD_LEN_28 (0x01 << 16) +#define ESAI_WORD_LEN_24 (0x02 << 16) +#define ESAI_WORD_LEN_20 (0x03 << 16) +#define ESAI_WORD_LEN_16 (0x04 << 16) +#define ESAI_WORD_LEN_12 (0x05 << 16) +#define ESAI_WORD_LEN_8 (0x06 << 16) +#define ESAI_WORD_LEN_4 (0x07 << 16) + +#define ESAI_SAISR_TODFE (1 << 17) +#define ESAI_SAISR_TEDE (1 << 16) +#define ESAI_SAISR_TDE (1 << 15) +#define ESAI_SAISR_TUE (1 << 14) +#define ESAI_SAISR_TFS (1 << 13) +#define ESAI_SAISR_RODF (1 << 10) +#define ESAI_SAISR_REDF (1 << 9) +#define ESAI_SAISR_RDF (1 << 8) +#define ESAI_SAISR_ROE (1 << 7) +#define ESAI_SAISR_RFS (1 << 6) +#define ESAI_SAISR_IF2 (1 << 2) +#define ESAI_SAISR_IF1 (1 << 1) +#define ESAI_SAISR_IF0 (1 << 0) + +#define ESAI_SAICR_ALC (1 << 8) +#define ESAI_SAICR_TEBE (1 << 7) +#define ESAI_SAICR_SYNC (1 << 6) +#define ESAI_SAICR_OF2 (1 << 2) +#define ESAI_SAICR_OF1 (1 << 1) +#define ESAI_SAICR_OF0 (1 << 0) + +#define ESAI_TCR_TLIE (1 << 23) +#define ESAI_TCR_TIE (1 << 22) +#define ESAI_TCR_TEDIE (1 << 21) +#define ESAI_TCR_TEIE (1 << 20) +#define ESAI_TCR_TPR (1 << 19) +#define ESAI_TCR_PADC (1 << 17) +#define ESAI_TCR_TFSR (1 << 16) +#define ESAI_TCR_TFSL (1 << 15) +#define ESAI_TCR_TWA (1 << 7) +#define ESAI_TCR_TSHFD_MSB (0 << 6) +#define ESAI_TCR_TSHFD_LSB (1 << 6) +#define ESAI_TCR_TE5 (1 << 5) +#define ESAI_TCR_TE4 (1 << 4) +#define ESAI_TCR_TE3 (1 << 3) +#define ESAI_TCR_TE2 (1 << 2) +#define ESAI_TCR_TE1 (1 << 1) +#define ESAI_TCR_TE0 (1 << 0) +#define ESAI_TCR_TE(x) (0x3f >> (6 - ((x + 1) >> 1))) + +#define ESAI_TCR_TSWS_MASK 0xff83ff +#define ESAI_TCR_TSWS_STL8_WDL8 (0x00 << 10) +#define ESAI_TCR_TSWS_STL12_WDL8 (0x04 << 10) +#define ESAI_TCR_TSWS_STL12_WDL12 (0x01 << 10) +#define ESAI_TCR_TSWS_STL16_WDL8 (0x08 << 10) +#define ESAI_TCR_TSWS_STL16_WDL12 (0x05 << 10) +#define ESAI_TCR_TSWS_STL16_WDL16 (0x02 << 10) +#define ESAI_TCR_TSWS_STL20_WDL8 (0x0c << 10) +#define ESAI_TCR_TSWS_STL20_WDL12 (0x09 << 10) +#define ESAI_TCR_TSWS_STL20_WDL16 (0x06 << 10) +#define ESAI_TCR_TSWS_STL20_WDL20 (0x03 << 10) +#define ESAI_TCR_TSWS_STL24_WDL8 (0x10 << 10) +#define ESAI_TCR_TSWS_STL24_WDL12 (0x0d << 10) +#define ESAI_TCR_TSWS_STL24_WDL16 (0x0a << 10) +#define ESAI_TCR_TSWS_STL24_WDL20 (0x07 << 10) +#define ESAI_TCR_TSWS_STL24_WDL24 (0x1e << 10) +#define ESAI_TCR_TSWS_STL32_WDL8 (0x18 << 10) +#define ESAI_TCR_TSWS_STL32_WDL12 (0x15 << 10) +#define ESAI_TCR_TSWS_STL32_WDL16 (0x12 << 10) +#define ESAI_TCR_TSWS_STL32_WDL20 (0x0f << 10) +#define ESAI_TCR_TSWS_STL32_WDL24 (0x1f << 10) + +#define ESAI_TCR_TMOD_MASK 0xfffcff +#define ESAI_TCR_TMOD_NORMAL (0x00 << 8) +#define ESAI_TCR_TMOD_ONDEMAND (0x01 << 8) +#define ESAI_TCR_TMOD_NETWORK (0x01 << 8) +#define ESAI_TCR_TMOD_RESERVED (0x02 << 8) +#define ESAI_TCR_TMOD_AC97 (0x03 << 8) + +#define ESAI_TCCR_THCKD (1 << 23) +#define ESAI_TCCR_TFSD (1 << 22) +#define ESAI_TCCR_TCKD (1 << 21) +#define ESAI_TCCR_THCKP (1 << 20) +#define ESAI_TCCR_TFSP (1 << 19) +#define ESAI_TCCR_TCKP (1 << 18) + +#define ESAI_TCCR_TPSR_MASK 0xfffeff +#define ESAI_TCCR_TPSR_BYPASS (1 << 8) +#define ESAI_TCCR_TPSR_DIV8 (0 << 8) + +#define ESAI_TCCR_TFP_MASK 0xfc3fff +#define ESAI_TCCR_TFP(x) ((x & 0xf) << 14) + +#define ESAI_TCCR_TDC_MASK 0xffc1ff +#define ESAI_TCCR_TDC(x) (((x) & 0x1f) << 9) + +#define ESAI_TCCR_TPM_MASK 0xffff00 +#define ESAI_TCCR_TPM(x) (x & 0xff) + +#define ESAI_RCR_RLIE (1 << 23) +#define ESAI_RCR_RIE (1 << 22) +#define ESAI_RCR_REDIE (1 << 21) +#define ESAI_RCR_REIE (1 << 20) +#define ESAI_RCR_RPR (1 << 19) +#define ESAI_RCR_RFSR (1 << 16) +#define ESAI_RCR_RFSL (1 << 15) +#define ESAI_RCR_RWA (1 << 7) +#define ESAI_RCR_RSHFD_MSB (0 << 6) +#define ESAI_RCR_RSHFD_LSB (1 << 6) +#define ESAI_RCR_RE3 (1 << 3) +#define ESAI_RCR_RE2 (1 << 2) +#define ESAI_RCR_RE1 (1 << 1) +#define ESAI_RCR_RE0 (1 << 0) +#define ESAI_RCR_RE(x) (0xf >> (4 - ((x + 1) >> 1))) + +#define ESAI_RCR_RSWS_MASK 0xff83ff +#define ESAI_RCR_RSWS_STL8_WDL8 (0x00 << 10) +#define ESAI_RCR_RSWS_STL12_WDL8 (0x04 << 10) +#define ESAI_RCR_RSWS_STL12_WDL12 (0x01 << 10) +#define ESAI_RCR_RSWS_STL16_WDL8 (0x08 << 10) +#define ESAI_RCR_RSWS_STL16_WDL12 (0x05 << 10) +#define ESAI_RCR_RSWS_STL16_WDL16 (0x02 << 10) +#define ESAI_RCR_RSWS_STL20_WDL8 (0x0c << 10) +#define ESAI_RCR_RSWS_STL20_WDL12 (0x09 << 10) +#define ESAI_RCR_RSWS_STL20_WDL16 (0x06 << 10) +#define ESAI_RCR_RSWS_STL20_WDL20 (0x03 << 10) +#define ESAI_RCR_RSWS_STL24_WDL8 (0x10 << 10) +#define ESAI_RCR_RSWS_STL24_WDL12 (0x0d << 10) +#define ESAI_RCR_RSWS_STL24_WDL16 (0x0a << 10) +#define ESAI_RCR_RSWS_STL24_WDL20 (0x07 << 10) +#define ESAI_RCR_RSWS_STL24_WDL24 (0x1e << 10) +#define ESAI_RCR_RSWS_STL32_WDL8 (0x18 << 10) +#define ESAI_RCR_RSWS_STL32_WDL12 (0x15 << 10) +#define ESAI_RCR_RSWS_STL32_WDL16 (0x12 << 10) +#define ESAI_RCR_RSWS_STL32_WDL20 (0x0f << 10) +#define ESAI_RCR_RSWS_STL32_WDL24 (0x1f << 10) + +#define ESAI_RCR_RMOD_MASK 0xfffcff +#define ESAI_RCR_RMOD_NORMAL (0x00 << 8) +#define ESAI_RCR_RMOD_ONDEMAND (0x01 << 8) +#define ESAI_RCR_RMOD_NETWORK (0x01 << 8) +#define ESAI_RCR_RMOD_RESERVED (0x02 << 8) +#define ESAI_RCR_RMOD_AC97 (0x03 << 8) + +#define ESAI_RCCR_RHCKD (1 << 23) +#define ESAI_RCCR_RFSD (1 << 22) +#define ESAI_RCCR_RCKD (1 << 21) +#define ESAI_RCCR_RHCKP (1 << 20) +#define ESAI_RCCR_RFSP (1 << 19) +#define ESAI_RCCR_RCKP (1 << 18) + +#define ESAI_RCCR_RPSR_MASK 0xfffeff +#define ESAI_RCCR_RPSR_BYPASS (1 << 8) +#define ESAI_RCCR_RPSR_DIV8 (0 << 8) + +#define ESAI_RCCR_RFP_MASK 0xfc3fff +#define ESAI_RCCR_RFP(x) ((x & 0xf) << 14) + +#define ESAI_RCCR_RDC_MASK 0xffc1ff +#define ESAI_RCCR_RDC(x) (((x) & 0x1f) << 9) + +#define ESAI_RCCR_RPM_MASK 0xffff00 +#define ESAI_RCCR_RPM(x) (x & 0xff) + +#define ESAI_GPIO_ESAI 0xfff + +/* ESAI clock source */ +#define ESAI_CLK_FSYS 0 +#define ESAI_CLK_EXTAL 1 + +/* ESAI clock divider */ +#define ESAI_TX_DIV_PSR 0 +#define ESAI_TX_DIV_PM 1 +#define ESAI_TX_DIV_FP 2 +#define ESAI_RX_DIV_PSR 3 +#define ESAI_RX_DIV_PM 4 +#define ESAI_RX_DIV_FP 5 + #define IMX_DAI_ESAI_TX 0x04 #define IMX_DAI_ESAI_RX 0x08 #define IMX_DAI_ESAI_TXRX (IMX_DAI_ESAI_TX | IMX_DAI_ESAI_RX) diff --git a/sound/soc/imx/imx-pcm.c b/sound/soc/imx/imx-pcm.c index 2487d9284ac3..3e8a90da35c2 100644 --- a/sound/soc/imx/imx-pcm.c +++ b/sound/soc/imx/imx-pcm.c @@ -579,6 +579,14 @@ static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) else { buf->area = iram_alloc(size, &buf_paddr); buf->addr = buf_paddr; + + if (!buf->area) { + pr_warning("imx-pcm: Falling back to external ram.\n"); + UseIram = 0; + buf->area = + dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + } } if (!buf->area) diff --git a/sound/soc/mxs/mxs-adc.c b/sound/soc/mxs/mxs-adc.c index e8bb4255fff5..7069927b1ac3 100644 --- a/sound/soc/mxs/mxs-adc.c +++ b/sound/soc/mxs/mxs-adc.c @@ -37,6 +37,7 @@ #define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000 #define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +#define ADC_VOLUME_MIN 0x37 struct mxs_pcm_dma_params mxs_audio_in = { .name = "mxs-audio-in", @@ -50,6 +51,166 @@ struct mxs_pcm_dma_params mxs_audio_out = { .irq = IRQ_DAC_DMA, }; +static struct delayed_work work; +static struct delayed_work adc_ramp_work; +static struct delayed_work dac_ramp_work; +static bool adc_ramp_done = 1; +static bool dac_ramp_done = 1; + +static void mxs_adc_schedule_work(struct delayed_work *work) +{ + schedule_delayed_work(work, HZ / 10); +} +static void mxs_adc_work(struct work_struct *work) +{ + /* disable irq */ + disable_irq(IRQ_HEADPHONE_SHORT); + + while (true) { + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + msleep(10); + if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL) + & BM_AUDIOOUT_ANACTRL_SHORT_LR_STS) != 0) { + /* rearm the short protection */ + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + printk(KERN_WARNING "WARNING : Headphone LR short!\r\n"); + } else { + printk(KERN_WARNING "INFO : Headphone LR no longer short!\r\n"); + break; + } + msleep(1000); + } + + /* power up the HEADPHONE and un-mute the HPVOL */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + + /* enable irq for next short detect*/ + enable_irq(IRQ_HEADPHONE_SHORT); +} + +static void mxs_adc_schedule_ramp_work(struct delayed_work *work) +{ + schedule_delayed_work(work, msecs_to_jiffies(2)); + adc_ramp_done = 0; +} + +static void mxs_adc_ramp_work(struct work_struct *work) +{ + u32 reg = 0; + u32 reg1 = 0; + u32 reg2 = 0; + u32 l, r; + u32 ll, rr; + int i; + + reg = __raw_readl(REGS_AUDIOIN_BASE + \ + HW_AUDIOIN_ADCVOLUME); + + reg1 = reg & ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg1 = reg1 & ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + /* minimize adc volume */ + reg2 = reg1 | + BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MIN) | + BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MIN); + __raw_writel(reg2, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + msleep(1); + + l = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT) >> + BP_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT) >> + BP_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + + /* fade in adc vol */ + for (i = ADC_VOLUME_MIN; (i < l) || (i < r);) { + i += 0x8; + ll = i < l ? i : l; + rr = i < r ? i : r; + reg2 = reg1 | + BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ll) | + BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(rr); + __raw_writel(reg2, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + msleep(1); + } + adc_ramp_done = 1; +} + +static void mxs_dac_schedule_ramp_work(struct delayed_work *work) +{ + schedule_delayed_work(work, msecs_to_jiffies(2)); + dac_ramp_done = 0; +} + +static void mxs_dac_ramp_work(struct work_struct *work) +{ + u32 reg = 0; + u32 reg1 = 0; + u32 l, r; + u32 ll, rr; + int i; + + /* unmute hp and speaker */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR); + + reg = __raw_readl(REGS_AUDIOOUT_BASE + \ + HW_AUDIOOUT_HPVOL); + + reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT; + reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT; + + l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >> + BP_AUDIOOUT_HPVOL_VOL_LEFT; + r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >> + BP_AUDIOOUT_HPVOL_VOL_RIGHT; + /* fade in hp vol */ + for (i = 0x7f; i > 0 ;) { + i -= 0x8; + ll = i > (int)l ? i : l; + rr = i > (int)r ? i : r; + reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll) + | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr); + __raw_writel(reg, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + msleep(1); + } + dac_ramp_done = 1; +} + +static irqreturn_t mxs_short_irq(int irq, void *dev_id) +{ + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + mxs_adc_schedule_work(&work); + return IRQ_HANDLED; +} static irqreturn_t mxs_err_irq(int irq, void *dev_id) { struct snd_pcm_substream *substream = dev_id; @@ -104,68 +265,47 @@ static int mxs_adc_trigger(struct snd_pcm_substream *substream, { int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; int ret = 0; - u32 reg = 0; - u32 reg1 = 0; - u32 l, r; - u32 ll, rr; - int i; switch (cmd) { case SNDRV_PCM_TRIGGER_START: if (playback) { - reg = __raw_readl(REGS_AUDIOOUT_BASE + \ - HW_AUDIOOUT_HPVOL); - reg1 = BM_AUDIOOUT_HPVOL_VOL_LEFT | \ - BM_AUDIOOUT_HPVOL_VOL_RIGHT; - __raw_writel(reg1, REGS_AUDIOOUT_BASE + \ - HW_AUDIOOUT_HPVOL); - - __raw_writel(BM_AUDIOOUT_CTRL_RUN, + /* enable the fifo error interrupt */ + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); - __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); - - reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT; - reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT; - - l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >> - BP_AUDIOOUT_HPVOL_VOL_LEFT; - r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >> - BP_AUDIOOUT_HPVOL_VOL_RIGHT; - for (i = 0x7f; i > 0 ; i -= 0x8) { - ll = i > l ? i : l; - rr = i > r ? i : r; - /* fade in hp vol */ - reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll) - | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr); - __raw_writel(reg, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); - udelay(100); - } - __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, - REGS_AUDIOIN_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR); - } - else + /* write a data to data reg to trigger the transfer */ + __raw_writel(0x0, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA); + mxs_dac_schedule_ramp_work(&dac_ramp_work); + } else { __raw_writel(BM_AUDIOIN_CTRL_RUN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); - + mxs_adc_schedule_ramp_work(&adc_ramp_work); + } break; case SNDRV_PCM_TRIGGER_STOP: if (playback) { - __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + if (dac_ramp_done == 0) { + cancel_delayed_work(&dac_ramp_work); + dac_ramp_done = 1; + } + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); - - __raw_writel(BM_AUDIOOUT_CTRL_RUN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* disable the fifo error interrupt */ + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); - } - else + } else { + if (adc_ramp_done == 0) { + cancel_delayed_work(&adc_ramp_work); + adc_ramp_done = 1; + } __raw_writel(BM_AUDIOIN_CTRL_RUN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + } break; case SNDRV_PCM_TRIGGER_RESUME: @@ -187,8 +327,13 @@ static int mxs_adc_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; int irq; + int irq_short; int ret; + INIT_DELAYED_WORK(&work, mxs_adc_work); + INIT_DELAYED_WORK(&adc_ramp_work, mxs_adc_ramp_work); + INIT_DELAYED_WORK(&dac_ramp_work, mxs_dac_ramp_work); + if (playback) { irq = IRQ_DAC_ERROR; cpu_dai->dma_data = &mxs_audio_out; @@ -205,14 +350,21 @@ static int mxs_adc_startup(struct snd_pcm_substream *substream, return ret; } + irq_short = IRQ_HEADPHONE_SHORT; + ret = request_irq(irq_short, mxs_short_irq, + IRQF_DISABLED | IRQF_SHARED, "MXS DAC/ADC HP SHORT", substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request ADC/DAC HP SHORT irq %d\n", + __func__, IRQ_DAC_ERROR); + return ret; + } + /* Enable error interrupt */ if (playback) { __raw_writel(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); __raw_writel(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); - __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, - REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); } else { __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); diff --git a/sound/soc/mxs/mxs-evk-adc.c b/sound/soc/mxs/mxs-evk-adc.c index bd1ed6bf691f..16c1cb4fab96 100644 --- a/sound/soc/mxs/mxs-evk-adc.c +++ b/sound/soc/mxs/mxs-evk-adc.c @@ -33,12 +33,119 @@ #include "mxs-adc.h" #include "mxs-pcm.h" +/* mxs evk machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + /* HPR/HPL OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HPR"}, + {"Headphone Jack", NULL, "HPL"}, + + /* SPEAKER OUT --> Ext Speaker */ + {"Ext Spk", NULL, "SPEAKER"}, +}; + +static int mxs_evk_jack_func; +static int mxs_evk_spk_func; + +static const char *jack_function[] = { "off", "on"}; + +static const char *spk_function[] = { "off", "on" }; + + +static const struct soc_enum mxs_evk_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static int mxs_evk_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mxs_evk_jack_func; + return 0; +} + +static int mxs_evk_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (mxs_evk_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + mxs_evk_jack_func = ucontrol->value.enumerated.item[0]; + if (mxs_evk_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int mxs_evk_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mxs_evk_spk_func; + return 0; +} + +static int mxs_evk_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (mxs_evk_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + mxs_evk_spk_func = ucontrol->value.enumerated.item[0]; + if (mxs_evk_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + + snd_soc_dapm_sync(codec); + return 1; +} +/* mxs evk card dapm widgets */ +static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new mxs_evk_controls[] = { + SOC_ENUM_EXT("HP Playback Switch", mxs_evk_enum[0], mxs_evk_get_jack, + mxs_evk_set_jack), + SOC_ENUM_EXT("Speaker Playback Switch", mxs_evk_enum[1], + mxs_evk_get_spk, mxs_evk_set_spk), +}; + +static int mxs_evk_codec_init(struct snd_soc_codec *codec) +{ + int i, ret; + /* Add mxs evk specific controls */ + snd_soc_add_controls(codec, mxs_evk_controls, + ARRAY_SIZE(mxs_evk_controls)); + + /* Add mxs evk specific widgets */ + snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets, + ARRAY_SIZE(mxs_evk_dapm_widgets)); + + /* Set up mxs evk specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + /* default on */ + mxs_evk_jack_func = 1; + mxs_evk_spk_func = 1; + + return ret; +} /* mxs evk dac/adc audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link mxs_evk_codec_dai = { .name = "MXS ADC/DAC", .stream_name = "MXS ADC/DAC", .cpu_dai = &mxs_adc_dai, .codec_dai = &mxs_codec_dai, + .init = mxs_evk_codec_init, }; /* mxs evk audio machine driver */ diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c index a9f0358687f4..f3cdcdbd2861 100644 --- a/sound/soc/mxs/mxs-pcm.c +++ b/sound/soc/mxs/mxs-pcm.c @@ -141,6 +141,7 @@ static int mxs_pcm_prepare(struct snd_pcm_substream *substream) /* Link with previous command */ prtd->dma_desc_array[i]->cmd.cmd.bits.bytes = prtd->dma_period; prtd->dma_desc_array[i]->cmd.cmd.bits.irq = 1; + prtd->dma_desc_array[i]->cmd.cmd.bits.dec_sem = 0; prtd->dma_desc_array[i]->cmd.cmd.bits.chain = 1; /* Set DMA direction */ if (playback) @@ -194,6 +195,8 @@ static void mxs_pcm_stop(struct snd_pcm_substream *substream) prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.command = NO_DMA_XFER; mxs_dma_unfreeze(prtd->dma_ch); + + mxs_dma_disable(prtd->dma_ch); } static int mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa17..4aba33fa6b70 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -774,9 +774,19 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (!w->power_check) continue; - power = w->power_check(w); - if (power) - sys_power = 1; + /* If we're suspending then pull down all the + * power. */ + switch (event) { + case SND_SOC_DAPM_STREAM_SUSPEND: + power = 0; + break; + + default: + power = w->power_check(w); + if (power) + sys_power = 1; + break; + } if (w->power == power) continue; diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index 44b9cdc8a83b..8da53c56c4d2 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -752,7 +752,7 @@ static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t s return 0; /* already large enough */ vfree(runtime->dma_area); } - runtime->dma_area = vmalloc(size); + runtime->dma_area = vmalloc_user(size); if (!runtime->dma_area) return -ENOMEM; runtime->dma_bytes = size; @@ -1934,7 +1934,7 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) struct snd_usb_stream *as = snd_pcm_substream_chip(substream); struct snd_usb_substream *subs = &as->substream[direction]; - if (subs->interface >= 0) { + if (!as->chip->shutdown && subs->interface >= 0) { usb_set_interface(subs->dev, subs->interface, 0); subs->interface = -1; } diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 8e7f78941ba6..e9a3a9dca15c 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -210,7 +210,7 @@ struct snd_usb_midi_endpoint_info { /* */ -#define combine_word(s) ((*s) | ((unsigned int)(s)[1] << 8)) +#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8)) #define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16)) #define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24)) |