diff options
Diffstat (limited to 'sound')
140 files changed, 16523 insertions, 2513 deletions
diff --git a/sound/Kconfig b/sound/Kconfig index fcad760f569..1fef141ef8e 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -97,6 +97,8 @@ source "sound/sh/Kconfig" # here assuming USB is defined before ALSA source "sound/usb/Kconfig" +source "sound/firewire/Kconfig" + # the following will depend on the order of config. # here assuming PCMCIA is defined before ALSA source "sound/pcmcia/Kconfig" diff --git a/sound/Makefile b/sound/Makefile index ec467decfa7..ce9132b1c39 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o obj-$(CONFIG_SOUND_PRIME) += oss/ obj-$(CONFIG_DMASOUND) += oss/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ - sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ + firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ obj-$(CONFIG_SND_AOA) += aoa/ # This one must be compilable even if sound is configured out diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index 91acc9a243e..7c1fc64cb53 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -30,6 +30,8 @@ #define DRIVER_NAME "aaci-pl041" +#define FRAME_PERIOD_US 21 + /* * PM support is not complete. Turn it off. */ @@ -48,7 +50,11 @@ static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97) if (v & SLFR_1RXV) readl(aaci->base + AACI_SL1RX); - writel(maincr, aaci->base + AACI_MAINCR); + if (maincr != readl(aaci->base + AACI_MAINCR)) { + writel(maincr, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); + } } /* @@ -64,8 +70,8 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { struct aaci *aaci = ac97->private_data; + int timeout; u32 v; - int timeout = 5000; if (ac97->num >= 4) return; @@ -81,14 +87,17 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, writel(val << 4, aaci->base + AACI_SL2TX); writel(reg << 12, aaci->base + AACI_SL1TX); - /* - * Wait for the transmission of both slots to complete. - */ + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; do { + udelay(1); v = readl(aaci->base + AACI_SLFR); } while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout); - if (!timeout) + if (v & (SLFR_1TXB|SLFR_2TXB)) dev_err(&aaci->dev->dev, "timeout waiting for write to complete\n"); @@ -101,9 +110,8 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { struct aaci *aaci = ac97->private_data; + int timeout, retries = 10; u32 v; - int timeout = 5000; - int retries = 10; if (ac97->num >= 4) return ~0; @@ -117,35 +125,34 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) */ writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX); - /* - * Wait for the transmission to complete. - */ + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; do { + udelay(1); v = readl(aaci->base + AACI_SLFR); } while ((v & SLFR_1TXB) && --timeout); - if (!timeout) { + if (v & SLFR_1TXB) { dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n"); v = ~0; goto out; } - /* - * Give the AC'97 codec more than enough time - * to respond. (42us = ~2 frames at 48kHz.) - */ - udelay(42); + /* Now wait for the response frame */ + udelay(FRAME_PERIOD_US); - /* - * Wait for slot 2 to indicate data. - */ - timeout = 5000; + /* And then wait an additional eight frame periods for data */ + timeout = FRAME_PERIOD_US * 8; do { + udelay(1); cond_resched(); v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV); } while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout); - if (!timeout) { + if (v != (SLFR_1RXV|SLFR_2RXV)) { dev_err(&aaci->dev->dev, "timeout on RX valid\n"); v = ~0; goto out; @@ -179,6 +186,7 @@ aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask) int timeout = 5000; do { + udelay(1); val = readl(aacirun->base + AACI_SR); } while (val & mask && timeout--); } @@ -874,7 +882,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci) * Give the AC'97 codec more than enough time * to wake up. (42us = ~2 frames at 48kHz.) */ - udelay(42); + udelay(FRAME_PERIOD_US * 2); ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus); if (ret) @@ -989,6 +997,8 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) * disabling the channel doesn't clear the FIFO. */ writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); writel(aaci->maincr, aaci->base + AACI_MAINCR); /* diff --git a/sound/core/control.c b/sound/core/control.c index 9ce00ed20fb..db51e4e6498 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -466,6 +466,52 @@ error: } /** + * snd_ctl_activate_id - activate/inactivate the control of the given id + * @card: the card instance + * @id: the control id to activate/inactivate + * @active: non-zero to activate + * + * Finds the control instance with the given id, and activate or + * inactivate the control together with notification, if changed. + * + * Returns 0 if unchanged, 1 if changed, or a negative error code on failure. + */ +int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id, + int active) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + ret = -ENOENT; + goto unlock; + } + index_offset = snd_ctl_get_ioff(kctl, &kctl->id); + vd = &kctl->vd[index_offset]; + ret = 0; + if (active) { + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) + goto unlock; + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } else { + if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) + goto unlock; + vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + ret = 1; + unlock: + up_write(&card->controls_rwsem); + if (ret > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, id); + return ret; +} +EXPORT_SYMBOL_GPL(snd_ctl_activate_id); + +/** * snd_ctl_rename_id - replace the id of a control on the card * @card: the card instance * @src_id: the old id diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig new file mode 100644 index 00000000000..e486f48660f --- /dev/null +++ b/sound/firewire/Kconfig @@ -0,0 +1,25 @@ +menuconfig SND_FIREWIRE + bool "FireWire sound devices" + depends on FIREWIRE + default y + help + Support for IEEE-1394/FireWire/iLink sound devices. + +if SND_FIREWIRE && FIREWIRE + +config SND_FIREWIRE_LIB + tristate + depends on SND_PCM + +config SND_FIREWIRE_SPEAKERS + tristate "FireWire speakers" + select SND_PCM + select SND_FIREWIRE_LIB + help + Say Y here to include support for the Griffin FireWave Surround + and the LaCie FireWire Speakers. + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-speakers. + +endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile new file mode 100644 index 00000000000..e5b1634d9ad --- /dev/null +++ b/sound/firewire/Makefile @@ -0,0 +1,6 @@ +snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ + fcp.o cmp.o amdtp.o +snd-firewire-speakers-objs := speakers.o + +obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o +obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c new file mode 100644 index 00000000000..b18140ff2b9 --- /dev/null +++ b/sound/firewire/amdtp.c @@ -0,0 +1,562 @@ +/* + * Audio and Music Data Transmission Protocol (IEC 61883-6) streams + * with Common Isochronous Packet (IEC 61883-1) headers + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/firewire.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include "amdtp.h" + +#define TICKS_PER_CYCLE 3072 +#define CYCLES_PER_SECOND 8000 +#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND) + +#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 µs */ + +#define TAG_CIP 1 + +#define CIP_EOH (1u << 31) +#define CIP_FMT_AM (0x10 << 24) +#define AMDTP_FDF_AM824 (0 << 19) +#define AMDTP_FDF_SFC_SHIFT 16 + +/* TODO: make these configurable */ +#define INTERRUPT_INTERVAL 16 +#define QUEUE_LENGTH 48 + +/** + * amdtp_out_stream_init - initialize an AMDTP output stream structure + * @s: the AMDTP output stream to initialize + * @unit: the target of the stream + * @flags: the packet transmission method to use + */ +int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, + enum cip_out_flags flags) +{ + if (flags != CIP_NONBLOCKING) + return -EINVAL; + + s->unit = fw_unit_get(unit); + s->flags = flags; + s->context = ERR_PTR(-1); + mutex_init(&s->mutex); + s->packet_index = 0; + + return 0; +} +EXPORT_SYMBOL(amdtp_out_stream_init); + +/** + * amdtp_out_stream_destroy - free stream resources + * @s: the AMDTP output stream to destroy + */ +void amdtp_out_stream_destroy(struct amdtp_out_stream *s) +{ + WARN_ON(!IS_ERR(s->context)); + mutex_destroy(&s->mutex); + fw_unit_put(s->unit); +} +EXPORT_SYMBOL(amdtp_out_stream_destroy); + +/** + * amdtp_out_stream_set_rate - set the sample rate + * @s: the AMDTP output stream to configure + * @rate: the sample rate + * + * The sample rate must be set before the stream is started, and must not be + * changed while the stream is running. + */ +void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate) +{ + static const struct { + unsigned int rate; + unsigned int syt_interval; + } rate_info[] = { + [CIP_SFC_32000] = { 32000, 8, }, + [CIP_SFC_44100] = { 44100, 8, }, + [CIP_SFC_48000] = { 48000, 8, }, + [CIP_SFC_88200] = { 88200, 16, }, + [CIP_SFC_96000] = { 96000, 16, }, + [CIP_SFC_176400] = { 176400, 32, }, + [CIP_SFC_192000] = { 192000, 32, }, + }; + unsigned int sfc; + + if (WARN_ON(!IS_ERR(s->context))) + return; + + for (sfc = 0; sfc < ARRAY_SIZE(rate_info); ++sfc) + if (rate_info[sfc].rate == rate) { + s->sfc = sfc; + s->syt_interval = rate_info[sfc].syt_interval; + return; + } + WARN_ON(1); +} +EXPORT_SYMBOL(amdtp_out_stream_set_rate); + +/** + * amdtp_out_stream_get_max_payload - get the stream's packet size + * @s: the AMDTP output stream + * + * This function must not be called before the stream has been configured + * with amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and + * amdtp_out_stream_set_midi(). + */ +unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s) +{ + static const unsigned int max_data_blocks[] = { + [CIP_SFC_32000] = 4, + [CIP_SFC_44100] = 6, + [CIP_SFC_48000] = 6, + [CIP_SFC_88200] = 12, + [CIP_SFC_96000] = 12, + [CIP_SFC_176400] = 23, + [CIP_SFC_192000] = 24, + }; + + s->data_block_quadlets = s->pcm_channels; + s->data_block_quadlets += DIV_ROUND_UP(s->midi_ports, 8); + + return 8 + max_data_blocks[s->sfc] * 4 * s->data_block_quadlets; +} +EXPORT_SYMBOL(amdtp_out_stream_get_max_payload); + +static void amdtp_write_s16(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); +static void amdtp_write_s32(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); + +/** + * amdtp_out_stream_set_pcm_format - set the PCM format + * @s: the AMDTP output stream to configure + * @format: the format of the ALSA PCM device + * + * The sample format must be set before the stream is started, and must not be + * changed while the stream is running. + */ +void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, + snd_pcm_format_t format) +{ + if (WARN_ON(!IS_ERR(s->context))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + s->transfer_samples = amdtp_write_s16; + break; + case SNDRV_PCM_FORMAT_S32: + s->transfer_samples = amdtp_write_s32; + break; + } +} +EXPORT_SYMBOL(amdtp_out_stream_set_pcm_format); + +static unsigned int calculate_data_blocks(struct amdtp_out_stream *s) +{ + unsigned int phase, data_blocks; + + if (!cip_sfc_is_base_44100(s->sfc)) { + /* Sample_rate / 8000 is an integer, and precomputed. */ + data_blocks = s->data_block_state; + } else { + phase = s->data_block_state; + + /* + * This calculates the number of data blocks per packet so that + * 1) the overall rate is correct and exactly synchronized to + * the bus clock, and + * 2) packets with a rounded-up number of blocks occur as early + * as possible in the sequence (to prevent underruns of the + * device's buffer). + */ + if (s->sfc == CIP_SFC_44100) + /* 6 6 5 6 5 6 5 ... */ + data_blocks = 5 + ((phase & 1) ^ + (phase == 0 || phase >= 40)); + else + /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */ + data_blocks = 11 * (s->sfc >> 1) + (phase == 0); + if (++phase >= (80 >> (s->sfc >> 1))) + phase = 0; + s->data_block_state = phase; + } + + return data_blocks; +} + +static unsigned int calculate_syt(struct amdtp_out_stream *s, + unsigned int cycle) +{ + unsigned int syt_offset, phase, index, syt; + + if (s->last_syt_offset < TICKS_PER_CYCLE) { + if (!cip_sfc_is_base_44100(s->sfc)) + syt_offset = s->last_syt_offset + s->syt_offset_state; + else { + /* + * The time, in ticks, of the n'th SYT_INTERVAL sample is: + * n * SYT_INTERVAL * 24576000 / sample_rate + * Modulo TICKS_PER_CYCLE, the difference between successive + * elements is about 1386.23. Rounding the results of this + * formula to the SYT precision results in a sequence of + * differences that begins with: + * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ... + * This code generates _exactly_ the same sequence. + */ + phase = s->syt_offset_state; + index = phase % 13; + syt_offset = s->last_syt_offset; + syt_offset += 1386 + ((index && !(index & 3)) || + phase == 146); + if (++phase >= 147) + phase = 0; + s->syt_offset_state = phase; + } + } else + syt_offset = s->last_syt_offset - TICKS_PER_CYCLE; + s->last_syt_offset = syt_offset; + + if (syt_offset < TICKS_PER_CYCLE) { + syt_offset += TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; + syt += syt_offset % TICKS_PER_CYCLE; + + return syt & 0xffff; + } else { + return 0xffff; /* no info */ + } +} + +static void amdtp_write_s32(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, frame_step, i, c; + const u32 *src; + + channels = s->pcm_channels; + src = (void *)runtime->dma_area + + s->pcm_buffer_pointer * (runtime->frame_bits / 8); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frame_step = s->data_block_quadlets - channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + src++; + buffer++; + } + buffer += frame_step; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void amdtp_write_s16(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, frame_step, i, c; + const u16 *src; + + channels = s->pcm_channels; + src = (void *)runtime->dma_area + + s->pcm_buffer_pointer * (runtime->frame_bits / 8); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + frame_step = s->data_block_quadlets - channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src << 8) | 0x40000000); + src++; + buffer++; + } + buffer += frame_step; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int i, c; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < s->pcm_channels; ++c) + buffer[c] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +static void amdtp_fill_midi(struct amdtp_out_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int i; + + for (i = 0; i < frames; ++i) + buffer[s->pcm_channels + i * s->data_block_quadlets] = + cpu_to_be32(0x80000000); +} + +static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) +{ + __be32 *buffer; + unsigned int index, data_blocks, syt, ptr; + struct snd_pcm_substream *pcm; + struct fw_iso_packet packet; + int err; + + if (s->packet_index < 0) + return; + index = s->packet_index; + + data_blocks = calculate_data_blocks(s); + syt = calculate_syt(s, cycle); + + buffer = s->buffer.packets[index].buffer; + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | + (s->data_block_quadlets << 16) | + s->data_block_counter); + buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | + (s->sfc << AMDTP_FDF_SFC_SHIFT) | syt); + buffer += 2; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + s->transfer_samples(s, pcm, buffer, data_blocks); + else + amdtp_fill_pcm_silence(s, buffer, data_blocks); + if (s->midi_ports) + amdtp_fill_midi(s, buffer, data_blocks); + + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + packet.payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; + packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL); + packet.skip = 0; + packet.tag = TAG_CIP; + packet.sy = 0; + packet.header_length = 0; + + err = fw_iso_context_queue(s->context, &packet, &s->buffer.iso_buffer, + s->buffer.packets[index].offset); + if (err < 0) { + dev_err(&s->unit->device, "queueing error: %d\n", err); + s->packet_index = -1; + amdtp_out_stream_pcm_abort(s); + return; + } + + if (++index >= QUEUE_LENGTH) + index = 0; + s->packet_index = index; + + if (pcm) { + ptr = s->pcm_buffer_pointer + data_blocks; + if (ptr >= pcm->runtime->buffer_size) + ptr -= pcm->runtime->buffer_size; + ACCESS_ONCE(s->pcm_buffer_pointer) = ptr; + + s->pcm_period_pointer += data_blocks; + if (s->pcm_period_pointer >= pcm->runtime->period_size) { + s->pcm_period_pointer -= pcm->runtime->period_size; + snd_pcm_period_elapsed(pcm); + } + } +} + +static void out_packet_callback(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, void *data) +{ + struct amdtp_out_stream *s = data; + unsigned int i, packets = header_length / 4; + + /* + * Compute the cycle of the last queued packet. + * (We need only the four lowest bits for the SYT, so we can ignore + * that bits 0-11 must wrap around at 3072.) + */ + cycle += QUEUE_LENGTH - packets; + + for (i = 0; i < packets; ++i) + queue_out_packet(s, ++cycle); +} + +static int queue_initial_skip_packets(struct amdtp_out_stream *s) +{ + struct fw_iso_packet skip_packet = { + .skip = 1, + }; + unsigned int i; + int err; + + for (i = 0; i < QUEUE_LENGTH; ++i) { + skip_packet.interrupt = IS_ALIGNED(s->packet_index + 1, + INTERRUPT_INTERVAL); + err = fw_iso_context_queue(s->context, &skip_packet, NULL, 0); + if (err < 0) + return err; + if (++s->packet_index >= QUEUE_LENGTH) + s->packet_index = 0; + } + + return 0; +} + +/** + * amdtp_out_stream_start - start sending packets + * @s: the AMDTP output stream to start + * @channel: the isochronous channel on the bus + * @speed: firewire speed code + * + * The stream cannot be started until it has been configured with + * amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and + * amdtp_out_stream_set_midi(); and it must be started before any + * PCM or MIDI device can be started. + */ +int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) +{ + static const struct { + unsigned int data_block; + unsigned int syt_offset; + } initial_state[] = { + [CIP_SFC_32000] = { 4, 3072 }, + [CIP_SFC_48000] = { 6, 1024 }, + [CIP_SFC_96000] = { 12, 1024 }, + [CIP_SFC_192000] = { 24, 1024 }, + [CIP_SFC_44100] = { 0, 67 }, + [CIP_SFC_88200] = { 0, 67 }, + [CIP_SFC_176400] = { 0, 67 }, + }; + int err; + + mutex_lock(&s->mutex); + + if (WARN_ON(!IS_ERR(s->context) || + (!s->pcm_channels && !s->midi_ports))) { + err = -EBADFD; + goto err_unlock; + } + + s->data_block_state = initial_state[s->sfc].data_block; + s->syt_offset_state = initial_state[s->sfc].syt_offset; + s->last_syt_offset = TICKS_PER_CYCLE; + + err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, + amdtp_out_stream_get_max_payload(s), + DMA_TO_DEVICE); + if (err < 0) + goto err_unlock; + + s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, + FW_ISO_CONTEXT_TRANSMIT, + channel, speed, 0, + out_packet_callback, s); + if (IS_ERR(s->context)) { + err = PTR_ERR(s->context); + if (err == -EBUSY) + dev_err(&s->unit->device, + "no free output stream on this controller\n"); + goto err_buffer; + } + + amdtp_out_stream_update(s); + + s->packet_index = 0; + s->data_block_counter = 0; + err = queue_initial_skip_packets(s); + if (err < 0) + goto err_context; + + err = fw_iso_context_start(s->context, -1, 0, 0); + if (err < 0) + goto err_context; + + mutex_unlock(&s->mutex); + + return 0; + +err_context: + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); +err_buffer: + iso_packets_buffer_destroy(&s->buffer, s->unit); +err_unlock: + mutex_unlock(&s->mutex); + + return err; +} +EXPORT_SYMBOL(amdtp_out_stream_start); + +/** + * amdtp_out_stream_update - update the stream after a bus reset + * @s: the AMDTP output stream + */ +void amdtp_out_stream_update(struct amdtp_out_stream *s) +{ + ACCESS_ONCE(s->source_node_id_field) = + (fw_parent_device(s->unit)->card->node_id & 0x3f) << 24; +} +EXPORT_SYMBOL(amdtp_out_stream_update); + +/** + * amdtp_out_stream_stop - stop sending packets + * @s: the AMDTP output stream to stop + * + * All PCM and MIDI devices of the stream must be stopped before the stream + * itself can be stopped. + */ +void amdtp_out_stream_stop(struct amdtp_out_stream *s) +{ + mutex_lock(&s->mutex); + + if (IS_ERR(s->context)) { + mutex_unlock(&s->mutex); + return; + } + + fw_iso_context_stop(s->context); + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); + iso_packets_buffer_destroy(&s->buffer, s->unit); + + mutex_unlock(&s->mutex); +} +EXPORT_SYMBOL(amdtp_out_stream_stop); + +/** + * amdtp_out_stream_pcm_abort - abort the running PCM device + * @s: the AMDTP stream about to be stopped + * + * If the isochronous stream needs to be stopped asynchronously, call this + * function first to stop the PCM device. + */ +void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s) +{ + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) { + snd_pcm_stream_lock_irq(pcm); + if (snd_pcm_running(pcm)) + snd_pcm_stop(pcm, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irq(pcm); + } +} +EXPORT_SYMBOL(amdtp_out_stream_pcm_abort); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h new file mode 100644 index 00000000000..537a9cb8358 --- /dev/null +++ b/sound/firewire/amdtp.h @@ -0,0 +1,169 @@ +#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include "packets-buffer.h" + +/** + * enum cip_out_flags - describes details of the streaming protocol + * @CIP_NONBLOCKING: In non-blocking mode, each packet contains + * sample_rate/8000 samples, with rounding up or down to adjust + * for clock skew and left-over fractional samples. This should + * be used if supported by the device. + */ +enum cip_out_flags { + CIP_NONBLOCKING = 0, +}; + +/** + * enum cip_sfc - a stream's sample rate + */ +enum cip_sfc { + CIP_SFC_32000 = 0, + CIP_SFC_44100 = 1, + CIP_SFC_48000 = 2, + CIP_SFC_88200 = 3, + CIP_SFC_96000 = 4, + CIP_SFC_176400 = 5, + CIP_SFC_192000 = 6, +}; + +#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \ + SNDRV_PCM_FMTBIT_S32) + +struct fw_unit; +struct fw_iso_context; +struct snd_pcm_substream; + +struct amdtp_out_stream { + struct fw_unit *unit; + enum cip_out_flags flags; + struct fw_iso_context *context; + struct mutex mutex; + + enum cip_sfc sfc; + unsigned int data_block_quadlets; + unsigned int pcm_channels; + unsigned int midi_ports; + void (*transfer_samples)(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); + + unsigned int syt_interval; + unsigned int source_node_id_field; + struct iso_packets_buffer buffer; + + struct snd_pcm_substream *pcm; + + int packet_index; + unsigned int data_block_counter; + + unsigned int data_block_state; + + unsigned int last_syt_offset; + unsigned int syt_offset_state; + + unsigned int pcm_buffer_pointer; + unsigned int pcm_period_pointer; +}; + +int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, + enum cip_out_flags flags); +void amdtp_out_stream_destroy(struct amdtp_out_stream *s); + +void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate); +unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s); + +int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed); +void amdtp_out_stream_update(struct amdtp_out_stream *s); +void amdtp_out_stream_stop(struct amdtp_out_stream *s); + +void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, + snd_pcm_format_t format); +void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s); + +/** + * amdtp_out_stream_set_pcm - configure format of PCM samples + * @s: the AMDTP output stream to be configured + * @pcm_channels: the number of PCM samples in each data block, to be encoded + * as AM824 multi-bit linear audio + * + * This function must not be called while the stream is running. + */ +static inline void amdtp_out_stream_set_pcm(struct amdtp_out_stream *s, + unsigned int pcm_channels) +{ + s->pcm_channels = pcm_channels; +} + +/** + * amdtp_out_stream_set_midi - configure format of MIDI data + * @s: the AMDTP output stream to be configured + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * + * This function must not be called while the stream is running. + */ +static inline void amdtp_out_stream_set_midi(struct amdtp_out_stream *s, + unsigned int midi_ports) +{ + s->midi_ports = midi_ports; +} + +/** + * amdtp_out_streaming_error - check for streaming error + * @s: the AMDTP output stream + * + * If this function returns true, the stream's packet queue has stopped due to + * an asynchronous error. + */ +static inline bool amdtp_out_streaming_error(struct amdtp_out_stream *s) +{ + return s->packet_index < 0; +} + +/** + * amdtp_out_stream_pcm_prepare - prepare PCM device for running + * @s: the AMDTP output stream + * + * This function should be called from the PCM device's .prepare callback. + */ +static inline void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s) +{ + s->pcm_buffer_pointer = 0; + s->pcm_period_pointer = 0; +} + +/** + * amdtp_out_stream_pcm_trigger - start/stop playback from a PCM device + * @s: the AMDTP output stream + * @pcm: the PCM device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of PCM data. This function should be called from the PCM + * device's .trigger callback. + */ +static inline void amdtp_out_stream_pcm_trigger(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm) +{ + ACCESS_ONCE(s->pcm) = pcm; +} + +/** + * amdtp_out_stream_pcm_pointer - get the PCM buffer position + * @s: the AMDTP output stream that transports the PCM data + * + * Returns the current buffer position, in frames. + */ +static inline unsigned long +amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s) +{ + return ACCESS_ONCE(s->pcm_buffer_pointer); +} + +static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) +{ + return sfc & 1; +} + +#endif diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 00000000000..4a37f3a6fab --- /dev/null +++ b/sound/firewire/cmp.c @@ -0,0 +1,308 @@ +/* + * Connection Management Procedures (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/sched.h> +#include "lib.h" +#include "iso-resources.h" +#include "cmp.h" + +#define IMPR_SPEED_MASK 0xc0000000 +#define IMPR_SPEED_SHIFT 30 +#define IMPR_XSPEED_MASK 0x00000060 +#define IMPR_XSPEED_SHIFT 5 +#define IMPR_PLUGS_MASK 0x0000001f + +#define IPCR_ONLINE 0x80000000 +#define IPCR_BCAST_CONN 0x40000000 +#define IPCR_P2P_CONN_MASK 0x3f000000 +#define IPCR_P2P_CONN_SHIFT 24 +#define IPCR_CHANNEL_MASK 0x003f0000 +#define IPCR_CHANNEL_SHIFT 16 + +enum bus_reset_handling { + ABORT_ON_BUS_RESET, + SUCCEED_ON_BUS_RESET, +}; + +static __attribute__((format(printf, 2, 3))) +void cmp_error(struct cmp_connection *c, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + dev_err(&c->resources.unit->device, "%cPCR%u: %pV", + 'i', c->pcr_index, &(struct va_format){ fmt, &va }); + va_end(va); +} + +static int pcr_modify(struct cmp_connection *c, + __be32 (*modify)(struct cmp_connection *c, __be32 old), + int (*check)(struct cmp_connection *c, __be32 pcr), + enum bus_reset_handling bus_reset_handling) +{ + struct fw_device *device = fw_parent_device(c->resources.unit); + __be32 *buffer = c->resources.buffer; + int generation = c->resources.generation; + int rcode, errors = 0; + __be32 old_arg; + int err; + + buffer[0] = c->last_pcr_value; + for (;;) { + old_arg = buffer[0]; + buffer[1] = modify(c, buffer[0]); + + rcode = fw_run_transaction( + device->card, TCODE_LOCK_COMPARE_SWAP, + device->node_id, generation, device->max_speed, + CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), + buffer, 8); + + if (rcode == RCODE_COMPLETE) { + if (buffer[0] == old_arg) /* success? */ + break; + + if (check) { + err = check(c, buffer[0]); + if (err < 0) + return err; + } + } else if (rcode == RCODE_GENERATION) + goto bus_reset; + else if (rcode_is_permanent_error(rcode) || ++errors >= 3) + goto io_error; + } + c->last_pcr_value = buffer[1]; + + return 0; + +io_error: + cmp_error(c, "transaction failed: %s\n", rcode_string(rcode)); + return -EIO; + +bus_reset: + return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0; +} + + +/** + * cmp_connection_init - initializes a connection manager + * @c: the connection manager to initialize + * @unit: a unit of the target device + * @ipcr_index: the index of the iPCR on the target device + */ +int cmp_connection_init(struct cmp_connection *c, + struct fw_unit *unit, + unsigned int ipcr_index) +{ + __be32 impr_be; + u32 impr; + int err; + + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + CSR_REGISTER_BASE + CSR_IMPR, + &impr_be, 4); + if (err < 0) + return err; + impr = be32_to_cpu(impr_be); + + if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) + return -EINVAL; + + err = fw_iso_resources_init(&c->resources, unit); + if (err < 0) + return err; + + c->connected = false; + mutex_init(&c->mutex); + c->last_pcr_value = cpu_to_be32(0x80000000); + c->pcr_index = ipcr_index; + c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; + if (c->max_speed == SCODE_BETA) + c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; + + return 0; +} +EXPORT_SYMBOL(cmp_connection_init); + +/** + * cmp_connection_destroy - free connection manager resources + * @c: the connection manager + */ +void cmp_connection_destroy(struct cmp_connection *c) +{ + WARN_ON(c->connected); + mutex_destroy(&c->mutex); + fw_iso_resources_destroy(&c->resources); +} +EXPORT_SYMBOL(cmp_connection_destroy); + + +static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) +{ + ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | + IPCR_P2P_CONN_MASK | + IPCR_CHANNEL_MASK); + ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); + ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); + + return ipcr; +} + +static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) +{ + if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | + IPCR_P2P_CONN_MASK)) { + cmp_error(c, "plug is already in use\n"); + return -EBUSY; + } + if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { + cmp_error(c, "plug is not on-line\n"); + return -ECONNREFUSED; + } + + return 0; +} + +/** + * cmp_connection_establish - establish a connection to the target + * @c: the connection manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * + * This function establishes a point-to-point connection from the local + * computer to the target by allocating isochronous resources (channel and + * bandwidth) and setting the target's input plug control register. When this + * function succeeds, the caller is responsible for starting transmitting + * packets. + */ +int cmp_connection_establish(struct cmp_connection *c, + unsigned int max_payload_bytes) +{ + int err; + + if (WARN_ON(c->connected)) + return -EISCONN; + + c->speed = min(c->max_speed, + fw_parent_device(c->resources.unit)->max_speed); + + mutex_lock(&c->mutex); + +retry_after_bus_reset: + err = fw_iso_resources_allocate(&c->resources, + max_payload_bytes, c->speed); + if (err < 0) + goto err_mutex; + + err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, + ABORT_ON_BUS_RESET); + if (err == -EAGAIN) { + fw_iso_resources_free(&c->resources); + goto retry_after_bus_reset; + } + if (err < 0) + goto err_resources; + + c->connected = true; + + mutex_unlock(&c->mutex); + + return 0; + +err_resources: + fw_iso_resources_free(&c->resources); +err_mutex: + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_establish); + +/** + * cmp_connection_update - update the connection after a bus reset + * @c: the connection manager + * + * This function must be called from the driver's .update handler to reestablish + * any connection that might have been active. + * + * Returns zero on success, or a negative error code. On an error, the + * connection is broken and the caller must stop transmitting iso packets. + */ +int cmp_connection_update(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return 0; + } + + err = fw_iso_resources_update(&c->resources); + if (err < 0) + goto err_unconnect; + + err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, + SUCCEED_ON_BUS_RESET); + if (err < 0) + goto err_resources; + + mutex_unlock(&c->mutex); + + return 0; + +err_resources: + fw_iso_resources_free(&c->resources); +err_unconnect: + c->connected = false; + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_update); + + +static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) +{ + return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); +} + +/** + * cmp_connection_break - break the connection to the target + * @c: the connection manager + * + * This function deactives the connection in the target's input plug control + * register, and frees the isochronous resources of the connection. Before + * calling this function, the caller should cease transmitting packets. + */ +void cmp_connection_break(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return; + } + + err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); + if (err < 0) + cmp_error(c, "plug is still connected\n"); + + fw_iso_resources_free(&c->resources); + + c->connected = false; + + mutex_unlock(&c->mutex); +} +EXPORT_SYMBOL(cmp_connection_break); diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h new file mode 100644 index 00000000000..f47de08feb1 --- /dev/null +++ b/sound/firewire/cmp.h @@ -0,0 +1,41 @@ +#ifndef SOUND_FIREWIRE_CMP_H_INCLUDED +#define SOUND_FIREWIRE_CMP_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/types.h> +#include "iso-resources.h" + +struct fw_unit; + +/** + * struct cmp_connection - manages an isochronous connection to a device + * @speed: the connection's actual speed + * + * This structure manages (using CMP) an isochronous stream from the local + * computer to a device's input plug (iPCR). + * + * There is no corresponding oPCR created on the local computer, so it is not + * possible to overlay connections on top of this one. + */ +struct cmp_connection { + int speed; + /* private: */ + bool connected; + struct mutex mutex; + struct fw_iso_resources resources; + __be32 last_pcr_value; + unsigned int pcr_index; + unsigned int max_speed; +}; + +int cmp_connection_init(struct cmp_connection *connection, + struct fw_unit *unit, + unsigned int ipcr_index); +void cmp_connection_destroy(struct cmp_connection *connection); + +int cmp_connection_establish(struct cmp_connection *connection, + unsigned int max_payload); +int cmp_connection_update(struct cmp_connection *connection); +void cmp_connection_break(struct cmp_connection *connection); + +#endif diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c new file mode 100644 index 00000000000..ec578b5ad8d --- /dev/null +++ b/sound/firewire/fcp.c @@ -0,0 +1,224 @@ +/* + * Function Control Protocol (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include "fcp.h" +#include "lib.h" + +#define CTS_AVC 0x00 + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define FCP_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(transactions_lock); +static LIST_HEAD(transactions); + +enum fcp_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE, +}; + +struct fcp_transaction { + struct list_head list; + struct fw_unit *unit; + void *response_buffer; + unsigned int response_size; + unsigned int response_match_bytes; + enum fcp_state state; + wait_queue_head_t wait; +}; + +/** + * fcp_avc_transaction - send an AV/C command and wait for its response + * @unit: a unit on the target device + * @command: a buffer containing the command frame; must be DMA-able + * @command_size: the size of @command + * @response: a buffer for the response frame + * @response_size: the maximum size of @response + * @response_match_bytes: a bitmap specifying the bytes used to detect the + * correct response frame + * + * This function sends a FCP command frame to the target and waits for the + * corresponding response frame to be returned. + * + * Because it is possible for multiple FCP transactions to be active at the + * same time, the correct response frame is detected by the value of certain + * bytes. These bytes must be set in @response before calling this function, + * and the corresponding bits must be set in @response_match_bytes. + * + * @command and @response can point to the same buffer. + * + * Asynchronous operation (INTERIM, NOTIFY) is not supported at the moment. + * + * Returns the actual size of the response frame, or a negative error code. + */ +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes) +{ + struct fcp_transaction t; + int tcode, ret, tries = 0; + + t.unit = unit; + t.response_buffer = response; + t.response_size = response_size; + t.response_match_bytes = response_match_bytes; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + + spin_lock_irq(&transactions_lock); + list_add_tail(&t.list, &transactions); + spin_unlock_irq(&transactions_lock); + + for (;;) { + tcode = command_size == 4 ? TCODE_WRITE_QUADLET_REQUEST + : TCODE_WRITE_BLOCK_REQUEST; + ret = snd_fw_transaction(t.unit, tcode, + CSR_REGISTER_BASE + CSR_FCP_COMMAND, + (void *)command, command_size); + if (ret < 0) + break; + + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(FCP_TIMEOUT_MS)); + + if (t.state == STATE_COMPLETE) { + ret = t.response_size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "FCP command timed out\n"); + ret = -EIO; + break; + } + } + + spin_lock_irq(&transactions_lock); + list_del(&t.list); + spin_unlock_irq(&transactions_lock); + + return ret; +} +EXPORT_SYMBOL(fcp_avc_transaction); + +/** + * fcp_bus_reset - inform the target handler about a bus reset + * @unit: the unit that might be used by fcp_avc_transaction() + * + * This function must be called from the driver's .update handler to inform + * the FCP transaction handler that a bus reset has happened. Any pending FCP + * transactions are retried. + */ +void fcp_bus_reset(struct fw_unit *unit) +{ + struct fcp_transaction *t; + + spin_lock_irq(&transactions_lock); + list_for_each_entry(t, &transactions, list) { + if (t->unit == unit && + t->state == STATE_PENDING) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transactions_lock); +} +EXPORT_SYMBOL(fcp_bus_reset); + +/* checks whether the response matches the masked bytes in response_buffer */ +static bool is_matching_response(struct fcp_transaction *transaction, + const void *response, size_t length) +{ + const u8 *p1, *p2; + unsigned int mask, i; + + p1 = response; + p2 = transaction->response_buffer; + mask = transaction->response_match_bytes; + + for (i = 0; ; ++i) { + if ((mask & 1) && p1[i] != p2[i]) + return false; + mask >>= 1; + if (!mask) + return true; + if (--length == 0) + return false; + } +} + +static void fcp_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct fcp_transaction *t; + unsigned long flags; + + if (length < 1 || (*(const u8 *)data & 0xf0) != CTS_AVC) + return; + + spin_lock_irqsave(&transactions_lock, flags); + list_for_each_entry(t, &transactions, list) { + struct fw_device *device = fw_parent_device(t->unit); + if (device->card != card || + device->generation != generation) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if (t->state == STATE_PENDING && + is_matching_response(t, data, length)) { + t->state = STATE_COMPLETE; + t->response_size = min((unsigned int)length, + t->response_size); + memcpy(t->response_buffer, data, t->response_size); + wake_up(&t->wait); + } + } + spin_unlock_irqrestore(&transactions_lock, flags); +} + +static struct fw_address_handler response_register_handler = { + .length = 0x200, + .address_callback = fcp_response, +}; + +static int __init fcp_module_init(void) +{ + static const struct fw_address_region response_register_region = { + .start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE, + .end = CSR_REGISTER_BASE + CSR_FCP_END, + }; + + fw_core_add_address_handler(&response_register_handler, + &response_register_region); + + return 0; +} + +static void __exit fcp_module_exit(void) +{ + WARN_ON(!list_empty(&transactions)); + fw_core_remove_address_handler(&response_register_handler); +} + +module_init(fcp_module_init); +module_exit(fcp_module_exit); diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h new file mode 100644 index 00000000000..86595688bd9 --- /dev/null +++ b/sound/firewire/fcp.h @@ -0,0 +1,12 @@ +#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED +#define SOUND_FIREWIRE_FCP_H_INCLUDED + +struct fw_unit; + +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes); +void fcp_bus_reset(struct fw_unit *unit); + +#endif diff --git a/sound/firewire/iso-resources.c b/sound/firewire/iso-resources.c new file mode 100644 index 00000000000..775dbd5f344 --- /dev/null +++ b/sound/firewire/iso-resources.c @@ -0,0 +1,232 @@ +/* + * isochronous resources helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include "iso-resources.h" + +/** + * fw_iso_resources_init - initializes a &struct fw_iso_resources + * @r: the resource manager to initialize + * @unit: the device unit for which the resources will be needed + * + * If the device does not support all channel numbers, change @r->channels_mask + * after calling this function. + */ +int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit) +{ + r->buffer = kmalloc(2 * 4, GFP_KERNEL); + if (!r->buffer) + return -ENOMEM; + + r->channels_mask = ~0uLL; + r->unit = fw_unit_get(unit); + mutex_init(&r->mutex); + r->allocated = false; + + return 0; +} + +/** + * fw_iso_resources_destroy - destroy a resource manager + * @r: the resource manager that is no longer needed + */ +void fw_iso_resources_destroy(struct fw_iso_resources *r) +{ + WARN_ON(r->allocated); + kfree(r->buffer); + mutex_destroy(&r->mutex); + fw_unit_put(r->unit); +} + +static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed) +{ + unsigned int bytes, s400_bytes; + + /* iso packets have three header quadlets and quadlet-aligned payload */ + bytes = 3 * 4 + ALIGN(max_payload_bytes, 4); + + /* convert to bandwidth units (quadlets at S1600 = bytes at S400) */ + if (speed <= SCODE_400) + s400_bytes = bytes * (1 << (SCODE_400 - speed)); + else + s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400)); + + return s400_bytes; +} + +static int current_bandwidth_overhead(struct fw_card *card) +{ + /* + * Under the usual pessimistic assumption (cable length 4.5 m), the + * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or + * 88.3 + N * 24.3 in bandwidth units. + * + * The calculation below tries to deduce N from the current gap count. + * If the gap count has been optimized by measuring the actual packet + * transmission time, this derived overhead should be near the actual + * overhead as well. + */ + return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512; +} + +static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card) +{ + for (;;) { + s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64(); + if (delay <= 0) + return 0; + if (schedule_timeout_interruptible(delay) > 0) + return -ERESTARTSYS; + } +} + +/** + * fw_iso_resources_allocate - allocate isochronous channel and bandwidth + * @r: the resource manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * @speed: the speed (e.g., SCODE_400) at which the packets will be sent + * + * This function allocates one isochronous channel and enough bandwidth for the + * specified packet size. + * + * Returns the channel number that the caller must use for streaming, or + * a negative error code. Due to potentionally long delays, this function is + * interruptible and can return -ERESTARTSYS. On success, the caller is + * responsible for calling fw_iso_resources_update() on bus resets, and + * fw_iso_resources_free() when the resources are not longer needed. + */ +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel, err; + + if (WARN_ON(r->allocated)) + return -EBADFD; + + r->bandwidth = packet_bandwidth(max_payload_bytes, speed); + +retry_after_bus_reset: + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + err = wait_isoch_resource_delay_after_bus_reset(card); + if (err < 0) + return err; + + mutex_lock(&r->mutex); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, r->channels_mask, + &channel, &bandwidth, true, r->buffer); + if (channel == -EAGAIN) { + mutex_unlock(&r->mutex); + goto retry_after_bus_reset; + } + if (channel >= 0) { + r->channel = channel; + r->allocated = true; + } else { + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} + +/** + * fw_iso_resources_update - update resource allocations after a bus reset + * @r: the resource manager + * + * This function must be called from the driver's .update handler to reallocate + * any resources that were allocated before the bus reset. It is safe to call + * this function if no resources are currently allocated. + * + * Returns a negative error code on failure. If this happens, the caller must + * stop streaming. + */ +int fw_iso_resources_update(struct fw_iso_resources *r) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel; + + mutex_lock(&r->mutex); + + if (!r->allocated) { + mutex_unlock(&r->mutex); + return 0; + } + + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, true, r->buffer); + /* + * When another bus reset happens, pretend that the allocation + * succeeded; we will try again for the new generation later. + */ + if (channel < 0 && channel != -EAGAIN) { + r->allocated = false; + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} + +/** + * fw_iso_resources_free - frees allocated resources + * @r: the resource manager + * + * This function deallocates the channel and bandwidth, if allocated. + */ +void fw_iso_resources_free(struct fw_iso_resources *r) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel; + + mutex_lock(&r->mutex); + + if (r->allocated) { + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, false, r->buffer); + if (channel < 0) + dev_err(&r->unit->device, + "isochronous resource deallocation failed\n"); + + r->allocated = false; + } + + mutex_unlock(&r->mutex); +} diff --git a/sound/firewire/iso-resources.h b/sound/firewire/iso-resources.h new file mode 100644 index 00000000000..3f0730e4d84 --- /dev/null +++ b/sound/firewire/iso-resources.h @@ -0,0 +1,39 @@ +#ifndef SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED +#define SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/types.h> + +struct fw_unit; + +/** + * struct fw_iso_resources - manages channel/bandwidth allocation + * @channels_mask: if the device does not support all channel numbers, set this + * bit mask to something else than the default (all ones) + * + * This structure manages (de)allocation of isochronous resources (channel and + * bandwidth) for one isochronous stream. + */ +struct fw_iso_resources { + u64 channels_mask; + /* private: */ + struct fw_unit *unit; + struct mutex mutex; + unsigned int channel; + unsigned int bandwidth; /* in bandwidth units, without overhead */ + unsigned int bandwidth_overhead; + int generation; /* in which allocation is valid */ + bool allocated; + __be32 *buffer; +}; + +int fw_iso_resources_init(struct fw_iso_resources *r, + struct fw_unit *unit); +void fw_iso_resources_destroy(struct fw_iso_resources *r); + +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed); +int fw_iso_resources_update(struct fw_iso_resources *r); +void fw_iso_resources_free(struct fw_iso_resources *r); + +#endif diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c new file mode 100644 index 00000000000..4750cea2210 --- /dev/null +++ b/sound/firewire/lib.c @@ -0,0 +1,85 @@ +/* + * miscellaneous helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/module.h> +#include "lib.h" + +#define ERROR_RETRY_DELAY_MS 5 + +/** + * rcode_string - convert a firewire result code to a string + * @rcode: the result + */ +const char *rcode_string(unsigned int rcode) +{ + static const char *const names[] = { + [RCODE_COMPLETE] = "complete", + [RCODE_CONFLICT_ERROR] = "conflict error", + [RCODE_DATA_ERROR] = "data error", + [RCODE_TYPE_ERROR] = "type error", + [RCODE_ADDRESS_ERROR] = "address error", + [RCODE_SEND_ERROR] = "send error", + [RCODE_CANCELLED] = "cancelled", + [RCODE_BUSY] = "busy", + [RCODE_GENERATION] = "generation", + [RCODE_NO_ACK] = "no ack", + }; + + if (rcode < ARRAY_SIZE(names) && names[rcode]) + return names[rcode]; + else + return "unknown"; +} +EXPORT_SYMBOL(rcode_string); + +/** + * snd_fw_transaction - send a request and wait for its completion + * @unit: the driver's unit on the target device + * @tcode: the transaction code + * @offset: the address in the target's address space + * @buffer: input/output data + * @length: length of @buffer + * + * Submits an asynchronous request to the target device, and waits for the + * response. The node ID and the current generation are derived from @unit. + * On a bus reset or an error, the transaction is retried a few times. + * Returns zero on success, or a negative error code. + */ +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length) +{ + struct fw_device *device = fw_parent_device(unit); + int generation, rcode, tries = 0; + + for (;;) { + generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + rcode = fw_run_transaction(device->card, tcode, + device->node_id, generation, + device->max_speed, offset, + buffer, length); + + if (rcode == RCODE_COMPLETE) + return 0; + + if (rcode_is_permanent_error(rcode) || ++tries >= 3) { + dev_err(&unit->device, "transaction failed: %s\n", + rcode_string(rcode)); + return -EIO; + } + + msleep(ERROR_RETRY_DELAY_MS); + } +} +EXPORT_SYMBOL(snd_fw_transaction); + +MODULE_DESCRIPTION("FireWire audio helper functions"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h new file mode 100644 index 00000000000..064f3fd9ab0 --- /dev/null +++ b/sound/firewire/lib.h @@ -0,0 +1,19 @@ +#ifndef SOUND_FIREWIRE_LIB_H_INCLUDED +#define SOUND_FIREWIRE_LIB_H_INCLUDED + +#include <linux/firewire-constants.h> +#include <linux/types.h> + +struct fw_unit; + +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length); +const char *rcode_string(unsigned int rcode); + +/* returns true if retrying the transaction would not make sense */ +static inline bool rcode_is_permanent_error(int rcode) +{ + return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR; +} + +#endif diff --git a/sound/firewire/packets-buffer.c b/sound/firewire/packets-buffer.c new file mode 100644 index 00000000000..1e20e60ba6a --- /dev/null +++ b/sound/firewire/packets-buffer.c @@ -0,0 +1,74 @@ +/* + * helpers for managing a buffer for many packets + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/firewire.h> +#include <linux/slab.h> +#include "packets-buffer.h" + +/** + * iso_packets_buffer_init - allocates the memory for packets + * @b: the buffer structure to initialize + * @unit: the device at the other end of the stream + * @count: the number of packets + * @packet_size: the (maximum) size of a packet, in bytes + * @direction: %DMA_TO_DEVICE or %DMA_FROM_DEVICE + */ +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction) +{ + unsigned int packets_per_page, pages; + unsigned int i, page_index, offset_in_page; + void *p; + int err; + + b->packets = kmalloc(count * sizeof(*b->packets), GFP_KERNEL); + if (!b->packets) { + err = -ENOMEM; + goto error; + } + + packet_size = L1_CACHE_ALIGN(packet_size); + packets_per_page = PAGE_SIZE / packet_size; + if (WARN_ON(!packets_per_page)) { + err = -EINVAL; + goto error; + } + pages = DIV_ROUND_UP(count, packets_per_page); + + err = fw_iso_buffer_init(&b->iso_buffer, fw_parent_device(unit)->card, + pages, direction); + if (err < 0) + goto err_packets; + + for (i = 0; i < count; ++i) { + page_index = i / packets_per_page; + p = page_address(b->iso_buffer.pages[page_index]); + offset_in_page = (i % packets_per_page) * packet_size; + b->packets[i].buffer = p + offset_in_page; + b->packets[i].offset = page_index * PAGE_SIZE + offset_in_page; + } + + return 0; + +err_packets: + kfree(b->packets); +error: + return err; +} + +/** + * iso_packets_buffer_destroy - frees packet buffer resources + * @b: the buffer structure to free + * @unit: the device at the other end of the stream + */ +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit) +{ + fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card); + kfree(b->packets); +} diff --git a/sound/firewire/packets-buffer.h b/sound/firewire/packets-buffer.h new file mode 100644 index 00000000000..6513c5cb6ea --- /dev/null +++ b/sound/firewire/packets-buffer.h @@ -0,0 +1,26 @@ +#ifndef SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED +#define SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED + +#include <linux/dma-mapping.h> +#include <linux/firewire.h> + +/** + * struct iso_packets_buffer - manages a buffer for many packets + * @iso_buffer: the memory containing the packets + * @packets: an array, with each element pointing to one packet + */ +struct iso_packets_buffer { + struct fw_iso_buffer iso_buffer; + struct { + void *buffer; + unsigned int offset; + } *packets; +}; + +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction); +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit); + +#endif diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c new file mode 100644 index 00000000000..0fce9218abb --- /dev/null +++ b/sound/firewire/speakers.c @@ -0,0 +1,858 @@ +/* + * OXFW970-based speakers driver + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "cmp.h" +#include "fcp.h" +#include "amdtp.h" +#include "lib.h" + +#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) +/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + +#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 +#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 + +#define VENDOR_GRIFFIN 0x001292 +#define VENDOR_LACIE 0x00d04b + +#define SPECIFIER_1394TA 0x00a02d +#define VERSION_AVC 0x010001 + +struct device_info { + const char *driver_name; + const char *short_name; + const char *long_name; + int (*pcm_constraints)(struct snd_pcm_runtime *runtime); + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +struct fwspk { + struct snd_card *card; + struct fw_unit *unit; + const struct device_info *device_info; + struct snd_pcm_substream *pcm; + struct mutex mutex; + struct cmp_connection connection; + struct amdtp_out_stream stream; + bool stream_running; + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; +}; + +MODULE_DESCRIPTION("FireWire speakers driver"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); + +static int firewave_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static unsigned int stereo_rates[] = { 48000, 96000 }; + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + + /* two channels work only at 48/96 kHz */ + if (snd_interval_max(channels) < 6) + return snd_interval_list(rate, 2, stereo_rates, 0); + return 0; +} + +static int firewave_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + static const struct snd_interval all_channels = { .min = 6, .max = 6 }; + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + /* 32/44.1 kHz work only with all six channels */ + if (snd_interval_max(rate) < 48000) + return snd_interval_refine(channels, &all_channels); + return 0; +} + +static int firewave_constraints(struct snd_pcm_runtime *runtime) +{ + static unsigned int channels_list[] = { 2, 6 }; + static struct snd_pcm_hw_constraint_list channels_list_constraint = { + .count = 2, + .list = channels_list, + }; + int err; + + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000; + runtime->hw.channels_max = 6; + + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &channels_list_constraint); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + firewave_rate_constraint, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + firewave_channels_constraint, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return 0; +} + +static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) +{ + runtime->hw.rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + + return 0; +} + +static int fwspk_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = AMDTP_OUT_PCM_FORMAT_BITS, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = 1, + .period_bytes_max = UINT_MAX, + .periods_min = 1, + .periods_max = UINT_MAX, + }; + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = hardware; + + err = fwspk->device_info->pcm_constraints(runtime); + if (err < 0) + return err; + err = snd_pcm_limit_hw_rates(runtime); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, 8192000); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return 0; +} + +static int fwspk_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void fwspk_stop_stream(struct fwspk *fwspk) +{ + if (fwspk->stream_running) { + amdtp_out_stream_stop(&fwspk->stream); + cmp_connection_break(&fwspk->connection); + fwspk->stream_running = false; + } +} + +static int fwspk_set_rate(struct fwspk *fwspk, unsigned int sfc) +{ + u8 *buf; + int err; + + buf = kmalloc(8, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C, CONTROL */ + buf[1] = 0xff; /* unit */ + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + buf[3] = 0x00; /* plug 0 */ + buf[4] = 0x90; /* format: audio */ + buf[5] = 0x00 | sfc; /* AM824, frequency */ + buf[6] = 0xff; /* SYT (not used) */ + buf[7] = 0xff; + + err = fcp_avc_transaction(fwspk->unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + goto error; + if (err < 6 || buf[0] != 0x09 /* ACCEPTED */) { + dev_err(&fwspk->unit->device, "failed to set sample rate\n"); + err = -EIO; + goto error; + } + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + goto error; + + amdtp_out_stream_set_rate(&fwspk->stream, params_rate(hw_params)); + amdtp_out_stream_set_pcm(&fwspk->stream, params_channels(hw_params)); + + amdtp_out_stream_set_pcm_format(&fwspk->stream, + params_format(hw_params)); + + err = fwspk_set_rate(fwspk, fwspk->stream.sfc); + if (err < 0) + goto err_buffer; + + return 0; + +err_buffer: + snd_pcm_lib_free_vmalloc_buffer(substream); +error: + return err; +} + +static int fwspk_hw_free(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + mutex_lock(&fwspk->mutex); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int fwspk_prepare(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + int err; + + mutex_lock(&fwspk->mutex); + + if (amdtp_out_streaming_error(&fwspk->stream)) + fwspk_stop_stream(fwspk); + + if (!fwspk->stream_running) { + err = cmp_connection_establish(&fwspk->connection, + amdtp_out_stream_get_max_payload(&fwspk->stream)); + if (err < 0) + goto err_mutex; + + err = amdtp_out_stream_start(&fwspk->stream, + fwspk->connection.resources.channel, + fwspk->connection.speed); + if (err < 0) + goto err_connection; + + fwspk->stream_running = true; + } + + mutex_unlock(&fwspk->mutex); + + amdtp_out_stream_pcm_prepare(&fwspk->stream); + + return 0; + +err_connection: + cmp_connection_break(&fwspk->connection); +err_mutex: + mutex_unlock(&fwspk->mutex); + + return err; +} + +static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct fwspk *fwspk = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_out_stream_pcm_trigger(&fwspk->stream, pcm); + return 0; +} + +static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) +{ + struct fwspk *fwspk = substream->private_data; + + return amdtp_out_stream_pcm_pointer(&fwspk->stream); +} + +static int fwspk_create_pcm(struct fwspk *fwspk) +{ + static struct snd_pcm_ops ops = { + .open = fwspk_open, + .close = fwspk_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fwspk_hw_params, + .hw_free = fwspk_hw_free, + .prepare = fwspk_prepare, + .trigger = fwspk_trigger, + .pointer = fwspk_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm); + if (err < 0) + return err; + pcm->private_data = fwspk; + strcpy(pcm->name, fwspk->device_info->short_name); + fwspk->pcm = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + fwspk->pcm->ops = &ops; + return 0; +} + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int fwspk_mute_command(struct fwspk *fwspk, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_volume_command(struct fwspk *fwspk, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&fwspk->unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&fwspk->unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int fwspk_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + + value->value.integer.value[0] = !fwspk->mute; + + return 0; +} + +static int fwspk_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == fwspk->mute) + return 0; + + err = fwspk_mute_command(fwspk, &mute, CTL_WRITE); + if (err < 0) + return err; + fwspk->mute = mute; + + return 1; +} + +static int fwspk_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct fwspk *fwspk = control->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = fwspk->device_info->mixer_channels; + info->value.integer.min = fwspk->volume_min; + info->value.integer.max = fwspk->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int fwspk_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = fwspk->volume[i]; + + return 0; +} + +static int fwspk_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct fwspk *fwspk = control->private_data; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + if (value->value.integer.value[i] < fwspk->volume_min || + value->value.integer.value[i] > fwspk->volume_max) + return -EINVAL; + if (value->value.integer.value[i] != + value->value.integer.value[0]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + fwspk->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) { + volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; + if (changed_channels & (1 << i)) { + err = fwspk_volume_command(fwspk, &volume, i, + CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + fwspk->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +static int fwspk_create_mixer(struct fwspk *fwspk) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = fwspk_mute_get, + .put = fwspk_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = fwspk_volume_info, + .get = fwspk_volume_get, + .put = fwspk_volume_put, + }, + }; + unsigned int i, first_ch; + int err; + + err = fwspk_volume_command(fwspk, &fwspk->volume_min, + 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = fwspk_volume_command(fwspk, &fwspk->volume_max, + 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ); + if (err < 0) + return err; + + first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < fwspk->device_info->mixer_channels; ++i) { + err = fwspk_volume_command(fwspk, &fwspk->volume[i], + first_ch + i, CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(fwspk->card, + snd_ctl_new1(&controls[i], fwspk)); + if (err < 0) + return err; + } + + return 0; +} + +static u32 fwspk_read_firmware_version(struct fw_unit *unit) +{ + __be32 data; + int err; + + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &data, 4); + return err >= 0 ? be32_to_cpu(data) : 0; +} + +static void fwspk_card_free(struct snd_card *card) +{ + struct fwspk *fwspk = card->private_data; + struct fw_device *dev = fw_parent_device(fwspk->unit); + + amdtp_out_stream_destroy(&fwspk->stream); + cmp_connection_destroy(&fwspk->connection); + fw_unit_put(fwspk->unit); + fw_device_put(dev); + mutex_destroy(&fwspk->mutex); +} + +static const struct device_info *__devinit fwspk_detect(struct fw_device *dev) +{ + static const struct device_info griffin_firewave = { + .driver_name = "FireWave", + .short_name = "FireWave", + .long_name = "Griffin FireWave Surround", + .pcm_constraints = firewave_constraints, + .mixer_channels = 6, + .mute_fb_id = 0x01, + .volume_fb_id = 0x02, + }; + static const struct device_info lacie_speakers = { + .driver_name = "FWSpeakers", + .short_name = "FireWire Speakers", + .long_name = "LaCie FireWire Speakers", + .pcm_constraints = lacie_speakers_constraints, + .mixer_channels = 1, + .mute_fb_id = 0x01, + .volume_fb_id = 0x01, + }; + struct fw_csr_iterator i; + int key, value; + + fw_csr_iterator_init(&i, dev->config_rom); + while (fw_csr_iterator_next(&i, &key, &value)) + if (key == CSR_VENDOR) + switch (value) { + case VENDOR_GRIFFIN: + return &griffin_firewave; + case VENDOR_LACIE: + return &lacie_speakers; + } + + return NULL; +} + +static int __devinit fwspk_probe(struct device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct fwspk *fwspk; + u32 firmware; + int err; + + err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + fwspk = card->private_data; + fwspk->card = card; + mutex_init(&fwspk->mutex); + fw_device_get(fw_dev); + fwspk->unit = fw_unit_get(unit); + fwspk->device_info = fwspk_detect(fw_dev); + if (!fwspk->device_info) { + err = -ENODEV; + goto err_unit; + } + + err = cmp_connection_init(&fwspk->connection, unit, 0); + if (err < 0) + goto err_unit; + + err = amdtp_out_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING); + if (err < 0) + goto err_connection; + + card->private_free = fwspk_card_free; + + strcpy(card->driver, fwspk->device_info->driver_name); + strcpy(card->shortname, fwspk->device_info->short_name); + firmware = fwspk_read_firmware_version(unit); + snprintf(card->longname, sizeof(card->longname), + "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + fwspk->device_info->long_name, + firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, "OXFW970"); + + err = fwspk_create_pcm(fwspk); + if (err < 0) + goto error; + + err = fwspk_create_mixer(fwspk); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(unit_dev, fwspk); + + return 0; + +err_connection: + cmp_connection_destroy(&fwspk->connection); +err_unit: + fw_unit_put(fwspk->unit); + fw_device_put(fw_dev); + mutex_destroy(&fwspk->mutex); +error: + snd_card_free(card); + return err; +} + +static int __devexit fwspk_remove(struct device *dev) +{ + struct fwspk *fwspk = dev_get_drvdata(dev); + + snd_card_disconnect(fwspk->card); + + mutex_lock(&fwspk->mutex); + amdtp_out_stream_pcm_abort(&fwspk->stream); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + + snd_card_free_when_closed(fwspk->card); + + return 0; +} + +static void fwspk_bus_reset(struct fw_unit *unit) +{ + struct fwspk *fwspk = dev_get_drvdata(&unit->device); + + fcp_bus_reset(fwspk->unit); + + if (cmp_connection_update(&fwspk->connection) < 0) { + mutex_lock(&fwspk->mutex); + amdtp_out_stream_pcm_abort(&fwspk->stream); + fwspk_stop_stream(fwspk); + mutex_unlock(&fwspk->mutex); + return; + } + + amdtp_out_stream_update(&fwspk->stream); +} + +static const struct ieee1394_device_id fwspk_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_GRIFFIN, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LACIE, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table); + +static struct fw_driver fwspk_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = fwspk_probe, + .remove = __devexit_p(fwspk_remove), + }, + .update = fwspk_bus_reset, + .id_table = fwspk_id_table, +}; + +static int __init alsa_fwspk_init(void) +{ + return driver_register(&fwspk_driver.driver); +} + +static void __exit alsa_fwspk_exit(void) +{ + driver_unregister(&fwspk_driver.driver); +} + +module_init(alsa_fwspk_init); +module_exit(alsa_fwspk_exit); diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c index a07b031090d..067982f4f18 100644 --- a/sound/pci/hda/patch_cirrus.c +++ b/sound/pci/hda/patch_cirrus.c @@ -1039,9 +1039,11 @@ static struct hda_verb cs_errata_init_verbs[] = { {0x11, AC_VERB_SET_PROC_COEF, 0x0008}, {0x11, AC_VERB_SET_PROC_STATE, 0x00}, +#if 0 /* Don't to set to D3 as we are in power-up sequence */ {0x07, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Rx: D3 */ {0x08, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Tx: D3 */ /*{0x01, AC_VERB_SET_POWER_STATE, 0x03},*/ /* AFG: D3 This is already handled */ +#endif {} /* terminator */ }; diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index a3efc52a34d..8224db5f043 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -50,10 +50,12 @@ source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" +source "sound/soc/mid-x86/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" # Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index ce913bf5213..1ed61c5df2c 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += mid-x86/ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += kirkwood/ @@ -17,4 +18,5 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c48b23c1d4f..d63c1754e05 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -26,17 +26,24 @@ config SND_SOC_ALL_CODECS select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI select SND_SOC_CX20442 select SND_SOC_DA7210 if I2C + select SND_SOC_DFBMCS320 select SND_SOC_JZ4740_CODEC if SOC_JZ4740 + select SND_SOC_LM4857 if I2C select SND_SOC_MAX98088 if I2C + select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 + select SND_SOC_SGTL5000 if I2C + select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER + select SND_SOC_TVL320AIC32X4 if I2C select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -76,6 +83,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C + select SND_SOC_WM8991 if I2C select SND_SOC_WM8993 if I2C select SND_SOC_WM8994 if MFD_WM8994 select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI @@ -155,6 +163,9 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_CS4271 + tristate + config SND_SOC_CX20442 tristate @@ -167,15 +178,28 @@ config SND_SOC_L3 config SND_SOC_DA7210 tristate +config SND_SOC_DFBMCS320 + tristate + config SND_SOC_DMIC tristate config SND_SOC_MAX98088 tristate +config SND_SOC_MAX9850 + tristate + config SND_SOC_PCM3008 tristate +#Freescale sgtl5000 codec +config SND_SOC_SGTL5000 + tristate + +config SND_SOC_SN95031 + tristate + config SND_SOC_SPDIF tristate @@ -192,6 +216,9 @@ config SND_SOC_TLV320AIC26 tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE depends on SPI +config SND_SOC_TVL320AIC32X4 + tristate + config SND_SOC_TLV320AIC3X tristate @@ -304,6 +331,9 @@ config SND_SOC_WM8988 config SND_SOC_WM8990 tristate +config SND_SOC_WM8991 + tristate + config SND_SOC_WM8993 tristate @@ -326,6 +356,9 @@ config SND_SOC_WM9713 tristate # Amp +config SND_SOC_LM4857 + tristate + config SND_SOC_MAX9877 tristate @@ -337,4 +370,3 @@ config SND_SOC_WM2000 config SND_SOC_WM9090 tristate - diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 579af9c4f12..379bc55f072 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -12,19 +12,25 @@ snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs4270-objs := cs4270.o +snd-soc-cs4271-objs := cs4271.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o +snd-soc-dfbmcs320-objs := dfbmcs320.o snd-soc-dmic-objs := dmic.o snd-soc-l3-objs := l3.o snd-soc-max98088-objs := max98088.o +snd-soc-max9850-objs := max9850.o snd-soc-pcm3008-objs := pcm3008.o +snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o +snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o @@ -61,6 +67,7 @@ snd-soc-wm8978-objs := wm8978.o snd-soc-wm8985-objs := wm8985.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm8991-objs := wm8991.o snd-soc-wm8993-objs := wm8993.o snd-soc-wm8994-objs := wm8994.o wm8994-tables.o snd-soc-wm8995-objs := wm8995.o @@ -72,6 +79,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-jz4740-codec-objs := jz4740.o # Amp +snd-soc-lm4857-objs := lm4857.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-wm2000-objs := wm2000.o @@ -88,23 +96,29 @@ obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o +obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o +obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o -obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o +obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o +obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o +obj-$(CONFIG_SND_SOC_TVL320AIC32X4) += snd-soc-tlv320aic32x4.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o @@ -141,6 +155,7 @@ obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o @@ -151,6 +166,7 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp +obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index c27f8f59dc6..cbf0b6d400b 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -294,7 +294,6 @@ static struct spi_driver ak4104_spi_driver = { static int __init ak4104_init(void) { - pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n"); return spi_register_driver(&ak4104_spi_driver); } module_init(ak4104_init); diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index f00eba313df..4be0570e3f1 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -116,6 +116,12 @@ #define BCKO_MASK (1 << 3) #define BCKO_64 BCKO_MASK +#define DIF_MASK (3 << 0) +#define DSP (0 << 0) +#define RIGHT_J (1 << 0) +#define LEFT_J (2 << 0) +#define I2S (3 << 0) + /* MD_CTL2 */ #define FS0 (1 << 0) #define FS1 (1 << 1) @@ -354,6 +360,24 @@ static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) snd_soc_update_bits(codec, PW_MGMT2, MS, data); snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko); + /* format type */ + data = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + data = LEFT_J; + break; + case SND_SOC_DAIFMT_I2S: + data = I2S; + break; + /* FIXME + * Please add RIGHT_J / DSP support here + */ + default: + return -EINVAL; + break; + } + snd_soc_update_bits(codec, MD_CTL1, DIF_MASK, data); + return 0; } diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 8b51245f231..0206a17d728 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -193,12 +193,12 @@ static struct cs4270_mode_ratios cs4270_mode_ratios[] = { /* The number of MCLK/LRCK ratios supported by the CS4270 */ #define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) -static int cs4270_reg_is_readable(unsigned int reg) +static int cs4270_reg_is_readable(struct snd_soc_codec *codec, unsigned int reg) { return (reg >= CS4270_FIRSTREG) && (reg <= CS4270_LASTREG); } -static int cs4270_reg_is_volatile(unsigned int reg) +static int cs4270_reg_is_volatile(struct snd_soc_codec *codec, unsigned int reg) { /* Unreadable registers are considered volatile */ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG)) @@ -719,7 +719,7 @@ static int cs4270_i2c_remove(struct i2c_client *i2c_client) /* * cs4270_id - I2C device IDs supported by this driver */ -static struct i2c_device_id cs4270_id[] = { +static const struct i2c_device_id cs4270_id[] = { {"cs4270", 0}, {} }; @@ -743,8 +743,6 @@ static struct i2c_driver cs4270_i2c_driver = { static int __init cs4270_init(void) { - pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n"); - return i2c_add_driver(&cs4270_i2c_driver); } module_init(cs4270_init); diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c new file mode 100644 index 00000000000..083aab96ca8 --- /dev/null +++ b/sound/soc/codecs/cs4271.c @@ -0,0 +1,667 @@ +/* + * CS4271 ASoC codec driver + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <sound/cs4271.h> + +#define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define CS4271_PCM_RATES SNDRV_PCM_RATE_8000_192000 + +/* + * CS4271 registers + * High byte represents SPI chip address (0x10) + write command (0) + * Low byte - codec register address + */ +#define CS4271_MODE1 0x2001 /* Mode Control 1 */ +#define CS4271_DACCTL 0x2002 /* DAC Control */ +#define CS4271_DACVOL 0x2003 /* DAC Volume & Mixing Control */ +#define CS4271_VOLA 0x2004 /* DAC Channel A Volume Control */ +#define CS4271_VOLB 0x2005 /* DAC Channel B Volume Control */ +#define CS4271_ADCCTL 0x2006 /* ADC Control */ +#define CS4271_MODE2 0x2007 /* Mode Control 2 */ +#define CS4271_CHIPID 0x2008 /* Chip ID */ + +#define CS4271_FIRSTREG CS4271_MODE1 +#define CS4271_LASTREG CS4271_MODE2 +#define CS4271_NR_REGS ((CS4271_LASTREG & 0xFF) + 1) + +/* Bit masks for the CS4271 registers */ +#define CS4271_MODE1_MODE_MASK 0xC0 +#define CS4271_MODE1_MODE_1X 0x00 +#define CS4271_MODE1_MODE_2X 0x80 +#define CS4271_MODE1_MODE_4X 0xC0 + +#define CS4271_MODE1_DIV_MASK 0x30 +#define CS4271_MODE1_DIV_1 0x00 +#define CS4271_MODE1_DIV_15 0x10 +#define CS4271_MODE1_DIV_2 0x20 +#define CS4271_MODE1_DIV_3 0x30 + +#define CS4271_MODE1_MASTER 0x08 + +#define CS4271_MODE1_DAC_DIF_MASK 0x07 +#define CS4271_MODE1_DAC_DIF_LJ 0x00 +#define CS4271_MODE1_DAC_DIF_I2S 0x01 +#define CS4271_MODE1_DAC_DIF_RJ16 0x02 +#define CS4271_MODE1_DAC_DIF_RJ24 0x03 +#define CS4271_MODE1_DAC_DIF_RJ20 0x04 +#define CS4271_MODE1_DAC_DIF_RJ18 0x05 + +#define CS4271_DACCTL_AMUTE 0x80 +#define CS4271_DACCTL_IF_SLOW 0x40 + +#define CS4271_DACCTL_DEM_MASK 0x30 +#define CS4271_DACCTL_DEM_DIS 0x00 +#define CS4271_DACCTL_DEM_441 0x10 +#define CS4271_DACCTL_DEM_48 0x20 +#define CS4271_DACCTL_DEM_32 0x30 + +#define CS4271_DACCTL_SVRU 0x08 +#define CS4271_DACCTL_SRD 0x04 +#define CS4271_DACCTL_INVA 0x02 +#define CS4271_DACCTL_INVB 0x01 + +#define CS4271_DACVOL_BEQUA 0x40 +#define CS4271_DACVOL_SOFT 0x20 +#define CS4271_DACVOL_ZEROC 0x10 + +#define CS4271_DACVOL_ATAPI_MASK 0x0F +#define CS4271_DACVOL_ATAPI_M_M 0x00 +#define CS4271_DACVOL_ATAPI_M_BR 0x01 +#define CS4271_DACVOL_ATAPI_M_BL 0x02 +#define CS4271_DACVOL_ATAPI_M_BLR2 0x03 +#define CS4271_DACVOL_ATAPI_AR_M 0x04 +#define CS4271_DACVOL_ATAPI_AR_BR 0x05 +#define CS4271_DACVOL_ATAPI_AR_BL 0x06 +#define CS4271_DACVOL_ATAPI_AR_BLR2 0x07 +#define CS4271_DACVOL_ATAPI_AL_M 0x08 +#define CS4271_DACVOL_ATAPI_AL_BR 0x09 +#define CS4271_DACVOL_ATAPI_AL_BL 0x0A +#define CS4271_DACVOL_ATAPI_AL_BLR2 0x0B +#define CS4271_DACVOL_ATAPI_ALR2_M 0x0C +#define CS4271_DACVOL_ATAPI_ALR2_BR 0x0D +#define CS4271_DACVOL_ATAPI_ALR2_BL 0x0E +#define CS4271_DACVOL_ATAPI_ALR2_BLR2 0x0F + +#define CS4271_VOLA_MUTE 0x80 +#define CS4271_VOLA_VOL_MASK 0x7F +#define CS4271_VOLB_MUTE 0x80 +#define CS4271_VOLB_VOL_MASK 0x7F + +#define CS4271_ADCCTL_DITHER16 0x20 + +#define CS4271_ADCCTL_ADC_DIF_MASK 0x10 +#define CS4271_ADCCTL_ADC_DIF_LJ 0x00 +#define CS4271_ADCCTL_ADC_DIF_I2S 0x10 + +#define CS4271_ADCCTL_MUTEA 0x08 +#define CS4271_ADCCTL_MUTEB 0x04 +#define CS4271_ADCCTL_HPFDA 0x02 +#define CS4271_ADCCTL_HPFDB 0x01 + +#define CS4271_MODE2_LOOP 0x10 +#define CS4271_MODE2_MUTECAEQUB 0x08 +#define CS4271_MODE2_FREEZE 0x04 +#define CS4271_MODE2_CPEN 0x02 +#define CS4271_MODE2_PDN 0x01 + +#define CS4271_CHIPID_PART_MASK 0xF0 +#define CS4271_CHIPID_REV_MASK 0x0F + +/* + * Default CS4271 power-up configuration + * Array contains non-existing in hw register at address 0 + * Array do not include Chip ID, as codec driver does not use + * registers read operations at all + */ +static const u8 cs4271_dflt_reg[CS4271_NR_REGS] = { + 0, + 0, + CS4271_DACCTL_AMUTE, + CS4271_DACVOL_SOFT | CS4271_DACVOL_ATAPI_AL_BR, + 0, + 0, + 0, + 0, +}; + +struct cs4271_private { + /* SND_SOC_I2C or SND_SOC_SPI */ + enum snd_soc_control_type bus_type; + void *control_data; + unsigned int mclk; + bool master; + bool deemph; + /* Current sample rate for de-emphasis control */ + int rate; + /* GPIO driving Reset pin, if any */ + int gpio_nreset; + /* GPIO that disable serial bus, if any */ + int gpio_disable; +}; + +/* + * @freq is the desired MCLK rate + * MCLK rate should (c) be the sample rate, multiplied by one of the + * ratios listed in cs4271_mclk_fs_ratios table + */ +static int cs4271_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 cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + + cs4271->mclk = freq; + return 0; +} + +static int cs4271_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + int ret; + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs4271->master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs4271->master = 1; + val |= CS4271_MODE1_MASTER; + break; + default: + dev_err(codec->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val |= CS4271_MODE1_DAC_DIF_LJ; + ret = snd_soc_update_bits(codec, CS4271_ADCCTL, + CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_LJ); + if (ret < 0) + return ret; + break; + case SND_SOC_DAIFMT_I2S: + val |= CS4271_MODE1_DAC_DIF_I2S; + ret = snd_soc_update_bits(codec, CS4271_ADCCTL, + CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_I2S); + if (ret < 0) + return ret; + break; + default: + dev_err(codec->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + ret = snd_soc_update_bits(codec, CS4271_MODE1, + CS4271_MODE1_DAC_DIF_MASK | CS4271_MODE1_MASTER, val); + if (ret < 0) + return ret; + return 0; +} + +static int cs4271_deemph[] = {0, 44100, 48000, 32000}; + +static int cs4271_set_deemph(struct snd_soc_codec *codec) +{ + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + int i, ret; + int val = CS4271_DACCTL_DEM_DIS; + + if (cs4271->deemph) { + /* Find closest de-emphasis freq */ + val = 1; + for (i = 2; i < ARRAY_SIZE(cs4271_deemph); i++) + if (abs(cs4271_deemph[i] - cs4271->rate) < + abs(cs4271_deemph[val] - cs4271->rate)) + val = i; + val <<= 4; + } + + ret = snd_soc_update_bits(codec, CS4271_DACCTL, + CS4271_DACCTL_DEM_MASK, val); + if (ret < 0) + return ret; + return 0; +} + +static int cs4271_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = cs4271->deemph; + return 0; +} + +static int cs4271_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + + cs4271->deemph = ucontrol->value.enumerated.item[0]; + return cs4271_set_deemph(codec); +} + +struct cs4271_clk_cfg { + bool master; /* codec mode */ + u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */ + unsigned short ratio; /* MCLK / sample rate */ + u8 ratio_mask; /* ratio bit mask for Master mode */ +}; + +static struct cs4271_clk_cfg cs4271_clk_tab[] = { + {1, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_3}, + {0, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_1X, 1024, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 512, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 256, CS4271_MODE1_DIV_2}, +}; + +#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab) + +static int cs4271_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_codec *codec = rtd->codec; + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + int i, ret; + unsigned int ratio, val; + + cs4271->rate = params_rate(params); + + /* Configure DAC */ + if (cs4271->rate < 50000) + val = CS4271_MODE1_MODE_1X; + else if (cs4271->rate < 100000) + val = CS4271_MODE1_MODE_2X; + else + val = CS4271_MODE1_MODE_4X; + + ratio = cs4271->mclk / cs4271->rate; + for (i = 0; i < CS4171_NR_RATIOS; i++) + if ((cs4271_clk_tab[i].master == cs4271->master) && + (cs4271_clk_tab[i].speed_mode == val) && + (cs4271_clk_tab[i].ratio == ratio)) + break; + + if (i == CS4171_NR_RATIOS) { + dev_err(codec->dev, "Invalid sample rate\n"); + return -EINVAL; + } + + val |= cs4271_clk_tab[i].ratio_mask; + + ret = snd_soc_update_bits(codec, CS4271_MODE1, + CS4271_MODE1_MODE_MASK | CS4271_MODE1_DIV_MASK, val); + if (ret < 0) + return ret; + + return cs4271_set_deemph(codec); +} + +static int cs4271_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int ret; + int val_a = 0; + int val_b = 0; + + if (mute) { + val_a = CS4271_VOLA_MUTE; + val_b = CS4271_VOLB_MUTE; + } + + ret = snd_soc_update_bits(codec, CS4271_VOLA, CS4271_VOLA_MUTE, val_a); + if (ret < 0) + return ret; + ret = snd_soc_update_bits(codec, CS4271_VOLB, CS4271_VOLB_MUTE, val_b); + if (ret < 0) + return ret; + + return 0; +} + +/* CS4271 controls */ +static DECLARE_TLV_DB_SCALE(cs4271_dac_tlv, -12700, 100, 0); + +static const struct snd_kcontrol_new cs4271_snd_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", CS4271_VOLA, CS4271_VOLB, + 0, 0x7F, 1, cs4271_dac_tlv), + SOC_SINGLE("Digital Loopback Switch", CS4271_MODE2, 4, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS4271_DACVOL, 5, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS4271_DACVOL, 4, 1, 0), + SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0, + cs4271_get_deemph, cs4271_put_deemph), + SOC_SINGLE("Auto-Mute Switch", CS4271_DACCTL, 7, 1, 0), + SOC_SINGLE("Slow Roll Off Filter Switch", CS4271_DACCTL, 6, 1, 0), + SOC_SINGLE("Soft Volume Ramp-Up Switch", CS4271_DACCTL, 3, 1, 0), + SOC_SINGLE("Soft Ramp-Down Switch", CS4271_DACCTL, 2, 1, 0), + SOC_SINGLE("Left Channel Inversion Switch", CS4271_DACCTL, 1, 1, 0), + SOC_SINGLE("Right Channel Inversion Switch", CS4271_DACCTL, 0, 1, 0), + SOC_DOUBLE("Master Capture Switch", CS4271_ADCCTL, 3, 2, 1, 1), + SOC_SINGLE("Dither 16-Bit Data Switch", CS4271_ADCCTL, 5, 1, 0), + SOC_DOUBLE("High Pass Filter Switch", CS4271_ADCCTL, 1, 0, 1, 1), + SOC_DOUBLE_R("Master Playback Switch", CS4271_VOLA, CS4271_VOLB, + 7, 1, 1), +}; + +static struct snd_soc_dai_ops cs4271_dai_ops = { + .hw_params = cs4271_hw_params, + .set_sysclk = cs4271_set_dai_sysclk, + .set_fmt = cs4271_set_dai_fmt, + .digital_mute = cs4271_digital_mute, +}; + +static struct snd_soc_dai_driver cs4271_dai = { + .name = "cs4271-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = CS4271_PCM_RATES, + .formats = CS4271_PCM_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS4271_PCM_RATES, + .formats = CS4271_PCM_FORMATS, + }, + .ops = &cs4271_dai_ops, + .symmetric_rates = 1, +}; + +#ifdef CONFIG_PM +static int cs4271_soc_suspend(struct snd_soc_codec *codec, pm_message_t mesg) +{ + int ret; + /* Set power-down bit */ + ret = snd_soc_update_bits(codec, CS4271_MODE2, 0, CS4271_MODE2_PDN); + if (ret < 0) + return ret; + return 0; +} + +static int cs4271_soc_resume(struct snd_soc_codec *codec) +{ + int ret; + /* Restore codec state */ + ret = snd_soc_cache_sync(codec); + if (ret < 0) + return ret; + /* then disable the power-down bit */ + ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + return 0; +} +#else +#define cs4271_soc_suspend NULL +#define cs4271_soc_resume NULL +#endif /* CONFIG_PM */ + +static int cs4271_probe(struct snd_soc_codec *codec) +{ + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + struct cs4271_platform_data *cs4271plat = codec->dev->platform_data; + int ret; + int gpio_nreset = -EINVAL; + + codec->control_data = cs4271->control_data; + + if (cs4271plat && gpio_is_valid(cs4271plat->gpio_nreset)) + gpio_nreset = cs4271plat->gpio_nreset; + + if (gpio_nreset >= 0) + if (gpio_request(gpio_nreset, "CS4271 Reset")) + gpio_nreset = -EINVAL; + if (gpio_nreset >= 0) { + /* Reset codec */ + gpio_direction_output(gpio_nreset, 0); + udelay(1); + gpio_set_value(gpio_nreset, 1); + /* Give the codec time to wake up */ + udelay(1); + } + + cs4271->gpio_nreset = gpio_nreset; + + /* + * In case of I2C, chip address specified in board data. + * So cache IO operations use 8 bit codec register address. + * In case of SPI, chip address and register address + * passed together as 16 bit value. + * Anyway, register address is masked with 0xFF inside + * soc-cache code. + */ + if (cs4271->bus_type == SND_SOC_SPI) + ret = snd_soc_codec_set_cache_io(codec, 16, 8, + cs4271->bus_type); + else + ret = snd_soc_codec_set_cache_io(codec, 8, 8, + cs4271->bus_type); + if (ret) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = snd_soc_update_bits(codec, CS4271_MODE2, 0, + CS4271_MODE2_PDN | CS4271_MODE2_CPEN); + if (ret < 0) + return ret; + ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + /* Power-up sequence requires 85 uS */ + udelay(85); + + return snd_soc_add_controls(codec, cs4271_snd_controls, + ARRAY_SIZE(cs4271_snd_controls)); +} + +static int cs4271_remove(struct snd_soc_codec *codec) +{ + struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); + int gpio_nreset; + + gpio_nreset = cs4271->gpio_nreset; + + if (gpio_is_valid(gpio_nreset)) { + /* Set codec to the reset state */ + gpio_set_value(gpio_nreset, 0); + gpio_free(gpio_nreset); + } + + return 0; +}; + +static struct snd_soc_codec_driver soc_codec_dev_cs4271 = { + .probe = cs4271_probe, + .remove = cs4271_remove, + .suspend = cs4271_soc_suspend, + .resume = cs4271_soc_resume, + .reg_cache_default = cs4271_dflt_reg, + .reg_cache_size = ARRAY_SIZE(cs4271_dflt_reg), + .reg_word_size = sizeof(cs4271_dflt_reg[0]), + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +#if defined(CONFIG_SPI_MASTER) +static int __devinit cs4271_spi_probe(struct spi_device *spi) +{ + struct cs4271_private *cs4271; + + cs4271 = devm_kzalloc(&spi->dev, sizeof(*cs4271), GFP_KERNEL); + if (!cs4271) + return -ENOMEM; + + spi_set_drvdata(spi, cs4271); + cs4271->control_data = spi; + cs4271->bus_type = SND_SOC_SPI; + + return snd_soc_register_codec(&spi->dev, &soc_codec_dev_cs4271, + &cs4271_dai, 1); +} + +static int __devexit cs4271_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static struct spi_driver cs4271_spi_driver = { + .driver = { + .name = "cs4271", + .owner = THIS_MODULE, + }, + .probe = cs4271_spi_probe, + .remove = __devexit_p(cs4271_spi_remove), +}; +#endif /* defined(CONFIG_SPI_MASTER) */ + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id cs4271_i2c_id[] = { + {"cs4271", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id); + +static int __devinit cs4271_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs4271_private *cs4271; + + cs4271 = devm_kzalloc(&client->dev, sizeof(*cs4271), GFP_KERNEL); + if (!cs4271) + return -ENOMEM; + + i2c_set_clientdata(client, cs4271); + cs4271->control_data = client; + cs4271->bus_type = SND_SOC_I2C; + + return snd_soc_register_codec(&client->dev, &soc_codec_dev_cs4271, + &cs4271_dai, 1); +} + +static int __devexit cs4271_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static struct i2c_driver cs4271_i2c_driver = { + .driver = { + .name = "cs4271", + .owner = THIS_MODULE, + }, + .id_table = cs4271_i2c_id, + .probe = cs4271_i2c_probe, + .remove = __devexit_p(cs4271_i2c_remove), +}; +#endif /* defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) */ + +/* + * We only register our serial bus driver here without + * assignment to particular chip. So if any of the below + * fails, there is some problem with I2C or SPI subsystem. + * In most cases this module will be compiled with support + * of only one serial bus. + */ +static int __init cs4271_modinit(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&cs4271_i2c_driver); + if (ret) { + pr_err("Failed to register CS4271 I2C driver: %d\n", ret); + return ret; + } +#endif + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&cs4271_spi_driver); + if (ret) { + pr_err("Failed to register CS4271 SPI driver: %d\n", ret); + return ret; + } +#endif + + return 0; +} +module_init(cs4271_modinit); + +static void __exit cs4271_modexit(void) +{ +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&cs4271_spi_driver); +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&cs4271_i2c_driver); +#endif +} +module_exit(cs4271_modexit); + +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/dfbmcs320.c b/sound/soc/codecs/dfbmcs320.c new file mode 100644 index 00000000000..704bbde6573 --- /dev/null +++ b/sound/soc/codecs/dfbmcs320.c @@ -0,0 +1,72 @@ +/* + * Driver for the DFBM-CS320 bluetooth module + * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> + +static struct snd_soc_dai_driver dfbmcs320_dai = { + .name = "dfbmcs320-pcm", + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_dfbmcs320; + +static int __devinit dfbmcs320_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_dfbmcs320, + &dfbmcs320_dai, 1); +} + +static int __devexit dfbmcs320_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver dfmcs320_driver = { + .driver = { + .name = "dfbmcs320", + .owner = THIS_MODULE, + }, + .probe = dfbmcs320_probe, + .remove = __devexit_p(dfbmcs320_remove), +}; + +static int __init dfbmcs320_init(void) +{ + return platform_driver_register(&dfmcs320_driver); +} +module_init(dfbmcs320_init); + +static void __exit dfbmcs320_exit(void) +{ + platform_driver_unregister(&dfmcs320_driver); +} +module_exit(dfbmcs320_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ASoC DFBM-CS320 bluethooth module driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/lm4857.c b/sound/soc/codecs/lm4857.c new file mode 100644 index 00000000000..72de47e5d04 --- /dev/null +++ b/sound/soc/codecs/lm4857.c @@ -0,0 +1,276 @@ +/* + * LM4857 AMP driver + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +struct lm4857 { + struct i2c_client *i2c; + uint8_t mode; +}; + +static const uint8_t lm4857_default_regs[] = { + 0x00, 0x00, 0x00, 0x00, +}; + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +static int lm4857_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + uint8_t data; + int ret; + + ret = snd_soc_cache_write(codec, reg, value); + if (ret < 0) + return ret; + + data = (reg << 6) | value; + ret = i2c_master_send(codec->control_data, &data, 1); + if (ret != 1) { + dev_err(codec->dev, "Failed to write register: %d\n", ret); + return ret; + } + + return 0; +} + +static unsigned int lm4857_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + ret = snd_soc_cache_read(codec, reg, &val); + if (ret) + return -1; + + return val; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = lm4857->mode; + + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + uint8_t value = ucontrol->value.integer.value[0]; + + lm4857->mode = value; + + if (codec->dapm.bias_level == SND_SOC_BIAS_ON) + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, value + 6); + + return 1; +} + +static int lm4857_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, lm4857->mode + 6); + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, 0); + break; + default: + break; + } + + codec->dapm.bias_level = level; + + return 0; +} + +static const char *lm4857_mode[] = { + "Earpiece", + "Loudspeaker", + "Loudspeaker + Headphone", + "Headphone", +}; + +static const struct soc_enum lm4857_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode); + +static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN"), + + SND_SOC_DAPM_OUTPUT("LS"), + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("EP"), +}; + +static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new lm4857_controls[] = { + SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + mono_tlv), + SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0), + SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0), + SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL, + LM4857_WAKEUP, 1, 0), + SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL, + LM4857_EPGAIN, 1, 0), + + SOC_ENUM_EXT("Mode", lm4857_mode_enum, + lm4857_get_mode, lm4857_set_mode), +}; + +/* There is a demux inbetween the the input signal and the output signals. + * Currently there is no easy way to model it in ASoC and since it does not make + * much of a difference in practice simply connect the input direclty to the + * outputs. */ +static const struct snd_soc_dapm_route lm4857_routes[] = { + {"LS", NULL, "IN"}, + {"HP", NULL, "IN"}, + {"EP", NULL, "IN"}, +}; + +static int lm4857_probe(struct snd_soc_codec *codec) +{ + struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + codec->control_data = lm4857->i2c; + + ret = snd_soc_add_controls(codec, lm4857_controls, + ARRAY_SIZE(lm4857_controls)); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, lm4857_dapm_widgets, + ARRAY_SIZE(lm4857_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, lm4857_routes, + ARRAY_SIZE(lm4857_routes)); + if (ret) + return ret; + + snd_soc_dapm_new_widgets(dapm); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_lm4857 = { + .write = lm4857_write, + .read = lm4857_read, + .probe = lm4857_probe, + .reg_cache_size = ARRAY_SIZE(lm4857_default_regs), + .reg_word_size = sizeof(uint8_t), + .reg_cache_default = lm4857_default_regs, + .set_bias_level = lm4857_set_bias_level, +}; + +static int __devinit lm4857_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lm4857 *lm4857; + int ret; + + lm4857 = kzalloc(sizeof(*lm4857), GFP_KERNEL); + if (!lm4857) + return -ENOMEM; + + i2c_set_clientdata(i2c, lm4857); + + lm4857->i2c = i2c; + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0); + + if (ret) { + kfree(lm4857); + return ret; + } + + return 0; +} + +static int __devexit lm4857_i2c_remove(struct i2c_client *i2c) +{ + struct lm4857 *lm4857 = i2c_get_clientdata(i2c); + + snd_soc_unregister_codec(&i2c->dev); + kfree(lm4857); + + return 0; +} + +static const struct i2c_device_id lm4857_i2c_id[] = { + { "lm4857", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id); + +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "lm4857", + .owner = THIS_MODULE, + }, + .probe = lm4857_i2c_probe, + .remove = __devexit_p(lm4857_i2c_remove), + .id_table = lm4857_i2c_id, +}; + +static int __init lm4857_init(void) +{ + return i2c_add_driver(&lm4857_i2c_driver); +} +module_init(lm4857_init); + +static void __exit lm4857_exit(void) +{ + i2c_del_driver(&lm4857_i2c_driver); +} +module_exit(lm4857_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("LM4857 amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index 89498f9ad2e..bd0517cb798 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -608,7 +608,7 @@ static struct { { 0xFF, 0x00, 1 }, /* FF */ }; -static int max98088_volatile_register(unsigned int reg) +static int max98088_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { return max98088_access[reg].vol; } diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c new file mode 100644 index 00000000000..208d2ee6185 --- /dev/null +++ b/sound/soc/codecs/max9850.c @@ -0,0 +1,389 @@ +/* + * max9850.c -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * + * Author: Christian Glindkamp <christian.glindkamp@taskit.de> + * + * Initial development of this code was funded by + * MICRONIC Computer Systeme GmbH, http://www.mcsberlin.de/ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "max9850.h" + +struct max9850_priv { + unsigned int sysclk; +}; + +/* max9850 register cache */ +static const u8 max9850_reg[MAX9850_CACHEREGNUM] = { + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* these registers are not used at the moment but provided for the sake of + * completeness */ +static int max9850_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case MAX9850_STATUSA: + case MAX9850_STATUSB: + return 1; + default: + return 0; + } +} + +static const unsigned int max9850_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0x18, 0x1f, TLV_DB_SCALE_ITEM(-7450, 400, 0), + 0x20, 0x33, TLV_DB_SCALE_ITEM(-4150, 200, 0), + 0x34, 0x37, TLV_DB_SCALE_ITEM(-150, 100, 0), + 0x38, 0x3f, TLV_DB_SCALE_ITEM(250, 50, 0), +}; + +static const struct snd_kcontrol_new max9850_controls[] = { +SOC_SINGLE_TLV("Headphone Volume", MAX9850_VOLUME, 0, 0x3f, 1, max9850_tlv), +SOC_SINGLE("Headphone Switch", MAX9850_VOLUME, 7, 1, 1), +SOC_SINGLE("Mono Switch", MAX9850_GENERAL_PURPOSE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new max9850_mixer_controls[] = { + SOC_DAPM_SINGLE("Line In Switch", MAX9850_ENABLE, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget max9850_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("Charge Pump 1", MAX9850_ENABLE, 4, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump 2", MAX9850_ENABLE, 5, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MCLK", MAX9850_ENABLE, 6, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("SHDN", MAX9850_ENABLE, 7, 0, NULL, 0), +SND_SOC_DAPM_MIXER_NAMED_CTL("Output Mixer", MAX9850_ENABLE, 2, 0, + &max9850_mixer_controls[0], + ARRAY_SIZE(max9850_mixer_controls)), +SND_SOC_DAPM_PGA("Headphone Output", MAX9850_ENABLE, 3, 0, NULL, 0), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", MAX9850_ENABLE, 0, 0), +SND_SOC_DAPM_OUTPUT("OUTL"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("OUTR"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_INPUT("INL"), +SND_SOC_DAPM_INPUT("INR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* output mixer */ + {"Output Mixer", NULL, "DAC"}, + {"Output Mixer", "Line In Switch", "Line Input"}, + + /* outputs */ + {"Headphone Output", NULL, "Output Mixer"}, + {"HPL", NULL, "Headphone Output"}, + {"HPR", NULL, "Headphone Output"}, + {"OUTL", NULL, "Output Mixer"}, + {"OUTR", NULL, "Output Mixer"}, + + /* inputs */ + {"Line Input", NULL, "INL"}, + {"Line Input", NULL, "INR"}, + + /* supplies */ + {"Output Mixer", NULL, "Charge Pump 1"}, + {"Output Mixer", NULL, "Charge Pump 2"}, + {"Output Mixer", NULL, "SHDN"}, + {"DAC", NULL, "MCLK"}, +}; + +static int max9850_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec); + u64 lrclk_div; + u8 sf, da; + + if (!max9850->sysclk) + return -EINVAL; + + /* lrclk_div = 2^22 * rate / iclk with iclk = mclk / sf */ + sf = (snd_soc_read(codec, MAX9850_CLOCK) >> 2) + 1; + lrclk_div = (1 << 22); + lrclk_div *= params_rate(params); + lrclk_div *= sf; + do_div(lrclk_div, max9850->sysclk); + + snd_soc_write(codec, MAX9850_LRCLK_MSB, (lrclk_div >> 8) & 0x7f); + snd_soc_write(codec, MAX9850_LRCLK_LSB, lrclk_div & 0xff); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + da = 0; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + da = 0x2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + da = 0x3; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, MAX9850_DIGITAL_AUDIO, 0x3, da); + + return 0; +} + +static int max9850_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 max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec); + + /* calculate mclk -> iclk divider */ + if (freq <= 13000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x0); + else if (freq <= 26000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x4); + else if (freq <= 40000000) + snd_soc_write(codec, MAX9850_CLOCK, 0x8); + else + return -EINVAL; + + max9850->sysclk = freq; + return 0; +} + +static int max9850_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 da = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da |= MAX9850_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + da |= MAX9850_DLY; + break; + case SND_SOC_DAIFMT_RIGHT_J: + da |= MAX9850_RTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + da |= MAX9850_BCINV | MAX9850_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + da |= MAX9850_BCINV; + break; + case SND_SOC_DAIFMT_NB_IF: + da |= MAX9850_INV; + break; + default: + return -EINVAL; + } + + /* set da */ + snd_soc_write(codec, MAX9850_DIGITAL_AUDIO, da); + + return 0; +} + +static int max9850_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = snd_soc_cache_sync(codec); + if (ret) { + dev_err(codec->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + } + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define MAX9850_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops max9850_dai_ops = { + .hw_params = max9850_hw_params, + .set_sysclk = max9850_set_dai_sysclk, + .set_fmt = max9850_set_dai_fmt, +}; + +static struct snd_soc_dai_driver max9850_dai = { + .name = "max9850-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX9850_RATES, + .formats = MAX9850_FORMATS + }, + .ops = &max9850_dai_ops, +}; + +#ifdef CONFIG_PM +static int max9850_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + max9850_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int max9850_resume(struct snd_soc_codec *codec) +{ + max9850_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define max9850_suspend NULL +#define max9850_resume NULL +#endif + +static int max9850_probe(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + /* enable zero-detect */ + snd_soc_update_bits(codec, MAX9850_GENERAL_PURPOSE, 1, 1); + /* enable slew-rate control */ + snd_soc_update_bits(codec, MAX9850_VOLUME, 0x40, 0x40); + /* set slew-rate 125ms */ + snd_soc_update_bits(codec, MAX9850_CHARGE_PUMP, 0xff, 0xc0); + + snd_soc_dapm_new_controls(dapm, max9850_dapm_widgets, + ARRAY_SIZE(max9850_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); + + snd_soc_add_controls(codec, max9850_controls, + ARRAY_SIZE(max9850_controls)); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_max9850 = { + .probe = max9850_probe, + .suspend = max9850_suspend, + .resume = max9850_resume, + .set_bias_level = max9850_set_bias_level, + .reg_cache_size = ARRAY_SIZE(max9850_reg), + .reg_word_size = sizeof(u8), + .reg_cache_default = max9850_reg, + .volatile_register = max9850_volatile_register, +}; + +static int __devinit max9850_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max9850_priv *max9850; + int ret; + + max9850 = kzalloc(sizeof(struct max9850_priv), GFP_KERNEL); + if (max9850 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max9850); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_max9850, &max9850_dai, 1); + if (ret < 0) + kfree(max9850); + return ret; +} + +static __devexit int max9850_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id max9850_i2c_id[] = { + { "max9850", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9850_i2c_id); + +static struct i2c_driver max9850_i2c_driver = { + .driver = { + .name = "max9850", + .owner = THIS_MODULE, + }, + .probe = max9850_i2c_probe, + .remove = __devexit_p(max9850_i2c_remove), + .id_table = max9850_i2c_id, +}; + +static int __init max9850_init(void) +{ + return i2c_add_driver(&max9850_i2c_driver); +} +module_init(max9850_init); + +static void __exit max9850_exit(void) +{ + i2c_del_driver(&max9850_i2c_driver); +} +module_exit(max9850_exit); + +MODULE_AUTHOR("Christian Glindkamp <christian.glindkamp@taskit.de>"); +MODULE_DESCRIPTION("ASoC MAX9850 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9850.h b/sound/soc/codecs/max9850.h new file mode 100644 index 00000000000..72b1ddb04b0 --- /dev/null +++ b/sound/soc/codecs/max9850.h @@ -0,0 +1,38 @@ +/* + * max9850.h -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * Author: Christian Glindkamp <christian.glindkamp@taskit.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _MAX9850_H +#define _MAX9850_H + +#define MAX9850_STATUSA 0x00 +#define MAX9850_STATUSB 0x01 +#define MAX9850_VOLUME 0x02 +#define MAX9850_GENERAL_PURPOSE 0x03 +#define MAX9850_INTERRUPT 0x04 +#define MAX9850_ENABLE 0x05 +#define MAX9850_CLOCK 0x06 +#define MAX9850_CHARGE_PUMP 0x07 +#define MAX9850_LRCLK_MSB 0x08 +#define MAX9850_LRCLK_LSB 0x09 +#define MAX9850_DIGITAL_AUDIO 0x0a + +#define MAX9850_CACHEREGNUM 11 + +/* MAX9850_DIGITAL_AUDIO */ +#define MAX9850_MASTER (1<<7) +#define MAX9850_INV (1<<6) +#define MAX9850_BCINV (1<<5) +#define MAX9850_DLY (1<<3) +#define MAX9850_RTJ (1<<2) + +#endif diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 00000000000..1f7217f703e --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1513 @@ +/* + * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver + * + * Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/tlv.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "sgtl5000.h" + +#define SGTL5000_DAP_REG_OFFSET 0x0100 +#define SGTL5000_MAX_REG_OFFSET 0x013A + +/* default value of sgtl5000 registers except DAP */ +static const u16 sgtl5000_regs[SGTL5000_MAX_REG_OFFSET >> 1] = { + 0xa011, /* 0x0000, CHIP_ID. 11 stand for revison 17 */ + 0x0000, /* 0x0002, CHIP_DIG_POWER. */ + 0x0008, /* 0x0004, CHIP_CKL_CTRL */ + 0x0010, /* 0x0006, CHIP_I2S_CTRL */ + 0x0000, /* 0x0008, reserved */ + 0x0008, /* 0x000A, CHIP_SSS_CTRL */ + 0x0000, /* 0x000C, reserved */ + 0x020c, /* 0x000E, CHIP_ADCDAC_CTRL */ + 0x3c3c, /* 0x0010, CHIP_DAC_VOL */ + 0x0000, /* 0x0012, reserved */ + 0x015f, /* 0x0014, CHIP_PAD_STRENGTH */ + 0x0000, /* 0x0016, reserved */ + 0x0000, /* 0x0018, reserved */ + 0x0000, /* 0x001A, reserved */ + 0x0000, /* 0x001E, reserved */ + 0x0000, /* 0x0020, CHIP_ANA_ADC_CTRL */ + 0x1818, /* 0x0022, CHIP_ANA_HP_CTRL */ + 0x0111, /* 0x0024, CHIP_ANN_CTRL */ + 0x0000, /* 0x0026, CHIP_LINREG_CTRL */ + 0x0000, /* 0x0028, CHIP_REF_CTRL */ + 0x0000, /* 0x002A, CHIP_MIC_CTRL */ + 0x0000, /* 0x002C, CHIP_LINE_OUT_CTRL */ + 0x0404, /* 0x002E, CHIP_LINE_OUT_VOL */ + 0x7060, /* 0x0030, CHIP_ANA_POWER */ + 0x5000, /* 0x0032, CHIP_PLL_CTRL */ + 0x0000, /* 0x0034, CHIP_CLK_TOP_CTRL */ + 0x0000, /* 0x0036, CHIP_ANA_STATUS */ + 0x0000, /* 0x0038, reserved */ + 0x0000, /* 0x003A, CHIP_ANA_TEST2 */ + 0x0000, /* 0x003C, CHIP_SHORT_CTRL */ + 0x0000, /* reserved */ +}; + +/* default value of dap registers */ +static const u16 sgtl5000_dap_regs[] = { + 0x0000, /* 0x0100, DAP_CONTROL */ + 0x0000, /* 0x0102, DAP_PEQ */ + 0x0040, /* 0x0104, DAP_BASS_ENHANCE */ + 0x051f, /* 0x0106, DAP_BASS_ENHANCE_CTRL */ + 0x0000, /* 0x0108, DAP_AUDIO_EQ */ + 0x0040, /* 0x010A, DAP_SGTL_SURROUND */ + 0x0000, /* 0x010C, DAP_FILTER_COEF_ACCESS */ + 0x0000, /* 0x010E, DAP_COEF_WR_B0_MSB */ + 0x0000, /* 0x0110, DAP_COEF_WR_B0_LSB */ + 0x0000, /* 0x0112, reserved */ + 0x0000, /* 0x0114, reserved */ + 0x002f, /* 0x0116, DAP_AUDIO_EQ_BASS_BAND0 */ + 0x002f, /* 0x0118, DAP_AUDIO_EQ_BAND0 */ + 0x002f, /* 0x011A, DAP_AUDIO_EQ_BAND2 */ + 0x002f, /* 0x011C, DAP_AUDIO_EQ_BAND3 */ + 0x002f, /* 0x011E, DAP_AUDIO_EQ_TREBLE_BAND4 */ + 0x8000, /* 0x0120, DAP_MAIN_CHAN */ + 0x0000, /* 0x0122, DAP_MIX_CHAN */ + 0x0510, /* 0x0124, DAP_AVC_CTRL */ + 0x1473, /* 0x0126, DAP_AVC_THRESHOLD */ + 0x0028, /* 0x0128, DAP_AVC_ATTACK */ + 0x0050, /* 0x012A, DAP_AVC_DECAY */ + 0x0000, /* 0x012C, DAP_COEF_WR_B1_MSB */ + 0x0000, /* 0x012E, DAP_COEF_WR_B1_LSB */ + 0x0000, /* 0x0130, DAP_COEF_WR_B2_MSB */ + 0x0000, /* 0x0132, DAP_COEF_WR_B2_LSB */ + 0x0000, /* 0x0134, DAP_COEF_WR_A1_MSB */ + 0x0000, /* 0x0136, DAP_COEF_WR_A1_LSB */ + 0x0000, /* 0x0138, DAP_COEF_WR_A2_MSB */ + 0x0000, /* 0x013A, DAP_COEF_WR_A2_LSB */ +}; + +/* regulator supplies for sgtl5000, VDDD is an optional external supply */ +enum sgtl5000_regulator_supplies { + VDDA, + VDDIO, + VDDD, + SGTL5000_SUPPLY_NUM +}; + +/* vddd is optional supply */ +static const char *supply_names[SGTL5000_SUPPLY_NUM] = { + "VDDA", + "VDDIO", + "VDDD" +}; + +#define LDO_CONSUMER_NAME "VDDD_LDO" +#define LDO_VOLTAGE 1200000 + +static struct regulator_consumer_supply ldo_consumer[] = { + REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL), +}; + +static struct regulator_init_data ldo_init_data = { + .constraints = { + .min_uV = 850000, + .max_uV = 1600000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 1, + .consumer_supplies = &ldo_consumer[0], +}; + +/* + * sgtl5000 internal ldo regulator, + * enabled when VDDD not provided + */ +struct ldo_regulator { + struct regulator_desc desc; + struct regulator_dev *dev; + int voltage; + void *codec_data; + bool enabled; +}; + +/* sgtl5000 private structure in codec */ +struct sgtl5000_priv { + int sysclk; /* sysclk rate */ + int master; /* i2s master or not */ + int fmt; /* i2s data format */ + struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM]; + struct ldo_regulator *ldo; +}; + +/* + * mic_bias power on/off share the same register bits with + * output impedance of mic bias, when power on mic bias, we + * need reclaim it to impedance value. + * 0x0 = Powered off + * 0x1 = 2Kohm + * 0x2 = 4Kohm + * 0x3 = 8Kohm + */ +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias resistor to 4Kohm */ + snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_4k, SGTL5000_BIAS_R_4k); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* + * SGTL5000_BIAS_R_8k as mask to clean the two bits + * of mic bias and output impedance + */ + snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_8k, 0); + break; + } + return 0; +} + +/* + * using codec assist to small pop, hp_powerup or lineout_powerup + * should stay setting until vag_powerup is fully ramped down, + * vag fully ramped down require 400ms. + */ +static int small_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, 0); + msleep(400); + break; + default: + break; + } + + return 0; +} + +/* input sources for ADC */ +static const char *adc_mux_text[] = { + "MIC_IN", "LINE_IN" +}; + +static const struct soc_enum adc_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text); + +static const struct snd_kcontrol_new adc_mux = +SOC_DAPM_ENUM("Capture Mux", adc_enum); + +/* input sources for DAC */ +static const char *dac_mux_text[] = { + "DAC", "LINE_IN" +}; + +static const struct soc_enum dac_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text); + +static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("Headphone Mux", dac_enum); + +static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LINE_IN"), + SND_SOC_DAPM_INPUT("MIC_IN"), + + SND_SOC_DAPM_OUTPUT("HP_OUT"), + SND_SOC_DAPM_OUTPUT("LINE_OUT"), + + SND_SOC_DAPM_MICBIAS_E("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0, + small_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0, + small_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), + SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &dac_mux), + + /* aif for i2s input */ + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", + 0, SGTL5000_CHIP_DIG_POWER, + 0, 0), + + /* aif for i2s output */ + SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", + 0, SGTL5000_CHIP_DIG_POWER, + 1, 0), + + SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0), + + SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0), +}; + +/* routes for sgtl5000 */ +static const struct snd_soc_dapm_route audio_map[] = { + {"Capture Mux", "LINE_IN", "LINE_IN"}, /* line_in --> adc_mux */ + {"Capture Mux", "MIC_IN", "MIC_IN"}, /* mic_in --> adc_mux */ + + {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */ + {"AIFOUT", NULL, "ADC"}, /* adc --> i2s_out */ + + {"DAC", NULL, "AIFIN"}, /* i2s-->dac,skip audio mux */ + {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */ + {"LO", NULL, "DAC"}, /* dac --> line_out */ + + {"Headphone Mux", "LINE_IN", "LINE_IN"},/* line_in --> hp_mux */ + {"HP", NULL, "Headphone Mux"}, /* hp_mux --> hp */ + + {"LINE_OUT", NULL, "LO"}, + {"HP_OUT", NULL, "HP"}, +}; + +/* custom function to fetch info of PCM playback volume */ +static int dac_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xfc - 0x3c; + return 0; +} + +/* + * custom function to get of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * register value map to userspace value + * + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + * ------------------------------ + * userspace value 0xc0 0 + */ +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg; + int l; + int r; + + reg = snd_soc_read(codec, SGTL5000_CHIP_DAC_VOL); + + /* get left channel volume */ + l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT; + + /* get right channel volume */ + r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT; + + /* make sure value fall in (0x3c,0xfc) */ + l = clamp(l, 0x3c, 0xfc); + r = clamp(r, 0x3c, 0xfc); + + /* invert it and map to userspace value */ + l = 0xfc - l; + r = 0xfc - r; + + ucontrol->value.integer.value[0] = l; + ucontrol->value.integer.value[1] = r; + + return 0; +} + +/* + * custom function to put of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * userspace value map to register value + * + * userspace value 0xc0 0 + * ------------------------------ + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + */ +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg; + int l; + int r; + + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + + /* make sure userspace volume fall in (0, 0xfc-0x3c) */ + l = clamp(l, 0, 0xfc - 0x3c); + r = clamp(r, 0, 0xfc - 0x3c); + + /* invert it, get the value can be set to register */ + l = 0xfc - l; + r = 0xfc - r; + + /* shift to get the register value */ + reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT | + r << SGTL5000_DAC_VOL_RIGHT_SHIFT; + + snd_soc_write(codec, SGTL5000_CHIP_DAC_VOL, reg); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0); + +/* tlv for mic gain, 0db 20db 30db 40db */ +static const unsigned int mic_gain_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0), +}; + +/* tlv for hp volume, -51.5db to 12.0db, step .5db */ +static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); + +static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { + /* SOC_DOUBLE_S8_TLV with invert */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = dac_info_volsw, + .get = dac_get_volsw, + .put = dac_put_volsw, + }, + + SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0), + SOC_SINGLE_TLV("Capture Attenuate Switch (-6dB)", + SGTL5000_CHIP_ANA_ADC_CTRL, + 8, 2, 0, capture_6db_attenuate), + SOC_SINGLE("Capture ZC Switch", SGTL5000_CHIP_ANA_CTRL, 1, 1, 0), + + SOC_DOUBLE_TLV("Headphone Playback Volume", + SGTL5000_CHIP_ANA_HP_CTRL, + 0, 8, + 0x7f, 1, + headphone_volume), + SOC_SINGLE("Headphone Playback ZC Switch", SGTL5000_CHIP_ANA_CTRL, + 5, 1, 0), + + SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, + 0, 4, 0, mic_gain_tlv), +}; + +/* mute the codec used by alsa core */ +static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT; + + snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL, + adcdac_ctrl, mute ? adcdac_ctrl : 0); + + return 0; +} + +/* set codec format */ +static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + u16 i2sctl = 0; + + sgtl5000->master = 0; + /* + * i2s clock and frame master setting. + * ONLY support: + * - clock and frame slave, + * - clock and frame master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + i2sctl |= SGTL5000_I2S_MASTER; + sgtl5000->master = 1; + break; + default: + return -EINVAL; + } + + /* setting i2s data format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2sctl |= SGTL5000_I2S_MODE_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: + i2sctl |= SGTL5000_I2S_MODE_PCM; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + case SND_SOC_DAIFMT_I2S: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2sctl |= SGTL5000_I2S_MODE_RJ; + i2sctl |= SGTL5000_I2S_LRPOL; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + default: + return -EINVAL; + } + + sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + i2sctl |= SGTL5000_I2S_SCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl); + + return 0; +} + +/* set codec sysclk */ +static int sgtl5000_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 sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case SGTL5000_SYSCLK: + sgtl5000->sysclk = freq; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * set clock according to i2s frame clock, + * sgtl5000 provide 2 clock sources. + * 1. sys_mclk. sample freq can only configure to + * 1/256, 1/384, 1/512 of sys_mclk. + * 2. pll. can derive any audio clocks. + * + * clock setting rules: + * 1. in slave mode, only sys_mclk can use. + * 2. as constraint by sys_mclk, sample freq should + * set to 32k, 44.1k and above. + * 3. using sys_mclk prefer to pll to save power. + */ +static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + int clk_ctl = 0; + int sys_fs; /* sample freq */ + + /* + * sample freq should be divided by frame clock, + * if frame clock lower than 44.1khz, sample feq should set to + * 32khz or 44.1khz. + */ + switch (frame_rate) { + case 8000: + case 16000: + sys_fs = 32000; + break; + case 11025: + case 22050: + sys_fs = 44100; + break; + default: + sys_fs = frame_rate; + break; + } + + /* set divided factor of frame clock */ + switch (sys_fs / frame_rate) { + case 4: + clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT; + break; + case 2: + clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT; + break; + case 1: + clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT; + break; + default: + return -EINVAL; + } + + /* set the sys_fs according to frame rate */ + switch (sys_fs) { + case 32000: + clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; + break; + case 44100: + clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT; + break; + case 48000: + clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT; + break; + case 96000: + clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT; + break; + default: + dev_err(codec->dev, "frame rate %d not supported\n", + frame_rate); + return -EINVAL; + } + + /* + * calculate the divider of mclk/sample_freq, + * factor of freq =96k can only be 256, since mclk in range (12m,27m) + */ + switch (sgtl5000->sysclk / sys_fs) { + case 256: + clk_ctl |= SGTL5000_MCLK_FREQ_256FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 384: + clk_ctl |= SGTL5000_MCLK_FREQ_384FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 512: + clk_ctl |= SGTL5000_MCLK_FREQ_512FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + default: + /* if mclk not satisify the divider, use pll */ + if (sgtl5000->master) { + clk_ctl |= SGTL5000_MCLK_FREQ_PLL << + SGTL5000_MCLK_FREQ_SHIFT; + } else { + dev_err(codec->dev, + "PLL not supported in slave mode\n"); + return -EINVAL; + } + } + + /* if using pll, please check manual 6.4.2 for detail */ + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + u64 out, t; + int div2; + int pll_ctl; + unsigned int in, int_div, frac_div; + + if (sgtl5000->sysclk > 17000000) { + div2 = 1; + in = sgtl5000->sysclk / 2; + } else { + div2 = 0; + in = sgtl5000->sysclk; + } + if (sys_fs == 44100) + out = 180633600; + else + out = 196608000; + t = do_div(out, in); + int_div = out; + t *= 2048; + do_div(t, in); + frac_div = t; + pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT | + frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT; + + snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl); + if (div2) + snd_soc_update_bits(codec, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + SGTL5000_INPUT_FREQ_DIV2); + else + snd_soc_update_bits(codec, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + 0); + + /* power up pll */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP); + } else { + /* power down pll */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + 0); + } + + /* if using pll, clk_ctrl must be set after pll power up */ + snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + * input: params_rate, params_fmt + */ +static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + int channels = params_channels(params); + int i2s_ctl = 0; + int stereo; + int ret; + + /* sysclk should already set */ + if (!sgtl5000->sysclk) { + dev_err(codec->dev, "%s: set sysclk first!\n", __func__); + return -EFAULT; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stereo = SGTL5000_DAC_STEREO; + else + stereo = SGTL5000_ADC_STEREO; + + /* set mono to save power */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, stereo, + channels == 1 ? 0 : stereo); + + /* set codec clock base on lrclk */ + ret = sgtl5000_set_clock(codec, params_rate(params)); + if (ret) + return ret; + + /* set i2s data format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl, i2s_ctl); + + return 0; +} + +static int ldo_regulator_is_enabled(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + + return ldo->enabled; +} + +static int ldo_regulator_enable(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; + int reg; + + if (ldo_regulator_is_enabled(dev)) + return 0; + + /* set regulator value firstly */ + reg = (1600 - ldo->voltage / 1000) / 50; + reg = clamp(reg, 0x0, 0xf); + + /* amend the voltage value, unit: uV */ + ldo->voltage = (1600 - reg * 50) * 1000; + + /* set voltage to register */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, reg); + + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINEREG_D_POWERUP, + SGTL5000_LINEREG_D_POWERUP); + + /* when internal ldo enabled, simple digital power can be disabled */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP, + 0); + + ldo->enabled = 1; + return 0; +} + +static int ldo_regulator_disable(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; + + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINEREG_D_POWERUP, + 0); + + /* clear voltage info */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, 0); + + ldo->enabled = 0; + + return 0; +} + +static int ldo_regulator_get_voltage(struct regulator_dev *dev) +{ + struct ldo_regulator *ldo = rdev_get_drvdata(dev); + + return ldo->voltage; +} + +static struct regulator_ops ldo_regulator_ops = { + .is_enabled = ldo_regulator_is_enabled, + .enable = ldo_regulator_enable, + .disable = ldo_regulator_disable, + .get_voltage = ldo_regulator_get_voltage, +}; + +static int ldo_regulator_register(struct snd_soc_codec *codec, + struct regulator_init_data *init_data, + int voltage) +{ + struct ldo_regulator *ldo; + + ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL); + + if (!ldo) { + dev_err(codec->dev, "failed to allocate ldo_regulator\n"); + return -ENOMEM; + } + + ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL); + if (!ldo->desc.name) { + kfree(ldo); + dev_err(codec->dev, "failed to allocate decs name memory\n"); + return -ENOMEM; + } + + ldo->desc.type = REGULATOR_VOLTAGE; + ldo->desc.owner = THIS_MODULE; + ldo->desc.ops = &ldo_regulator_ops; + ldo->desc.n_voltages = 1; + + ldo->codec_data = codec; + ldo->voltage = voltage; + + ldo->dev = regulator_register(&ldo->desc, codec->dev, + init_data, ldo); + if (IS_ERR(ldo->dev)) { + int ret = PTR_ERR(ldo->dev); + + dev_err(codec->dev, "failed to register regulator\n"); + kfree(ldo->desc.name); + kfree(ldo); + + return ret; + } + + return 0; +} + +static int ldo_regulator_remove(struct snd_soc_codec *codec) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + struct ldo_regulator *ldo = sgtl5000->ldo; + + if (!ldo) + return 0; + + regulator_unregister(ldo->dev); + kfree(ldo->desc.name); + kfree(ldo); + + return 0; +} + +/* + * set dac bias + * common state changes: + * startup: + * off --> standby --> prepare --> on + * standby --> prepare --> on + * + * stop: + * on --> prepare --> standby + */ +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int ret; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable( + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + return ret; + udelay(10); + } + + break; + case SND_SOC_BIAS_OFF: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops sgtl5000_ops = { + .hw_params = sgtl5000_pcm_hw_params, + .digital_mute = sgtl5000_digital_mute, + .set_fmt = sgtl5000_set_dai_fmt, + .set_sysclk = sgtl5000_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver sgtl5000_dai = { + .name = "sgtl5000", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + /* + * only support 8~48K + 96K, + * TODO modify hw_param to support more + */ + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .ops = &sgtl5000_ops, + .symmetric_rates = 1, +}; + +static int sgtl5000_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case SGTL5000_CHIP_ID: + case SGTL5000_CHIP_ADCDAC_CTRL: + case SGTL5000_CHIP_ANA_STATUS: + return 1; + } + + return 0; +} + +#ifdef CONFIG_SUSPEND +static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +/* + * restore all sgtl5000 registers, + * since a big hole between dap and regular registers, + * we will restore them respectively. + */ +static int sgtl5000_restore_regs(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i; + int regular_regs = SGTL5000_CHIP_SHORT_CTRL >> 1; + + /* restore regular registers */ + for (i = 0; i < regular_regs; i++) { + int reg = i << 1; + + /* this regs depends on the others */ + if (reg == SGTL5000_CHIP_ANA_POWER || + reg == SGTL5000_CHIP_CLK_CTRL || + reg == SGTL5000_CHIP_LINREG_CTRL || + reg == SGTL5000_CHIP_LINE_OUT_CTRL || + reg == SGTL5000_CHIP_CLK_CTRL) + continue; + + snd_soc_write(codec, reg, cache[i]); + } + + /* restore dap registers */ + for (i = SGTL5000_DAP_REG_OFFSET >> 1; + i < SGTL5000_MAX_REG_OFFSET >> 1; i++) { + int reg = i << 1; + + snd_soc_write(codec, reg, cache[i]); + } + + /* + * restore power and other regs according + * to set_power() and set_clock() + */ + snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, + cache[SGTL5000_CHIP_LINREG_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, + cache[SGTL5000_CHIP_ANA_POWER >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, + cache[SGTL5000_CHIP_CLK_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL, + cache[SGTL5000_CHIP_REF_CTRL >> 1]); + + snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, + cache[SGTL5000_CHIP_LINE_OUT_CTRL >> 1]); + return 0; +} + +static int sgtl5000_resume(struct snd_soc_codec *codec) +{ + /* Bring the codec back up to standby to enable regulators */ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restore registers by cached in memory */ + sgtl5000_restore_regs(codec); + return 0; +} +#else +#define sgtl5000_suspend NULL +#define sgtl5000_resume NULL +#endif /* CONFIG_SUSPEND */ + +/* + * sgtl5000 has 3 internal power supplies: + * 1. VAG, normally set to vdda/2 + * 2. chargepump, set to different value + * according to voltage of vdda and vddio + * 3. line out VAG, normally set to vddio/2 + * + * and should be set according to: + * 1. vddd provided by external or not + * 2. vdda and vddio voltage value. > 3.1v or not + * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd. + */ +static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) +{ + int vddd; + int vdda; + int vddio; + u16 ana_pwr; + u16 lreg_ctrl; + int vag; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer); + vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer); + vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer); + + vdda = vdda / 1000; + vddio = vddio / 1000; + vddd = vddd / 1000; + + if (vdda <= 0 || vddio <= 0 || vddd < 0) { + dev_err(codec->dev, "regulator voltage not set correctly\n"); + + return -EINVAL; + } + + /* according to datasheet, maximum voltage of supplies */ + if (vdda > 3600 || vddio > 3600 || vddd > 1980) { + dev_err(codec->dev, + "exceed max voltage vdda %dmv vddio %dma vddd %dma\n", + vdda, vddio, vddd); + + return -EINVAL; + } + + /* reset value */ + ana_pwr = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr |= SGTL5000_DAC_STEREO | + SGTL5000_ADC_STEREO | + SGTL5000_REFTOP_POWERUP; + lreg_ctrl = snd_soc_read(codec, SGTL5000_CHIP_LINREG_CTRL); + + if (vddio < 3100 && vdda < 3100) { + /* enable internal oscillator used for charge pump */ + snd_soc_update_bits(codec, SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INT_OSC_EN, + SGTL5000_INT_OSC_EN); + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } else if (vddio >= 3100 && vdda >= 3100) { + /* + * if vddio and vddd > 3.1v, + * charge pump should be clean before set ana_pwr + */ + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VDDC_CHRGPMP_POWERUP, 0); + + /* VDDC use VDDIO rail */ + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO << + SGTL5000_VDDC_MAN_ASSN_SHIFT; + } + + snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + + /* set voltage to register */ + snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, + (0x1 << 4) - 1, 0x8); + + /* + * if vddd linear reg has been enabled, + * simple digital supply should be clear to get + * proper VDDD voltage. + */ + if (ana_pwr & SGTL5000_LINEREG_D_POWERUP) + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP, + 0); + else + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP | + SGTL5000_STARTUP_POWERUP, + 0); + + /* + * set ADC/DAC VAG to vdda / 2, + * should stay in range (0.8v, 1.575v) + */ + vag = vdda / 2; + if (vag <= SGTL5000_ANA_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP * + (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT)) + vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT; + else + vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP; + + snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, + vag << SGTL5000_ANA_GND_SHIFT, + vag << SGTL5000_ANA_GND_SHIFT); + + /* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */ + vag = vddio / 2; + if (vag <= SGTL5000_LINE_OUT_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_LINE_OUT_GND_BASE + + SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX) + vag = SGTL5000_LINE_OUT_GND_MAX; + else + vag = (vag - SGTL5000_LINE_OUT_GND_BASE) / + SGTL5000_LINE_OUT_GND_STP; + + snd_soc_update_bits(codec, SGTL5000_CHIP_LINE_OUT_CTRL, + vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT, + vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT); + + return 0; +} + +static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) +{ + u16 reg; + int ret; + int rev; + int i; + int external_vddd = 0; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++) + sgtl5000->supplies[i].supply = supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (!ret) + external_vddd = 1; + else { + /* set internal ldo to 1.2v */ + int voltage = LDO_VOLTAGE; + + ret = ldo_regulator_register(codec, &ldo_init_data, voltage); + if (ret) { + dev_err(codec->dev, + "Failed to register vddd internal supplies: %d\n", + ret); + return ret; + } + + sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; + + ret = regulator_bulk_get(codec->dev, + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + + if (ret) { + ldo_regulator_remove(codec); + dev_err(codec->dev, + "Failed to request supplies: %d\n", ret); + + return ret; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + goto err_regulator_free; + + /* wait for all power rails bring up */ + udelay(10); + + /* read chip information */ + reg = snd_soc_read(codec, SGTL5000_CHIP_ID); + if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != + SGTL5000_PARTID_PART_ID) { + dev_err(codec->dev, + "Device with ID register %x is not a sgtl5000\n", reg); + ret = -ENODEV; + goto err_regulator_disable; + } + + rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; + dev_info(codec->dev, "sgtl5000 revision %d\n", rev); + + /* + * workaround for revision 0x11 and later, + * roll back to use internal LDO + */ + if (external_vddd && rev >= 0x11) { + int voltage = LDO_VOLTAGE; + /* disable all regulator first */ + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + /* free VDDD regulator */ + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + + ret = ldo_regulator_register(codec, &ldo_init_data, voltage); + if (ret) + return ret; + + sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; + + ret = regulator_bulk_get(codec->dev, + ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) { + ldo_regulator_remove(codec); + dev_err(codec->dev, + "Failed to request supplies: %d\n", ret); + + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (ret) + goto err_regulator_free; + + /* wait for all power rails bring up */ + udelay(10); + } + + return 0; + +err_regulator_disable: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); +err_regulator_free: + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + if (external_vddd) + ldo_regulator_remove(codec); + return ret; + +} + +static int sgtl5000_probe(struct snd_soc_codec *codec) +{ + int ret; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + /* setup i2c data ops */ + ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = sgtl5000_enable_regulators(codec); + if (ret) + return ret; + + /* power up sgtl5000 */ + ret = sgtl5000_set_power_regs(codec); + if (ret) + goto err; + + /* enable small pop, introduce 400ms delay in turning off */ + snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, + SGTL5000_SMALL_POP, + SGTL5000_SMALL_POP); + + /* disable short cut detector */ + snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0); + + /* + * set i2s as default input of sound switch + * TODO: add sound switch to control and dapm widge. + */ + snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL, + SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT); + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, + SGTL5000_ADC_EN | SGTL5000_DAC_EN); + + /* enable dac volume ramp by default */ + snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_DAC_VOL_RAMP_EN | + SGTL5000_DAC_MUTE_RIGHT | + SGTL5000_DAC_MUTE_LEFT); + + snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL, + SGTL5000_HP_ZCD_EN | + SGTL5000_ADC_ZCD_EN); + + snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0); + + /* + * disable DAP + * TODO: + * Enable DAP in kcontrol and dapm. + */ + snd_soc_write(codec, SGTL5000_DAP_CTRL, 0); + + /* leading to standby state */ + ret = sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto err; + + snd_soc_add_controls(codec, sgtl5000_snd_controls, + ARRAY_SIZE(sgtl5000_snd_controls)); + + snd_soc_dapm_new_controls(&codec->dapm, sgtl5000_dapm_widgets, + ARRAY_SIZE(sgtl5000_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, audio_map, + ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(&codec->dapm); + + return 0; + +err: + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + ldo_regulator_remove(codec); + + return ret; +} + +static int sgtl5000_remove(struct snd_soc_codec *codec) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); + ldo_regulator_remove(codec); + + return 0; +} + +static struct snd_soc_codec_driver sgtl5000_driver = { + .probe = sgtl5000_probe, + .remove = sgtl5000_remove, + .suspend = sgtl5000_suspend, + .resume = sgtl5000_resume, + .set_bias_level = sgtl5000_set_bias_level, + .reg_cache_size = ARRAY_SIZE(sgtl5000_regs), + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .reg_cache_default = sgtl5000_regs, + .volatile_register = sgtl5000_volatile_register, +}; + +static __devinit int sgtl5000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sgtl5000_priv *sgtl5000; + int ret; + + sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL); + if (!sgtl5000) + return -ENOMEM; + + /* + * copy DAP default values to default value array. + * sgtl5000 register space has a big hole, merge it + * at init phase makes life easy. + * FIXME: should we drop 'const' of sgtl5000_regs? + */ + memcpy((void *)(&sgtl5000_regs[0] + (SGTL5000_DAP_REG_OFFSET >> 1)), + sgtl5000_dap_regs, + SGTL5000_MAX_REG_OFFSET - SGTL5000_DAP_REG_OFFSET); + + i2c_set_clientdata(client, sgtl5000); + + ret = snd_soc_register_codec(&client->dev, + &sgtl5000_driver, &sgtl5000_dai, 1); + if (ret) { + dev_err(&client->dev, "Failed to register codec: %d\n", ret); + kfree(sgtl5000); + return ret; + } + + return 0; +} + +static __devexit int sgtl5000_i2c_remove(struct i2c_client *client) +{ + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + kfree(sgtl5000); + return 0; +} + +static const struct i2c_device_id sgtl5000_id[] = { + {"sgtl5000", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, sgtl5000_id); + +static struct i2c_driver sgtl5000_i2c_driver = { + .driver = { + .name = "sgtl5000", + .owner = THIS_MODULE, + }, + .probe = sgtl5000_i2c_probe, + .remove = __devexit_p(sgtl5000_i2c_remove), + .id_table = sgtl5000_id, +}; + +static int __init sgtl5000_modinit(void) +{ + return i2c_add_driver(&sgtl5000_i2c_driver); +} +module_init(sgtl5000_modinit); + +static void __exit sgtl5000_exit(void) +{ + i2c_del_driver(&sgtl5000_i2c_driver); +} +module_exit(sgtl5000_exit); + +MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Zeng Zhaoming <zhaoming.zeng@freescale.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 00000000000..eec3ab368f3 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,400 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2010-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _SGTL5000_H +#define _SGTL5000_H + +/* + * Register values. + */ +#define SGTL5000_CHIP_ID 0x0000 +#define SGTL5000_CHIP_DIG_POWER 0x0002 +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +#define SGTL5000_CHIP_SSS_CTRL 0x000a +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e +#define SGTL5000_CHIP_DAC_VOL 0x0010 +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +#define SGTL5000_CHIP_REF_CTRL 0x0028 +#define SGTL5000_CHIP_MIC_CTRL 0x002a +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e +#define SGTL5000_CHIP_ANA_POWER 0x0030 +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +#define SGTL5000_CHIP_SHORT_CTRL 0x003c +#define SGTL5000_CHIP_ANA_TEST2 0x003a +#define SGTL5000_DAP_CTRL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SURROUND 0x010a +#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116 +#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118 +#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a +#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c +#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012a +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a + +/* + * Field Definitions. + */ + +/* + * SGTL5000_CHIP_ID + */ +#define SGTL5000_PARTID_MASK 0xff00 +#define SGTL5000_PARTID_SHIFT 8 +#define SGTL5000_PARTID_WIDTH 8 +#define SGTL5000_PARTID_PART_ID 0xa0 +#define SGTL5000_REVID_MASK 0x00ff +#define SGTL5000_REVID_SHIFT 0 +#define SGTL5000_REVID_WIDTH 8 + +/* + * SGTL5000_CHIP_DIG_POWER + */ +#define SGTL5000_ADC_EN 0x0040 +#define SGTL5000_DAC_EN 0x0020 +#define SGTL5000_DAP_POWERUP 0x0010 +#define SGTL5000_I2S_OUT_POWERUP 0x0002 +#define SGTL5000_I2S_IN_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_CLK_CTRL + */ +#define SGTL5000_RATE_MODE_MASK 0x0030 +#define SGTL5000_RATE_MODE_SHIFT 4 +#define SGTL5000_RATE_MODE_WIDTH 2 +#define SGTL5000_RATE_MODE_DIV_1 0 +#define SGTL5000_RATE_MODE_DIV_2 1 +#define SGTL5000_RATE_MODE_DIV_4 2 +#define SGTL5000_RATE_MODE_DIV_6 3 +#define SGTL5000_SYS_FS_MASK 0x000c +#define SGTL5000_SYS_FS_SHIFT 2 +#define SGTL5000_SYS_FS_WIDTH 2 +#define SGTL5000_SYS_FS_32k 0x0 +#define SGTL5000_SYS_FS_44_1k 0x1 +#define SGTL5000_SYS_FS_48k 0x2 +#define SGTL5000_SYS_FS_96k 0x3 +#define SGTL5000_MCLK_FREQ_MASK 0x0003 +#define SGTL5000_MCLK_FREQ_SHIFT 0 +#define SGTL5000_MCLK_FREQ_WIDTH 2 +#define SGTL5000_MCLK_FREQ_256FS 0x0 +#define SGTL5000_MCLK_FREQ_384FS 0x1 +#define SGTL5000_MCLK_FREQ_512FS 0x2 +#define SGTL5000_MCLK_FREQ_PLL 0x3 + +/* + * SGTL5000_CHIP_I2S_CTRL + */ +#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100 +#define SGTL5000_I2S_SCLKFREQ_SHIFT 8 +#define SGTL5000_I2S_SCLKFREQ_WIDTH 1 +#define SGTL5000_I2S_SCLKFREQ_64FS 0x0 +#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */ +#define SGTL5000_I2S_MASTER 0x0080 +#define SGTL5000_I2S_SCLK_INV 0x0040 +#define SGTL5000_I2S_DLEN_MASK 0x0030 +#define SGTL5000_I2S_DLEN_SHIFT 4 +#define SGTL5000_I2S_DLEN_WIDTH 2 +#define SGTL5000_I2S_DLEN_32 0x0 +#define SGTL5000_I2S_DLEN_24 0x1 +#define SGTL5000_I2S_DLEN_20 0x2 +#define SGTL5000_I2S_DLEN_16 0x3 +#define SGTL5000_I2S_MODE_MASK 0x000c +#define SGTL5000_I2S_MODE_SHIFT 2 +#define SGTL5000_I2S_MODE_WIDTH 2 +#define SGTL5000_I2S_MODE_I2S_LJ 0x0 +#define SGTL5000_I2S_MODE_RJ 0x1 +#define SGTL5000_I2S_MODE_PCM 0x2 +#define SGTL5000_I2S_LRALIGN 0x0002 +#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */ + +/* + * SGTL5000_CHIP_SSS_CTRL + */ +#define SGTL5000_DAP_MIX_LRSWAP 0x4000 +#define SGTL5000_DAP_LRSWAP 0x2000 +#define SGTL5000_DAC_LRSWAP 0x1000 +#define SGTL5000_I2S_OUT_LRSWAP 0x0400 +#define SGTL5000_DAP_MIX_SEL_MASK 0x0300 +#define SGTL5000_DAP_MIX_SEL_SHIFT 8 +#define SGTL5000_DAP_MIX_SEL_WIDTH 2 +#define SGTL5000_DAP_MIX_SEL_ADC 0x0 +#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1 +#define SGTL5000_DAP_SEL_MASK 0x00c0 +#define SGTL5000_DAP_SEL_SHIFT 6 +#define SGTL5000_DAP_SEL_WIDTH 2 +#define SGTL5000_DAP_SEL_ADC 0x0 +#define SGTL5000_DAP_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_MASK 0x0030 +#define SGTL5000_DAC_SEL_SHIFT 4 +#define SGTL5000_DAC_SEL_WIDTH 2 +#define SGTL5000_DAC_SEL_ADC 0x0 +#define SGTL5000_DAC_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_DAP 0x3 +#define SGTL5000_I2S_OUT_SEL_MASK 0x0003 +#define SGTL5000_I2S_OUT_SEL_SHIFT 0 +#define SGTL5000_I2S_OUT_SEL_WIDTH 2 +#define SGTL5000_I2S_OUT_SEL_ADC 0x0 +#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1 +#define SGTL5000_I2S_OUT_SEL_DAP 0x3 + +/* + * SGTL5000_CHIP_ADCDAC_CTRL + */ +#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000 +#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000 +#define SGTL5000_DAC_VOL_RAMP_EN 0x0200 +#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100 +#define SGTL5000_DAC_MUTE_RIGHT 0x0008 +#define SGTL5000_DAC_MUTE_LEFT 0x0004 +#define SGTL5000_ADC_HPF_FREEZE 0x0002 +#define SGTL5000_ADC_HPF_BYPASS 0x0001 + +/* + * SGTL5000_CHIP_DAC_VOL + */ +#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00 +#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8 +#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8 +#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff +#define SGTL5000_DAC_VOL_LEFT_SHIFT 0 +#define SGTL5000_DAC_VOL_LEFT_WIDTH 8 + +/* + * SGTL5000_CHIP_PAD_STRENGTH + */ +#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300 +#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8 +#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0 +#define SGTL5000_PAD_I2S_SCLK_SHIFT 6 +#define SGTL5000_PAD_I2S_SCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030 +#define SGTL5000_PAD_I2S_DOUT_SHIFT 4 +#define SGTL5000_PAD_I2S_DOUT_WIDTH 2 +#define SGTL5000_PAD_I2C_SDA_MASK 0x000c +#define SGTL5000_PAD_I2C_SDA_SHIFT 2 +#define SGTL5000_PAD_I2C_SDA_WIDTH 2 +#define SGTL5000_PAD_I2C_SCL_MASK 0x0003 +#define SGTL5000_PAD_I2C_SCL_SHIFT 0 +#define SGTL5000_PAD_I2C_SCL_WIDTH 2 + +/* + * SGTL5000_CHIP_ANA_ADC_CTRL + */ +#define SGTL5000_ADC_VOL_M6DB 0x0100 +#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0 +#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4 +#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4 +#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f +#define SGTL5000_ADC_VOL_LEFT_SHIFT 0 +#define SGTL5000_ADC_VOL_LEFT_WIDTH 4 + +/* + * SGTL5000_CHIP_ANA_HP_CTRL + */ +#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00 +#define SGTL5000_HP_VOL_RIGHT_SHIFT 8 +#define SGTL5000_HP_VOL_RIGHT_WIDTH 7 +#define SGTL5000_HP_VOL_LEFT_MASK 0x007f +#define SGTL5000_HP_VOL_LEFT_SHIFT 0 +#define SGTL5000_HP_VOL_LEFT_WIDTH 7 + +/* + * SGTL5000_CHIP_ANA_CTRL + */ +#define SGTL5000_LINE_OUT_MUTE 0x0100 +#define SGTL5000_HP_SEL_MASK 0x0040 +#define SGTL5000_HP_SEL_SHIFT 6 +#define SGTL5000_HP_SEL_WIDTH 1 +#define SGTL5000_HP_SEL_DAC 0x0 +#define SGTL5000_HP_SEL_LINE_IN 0x1 +#define SGTL5000_HP_ZCD_EN 0x0020 +#define SGTL5000_HP_MUTE 0x0010 +#define SGTL5000_ADC_SEL_MASK 0x0004 +#define SGTL5000_ADC_SEL_SHIFT 2 +#define SGTL5000_ADC_SEL_WIDTH 1 +#define SGTL5000_ADC_SEL_MIC 0x0 +#define SGTL5000_ADC_SEL_LINE_IN 0x1 +#define SGTL5000_ADC_ZCD_EN 0x0002 +#define SGTL5000_ADC_MUTE 0x0001 + +/* + * SGTL5000_CHIP_LINREG_CTRL + */ +#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040 +#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6 +#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1 +#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0 +#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1 +#define SGTL5000_VDDC_ASSN_OVRD 0x0020 +#define SGTL5000_LINREG_VDDD_MASK 0x000f +#define SGTL5000_LINREG_VDDD_SHIFT 0 +#define SGTL5000_LINREG_VDDD_WIDTH 4 + +/* + * SGTL5000_CHIP_REF_CTRL + */ +#define SGTL5000_ANA_GND_MASK 0x01f0 +#define SGTL5000_ANA_GND_SHIFT 4 +#define SGTL5000_ANA_GND_WIDTH 5 +#define SGTL5000_ANA_GND_BASE 800 /* mv */ +#define SGTL5000_ANA_GND_STP 25 /*mv */ +#define SGTL5000_BIAS_CTRL_MASK 0x000e +#define SGTL5000_BIAS_CTRL_SHIFT 1 +#define SGTL5000_BIAS_CTRL_WIDTH 3 +#define SGTL5000_SMALL_POP 0x0001 + +/* + * SGTL5000_CHIP_MIC_CTRL + */ +#define SGTL5000_BIAS_R_MASK 0x0200 +#define SGTL5000_BIAS_R_SHIFT 8 +#define SGTL5000_BIAS_R_WIDTH 2 +#define SGTL5000_BIAS_R_off 0x0 +#define SGTL5000_BIAS_R_2K 0x1 +#define SGTL5000_BIAS_R_4k 0x2 +#define SGTL5000_BIAS_R_8k 0x3 +#define SGTL5000_BIAS_VOLT_MASK 0x0070 +#define SGTL5000_BIAS_VOLT_SHIFT 4 +#define SGTL5000_BIAS_VOLT_WIDTH 3 +#define SGTL5000_MIC_GAIN_MASK 0x0003 +#define SGTL5000_MIC_GAIN_SHIFT 0 +#define SGTL5000_MIC_GAIN_WIDTH 2 + +/* + * SGTL5000_CHIP_LINE_OUT_CTRL + */ +#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00 +#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8 +#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4 +#define SGTL5000_LINE_OUT_CURRENT_180u 0x0 +#define SGTL5000_LINE_OUT_CURRENT_270u 0x1 +#define SGTL5000_LINE_OUT_CURRENT_360u 0x3 +#define SGTL5000_LINE_OUT_CURRENT_450u 0x7 +#define SGTL5000_LINE_OUT_CURRENT_540u 0xf +#define SGTL5000_LINE_OUT_GND_MASK 0x003f +#define SGTL5000_LINE_OUT_GND_SHIFT 0 +#define SGTL5000_LINE_OUT_GND_WIDTH 6 +#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */ +#define SGTL5000_LINE_OUT_GND_STP 25 +#define SGTL5000_LINE_OUT_GND_MAX 0x23 + +/* + * SGTL5000_CHIP_LINE_OUT_VOL + */ +#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00 +#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8 +#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5 +#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f +#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0 +#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5 + +/* + * SGTL5000_CHIP_ANA_POWER + */ +#define SGTL5000_DAC_STEREO 0x4000 +#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 +#define SGTL5000_STARTUP_POWERUP 0x1000 +#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 +#define SGTL5000_PLL_POWERUP 0x0400 +#define SGTL5000_LINEREG_D_POWERUP 0x0200 +#define SGTL5000_VCOAMP_POWERUP 0x0100 +#define SGTL5000_VAG_POWERUP 0x0080 +#define SGTL5000_ADC_STEREO 0x0040 +#define SGTL5000_REFTOP_POWERUP 0x0020 +#define SGTL5000_HP_POWERUP 0x0010 +#define SGTL5000_DAC_POWERUP 0x0008 +#define SGTL5000_CAPLESS_HP_POWERUP 0x0004 +#define SGTL5000_ADC_POWERUP 0x0002 +#define SGTL5000_LINE_OUT_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_PLL_CTRL + */ +#define SGTL5000_PLL_INT_DIV_MASK 0xf800 +#define SGTL5000_PLL_INT_DIV_SHIFT 11 +#define SGTL5000_PLL_INT_DIV_WIDTH 5 +#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700 +#define SGTL5000_PLL_FRAC_DIV_SHIFT 0 +#define SGTL5000_PLL_FRAC_DIV_WIDTH 11 + +/* + * SGTL5000_CHIP_CLK_TOP_CTRL + */ +#define SGTL5000_INT_OSC_EN 0x0800 +#define SGTL5000_INPUT_FREQ_DIV2 0x0008 + +/* + * SGTL5000_CHIP_ANA_STATUS + */ +#define SGTL5000_HP_LRSHORT 0x0200 +#define SGTL5000_CAPLESS_SHORT 0x0100 +#define SGTL5000_PLL_LOCKED 0x0010 + +/* + * SGTL5000_CHIP_SHORT_CTRL + */ +#define SGTL5000_LVLADJR_MASK 0x7000 +#define SGTL5000_LVLADJR_SHIFT 12 +#define SGTL5000_LVLADJR_WIDTH 3 +#define SGTL5000_LVLADJL_MASK 0x0700 +#define SGTL5000_LVLADJL_SHIFT 8 +#define SGTL5000_LVLADJL_WIDTH 3 +#define SGTL5000_LVLADJC_MASK 0x0070 +#define SGTL5000_LVLADJC_SHIFT 4 +#define SGTL5000_LVLADJC_WIDTH 3 +#define SGTL5000_LR_SHORT_MOD_MASK 0x000c +#define SGTL5000_LR_SHORT_MOD_SHIFT 2 +#define SGTL5000_LR_SHORT_MOD_WIDTH 2 +#define SGTL5000_CM_SHORT_MOD_MASK 0x0003 +#define SGTL5000_CM_SHORT_MOD_SHIFT 0 +#define SGTL5000_CM_SHORT_MOD_WIDTH 2 + +/* + *SGTL5000_CHIP_ANA_TEST2 + */ +#define SGTL5000_MONO_DAC 0x1000 + +/* + * SGTL5000_DAP_CTRL + */ +#define SGTL5000_DAP_MIX_EN 0x0010 +#define SGTL5000_DAP_EN 0x0001 + +#define SGTL5000_SYSCLK 0x00 +#define SGTL5000_LRCLK 0x01 + +#endif diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c new file mode 100644 index 00000000000..2a30eae1881 --- /dev/null +++ b/sound/soc/codecs/sn95031.c @@ -0,0 +1,949 @@ +/* + * sn95031.c - TI sn95031 Codec driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/intel_scu_ipc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/jack.h> +#include "sn95031.h" + +#define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100) +#define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +/* adc helper functions */ + +/* enables mic bias voltage */ +static void sn95031_enable_mic_bias(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_VAUD, BIT(2)|BIT(1)|BIT(0)); + snd_soc_update_bits(codec, SN95031_MICBIAS, BIT(2), BIT(2)); +} + +/* Enable/Disable the ADC depending on the argument */ +static void configure_adc(struct snd_soc_codec *sn95031_codec, int val) +{ + int value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if (val) { + /* Enable and start the ADC */ + value |= (SN95031_ADC_ENBL | SN95031_ADC_START); + value &= (~SN95031_ADC_NO_LOOP); + } else { + /* Just stop the ADC */ + value &= (~SN95031_ADC_START); + } + snd_soc_write(sn95031_codec, SN95031_ADC1CNTL1, value); +} + +/* + * finds an empty channel for conversion + * If the ADC is not enabled then start using 0th channel + * itself. Otherwise find an empty channel by looking for a + * channel in which the stopbit is set to 1. returns the index + * of the first free channel if succeeds or an error code. + * + * Context: can sleep + * + */ +static int find_free_channel(struct snd_soc_codec *sn95031_codec) +{ + int ret = 0, i, value; + + /* check whether ADC is enabled */ + value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1); + + if ((value & SN95031_ADC_ENBL) == 0) + return 0; + + /* ADC is already enabled; Looking for an empty channel */ + for (i = 0; i < SN95031_ADC_CHANLS_MAX; i++) { + value = snd_soc_read(sn95031_codec, + SN95031_ADC_CHNL_START_ADDR + i); + if (value & SN95031_STOPBIT_MASK) { + ret = i; + break; + } + } + return (ret > SN95031_ADC_LOOP_MAX) ? (-EINVAL) : ret; +} + +/* Initialize the ADC for reading micbias values. Can sleep. */ +static int sn95031_initialize_adc(struct snd_soc_codec *sn95031_codec) +{ + int base_addr, chnl_addr; + int value; + static int channel_index; + + /* Index of the first channel in which the stop bit is set */ + channel_index = find_free_channel(sn95031_codec); + if (channel_index < 0) { + pr_err("No free ADC channels"); + return channel_index; + } + + base_addr = SN95031_ADC_CHNL_START_ADDR + channel_index; + + if (!(channel_index == 0 || channel_index == SN95031_ADC_LOOP_MAX)) { + /* Reset stop bit for channels other than 0 and 12 */ + value = snd_soc_read(sn95031_codec, base_addr); + /* Set the stop bit to zero */ + snd_soc_write(sn95031_codec, base_addr, value & 0xEF); + /* Index of the first free channel */ + base_addr++; + channel_index++; + } + + /* Since this is the last channel, set the stop bit + to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ + snd_soc_write(sn95031_codec, base_addr, + SN95031_AUDIO_DETECT_CODE | 0x10); + + chnl_addr = SN95031_ADC_DATA_START_ADDR + 2 * channel_index; + pr_debug("mid_initialize : %x", chnl_addr); + configure_adc(sn95031_codec, 1); + return chnl_addr; +} + + +/* reads the ADC registers and gets the mic bias value in mV. */ +static unsigned int sn95031_get_mic_bias(struct snd_soc_codec *codec) +{ + u16 adc_adr = sn95031_initialize_adc(codec); + u16 adc_val1, adc_val2; + unsigned int mic_bias; + + sn95031_enable_mic_bias(codec); + + /* Enable the sound card for conversion before reading */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x05); + /* Re-toggle the RRDATARD bit */ + snd_soc_write(codec, SN95031_ADC1CNTL3, 0x04); + + /* Read the higher bits of data */ + msleep(1000); + adc_val1 = snd_soc_read(codec, adc_adr); + adc_adr++; + adc_val2 = snd_soc_read(codec, adc_adr); + + /* Adding lower two bits to the higher bits */ + mic_bias = (adc_val1 << 2) + (adc_val2 & 3); + mic_bias = (mic_bias * SN95031_ADC_ONE_LSB_MULTIPLIER) / 1000; + pr_debug("mic bias = %dmV\n", mic_bias); + return mic_bias; +} +EXPORT_SYMBOL_GPL(sn95031_get_mic_bias); +/*end - adc helper functions */ + +static inline unsigned int sn95031_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value = 0; + int ret; + + ret = intel_scu_ipc_ioread8(reg, &value); + if (ret) + pr_err("read of %x failed, err %d\n", reg, ret); + return value; + +} + +static inline int sn95031_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + int ret; + + ret = intel_scu_ipc_iowrite8(reg, value); + if (ret) + pr_err("write of %x failed, err %d\n", reg, ret); + return ret; +} + +static int sn95031_set_vaud_bias(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + pr_debug("vaud_bias powering up pll\n"); + /* power up the pll */ + snd_soc_write(codec, SN95031_AUDPLLCTRL, BIT(5)); + /* enable pcm 2 */ + snd_soc_update_bits(codec, SN95031_PCM2C2, + BIT(0), BIT(0)); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + pr_debug("vaud_bias power up rail\n"); + /* power up the rail */ + snd_soc_write(codec, SN95031_VAUD, + BIT(2)|BIT(1)|BIT(0)); + msleep(1); + } else if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) { + /* turn off pcm */ + pr_debug("vaud_bias power dn pcm\n"); + snd_soc_update_bits(codec, SN95031_PCM2C2, BIT(0), 0); + snd_soc_write(codec, SN95031_AUDPLLCTRL, 0); + } + break; + + + case SND_SOC_BIAS_OFF: + pr_debug("vaud_bias _OFF doing rail shutdown\n"); + snd_soc_write(codec, SN95031_VAUD, BIT(3)); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static int sn95031_vhs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) { + pr_debug("VHS SND_SOC_DAPM_EVENT_ON doing rail startup now\n"); + /* power up the rail */ + snd_soc_write(w->codec, SN95031_VHSP, 0x3D); + snd_soc_write(w->codec, SN95031_VHSN, 0x3F); + msleep(1); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + pr_debug("VHS SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n"); + snd_soc_write(w->codec, SN95031_VHSP, 0xC4); + snd_soc_write(w->codec, SN95031_VHSN, 0x04); + } + return 0; +} + +static int sn95031_vihf_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) { + pr_debug("VIHF SND_SOC_DAPM_EVENT_ON doing rail startup now\n"); + /* power up the rail */ + snd_soc_write(w->codec, SN95031_VIHF, 0x27); + msleep(1); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + pr_debug("VIHF SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n"); + snd_soc_write(w->codec, SN95031_VIHF, 0x24); + } + return 0; +} + +static int sn95031_dmic12_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + unsigned int ldo = 0, clk_dir = 0, data_dir = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ldo = BIT(5)|BIT(4); + clk_dir = BIT(0); + data_dir = BIT(7); + } + /* program DMIC LDO, clock and set clock */ + snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo); + snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(0), clk_dir); + snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(7), data_dir); + return 0; +} + +static int sn95031_dmic34_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + unsigned int ldo = 0, clk_dir = 0, data_dir = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ldo = BIT(5)|BIT(4); + clk_dir = BIT(2); + data_dir = BIT(1); + } + /* program DMIC LDO, clock and set clock */ + snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo); + snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(2), clk_dir); + snd_soc_update_bits(w->codec, SN95031_DMICBUF45, BIT(1), data_dir); + return 0; +} + +static int sn95031_dmic56_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + unsigned int ldo = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ldo = BIT(7)|BIT(6); + + /* program DMIC LDO */ + snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(7)|BIT(6), ldo); + return 0; +} + +/* mux controls */ +static const char *sn95031_mic_texts[] = { "AMIC", "LineIn" }; + +static const struct soc_enum sn95031_micl_enum = + SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 1, 2, sn95031_mic_texts); + +static const struct snd_kcontrol_new sn95031_micl_mux_control = + SOC_DAPM_ENUM("Route", sn95031_micl_enum); + +static const struct soc_enum sn95031_micr_enum = + SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 3, 2, sn95031_mic_texts); + +static const struct snd_kcontrol_new sn95031_micr_mux_control = + SOC_DAPM_ENUM("Route", sn95031_micr_enum); + +static const char *sn95031_input_texts[] = { "DMIC1", "DMIC2", "DMIC3", + "DMIC4", "DMIC5", "DMIC6", + "ADC Left", "ADC Right" }; + +static const struct soc_enum sn95031_input1_enum = + SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 0, 8, sn95031_input_texts); + +static const struct snd_kcontrol_new sn95031_input1_mux_control = + SOC_DAPM_ENUM("Route", sn95031_input1_enum); + +static const struct soc_enum sn95031_input2_enum = + SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 4, 8, sn95031_input_texts); + +static const struct snd_kcontrol_new sn95031_input2_mux_control = + SOC_DAPM_ENUM("Route", sn95031_input2_enum); + +static const struct soc_enum sn95031_input3_enum = + SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 0, 8, sn95031_input_texts); + +static const struct snd_kcontrol_new sn95031_input3_mux_control = + SOC_DAPM_ENUM("Route", sn95031_input3_enum); + +static const struct soc_enum sn95031_input4_enum = + SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 4, 8, sn95031_input_texts); + +static const struct snd_kcontrol_new sn95031_input4_mux_control = + SOC_DAPM_ENUM("Route", sn95031_input4_enum); + +/* capture path controls */ + +static const char *sn95031_micmode_text[] = {"Single Ended", "Differential"}; + +/* 0dB to 30dB in 10dB steps */ +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 10, 0); + +static const struct soc_enum sn95031_micmode1_enum = + SOC_ENUM_SINGLE(SN95031_MICAMP1, 1, 2, sn95031_micmode_text); +static const struct soc_enum sn95031_micmode2_enum = + SOC_ENUM_SINGLE(SN95031_MICAMP2, 1, 2, sn95031_micmode_text); + +static const char *sn95031_dmic_cfg_text[] = {"GPO", "DMIC"}; + +static const struct soc_enum sn95031_dmic12_cfg_enum = + SOC_ENUM_SINGLE(SN95031_DMICMUX, 0, 2, sn95031_dmic_cfg_text); +static const struct soc_enum sn95031_dmic34_cfg_enum = + SOC_ENUM_SINGLE(SN95031_DMICMUX, 1, 2, sn95031_dmic_cfg_text); +static const struct soc_enum sn95031_dmic56_cfg_enum = + SOC_ENUM_SINGLE(SN95031_DMICMUX, 2, 2, sn95031_dmic_cfg_text); + +static const struct snd_kcontrol_new sn95031_snd_controls[] = { + SOC_ENUM("Mic1Mode Capture Route", sn95031_micmode1_enum), + SOC_ENUM("Mic2Mode Capture Route", sn95031_micmode2_enum), + SOC_ENUM("DMIC12 Capture Route", sn95031_dmic12_cfg_enum), + SOC_ENUM("DMIC34 Capture Route", sn95031_dmic34_cfg_enum), + SOC_ENUM("DMIC56 Capture Route", sn95031_dmic56_cfg_enum), + SOC_SINGLE_TLV("Mic1 Capture Volume", SN95031_MICAMP1, + 2, 4, 0, mic_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", SN95031_MICAMP2, + 2, 4, 0, mic_tlv), +}; + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget sn95031_dapm_widgets[] = { + + /* all end points mic, hs etc */ + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("EPOUT"), + SND_SOC_DAPM_OUTPUT("IHFOUTL"), + SND_SOC_DAPM_OUTPUT("IHFOUTR"), + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + SND_SOC_DAPM_OUTPUT("VIB1OUT"), + SND_SOC_DAPM_OUTPUT("VIB2OUT"), + + SND_SOC_DAPM_INPUT("AMIC1"), /* headset mic */ + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + SND_SOC_DAPM_INPUT("DMIC5"), + SND_SOC_DAPM_INPUT("DMIC6"), + SND_SOC_DAPM_INPUT("LINEINL"), + SND_SOC_DAPM_INPUT("LINEINR"), + + SND_SOC_DAPM_MICBIAS("AMIC1Bias", SN95031_MICBIAS, 2, 0), + SND_SOC_DAPM_MICBIAS("AMIC2Bias", SN95031_MICBIAS, 3, 0), + SND_SOC_DAPM_MICBIAS("DMIC12Bias", SN95031_DMICMUX, 3, 0), + SND_SOC_DAPM_MICBIAS("DMIC34Bias", SN95031_DMICMUX, 4, 0), + SND_SOC_DAPM_MICBIAS("DMIC56Bias", SN95031_DMICMUX, 5, 0), + + SND_SOC_DAPM_SUPPLY("DMIC12supply", SN95031_DMICLK, 0, 0, + sn95031_dmic12_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DMIC34supply", SN95031_DMICLK, 1, 0, + sn95031_dmic34_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DMIC56supply", SN95031_DMICLK, 2, 0, + sn95031_dmic56_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT("PCM_Out", "Capture", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("Headset Rail", SND_SOC_NOPM, 0, 0, + sn95031_vhs_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Speaker Rail", SND_SOC_NOPM, 0, 0, + sn95031_vihf_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* playback path driver enables */ + SND_SOC_DAPM_PGA("Headset Left Playback", + SN95031_DRIVEREN, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right Playback", + SN95031_DRIVEREN, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left Playback", + SN95031_DRIVEREN, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right Playback", + SN95031_DRIVEREN, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Vibra1 Playback", + SN95031_DRIVEREN, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Vibra2 Playback", + SN95031_DRIVEREN, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Earpiece Playback", + SN95031_DRIVEREN, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Left Playback", + SN95031_LOCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Right Playback", + SN95031_LOCTL, 4, 0, NULL, 0), + + /* playback path filter enable */ + SND_SOC_DAPM_PGA("Headset Left Filter", + SN95031_HSEPRXCTRL, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right Filter", + SN95031_HSEPRXCTRL, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left Filter", + SN95031_IHFRXCTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right Filter", + SN95031_IHFRXCTRL, 1, 0, NULL, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("HSDAC Left", "Headset", + SN95031_DACCONFIG, 0, 0), + SND_SOC_DAPM_DAC("HSDAC Right", "Headset", + SN95031_DACCONFIG, 1, 0), + SND_SOC_DAPM_DAC("IHFDAC Left", "Speaker", + SN95031_DACCONFIG, 2, 0), + SND_SOC_DAPM_DAC("IHFDAC Right", "Speaker", + SN95031_DACCONFIG, 3, 0), + SND_SOC_DAPM_DAC("Vibra1 DAC", "Vibra1", + SN95031_VIB1C5, 1, 0), + SND_SOC_DAPM_DAC("Vibra2 DAC", "Vibra2", + SN95031_VIB2C5, 1, 0), + + /* capture widgets */ + SND_SOC_DAPM_PGA("LineIn Enable Left", SN95031_MICAMP1, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("LineIn Enable Right", SN95031_MICAMP2, + 7, 0, NULL, 0), + + SND_SOC_DAPM_PGA("MIC1 Enable", SN95031_MICAMP1, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC2 Enable", SN95031_MICAMP2, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("TX1 Enable", SN95031_AUDIOTXEN, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("TX2 Enable", SN95031_AUDIOTXEN, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("TX3 Enable", SN95031_AUDIOTXEN, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("TX4 Enable", SN95031_AUDIOTXEN, 5, 0, NULL, 0), + + /* ADC have null stream as they will be turned ON by TX path */ + SND_SOC_DAPM_ADC("ADC Left", NULL, + SN95031_ADCCONFIG, 0, 0), + SND_SOC_DAPM_ADC("ADC Right", NULL, + SN95031_ADCCONFIG, 2, 0), + + SND_SOC_DAPM_MUX("Mic_InputL Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_micl_mux_control), + SND_SOC_DAPM_MUX("Mic_InputR Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_micr_mux_control), + + SND_SOC_DAPM_MUX("Txpath1 Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_input1_mux_control), + SND_SOC_DAPM_MUX("Txpath2 Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_input2_mux_control), + SND_SOC_DAPM_MUX("Txpath3 Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_input3_mux_control), + SND_SOC_DAPM_MUX("Txpath4 Capture Route", + SND_SOC_NOPM, 0, 0, &sn95031_input4_mux_control), + +}; + +static const struct snd_soc_dapm_route sn95031_audio_map[] = { + /* headset and earpiece map */ + { "HPOUTL", NULL, "Headset Rail"}, + { "HPOUTR", NULL, "Headset Rail"}, + { "HPOUTL", NULL, "Headset Left Playback" }, + { "HPOUTR", NULL, "Headset Right Playback" }, + { "EPOUT", NULL, "Earpiece Playback" }, + { "Headset Left Playback", NULL, "Headset Left Filter"}, + { "Headset Right Playback", NULL, "Headset Right Filter"}, + { "Earpiece Playback", NULL, "Headset Left Filter"}, + { "Headset Left Filter", NULL, "HSDAC Left"}, + { "Headset Right Filter", NULL, "HSDAC Right"}, + + /* speaker map */ + { "IHFOUTL", NULL, "Speaker Rail"}, + { "IHFOUTR", NULL, "Speaker Rail"}, + { "IHFOUTL", "NULL", "Speaker Left Playback"}, + { "IHFOUTR", "NULL", "Speaker Right Playback"}, + { "Speaker Left Playback", NULL, "Speaker Left Filter"}, + { "Speaker Right Playback", NULL, "Speaker Right Filter"}, + { "Speaker Left Filter", NULL, "IHFDAC Left"}, + { "Speaker Right Filter", NULL, "IHFDAC Right"}, + + /* vibra map */ + { "VIB1OUT", NULL, "Vibra1 Playback"}, + { "Vibra1 Playback", NULL, "Vibra1 DAC"}, + + { "VIB2OUT", NULL, "Vibra2 Playback"}, + { "Vibra2 Playback", NULL, "Vibra2 DAC"}, + + /* lineout */ + { "LINEOUTL", NULL, "Lineout Left Playback"}, + { "LINEOUTR", NULL, "Lineout Right Playback"}, + { "Lineout Left Playback", NULL, "Headset Left Filter"}, + { "Lineout Left Playback", NULL, "Speaker Left Filter"}, + { "Lineout Left Playback", NULL, "Vibra1 DAC"}, + { "Lineout Right Playback", NULL, "Headset Right Filter"}, + { "Lineout Right Playback", NULL, "Speaker Right Filter"}, + { "Lineout Right Playback", NULL, "Vibra2 DAC"}, + + /* Headset (AMIC1) mic */ + { "AMIC1Bias", NULL, "AMIC1"}, + { "MIC1 Enable", NULL, "AMIC1Bias"}, + { "Mic_InputL Capture Route", "AMIC", "MIC1 Enable"}, + + /* AMIC2 */ + { "AMIC2Bias", NULL, "AMIC2"}, + { "MIC2 Enable", NULL, "AMIC2Bias"}, + { "Mic_InputR Capture Route", "AMIC", "MIC2 Enable"}, + + + /* Linein */ + { "LineIn Enable Left", NULL, "LINEINL"}, + { "LineIn Enable Right", NULL, "LINEINR"}, + { "Mic_InputL Capture Route", "LineIn", "LineIn Enable Left"}, + { "Mic_InputR Capture Route", "LineIn", "LineIn Enable Right"}, + + /* ADC connection */ + { "ADC Left", NULL, "Mic_InputL Capture Route"}, + { "ADC Right", NULL, "Mic_InputR Capture Route"}, + + /*DMIC connections */ + { "DMIC1", NULL, "DMIC12supply"}, + { "DMIC2", NULL, "DMIC12supply"}, + { "DMIC3", NULL, "DMIC34supply"}, + { "DMIC4", NULL, "DMIC34supply"}, + { "DMIC5", NULL, "DMIC56supply"}, + { "DMIC6", NULL, "DMIC56supply"}, + + { "DMIC12Bias", NULL, "DMIC1"}, + { "DMIC12Bias", NULL, "DMIC2"}, + { "DMIC34Bias", NULL, "DMIC3"}, + { "DMIC34Bias", NULL, "DMIC4"}, + { "DMIC56Bias", NULL, "DMIC5"}, + { "DMIC56Bias", NULL, "DMIC6"}, + + /*TX path inputs*/ + { "Txpath1 Capture Route", "ADC Left", "ADC Left"}, + { "Txpath2 Capture Route", "ADC Left", "ADC Left"}, + { "Txpath3 Capture Route", "ADC Left", "ADC Left"}, + { "Txpath4 Capture Route", "ADC Left", "ADC Left"}, + { "Txpath1 Capture Route", "ADC Right", "ADC Right"}, + { "Txpath2 Capture Route", "ADC Right", "ADC Right"}, + { "Txpath3 Capture Route", "ADC Right", "ADC Right"}, + { "Txpath4 Capture Route", "ADC Right", "ADC Right"}, + { "Txpath1 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath2 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath3 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath4 Capture Route", "DMIC1", "DMIC1"}, + { "Txpath1 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath2 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath3 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath4 Capture Route", "DMIC2", "DMIC2"}, + { "Txpath1 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath2 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath3 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath4 Capture Route", "DMIC3", "DMIC3"}, + { "Txpath1 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath2 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath3 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath4 Capture Route", "DMIC4", "DMIC4"}, + { "Txpath1 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath2 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath3 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath4 Capture Route", "DMIC5", "DMIC5"}, + { "Txpath1 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath2 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath3 Capture Route", "DMIC6", "DMIC6"}, + { "Txpath4 Capture Route", "DMIC6", "DMIC6"}, + + /* tx path */ + { "TX1 Enable", NULL, "Txpath1 Capture Route"}, + { "TX2 Enable", NULL, "Txpath2 Capture Route"}, + { "TX3 Enable", NULL, "Txpath3 Capture Route"}, + { "TX4 Enable", NULL, "Txpath4 Capture Route"}, + { "PCM_Out", NULL, "TX1 Enable"}, + { "PCM_Out", NULL, "TX2 Enable"}, + { "PCM_Out", NULL, "TX3 Enable"}, + { "PCM_Out", NULL, "TX4 Enable"}, + +}; + +/* speaker and headset mutes, for audio pops and clicks */ +static int sn95031_pcm_hs_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, + SN95031_HSLVOLCTRL, BIT(7), (!mute << 7)); + snd_soc_update_bits(dai->codec, + SN95031_HSRVOLCTRL, BIT(7), (!mute << 7)); + return 0; +} + +static int sn95031_pcm_spkr_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, + SN95031_IHFLVOLCTRL, BIT(7), (!mute << 7)); + snd_soc_update_bits(dai->codec, + SN95031_IHFRVOLCTRL, BIT(7), (!mute << 7)); + return 0; +} + +int sn95031_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + unsigned int format, rate; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + format = BIT(4)|BIT(5); + break; + + case SNDRV_PCM_FORMAT_S24_LE: + format = 0; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(dai->codec, SN95031_PCM2C2, + BIT(4)|BIT(5), format); + + switch (params_rate(params)) { + case 48000: + pr_debug("RATE_48000\n"); + rate = 0; + break; + + case 44100: + pr_debug("RATE_44100\n"); + rate = BIT(7); + break; + + default: + pr_err("ERR rate %d\n", params_rate(params)); + return -EINVAL; + } + snd_soc_update_bits(dai->codec, SN95031_PCM1C1, BIT(7), rate); + + return 0; +} + +/* Codec DAI section */ +static struct snd_soc_dai_ops sn95031_headset_dai_ops = { + .digital_mute = sn95031_pcm_hs_mute, + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_speaker_dai_ops = { + .digital_mute = sn95031_pcm_spkr_mute, + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_vib1_dai_ops = { + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_vib2_dai_ops = { + .hw_params = sn95031_pcm_hw_params, +}; + +struct snd_soc_dai_driver sn95031_dais[] = { +{ + .name = "SN95031 Headset", + .playback = { + .stream_name = "Headset", + .channels_min = 2, + .channels_max = 2, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 5, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_headset_dai_ops, +}, +{ .name = "SN95031 Speaker", + .playback = { + .stream_name = "Speaker", + .channels_min = 2, + .channels_max = 2, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_speaker_dai_ops, +}, +{ .name = "SN95031 Vibra1", + .playback = { + .stream_name = "Vibra1", + .channels_min = 1, + .channels_max = 1, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_vib1_dai_ops, +}, +{ .name = "SN95031 Vibra2", + .playback = { + .stream_name = "Vibra2", + .channels_min = 1, + .channels_max = 1, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_vib2_dai_ops, +}, +}; + +static inline void sn95031_disable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL2, 0x00); +} + +static inline void sn95031_enable_jack_btn(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, SN95031_BTNCTRL1, 0x77); + snd_soc_write(codec, SN95031_BTNCTRL2, 0x01); +} + +static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack) +{ + int micbias = sn95031_get_mic_bias(mfld_jack->codec); + + int jack_type = snd_soc_jack_get_type(mfld_jack, micbias); + + pr_debug("jack type detected = %d\n", jack_type); + if (jack_type == SND_JACK_HEADSET) + sn95031_enable_jack_btn(mfld_jack->codec); + return jack_type; +} + +void sn95031_jack_detection(struct mfld_jack_data *jack_data) +{ + unsigned int status; + unsigned int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_HEADSET; + + pr_debug("interrupt id read in sram = 0x%x\n", jack_data->intr_id); + if (jack_data->intr_id & 0x1) { + pr_debug("short_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_0; + } else if (jack_data->intr_id & 0x2) { + pr_debug("long_push detected\n"); + status = SND_JACK_HEADSET | SND_JACK_BTN_1; + } else if (jack_data->intr_id & 0x4) { + pr_debug("headset or headphones inserted\n"); + status = sn95031_get_headset_state(jack_data->mfld_jack); + } else if (jack_data->intr_id & 0x8) { + pr_debug("headset or headphones removed\n"); + status = 0; + sn95031_disable_jack_btn(jack_data->mfld_jack->codec); + } else { + pr_err("unidentified interrupt\n"); + return; + } + + snd_soc_jack_report(jack_data->mfld_jack, status, mask); + /*button pressed and released so we send explicit button release */ + if ((status & SND_JACK_BTN_0) | (status & SND_JACK_BTN_1)) + snd_soc_jack_report(jack_data->mfld_jack, + SND_JACK_HEADSET, mask); +} +EXPORT_SYMBOL_GPL(sn95031_jack_detection); + +/* codec registration */ +static int sn95031_codec_probe(struct snd_soc_codec *codec) +{ + int ret; + + pr_debug("codec_probe called\n"); + + codec->dapm.bias_level = SND_SOC_BIAS_OFF; + codec->dapm.idle_bias_off = 1; + + /* PCM interface config + * This sets the pcm rx slot conguration to max 6 slots + * for max 4 dais (2 stereo and 2 mono) + */ + snd_soc_write(codec, SN95031_PCM2RXSLOT01, 0x10); + snd_soc_write(codec, SN95031_PCM2RXSLOT23, 0x32); + snd_soc_write(codec, SN95031_PCM2RXSLOT45, 0x54); + snd_soc_write(codec, SN95031_PCM2TXSLOT01, 0x10); + snd_soc_write(codec, SN95031_PCM2TXSLOT23, 0x32); + /* pcm port setting + * This sets the pcm port to slave and clock at 19.2Mhz which + * can support 6slots, sampling rate set per stream in hw-params + */ + snd_soc_write(codec, SN95031_PCM1C1, 0x00); + snd_soc_write(codec, SN95031_PCM2C1, 0x01); + snd_soc_write(codec, SN95031_PCM2C2, 0x0A); + snd_soc_write(codec, SN95031_HSMIXER, BIT(0)|BIT(4)); + /* vendor vibra workround, the vibras are muted by + * custom register so unmute them + */ + snd_soc_write(codec, SN95031_SSR5, 0x80); + snd_soc_write(codec, SN95031_SSR6, 0x80); + snd_soc_write(codec, SN95031_VIB1C5, 0x00); + snd_soc_write(codec, SN95031_VIB2C5, 0x00); + /* configure vibras for pcm port */ + snd_soc_write(codec, SN95031_VIB1C3, 0x00); + snd_soc_write(codec, SN95031_VIB2C3, 0x00); + + /* soft mute ramp time */ + snd_soc_write(codec, SN95031_SOFTMUTE, 0x3); + /* fix the initial volume at 1dB, + * default in +9dB, + * 1dB give optimal swing on DAC, amps + */ + snd_soc_write(codec, SN95031_HSLVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_HSRVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_IHFLVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_IHFRVOLCTRL, 0x08); + /* dac mode and lineout workaround */ + snd_soc_write(codec, SN95031_SSR2, 0x10); + snd_soc_write(codec, SN95031_SSR3, 0x40); + + snd_soc_add_controls(codec, sn95031_snd_controls, + ARRAY_SIZE(sn95031_snd_controls)); + + ret = snd_soc_dapm_new_controls(&codec->dapm, sn95031_dapm_widgets, + ARRAY_SIZE(sn95031_dapm_widgets)); + if (ret) + pr_err("soc_dapm_new_control failed %d", ret); + ret = snd_soc_dapm_add_routes(&codec->dapm, sn95031_audio_map, + ARRAY_SIZE(sn95031_audio_map)); + if (ret) + pr_err("soc_dapm_add_routes failed %d", ret); + + return ret; +} + +static int sn95031_codec_remove(struct snd_soc_codec *codec) +{ + pr_debug("codec_remove called\n"); + sn95031_set_vaud_bias(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +struct snd_soc_codec_driver sn95031_codec = { + .probe = sn95031_codec_probe, + .remove = sn95031_codec_remove, + .read = sn95031_read, + .write = sn95031_write, + .set_bias_level = sn95031_set_vaud_bias, +}; + +static int __devinit sn95031_device_probe(struct platform_device *pdev) +{ + pr_debug("codec device probe called for %s\n", dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &sn95031_codec, + sn95031_dais, ARRAY_SIZE(sn95031_dais)); +} + +static int __devexit sn95031_device_remove(struct platform_device *pdev) +{ + pr_debug("codec device remove called\n"); + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver sn95031_codec_driver = { + .driver = { + .name = "sn95031", + .owner = THIS_MODULE, + }, + .probe = sn95031_device_probe, + .remove = sn95031_device_remove, +}; + +static int __init sn95031_init(void) +{ + pr_debug("driver init called\n"); + return platform_driver_register(&sn95031_codec_driver); +} +module_init(sn95031_init); + +static void __exit sn95031_exit(void) +{ + pr_debug("driver exit called\n"); + platform_driver_unregister(&sn95031_codec_driver); +} +module_exit(sn95031_exit); + +MODULE_DESCRIPTION("ASoC TI SN95031 codec driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sn95031"); diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h new file mode 100644 index 00000000000..20376d234fb --- /dev/null +++ b/sound/soc/codecs/sn95031.h @@ -0,0 +1,132 @@ +/* + * sn95031.h - TI sn95031 Codec driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#ifndef _SN95031_H +#define _SN95031_H + +/*register map*/ +#define SN95031_VAUD 0xDB +#define SN95031_VHSP 0xDC +#define SN95031_VHSN 0xDD +#define SN95031_VIHF 0xC9 + +#define SN95031_AUDPLLCTRL 0x240 +#define SN95031_DMICBUF0123 0x241 +#define SN95031_DMICBUF45 0x242 +#define SN95031_DMICGPO 0x244 +#define SN95031_DMICMUX 0x245 +#define SN95031_DMICLK 0x246 +#define SN95031_MICBIAS 0x247 +#define SN95031_ADCCONFIG 0x248 +#define SN95031_MICAMP1 0x249 +#define SN95031_MICAMP2 0x24A +#define SN95031_NOISEMUX 0x24B +#define SN95031_AUDIOMUX12 0x24C +#define SN95031_AUDIOMUX34 0x24D +#define SN95031_AUDIOSINC 0x24E +#define SN95031_AUDIOTXEN 0x24F +#define SN95031_HSEPRXCTRL 0x250 +#define SN95031_IHFRXCTRL 0x251 +#define SN95031_HSMIXER 0x256 +#define SN95031_DACCONFIG 0x257 +#define SN95031_SOFTMUTE 0x258 +#define SN95031_HSLVOLCTRL 0x259 +#define SN95031_HSRVOLCTRL 0x25A +#define SN95031_IHFLVOLCTRL 0x25B +#define SN95031_IHFRVOLCTRL 0x25C +#define SN95031_DRIVEREN 0x25D +#define SN95031_LOCTL 0x25E +#define SN95031_VIB1C1 0x25F +#define SN95031_VIB1C2 0x260 +#define SN95031_VIB1C3 0x261 +#define SN95031_VIB1SPIPCM1 0x262 +#define SN95031_VIB1SPIPCM2 0x263 +#define SN95031_VIB1C5 0x264 +#define SN95031_VIB2C1 0x265 +#define SN95031_VIB2C2 0x266 +#define SN95031_VIB2C3 0x267 +#define SN95031_VIB2SPIPCM1 0x268 +#define SN95031_VIB2SPIPCM2 0x269 +#define SN95031_VIB2C5 0x26A +#define SN95031_BTNCTRL1 0x26B +#define SN95031_BTNCTRL2 0x26C +#define SN95031_PCM1TXSLOT01 0x26D +#define SN95031_PCM1TXSLOT23 0x26E +#define SN95031_PCM1TXSLOT45 0x26F +#define SN95031_PCM1RXSLOT0_3 0x270 +#define SN95031_PCM1RXSLOT45 0x271 +#define SN95031_PCM2TXSLOT01 0x272 +#define SN95031_PCM2TXSLOT23 0x273 +#define SN95031_PCM2TXSLOT45 0x274 +#define SN95031_PCM2RXSLOT01 0x275 +#define SN95031_PCM2RXSLOT23 0x276 +#define SN95031_PCM2RXSLOT45 0x277 +#define SN95031_PCM1C1 0x278 +#define SN95031_PCM1C2 0x279 +#define SN95031_PCM1C3 0x27A +#define SN95031_PCM2C1 0x27B +#define SN95031_PCM2C2 0x27C +/*end codec register defn*/ + +/*vendor defn these are not part of avp*/ +#define SN95031_SSR2 0x381 +#define SN95031_SSR3 0x382 +#define SN95031_SSR5 0x384 +#define SN95031_SSR6 0x385 + +/* ADC registers */ + +#define SN95031_ADC1CNTL1 0x1C0 +#define SN95031_ADC_ENBL 0x10 +#define SN95031_ADC_START 0x08 +#define SN95031_ADC1CNTL3 0x1C2 +#define SN95031_ADCTHERM_ENBL 0x04 +#define SN95031_ADCRRDATA_ENBL 0x05 +#define SN95031_STOPBIT_MASK 16 +#define SN95031_ADCTHERM_MASK 4 +#define SN95031_ADC_CHANLS_MAX 15 /* Number of ADC channels */ +#define SN95031_ADC_LOOP_MAX (SN95031_ADC_CHANLS_MAX - 1) +#define SN95031_ADC_NO_LOOP 0x07 +#define SN95031_AUDIO_GPIO_CTRL 0x070 + +/* ADC channel code values */ +#define SN95031_AUDIO_DETECT_CODE 0x06 + +/* ADC base addresses */ +#define SN95031_ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ +#define SN95031_ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ +/* multipier to convert to mV */ +#define SN95031_ADC_ONE_LSB_MULTIPLIER 2346 + + +struct mfld_jack_data { + int intr_id; + int micbias_vol; + struct snd_soc_jack *mfld_jack; +}; + +extern void sn95031_jack_detection(struct mfld_jack_data *jack_data); + +#endif diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c new file mode 100644 index 00000000000..e93b9d1ae1d --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -0,0 +1,794 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin <javier.martin@vista-silicon.com> + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/cdev.h> +#include <linux/slab.h> + +#include <sound/tlv320aic32x4.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "tlv320aic32x4.h" + +struct aic32x4_rate_divs { + u32 mclk; + u32 rate; + u8 p_val; + u8 pll_j; + u16 pll_d; + u16 dosr; + u8 ndac; + u8 mdac; + u8 aosr; + u8 nadc; + u8 madc; + u8 blck_N; +}; + +struct aic32x4_priv { + u32 sysclk; + s32 master; + u8 page_no; + void *control_data; + u32 power_cfg; + u32 micpga_routing; + bool swapdacs; +}; + +/* 0dB min, 1dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_step_1, 0, 100, 0); +/* 0dB min, 0.5dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0); + +static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_TLV("PCM Playback Volume", AIC32X4_LDACVOL, + AIC32X4_RDACVOL, 0, 0x30, 0, tlv_step_0_5), + SOC_DOUBLE_R_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 0, 0x1D, 0, tlv_step_1), + SOC_DOUBLE_R_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 0, 0x1D, 0, tlv_step_1), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_TLV("ADC Level Volume", AIC32X4_LADCVOL, + AIC32X4_RADCVOL, 0, 0x28, 0, tlv_step_0_5), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), + SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, + 4, 0x07, 0), + SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, + 0, 0x03, 0), + SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, + 6, 0x03, 0), + SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, + 1, 0x1F, 0), + SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, + 0, 0x7F, 0), + SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, + 0, 0x1F, 0), + SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, + 0, 0x0F, 0), +}; + +static const struct aic32x4_rate_divs aic32x4_divs[] = { + /* 8k rate */ + {AIC32X4_FREQ_12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24}, + {AIC32X4_FREQ_24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24}, + {AIC32X4_FREQ_25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24}, + /* 11.025k rate */ + {AIC32X4_FREQ_12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16}, + {AIC32X4_FREQ_24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16}, + /* 16k rate */ + {AIC32X4_FREQ_12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12}, + {AIC32X4_FREQ_24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12}, + {AIC32X4_FREQ_25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12}, + /* 22.05k rate */ + {AIC32X4_FREQ_12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8}, + {AIC32X4_FREQ_24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8}, + {AIC32X4_FREQ_25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8}, + /* 32k rate */ + {AIC32X4_FREQ_12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6}, + {AIC32X4_FREQ_24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6}, + /* 44.1k rate */ + {AIC32X4_FREQ_12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4}, + {AIC32X4_FREQ_24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4}, + {AIC32X4_FREQ_25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4}, + /* 48k rate */ + {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, + {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, + {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4} +}; + +static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new hpr_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lol_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lor_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0), +}; + +static const struct snd_kcontrol_new left_input_mixer_controls[] = { + SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0), + SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0), + SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0), +}; + +static const struct snd_kcontrol_new right_input_mixer_controls[] = { + SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0), + SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0), + SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0), + SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0, + &hpl_output_mixer_controls[0], + ARRAY_SIZE(hpl_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0, + &lol_output_mixer_controls[0], + ARRAY_SIZE(lol_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0), + SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0, + &hpr_output_mixer_controls[0], + ARRAY_SIZE(hpr_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0, + &lor_output_mixer_controls[0], + ARRAY_SIZE(lor_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, + &left_input_mixer_controls[0], + ARRAY_SIZE(left_input_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, + &right_input_mixer_controls[0], + ARRAY_SIZE(right_input_mixer_controls)), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOL"), + SND_SOC_DAPM_OUTPUT("LOR"), + SND_SOC_DAPM_INPUT("IN1_L"), + SND_SOC_DAPM_INPUT("IN1_R"), + SND_SOC_DAPM_INPUT("IN2_L"), + SND_SOC_DAPM_INPUT("IN2_R"), + SND_SOC_DAPM_INPUT("IN3_L"), + SND_SOC_DAPM_INPUT("IN3_R"), +}; + +static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + /* Left Output */ + {"HPL Output Mixer", "L_DAC Switch", "Left DAC"}, + {"HPL Output Mixer", "IN1_L Switch", "IN1_L"}, + + {"HPL Power", NULL, "HPL Output Mixer"}, + {"HPL", NULL, "HPL Power"}, + + {"LOL Output Mixer", "L_DAC Switch", "Left DAC"}, + + {"LOL Power", NULL, "LOL Output Mixer"}, + {"LOL", NULL, "LOL Power"}, + + /* Right Output */ + {"HPR Output Mixer", "R_DAC Switch", "Right DAC"}, + {"HPR Output Mixer", "IN1_R Switch", "IN1_R"}, + + {"HPR Power", NULL, "HPR Output Mixer"}, + {"HPR", NULL, "HPR Power"}, + + {"LOR Output Mixer", "R_DAC Switch", "Right DAC"}, + + {"LOR Power", NULL, "LOR Output Mixer"}, + {"LOR", NULL, "LOR Power"}, + + /* Left input */ + {"Left Input Mixer", "IN1_L P Switch", "IN1_L"}, + {"Left Input Mixer", "IN2_L P Switch", "IN2_L"}, + {"Left Input Mixer", "IN3_L P Switch", "IN3_L"}, + + {"Left ADC", NULL, "Left Input Mixer"}, + + /* Right Input */ + {"Right Input Mixer", "IN1_R P Switch", "IN1_R"}, + {"Right Input Mixer", "IN2_R P Switch", "IN2_R"}, + {"Right Input Mixer", "IN3_R P Switch", "IN3_R"}, + + {"Right ADC", NULL, "Right Input Mixer"}, +}; + +static inline int aic32x4_change_page(struct snd_soc_codec *codec, + unsigned int new_page) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + int ret; + + data[0] = 0x00; + data[1] = new_page & 0xff; + + ret = codec->hw_write(codec->control_data, data, 2); + if (ret == 2) { + aic32x4->page_no = new_page; + return 0; + } else { + return ret; + } +} + +static int aic32x4_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + unsigned int page = reg / 128; + unsigned int fixed_reg = reg % 128; + u8 data[2]; + int ret; + + /* A write to AIC32X4_PSEL is really a non-explicit page change */ + if (reg == AIC32X4_PSEL) + return aic32x4_change_page(codec, val); + + if (aic32x4->page_no != page) { + ret = aic32x4_change_page(codec, page); + if (ret != 0) + return ret; + } + + data[0] = fixed_reg & 0xff; + data[1] = val & 0xff; + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static unsigned int aic32x4_read(struct snd_soc_codec *codec, unsigned int reg) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + unsigned int page = reg / 128; + unsigned int fixed_reg = reg % 128; + int ret; + + if (aic32x4->page_no != page) { + ret = aic32x4_change_page(codec, page); + if (ret != 0) + return ret; + } + return i2c_smbus_read_byte_data(codec->control_data, fixed_reg & 0xff); +} + +static inline int aic32x4_get_divs(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) { + if ((aic32x4_divs[i].rate == rate) + && (aic32x4_divs[i].mclk == mclk)) { + return i; + } + } + printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n"); + return -EINVAL; +} + +static int aic32x4_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(&codec->dapm, aic32x4_dapm_widgets, + ARRAY_SIZE(aic32x4_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, aic32x4_dapm_routes, + ARRAY_SIZE(aic32x4_dapm_routes)); + + snd_soc_dapm_new_widgets(&codec->dapm); + return 0; +} + +static int aic32x4_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 aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + + switch (freq) { + case AIC32X4_FREQ_12000000: + case AIC32X4_FREQ_24000000: + case AIC32X4_FREQ_25000000: + aic32x4->sysclk = freq; + return 0; + } + printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n"); + return -EINVAL; +} + +static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg_1; + u8 iface_reg_2; + u8 iface_reg_3; + + iface_reg_1 = snd_soc_read(codec, AIC32X4_IFACE1); + iface_reg_1 = iface_reg_1 & ~(3 << 6 | 3 << 2); + iface_reg_2 = snd_soc_read(codec, AIC32X4_IFACE2); + iface_reg_2 = 0; + iface_reg_3 = snd_soc_read(codec, AIC32X4_IFACE3); + iface_reg_3 = iface_reg_3 & ~(1 << 3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aic32x4->master = 1; + iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aic32x4->master = 0; + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT); + iface_reg_3 |= (1 << 3); /* invert bit clock */ + iface_reg_2 = 0x01; /* add offset 1 */ + break; + case SND_SOC_DAIFMT_DSP_B: + iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT); + iface_reg_3 |= (1 << 3); /* invert bit clock */ + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg_1 |= + (AIC32X4_RIGHT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg_1 |= + (AIC32X4_LEFT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT); + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); + return -EINVAL; + } + + snd_soc_write(codec, AIC32X4_IFACE1, iface_reg_1); + snd_soc_write(codec, AIC32X4_IFACE2, iface_reg_2); + snd_soc_write(codec, AIC32X4_IFACE3, iface_reg_3); + return 0; +} + +static int aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 data; + int i; + + i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params)); + if (i < 0) { + printk(KERN_ERR "aic32x4: sampling rate not supported\n"); + return i; + } + + /* Use PLL as CODEC_CLKIN and DAC_MOD_CLK as BDIV_CLKIN */ + snd_soc_write(codec, AIC32X4_CLKMUX, AIC32X4_PLLCLKIN); + snd_soc_write(codec, AIC32X4_IFACE3, AIC32X4_DACMOD2BCLK); + + /* We will fix R value to 1 and will make P & J=K.D as varialble */ + data = snd_soc_read(codec, AIC32X4_PLLPR); + data &= ~(7 << 4); + snd_soc_write(codec, AIC32X4_PLLPR, + (data | (aic32x4_divs[i].p_val << 4) | 0x01)); + + snd_soc_write(codec, AIC32X4_PLLJ, aic32x4_divs[i].pll_j); + + snd_soc_write(codec, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8)); + snd_soc_write(codec, AIC32X4_PLLDLSB, + (aic32x4_divs[i].pll_d & 0xff)); + + /* NDAC divider value */ + data = snd_soc_read(codec, AIC32X4_NDAC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_NDAC, data | aic32x4_divs[i].ndac); + + /* MDAC divider value */ + data = snd_soc_read(codec, AIC32X4_MDAC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_MDAC, data | aic32x4_divs[i].mdac); + + /* DOSR MSB & LSB values */ + snd_soc_write(codec, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8); + snd_soc_write(codec, AIC32X4_DOSRLSB, + (aic32x4_divs[i].dosr & 0xff)); + + /* NADC divider value */ + data = snd_soc_read(codec, AIC32X4_NADC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_NADC, data | aic32x4_divs[i].nadc); + + /* MADC divider value */ + data = snd_soc_read(codec, AIC32X4_MADC); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_MADC, data | aic32x4_divs[i].madc); + + /* AOSR value */ + snd_soc_write(codec, AIC32X4_AOSR, aic32x4_divs[i].aosr); + + /* BCLK N divider */ + data = snd_soc_read(codec, AIC32X4_BCLKN); + data &= ~(0x7f); + snd_soc_write(codec, AIC32X4_BCLKN, data | aic32x4_divs[i].blck_N); + + data = snd_soc_read(codec, AIC32X4_IFACE1); + data = data & ~(3 << 4); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_DOSRMSB_SHIFT); + break; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_DOSRMSB_SHIFT); + break; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_DOSRMSB_SHIFT); + break; + } + snd_soc_write(codec, AIC32X4_IFACE1, data); + + return 0; +} + +static int aic32x4_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 dac_reg; + + dac_reg = snd_soc_read(codec, AIC32X4_DACMUTE) & ~AIC32X4_MUTEON; + if (mute) + snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg | AIC32X4_MUTEON); + else + snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg); + return 0; +} + +static int aic32x4_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u8 value; + + switch (level) { + case SND_SOC_BIAS_ON: + if (aic32x4->master) { + /* Switch on PLL */ + value = snd_soc_read(codec, AIC32X4_PLLPR); + snd_soc_write(codec, AIC32X4_PLLPR, + (value | AIC32X4_PLLEN)); + + /* Switch on NDAC Divider */ + value = snd_soc_read(codec, AIC32X4_NDAC); + snd_soc_write(codec, AIC32X4_NDAC, + value | AIC32X4_NDACEN); + + /* Switch on MDAC Divider */ + value = snd_soc_read(codec, AIC32X4_MDAC); + snd_soc_write(codec, AIC32X4_MDAC, + value | AIC32X4_MDACEN); + + /* Switch on NADC Divider */ + value = snd_soc_read(codec, AIC32X4_NADC); + snd_soc_write(codec, AIC32X4_NADC, + value | AIC32X4_MDACEN); + + /* Switch on MADC Divider */ + value = snd_soc_read(codec, AIC32X4_MADC); + snd_soc_write(codec, AIC32X4_MADC, + value | AIC32X4_MDACEN); + + /* Switch on BCLK_N Divider */ + value = snd_soc_read(codec, AIC32X4_BCLKN); + snd_soc_write(codec, AIC32X4_BCLKN, + value | AIC32X4_BCLKEN); + } + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (aic32x4->master) { + /* Switch off PLL */ + value = snd_soc_read(codec, AIC32X4_PLLPR); + snd_soc_write(codec, AIC32X4_PLLPR, + (value & ~AIC32X4_PLLEN)); + + /* Switch off NDAC Divider */ + value = snd_soc_read(codec, AIC32X4_NDAC); + snd_soc_write(codec, AIC32X4_NDAC, + value & ~AIC32X4_NDACEN); + + /* Switch off MDAC Divider */ + value = snd_soc_read(codec, AIC32X4_MDAC); + snd_soc_write(codec, AIC32X4_MDAC, + value & ~AIC32X4_MDACEN); + + /* Switch off NADC Divider */ + value = snd_soc_read(codec, AIC32X4_NADC); + snd_soc_write(codec, AIC32X4_NADC, + value & ~AIC32X4_NDACEN); + + /* Switch off MADC Divider */ + value = snd_soc_read(codec, AIC32X4_MADC); + snd_soc_write(codec, AIC32X4_MADC, + value & ~AIC32X4_MDACEN); + value = snd_soc_read(codec, AIC32X4_BCLKN); + + /* Switch off BCLK_N Divider */ + snd_soc_write(codec, AIC32X4_BCLKN, + value & ~AIC32X4_BCLKEN); + } + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000 +#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops aic32x4_ops = { + .hw_params = aic32x4_hw_params, + .digital_mute = aic32x4_mute, + .set_fmt = aic32x4_set_dai_fmt, + .set_sysclk = aic32x4_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver aic32x4_dai = { + .name = "tlv320aic32x4-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .ops = &aic32x4_ops, + .symmetric_rates = 1, +}; + +static int aic32x4_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aic32x4_resume(struct snd_soc_codec *codec) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int aic32x4_probe(struct snd_soc_codec *codec) +{ + struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); + u32 tmp_reg; + + codec->hw_write = (hw_write_t) i2c_master_send; + codec->control_data = aic32x4->control_data; + + snd_soc_write(codec, AIC32X4_RESET, 0x01); + + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_write(codec, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN | + AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) { + snd_soc_write(codec, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) { + snd_soc_write(codec, AIC32X4_LDOCTL, AIC32X4_LDOCTLEN); + } + tmp_reg = snd_soc_read(codec, AIC32X4_CMMODE); + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) { + tmp_reg |= AIC32X4_LDOIN_18_36; + } + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) { + tmp_reg |= AIC32X4_LDOIN2HP; + } + snd_soc_write(codec, AIC32X4_CMMODE, tmp_reg); + + /* Do DACs need to be swapped? */ + if (aic32x4->swapdacs) { + snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2RCHN | AIC32X4_RDAC2LCHN); + } else { + snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN); + } + + /* Mic PGA routing */ + if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) { + snd_soc_write(codec, AIC32X4_LMICPGANIN, AIC32X4_LMICPGANIN_IN2R_10K); + } + if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) { + snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_IN1L_10K); + } + + aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + snd_soc_add_controls(codec, aic32x4_snd_controls, + ARRAY_SIZE(aic32x4_snd_controls)); + aic32x4_add_widgets(codec); + + return 0; +} + +static int aic32x4_remove(struct snd_soc_codec *codec) +{ + aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { + .read = aic32x4_read, + .write = aic32x4_write, + .probe = aic32x4_probe, + .remove = aic32x4_remove, + .suspend = aic32x4_suspend, + .resume = aic32x4_resume, + .set_bias_level = aic32x4_set_bias_level, +}; + +static __devinit int aic32x4_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct aic32x4_pdata *pdata = i2c->dev.platform_data; + struct aic32x4_priv *aic32x4; + int ret; + + aic32x4 = kzalloc(sizeof(struct aic32x4_priv), GFP_KERNEL); + if (aic32x4 == NULL) + return -ENOMEM; + + aic32x4->control_data = i2c; + i2c_set_clientdata(i2c, aic32x4); + + if (pdata) { + aic32x4->power_cfg = pdata->power_cfg; + aic32x4->swapdacs = pdata->swapdacs; + aic32x4->micpga_routing = pdata->micpga_routing; + } else { + aic32x4->power_cfg = 0; + aic32x4->swapdacs = false; + aic32x4->micpga_routing = 0; + } + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_aic32x4, &aic32x4_dai, 1); + if (ret < 0) + kfree(aic32x4); + return ret; +} + +static __devexit int aic32x4_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id aic32x4_i2c_id[] = { + { "tlv320aic32x4", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); + +static struct i2c_driver aic32x4_i2c_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + }, + .probe = aic32x4_i2c_probe, + .remove = __devexit_p(aic32x4_i2c_remove), + .id_table = aic32x4_i2c_id, +}; + +static int __init aic32x4_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&aic32x4_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register aic32x4 I2C driver: %d\n", + ret); + } + return ret; +} +module_init(aic32x4_modinit); + +static void __exit aic32x4_exit(void) +{ + i2c_del_driver(&aic32x4_i2c_driver); +} +module_exit(aic32x4_exit); + +MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h new file mode 100644 index 00000000000..aae2b244039 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -0,0 +1,143 @@ +/* + * tlv320aic32x4.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#ifndef _TLV320AIC32X4_H +#define _TLV320AIC32X4_H + +/* tlv320aic32x4 register space (in decimal to match datasheet) */ + +#define AIC32X4_PAGE1 128 + +#define AIC32X4_PSEL 0 +#define AIC32X4_RESET 1 +#define AIC32X4_CLKMUX 4 +#define AIC32X4_PLLPR 5 +#define AIC32X4_PLLJ 6 +#define AIC32X4_PLLDMSB 7 +#define AIC32X4_PLLDLSB 8 +#define AIC32X4_NDAC 11 +#define AIC32X4_MDAC 12 +#define AIC32X4_DOSRMSB 13 +#define AIC32X4_DOSRLSB 14 +#define AIC32X4_NADC 18 +#define AIC32X4_MADC 19 +#define AIC32X4_AOSR 20 +#define AIC32X4_CLKMUX2 25 +#define AIC32X4_CLKOUTM 26 +#define AIC32X4_IFACE1 27 +#define AIC32X4_IFACE2 28 +#define AIC32X4_IFACE3 29 +#define AIC32X4_BCLKN 30 +#define AIC32X4_IFACE4 31 +#define AIC32X4_IFACE5 32 +#define AIC32X4_IFACE6 33 +#define AIC32X4_DOUTCTL 53 +#define AIC32X4_DINCTL 54 +#define AIC32X4_DACSPB 60 +#define AIC32X4_ADCSPB 61 +#define AIC32X4_DACSETUP 63 +#define AIC32X4_DACMUTE 64 +#define AIC32X4_LDACVOL 65 +#define AIC32X4_RDACVOL 66 +#define AIC32X4_ADCSETUP 81 +#define AIC32X4_ADCFGA 82 +#define AIC32X4_LADCVOL 83 +#define AIC32X4_RADCVOL 84 +#define AIC32X4_LAGC1 86 +#define AIC32X4_LAGC2 87 +#define AIC32X4_LAGC3 88 +#define AIC32X4_LAGC4 89 +#define AIC32X4_LAGC5 90 +#define AIC32X4_LAGC6 91 +#define AIC32X4_LAGC7 92 +#define AIC32X4_RAGC1 94 +#define AIC32X4_RAGC2 95 +#define AIC32X4_RAGC3 96 +#define AIC32X4_RAGC4 97 +#define AIC32X4_RAGC5 98 +#define AIC32X4_RAGC6 99 +#define AIC32X4_RAGC7 100 +#define AIC32X4_PWRCFG (AIC32X4_PAGE1 + 1) +#define AIC32X4_LDOCTL (AIC32X4_PAGE1 + 2) +#define AIC32X4_OUTPWRCTL (AIC32X4_PAGE1 + 9) +#define AIC32X4_CMMODE (AIC32X4_PAGE1 + 10) +#define AIC32X4_HPLROUTE (AIC32X4_PAGE1 + 12) +#define AIC32X4_HPRROUTE (AIC32X4_PAGE1 + 13) +#define AIC32X4_LOLROUTE (AIC32X4_PAGE1 + 14) +#define AIC32X4_LORROUTE (AIC32X4_PAGE1 + 15) +#define AIC32X4_HPLGAIN (AIC32X4_PAGE1 + 16) +#define AIC32X4_HPRGAIN (AIC32X4_PAGE1 + 17) +#define AIC32X4_LOLGAIN (AIC32X4_PAGE1 + 18) +#define AIC32X4_LORGAIN (AIC32X4_PAGE1 + 19) +#define AIC32X4_HEADSTART (AIC32X4_PAGE1 + 20) +#define AIC32X4_MICBIAS (AIC32X4_PAGE1 + 51) +#define AIC32X4_LMICPGAPIN (AIC32X4_PAGE1 + 52) +#define AIC32X4_LMICPGANIN (AIC32X4_PAGE1 + 54) +#define AIC32X4_RMICPGAPIN (AIC32X4_PAGE1 + 55) +#define AIC32X4_RMICPGANIN (AIC32X4_PAGE1 + 57) +#define AIC32X4_FLOATINGINPUT (AIC32X4_PAGE1 + 58) +#define AIC32X4_LMICPGAVOL (AIC32X4_PAGE1 + 59) +#define AIC32X4_RMICPGAVOL (AIC32X4_PAGE1 + 60) + +#define AIC32X4_FREQ_12000000 12000000 +#define AIC32X4_FREQ_24000000 24000000 +#define AIC32X4_FREQ_25000000 25000000 + +#define AIC32X4_WORD_LEN_16BITS 0x00 +#define AIC32X4_WORD_LEN_20BITS 0x01 +#define AIC32X4_WORD_LEN_24BITS 0x02 +#define AIC32X4_WORD_LEN_32BITS 0x03 + +#define AIC32X4_I2S_MODE 0x00 +#define AIC32X4_DSP_MODE 0x01 +#define AIC32X4_RIGHT_JUSTIFIED_MODE 0x02 +#define AIC32X4_LEFT_JUSTIFIED_MODE 0x03 + +#define AIC32X4_AVDDWEAKDISABLE 0x08 +#define AIC32X4_LDOCTLEN 0x01 + +#define AIC32X4_LDOIN_18_36 0x01 +#define AIC32X4_LDOIN2HP 0x02 + +#define AIC32X4_DACSPBLOCK_MASK 0x1f +#define AIC32X4_ADCSPBLOCK_MASK 0x1f + +#define AIC32X4_PLLJ_SHIFT 6 +#define AIC32X4_DOSRMSB_SHIFT 4 + +#define AIC32X4_PLLCLKIN 0x03 + +#define AIC32X4_MICBIAS_LDOIN 0x08 +#define AIC32X4_MICBIAS_2075V 0x60 + +#define AIC32X4_LMICPGANIN_IN2R_10K 0x10 +#define AIC32X4_RMICPGANIN_IN1L_10K 0x10 + +#define AIC32X4_LMICPGAVOL_NOGAIN 0x80 +#define AIC32X4_RMICPGAVOL_NOGAIN 0x80 + +#define AIC32X4_BCLKMASTER 0x08 +#define AIC32X4_WCLKMASTER 0x04 +#define AIC32X4_PLLEN (0x01 << 7) +#define AIC32X4_NDACEN (0x01 << 7) +#define AIC32X4_MDACEN (0x01 << 7) +#define AIC32X4_NADCEN (0x01 << 7) +#define AIC32X4_MADCEN (0x01 << 7) +#define AIC32X4_BCLKEN (0x01 << 7) +#define AIC32X4_DACEN (0x03 << 6) +#define AIC32X4_RDAC2LCHN (0x02 << 2) +#define AIC32X4_LDAC2RCHN (0x02 << 4) +#define AIC32X4_LDAC2LCHN (0x01 << 4) +#define AIC32X4_RDAC2RCHN (0x01 << 2) + +#define AIC32X4_SSTEP2WCLK 0x01 +#define AIC32X4_MUTEON 0x0C +#define AIC32X4_DACMOD2BCLK 0x01 + +#endif /* _TLV320AIC32X4_H */ diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 71d7be8ac48..00b6d87e7bd 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1615,6 +1615,7 @@ static const struct i2c_device_id tlv320dac33_i2c_id[] = { }, { }, }; +MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id); static struct i2c_driver tlv320dac33_i2c_driver = { .driver = { diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 4bbf1b15a49..482fcdb59bf 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -724,8 +724,8 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, return 0; } -void twl6040_hs_jack_report(struct snd_soc_codec *codec, - struct snd_soc_jack *jack, int report) +static void twl6040_hs_jack_report(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int report) { struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int status; diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 80ddf4fd23d..a3b9cbb20ee 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -836,24 +836,25 @@ static void wm2000_i2c_shutdown(struct i2c_client *i2c) } #ifdef CONFIG_PM -static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg) +static int wm2000_i2c_suspend(struct device *dev) { + struct i2c_client *i2c = to_i2c_client(dev); struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); return wm2000_anc_transition(wm2000, ANC_OFF); } -static int wm2000_i2c_resume(struct i2c_client *i2c) +static int wm2000_i2c_resume(struct device *dev) { + struct i2c_client *i2c = to_i2c_client(dev); struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); return wm2000_anc_set_mode(wm2000); } -#else -#define wm2000_i2c_suspend NULL -#define wm2000_i2c_resume NULL #endif +static SIMPLE_DEV_PM_OPS(wm2000_pm, wm2000_i2c_suspend, wm2000_i2c_resume); + static const struct i2c_device_id wm2000_i2c_id[] = { { "wm2000", 0 }, { } @@ -864,11 +865,10 @@ static struct i2c_driver wm2000_i2c_driver = { .driver = { .name = "wm2000", .owner = THIS_MODULE, + .pm = &wm2000_pm, }, .probe = wm2000_i2c_probe, .remove = __devexit_p(wm2000_i2c_remove), - .suspend = wm2000_i2c_suspend, - .resume = wm2000_i2c_resume, .shutdown = wm2000_i2c_shutdown, .id_table = wm2000_i2c_id, }; diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 5eb2f501ce3..4fd4d8dca0f 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -58,7 +58,7 @@ static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = { 0x0000, /* R8 - ZERO_DETECT */ }; -static int wm8523_volatile_register(unsigned int reg) +static int wm8523_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8523_DEVICE_ID: @@ -414,7 +414,6 @@ static int wm8523_resume(struct snd_soc_codec *codec) static int wm8523_probe(struct snd_soc_codec *codec) { struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret, i; codec->hw_write = (hw_write_t)i2c_master_send; @@ -471,8 +470,9 @@ static int wm8523_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU; - reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC; + snd_soc_update_bits(codec, WM8523_DAC_GAINR, + WM8523_DACR_VU, WM8523_DACR_VU); + snd_soc_update_bits(codec, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC); wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 494f2d31d75..25af901fe81 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -421,7 +421,6 @@ static int wm8741_resume(struct snd_soc_codec *codec) static int wm8741_probe(struct snd_soc_codec *codec) { struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret = 0; ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type); @@ -437,10 +436,14 @@ static int wm8741_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU */ - reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL; - reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM; - reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL; - reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM; + snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); snd_soc_add_controls(codec, wm8741_snd_controls, ARRAY_SIZE(wm8741_snd_controls)); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 79b02ae125c..3f09deea8d9 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -55,8 +55,10 @@ static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); -static void wm8753_set_dai_mode(struct snd_soc_codec *codec, - struct snd_soc_dai *dai, unsigned int hifi); +static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt); +static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt); /* * wm8753 register cache @@ -87,6 +89,10 @@ struct wm8753_priv { enum snd_soc_control_type control_type; unsigned int sysclk; unsigned int pcmclk; + + unsigned int voice_fmt; + unsigned int hifi_fmt; + int dai_func; }; @@ -170,9 +176,9 @@ static int wm8753_get_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int mode = snd_soc_read(codec, WM8753_IOCTL); + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); - ucontrol->value.integer.value[0] = (mode & 0xc) >> 2; + ucontrol->value.integer.value[0] = wm8753->dai_func; return 0; } @@ -180,16 +186,26 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int mode = snd_soc_read(codec, WM8753_IOCTL); struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + u16 ioctl; + + if (codec->active) + return -EBUSY; + + ioctl = snd_soc_read(codec, WM8753_IOCTL); + + wm8753->dai_func = ucontrol->value.integer.value[0]; + + if (((ioctl >> 2) & 0x3) == wm8753->dai_func) + return 1; + + ioctl = (ioctl & 0x1f3) | (wm8753->dai_func << 2); + snd_soc_write(codec, WM8753_IOCTL, ioctl); - if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0]) - return 0; - mode &= 0xfff3; - mode |= (ucontrol->value.integer.value[0] << 2); + wm8753_hifi_write_dai_fmt(codec, wm8753->hifi_fmt); + wm8753_voice_write_dai_fmt(codec, wm8753->voice_fmt); - wm8753->dai_func = ucontrol->value.integer.value[0]; return 1; } @@ -828,10 +844,9 @@ static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai, /* * Set's ADC and Voice DAC format. */ -static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01ec; /* interface format */ @@ -858,13 +873,6 @@ static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static int wm8753_pcm_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - wm8753_set_dai_mode(dai->codec, dai, 0); - return 0; -} - /* * Set PCM DAI bit size and sample rate. */ @@ -905,10 +913,9 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, /* * Set's PCM dai fmt and BCLK. */ -static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 voice, ioctl; voice = snd_soc_read(codec, WM8753_PCM) & 0x011f; @@ -999,10 +1006,9 @@ static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, /* * Set's HiFi DAC format. */ -static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01e0; /* interface format */ @@ -1032,10 +1038,9 @@ static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, /* * Set's I2S DAI format. */ -static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 ioctl, hifi; hifi = snd_soc_read(codec, WM8753_HIFI) & 0x011f; @@ -1098,13 +1103,6 @@ static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static int wm8753_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - wm8753_set_dai_mode(dai->codec, dai, 1); - return 0; -} - /* * Set PCM DAI bit size and sample rate. */ @@ -1147,61 +1145,117 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as pcmclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock); - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_pcm_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } -static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_hdac_set_dai_fmt(codec, fmt); } -static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as pcmclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock); - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } -static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec *codec, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; u16 clock; /* set clk source as mclk */ clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb; snd_soc_write(codec, WM8753_CLOCK, clock | 0x4); - if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + if (wm8753_hdac_set_dai_fmt(codec, fmt) < 0) return -EINVAL; - if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) - return -EINVAL; - return wm8753_i2s_set_dai_fmt(codec_dai, fmt); + return wm8753_vdac_adc_set_dai_fmt(codec, fmt); } +static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (wm8753->dai_func) { + case 0: + ret = wm8753_mode1h_set_dai_fmt(codec, fmt); + break; + case 1: + ret = wm8753_mode2_set_dai_fmt(codec, fmt); + break; + case 2: + case 3: + ret = wm8753_mode3_4_set_dai_fmt(codec, fmt); + break; + default: + break; + } + if (ret) + return ret; + + return wm8753_i2s_set_dai_fmt(codec, fmt); +} + +static int wm8753_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + + wm8753->hifi_fmt = fmt; + + return wm8753_hifi_write_dai_fmt(codec, fmt); +}; + +static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + if (wm8753->dai_func != 0) + return 0; + + ret = wm8753_mode1v_set_dai_fmt(codec, fmt); + if (ret) + return ret; + ret = wm8753_pcm_set_dai_fmt(codec, fmt); + if (ret) + return ret; + + return 0; +}; + +static int wm8753_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); + + wm8753->voice_fmt = fmt; + + return wm8753_voice_write_dai_fmt(codec, fmt); +}; + static int wm8753_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; @@ -1268,57 +1322,25 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec, * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode1 = { - .startup = wm8753_i2s_startup, +static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .hw_params = wm8753_i2s_hw_params, .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1h_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode1 = { - .startup = wm8753_pcm_startup, - .hw_params = wm8753_pcm_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1v_set_dai_fmt, + .set_fmt = wm8753_hifi_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, }; -static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode2 = { - .startup = wm8753_pcm_startup, +static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { .hw_params = wm8753_pcm_hw_params, .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode2_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode3 = { - .startup = wm8753_i2s_startup, - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, -}; - -static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode4 = { - .startup = wm8753_i2s_startup, - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_fmt = wm8753_voice_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, }; -static struct snd_soc_dai_driver wm8753_all_dai[] = { +static struct snd_soc_dai_driver wm8753_dai[] = { /* DAI HiFi mode 1 */ { .name = "wm8753-hifi", .playback = { @@ -1326,14 +1348,16 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS}, + .formats = WM8753_FORMATS + }, .capture = { /* dummy for fast DAI switching */ .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS}, - .ops = &wm8753_dai_ops_hifi_mode1, + .formats = WM8753_FORMATS + }, + .ops = &wm8753_dai_ops_hifi_mode, }, /* DAI Voice mode 1 */ { .name = "wm8753-voice", @@ -1342,97 +1366,19 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_voice_mode1, -}, -/* DAI HiFi mode 2 - dummy */ -{ .name = "wm8753-hifi", -}, -/* DAI Voice mode 2 */ -{ .name = "wm8753-voice", - .playback = { - .stream_name = "Voice Playback", - .channels_min = 1, - .channels_max = 1, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_voice_mode2, -}, -/* DAI HiFi mode 3 */ -{ .name = "wm8753-hifi", - .playback = { - .stream_name = "HiFi Playback", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_hifi_mode3, -}, -/* DAI Voice mode 3 - dummy */ -{ .name = "wm8753-voice", -}, -/* DAI HiFi mode 4 */ -{ .name = "wm8753-hifi", - .playback = { - .stream_name = "HiFi Playback", - .channels_min = 1, - .channels_max = 2, - .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, + .formats = WM8753_FORMATS, + }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, - .ops = &wm8753_dai_ops_hifi_mode4, -}, -/* DAI Voice mode 4 - dummy */ -{ .name = "wm8753-voice", -}, -}; - -static struct snd_soc_dai_driver wm8753_dai[] = { - { - .name = "wm8753-aif0", - }, - { - .name = "wm8753-aif1", + .formats = WM8753_FORMATS, }, + .ops = &wm8753_dai_ops_voice_mode, +}, }; -static void wm8753_set_dai_mode(struct snd_soc_codec *codec, - struct snd_soc_dai *dai, unsigned int hifi) -{ - struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); - - if (wm8753->dai_func < 4) { - if (hifi) - dai->driver = &wm8753_all_dai[wm8753->dai_func << 1]; - else - dai->driver = &wm8753_all_dai[(wm8753->dai_func << 1) + 1]; - } - snd_soc_write(codec, WM8753_IOCTL, wm8753->dai_func); -} - static void wm8753_work(struct work_struct *work) { struct snd_soc_dapm_context *dapm = diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 6dae1b40c9f..6785688f880 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -175,7 +175,7 @@ static int txsrc_put(struct snd_kcontrol *kcontrol, return 0; } -static int wm8804_volatile(unsigned int reg) +static int wm8804_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8804_RST_DEVID1: diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index cd0959926d1..449ea09a193 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -180,7 +180,7 @@ static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { /* Remaining registers all zero */ }; -static int wm8900_volatile_register(unsigned int reg) +static int wm8900_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8900_REG_ID: diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 017d99ceb42..ae1cadfae84 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -2,6 +2,7 @@ * wm8903.c -- WM8903 ALSA SoC Audio driver * * Copyright 2008 Wolfson Microelectronics + * Copyright 2011 NVIDIA, Inc. * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> * @@ -19,6 +20,7 @@ #include <linux/init.h> #include <linux/completion.h> #include <linux/delay.h> +#include <linux/gpio.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> @@ -213,6 +215,7 @@ static u16 wm8903_reg_defaults[] = { }; struct wm8903_priv { + struct snd_soc_codec *codec; int sysclk; int irq; @@ -220,25 +223,36 @@ struct wm8903_priv { int fs; int deemph; + int dcs_pending; + int dcs_cache[4]; + /* Reference count */ int class_w_users; - struct completion wseq; - struct snd_soc_jack *mic_jack; int mic_det; int mic_short; int mic_last_report; int mic_delay; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif }; -static int wm8903_volatile_register(unsigned int reg) +static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8903_SW_RESET_AND_ID: case WM8903_REVISION_NUMBER: case WM8903_INTERRUPT_STATUS_1: case WM8903_WRITE_SEQUENCER_4: + case WM8903_POWER_MANAGEMENT_3: + case WM8903_POWER_MANAGEMENT_2: + case WM8903_DC_SERVO_READBACK_1: + case WM8903_DC_SERVO_READBACK_2: + case WM8903_DC_SERVO_READBACK_3: + case WM8903_DC_SERVO_READBACK_4: return 1; default: @@ -246,50 +260,6 @@ static int wm8903_volatile_register(unsigned int reg) } } -static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) -{ - u16 reg[5]; - struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - - BUG_ON(start > 48); - - /* Enable the sequencer if it's not already on */ - reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, - reg[0] | WM8903_WSEQ_ENA); - - dev_dbg(codec->dev, "Starting sequence at %d\n", start); - - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3, - start | WM8903_WSEQ_START); - - /* Wait for it to complete. If we have the interrupt wired up then - * that will break us out of the poll early. - */ - do { - wait_for_completion_timeout(&wm8903->wseq, - msecs_to_jiffies(10)); - - reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); - } while (reg[4] & WM8903_WSEQ_BUSY); - - dev_dbg(codec->dev, "Sequence complete\n"); - - /* Disable the sequencer again if we enabled it */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); - - return 0; -} - -static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) -{ - int i; - - /* There really ought to be something better we can do here :/ */ - for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) - cache[i] = codec->hw_read(codec, i); -} - static void wm8903_reset(struct snd_soc_codec *codec) { snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0); @@ -297,11 +267,6 @@ static void wm8903_reset(struct snd_soc_codec *codec) sizeof(wm8903_reg_defaults)); } -#define WM8903_OUTPUT_SHORT 0x8 -#define WM8903_OUTPUT_OUT 0x4 -#define WM8903_OUTPUT_INT 0x2 -#define WM8903_OUTPUT_IN 0x1 - static int wm8903_cp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -311,97 +276,101 @@ static int wm8903_cp_event(struct snd_soc_dapm_widget *w, return 0; } -/* - * Event for headphone and line out amplifier power changes. Special - * power up/down sequences are required in order to maximise pop/click - * performance. - */ -static int wm8903_output_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static int wm8903_dcs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 val; - u16 reg; - u16 dcs_reg; - u16 dcs_bit; - int shift; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - switch (w->reg) { - case WM8903_POWER_MANAGEMENT_2: - reg = WM8903_ANALOGUE_HP_0; - dcs_bit = 0 + w->shift; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8903->dcs_pending |= 1 << w->shift; break; - case WM8903_POWER_MANAGEMENT_3: - reg = WM8903_ANALOGUE_LINEOUT_0; - dcs_bit = 2 + w->shift; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + 1 << w->shift, 0); break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ } - switch (w->shift) { - case 0: - shift = 0; - break; - case 1: - shift = 4; - break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ - } + return 0; +} - if (event & SND_SOC_DAPM_PRE_PMU) { - val = snd_soc_read(codec, reg); +#define WM8903_DCS_MODE_WRITE_STOP 0 +#define WM8903_DCS_MODE_START_STOP 2 - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } +static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type event, int subseq) +{ + struct snd_soc_codec *codec = container_of(dapm, + struct snd_soc_codec, dapm); + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int dcs_mode = WM8903_DCS_MODE_WRITE_STOP; + int i, val; - if (event & SND_SOC_DAPM_POST_PMU) { - val = snd_soc_read(codec, reg); + /* Complete any pending DC servo starts */ + if (wm8903->dcs_pending) { + dev_dbg(codec->dev, "Starting DC servo for %x\n", + wm8903->dcs_pending); - val |= (WM8903_OUTPUT_IN << shift); - snd_soc_write(codec, reg, val); + /* If we've no cached values then we need to do startup */ + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - val |= (WM8903_OUTPUT_INT << shift); - snd_soc_write(codec, reg, val); + if (wm8903->dcs_cache[i]) { + dev_dbg(codec->dev, + "Restore DC servo %d value %x\n", + 3 - i, wm8903->dcs_cache[i]); + + snd_soc_write(codec, WM8903_DC_SERVO_4 + i, + wm8903->dcs_cache[i] & 0xff); + } else { + dev_dbg(codec->dev, + "Calibrate DC servo %d\n", 3 - i); + dcs_mode = WM8903_DCS_MODE_START_STOP; + } + } - /* Turn on the output ENA_OUTP */ - val |= (WM8903_OUTPUT_OUT << shift); - snd_soc_write(codec, reg, val); + /* Don't trust the cache for analogue */ + if (wm8903->class_w_users) + dcs_mode = WM8903_DCS_MODE_START_STOP; - /* Enable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg |= dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + snd_soc_update_bits(codec, WM8903_DC_SERVO_2, + WM8903_DCS_MODE_MASK, dcs_mode); - /* Remove the short */ - val |= (WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + WM8903_DCS_ENA_MASK, wm8903->dcs_pending); - if (event & SND_SOC_DAPM_PRE_PMD) { - val = snd_soc_read(codec, reg); + switch (dcs_mode) { + case WM8903_DCS_MODE_WRITE_STOP: + break; - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); + case WM8903_DCS_MODE_START_STOP: + msleep(270); - /* Disable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg &= ~dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Cache the measured offsets for digital */ + if (wm8903->class_w_users) + break; - /* Then disable the intermediate and output stages */ - val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | - WM8903_OUTPUT_IN) << shift); - snd_soc_write(codec, reg, val); - } + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - return 0; + val = snd_soc_read(codec, + WM8903_DC_SERVO_READBACK_1 + i); + dev_dbg(codec->dev, "DC servo %d: %x\n", + 3 - i, val); + wm8903->dcs_cache[i] = val; + } + break; + + default: + pr_warn("DCS mode %d delay not set\n", dcs_mode); + break; + } + + wm8903->dcs_pending = 0; + } } /* @@ -667,6 +636,22 @@ static const struct soc_enum lsidetone_enum = static const struct soc_enum rsidetone_enum = SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text); +static const char *aif_text[] = { + "Left", "Right" +}; + +static const struct soc_enum lcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text); + +static const struct soc_enum rcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text); + +static const struct soc_enum lplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text); + +static const struct soc_enum rplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text); + static const struct snd_kcontrol_new wm8903_snd_controls[] = { /* Input PGAs - No TLV since the scale depends on PGA mode */ @@ -784,6 +769,18 @@ static const struct snd_kcontrol_new lsidetone_mux = static const struct snd_kcontrol_new rsidetone_mux = SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); +static const struct snd_kcontrol_new lcapture_mux = + SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum); + +static const struct snd_kcontrol_new rcapture_mux = + SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum); + +static const struct snd_kcontrol_new lplay_mux = + SOC_DAPM_ENUM("Left Playback Mux", lplay_enum); + +static const struct snd_kcontrol_new rplay_mux = + SOC_DAPM_ENUM("Right Playback Mux", rplay_enum); + static const struct snd_kcontrol_new left_output_mixer[] = { SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), @@ -847,14 +844,26 @@ SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), -SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), -SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), +SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux), +SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux), + +SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), -SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), -SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), +SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux), +SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0), SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, left_output_mixer, ARRAY_SIZE(left_output_mixer)), @@ -866,23 +875,45 @@ SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 1, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 0, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), - -SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 4, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 0, 0, + NULL, 0), + +SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 5, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0, + NULL, 0), + +SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, NULL, 0), @@ -892,10 +923,18 @@ SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, wm8903_cp_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), }; static const struct snd_soc_dapm_route intercon[] = { + { "CLK_DSP", NULL, "CLK_SYS" }, + { "Mic Bias", NULL, "CLK_SYS" }, + { "HPL_DCS", NULL, "CLK_SYS" }, + { "HPR_DCS", NULL, "CLK_SYS" }, + { "LINEOUTL_DCS", NULL, "CLK_SYS" }, + { "LINEOUTR_DCS", NULL, "CLK_SYS" }, + { "Left Input Mux", "IN1L", "IN1L" }, { "Left Input Mux", "IN2L", "IN2L" }, { "Left Input Mux", "IN3L", "IN3L" }, @@ -936,18 +975,36 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Input PGA", NULL, "Left Input Mode Mux" }, { "Right Input PGA", NULL, "Right Input Mode Mux" }, + { "Left Capture Mux", "Left", "ADCL" }, + { "Left Capture Mux", "Right", "ADCR" }, + + { "Right Capture Mux", "Left", "ADCL" }, + { "Right Capture Mux", "Right", "ADCR" }, + + { "AIFTXL", NULL, "Left Capture Mux" }, + { "AIFTXR", NULL, "Right Capture Mux" }, + { "ADCL", NULL, "Left Input PGA" }, { "ADCL", NULL, "CLK_DSP" }, { "ADCR", NULL, "Right Input PGA" }, { "ADCR", NULL, "CLK_DSP" }, + { "Left Playback Mux", "Left", "AIFRXL" }, + { "Left Playback Mux", "Right", "AIFRXR" }, + + { "Right Playback Mux", "Left", "AIFRXL" }, + { "Right Playback Mux", "Right", "AIFRXR" }, + { "DACL Sidetone", "Left", "ADCL" }, { "DACL Sidetone", "Right", "ADCR" }, { "DACR Sidetone", "Left", "ADCL" }, { "DACR Sidetone", "Right", "ADCR" }, + { "DACL", NULL, "Left Playback Mux" }, { "DACL", NULL, "DACL Sidetone" }, { "DACL", NULL, "CLK_DSP" }, + + { "DACR", NULL, "Right Playback Mux" }, { "DACR", NULL, "DACR Sidetone" }, { "DACR", NULL, "CLK_DSP" }, @@ -980,11 +1037,35 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, - { "HPOUTL", NULL, "Left Headphone Output PGA" }, - { "HPOUTR", NULL, "Right Headphone Output PGA" }, + { "HPL_ENA_DLY", NULL, "Left Headphone Output PGA" }, + { "HPR_ENA_DLY", NULL, "Right Headphone Output PGA" }, + { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" }, + { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" }, + + { "HPL_DCS", NULL, "DCS Master" }, + { "HPR_DCS", NULL, "DCS Master" }, + { "LINEOUTL_DCS", NULL, "DCS Master" }, + { "LINEOUTR_DCS", NULL, "DCS Master" }, + + { "HPL_DCS", NULL, "HPL_ENA_DLY" }, + { "HPR_DCS", NULL, "HPR_ENA_DLY" }, + { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" }, + { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" }, - { "LINEOUTL", NULL, "Left Line Output PGA" }, - { "LINEOUTR", NULL, "Right Line Output PGA" }, + { "HPL_ENA_OUTP", NULL, "HPL_DCS" }, + { "HPR_ENA_OUTP", NULL, "HPR_DCS" }, + { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" }, + { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" }, + + { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" }, + { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" }, + { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" }, + { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" }, + + { "HPOUTL", NULL, "HPL_RMV_SHORT" }, + { "HPOUTR", NULL, "HPR_RMV_SHORT" }, + { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" }, + { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" }, { "LOP", NULL, "Left Speaker PGA" }, { "LON", NULL, "Left Speaker PGA" }, @@ -1012,29 +1093,71 @@ static int wm8903_add_widgets(struct snd_soc_codec *codec) static int wm8903_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 reg; - switch (level) { case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_50K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { - snd_soc_write(codec, WM8903_CLOCK_RATES_2, - WM8903_CLK_SYS_ENA); - - /* Change DC servo dither level in startup sequence */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); - - wm8903_run_sequence(codec, 0); - wm8903_sync_reg_cache(codec, codec->reg_cache); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_POBCTRL | WM8903_ISEL_MASK | + WM8903_STARTUP_BIAS_ENA | + WM8903_BIAS_ENA, + WM8903_POBCTRL | + (2 << WM8903_ISEL_SHIFT) | + WM8903_STARTUP_BIAS_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, + WM8903_SPK_DISCHARGE); + + msleep(33); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + WM8903_SPKL_ENA | WM8903_SPKR_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_RES_MASK | + WM8903_VMID_BUF_ENA, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + (2 << WM8903_VMID_SOFT_SHIFT) | + WM8903_VMID_RES_250K | + WM8903_VMID_BUF_ENA); + + msleep(129); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA | WM8903_POBCTRL, + WM8903_BIAS_ENA); /* By default no bypass paths are enabled so * enable Class W support. @@ -1047,17 +1170,32 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, WM8903_CP_DYN_V); } - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_250K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_250K); break; case SND_SOC_BIAS_OFF: - wm8903_run_sequence(codec, 32); - reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2); - reg &= ~WM8903_CLK_SYS_ENA; - snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, + 2 << WM8903_VMID_SOFT_SHIFT); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_BUF_ENA, 0); + + msleep(290); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_BUF_ENA, 0); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_STARTUP_BIAS_ENA, 0); break; } @@ -1510,8 +1648,7 @@ static irqreturn_t wm8903_irq(int irq, void *data) int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask; if (int_val & WM8903_WSEQ_BUSY_EINT) { - dev_dbg(codec->dev, "Write sequencer done\n"); - complete(&wm8903->wseq); + dev_warn(codec->dev, "Write sequencer done\n"); } /* @@ -1635,6 +1772,120 @@ static int wm8903_resume(struct snd_soc_codec *codec) return 0; } +#ifdef CONFIG_GPIOLIB +static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8903_priv, gpio_chip); +} + +static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= WM8903_NUM_GPIO) + return -EINVAL; + + return 0; +} + +static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + unsigned int mask, val; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK; + val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) | + WM8903_GP1_DIR; + + return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + mask, val); +} + +static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + int reg; + + reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset); + + return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT; +} + +static int wm8903_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + unsigned int mask, val; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK; + val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) | + (value << WM8903_GP2_LVL_SHIFT); + + return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + mask, val); +} + +static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + + snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + WM8903_GP1_LVL_MASK, + !!value << WM8903_GP1_LVL_SHIFT); +} + +static struct gpio_chip wm8903_template_chip = { + .label = "wm8903", + .owner = THIS_MODULE, + .request = wm8903_gpio_request, + .direction_input = wm8903_gpio_direction_in, + .get = wm8903_gpio_get, + .direction_output = wm8903_gpio_direction_out, + .set = wm8903_gpio_set, + .can_sleep = 1, +}; + +static void wm8903_init_gpio(struct snd_soc_codec *codec) +{ + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev); + int ret; + + wm8903->gpio_chip = wm8903_template_chip; + wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO; + wm8903->gpio_chip.dev = codec->dev; + + if (pdata && pdata->gpio_base) + wm8903->gpio_chip.base = pdata->gpio_base; + else + wm8903->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8903->gpio_chip); + if (ret != 0) + dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm8903_free_gpio(struct snd_soc_codec *codec) +{ + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = gpiochip_remove(&wm8903->gpio_chip); + if (ret != 0) + dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret); +} +#else +static void wm8903_init_gpio(struct snd_soc_codec *codec) +{ +} + +static void wm8903_free_gpio(struct snd_soc_codec *codec) +{ +} +#endif + static int wm8903_probe(struct snd_soc_codec *codec) { struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev); @@ -1643,7 +1894,7 @@ static int wm8903_probe(struct snd_soc_codec *codec) int trigger, irq_pol; u16 val; - init_completion(&wm8903->wseq); + wm8903->codec = codec; ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); if (ret != 0) { @@ -1659,19 +1910,33 @@ static int wm8903_probe(struct snd_soc_codec *codec) } val = snd_soc_read(codec, WM8903_REVISION_NUMBER); - dev_info(codec->dev, "WM8903 revision %d\n", - val & WM8903_CHIP_REV_MASK); + dev_info(codec->dev, "WM8903 revision %c\n", + (val & WM8903_CHIP_REV_MASK) + 'A'); wm8903_reset(codec); /* Set up GPIOs and microphone detection */ if (pdata) { + bool mic_gpio = false; + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { - if (!pdata->gpio_cfg[i]) + if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG) continue; snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, pdata->gpio_cfg[i] & 0xffff); + + val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK) + >> WM8903_GP1_FN_SHIFT; + + switch (val) { + case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT: + case WM8903_GPn_FN_MICBIAS_SHORT_DETECT: + mic_gpio = true; + break; + default: + break; + } } snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, @@ -1682,6 +1947,14 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + /* If microphone detection is enabled by pdata but + * detected via IRQ then interrupts can be lost before + * the machine driver has set up microphone detection + * IRQs as the IRQs are clear on read. The detection + * will be enabled when the machine driver configures. + */ + WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA)); + wm8903->mic_delay = pdata->micdet_delay; } @@ -1741,20 +2014,23 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); /* Enable DAC soft mute by default */ - val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); - val |= WM8903_DAC_MUTEMODE; - snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val); + snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE); snd_soc_add_controls(codec, wm8903_snd_controls, ARRAY_SIZE(wm8903_snd_controls)); wm8903_add_widgets(codec); + wm8903_init_gpio(codec); + return ret; } /* power down chip */ static int wm8903_remove(struct snd_soc_codec *codec) { + wm8903_free_gpio(codec); wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1769,6 +2045,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8903 = { .reg_word_size = sizeof(u16), .reg_cache_default = wm8903_reg_defaults, .volatile_register = wm8903_volatile_register, + .seq_notifier = wm8903_seq_notifier, }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -1807,7 +2084,7 @@ MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); static struct i2c_driver wm8903_i2c_driver = { .driver = { - .name = "wm8903-codec", + .name = "wm8903", .owner = THIS_MODULE, }, .probe = wm8903_i2c_probe, diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index e3ec2433b21..db949311c0f 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -75,6 +75,14 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec, #define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 #define WM8903_DC_SERVO_0 0x43 #define WM8903_DC_SERVO_2 0x45 +#define WM8903_DC_SERVO_4 0x47 +#define WM8903_DC_SERVO_5 0x48 +#define WM8903_DC_SERVO_6 0x49 +#define WM8903_DC_SERVO_7 0x4A +#define WM8903_DC_SERVO_READBACK_1 0x51 +#define WM8903_DC_SERVO_READBACK_2 0x52 +#define WM8903_DC_SERVO_READBACK_3 0x53 +#define WM8903_DC_SERVO_READBACK_4 0x54 #define WM8903_ANALOGUE_HP_0 0x5A #define WM8903_ANALOGUE_LINEOUT_0 0x5E #define WM8903_CHARGE_PUMP_0 0x62 diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 9de44a4c05c..443ae580445 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -596,7 +596,7 @@ static struct { { 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */ }; -static int wm8904_volatile_register(unsigned int reg) +static int wm8904_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { return wm8904_access[reg].vol; } @@ -2436,19 +2436,28 @@ static int wm8904_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8904_ADC_DIGITAL_VOLUME_LEFT] |= WM8904_ADC_VU; - reg_cache[WM8904_ADC_DIGITAL_VOLUME_RIGHT] |= WM8904_ADC_VU; - reg_cache[WM8904_DAC_DIGITAL_VOLUME_LEFT] |= WM8904_DAC_VU; - reg_cache[WM8904_DAC_DIGITAL_VOLUME_RIGHT] |= WM8904_DAC_VU; - reg_cache[WM8904_ANALOGUE_OUT1_LEFT] |= WM8904_HPOUT_VU | - WM8904_HPOUTLZC; - reg_cache[WM8904_ANALOGUE_OUT1_RIGHT] |= WM8904_HPOUT_VU | - WM8904_HPOUTRZC; - reg_cache[WM8904_ANALOGUE_OUT2_LEFT] |= WM8904_LINEOUT_VU | - WM8904_LINEOUTLZC; - reg_cache[WM8904_ANALOGUE_OUT2_RIGHT] |= WM8904_LINEOUT_VU | - WM8904_LINEOUTRZC; - reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE; + snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_LEFT, + WM8904_ADC_VU, WM8904_ADC_VU); + snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_RIGHT, + WM8904_ADC_VU, WM8904_ADC_VU); + snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_LEFT, + WM8904_DAC_VU, WM8904_DAC_VU); + snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_RIGHT, + WM8904_DAC_VU, WM8904_DAC_VU); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_LEFT, + WM8904_HPOUT_VU | WM8904_HPOUTLZC, + WM8904_HPOUT_VU | WM8904_HPOUTLZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_RIGHT, + WM8904_HPOUT_VU | WM8904_HPOUTRZC, + WM8904_HPOUT_VU | WM8904_HPOUTRZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_LEFT, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_RIGHT, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC); + snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0, + WM8904_SR_MODE, 0); /* Apply configuration from the platform data. */ if (wm8904->pdata) { @@ -2469,10 +2478,12 @@ static int wm8904_probe(struct snd_soc_codec *codec) /* Set Class W by default - this will be managed by the Class * G widget at runtime where bypass paths are available. */ - reg_cache[WM8904_CLASS_W_0] |= WM8904_CP_DYN_PWR; + snd_soc_update_bits(codec, WM8904_CLASS_W_0, + WM8904_CP_DYN_PWR, WM8904_CP_DYN_PWR); /* Use normal bias source */ - reg_cache[WM8904_BIAS_CONTROL_0] &= ~WM8904_POBCTRL; + snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0, + WM8904_POBCTRL, 0); wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 7167dfc96aa..5e0214d6293 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -934,16 +934,27 @@ static int wm8955_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU; - reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU; - reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC; - reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC; - reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC; - reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC; - reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC; + snd_soc_update_bits(codec, WM8955_LEFT_DAC_VOLUME, + WM8955_LDVU, WM8955_LDVU); + snd_soc_update_bits(codec, WM8955_RIGHT_DAC_VOLUME, + WM8955_RDVU, WM8955_RDVU); + snd_soc_update_bits(codec, WM8955_LOUT1_VOLUME, + WM8955_LO1VU | WM8955_LO1ZC, + WM8955_LO1VU | WM8955_LO1ZC); + snd_soc_update_bits(codec, WM8955_ROUT1_VOLUME, + WM8955_RO1VU | WM8955_RO1ZC, + WM8955_RO1VU | WM8955_RO1ZC); + snd_soc_update_bits(codec, WM8955_LOUT2_VOLUME, + WM8955_LO2VU | WM8955_LO2ZC, + WM8955_LO2VU | WM8955_LO2ZC); + snd_soc_update_bits(codec, WM8955_ROUT2_VOLUME, + WM8955_RO2VU | WM8955_RO2ZC, + WM8955_RO2VU | WM8955_RO2ZC); + snd_soc_update_bits(codec, WM8955_MONOOUT_VOLUME, + WM8955_MOZC, WM8955_MOZC); /* Also enable adaptive bass boost by default */ - reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB; + snd_soc_update_bits(codec, WM8955_BASS_CONTROL, WM8955_BB, WM8955_BB); /* Set platform data values */ if (pdata) { diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 55252e7d02c..cdee8103d09 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -291,7 +291,7 @@ struct wm8961_priv { int sysclk; }; -static int wm8961_volatile_register(unsigned int reg) +static int wm8961_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8961_SOFTWARE_RESET: diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index b9cb1fcf8c9..3b71dd65c96 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -1938,7 +1938,7 @@ static const struct wm8962_reg_access { [21139] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21139 - VSS_XTS32_0 */ }; -static int wm8962_volatile_register(unsigned int reg) +static int wm8962_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { if (wm8962_reg_access[reg].vol) return 1; @@ -1946,7 +1946,7 @@ static int wm8962_volatile_register(unsigned int reg) return 0; } -static int wm8962_readable_register(unsigned int reg) +static int wm8962_readable_register(struct snd_soc_codec *codec, unsigned int reg) { if (wm8962_reg_access[reg].read) return 1; @@ -3635,7 +3635,7 @@ static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value) struct snd_soc_codec *codec = wm8962->codec; snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset, - WM8962_GP2_LVL, value << WM8962_GP2_LVL_SHIFT); + WM8962_GP2_LVL, !!value << WM8962_GP2_LVL_SHIFT); } static int wm8962_gpio_direction_out(struct gpio_chip *chip, @@ -3822,16 +3822,26 @@ static int wm8962_probe(struct snd_soc_codec *codec) } /* Latch volume update bits */ - reg_cache[WM8962_LEFT_INPUT_VOLUME] |= WM8962_IN_VU; - reg_cache[WM8962_RIGHT_INPUT_VOLUME] |= WM8962_IN_VU; - reg_cache[WM8962_LEFT_ADC_VOLUME] |= WM8962_ADC_VU; - reg_cache[WM8962_RIGHT_ADC_VOLUME] |= WM8962_ADC_VU; - reg_cache[WM8962_LEFT_DAC_VOLUME] |= WM8962_DAC_VU; - reg_cache[WM8962_RIGHT_DAC_VOLUME] |= WM8962_DAC_VU; - reg_cache[WM8962_SPKOUTL_VOLUME] |= WM8962_SPKOUT_VU; - reg_cache[WM8962_SPKOUTR_VOLUME] |= WM8962_SPKOUT_VU; - reg_cache[WM8962_HPOUTL_VOLUME] |= WM8962_HPOUT_VU; - reg_cache[WM8962_HPOUTR_VOLUME] |= WM8962_HPOUT_VU; + snd_soc_update_bits(codec, WM8962_LEFT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + snd_soc_update_bits(codec, WM8962_LEFT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + snd_soc_update_bits(codec, WM8962_LEFT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + snd_soc_update_bits(codec, WM8962_SPKOUTL_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + snd_soc_update_bits(codec, WM8962_SPKOUTR_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + snd_soc_update_bits(codec, WM8962_HPOUTL_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); + snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); wm8962_add_widgets(codec); diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 4bbc3442703..85e3e630e76 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -93,6 +93,7 @@ static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(limiter_tlv, 0, 100, 0); static const struct snd_kcontrol_new wm8978_snd_controls[] = { @@ -144,19 +145,19 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { SOC_SINGLE("DAC Playback Limiter Threshold", WM8978_DAC_LIMITER_2, 4, 7, 0), - SOC_SINGLE("DAC Playback Limiter Boost", - WM8978_DAC_LIMITER_2, 0, 15, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Volume", + WM8978_DAC_LIMITER_2, 0, 12, 0, limiter_tlv), SOC_ENUM("ALC Enable Switch", alc1), SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0), SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0), - SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0), + SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 10, 0), SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0), SOC_ENUM("ALC Capture Mode", alc3), - SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0), - SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 10, 0), SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0), SOC_SINGLE("ALC Capture Noise Gate Threshold", @@ -211,8 +212,10 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = { WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1), /* DAC / ADC oversampling */ - SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, 8, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, + 5, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, + 5, 1, 0), }; /* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ @@ -965,7 +968,7 @@ static int wm8978_probe(struct snd_soc_codec *codec) * written. */ for (i = 0; i < ARRAY_SIZE(update_reg); i++) - ((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100; + snd_soc_update_bits(codec, update_reg[i], 0x100, 0x100); /* Reset the codec */ ret = snd_soc_write(codec, WM8978_RESET, 0); diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c new file mode 100644 index 00000000000..28fdfd66661 --- /dev/null +++ b/sound/soc/codecs/wm8991.c @@ -0,0 +1,1427 @@ +/* + * wm8991.c -- WM8991 ALSA Soc Audio driver + * + * Copyright 2007-2010 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> + +#include "wm8991.h" + +struct wm8991_priv { + enum snd_soc_control_type control_type; + unsigned int pcmclk; +}; + +static const u16 wm8991_reg_defs[] = { + 0x8991, /* R0 - Reset */ + 0x0000, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x4050, /* R4 - Audio Interface (1) */ + 0x4000, /* R5 - Audio Interface (2) */ + 0x01C8, /* R6 - Clocking (1) */ + 0x0000, /* R7 - Clocking (2) */ + 0x0040, /* R8 - Audio Interface (3) */ + 0x0040, /* R9 - Audio Interface (4) */ + 0x0004, /* R10 - DAC CTRL */ + 0x00C0, /* R11 - Left DAC Digital Volume */ + 0x00C0, /* R12 - Right DAC Digital Volume */ + 0x0000, /* R13 - Digital Side Tone */ + 0x0100, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x1000, /* R19 - GPIO1 & GPIO2 */ + 0x1010, /* R20 - GPIO3 & GPIO4 */ + 0x1010, /* R21 - GPIO5 & GPIO6 */ + 0x8000, /* R22 - GPIOCTRL 2 */ + 0x0800, /* R23 - GPIO_POL */ + 0x008B, /* R24 - Left Line Input 1&2 Volume */ + 0x008B, /* R25 - Left Line Input 3&4 Volume */ + 0x008B, /* R26 - Right Line Input 1&2 Volume */ + 0x008B, /* R27 - Right Line Input 3&4 Volume */ + 0x0000, /* R28 - Left Output Volume */ + 0x0000, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0022, /* R31 - Out3/4 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - Speaker Volume */ + 0x0003, /* R35 - ClassD1 */ + 0x0000, /* R36 */ + 0x0100, /* R37 - ClassD3 */ + 0x0000, /* R38 */ + 0x0000, /* R39 - Input Mixer1 */ + 0x0000, /* R40 - Input Mixer2 */ + 0x0000, /* R41 - Input Mixer3 */ + 0x0000, /* R42 - Input Mixer4 */ + 0x0000, /* R43 - Input Mixer5 */ + 0x0000, /* R44 - Input Mixer6 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0000, /* R47 - Output Mixer3 */ + 0x0000, /* R48 - Output Mixer4 */ + 0x0000, /* R49 - Output Mixer5 */ + 0x0000, /* R50 - Output Mixer6 */ + 0x0180, /* R51 - Out3/4 Mixer */ + 0x0000, /* R52 - Line Mixer1 */ + 0x0000, /* R53 - Line Mixer2 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 - Additional Control */ + 0x0000, /* R56 - AntiPOP1 */ + 0x0000, /* R57 - AntiPOP2 */ + 0x0000, /* R58 - MICBIAS */ + 0x0000, /* R59 */ + 0x0008, /* R60 - PLL1 */ + 0x0031, /* R61 - PLL2 */ + 0x0026, /* R62 - PLL3 */ +}; + +#define wm8991_reset(c) snd_soc_write(c, WM8991_RESET, 0) + +static const unsigned int rec_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1500, 600), +}; + +static const unsigned int in_pga_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 0x1F, TLV_DB_LINEAR_ITEM(-1650, 3000), +}; + +static const unsigned int out_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(0, -2100), +}; + +static const unsigned int out_pga_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 127, TLV_DB_LINEAR_ITEM(-7300, 600), +}; + +static const unsigned int out_omix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-600, 0), +}; + +static const unsigned int out_dac_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 255, TLV_DB_LINEAR_ITEM(-7163, 0), +}; + +static const unsigned int in_adc_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 255, TLV_DB_LINEAR_ITEM(-7163, 1763), +}; + +static const unsigned int out_sidetone_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 31, TLV_DB_LINEAR_ITEM(-3600, 0), +}; + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_read(codec, reg); + return snd_soc_write(codec, reg, val | 0x0100); +} + +static const char *wm8991_digital_sidetone[] = +{"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8991_left_digital_sidetone_enum = + SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACL_SHIFT, + WM8991_ADC_TO_DACL_MASK, + wm8991_digital_sidetone); + +static const struct soc_enum wm8991_right_digital_sidetone_enum = + SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACR_SHIFT, + WM8991_ADC_TO_DACR_MASK, + wm8991_digital_sidetone); + +static const char *wm8991_adcmode[] = +{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8991_right_adcmode_enum = + SOC_ENUM_SINGLE(WM8991_ADC_CTRL, + WM8991_ADC_HPF_CUT_SHIFT, + WM8991_ADC_HPF_CUT_MASK, + wm8991_adcmode); + +static const struct snd_kcontrol_new wm8991_snd_controls[] = { + /* INMIXL */ + SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), + SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), + /* INMIXR */ + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + + /* LOMIX */ + SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LLI3LOVOL_SHIFT, WM8991_LLI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LR12LOVOL_SHIFT, WM8991_LR12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LL12LOVOL_SHIFT, WM8991_LL12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRI3LOVOL_SHIFT, WM8991_LRI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + + /* ROMIX */ + SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RRI3ROVOL_SHIFT, WM8991_RRI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RL12ROVOL_SHIFT, WM8991_RL12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RR12ROVOL_SHIFT, WM8991_RR12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLI3ROVOL_SHIFT, WM8991_RLI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLBROVOL_SHIFT, WM8991_RLBROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RRBROVOL_SHIFT, WM8991_RRBROVOL_MASK, 1, out_mix_tlv), + + /* LOUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8991_LEFT_OUTPUT_VOLUME, + WM8991_LOUTVOL_SHIFT, WM8991_LOUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOUT ZC", WM8991_LEFT_OUTPUT_VOLUME, WM8991_LOZC_BIT, 1, 0), + + /* ROUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8991_RIGHT_OUTPUT_VOLUME, + WM8991_ROUTVOL_SHIFT, WM8991_ROUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROUT ZC", WM8991_RIGHT_OUTPUT_VOLUME, WM8991_ROZC_BIT, 1, 0), + + /* LOPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAVOL_SHIFT, WM8991_LOPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOPGA ZC Switch", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAZC_BIT, 1, 0), + + /* ROPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAVOL_SHIFT, WM8991_ROPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROPGA ZC Switch", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAZC_BIT, 1, 0), + + SOC_SINGLE("LON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LONMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOPMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOATTN_BIT, 1, 0), + SOC_SINGLE("RON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_RONMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROPMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROATTN_BIT, 1, 0), + + SOC_SINGLE("OUT3 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3MUTE_BIT, 1, 0), + SOC_SINGLE("OUT3 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3ATTN_BIT, 1, 0), + + SOC_SINGLE("OUT4 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4MUTE_BIT, 1, 0), + SOC_SINGLE("OUT4 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4ATTN_BIT, 1, 0), + + SOC_SINGLE("Speaker Mode Switch", WM8991_CLASSD1, + WM8991_CDMODE_BIT, 1, 0), + + SOC_SINGLE("Speaker Output Attenuation Volume", WM8991_SPEAKER_VOLUME, + WM8991_SPKVOL_SHIFT, WM8991_SPKVOL_MASK, 0), + SOC_SINGLE("Speaker DC Boost Volume", WM8991_CLASSD3, + WM8991_DCGAIN_SHIFT, WM8991_DCGAIN_MASK, 0), + SOC_SINGLE("Speaker AC Boost Volume", WM8991_CLASSD3, + WM8991_ACGAIN_SHIFT, WM8991_ACGAIN_MASK, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8991_LEFT_DAC_DIGITAL_VOLUME, + WM8991_DACL_VOL_SHIFT, + WM8991_DACL_VOL_MASK, + 0, + out_dac_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8991_RIGHT_DAC_DIGITAL_VOLUME, + WM8991_DACR_VOL_SHIFT, + WM8991_DACR_VOL_MASK, + 0, + out_dac_tlv), + + SOC_ENUM("Left Digital Sidetone", wm8991_left_digital_sidetone_enum), + SOC_ENUM("Right Digital Sidetone", wm8991_right_digital_sidetone_enum), + + SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCL_DAC_SVOL_SHIFT, WM8991_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCR_DAC_SVOL_SHIFT, WM8991_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + + SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8991_ADC_CTRL, + WM8991_ADC_HPF_ENA_BIT, 1, 0), + + SOC_ENUM("ADC HPF Mode", wm8991_right_adcmode_enum), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8991_LEFT_ADC_DIGITAL_VOLUME, + WM8991_ADCL_VOL_SHIFT, + WM8991_ADCL_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8991_RIGHT_ADC_DIGITAL_VOLUME, + WM8991_ADCR_VOL_SHIFT, + WM8991_ADCR_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LIN12VOL_SHIFT, + WM8991_LIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN12 ZC Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12ZC_BIT, 1, 0), + + SOC_SINGLE("LIN12 Mute Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LIN34VOL_SHIFT, + WM8991_LIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN34 ZC Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34ZC_BIT, 1, 0), + + SOC_SINGLE("LIN34 Mute Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RIN12VOL_SHIFT, + WM8991_RIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN12 ZC Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12ZC_BIT, 1, 0), + + SOC_SINGLE("RIN12 Mute Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RIN34VOL_SHIFT, + WM8991_RIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN34 ZC Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34ZC_BIT, 1, 0), + + SOC_SINGLE("RIN34 Mute Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34MUTE_BIT, 1, 0), +}; + +/* + * _DAPM_ Controls + */ +static int inmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = snd_soc_read(w->codec, WM8991_POWER_MANAGEMENT_2); + fakepower = snd_soc_read(w->codec, WM8991_INTDRIVBITS); + + if (fakepower & ((1 << WM8991_INMIXL_PWR_BIT) | + (1 << WM8991_AINLMUX_PWR_BIT))) + reg |= WM8991_AINL_ENA; + else + reg &= ~WM8991_AINL_ENA; + + if (fakepower & ((1 << WM8991_INMIXR_PWR_BIT) | + (1 << WM8991_AINRMUX_PWR_BIT))) + reg |= WM8991_AINR_ENA; + else + reg &= ~WM8991_AINL_ENA; + + snd_soc_write(w->codec, WM8991_POWER_MANAGEMENT_2, reg); + return 0; +} + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8991_SPEAKER_MIXER | (WM8991_LDSPK_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER1); + if (reg & WM8991_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + + case WM8991_SPEAKER_MIXER | (WM8991_RDSPK_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER2); + if (reg & WM8991_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER1 | (WM8991_LDLO_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER); + if (reg & WM8991_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER2 | (WM8991_RDRO_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER); + if (reg & WM8991_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_lin12_pga_controls[] = { + SOC_DAPM_SINGLE("LIN1 Switch", WM8991_INPUT_MIXER2, WM8991_LMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN2 Switch", WM8991_INPUT_MIXER2, WM8991_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_lin34_pga_controls[] = { + SOC_DAPM_SINGLE("LIN3 Switch", WM8991_INPUT_MIXER2, WM8991_LMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN4 Switch", WM8991_INPUT_MIXER2, WM8991_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_rin12_pga_controls[] = { + SOC_DAPM_SINGLE("RIN1 Switch", WM8991_INPUT_MIXER2, WM8991_RMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN2 Switch", WM8991_INPUT_MIXER2, WM8991_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_rin34_pga_controls[] = { + SOC_DAPM_SINGLE("RIN3 Switch", WM8991_INPUT_MIXER2, WM8991_RMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN4 Switch", WM8991_INPUT_MIXER2, WM8991_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8991_dapm_inmixl_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8991_INPUT_MIXER3, + WM8991_LDBVOL_SHIFT, WM8991_LDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8991_INPUT_MIXER5, WM8991_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("LINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("LINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8991_INPUT_MIXER4, + WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8991_ainlmux[] = +{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8991_ainlmux_enum = + SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINLMODE_SHIFT, + ARRAY_SIZE(wm8991_ainlmux), wm8991_ainlmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainlmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8991_ainrmux[] = +{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8991_ainrmux_enum = + SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINRMODE_SHIFT, + ARRAY_SIZE(wm8991_ainrmux), wm8991_ainrmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainrmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8991_dapm_rxvoice_controls[] = { + SOC_DAPM_SINGLE_TLV("LIN4RXN", WM8991_INPUT_MIXER5, WM8991_LR4BVOL_SHIFT, + WM8991_LR4BVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN4RXP", WM8991_INPUT_MIXER6, WM8991_RL4BVOL_SHIFT, + WM8991_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lomix_controls[] = { + SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LR12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LL12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8991_OUTPUT_MIXER1, + WM8991_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8991_dapm_romix_controls[] = { + SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RL12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RR12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8991_OUTPUT_MIXER2, + WM8991_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lonmix_controls[] = { + SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LROPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8991_LINE_MIXER1, + WM8991_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lopmix_controls[] = { + SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LR12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LL12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ronmix_controls[] = { + SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RLOPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8991_LINE_MIXER2, + WM8991_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ropmix_controls[] = { + SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RR12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out3mix_controls[] = { + SOC_DAPM_SINGLE("OUT3MIX LIN4RXN Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_LI4O3_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out4mix_controls[] = { + SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_RPGAO4_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT4MIX RIN4RXP Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8991_dapm_spkmix_controls[] = { + SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LI2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LB2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_LOPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_LDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_RDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_ROPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = { + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + SND_SOC_DAPM_INPUT("LIN3"), + SND_SOC_DAPM_INPUT("LIN4RXN"), + SND_SOC_DAPM_INPUT("RIN3"), + SND_SOC_DAPM_INPUT("RIN4RXP"), + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("RIN2"), + SND_SOC_DAPM_INPUT("Internal ADC Source"), + + /* DACs */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCL_ENA_BIT, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCR_ENA_BIT, 0), + + /* Input PGAs */ + SND_SOC_DAPM_MIXER("LIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN12_ENA_BIT, + 0, &wm8991_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin12_pga_controls)), + SND_SOC_DAPM_MIXER("LIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN34_ENA_BIT, + 0, &wm8991_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin34_pga_controls)), + SND_SOC_DAPM_MIXER("RIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN12_ENA_BIT, + 0, &wm8991_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin12_pga_controls)), + SND_SOC_DAPM_MIXER("RIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN34_ENA_BIT, + 0, &wm8991_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)), + + /* INMIXL */ + SND_SOC_DAPM_MIXER_E("INMIXL", WM8991_INTDRIVBITS, WM8991_INMIXL_PWR_BIT, 0, + &wm8991_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* AINLMUX */ + SND_SOC_DAPM_MUX_E("AINLMUX", WM8991_INTDRIVBITS, WM8991_AINLMUX_PWR_BIT, 0, + &wm8991_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* INMIXR */ + SND_SOC_DAPM_MIXER_E("INMIXR", WM8991_INTDRIVBITS, WM8991_INMIXR_PWR_BIT, 0, + &wm8991_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* AINRMUX */ + SND_SOC_DAPM_MUX_E("AINRMUX", WM8991_INTDRIVBITS, WM8991_AINRMUX_PWR_BIT, 0, + &wm8991_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACL_ENA_BIT, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACR_ENA_BIT, 0), + + /* LOMIX */ + SND_SOC_DAPM_MIXER_E("LOMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOMIX_ENA_BIT, + 0, &wm8991_dapm_lomix_controls[0], + ARRAY_SIZE(wm8991_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LONMIX */ + SND_SOC_DAPM_MIXER("LONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LON_ENA_BIT, 0, + &wm8991_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lonmix_controls)), + + /* LOPMIX */ + SND_SOC_DAPM_MIXER("LOPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOP_ENA_BIT, 0, + &wm8991_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lopmix_controls)), + + /* OUT3MIX */ + SND_SOC_DAPM_MIXER("OUT3MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT3_ENA_BIT, 0, + &wm8991_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out3mix_controls)), + + /* SPKMIX */ + SND_SOC_DAPM_MIXER_E("SPKMIX", WM8991_POWER_MANAGEMENT_1, WM8991_SPK_ENA_BIT, 0, + &wm8991_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8991_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + + /* OUT4MIX */ + SND_SOC_DAPM_MIXER("OUT4MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT4_ENA_BIT, 0, + &wm8991_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out4mix_controls)), + + /* ROPMIX */ + SND_SOC_DAPM_MIXER("ROPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROP_ENA_BIT, 0, + &wm8991_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ropmix_controls)), + + /* RONMIX */ + SND_SOC_DAPM_MIXER("RONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_RON_ENA_BIT, 0, + &wm8991_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ronmix_controls)), + + /* ROMIX */ + SND_SOC_DAPM_MIXER_E("ROMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROMIX_ENA_BIT, + 0, &wm8991_dapm_romix_controls[0], + ARRAY_SIZE(wm8991_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LOUT PGA */ + SND_SOC_DAPM_PGA("LOUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_LOUT_ENA_BIT, 0, + NULL, 0), + + /* ROUT PGA */ + SND_SOC_DAPM_PGA("ROUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_ROUT_ENA_BIT, 0, + NULL, 0), + + /* LOPGA */ + SND_SOC_DAPM_PGA("LOPGA", WM8991_POWER_MANAGEMENT_3, WM8991_LOPGA_ENA_BIT, 0, + NULL, 0), + + /* ROPGA */ + SND_SOC_DAPM_PGA("ROPGA", WM8991_POWER_MANAGEMENT_3, WM8991_ROPGA_ENA_BIT, 0, + NULL, 0), + + /* MICBIAS */ + SND_SOC_DAPM_MICBIAS("MICBIAS", WM8991_POWER_MANAGEMENT_1, + WM8991_MICBIAS_ENA_BIT, 0), + + SND_SOC_DAPM_OUTPUT("LON"), + SND_SOC_DAPM_OUTPUT("LOP"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("SPKN"), + SND_SOC_DAPM_OUTPUT("SPKP"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("OUT4"), + SND_SOC_DAPM_OUTPUT("ROP"), + SND_SOC_DAPM_OUTPUT("RON"), + SND_SOC_DAPM_OUTPUT("OUT"), + + SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AINLMUX */ + {"AINLMUX", "INMIXL Mix", "INMIXL"}, + {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AINLMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINLMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Left ADC", NULL, "AINLMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AINRMUX */ + {"AINRMUX", "INMIXR Mix", "INMIXR"}, + {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AINRMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINRMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Right ADC", NULL, "AINRMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4RXN Bypass Switch", "LIN4RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4RXP Bypass Switch", "RIN4RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8991 N value outwith recommended range! N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8991_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, int src, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_codec *codec = codec_dai->codec; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + reg |= WM8991_PLL_ENA; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = snd_soc_read(codec, WM8991_CLOCKING_2); + snd_soc_write(codec, WM8991_CLOCKING_2, reg | WM8991_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if neccessary */ + snd_soc_write(codec, WM8991_PLL1, pll_div.n | WM8991_SDM | + (pll_div.div2 ? WM8991_PRESCALE : 0)); + snd_soc_write(codec, WM8991_PLL2, (u8)(pll_div.k>>8)); + snd_soc_write(codec, WM8991_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + reg &= ~WM8991_PLL_ENA; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8991_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1); + audio3 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8991_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8991_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8991_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8991_AIF_TMF_I2S; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8991_AIF_TMF_RIGHTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8991_AIF_TMF_LEFTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8991_AIF_TMF_DSP; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8991_AIF_TMF_DSP | WM8991_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8991_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8991_MCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_MCLK_DIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_DACCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_DAC_CLKDIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_ADCCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_ADC_CLKDIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_BCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_1) & + ~WM8991_BCLK_DIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8991_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + u16 audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1); + + audio1 &= ~WM8991_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8991_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8991_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8991_AIF_WL_32BITS; + break; + } + + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8991_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val; + + val = snd_soc_read(codec, WM8991_DAC_CTRL) & ~WM8991_DAC_MUTE; + if (mute) + snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + else + snd_soc_write(codec, WM8991_DAC_CTRL, val); + return 0; +} + +static int wm8991_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + snd_soc_cache_sync(codec); + /* Enable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(300); + + /* Disable VMIDTOG */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL); + + /* disable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, 0); + + /* Enable outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1b00); + + msleep(50); + + /* Enable VMID at 2x50k */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f02); + + msleep(100); + + /* Enable VREF */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + msleep(600); + + /* Enable BUFIOEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* Disable outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_BUFIOEN); + } + + /* VMID=2*250k */ + val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_POBCTRL | WM8991_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* mute DAC */ + val = snd_soc_read(codec, WM8991_DAC_CTRL); + snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + + /* Enable any disabled outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f01); + + msleep(300); + + /* Enable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Disable VREF */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, 0x0); + codec->cache_sync = 1; + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static int wm8991_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8991_resume(struct snd_soc_codec *codec) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +/* power down chip */ +static int wm8991_remove(struct snd_soc_codec *codec) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8991_probe(struct snd_soc_codec *codec) +{ + struct wm8991_priv *wm8991; + int ret; + unsigned int reg; + + wm8991 = snd_soc_codec_get_drvdata(codec); + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8991->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret); + return ret; + } + + ret = wm8991_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + reg = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_4); + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_4, reg | WM8991_ALRCGPIO1); + + reg = snd_soc_read(codec, WM8991_GPIO1_GPIO2) & + ~WM8991_GPIO1_SEL_MASK; + snd_soc_write(codec, WM8991_GPIO1_GPIO2, reg | 1); + + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1); + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, reg | WM8991_VREF_ENA| + WM8991_VMID_MODE_MASK); + + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg | WM8991_OPCLK_ENA); + + snd_soc_write(codec, WM8991_DAC_CTRL, 0); + snd_soc_write(codec, WM8991_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_write(codec, WM8991_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + snd_soc_add_controls(codec, wm8991_snd_controls, + ARRAY_SIZE(wm8991_snd_controls)); + + snd_soc_dapm_new_controls(&codec->dapm, wm8991_dapm_widgets, + ARRAY_SIZE(wm8991_dapm_widgets)); + snd_soc_dapm_add_routes(&codec->dapm, audio_map, + ARRAY_SIZE(audio_map)); + return 0; +} + +#define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8991_ops = { + .hw_params = wm8991_hw_params, + .digital_mute = wm8991_mute, + .set_fmt = wm8991_set_dai_fmt, + .set_clkdiv = wm8991_set_dai_clkdiv, + .set_pll = wm8991_set_dai_pll +}; + +/* + * The WM8991 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +static struct snd_soc_dai_driver wm8991_dai = { + /* ADC/DAC on primary */ + .name = "wm8991", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .ops = &wm8991_ops +}; + +static struct snd_soc_codec_driver soc_codec_dev_wm8991 = { + .probe = wm8991_probe, + .remove = wm8991_remove, + .suspend = wm8991_suspend, + .resume = wm8991_resume, + .set_bias_level = wm8991_set_bias_level, + .reg_cache_size = WM8991_MAX_REGISTER + 1, + .reg_word_size = sizeof(u16), + .reg_cache_default = wm8991_reg_defs +}; + +static __devinit int wm8991_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8991_priv *wm8991; + int ret; + + wm8991 = kzalloc(sizeof *wm8991, GFP_KERNEL); + if (!wm8991) + return -ENOMEM; + + wm8991->control_type = SND_SOC_I2C; + i2c_set_clientdata(i2c, wm8991); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_wm8991, &wm8991_dai, 1); + if (ret < 0) + kfree(wm8991); + return ret; +} + +static __devexit int wm8991_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id wm8991_i2c_id[] = { + { "wm8991", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8991_i2c_id); + +static struct i2c_driver wm8991_i2c_driver = { + .driver = { + .name = "wm8991", + .owner = THIS_MODULE, + }, + .probe = wm8991_i2c_probe, + .remove = __devexit_p(wm8991_i2c_remove), + .id_table = wm8991_i2c_id, +}; + +static int __init wm8991_modinit(void) +{ + int ret; + ret = i2c_add_driver(&wm8991_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8991 I2C driver: %d\n", + ret); + } + return 0; +} +module_init(wm8991_modinit); + +static void __exit wm8991_exit(void) +{ + i2c_del_driver(&wm8991_i2c_driver); +} +module_exit(wm8991_exit); + +MODULE_DESCRIPTION("ASoC WM8991 driver"); +MODULE_AUTHOR("Graeme Gregory"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h new file mode 100644 index 00000000000..8a942efd18a --- /dev/null +++ b/sound/soc/codecs/wm8991.h @@ -0,0 +1,833 @@ +/* + * wm8991.h -- audio driver for WM8991 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8991_H +#define _WM8991_H + +/* + * Register values. + */ +#define WM8991_RESET 0x00 +#define WM8991_POWER_MANAGEMENT_1 0x01 +#define WM8991_POWER_MANAGEMENT_2 0x02 +#define WM8991_POWER_MANAGEMENT_3 0x03 +#define WM8991_AUDIO_INTERFACE_1 0x04 +#define WM8991_AUDIO_INTERFACE_2 0x05 +#define WM8991_CLOCKING_1 0x06 +#define WM8991_CLOCKING_2 0x07 +#define WM8991_AUDIO_INTERFACE_3 0x08 +#define WM8991_AUDIO_INTERFACE_4 0x09 +#define WM8991_DAC_CTRL 0x0A +#define WM8991_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8991_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8991_DIGITAL_SIDE_TONE 0x0D +#define WM8991_ADC_CTRL 0x0E +#define WM8991_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8991_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8991_GPIO_CTRL_1 0x12 +#define WM8991_GPIO1_GPIO2 0x13 +#define WM8991_GPIO3_GPIO4 0x14 +#define WM8991_GPIO5_GPIO6 0x15 +#define WM8991_GPIOCTRL_2 0x16 +#define WM8991_GPIO_POL 0x17 +#define WM8991_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8991_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8991_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8991_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8991_LEFT_OUTPUT_VOLUME 0x1C +#define WM8991_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8991_LINE_OUTPUTS_VOLUME 0x1E +#define WM8991_OUT3_4_VOLUME 0x1F +#define WM8991_LEFT_OPGA_VOLUME 0x20 +#define WM8991_RIGHT_OPGA_VOLUME 0x21 +#define WM8991_SPEAKER_VOLUME 0x22 +#define WM8991_CLASSD1 0x23 +#define WM8991_CLASSD3 0x25 +#define WM8991_INPUT_MIXER1 0x27 +#define WM8991_INPUT_MIXER2 0x28 +#define WM8991_INPUT_MIXER3 0x29 +#define WM8991_INPUT_MIXER4 0x2A +#define WM8991_INPUT_MIXER5 0x2B +#define WM8991_INPUT_MIXER6 0x2C +#define WM8991_OUTPUT_MIXER1 0x2D +#define WM8991_OUTPUT_MIXER2 0x2E +#define WM8991_OUTPUT_MIXER3 0x2F +#define WM8991_OUTPUT_MIXER4 0x30 +#define WM8991_OUTPUT_MIXER5 0x31 +#define WM8991_OUTPUT_MIXER6 0x32 +#define WM8991_OUT3_4_MIXER 0x33 +#define WM8991_LINE_MIXER1 0x34 +#define WM8991_LINE_MIXER2 0x35 +#define WM8991_SPEAKER_MIXER 0x36 +#define WM8991_ADDITIONAL_CONTROL 0x37 +#define WM8991_ANTIPOP1 0x38 +#define WM8991_ANTIPOP2 0x39 +#define WM8991_MICBIAS 0x3A +#define WM8991_PLL1 0x3C +#define WM8991_PLL2 0x3D +#define WM8991_PLL3 0x3E +#define WM8991_INTDRIVBITS 0x3F + +#define WM8991_REGISTER_COUNT 60 +#define WM8991_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8991_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8991_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8991_SPK_ENA_BIT 12 +#define WM8991_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8991_OUT3_ENA_BIT 11 +#define WM8991_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8991_OUT4_ENA_BIT 10 +#define WM8991_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8991_LOUT_ENA_BIT 9 +#define WM8991_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8991_ROUT_ENA_BIT 8 +#define WM8991_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8991_MICBIAS_ENA_BIT 4 +#define WM8991_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8991_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8991_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8991_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8991_PLL_ENA_BIT 15 +#define WM8991_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8991_TSHUT_ENA_BIT 14 +#define WM8991_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8991_TSHUT_OPDIS_BIT 13 +#define WM8991_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8991_OPCLK_ENA_BIT 11 +#define WM8991_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8991_AINL_ENA_BIT 9 +#define WM8991_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8991_AINR_ENA_BIT 8 +#define WM8991_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8991_LIN34_ENA_BIT 7 +#define WM8991_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8991_LIN12_ENA_BIT 6 +#define WM8991_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8991_RIN34_ENA_BIT 5 +#define WM8991_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8991_RIN12_ENA_BIT 4 +#define WM8991_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8991_ADCL_ENA_BIT 1 +#define WM8991_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8991_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8991_LON_ENA 0x2000 /* LON_ENA */ +#define WM8991_LON_ENA_BIT 13 +#define WM8991_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8991_LOP_ENA_BIT 12 +#define WM8991_RON_ENA 0x0800 /* RON_ENA */ +#define WM8991_RON_ENA_BIT 11 +#define WM8991_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8991_ROP_ENA_BIT 10 +#define WM8991_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8991_LOPGA_ENA_BIT 7 +#define WM8991_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8991_ROPGA_ENA_BIT 6 +#define WM8991_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8991_LOMIX_ENA_BIT 5 +#define WM8991_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8991_ROMIX_ENA_BIT 4 +#define WM8991_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8991_DACL_ENA_BIT 1 +#define WM8991_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8991_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8991_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8991_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8991_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8991_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8991_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8991_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8991_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8991_AIF_WL_16BITS (0 << 5) +#define WM8991_AIF_WL_20BITS (1 << 5) +#define WM8991_AIF_WL_24BITS (2 << 5) +#define WM8991_AIF_WL_32BITS (3 << 5) +#define WM8991_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8991_AIF_TMF_RIGHTJ (0 << 3) +#define WM8991_AIF_TMF_LEFTJ (1 << 3) +#define WM8991_AIF_TMF_I2S (2 << 3) +#define WM8991_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8991_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8991_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8991_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8991_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8991_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */ +#define WM8991_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8991_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8991_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8991_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8991_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8991_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8991_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8991_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8991_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8991_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8991_BCLK_DIV_1 (0x0 << 1) +#define WM8991_BCLK_DIV_1_5 (0x1 << 1) +#define WM8991_BCLK_DIV_2 (0x2 << 1) +#define WM8991_BCLK_DIV_3 (0x3 << 1) +#define WM8991_BCLK_DIV_4 (0x4 << 1) +#define WM8991_BCLK_DIV_5_5 (0x5 << 1) +#define WM8991_BCLK_DIV_6 (0x6 << 1) +#define WM8991_BCLK_DIV_8 (0x7 << 1) +#define WM8991_BCLK_DIV_11 (0x8 << 1) +#define WM8991_BCLK_DIV_12 (0x9 << 1) +#define WM8991_BCLK_DIV_16 (0xA << 1) +#define WM8991_BCLK_DIV_22 (0xB << 1) +#define WM8991_BCLK_DIV_24 (0xC << 1) +#define WM8991_BCLK_DIV_32 (0xD << 1) +#define WM8991_BCLK_DIV_44 (0xE << 1) +#define WM8991_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8991_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8991_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8991_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8991_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8991_MCLK_DIV_1 (0 << 11) +#define WM8991_MCLK_DIV_2 ( 2 << 11) +#define WM8991_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8991_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV - [7:5] */ +#define WM8991_ADC_CLKDIV_1 (0 << 5) +#define WM8991_ADC_CLKDIV_1_5 (1 << 5) +#define WM8991_ADC_CLKDIV_2 (2 << 5) +#define WM8991_ADC_CLKDIV_3 (3 << 5) +#define WM8991_ADC_CLKDIV_4 (4 << 5) +#define WM8991_ADC_CLKDIV_5_5 (5 << 5) +#define WM8991_ADC_CLKDIV_6 (6 << 5) +#define WM8991_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8991_DAC_CLKDIV_1 (0 << 2) +#define WM8991_DAC_CLKDIV_1_5 (1 << 2) +#define WM8991_DAC_CLKDIV_2 (2 << 2) +#define WM8991_DAC_CLKDIV_3 (3 << 2) +#define WM8991_DAC_CLKDIV_4 (4 << 2) +#define WM8991_DAC_CLKDIV_5_5 (5 << 2) +#define WM8991_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8991_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8991_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8991_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8991_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8991_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE - [10:0] */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8991_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8991_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8991_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8991_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8991_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE - [10:0] */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8991_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8991_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8991_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8991_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8991_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8991_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8991_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8991_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8991_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8991_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8991_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8991_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL - [12:9] */ +#define WM8991_ADCL_DAC_SVOL_SHIFT 9 +#define WM8991_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL - [8:5] */ +#define WM8991_ADCR_DAC_SVOL_SHIFT 5 +#define WM8991_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8991_ADC_TO_DACL_SHIFT 2 +#define WM8991_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8991_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8991_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8991_ADC_HPF_ENA_BIT 8 +#define WM8991_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8991_ADC_HPF_CUT_SHIFT 5 +#define WM8991_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8991_ADCL_DATINV_BIT 1 +#define WM8991_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8991_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8991_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8991_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8991_IRQ 0x1000 /* IRQ */ +#define WM8991_TEMPOK 0x0800 /* TEMPOK */ +#define WM8991_MICSHRT 0x0400 /* MICSHRT */ +#define WM8991_MICDET 0x0200 /* MICDET */ +#define WM8991_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8991_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8991_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8991_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8991_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8991_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8991_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8991_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8991_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8991_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8991_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8991_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8991_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8991_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8991_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8991_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8991_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8991_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8991_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8991_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8991_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8991_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8991_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8991_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8991_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8991_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8991_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8991_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8991_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8991_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8991_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8991_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8991_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8991_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8991_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8991_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8991_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8991_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8991_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8991_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8991_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8991_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8991_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8991_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8991_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8991_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8991_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8991_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8991_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8991_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8991_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8991_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8991_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8991_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8991_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8991_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8991_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8991_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8991_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8991_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8991_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8991_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8991_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8991_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8991_LI12MUTE_BIT 7 +#define WM8991_LI12ZC 0x0040 /* LI12ZC */ +#define WM8991_LI12ZC_BIT 6 +#define WM8991_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8991_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8991_LI34MUTE_BIT 7 +#define WM8991_LI34ZC 0x0040 /* LI34ZC */ +#define WM8991_LI34ZC_BIT 6 +#define WM8991_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8991_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8991_RI12MUTE_BIT 7 +#define WM8991_RI12ZC 0x0040 /* RI12ZC */ +#define WM8991_RI12ZC_BIT 6 +#define WM8991_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8991_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8991_RI34MUTE_BIT 7 +#define WM8991_RI34ZC 0x0040 /* RI34ZC */ +#define WM8991_RI34ZC_BIT 6 +#define WM8991_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8991_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOZC 0x0080 /* LOZC */ +#define WM8991_LOZC_BIT 7 +#define WM8991_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8991_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROZC 0x0080 /* ROZC */ +#define WM8991_ROZC_BIT 7 +#define WM8991_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8991_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8991_LONMUTE 0x0040 /* LONMUTE */ +#define WM8991_LONMUTE_BIT 6 +#define WM8991_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8991_LOPMUTE_BIT 5 +#define WM8991_LOATTN 0x0010 /* LOATTN */ +#define WM8991_LOATTN_BIT 4 +#define WM8991_RONMUTE 0x0004 /* RONMUTE */ +#define WM8991_RONMUTE_BIT 2 +#define WM8991_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8991_ROPMUTE_BIT 1 +#define WM8991_ROATTN 0x0001 /* ROATTN */ +#define WM8991_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8991_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8991_OUT3MUTE_BIT 5 +#define WM8991_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8991_OUT3ATTN_BIT 4 +#define WM8991_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8991_OUT4MUTE_BIT 1 +#define WM8991_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8991_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8991_LOPGAZC_BIT 7 +#define WM8991_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8991_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8991_ROPGAZC_BIT 7 +#define WM8991_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8991_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8991_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */ +#define WM8991_SPKVOL_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8991_CDMODE 0x0100 /* CDMODE */ +#define WM8991_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8991_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8991_DCGAIN_SHIFT 3 +#define WM8991_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8991_ACGAIN_SHIFT 0 +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8991_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8991_AINLMODE_SHIFT 2 +#define WM8991_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8991_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8991_LMP4 0x0080 /* LMP4 */ +#define WM8991_LMP4_BIT 7 /* LMP4 */ +#define WM8991_LMN3 0x0040 /* LMN3 */ +#define WM8991_LMN3_BIT 6 /* LMN3 */ +#define WM8991_LMP2 0x0020 /* LMP2 */ +#define WM8991_LMP2_BIT 5 /* LMP2 */ +#define WM8991_LMN1 0x0010 /* LMN1 */ +#define WM8991_LMN1_BIT 4 /* LMN1 */ +#define WM8991_RMP4 0x0008 /* RMP4 */ +#define WM8991_RMP4_BIT 3 /* RMP4 */ +#define WM8991_RMN3 0x0004 /* RMN3 */ +#define WM8991_RMN3_BIT 2 /* RMN3 */ +#define WM8991_RMP2 0x0002 /* RMP2 */ +#define WM8991_RMP2_BIT 1 /* RMP2 */ +#define WM8991_RMN1 0x0001 /* RMN1 */ +#define WM8991_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8991_L34MNB 0x0100 /* L34MNB */ +#define WM8991_L34MNB_BIT 8 +#define WM8991_L34MNBST 0x0080 /* L34MNBST */ +#define WM8991_L34MNBST_BIT 7 +#define WM8991_L12MNB 0x0020 /* L12MNB */ +#define WM8991_L12MNB_BIT 5 +#define WM8991_L12MNBST 0x0010 /* L12MNBST */ +#define WM8991_L12MNBST_BIT 4 +#define WM8991_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8991_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8991_R34MNB 0x0100 /* R34MNB */ +#define WM8991_R34MNB_BIT 8 +#define WM8991_R34MNBST 0x0080 /* R34MNBST */ +#define WM8991_R34MNBST_BIT 7 +#define WM8991_R12MNB 0x0020 /* R12MNB */ +#define WM8991_R12MNB_BIT 5 +#define WM8991_R12MNBST 0x0010 /* R12MNBST */ +#define WM8991_R12MNBST_BIT 4 +#define WM8991_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8991_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8991_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8991_LI2BVOL_SHIFT 6 +#define WM8991_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8991_LR4BVOL_SHIFT 3 +#define WM8991_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8991_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8991_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8991_RI2BVOL_SHIFT 6 +#define WM8991_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8991_RL4BVOL_SHIFT 3 +#define WM8991_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8991_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8991_LRBLO 0x0080 /* LRBLO */ +#define WM8991_LRBLO_BIT 7 +#define WM8991_LLBLO 0x0040 /* LLBLO */ +#define WM8991_LLBLO_BIT 6 +#define WM8991_LRI3LO 0x0020 /* LRI3LO */ +#define WM8991_LRI3LO_BIT 5 +#define WM8991_LLI3LO 0x0010 /* LLI3LO */ +#define WM8991_LLI3LO_BIT 4 +#define WM8991_LR12LO 0x0008 /* LR12LO */ +#define WM8991_LR12LO_BIT 3 +#define WM8991_LL12LO 0x0004 /* LL12LO */ +#define WM8991_LL12LO_BIT 2 +#define WM8991_LDLO 0x0001 /* LDLO */ +#define WM8991_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8991_RLBRO 0x0080 /* RLBRO */ +#define WM8991_RLBRO_BIT 7 +#define WM8991_RRBRO 0x0040 /* RRBRO */ +#define WM8991_RRBRO_BIT 6 +#define WM8991_RLI3RO 0x0020 /* RLI3RO */ +#define WM8991_RLI3RO_BIT 5 +#define WM8991_RRI3RO 0x0010 /* RRI3RO */ +#define WM8991_RRI3RO_BIT 4 +#define WM8991_RL12RO 0x0008 /* RL12RO */ +#define WM8991_RL12RO_BIT 3 +#define WM8991_RR12RO 0x0004 /* RR12RO */ +#define WM8991_RR12RO_BIT 2 +#define WM8991_RDRO 0x0001 /* RDRO */ +#define WM8991_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8991_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8991_LLI3LOVOL_SHIFT 6 +#define WM8991_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8991_LR12LOVOL_SHIFT 3 +#define WM8991_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8991_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8991_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8991_RRI3ROVOL_SHIFT 6 +#define WM8991_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8991_RL12ROVOL_SHIFT 3 +#define WM8991_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8991_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8991_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8991_LRI3LOVOL_SHIFT 6 +#define WM8991_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8991_LRBLOVOL_SHIFT 3 +#define WM8991_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8991_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8991_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8991_RLI3ROVOL_SHIFT 6 +#define WM8991_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8991_RLBROVOL_SHIFT 3 +#define WM8991_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8991_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8991_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8991_LI4O3 0x0020 /* LI4O3 */ +#define WM8991_LI4O3_BIT 5 +#define WM8991_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8991_LPGAO3_BIT 4 +#define WM8991_RI4O4 0x0002 /* RI4O4 */ +#define WM8991_RI4O4_BIT 1 +#define WM8991_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8991_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8991_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8991_LLOPGALON_BIT 6 +#define WM8991_LROPGALON 0x0020 /* LROPGALON */ +#define WM8991_LROPGALON_BIT 5 +#define WM8991_LOPLON 0x0010 /* LOPLON */ +#define WM8991_LOPLON_BIT 4 +#define WM8991_LR12LOP 0x0004 /* LR12LOP */ +#define WM8991_LR12LOP_BIT 2 +#define WM8991_LL12LOP 0x0002 /* LL12LOP */ +#define WM8991_LL12LOP_BIT 1 +#define WM8991_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8991_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8991_RROPGARON 0x0040 /* RROPGARON */ +#define WM8991_RROPGARON_BIT 6 +#define WM8991_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8991_RLOPGARON_BIT 5 +#define WM8991_ROPRON 0x0010 /* ROPRON */ +#define WM8991_ROPRON_BIT 4 +#define WM8991_RL12ROP 0x0004 /* RL12ROP */ +#define WM8991_RL12ROP_BIT 2 +#define WM8991_RR12ROP 0x0002 /* RR12ROP */ +#define WM8991_RR12ROP_BIT 1 +#define WM8991_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8991_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8991_LB2SPK 0x0080 /* LB2SPK */ +#define WM8991_LB2SPK_BIT 7 +#define WM8991_RB2SPK 0x0040 /* RB2SPK */ +#define WM8991_RB2SPK_BIT 6 +#define WM8991_LI2SPK 0x0020 /* LI2SPK */ +#define WM8991_LI2SPK_BIT 5 +#define WM8991_RI2SPK 0x0010 /* RI2SPK */ +#define WM8991_RI2SPK_BIT 4 +#define WM8991_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8991_LOPGASPK_BIT 3 +#define WM8991_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8991_ROPGASPK_BIT 2 +#define WM8991_LDSPK 0x0002 /* LDSPK */ +#define WM8991_LDSPK_BIT 1 +#define WM8991_RDSPK 0x0001 /* RDSPK */ +#define WM8991_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8991_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8991_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8991_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8991_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8991_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8991_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8991_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8991_SOFTST 0x0040 /* SOFTST */ +#define WM8991_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8991_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8991_POBCTRL 0x0002 /* POBCTRL */ +#define WM8991_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8991_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8991_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8991_MCD 0x0004 /* MCD */ +#define WM8991_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8991_SDM 0x0080 /* SDM */ +#define WM8991_PRESCALE 0x0040 /* PRESCALE */ +#define WM8991_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8991_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +/* + * R63 (0x3F) - Internal Driver Bits + */ +#define WM8991_INMIXL_PWR_BIT 0 +#define WM8991_AINLMUX_PWR_BIT 1 +#define WM8991_INMIXR_PWR_BIT 2 +#define WM8991_AINRMUX_PWR_BIT 3 + +#define WM8991_MCLK_DIV 0 +#define WM8991_DACCLK_DIV 1 +#define WM8991_ADCCLK_DIV 2 +#define WM8991_BCLK_DIV 3 + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + 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, \ + .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +#endif /* _WM8991_H */ diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 18c0d9ce7c3..379fa22c5b6 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -242,7 +242,7 @@ struct wm8993_priv { int fll_src; }; -static int wm8993_volatile(unsigned int reg) +static int wm8993_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8993_SOFTWARE_RESET: diff --git a/sound/soc/codecs/wm8994-tables.c b/sound/soc/codecs/wm8994-tables.c index 68e9b024dd4..a87adbd05ee 100644 --- a/sound/soc/codecs/wm8994-tables.c +++ b/sound/soc/codecs/wm8994-tables.c @@ -62,8 +62,8 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x00FF, 0x00FF }, /* R58 - MICBIAS */ { 0x000F, 0x000F }, /* R59 - LDO 1 */ { 0x0007, 0x0007 }, /* R60 - LDO 2 */ - { 0x0000, 0x0000 }, /* R61 */ - { 0x0000, 0x0000 }, /* R62 */ + { 0xFFFF, 0xFFFF }, /* R61 */ + { 0xFFFF, 0xFFFF }, /* R62 */ { 0x0000, 0x0000 }, /* R63 */ { 0x0000, 0x0000 }, /* R64 */ { 0x0000, 0x0000 }, /* R65 */ @@ -209,9 +209,9 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x0000, 0x0000 }, /* R205 */ { 0x0000, 0x0000 }, /* R206 */ { 0x0000, 0x0000 }, /* R207 */ - { 0x0000, 0x0000 }, /* R208 */ - { 0x0000, 0x0000 }, /* R209 */ - { 0x0000, 0x0000 }, /* R210 */ + { 0xFFFF, 0xFFFF }, /* R208 */ + { 0xFFFF, 0xFFFF }, /* R209 */ + { 0xFFFF, 0xFFFF }, /* R210 */ { 0x0000, 0x0000 }, /* R211 */ { 0x0000, 0x0000 }, /* R212 */ { 0x0000, 0x0000 }, /* R213 */ @@ -1573,7 +1573,7 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = { { 0x03C3, 0x03C3 }, /* R1569 - Sidetone */ }; -const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = { +const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = { 0x8994, /* R0 - Software Reset */ 0x0000, /* R1 - Power Management (1) */ 0x6000, /* R2 - Power Management (2) */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index ebaee5ca743..3dc64c8b6a5 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -102,17 +102,19 @@ struct wm8994_priv { wm8958_micdet_cb jack_cb; void *jack_cb_data; - bool jack_is_mic; - bool jack_is_video; + int micdet_irq; int revision; struct wm8994_pdata *pdata; unsigned int aif1clk_enable:1; unsigned int aif2clk_enable:1; + + unsigned int aif1clk_disable:1; + unsigned int aif2clk_disable:1; }; -static int wm8994_readable(unsigned int reg) +static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8994_GPIO_1: @@ -139,7 +141,7 @@ static int wm8994_readable(unsigned int reg) return wm8994_access_masks[reg].readable != 0; } -static int wm8994_volatile(unsigned int reg) +static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg) { if (reg >= WM8994_CACHE_SIZE) return 1; @@ -167,7 +169,7 @@ static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, BUG_ON(reg > WM8994_MAX_REGISTER); - if (!wm8994_volatile(reg)) { + if (!wm8994_volatile(codec, reg)) { ret = snd_soc_cache_write(codec, reg, value); if (ret != 0) dev_err(codec->dev, "Cache write to %x failed: %d\n", @@ -185,7 +187,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec, BUG_ON(reg > WM8994_MAX_REGISTER); - if (!wm8994_volatile(reg) && wm8994_readable(reg) && + if (!wm8994_volatile(codec, reg) && wm8994_readable(codec, reg) && reg < codec->driver->reg_cache_size) { ret = snd_soc_cache_read(codec, reg, &val); if (ret >= 0) @@ -526,7 +528,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 =snd_soc_codec_get_drvdata(codec); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int block = wm8994_get_retune_mobile_block(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; @@ -1015,14 +1017,18 @@ static int late_enable_ev(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - if (wm8994->aif1clk_enable) + if (wm8994->aif1clk_enable) { snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, WM8994_AIF1CLK_ENA_MASK, WM8994_AIF1CLK_ENA); - if (wm8994->aif2clk_enable) + wm8994->aif1clk_enable = 0; + } + if (wm8994->aif2clk_enable) { snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, WM8994_AIF2CLK_ENA_MASK, WM8994_AIF2CLK_ENA); + wm8994->aif2clk_enable = 0; + } break; } @@ -1037,15 +1043,15 @@ static int late_disable_ev(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_POST_PMD: - if (wm8994->aif1clk_enable) { + if (wm8994->aif1clk_disable) { snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, WM8994_AIF1CLK_ENA_MASK, 0); - wm8994->aif1clk_enable = 0; + wm8994->aif1clk_disable = 0; } - if (wm8994->aif2clk_enable) { + if (wm8994->aif2clk_disable) { snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, WM8994_AIF2CLK_ENA_MASK, 0); - wm8994->aif2clk_enable = 0; + wm8994->aif2clk_disable = 0; } break; } @@ -1063,6 +1069,9 @@ static int aif1clk_ev(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMU: wm8994->aif1clk_enable = 1; break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif1clk_disable = 1; + break; } return 0; @@ -1078,11 +1087,28 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMU: wm8994->aif2clk_enable = 1; break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif2clk_disable = 1; + break; } return 0; } +static int adc_mux_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + +static int micbias_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + static int dac_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -1398,16 +1424,32 @@ SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0, static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = { SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0), -SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), +SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0), SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0), }; +static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = { +SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = { +SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), +SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), +}; + static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DMIC1DAT"), SND_SOC_DAPM_INPUT("DMIC2DAT"), SND_SOC_DAPM_INPUT("Clock"), +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8994_MICBIAS, 2, 0), +SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -1497,9 +1539,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0), SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0), SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), -SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), -SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), - SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), @@ -1726,6 +1765,8 @@ static const struct snd_soc_dapm_route wm8994_revd_intercon[] = { { "AIF2DACDAT", NULL, "AIF1DACDAT" }, { "AIF1ADCDAT", NULL, "AIF2ADCDAT" }, { "AIF2ADCDAT", NULL, "AIF1ADCDAT" }, + { "MICBIAS", NULL, "CLK_SYS" }, + { "MICBIAS", NULL, "MICBIAS Supply" }, }; static const struct snd_soc_dapm_route wm8994_intercon[] = { @@ -2854,6 +2895,13 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994) else snd_soc_add_controls(wm8994->codec, wm8994_eq_controls, ARRAY_SIZE(wm8994_eq_controls)); + + for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) { + if (pdata->micbias[i]) { + snd_soc_write(codec, WM8958_MICBIAS1 + i, + pdata->micbias[i] & 0xffff); + } + } } /** @@ -2964,46 +3012,18 @@ static void wm8958_default_micdet(u16 status, void *data) int report = 0; /* If nothing present then clear our statuses */ - if (!(status & WM8958_MICD_STS)) { - wm8994->jack_is_video = false; - wm8994->jack_is_mic = false; + if (!(status & WM8958_MICD_STS)) goto done; - } - - /* Assume anything over 475 ohms is a microphone and remember - * that we've seen one (since buttons override it) */ - if (status & 0x600) - wm8994->jack_is_mic = true; - if (wm8994->jack_is_mic) - report |= SND_JACK_MICROPHONE; - /* Video has an impedence of approximately 75 ohms; assume - * this isn't used as a button and remember it since buttons - * override it. */ - if (status & 0x40) - wm8994->jack_is_video = true; - if (wm8994->jack_is_video) - report |= SND_JACK_VIDEOOUT; + report = SND_JACK_MICROPHONE; /* Everything else is buttons; just assign slots */ - if (status & 0x4) + if (status & 0x1c0) report |= SND_JACK_BTN_0; - if (status & 0x8) - report |= SND_JACK_BTN_1; - if (status & 0x10) - report |= SND_JACK_BTN_2; - if (status & 0x20) - report |= SND_JACK_BTN_3; - if (status & 0x80) - report |= SND_JACK_BTN_4; - if (status & 0x100) - report |= SND_JACK_BTN_5; done: snd_soc_jack_report(wm8994->micdet[0].jack, report, - SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5 | - SND_JACK_MICROPHONE | SND_JACK_VIDEOOUT); + SND_JACK_BTN_0 | SND_JACK_MICROPHONE); } /** @@ -3102,13 +3122,19 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->pdata = dev_get_platdata(codec->dev->parent); wm8994->codec = codec; + if (wm8994->pdata && wm8994->pdata->micdet_irq) + wm8994->micdet_irq = wm8994->pdata->micdet_irq; + else if (wm8994->pdata && wm8994->pdata->irq_base) + wm8994->micdet_irq = wm8994->pdata->irq_base + + WM8994_IRQ_MIC1_DET; + pm_runtime_enable(codec->dev); pm_runtime_resume(codec->dev); /* Read our current status back from the chip - we don't want to * reset as this may interfere with the GPIO or LDO operation. */ for (i = 0; i < WM8994_CACHE_SIZE; i++) { - if (!wm8994_readable(i) || wm8994_volatile(i)) + if (!wm8994_readable(codec, i) || wm8994_volatile(codec, i)) continue; ret = wm8994_reg_read(codec->control_data, i); @@ -3150,14 +3176,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) switch (control->type) { case WM8994: - ret = wm8994_request_irq(codec->control_data, - WM8994_IRQ_MIC1_DET, - wm8994_mic_irq, "Mic 1 detect", - wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic1 detect IRQ: %d\n", - ret); + if (wm8994->micdet_irq) { + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8994_mic_irq, + IRQF_TRIGGER_RISING, + "Mic1 detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic1 detect IRQ: %d\n", + ret); + } ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, @@ -3188,15 +3217,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; case WM8958: - ret = wm8994_request_irq(codec->control_data, - WM8994_IRQ_MIC1_DET, - wm8958_mic_irq, "Mic detect", - wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic detect IRQ: %d\n", - ret); - break; + if (wm8994->micdet_irq) { + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8958_mic_irq, + IRQF_TRIGGER_RISING, + "Mic detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic detect IRQ: %d\n", + ret); + } } /* Remember if AIFnLRCLK is configured as a GPIO. This should be @@ -3280,11 +3311,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) if (wm8994->revision < 4) { snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets, ARRAY_SIZE(wm8994_lateclk_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets, + ARRAY_SIZE(wm8994_adc_revd_widgets)); snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets, ARRAY_SIZE(wm8994_dac_revd_widgets)); } else { snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, ARRAY_SIZE(wm8994_dac_widgets)); } @@ -3292,6 +3327,12 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) case WM8958: snd_soc_add_controls(codec, wm8958_snd_controls, ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, ARRAY_SIZE(wm8958_dapm_widgets)); break; @@ -3317,6 +3358,8 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) } break; case WM8958: + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); snd_soc_dapm_add_routes(dapm, wm8958_intercon, ARRAY_SIZE(wm8958_intercon)); break; @@ -3328,7 +3371,8 @@ err_irq: wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); err: kfree(wm8994); return ret; @@ -3345,8 +3389,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) switch (control->type) { case WM8994: - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, - wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, @@ -3356,8 +3400,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) break; case WM8958: - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, - wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); break; } kfree(wm8994->retune_mobile_texts); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 0c355bfc88f..999b8851226 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -43,6 +43,6 @@ struct wm8994_access_mask { }; extern const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE]; -extern const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE]; +extern const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE]; #endif diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c index 608c84c5aa8..67eaaecbb42 100644 --- a/sound/soc/codecs/wm8995.c +++ b/sound/soc/codecs/wm8995.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -30,6 +31,18 @@ #include "wm8995.h" +#define WM8995_NUM_SUPPLIES 8 +static const char *wm8995_supply_names[WM8995_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD1", + "DBVDD2", + "DBVDD3", + "AVDD1", + "AVDD2", + "CPVDD", + "MICVDD" +}; + static const u16 wm8995_reg_defs[WM8995_MAX_REGISTER + 1] = { [0] = 0x8995, [5] = 0x0100, [16] = 0x000b, [17] = 0x000b, [24] = 0x02c0, [25] = 0x02c0, [26] = 0x02c0, [27] = 0x02c0, @@ -126,8 +139,37 @@ struct wm8995_priv { int mclk[2]; int aifclk[2]; struct fll_config fll[2], fll_suspend[2]; + struct regulator_bulk_data supplies[WM8995_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8995_NUM_SUPPLIES]; + struct snd_soc_codec *codec; }; +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8995_REGULATOR_EVENT(n) \ +static int wm8995_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8995_priv *wm8995 = container_of(nb, struct wm8995_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + wm8995->codec->cache_sync = 1; \ + } \ + return 0; \ +} + +WM8995_REGULATOR_EVENT(0) +WM8995_REGULATOR_EVENT(1) +WM8995_REGULATOR_EVENT(2) +WM8995_REGULATOR_EVENT(3) +WM8995_REGULATOR_EVENT(4) +WM8995_REGULATOR_EVENT(5) +WM8995_REGULATOR_EVENT(6) +WM8995_REGULATOR_EVENT(7) + static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); static const DECLARE_TLV_DB_SCALE(in1lr_pga_tlv, -1650, 150, 0); static const DECLARE_TLV_DB_SCALE(in1l_boost_tlv, 0, 600, 0); @@ -909,7 +951,7 @@ static const struct snd_soc_dapm_route wm8995_intercon[] = { { "SPK2R", NULL, "SPK2R Driver" } }; -static int wm8995_volatile(unsigned int reg) +static int wm8995_volatile(struct snd_soc_codec *codec, unsigned int reg) { /* out of bounds registers are generally considered * volatile to support register banks that are partially @@ -1483,6 +1525,11 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) + return ret; + ret = snd_soc_cache_sync(codec); if (ret) { dev_err(codec->dev, @@ -1492,12 +1539,13 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec, snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1, WM8995_BG_ENA_MASK, WM8995_BG_ENA); - } break; case SND_SOC_BIAS_OFF: snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1, WM8995_BG_ENA_MASK, 0); + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); break; } @@ -1536,10 +1584,12 @@ static int wm8995_remove(struct snd_soc_codec *codec) static int wm8995_probe(struct snd_soc_codec *codec) { struct wm8995_priv *wm8995; + int i; int ret; codec->dapm.idle_bias_off = 1; wm8995 = snd_soc_codec_get_drvdata(codec); + wm8995->codec = codec; ret = snd_soc_codec_set_cache_io(codec, 16, 16, wm8995->control_type); if (ret < 0) { @@ -1547,21 +1597,58 @@ static int wm8995_probe(struct snd_soc_codec *codec) return ret; } + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) + wm8995->supplies[i].supply = wm8995_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8995->disable_nb[0].notifier_call = wm8995_regulator_event_0; + wm8995->disable_nb[1].notifier_call = wm8995_regulator_event_1; + wm8995->disable_nb[2].notifier_call = wm8995_regulator_event_2; + wm8995->disable_nb[3].notifier_call = wm8995_regulator_event_3; + wm8995->disable_nb[4].notifier_call = wm8995_regulator_event_4; + wm8995->disable_nb[5].notifier_call = wm8995_regulator_event_5; + wm8995->disable_nb[6].notifier_call = wm8995_regulator_event_6; + wm8995->disable_nb[7].notifier_call = wm8995_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) { + ret = regulator_register_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); + if (ret) { + dev_err(codec->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_reg_get; + } + ret = snd_soc_read(codec, WM8995_SOFTWARE_RESET); if (ret < 0) { dev_err(codec->dev, "Failed to read device ID: %d\n", ret); - return ret; + goto err_reg_enable; } if (ret != 0x8995) { dev_err(codec->dev, "Invalid device ID: %#x\n", ret); - return -EINVAL; + goto err_reg_enable; } ret = snd_soc_write(codec, WM8995_SOFTWARE_RESET, 0); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset: %d\n", ret); - return ret; + goto err_reg_enable; } wm8995_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1596,6 +1683,12 @@ static int wm8995_probe(struct snd_soc_codec *codec) ARRAY_SIZE(wm8995_intercon)); return 0; + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); +err_reg_get: + regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); + return ret; } #define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 43825b2102a..55cdf298202 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -15,6 +15,7 @@ #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/device.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> @@ -166,10 +167,10 @@ struct wm9081_priv { int fll_fref; int fll_fout; int tdm_width; - struct wm9081_retune_mobile_config *retune; + struct wm9081_pdata pdata; }; -static int wm9081_volatile_register(unsigned int reg) +static int wm9081_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM9081_SOFTWARE_RESET: @@ -388,27 +389,6 @@ SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0), SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0), }; -static int speaker_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct snd_soc_codec *codec = w->codec; - unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT); - - switch (event) { - case SND_SOC_DAPM_POST_PMU: - reg |= WM9081_SPK_ENA; - break; - - case SND_SOC_DAPM_PRE_PMD: - reg &= ~WM9081_SPK_ENA; - break; - } - - snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg); - - return 0; -} - struct _fll_div { u16 fll_fratio; u16 fll_outdiv; @@ -746,9 +726,8 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), -SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0, - speaker_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LINEOUT"), SND_SOC_DAPM_OUTPUT("SPKN"), @@ -761,7 +740,7 @@ SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0), }; -static const struct snd_soc_dapm_route audio_paths[] = { +static const struct snd_soc_dapm_route wm9081_audio_paths[] = { { "DAC", NULL, "CLK_SYS" }, { "DAC", NULL, "CLK_DSP" }, @@ -779,8 +758,10 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Speaker PGA", NULL, "TOCLK" }, { "Speaker PGA", NULL, "CLK_SYS" }, - { "SPKN", NULL, "Speaker PGA" }, - { "SPKP", NULL, "Speaker PGA" }, + { "Speaker", NULL, "Speaker PGA" }, + + { "SPKN", NULL, "Speaker" }, + { "SPKP", NULL, "Speaker" }, }; static int wm9081_set_bias_level(struct snd_soc_codec *codec, @@ -1081,21 +1062,22 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, aif4 |= wm9081->bclk / wm9081->fs; /* Apply a ReTune Mobile configuration if it's in use */ - if (wm9081->retune) { - struct wm9081_retune_mobile_config *retune = wm9081->retune; + if (wm9081->pdata.num_retune_configs) { + struct wm9081_pdata *pdata = &wm9081->pdata; struct wm9081_retune_mobile_setting *s; int eq1; best = 0; - best_val = abs(retune->configs[0].rate - wm9081->fs); - for (i = 0; i < retune->num_configs; i++) { - cur_val = abs(retune->configs[i].rate - wm9081->fs); + best_val = abs(pdata->retune_configs[0].rate - wm9081->fs); + for (i = 0; i < pdata->num_retune_configs; i++) { + cur_val = abs(pdata->retune_configs[i].rate - + wm9081->fs); if (cur_val < best_val) { best_val = cur_val; best = i; } } - s = &retune->configs[best]; + s = &pdata->retune_configs[best]; dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n", s->name, s->rate); @@ -1138,10 +1120,9 @@ static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) return 0; } -static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, +static int wm9081_set_sysclk(struct snd_soc_codec *codec, int clk_id, unsigned int freq, int dir) { - struct snd_soc_codec *codec = codec_dai->codec; struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { @@ -1206,7 +1187,6 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, static struct snd_soc_dai_ops wm9081_dai_ops = { .hw_params = wm9081_hw_params, - .set_sysclk = wm9081_set_sysclk, .set_fmt = wm9081_set_dai_fmt, .digital_mute = wm9081_digital_mute, .set_tdm_slot = wm9081_set_tdm_slot, @@ -1230,7 +1210,6 @@ static struct snd_soc_dai_driver wm9081_dai = { static int wm9081_probe(struct snd_soc_codec *codec) { struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); - struct snd_soc_dapm_context *dapm = &codec->dapm; int ret; u16 reg; @@ -1254,6 +1233,14 @@ static int wm9081_probe(struct snd_soc_codec *codec) return ret; } + reg = 0; + if (wm9081->pdata.irq_high) + reg |= WM9081_IRQ_POL; + if (!wm9081->pdata.irq_cmos) + reg |= WM9081_IRQ_OP_CTRL; + snd_soc_update_bits(codec, WM9081_INTERRUPT_CONTROL, + WM9081_IRQ_POL | WM9081_IRQ_OP_CTRL, reg); + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Enable zero cross by default */ @@ -1265,17 +1252,13 @@ static int wm9081_probe(struct snd_soc_codec *codec) snd_soc_add_controls(codec, wm9081_snd_controls, ARRAY_SIZE(wm9081_snd_controls)); - if (!wm9081->retune) { + if (!wm9081->pdata.num_retune_configs) { dev_dbg(codec->dev, "No ReTune Mobile data, using normal EQ\n"); snd_soc_add_controls(codec, wm9081_eq_controls, ARRAY_SIZE(wm9081_eq_controls)); } - snd_soc_dapm_new_controls(dapm, wm9081_dapm_widgets, - ARRAY_SIZE(wm9081_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); - return ret; } @@ -1319,11 +1302,19 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9081 = { .remove = wm9081_remove, .suspend = wm9081_suspend, .resume = wm9081_resume, + + .set_sysclk = wm9081_set_sysclk, .set_bias_level = wm9081_set_bias_level, + .reg_cache_size = ARRAY_SIZE(wm9081_reg_defaults), .reg_word_size = sizeof(u16), .reg_cache_default = wm9081_reg_defaults, .volatile_register = wm9081_volatile_register, + + .dapm_widgets = wm9081_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9081_dapm_widgets), + .dapm_routes = wm9081_audio_paths, + .num_dapm_routes = ARRAY_SIZE(wm9081_audio_paths), }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -1341,6 +1332,10 @@ static __devinit int wm9081_i2c_probe(struct i2c_client *i2c, wm9081->control_type = SND_SOC_I2C; wm9081->control_data = i2c; + if (dev_get_platdata(&i2c->dev)) + memcpy(&wm9081->pdata, dev_get_platdata(&i2c->dev), + sizeof(wm9081->pdata)); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm9081, &wm9081_dai, 1); if (ret < 0) @@ -1363,7 +1358,7 @@ MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id); static struct i2c_driver wm9081_i2c_driver = { .driver = { - .name = "wm9081-codec", + .name = "wm9081", .owner = THIS_MODULE, }, .probe = wm9081_i2c_probe, diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c index a788c429704..4de12203e61 100644 --- a/sound/soc/codecs/wm9090.c +++ b/sound/soc/codecs/wm9090.c @@ -144,7 +144,7 @@ struct wm9090_priv { void *control_data; }; -static int wm9090_volatile(unsigned int reg) +static int wm9090_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM9090_SOFTWARE_RESET: @@ -518,7 +518,7 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec, for (i = 1; i < codec->driver->reg_cache_size; i++) { if (reg_cache[i] == wm9090_reg_defaults[i]) continue; - if (wm9090_volatile(i)) + if (wm9090_volatile(codec, i)) continue; ret = snd_soc_write(codec, i, reg_cache[i]); @@ -551,7 +551,6 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec, static int wm9090_probe(struct snd_soc_codec *codec) { struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret; codec->control_data = wm9090->control_data; @@ -576,22 +575,30 @@ static int wm9090_probe(struct snd_soc_codec *codec) /* Configure some defaults; they will be written out when we * bring the bias up. */ - reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU - | WM9090_IN1A_ZC; - reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU - | WM9090_IN1B_ZC; - reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU - | WM9090_IN2A_ZC; - reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU - | WM9090_IN2B_ZC; - reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |= - WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC; - reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |= - WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC; - reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |= - WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC; - - reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA; + snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_A_VOLUME, + WM9090_IN1_VU | WM9090_IN1A_ZC, + WM9090_IN1_VU | WM9090_IN1A_ZC); + snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_B_VOLUME, + WM9090_IN1_VU | WM9090_IN1B_ZC, + WM9090_IN1_VU | WM9090_IN1B_ZC); + snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_A_VOLUME, + WM9090_IN2_VU | WM9090_IN2A_ZC, + WM9090_IN2_VU | WM9090_IN2A_ZC); + snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_B_VOLUME, + WM9090_IN2_VU | WM9090_IN2B_ZC, + WM9090_IN2_VU | WM9090_IN2B_ZC); + snd_soc_update_bits(codec, WM9090_SPEAKER_VOLUME_LEFT, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC); + snd_soc_update_bits(codec, WM9090_LEFT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC); + snd_soc_update_bits(codec, WM9090_RIGHT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC); + + snd_soc_update_bits(codec, WM9090_CLOCKING_1, + WM9090_TOCLK_ENA, WM9090_TOCLK_ENA); wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 51689270606..7b6b3c18e29 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -82,7 +82,8 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) } while (reg & op && count < 400); if (reg & op) - dev_err(codec->dev, "Timed out waiting for DC Servo\n"); + dev_err(codec->dev, "Timed out waiting for DC Servo %x\n", + op); } /* diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 9e0e565e6ed..d0d60b8a54d 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -658,7 +658,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) return -ENODEV; } - ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, + ioarea = request_mem_region(mem->start, resource_size(mem), pdev->name); if (!ioarea) { dev_err(&pdev->dev, "McBSP region already claimed\n"); @@ -694,20 +694,25 @@ static int davinci_i2s_probe(struct platform_device *pdev) } clk_enable(dev->clk); - dev->base = (void __iomem *)IO_ADDRESS(mem->start); + dev->base = ioremap(mem->start, resource_size(mem)); + if (!dev->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release_clk; + } dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr = - (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DXR_REG); + (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG); dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr = - (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DRR_REG); + (dma_addr_t)(mem->start + DAVINCI_MCBSP_DRR_REG); /* first TX, then RX */ res = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); ret = -ENXIO; - goto err_free_mem; + goto err_iounmap; } dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = res->start; @@ -715,7 +720,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); ret = -ENXIO; - goto err_free_mem; + goto err_iounmap; } dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start; dev->dev = &pdev->dev; @@ -724,14 +729,19 @@ static int davinci_i2s_probe(struct platform_device *pdev) ret = snd_soc_register_dai(&pdev->dev, &davinci_i2s_dai); if (ret != 0) - goto err_free_mem; + goto err_iounmap; return 0; +err_iounmap: + iounmap(dev->base); +err_release_clk: + clk_disable(dev->clk); + clk_put(dev->clk); err_free_mem: kfree(dev); err_release_region: - release_mem_region(mem->start, (mem->end - mem->start) + 1); + release_mem_region(mem->start, resource_size(mem)); return ret; } @@ -747,7 +757,7 @@ static int davinci_i2s_remove(struct platform_device *pdev) dev->clk = NULL; kfree(dev); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(mem->start, (mem->end - mem->start) + 1); + release_mem_region(mem->start, resource_size(mem)); return 0; } diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index fb55d2c5d70..a5af834c8ef 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -868,7 +868,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev) } ioarea = request_mem_region(mem->start, - (mem->end - mem->start) + 1, pdev->name); + resource_size(mem), pdev->name); if (!ioarea) { dev_err(&pdev->dev, "Audio region already claimed\n"); ret = -EBUSY; @@ -885,7 +885,13 @@ static int davinci_mcasp_probe(struct platform_device *pdev) clk_enable(dev->clk); dev->clk_active = 1; - dev->base = (void __iomem *)IO_ADDRESS(mem->start); + dev->base = ioremap(mem->start, resource_size(mem)); + if (!dev->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release_clk; + } + dev->op_mode = pdata->op_mode; dev->tdm_slots = pdata->tdm_slots; dev->num_serializer = pdata->num_serializer; @@ -899,14 +905,14 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->asp_chan_q = pdata->asp_chan_q; dma_data->ram_chan_q = pdata->ram_chan_q; dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset + - io_v2p(dev->base)); + mem->start); /* first TX, then RX */ res = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); ret = -ENODEV; - goto err_release_region; + goto err_iounmap; } dma_data->channel = res->start; @@ -915,13 +921,13 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->asp_chan_q = pdata->asp_chan_q; dma_data->ram_chan_q = pdata->ram_chan_q; dma_data->dma_addr = (dma_addr_t)(pdata->rx_dma_offset + - io_v2p(dev->base)); + mem->start); res = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); ret = -ENODEV; - goto err_release_region; + goto err_iounmap; } dma_data->channel = res->start; @@ -929,11 +935,16 @@ static int davinci_mcasp_probe(struct platform_device *pdev) ret = snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]); if (ret != 0) - goto err_release_region; + goto err_iounmap; return 0; +err_iounmap: + iounmap(dev->base); +err_release_clk: + clk_disable(dev->clk); + clk_put(dev->clk); err_release_region: - release_mem_region(mem->start, (mem->end - mem->start) + 1); + release_mem_region(mem->start, resource_size(mem)); err_release_data: kfree(dev); @@ -951,7 +962,7 @@ static int davinci_mcasp_remove(struct platform_device *pdev) dev->clk = NULL; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(mem->start, (mem->end - mem->start) + 1); + release_mem_region(mem->start, resource_size(mem)); kfree(dev); diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig index 57429041189..91a28de9410 100644 --- a/sound/soc/ep93xx/Kconfig +++ b/sound/soc/ep93xx/Kconfig @@ -30,3 +30,12 @@ config SND_EP93XX_SOC_SIMONE help Say Y or M here if you want to add support for AC97 audio on the Simplemachines Sim.One board. + +config SND_EP93XX_SOC_EDB93XX + tristate "SoC Audio support for Cirrus Logic EDB93xx boards" + depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A) + select SND_EP93XX_SOC_I2S + select SND_SOC_CS4271 + help + Say Y or M here if you want to add support for I2S audio on the + Cirrus Logic EDB93xx boards. diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile index 8e7977fb6b7..5514146cbdf 100644 --- a/sound/soc/ep93xx/Makefile +++ b/sound/soc/ep93xx/Makefile @@ -10,6 +10,8 @@ obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o # EP93XX Machine Support snd-soc-snappercl15-objs := snappercl15.o snd-soc-simone-objs := simone.o +snd-soc-edb93xx-objs := edb93xx.o obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o +obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o diff --git a/sound/soc/ep93xx/edb93xx.c b/sound/soc/ep93xx/edb93xx.c new file mode 100644 index 00000000000..d3aa15119d2 --- /dev/null +++ b/sound/soc/ep93xx/edb93xx.c @@ -0,0 +1,142 @@ +/* + * SoC audio for EDB93xx + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include "ep93xx-pcm.h" + +#define edb93xx_has_audio() (machine_is_edb9301() || \ + machine_is_edb9302() || \ + machine_is_edb9302a() || \ + machine_is_edb9307a() || \ + machine_is_edb9315a()) + +static int edb93xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int err; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + + /* + * According to CS4271 datasheet we use MCLK/LRCK=256 for + * rates below 50kHz and 128 for higher sample rates + */ + if (rate < 50000) + mclk_rate = rate * 64 * 4; + else + mclk_rate = rate * 64 * 2; + + err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + if (err) + return err; + + err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, + SND_SOC_CLOCK_IN); + if (err) + return err; + + return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, + SND_SOC_CLOCK_OUT); +} + +static struct snd_soc_ops edb93xx_ops = { + .hw_params = edb93xx_hw_params, +}; + +static struct snd_soc_dai_link edb93xx_dai = { + .name = "CS4271", + .stream_name = "CS4271 HiFi", + .platform_name = "ep93xx-pcm-audio", + .cpu_dai_name = "ep93xx-i2s", + .codec_name = "spi0.0", + .codec_dai_name = "cs4271-hifi", + .ops = &edb93xx_ops, +}; + +static struct snd_soc_card snd_soc_edb93xx = { + .name = "EDB93XX", + .dai_link = &edb93xx_dai, + .num_links = 1, +}; + +static struct platform_device *edb93xx_snd_device; + +static int __init edb93xx_init(void) +{ + int ret; + + if (!edb93xx_has_audio()) + return -ENODEV; + + ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97, + EP93XX_SYSCON_I2SCLKDIV_ORIDE | + EP93XX_SYSCON_I2SCLKDIV_SPOL); + if (ret) + return ret; + + edb93xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!edb93xx_snd_device) { + ret = -ENOMEM; + goto free_i2s; + } + + platform_set_drvdata(edb93xx_snd_device, &snd_soc_edb93xx); + ret = platform_device_add(edb93xx_snd_device); + if (ret) + goto device_put; + + return 0; + +device_put: + platform_device_put(edb93xx_snd_device); +free_i2s: + ep93xx_i2s_release(); + return ret; +} +module_init(edb93xx_init); + +static void __exit edb93xx_exit(void) +{ + platform_device_unregister(edb93xx_snd_device); + ep93xx_i2s_release(); +} +module_exit(edb93xx_exit); + +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_DESCRIPTION("ALSA SoC EDB93xx"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ep93xx/ep93xx-ac97.c b/sound/soc/ep93xx/ep93xx-ac97.c index 68a0bae1208..104e95cda0a 100644 --- a/sound/soc/ep93xx/ep93xx-ac97.c +++ b/sound/soc/ep93xx/ep93xx-ac97.c @@ -253,7 +253,6 @@ static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream, struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai); unsigned v = 0; - switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c index fff579a1c13..042f4e93746 100644 --- a/sound/soc/ep93xx/ep93xx-i2s.c +++ b/sound/soc/ep93xx/ep93xx-i2s.c @@ -242,7 +242,7 @@ static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream, { struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); unsigned word_len, div, sdiv, lrdiv; - int found = 0, err; + int err; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: @@ -275,15 +275,14 @@ static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream, * the codec uses. */ div = clk_get_rate(info->mclk) / params_rate(params); - for (sdiv = 2; sdiv <= 4; sdiv += 2) - for (lrdiv = 64; lrdiv <= 128; lrdiv <<= 1) - if (sdiv * lrdiv == div) { - found = 1; - goto out; - } -out: - if (!found) - return -EINVAL; + sdiv = 4; + if (div > (256 + 512) / 2) { + lrdiv = 128; + } else { + lrdiv = 64; + if (div < (128 + 256) / 2) + sdiv = 2; + } err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv); if (err) @@ -314,10 +313,12 @@ static int ep93xx_i2s_suspend(struct snd_soc_dai *dai) struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); if (!dai->active) - return; + return 0; ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; } static int ep93xx_i2s_resume(struct snd_soc_dai *dai) @@ -325,10 +326,12 @@ static int ep93xx_i2s_resume(struct snd_soc_dai *dai) struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); if (!dai->active) - return; + return 0; ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; } #else #define ep93xx_i2s_suspend NULL @@ -352,13 +355,13 @@ static struct snd_soc_dai_driver ep93xx_i2s_dai = { .playback = { .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = EP93XX_I2S_FORMATS, }, .capture = { .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = EP93XX_I2S_FORMATS, }, .ops = &ep93xx_i2s_dai_ops, diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c index 06670776f64..a456e491155 100644 --- a/sound/soc/ep93xx/ep93xx-pcm.c +++ b/sound/soc/ep93xx/ep93xx-pcm.c @@ -35,9 +35,9 @@ static const struct snd_pcm_hardware ep93xx_pcm_hardware = { SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER), - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = SNDRV_PCM_RATE_8000_192000, .rate_min = SNDRV_PCM_RATE_8000, - .rate_max = SNDRV_PCM_RATE_96000, + .rate_max = SNDRV_PCM_RATE_192000, .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index 7d7847a1e66..c16c6b2eff9 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -53,9 +53,8 @@ struct mpc8610_hpcd_data { * * Here we program the DMACR and PMUXCR registers. */ -static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) +static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(sound_device); struct mpc8610_hpcd_data *machine_data = container_of(card, struct mpc8610_hpcd_data, card); struct ccsr_guts_86xx __iomem *guts; @@ -138,9 +137,8 @@ static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) * This function is called to remove the sound device for one SSI. We * de-program the DMACR and PMUXCR register. */ -static int mpc8610_hpcd_machine_remove(struct platform_device *sound_device) +static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(sound_device); struct mpc8610_hpcd_data *machine_data = container_of(card, struct mpc8610_hpcd_data, card); struct ccsr_guts_86xx __iomem *guts; diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index 026b756961e..66e0b68af14 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -85,9 +85,8 @@ struct machine_data { * * Here we program the DMACR and PMUXCR registers. */ -static int p1022_ds_machine_probe(struct platform_device *sound_device) +static int p1022_ds_machine_probe(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(sound_device); struct machine_data *mdata = container_of(card, struct machine_data, card); struct ccsr_guts_85xx __iomem *guts; @@ -160,9 +159,8 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) * This function is called to remove the sound device for one SSI. We * de-program the DMACR and PMUXCR register. */ -static int p1022_ds_machine_remove(struct platform_device *sound_device) +static int p1022_ds_machine_remove(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(sound_device); struct machine_data *mdata = container_of(card, struct machine_data, card); struct ccsr_guts_85xx __iomem *guts; diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 642270a635e..d8f130d39dd 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -30,6 +30,16 @@ config SND_MXC_SOC_WM1133_EV1 Enable support for audio on the i.MX31ADS with the WM1133-EV1 PMIC board with WM8835x fitted. +config SND_SOC_MX27VIS_AIC32X4 + tristate "SoC audio support for Visstrim M10 boards" + depends on MACH_IMX27_VISSTRIM_M10 + select SND_SOC_TVL320AIC32X4 + select SND_MXC_SOC_SSI + select SND_MXC_SOC_MX2 + help + Say Y if you want to add support for SoC audio on Visstrim SM10 + board with TLV320AIC32X4 codec. + config SND_SOC_PHYCORE_AC97 tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" depends on MACH_PCM043 || MACH_PCA100 @@ -44,7 +54,8 @@ config SND_SOC_EUKREA_TLV320 tristate "Eukrea TLV320" depends on MACH_EUKREA_MBIMX27_BASEBOARD \ || MACH_EUKREA_MBIMXSD25_BASEBOARD \ - || MACH_EUKREA_MBIMXSD35_BASEBOARD + || MACH_EUKREA_MBIMXSD35_BASEBOARD \ + || MACH_EUKREA_MBIMXSD51_BASEBOARD select SND_SOC_TLV320AIC23 select SND_MXC_SOC_SSI select SND_MXC_SOC_FIQ diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index b67fc02a4ec..d6d609ba7e2 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -10,8 +10,10 @@ obj-$(CONFIG_SND_MXC_SOC_MX2) += snd-soc-imx-mx2.o # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c index 1e9bccae4e8..75fb4b83548 100644 --- a/sound/soc/imx/eukrea-tlv320.c +++ b/sound/soc/imx/eukrea-tlv320.c @@ -98,7 +98,8 @@ static int __init eukrea_tlv320_init(void) int ret; if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd() - && !machine_is_eukrea_cpuimx35sd()) + && !machine_is_eukrea_cpuimx35sd() + && !machine_is_eukrea_cpuimx51sd()) /* return happy. We might run on a totally different machine */ return 0; diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c index 30894ea7f33..bc92ec62000 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/imx/imx-ssi.c @@ -108,7 +108,7 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) break; case SND_SOC_DAIFMT_DSP_B: /* data on rising edge of bclk, frame high with data */ - strcr |= SSI_STCR_TFSL; + strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0; break; case SND_SOC_DAIFMT_DSP_A: /* data on rising edge of bclk, frame high 1clk before data */ @@ -656,6 +656,9 @@ static int imx_ssi_probe(struct platform_device *pdev) ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0; ssi->dma_params_tx.dma_addr = res->start + SSI_STX0; + ssi->dma_params_tx.burstsize = 4; + ssi->dma_params_rx.burstsize = 4; + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); if (res) ssi->dma_params_tx.dma = res->start; diff --git a/sound/soc/imx/mx27vis-aic32x4.c b/sound/soc/imx/mx27vis-aic32x4.c new file mode 100644 index 00000000000..054110b91d4 --- /dev/null +++ b/sound/soc/imx/mx27vis-aic32x4.c @@ -0,0 +1,137 @@ +/* + * mx27vis-aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin <javier.martin@vista-silicon.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> +#include <mach/audmux.h> + +#include "../codecs/tlv320aic32x4.h" +#include "imx-ssi.h" + +static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + u32 dai_format; + + dai_format = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 25000000, SND_SOC_CLOCK_OUT); + if (ret) { + pr_err("%s: failed setting codec sysclk\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret) { + pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops mx27vis_aic32x4_snd_ops = { + .hw_params = mx27vis_aic32x4_hw_params, +}; + +static struct snd_soc_dai_link mx27vis_aic32x4_dai = { + .name = "tlv320aic32x4", + .stream_name = "TLV320AIC32X4", + .codec_dai_name = "tlv320aic32x4-hifi", + .platform_name = "imx-pcm-audio.0", + .codec_name = "tlv320aic32x4.0-0018", + .cpu_dai_name = "imx-ssi.0", + .ops = &mx27vis_aic32x4_snd_ops, +}; + +static struct snd_soc_card mx27vis_aic32x4 = { + .name = "visstrim_m10-audio", + .dai_link = &mx27vis_aic32x4_dai, + .num_links = 1, +}; + +static struct platform_device *mx27vis_aic32x4_snd_device; + +static int __init mx27vis_aic32x4_init(void) +{ + int ret; + + mx27vis_aic32x4_snd_device = platform_device_alloc("soc-audio", -1); + if (!mx27vis_aic32x4_snd_device) + return -ENOMEM; + + platform_set_drvdata(mx27vis_aic32x4_snd_device, &mx27vis_aic32x4); + ret = platform_device_add(mx27vis_aic32x4_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(mx27vis_aic32x4_snd_device); + } + + /* Connect SSI0 as clock slave to SSI1 external pins */ + mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + MXC_AUDMUX_V1_PCR_SYN | + MXC_AUDMUX_V1_PCR_TFSDIR | + MXC_AUDMUX_V1_PCR_TCLKDIR | + MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | + MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) + ); + mxc_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1, + MXC_AUDMUX_V1_PCR_SYN | + MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + + return ret; +} + +static void __exit mx27vis_aic32x4_exit(void) +{ + platform_device_unregister(mx27vis_aic32x4_snd_device); +} + +module_init(mx27vis_aic32x4_init); +module_exit(mx27vis_aic32x4_exit); + +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); +MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mid-x86/Kconfig b/sound/soc/mid-x86/Kconfig new file mode 100644 index 00000000000..29350428f1c --- /dev/null +++ b/sound/soc/mid-x86/Kconfig @@ -0,0 +1,14 @@ +config SND_MFLD_MACHINE + tristate "SOC Machine Audio driver for Intel Medfield MID platform" + depends on INTEL_SCU_IPC + depends on SND_INTEL_SST + select SND_SOC_SN95031 + select SND_SST_PLATFORM + help + This adds support for ASoC machine driver for Intel(R) MID Medfield platform + used as alsa device in audio substem in Intel(R) MID devices + Say Y if you have such a device + If unsure select "N". + +config SND_SST_PLATFORM + tristate diff --git a/sound/soc/mid-x86/Makefile b/sound/soc/mid-x86/Makefile new file mode 100644 index 00000000000..63988333946 --- /dev/null +++ b/sound/soc/mid-x86/Makefile @@ -0,0 +1,5 @@ +snd-soc-sst-platform-objs := sst_platform.o +snd-soc-mfld-machine-objs := mfld_machine.o + +obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o +obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o diff --git a/sound/soc/mid-x86/mfld_machine.c b/sound/soc/mid-x86/mfld_machine.c new file mode 100644 index 00000000000..429aa1be2cf --- /dev/null +++ b/sound/soc/mid-x86/mfld_machine.c @@ -0,0 +1,452 @@ +/* + * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include "../codecs/sn95031.h" + +#define MID_MONO 1 +#define MID_STEREO 2 +#define MID_MAX_CAP 5 +#define MFLD_JACK_INSERT 0x04 + +enum soc_mic_bias_zones { + MFLD_MV_START = 0, + /* mic bias volutage range for Headphones*/ + MFLD_MV_HP = 400, + /* mic bias volutage range for American Headset*/ + MFLD_MV_AM_HS = 650, + /* mic bias volutage range for Headset*/ + MFLD_MV_HS = 2000, + MFLD_MV_UNDEFINED, +}; + +static unsigned int hs_switch; +static unsigned int lo_dac; + +struct mfld_mc_private { + struct platform_device *socdev; + void __iomem *int_base; + struct snd_soc_codec *codec; + u8 interrupt_status; +}; + +struct snd_soc_jack mfld_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin mfld_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "AMIC1", + .mask = SND_JACK_MICROPHONE, + }, +}; + +/* jack detection voltage zones */ +static struct snd_soc_jack_zone mfld_zones[] = { + {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE}, + {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET}, +}; + +/* sound card controls */ +static const char *headset_switch_text[] = {"Earpiece", "Headset"}; + +static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"}; + +static const struct soc_enum headset_enum = + SOC_ENUM_SINGLE_EXT(2, headset_switch_text); + +static const struct soc_enum lo_enum = + SOC_ENUM_SINGLE_EXT(4, lo_text); + +static int headset_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = hs_switch; + return 0; +} + +static int headset_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] == hs_switch) + return 0; + + if (ucontrol->value.integer.value[0]) { + pr_debug("hs_set HS path\n"); + snd_soc_dapm_enable_pin(&codec->dapm, "Headphones"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + } else { + pr_debug("hs_set EP path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); + snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); + } + snd_soc_dapm_sync(&codec->dapm); + hs_switch = ucontrol->value.integer.value[0]; + + return 0; +} + +static void lo_enable_out_pins(struct snd_soc_codec *codec) +{ + snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "VIB1OUT"); + snd_soc_dapm_enable_pin(&codec->dapm, "VIB2OUT"); + if (hs_switch) { + snd_soc_dapm_enable_pin(&codec->dapm, "Headphones"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + } else { + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); + snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); + } +} + +static int lo_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lo_dac; + return 0; +} + +static int lo_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] == lo_dac) + return 0; + + /* we dont want to work with last state of lineout so just enable all + * pins and then disable pins not required + */ + lo_enable_out_pins(codec); + switch (ucontrol->value.integer.value[0]) { + case 0: + pr_debug("set vibra path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "VIB1OUT"); + snd_soc_dapm_disable_pin(&codec->dapm, "VIB2OUT"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0); + break; + + case 1: + pr_debug("set hs path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "Headphones"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x22); + break; + + case 2: + pr_debug("set spkr path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTR"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x44); + break; + + case 3: + pr_debug("set null path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTR"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x66); + break; + } + snd_soc_dapm_sync(&codec->dapm); + lo_dac = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new mfld_snd_controls[] = { + SOC_ENUM_EXT("Playback Switch", headset_enum, + headset_get_switch, headset_set_switch), + SOC_ENUM_EXT("Lineout Mux", lo_enum, + lo_get_switch, lo_set_switch), +}; + +static const struct snd_soc_dapm_widget mfld_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route mfld_map[] = { + {"Headphones", NULL, "HPOUTR"}, + {"Headphones", NULL, "HPOUTL"}, + {"Mic", NULL, "AMIC1"}, +}; + +static void mfld_jack_check(unsigned int intr_status) +{ + struct mfld_jack_data jack_data; + + jack_data.mfld_jack = &mfld_jack; + jack_data.intr_id = intr_status; + + sn95031_jack_detection(&jack_data); + /* TODO: add american headset detection post gpiolib support */ +} + +static int mfld_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_codec *codec = runtime->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret_val; + + /* Add jack sense widgets */ + snd_soc_dapm_new_controls(dapm, mfld_widgets, ARRAY_SIZE(mfld_widgets)); + + /* Set up the map */ + snd_soc_dapm_add_routes(dapm, mfld_map, ARRAY_SIZE(mfld_map)); + + /* always connected */ + snd_soc_dapm_enable_pin(dapm, "Headphones"); + snd_soc_dapm_enable_pin(dapm, "Mic"); + snd_soc_dapm_sync(dapm); + + ret_val = snd_soc_add_controls(codec, mfld_snd_controls, + ARRAY_SIZE(mfld_snd_controls)); + if (ret_val) { + pr_err("soc_add_controls failed %d", ret_val); + return ret_val; + } + /* default is earpiece pin, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "Headphones"); + /* default is lineout NC, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); + lo_dac = 3; + hs_switch = 0; + /* we dont use linein in this so set to NC */ + snd_soc_dapm_disable_pin(dapm, "LINEINL"); + snd_soc_dapm_disable_pin(dapm, "LINEINR"); + snd_soc_dapm_sync(dapm); + + /* Headset and button jack detection */ + ret_val = snd_soc_jack_new(codec, "Intel(R) MID Audio Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1, &mfld_jack); + if (ret_val) { + pr_err("jack creation failed\n"); + return ret_val; + } + + ret_val = snd_soc_jack_add_pins(&mfld_jack, + ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins); + if (ret_val) { + pr_err("adding jack pins failed\n"); + return ret_val; + } + ret_val = snd_soc_jack_add_zones(&mfld_jack, + ARRAY_SIZE(mfld_zones), mfld_zones); + if (ret_val) { + pr_err("adding jack zones failed\n"); + return ret_val; + } + + /* we want to check if anything is inserted at boot, + * so send a fake event to codec and it will read adc + * to find if anything is there or not */ + mfld_jack_check(MFLD_JACK_INSERT); + return ret_val; +} + +struct snd_soc_dai_link mfld_msic_dailink[] = { + { + .name = "Medfield Headset", + .stream_name = "Headset", + .cpu_dai_name = "Headset-cpu-dai", + .codec_dai_name = "SN95031 Headset", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = mfld_init, + }, + { + .name = "Medfield Speaker", + .stream_name = "Speaker", + .cpu_dai_name = "Speaker-cpu-dai", + .codec_dai_name = "SN95031 Speaker", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Vibra", + .stream_name = "Vibra1", + .cpu_dai_name = "Vibra1-cpu-dai", + .codec_dai_name = "SN95031 Vibra1", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Haptics", + .stream_name = "Vibra2", + .cpu_dai_name = "Vibra2-cpu-dai", + .codec_dai_name = "SN95031 Vibra2", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_mfld = { + .name = "medfield_audio", + .dai_link = mfld_msic_dailink, + .num_links = ARRAY_SIZE(mfld_msic_dailink), +}; + +static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev) +{ + struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev; + + memcpy_fromio(&mc_private->interrupt_status, + ((void *)(mc_private->int_base)), + sizeof(u8)); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t snd_mfld_jack_detection(int irq, void *data) +{ + struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data; + + if (mfld_jack.codec == NULL) + return IRQ_HANDLED; + mfld_jack_check(mc_drv_ctx->interrupt_status); + + return IRQ_HANDLED; +} + +static int __devinit snd_mfld_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0, irq; + struct mfld_mc_private *mc_drv_ctx; + struct resource *irq_mem; + + pr_debug("snd_mfld_mc_probe called\n"); + + /* retrive the irq number */ + irq = platform_get_irq(pdev, 0); + + /* audio interrupt base of SRAM location where + * interrupts are stored by System FW */ + mc_drv_ctx = kzalloc(sizeof(*mc_drv_ctx), GFP_ATOMIC); + if (!mc_drv_ctx) { + pr_err("allocation failed\n"); + return -ENOMEM; + } + + irq_mem = platform_get_resource_byname( + pdev, IORESOURCE_MEM, "IRQ_BASE"); + if (!irq_mem) { + pr_err("no mem resource given\n"); + ret_val = -ENODEV; + goto unalloc; + } + mc_drv_ctx->int_base = ioremap_nocache(irq_mem->start, + resource_size(irq_mem)); + if (!mc_drv_ctx->int_base) { + pr_err("Mapping of cache failed\n"); + ret_val = -ENOMEM; + goto unalloc; + } + /* register for interrupt */ + ret_val = request_threaded_irq(irq, snd_mfld_jack_intr_handler, + snd_mfld_jack_detection, + IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx); + if (ret_val) { + pr_err("cannot register IRQ\n"); + goto unalloc; + } + /* register the soc card */ + snd_soc_card_mfld.dev = &pdev->dev; + ret_val = snd_soc_register_card(&snd_soc_card_mfld); + if (ret_val) { + pr_debug("snd_soc_register_card failed %d\n", ret_val); + goto freeirq; + } + platform_set_drvdata(pdev, mc_drv_ctx); + pr_debug("successfully exited probe\n"); + return ret_val; + +freeirq: + free_irq(irq, mc_drv_ctx); +unalloc: + kfree(mc_drv_ctx); + return ret_val; +} + +static int __devexit snd_mfld_mc_remove(struct platform_device *pdev) +{ + struct mfld_mc_private *mc_drv_ctx = platform_get_drvdata(pdev); + + pr_debug("snd_mfld_mc_remove called\n"); + free_irq(platform_get_irq(pdev, 0), mc_drv_ctx); + snd_soc_unregister_card(&snd_soc_card_mfld); + kfree(mc_drv_ctx); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver snd_mfld_mc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "msic_audio", + }, + .probe = snd_mfld_mc_probe, + .remove = __devexit_p(snd_mfld_mc_remove), +}; + +static int __init snd_mfld_driver_init(void) +{ + pr_debug("snd_mfld_driver_init called\n"); + return platform_driver_register(&snd_mfld_mc_driver); +} +module_init(snd_mfld_driver_init); + +static void __exit snd_mfld_driver_exit(void) +{ + pr_debug("snd_mfld_driver_exit called\n"); + platform_driver_unregister(&snd_mfld_mc_driver); +} +module_exit(snd_mfld_driver_exit); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msic-audio"); diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c new file mode 100644 index 00000000000..ee2c22475a7 --- /dev/null +++ b/sound/soc/mid-x86/sst_platform.c @@ -0,0 +1,474 @@ +/* + * sst_platform.c - Intel MID Platform driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/slab.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../../drivers/staging/intel_sst/intel_sst_ioctl.h" +#include "../../../drivers/staging/intel_sst/intel_sst.h" +#include "sst_platform.h" + +static struct snd_pcm_hardware sst_platform_pcm_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP| + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 | + SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 | + SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32), + .rates = (SNDRV_PCM_RATE_8000| + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = SST_MIN_RATE, + .rate_max = SST_MAX_RATE, + .channels_min = SST_MIN_CHANNEL, + .channels_max = SST_MAX_CHANNEL, + .buffer_bytes_max = SST_MAX_BUFFER, + .period_bytes_min = SST_MIN_PERIOD_BYTES, + .period_bytes_max = SST_MAX_PERIOD_BYTES, + .periods_min = SST_MIN_PERIODS, + .periods_max = SST_MAX_PERIODS, + .fifo_size = SST_FIFO_SIZE, +}; + +/* MFLD - MSIC */ +struct snd_soc_dai_driver sst_platform_dai[] = { +{ + .name = "Headset-cpu-dai", + .id = 0, + .playback = { + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 5, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Speaker-cpu-dai", + .id = 1, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Vibra1-cpu-dai", + .id = 2, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_MONO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Vibra2-cpu-dai", + .id = 3, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +}; + +/* helper functions */ +static inline void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) +{ + spin_lock(&stream->status_lock); + stream->stream_status = state; + spin_unlock(&stream->status_lock); +} + +static inline int sst_get_stream_status(struct sst_runtime_stream *stream) +{ + int state; + + spin_lock(&stream->status_lock); + state = stream->stream_status; + spin_unlock(&stream->status_lock); + return state; +} + +static void sst_fill_pcm_params(struct snd_pcm_substream *substream, + struct snd_sst_stream_params *param) +{ + + param->uc.pcm_params.codec = SST_CODEC_TYPE_PCM; + param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; + param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; + param->uc.pcm_params.reserved = 0; + param->uc.pcm_params.sfreq = substream->runtime->rate; + param->uc.pcm_params.ring_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + param->uc.pcm_params.period_count = substream->runtime->period_size; + param->uc.pcm_params.ring_buffer_addr = + virt_to_phys(substream->dma_buffer.area); + pr_debug("period_cnt = %d\n", param->uc.pcm_params.period_count); + pr_debug("sfreq= %d, wd_sz = %d\n", + param->uc.pcm_params.sfreq, param->uc.pcm_params.pcm_wd_sz); +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_sst_stream_params param = {{{0,},},}; + struct snd_sst_params str_params = {0}; + int ret_val; + + /* set codec params and inform SST driver the same */ + sst_fill_pcm_params(substream, ¶m); + substream->runtime->dma_area = substream->dma_buffer.area; + str_params.sparams = param; + str_params.codec = param.uc.pcm_params.codec; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + str_params.ops = STREAM_OPS_PLAYBACK; + str_params.device_type = substream->pcm->device + 1; + pr_debug("Playbck stream,Device %d\n", + substream->pcm->device); + } else { + str_params.ops = STREAM_OPS_CAPTURE; + str_params.device_type = SND_SST_DEVICE_CAPTURE; + pr_debug("Capture stream,Device %d\n", + substream->pcm->device); + } + ret_val = stream->sstdrv_ops->pcm_control->open(&str_params); + pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val); + if (ret_val < 0) + return ret_val; + + stream->stream_info.str_id = ret_val; + pr_debug("str id : %d\n", stream->stream_info.str_id); + return ret_val; +} + +static void sst_period_elapsed(void *mad_substream) +{ + struct snd_pcm_substream *substream = mad_substream; + struct sst_runtime_stream *stream; + int status; + + if (!substream || !substream->runtime) + return; + stream = substream->runtime->private_data; + if (!stream) + return; + status = sst_get_stream_status(stream); + if (status != SST_PLATFORM_RUNNING) + return; + snd_pcm_period_elapsed(substream); +} + +static int sst_platform_init_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + int ret_val; + + pr_debug("setting buffer ptr param\n"); + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.period_elapsed = sst_period_elapsed; + stream->stream_info.mad_substream = substream; + stream->stream_info.buffer_ptr = 0; + stream->stream_info.sfreq = substream->runtime->rate; + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_STREAM_INIT, &stream->stream_info); + if (ret_val) + pr_err("control_set ret error %d\n", ret_val); + return ret_val; + +} +/* end -- helper functions */ + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct sst_runtime_stream *stream; + int ret_val = 0; + + pr_debug("sst_platform_open called\n"); + runtime = substream->runtime; + runtime->hw = sst_platform_pcm_hw; + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + spin_lock_init(&stream->status_lock); + stream->stream_info.str_id = 0; + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.mad_substream = substream; + /* allocate memory for SST API set */ + stream->sstdrv_ops = kzalloc(sizeof(*stream->sstdrv_ops), + GFP_KERNEL); + if (!stream->sstdrv_ops) { + pr_err("sst: mem allocation for ops fail\n"); + kfree(stream); + return -ENOMEM; + } + stream->sstdrv_ops->vendor_id = MSIC_VENDOR_ID; + /* registering with SST driver to get access to SST APIs to use */ + ret_val = register_sst_card(stream->sstdrv_ops); + if (ret_val) { + pr_err("sst: sst card registration failed\n"); + return ret_val; + } + runtime->private_data = stream; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int sst_platform_close(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + pr_debug("sst_platform_close called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (str_id) + ret_val = stream->sstdrv_ops->pcm_control->close(str_id); + kfree(stream->sstdrv_ops); + kfree(stream); + return ret_val; +} + +static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + pr_debug("sst_platform_pcm_prepare called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (stream->stream_info.str_id) { + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_DROP, &str_id); + return ret_val; + } + + ret_val = sst_platform_alloc_stream(substream); + if (ret_val < 0) + return ret_val; + snprintf(substream->pcm->id, sizeof(substream->pcm->id), + "%d", stream->stream_info.str_id); + + ret_val = sst_platform_init_stream(substream); + if (ret_val) + return ret_val; + substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; + return ret_val; +} + +static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int ret_val = 0, str_id; + struct sst_runtime_stream *stream; + int str_cmd, status; + + pr_debug("sst_platform_pcm_trigger called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pr_debug("sst: Trigger Start\n"); + str_cmd = SST_SND_START; + status = SST_PLATFORM_RUNNING; + stream->stream_info.mad_substream = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("sst: in stop\n"); + str_cmd = SST_SND_DROP; + status = SST_PLATFORM_DROPPED; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("sst: in pause\n"); + str_cmd = SST_SND_PAUSE; + status = SST_PLATFORM_PAUSED; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("sst: in pause release\n"); + str_cmd = SST_SND_RESUME; + status = SST_PLATFORM_RUNNING; + break; + default: + return -EINVAL; + } + ret_val = stream->sstdrv_ops->pcm_control->device_control(str_cmd, + &str_id); + if (!ret_val) + sst_set_stream_status(stream, status); + + return ret_val; +} + + +static snd_pcm_uframes_t sst_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val, status; + struct pcm_stream_info *str_info; + + stream = substream->runtime->private_data; + status = sst_get_stream_status(stream); + if (status == SST_PLATFORM_INIT) + return 0; + str_info = &stream->stream_info; + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_BUFFER_POINTER, str_info); + if (ret_val) { + pr_err("sst: error code = %d\n", ret_val); + return ret_val; + } + return stream->stream_info.buffer_ptr; +} + +static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + + return 0; +} + +static struct snd_pcm_ops sst_platform_ops = { + .open = sst_platform_open, + .close = sst_platform_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = sst_platform_pcm_prepare, + .trigger = sst_platform_pcm_trigger, + .pointer = sst_platform_pcm_pointer, + .hw_params = sst_platform_pcm_hw_params, +}; + +static void sst_pcm_free(struct snd_pcm *pcm) +{ + pr_debug("sst_pcm_free called\n"); + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int retval = 0; + + pr_debug("sst_pcm_new called\n"); + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + SST_MIN_BUFFER, SST_MAX_BUFFER); + if (retval) { + pr_err("dma buffer allocationf fail\n"); + return retval; + } + } + return retval; +} +struct snd_soc_platform_driver sst_soc_platform_drv = { + .ops = &sst_platform_ops, + .pcm_new = sst_pcm_new, + .pcm_free = sst_pcm_free, +}; + +static int sst_platform_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("sst_platform_probe called\n"); + ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); + if (ret) { + pr_err("registering soc platform failed\n"); + return ret; + } + + ret = snd_soc_register_dais(&pdev->dev, + sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); + if (ret) { + pr_err("registering cpu dais failed\n"); + snd_soc_unregister_platform(&pdev->dev); + } + return ret; +} + +static int sst_platform_remove(struct platform_device *pdev) +{ + + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sst_platform_dai)); + snd_soc_unregister_platform(&pdev->dev); + pr_debug("sst_platform_remove sucess\n"); + return 0; +} + +static struct platform_driver sst_platform_driver = { + .driver = { + .name = "sst-platform", + .owner = THIS_MODULE, + }, + .probe = sst_platform_probe, + .remove = sst_platform_remove, +}; + +static int __init sst_soc_platform_init(void) +{ + pr_debug("sst_soc_platform_init called\n"); + return platform_driver_register(&sst_platform_driver); +} +module_init(sst_soc_platform_init); + +static void __exit sst_soc_platform_exit(void) +{ + platform_driver_unregister(&sst_platform_driver); + pr_debug("sst_soc_platform_exit sucess\n"); +} +module_exit(sst_soc_platform_exit); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sst-platform"); diff --git a/sound/soc/mid-x86/sst_platform.h b/sound/soc/mid-x86/sst_platform.h new file mode 100644 index 00000000000..df370286694 --- /dev/null +++ b/sound/soc/mid-x86/sst_platform.h @@ -0,0 +1,63 @@ +/* + * sst_platform.h - Intel MID Platform driver header file + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ + +#ifndef __SST_PLATFORMDRV_H__ +#define __SST_PLATFORMDRV_H__ + +#define SST_MONO 1 +#define SST_STEREO 2 +#define SST_MAX_CAP 5 + +#define SST_MIN_RATE 8000 +#define SST_MAX_RATE 48000 +#define SST_MIN_CHANNEL 1 +#define SST_MAX_CHANNEL 5 +#define SST_MAX_BUFFER (800*1024) +#define SST_MIN_BUFFER (800*1024) +#define SST_MIN_PERIOD_BYTES 32 +#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER +#define SST_MIN_PERIODS 2 +#define SST_MAX_PERIODS (1024*2) +#define SST_FIFO_SIZE 0 +#define SST_CARD_NAMES "intel_mid_card" +#define MSIC_VENDOR_ID 3 + +struct sst_runtime_stream { + int stream_status; + struct pcm_stream_info stream_info; + struct intel_sst_card_ops *sstdrv_ops; + spinlock_t status_lock; +}; + +enum sst_drv_status { + SST_PLATFORM_INIT = 1, + SST_PLATFORM_STARTED, + SST_PLATFORM_RUNNING, + SST_PLATFORM_PAUSED, + SST_PLATFORM_DROPPED, +}; + +#endif diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index a088db6d509..b5922984eac 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -24,6 +24,7 @@ config SND_OMAP_SOC_RX51 select OMAP_MCBSP select SND_OMAP_SOC_MCBSP select SND_SOC_TLV320AIC3X + select SND_SOC_TPA6130A2 help Say Y if you want to add support for SoC audio on Nokia RX-51 hardware. This is also known as Nokia N900 product. diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index 09fb0df8d41..d0986220eff 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -31,6 +31,7 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <plat/mcbsp.h> +#include "../codecs/tpa6130a2.h" #include <asm/mach-types.h> @@ -39,6 +40,7 @@ #define RX51_TVOUT_SEL_GPIO 40 #define RX51_JACK_DETECT_GPIO 177 +#define RX51_ECI_SW_GPIO 182 /* * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c @@ -47,7 +49,9 @@ enum { RX51_JACK_DISABLED, - RX51_JACK_TVOUT, /* tv-out */ + RX51_JACK_TVOUT, /* tv-out with stereo output */ + RX51_JACK_HP, /* headphone: stereo output, no mic */ + RX51_JACK_HS, /* headset: stereo output with mic */ }; static int rx51_spk_func; @@ -57,6 +61,19 @@ static int rx51_jack_func; static void rx51_ext_control(struct snd_soc_codec *codec) { struct snd_soc_dapm_context *dapm = &codec->dapm; + int hp = 0, hs = 0, tvout = 0; + + switch (rx51_jack_func) { + case RX51_JACK_TVOUT: + tvout = 1; + hp = 1; + break; + case RX51_JACK_HS: + hs = 1; + case RX51_JACK_HP: + hp = 1; + break; + } if (rx51_spk_func) snd_soc_dapm_enable_pin(dapm, "Ext Spk"); @@ -66,9 +83,16 @@ static void rx51_ext_control(struct snd_soc_codec *codec) snd_soc_dapm_enable_pin(dapm, "DMic"); else snd_soc_dapm_disable_pin(dapm, "DMic"); + if (hp) + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); + if (hs) + snd_soc_dapm_enable_pin(dapm, "HS Mic"); + else + snd_soc_dapm_disable_pin(dapm, "HS Mic"); - gpio_set_value(RX51_TVOUT_SEL_GPIO, - rx51_jack_func == RX51_JACK_TVOUT); + gpio_set_value(RX51_TVOUT_SEL_GPIO, tvout); snd_soc_dapm_sync(dapm); } @@ -153,6 +177,19 @@ static int rx51_spk_event(struct snd_soc_dapm_widget *w, return 0; } +static int rx51_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = w->dapm->codec; + + if (SND_SOC_DAPM_EVENT_ON(event)) + tpa6130a2_stereo_enable(codec, 1); + else + tpa6130a2_stereo_enable(codec, 0); + + return 0; +} + static int rx51_get_input(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -203,7 +240,7 @@ static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { { .gpio = RX51_JACK_DETECT_GPIO, .name = "avdet-gpio", - .report = SND_JACK_VIDEOOUT, + .report = SND_JACK_HEADSET, .invert = 1, .debounce_time = 200, }, @@ -212,19 +249,38 @@ static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), SND_SOC_DAPM_MIC("DMic", NULL), + SND_SOC_DAPM_HP("Headphone Jack", rx51_hp_event), + SND_SOC_DAPM_MIC("HS Mic", NULL), + SND_SOC_DAPM_LINE("FM Transmitter", NULL), +}; + +static const struct snd_soc_dapm_widget aic34_dapm_widgetsb[] = { + SND_SOC_DAPM_SPK("Earphone", NULL), }; static const struct snd_soc_dapm_route audio_map[] = { {"Ext Spk", NULL, "HPLOUT"}, {"Ext Spk", NULL, "HPROUT"}, + {"Headphone Jack", NULL, "LLOUT"}, + {"Headphone Jack", NULL, "RLOUT"}, + {"FM Transmitter", NULL, "LLOUT"}, + {"FM Transmitter", NULL, "RLOUT"}, {"DMic Rate 64", NULL, "Mic Bias 2V"}, {"Mic Bias 2V", NULL, "DMic"}, }; +static const struct snd_soc_dapm_route audio_mapb[] = { + {"b LINE2R", NULL, "MONO_LOUT"}, + {"Earphone", NULL, "b HPLOUT"}, + + {"LINE1L", NULL, "b Mic Bias 2.5V"}, + {"b Mic Bias 2.5V", NULL, "HS Mic"} +}; + static const char *spk_function[] = {"Off", "On"}; static const char *input_function[] = {"ADC", "Digital Mic"}; -static const char *jack_function[] = {"Off", "TV-OUT"}; +static const char *jack_function[] = {"Off", "TV-OUT", "Headphone", "Headset"}; static const struct soc_enum rx51_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), @@ -239,6 +295,11 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = { rx51_get_input, rx51_set_input), SOC_ENUM_EXT("Jack Function", rx51_enum[2], rx51_get_jack, rx51_set_jack), + SOC_DAPM_PIN_SWITCH("FM Transmitter"), +}; + +static const struct snd_kcontrol_new aic34_rx51_controlsb[] = { + SOC_DAPM_PIN_SWITCH("Earphone"), }; static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) @@ -265,11 +326,21 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) /* Set up RX-51 specific audio path audio_map */ snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + err = tpa6130a2_add_controls(codec); + if (err < 0) + return err; + snd_soc_limit_volume(codec, "TPA6130A2 Headphone Playback Volume", 42); + + err = omap_mcbsp_st_add_controls(codec, 1); + if (err < 0) + return err; + snd_soc_dapm_sync(dapm); /* AV jack detection */ err = snd_soc_jack_new(codec, "AV Jack", - SND_JACK_VIDEOOUT, &rx51_av_jack); + SND_JACK_HEADSET | SND_JACK_VIDEOOUT, + &rx51_av_jack); if (err) return err; err = snd_soc_jack_add_gpios(&rx51_av_jack, @@ -279,6 +350,24 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) return err; } +static int rx51_aic34b_init(struct snd_soc_dapm_context *dapm) +{ + int err; + + err = snd_soc_add_controls(dapm->codec, aic34_rx51_controlsb, + ARRAY_SIZE(aic34_rx51_controlsb)); + if (err < 0) + return err; + + err = snd_soc_dapm_new_controls(dapm, aic34_dapm_widgetsb, + ARRAY_SIZE(aic34_dapm_widgetsb)); + if (err < 0) + return 0; + + return snd_soc_dapm_add_routes(dapm, audio_mapb, + ARRAY_SIZE(audio_mapb)); +} + /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link rx51_dai[] = { { @@ -293,11 +382,30 @@ static struct snd_soc_dai_link rx51_dai[] = { }, }; +struct snd_soc_aux_dev rx51_aux_dev[] = { + { + .name = "TLV320AIC34b", + .codec_name = "tlv320aic3x-codec.2-0019", + .init = rx51_aic34b_init, + }, +}; + +static struct snd_soc_codec_conf rx51_codec_conf[] = { + { + .dev_name = "tlv320aic3x-codec.2-0019", + .name_prefix = "b", + }, +}; + /* Audio card */ static struct snd_soc_card rx51_sound_card = { .name = "RX-51", .dai_link = rx51_dai, .num_links = ARRAY_SIZE(rx51_dai), + .aux_dev = rx51_aux_dev, + .num_aux_devs = ARRAY_SIZE(rx51_aux_dev), + .codec_conf = rx51_codec_conf, + .num_configs = ARRAY_SIZE(rx51_codec_conf), }; static struct platform_device *rx51_snd_device; @@ -309,10 +417,14 @@ static int __init rx51_soc_init(void) if (!machine_is_nokia_rx51()) return -ENODEV; - err = gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel"); + err = gpio_request_one(RX51_TVOUT_SEL_GPIO, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, "tvout_sel"); if (err) goto err_gpio_tvout_sel; - gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0); + err = gpio_request_one(RX51_ECI_SW_GPIO, + GPIOF_DIR_OUT | GPIOF_INIT_HIGH, "eci_sw"); + if (err) + goto err_gpio_eci_sw; rx51_snd_device = platform_device_alloc("soc-audio", -1); if (!rx51_snd_device) { @@ -330,6 +442,8 @@ static int __init rx51_soc_init(void) err2: platform_device_put(rx51_snd_device); err1: + gpio_free(RX51_ECI_SW_GPIO); +err_gpio_eci_sw: gpio_free(RX51_TVOUT_SEL_GPIO); err_gpio_tvout_sel: @@ -342,6 +456,7 @@ static void __exit rx51_soc_exit(void) rx51_av_jack_gpios); platform_device_unregister(rx51_snd_device); + gpio_free(RX51_ECI_SW_GPIO); gpio_free(RX51_TVOUT_SEL_GPIO); } diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c index 0fd60f42303..2afabaf5949 100644 --- a/sound/soc/pxa/raumfeld.c +++ b/sound/soc/pxa/raumfeld.c @@ -151,13 +151,13 @@ static struct snd_soc_ops raumfeld_cs4270_ops = { .hw_params = raumfeld_cs4270_hw_params, }; -static int raumfeld_line_suspend(struct platform_device *pdev, pm_message_t state) +static int raumfeld_line_suspend(struct snd_soc_card *card) { raumfeld_enable_audio(false); return 0; } -static int raumfeld_line_resume(struct platform_device *pdev) +static int raumfeld_line_resume(struct snd_soc_card *card) { raumfeld_enable_audio(true); return 0; @@ -229,19 +229,19 @@ static struct snd_soc_dai_link raumfeld_dai[] = { { .name = "ak4104", .stream_name = "Playback", - .cpu_dai_name = "pxa-ssp-dai.1", - .codec_dai_name = "ak4104-hifi", - .platform_name = "pxa-pcm-audio", + .cpu_dai_name = "pxa-ssp-dai.1", + .codec_dai_name = "ak4104-hifi", + .platform_name = "pxa-pcm-audio", .ops = &raumfeld_ak4104_ops, - .codec_name = "ak4104-codec.0", + .codec_name = "ak4104-codec.0", }, { .name = "CS4270", .stream_name = "CS4270", - .cpu_dai_name = "pxa-ssp-dai.0", - .platform_name = "pxa-pcm-audio", - .codec_dai_name = "cs4270-hifi", - .codec_name = "cs4270-codec.0-0048", + .cpu_dai_name = "pxa-ssp-dai.0", + .platform_name = "pxa-pcm-audio", + .codec_dai_name = "cs4270-hifi", + .codec_name = "cs4270-codec.0-0048", .ops = &raumfeld_cs4270_ops, },}; diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index 4b6e5d608b4..9a235136695 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -237,7 +237,7 @@ static struct snd_soc_dai_link tosa_dai[] = { }, }; -static int tosa_probe(struct platform_device *dev) +static int tosa_probe(struct snd_soc_card *card) { int ret; @@ -251,7 +251,7 @@ static int tosa_probe(struct platform_device *dev) return ret; } -static int tosa_remove(struct platform_device *dev) +static int tosa_remove(struct snd_soc_card *card) { gpio_free(TOSA_GPIO_L_MUTE); return 0; diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c index 3ceaef68e01..d69d9fc3223 100644 --- a/sound/soc/pxa/z2.c +++ b/sound/soc/pxa/z2.c @@ -95,6 +95,11 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE, }, + { + .pin = "Ext Spk", + .mask = SND_JACK_HEADPHONE, + .invert = 1 + }, }; /* Headset jack detection gpios */ @@ -147,7 +152,7 @@ static int z2_wm8750_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_disable_pin(dapm, "LINPUT3"); snd_soc_dapm_disable_pin(dapm, "RINPUT3"); snd_soc_dapm_disable_pin(dapm, "OUT3"); - snd_soc_dapm_disable_pin(dapm, "MONO"); + snd_soc_dapm_disable_pin(dapm, "MONO1"); /* Add z2 specific widgets */ snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets, diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c index 25bba108fea..ac577263b3e 100644 --- a/sound/soc/pxa/zylonite.c +++ b/sound/soc/pxa/zylonite.c @@ -189,7 +189,7 @@ static struct snd_soc_dai_link zylonite_dai[] = { }, }; -static int zylonite_probe(struct platform_device *pdev) +static int zylonite_probe(struct snd_soc_card *card) { int ret; @@ -216,7 +216,7 @@ static int zylonite_probe(struct platform_device *pdev) return 0; } -static int zylonite_remove(struct platform_device *pdev) +static int zylonite_remove(struct snd_soc_card *card) { if (clk_pout) { clk_disable(pout); @@ -226,8 +226,7 @@ static int zylonite_remove(struct platform_device *pdev) return 0; } -static int zylonite_suspend_post(struct platform_device *pdev, - pm_message_t state) +static int zylonite_suspend_post(struct snd_soc_card *card) { if (clk_pout) clk_disable(pout); @@ -235,7 +234,7 @@ static int zylonite_suspend_post(struct platform_device *pdev, return 0; } -static int zylonite_resume_pre(struct platform_device *pdev) +static int zylonite_resume_pre(struct snd_soc_card *card) { int ret = 0; diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index a6a6b5fa2f2..a08237acc53 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -35,23 +35,16 @@ config SND_SAMSUNG_I2S tristate config SND_SOC_SAMSUNG_NEO1973_WM8753 - tristate "SoC I2S Audio support for NEO1973 - WM8753" - depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA01 + tristate "Audio support for Openmoko Neo1973 Smartphones (GTA01/GTA02)" + depends on SND_SOC_SAMSUNG && (MACH_NEO1973_GTA01 || MACH_NEO1973_GTA02) select SND_S3C24XX_I2S select SND_SOC_WM8753 + select SND_SOC_LM4857 if MACH_NEO1973_GTA01 + select SND_SOC_DFBMCS320 help - Say Y if you want to add support for SoC audio on smdk2440 - with the WM8753. + Say Y here to enable audio support for the Openmoko Neo1973 + Smartphones. -config SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753 - tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)" - depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA02 - select SND_S3C24XX_I2S - select SND_SOC_WM8753 - help - This driver provides audio support for the Openmoko Neo FreeRunner - smartphone. - config SND_SOC_SAMSUNG_JIVE_WM8750 tristate "SoC I2S Audio support for Jive" depends on SND_SOC_SAMSUNG && MACH_JIVE diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 705d4e8a672..294dec05c26 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -20,7 +20,6 @@ obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o # S3C24XX Machine Support snd-soc-jive-wm8750-objs := jive_wm8750.o snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o -snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o @@ -38,7 +37,6 @@ snd-soc-smdk-spdif-objs := smdk_spdif.o obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o -obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o obj-$(CONFIG_SND_SOC_SAMSUNG_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c index 4770a955034..f97110e72e8 100644 --- a/sound/soc/samsung/ac97.c +++ b/sound/soc/samsung/ac97.c @@ -12,24 +12,24 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> #include <linux/io.h> #include <linux/delay.h> #include <linux/clk.h> #include <sound/soc.h> -#include <plat/regs-ac97.h> #include <mach/dma.h> +#include <plat/regs-ac97.h> #include <plat/audio.h> #include "dma.h" -#include "ac97.h" #define AC_CMD_ADDR(x) (x << 16) #define AC_CMD_DATA(x) (x & 0xffff) +#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1 + struct s3c_ac97_info { struct clk *ac97_clk; void __iomem *regs; diff --git a/sound/soc/samsung/ac97.h b/sound/soc/samsung/ac97.h deleted file mode 100644 index 0d0e1b51145..00000000000 --- a/sound/soc/samsung/ac97.h +++ /dev/null @@ -1,21 +0,0 @@ -/* sound/soc/samsung/ac97.h - * - * ALSA SoC Audio Layer - S3C AC97 Controller driver - * Evolved from s3c2443-ac97.h - * - * Copyright (c) 2010 Samsung Electronics Co. Ltd - * Author: Jaswinder Singh <jassi.brar@samsung.com> - * Credits: Graeme Gregory, Sean Choi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef __S3C_AC97_H_ -#define __S3C_AC97_H_ - -#define S3C_AC97_DAI_PCM 0 -#define S3C_AC97_DAI_MIC 1 - -#endif /* __S3C_AC97_H_ */ diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index 21240198c5d..5cb3b880f0d 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -14,17 +14,11 @@ * option) any later version. */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dma-mapping.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <asm/dma.h> #include <mach/hardware.h> @@ -32,6 +26,9 @@ #include "dma.h" +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + static const struct snd_pcm_hardware dma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -313,7 +310,7 @@ dma_pointer(struct snd_pcm_substream *substream) /* we seem to be getting the odd error from the pcm library due * to out-of-bounds pointers. this is maybe due to the dma engine * not having loaded the new values for the channel before being - * callled... (todo - fix ) + * called... (todo - fix ) */ if (res >= snd_pcm_lib_buffer_bytes(substream)) { diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index f8cd2b4223a..c50659269a4 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -12,9 +12,6 @@ #ifndef _S3C_AUDIO_H #define _S3C_AUDIO_H -#define ST_RUNNING (1<<0) -#define ST_OPENED (1<<1) - struct s3c_dma_params { struct s3c2410_dma_client *client; /* stream identifier */ int channel; /* Channel ID */ @@ -22,9 +19,4 @@ struct s3c_dma_params { int dma_size; /* Size of the DMA transfer */ }; -#define S3C24XX_DAI_I2S 0 - -/* platform data */ -extern struct snd_ac97_bus_ops s3c24xx_ac97_ops; - #endif diff --git a/sound/soc/samsung/goni_wm8994.c b/sound/soc/samsung/goni_wm8994.c index 34dd9ef1b9c..f6b3a3ce591 100644 --- a/sound/soc/samsung/goni_wm8994.c +++ b/sound/soc/samsung/goni_wm8994.c @@ -11,21 +11,13 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/io.h> -#include <linux/platform_device.h> #include <sound/soc.h> #include <sound/jack.h> + #include <asm/mach-types.h> #include <mach/gpio.h> -#include <mach/regs-clock.h> -#include <linux/mfd/wm8994/core.h> -#include <linux/mfd/wm8994/registers.h> #include "../codecs/wm8994.h" -#include "dma.h" -#include "i2s.h" #define MACHINE_NAME 0 #define CPU_VOICE_DAI 1 diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c index c45f7ce14d6..241f55d0066 100644 --- a/sound/soc/samsung/h1940_uda1380.c +++ b/sound/soc/samsung/h1940_uda1380.c @@ -13,25 +13,16 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> -#include <linux/i2c.h> #include <linux/gpio.h> #include <sound/soc.h> -#include <sound/uda1380.h> #include <sound/jack.h> #include <plat/regs-iis.h> - #include <mach/h1940-latch.h> - #include <asm/mach-types.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda1380.h" static unsigned int rates[] = { 11025, diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index d00ac3a7102..ffa09b3b2ca 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -15,9 +15,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c index 08802520e01..3b53ad54bc3 100644 --- a/sound/soc/samsung/jive_wm8750.c +++ b/sound/soc/samsung/jive_wm8750.c @@ -11,22 +11,11 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/clk.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> #include <asm/mach-types.h> -#include "dma.h" #include "s3c2412-i2s.h" - #include "../codecs/wm8750.h" static const struct snd_soc_dapm_route audio_map[] = { diff --git a/sound/soc/samsung/lm4857.h b/sound/soc/samsung/lm4857.h deleted file mode 100644 index 0cf5b7011d6..00000000000 --- a/sound/soc/samsung/lm4857.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * lm4857.h -- ALSA Soc Audio Layer - * - * Copyright 2007 Wolfson Microelectronics PLC. - * Author: Graeme Gregory - * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * Revision history - * 18th Jun 2007 Initial version. - */ - -#ifndef LM4857_H_ -#define LM4857_H_ - -/* The register offsets in the cache array */ -#define LM4857_MVOL 0 -#define LM4857_LVOL 1 -#define LM4857_RVOL 2 -#define LM4857_CTRL 3 - -/* the shifts required to set these bits */ -#define LM4857_3D 5 -#define LM4857_WAKEUP 5 -#define LM4857_EPGAIN 4 - -#endif /*LM4857_H_*/ - diff --git a/sound/soc/samsung/ln2440sbc_alc650.c b/sound/soc/samsung/ln2440sbc_alc650.c index a2bb34def74..bd91c19a6c0 100644 --- a/sound/soc/samsung/ln2440sbc_alc650.c +++ b/sound/soc/samsung/ln2440sbc_alc650.c @@ -16,15 +16,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card ln2440sbc; static struct snd_soc_dai_link ln2440sbc_dai[] = { diff --git a/sound/soc/samsung/neo1973_gta02_wm8753.c b/sound/soc/samsung/neo1973_gta02_wm8753.c deleted file mode 100644 index 0d0ae2b9eef..00000000000 --- a/sound/soc/samsung/neo1973_gta02_wm8753.c +++ /dev/null @@ -1,504 +0,0 @@ -/* - * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02) - * - * Copyright 2007 Openmoko Inc - * Author: Graeme Gregory <graeme@openmoko.org> - * Copyright 2007 Wolfson Microelectronics PLC. - * Author: Graeme Gregory <linux@wolfsonmicro.com> - * Copyright 2009 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/gpio.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> - -#include <asm/mach-types.h> - -#include <plat/regs-iis.h> - -#include <mach/regs-clock.h> -#include <asm/io.h> -#include <mach/gta02.h> -#include "../codecs/wm8753.h" -#include "dma.h" -#include "s3c24xx-i2s.h" - -static struct snd_soc_card neo1973_gta02; - -static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - unsigned int pll_out = 0, bclk = 0; - int ret = 0; - unsigned long iis_clkrate; - - iis_clkrate = s3c24xx_i2s_get_clockrate(); - - switch (params_rate(params)) { - case 8000: - case 16000: - pll_out = 12288000; - break; - case 48000: - bclk = WM8753_BCLK_DIV_4; - pll_out = 12288000; - break; - case 96000: - bclk = WM8753_BCLK_DIV_2; - pll_out = 12288000; - break; - case 11025: - bclk = WM8753_BCLK_DIV_16; - pll_out = 11289600; - break; - case 22050: - bclk = WM8753_BCLK_DIV_8; - pll_out = 11289600; - break; - case 44100: - bclk = WM8753_BCLK_DIV_4; - pll_out = 11289600; - break; - case 88200: - bclk = WM8753_BCLK_DIV_2; - pll_out = 11289600; - break; - } - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, - SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* set MCLK division for sample rate */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, - S3C2410_IISMOD_32FS); - if (ret < 0) - return ret; - - /* set codec BCLK division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, - WM8753_BCLKDIV, bclk); - if (ret < 0) - return ret; - - /* set prescaler division for sample rate */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, - S3C24XX_PRESCALE(4, 4)); - if (ret < 0) - return ret; - - /* codec PLL input is PCLK/4 */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, - iis_clkrate / 4, pll_out); - if (ret < 0) - return ret; - - return 0; -} - -static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - - /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); -} - -/* - * Neo1973 WM8753 HiFi DAI opserations. - */ -static struct snd_soc_ops neo1973_gta02_hifi_ops = { - .hw_params = neo1973_gta02_hifi_hw_params, - .hw_free = neo1973_gta02_hifi_hw_free, -}; - -static int neo1973_gta02_voice_hw_params( - struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - unsigned int pcmdiv = 0; - int ret = 0; - unsigned long iis_clkrate; - - iis_clkrate = s3c24xx_i2s_get_clockrate(); - - if (params_rate(params) != 8000) - return -EINVAL; - if (params_channels(params) != 1) - return -EINVAL; - - pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ - - /* todo: gg check mode (DSP_B) against CSR datasheet */ - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, - 12288000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* set codec PCM division for sample rate */ - ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, - pcmdiv); - if (ret < 0) - return ret; - - /* configure and enable PLL for 12.288MHz output */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, - iis_clkrate / 4, 12288000); - if (ret < 0) - return ret; - - return 0; -} - -static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - - /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); -} - -static struct snd_soc_ops neo1973_gta02_voice_ops = { - .hw_params = neo1973_gta02_voice_hw_params, - .hw_free = neo1973_gta02_voice_hw_free, -}; - -#define LM4853_AMP 1 -#define LM4853_SPK 2 - -static u8 lm4853_state; - -/* This has no effect, it exists only to maintain compatibility with - * existing ALSA state files. - */ -static int lm4853_set_state(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int val = ucontrol->value.integer.value[0]; - - if (val) - lm4853_state |= LM4853_AMP; - else - lm4853_state &= ~LM4853_AMP; - - return 0; -} - -static int lm4853_get_state(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP; - - return 0; -} - -static int lm4853_set_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int val = ucontrol->value.integer.value[0]; - - if (val) { - lm4853_state |= LM4853_SPK; - gpio_set_value(GTA02_GPIO_HP_IN, 0); - } else { - lm4853_state &= ~LM4853_SPK; - gpio_set_value(GTA02_GPIO_HP_IN, 1); - } - - return 0; -} - -static int lm4853_get_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1; - - return 0; -} - -static int lm4853_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, - int event) -{ - gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event)); - - return 0; -} - -static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { - SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), - SND_SOC_DAPM_LINE("GSM Line Out", NULL), - SND_SOC_DAPM_LINE("GSM Line In", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Handset Mic", NULL), - SND_SOC_DAPM_SPK("Handset Spk", NULL), -}; - - -/* example machine audio_mapnections */ -static const struct snd_soc_dapm_route audio_map[] = { - - /* Connections to the lm4853 amp */ - {"Stereo Out", NULL, "LOUT1"}, - {"Stereo Out", NULL, "ROUT1"}, - - /* Connections to the GSM Module */ - {"GSM Line Out", NULL, "MONO1"}, - {"GSM Line Out", NULL, "MONO2"}, - {"RXP", NULL, "GSM Line In"}, - {"RXN", NULL, "GSM Line In"}, - - /* Connections to Headset */ - {"MIC1", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Headset Mic"}, - - /* Call Mic */ - {"MIC2", NULL, "Mic Bias"}, - {"MIC2N", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Handset Mic"}, - - /* Call Speaker */ - {"Handset Spk", NULL, "LOUT2"}, - {"Handset Spk", NULL, "ROUT2"}, - - /* Connect the ALC pins */ - {"ACIN", NULL, "ACOP"}, -}; - -static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { - SOC_DAPM_PIN_SWITCH("Stereo Out"), - SOC_DAPM_PIN_SWITCH("GSM Line Out"), - SOC_DAPM_PIN_SWITCH("GSM Line In"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Handset Mic"), - SOC_DAPM_PIN_SWITCH("Handset Spk"), - - /* This has no effect, it exists only to maintain compatibility with - * existing ALSA state files. - */ - SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0, - lm4853_get_state, - lm4853_set_state), - SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0, - lm4853_get_spk, - lm4853_set_spk), -}; - -/* - * This is an example machine initialisation for a wm8753 connected to a - * neo1973 GTA02. - */ -static int neo1973_gta02_wm8753_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_codec *codec = rtd->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; - int err; - - /* set up NC codec pins */ - snd_soc_dapm_nc_pin(dapm, "OUT3"); - snd_soc_dapm_nc_pin(dapm, "OUT4"); - snd_soc_dapm_nc_pin(dapm, "LINE1"); - snd_soc_dapm_nc_pin(dapm, "LINE2"); - - /* Add neo1973 gta02 specific widgets */ - snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets, - ARRAY_SIZE(wm8753_dapm_widgets)); - - /* add neo1973 gta02 specific controls */ - err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls, - ARRAY_SIZE(wm8753_neo1973_gta02_controls)); - - if (err < 0) - return err; - - /* set up neo1973 gta02 specific audio path audio_map */ - snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); - - /* set endpoints to default off mode */ - snd_soc_dapm_disable_pin(dapm, "Stereo Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Handset Mic"); - snd_soc_dapm_disable_pin(dapm, "Handset Spk"); - - /* allow audio paths from the GSM modem to run during suspend */ - snd_soc_dapm_ignore_suspend(dapm, "Stereo Out"); - snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out"); - snd_soc_dapm_ignore_suspend(dapm, "GSM Line In"); - snd_soc_dapm_ignore_suspend(dapm, "Headset Mic"); - snd_soc_dapm_ignore_suspend(dapm, "Handset Mic"); - snd_soc_dapm_ignore_suspend(dapm, "Handset Spk"); - - snd_soc_dapm_sync(dapm); - - return 0; -} - -/* - * BT Codec DAI - */ -static struct snd_soc_dai_driver bt_dai = { - .name = "bluetooth-dai", - .playback = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .capture = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, -}; - -static struct snd_soc_dai_link neo1973_gta02_dai[] = { -{ /* Hifi Playback - for similatious use with voice below */ - .name = "WM8753", - .stream_name = "WM8753 HiFi", - .cpu_dai_name = "s3c24xx-iis", - .codec_dai_name = "wm8753-hifi", - .init = neo1973_gta02_wm8753_init, - .platform_name = "samsung-audio", - .codec_name = "wm8753-codec.0-001a", - .ops = &neo1973_gta02_hifi_ops, -}, -{ /* Voice via BT */ - .name = "Bluetooth", - .stream_name = "Voice", - .cpu_dai_name = "bluetooth-dai", - .codec_dai_name = "wm8753-voice", - .ops = &neo1973_gta02_voice_ops, - .codec_name = "wm8753-codec.0-001a", - .platform_name = "samsung-audio", -}, -}; - -static struct snd_soc_card neo1973_gta02 = { - .name = "neo1973-gta02", - .dai_link = neo1973_gta02_dai, - .num_links = ARRAY_SIZE(neo1973_gta02_dai), -}; - -static struct platform_device *neo1973_gta02_snd_device; - -static int __init neo1973_gta02_init(void) -{ - int ret; - - if (!machine_is_neo1973_gta02()) { - printk(KERN_INFO - "Only GTA02 is supported by this ASoC driver\n"); - return -ENODEV; - } - - neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1); - if (!neo1973_gta02_snd_device) - return -ENOMEM; - - /* register bluetooth DAI here */ - ret = snd_soc_register_dai(&neo1973_gta02_snd_device->dev, &bt_dai); - if (ret) - goto err_put_device; - - platform_set_drvdata(neo1973_gta02_snd_device, &neo1973_gta02); - ret = platform_device_add(neo1973_gta02_snd_device); - - if (ret) - goto err_unregister_dai; - - /* Initialise GPIOs used by amp */ - ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN"); - if (ret) { - pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN); - goto err_del_device; - } - - ret = gpio_direction_output(GTA02_GPIO_HP_IN, 1); - if (ret) { - pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN); - goto err_free_gpio_hp_in; - } - - ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT"); - if (ret) { - pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT); - goto err_free_gpio_hp_in; - } - - ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1); - if (ret) { - pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT); - goto err_free_gpio_amp_shut; - } - - return 0; - -err_free_gpio_amp_shut: - gpio_free(GTA02_GPIO_AMP_SHUT); -err_free_gpio_hp_in: - gpio_free(GTA02_GPIO_HP_IN); -err_del_device: - platform_device_del(neo1973_gta02_snd_device); -err_unregister_dai: - snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev); -err_put_device: - platform_device_put(neo1973_gta02_snd_device); - return ret; -} -module_init(neo1973_gta02_init); - -static void __exit neo1973_gta02_exit(void) -{ - snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev); - platform_device_unregister(neo1973_gta02_snd_device); - gpio_free(GTA02_GPIO_HP_IN); - gpio_free(GTA02_GPIO_AMP_SHUT); -} -module_exit(neo1973_gta02_exit); - -/* Module information */ -MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org"); -MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c index d20815d5ab2..78bfdb3f5d7 100644 --- a/sound/soc/samsung/neo1973_wm8753.c +++ b/sound/soc/samsung/neo1973_wm8753.c @@ -1,57 +1,32 @@ /* - * neo1973_wm8753.c -- SoC audio for Neo1973 + * neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices * + * Copyright 2007 Openmoko Inc + * Author: Graeme Gregory <graeme@openmoko.org> * Copyright 2007 Wolfson Microelectronics PLC. * Author: Graeme Gregory * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * Copyright 2009 Wolfson Microelectronics * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * */ #include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> #include <linux/platform_device.h> -#include <linux/i2c.h> -#include <sound/core.h> -#include <sound/pcm.h> +#include <linux/gpio.h> + #include <sound/soc.h> -#include <sound/tlv.h> #include <asm/mach-types.h> -#include <asm/hardware/scoop.h> -#include <mach/regs-clock.h> -#include <mach/regs-gpio.h> -#include <mach/hardware.h> -#include <linux/io.h> -#include <mach/spi-gpio.h> - #include <plat/regs-iis.h> +#include <mach/gta02.h> #include "../codecs/wm8753.h" -#include "lm4857.h" -#include "dma.h" #include "s3c24xx-i2s.h" -/* define the scenarios */ -#define NEO_AUDIO_OFF 0 -#define NEO_GSM_CALL_AUDIO_HANDSET 1 -#define NEO_GSM_CALL_AUDIO_HEADSET 2 -#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3 -#define NEO_STEREO_TO_SPEAKERS 4 -#define NEO_STEREO_TO_HEADPHONES 5 -#define NEO_CAPTURE_HANDSET 6 -#define NEO_CAPTURE_HEADSET 7 -#define NEO_CAPTURE_BLUETOOTH 8 - -static struct snd_soc_card neo1973; -static struct i2c_client *i2c; - static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -62,8 +37,6 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - pr_debug("Entered %s\n", __func__); - iis_clkrate = s3c24xx_i2s_get_clockrate(); switch (params_rate(params)) { @@ -148,8 +121,6 @@ static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - pr_debug("Entered %s\n", __func__); - /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); } @@ -171,8 +142,6 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - pr_debug("Entered %s\n", __func__); - iis_clkrate = s3c24xx_i2s_get_clockrate(); if (params_rate(params) != 8000) @@ -214,8 +183,6 @@ static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - pr_debug("Entered %s\n", __func__); - /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); } @@ -225,335 +192,232 @@ static struct snd_soc_ops neo1973_voice_ops = { .hw_free = neo1973_voice_hw_free, }; -static int neo1973_scenario; - -static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = neo1973_scenario; - return 0; -} - -static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) -{ - struct snd_soc_dapm_context *dapm = &codec->dapm; - - pr_debug("Entered %s\n", __func__); - - switch (neo1973_scenario) { - case NEO_AUDIO_OFF: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_HANDSET: - snd_soc_dapm_enable_pin(dapm, "Audio Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_enable_pin(dapm, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_HEADSET: - snd_soc_dapm_enable_pin(dapm, "Audio Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line In"); - snd_soc_dapm_enable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_GSM_CALL_AUDIO_BLUETOOTH: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_enable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_STEREO_TO_SPEAKERS: - snd_soc_dapm_enable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_STEREO_TO_HEADPHONES: - snd_soc_dapm_enable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_CAPTURE_HANDSET: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_enable_pin(dapm, "Call Mic"); - break; - case NEO_CAPTURE_HEADSET: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_enable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - case NEO_CAPTURE_BLUETOOTH: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - break; - default: - snd_soc_dapm_disable_pin(dapm, "Audio Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); - snd_soc_dapm_disable_pin(dapm, "GSM Line In"); - snd_soc_dapm_disable_pin(dapm, "Headset Mic"); - snd_soc_dapm_disable_pin(dapm, "Call Mic"); - } +/* Shared routes and controls */ - snd_soc_dapm_sync(dapm); +static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = { + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Handset Mic", NULL), +}; - return 0; -} +static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = { + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, -static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, - pr_debug("Entered %s\n", __func__); + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Handset Mic"}, - if (neo1973_scenario == ucontrol->value.integer.value[0]) - return 0; + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +}; - neo1973_scenario = ucontrol->value.integer.value[0]; - set_scenario_endpoints(codec, neo1973_scenario); - return 1; -} +static const struct snd_kcontrol_new neo1973_wm8753_controls[] = { + SOC_DAPM_PIN_SWITCH("GSM Line Out"), + SOC_DAPM_PIN_SWITCH("GSM Line In"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Mic"), +}; -static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; +/* GTA02 specific routes and controlls */ -static void lm4857_write_regs(void) -{ - pr_debug("Entered %s\n", __func__); +#ifdef CONFIG_MACH_NEO1973_GTA02 - if (i2c_master_send(i2c, lm4857_regs, 4) != 4) - printk(KERN_ERR "lm4857: i2c write failed\n"); -} +static int gta02_speaker_enabled; -static int lm4857_get_reg(struct snd_kcontrol *kcontrol, +static int lm4853_set_spk(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int shift = mc->shift; - int mask = mc->max; + gta02_speaker_enabled = ucontrol->value.integer.value[0]; - pr_debug("Entered %s\n", __func__); + gpio_set_value(GTA02_GPIO_HP_IN, !gta02_speaker_enabled); - ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; return 0; } -static int lm4857_set_reg(struct snd_kcontrol *kcontrol, +static int lm4853_get_spk(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int shift = mc->shift; - int mask = mc->max; - - if (((lm4857_regs[reg] >> shift) & mask) == - ucontrol->value.integer.value[0]) - return 0; - - lm4857_regs[reg] &= ~(mask << shift); - lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift; - lm4857_write_regs(); - return 1; + ucontrol->value.integer.value[0] = gta02_speaker_enabled; + return 0; } -static int lm4857_get_mode(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int lm4853_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) { - u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; - - pr_debug("Entered %s\n", __func__); - - if (value) - value -= 5; + gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event)); - ucontrol->value.integer.value[0] = value; return 0; } -static int lm4857_set_mode(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - u8 value = ucontrol->value.integer.value[0]; - - pr_debug("Entered %s\n", __func__); - - if (value) - value += 5; - - if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value) - return 0; - - lm4857_regs[LM4857_CTRL] &= 0xF0; - lm4857_regs[LM4857_CTRL] |= value; - lm4857_write_regs(); - return 1; -} +static const struct snd_soc_dapm_route neo1973_gta02_routes[] = { + /* Connections to the amp */ + {"Stereo Out", NULL, "LOUT1"}, + {"Stereo Out", NULL, "ROUT1"}, -static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { - SND_SOC_DAPM_LINE("Audio Out", NULL), - SND_SOC_DAPM_LINE("GSM Line Out", NULL), - SND_SOC_DAPM_LINE("GSM Line In", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), - SND_SOC_DAPM_MIC("Call Mic", NULL), + /* Call Speaker */ + {"Handset Spk", NULL, "LOUT2"}, + {"Handset Spk", NULL, "ROUT2"}, }; +static const struct snd_kcontrol_new neo1973_gta02_wm8753_controls[] = { + SOC_DAPM_PIN_SWITCH("Handset Spk"), + SOC_DAPM_PIN_SWITCH("Stereo Out"), -static const struct snd_soc_dapm_route dapm_routes[] = { - - /* Connections to the lm4857 amp */ - {"Audio Out", NULL, "LOUT1"}, - {"Audio Out", NULL, "ROUT1"}, - - /* Connections to the GSM Module */ - {"GSM Line Out", NULL, "MONO1"}, - {"GSM Line Out", NULL, "MONO2"}, - {"RXP", NULL, "GSM Line In"}, - {"RXN", NULL, "GSM Line In"}, + SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0, + lm4853_get_spk, + lm4853_set_spk), +}; - /* Connections to Headset */ - {"MIC1", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Headset Mic"}, +static const struct snd_soc_dapm_widget neo1973_gta02_wm8753_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Handset Spk", NULL), + SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), +}; - /* Call Mic */ - {"MIC2", NULL, "Mic Bias"}, - {"MIC2N", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Call Mic"}, +static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; - /* Connect the ALC pins */ - {"ACIN", NULL, "ACOP"}, -}; + ret = snd_soc_dapm_new_controls(dapm, neo1973_gta02_wm8753_dapm_widgets, + ARRAY_SIZE(neo1973_gta02_wm8753_dapm_widgets)); + if (ret) + return ret; -static const char *lm4857_mode[] = { - "Off", - "Call Speaker", - "Stereo Speakers", - "Stereo Speakers + Headphones", - "Headphones" -}; + ret = snd_soc_dapm_add_routes(dapm, neo1973_gta02_routes, + ARRAY_SIZE(neo1973_gta02_routes)); + if (ret) + return ret; -static const struct soc_enum lm4857_mode_enum[] = { - SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode), -}; + ret = snd_soc_add_controls(codec, neo1973_gta02_wm8753_controls, + ARRAY_SIZE(neo1973_gta02_wm8753_controls)); + if (ret) + return ret; -static const char *neo_scenarios[] = { - "Off", - "GSM Handset", - "GSM Headset", - "GSM Bluetooth", - "Speakers", - "Headphones", - "Capture Handset", - "Capture Headset", - "Capture Bluetooth" -}; + snd_soc_dapm_disable_pin(dapm, "Stereo Out"); + snd_soc_dapm_disable_pin(dapm, "Handset Spk"); + snd_soc_dapm_ignore_suspend(dapm, "Stereo Out"); + snd_soc_dapm_ignore_suspend(dapm, "Handset Spk"); -static const struct soc_enum neo_scenario_enum[] = { - SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios), -}; + return 0; +} -static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); -static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); - -static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { - SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg, stereo_tlv), - SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg, stereo_tlv), - SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg, mono_tlv), - SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], - lm4857_get_mode, lm4857_set_mode), - SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], - neo1973_get_scenario, neo1973_set_scenario), - SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0, - lm4857_get_reg, lm4857_set_reg), - SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0, - lm4857_get_reg, lm4857_set_reg), - SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0, - lm4857_get_reg, lm4857_set_reg), - SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0, - lm4857_get_reg, lm4857_set_reg), -}; +#else +static int neo1973_gta02_wm8753_init(struct snd_soc_code *codec) { return 0; } +#endif -/* - * This is an example machine initialisation for a wm8753 connected to a - * neo1973 II. It is missing logic to detect hp/mic insertions and logic - * to re-route the audio in such an event. - */ static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; - int err; - - pr_debug("Entered %s\n", __func__); + int ret; /* set up NC codec pins */ - snd_soc_dapm_nc_pin(dapm, "LOUT2"); - snd_soc_dapm_nc_pin(dapm, "ROUT2"); + if (machine_is_neo1973_gta01()) { + snd_soc_dapm_nc_pin(dapm, "LOUT2"); + snd_soc_dapm_nc_pin(dapm, "ROUT2"); + } snd_soc_dapm_nc_pin(dapm, "OUT3"); snd_soc_dapm_nc_pin(dapm, "OUT4"); snd_soc_dapm_nc_pin(dapm, "LINE1"); snd_soc_dapm_nc_pin(dapm, "LINE2"); /* Add neo1973 specific widgets */ - snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets, - ARRAY_SIZE(wm8753_dapm_widgets)); - - /* set endpoints to default mode */ - set_scenario_endpoints(codec, NEO_AUDIO_OFF); + ret = snd_soc_dapm_new_controls(dapm, neo1973_wm8753_dapm_widgets, + ARRAY_SIZE(neo1973_wm8753_dapm_widgets)); + if (ret) + return ret; /* add neo1973 specific controls */ - err = snd_soc_add_controls(codec, wm8753_neo1973_controls, - ARRAY_SIZE(8753_neo1973_controls)); - if (err < 0) - return err; + ret = snd_soc_add_controls(codec, neo1973_wm8753_controls, + ARRAY_SIZE(neo1973_wm8753_controls)); + if (ret) + return ret; /* set up neo1973 specific audio routes */ - err = snd_soc_dapm_add_routes(dapm, dapm_routes, - ARRAY_SIZE(dapm_routes)); + ret = snd_soc_dapm_add_routes(dapm, neo1973_wm8753_routes, + ARRAY_SIZE(neo1973_wm8753_routes)); + if (ret) + return ret; + + /* set endpoints to default off mode */ + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Handset Mic"); + + /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(dapm, "GSM Line In"); + snd_soc_dapm_ignore_suspend(dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(dapm, "Handset Mic"); + + if (machine_is_neo1973_gta02()) { + ret = neo1973_gta02_wm8753_init(codec); + if (ret) + return ret; + } snd_soc_dapm_sync(dapm); + return 0; } -/* - * BT Codec DAI - */ -static struct snd_soc_dai bt_dai = { - .name = "bluetooth-dai", - .playback = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .capture = { - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +/* GTA01 specific controlls */ + +#ifdef CONFIG_MACH_NEO1973_GTA01 + +static const struct snd_soc_dapm_route neo1973_lm4857_routes[] = { + {"Amp IN", NULL, "ROUT1"}, + {"Amp IN", NULL, "LOUT1"}, + + {"Handset Spk", NULL, "Amp EP"}, + {"Stereo Out", NULL, "Amp LS"}, + {"Headphone", NULL, "Amp HP"}, +}; + +static const struct snd_soc_dapm_widget neo1973_lm4857_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Handset Spk", NULL), + SND_SOC_DAPM_SPK("Stereo Out", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), }; +static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm) +{ + int ret; + + ret = snd_soc_dapm_new_controls(dapm, neo1973_lm4857_dapm_widgets, + ARRAY_SIZE(neo1973_lm4857_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, neo1973_lm4857_routes, + ARRAY_SIZE(neo1973_lm4857_routes)); + if (ret) + return ret; + + snd_soc_dapm_ignore_suspend(dapm, "Stereo Out"); + snd_soc_dapm_ignore_suspend(dapm, "Handset Spk"); + snd_soc_dapm_ignore_suspend(dapm, "Headphone"); + + snd_soc_dapm_sync(dapm); + + return 0; +} + +#else +static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm) { return 0; }; +#endif + static struct snd_soc_dai_link neo1973_dai[] = { { /* Hifi Playback - for similatious use with voice below */ .name = "WM8753", @@ -569,90 +433,49 @@ static struct snd_soc_dai_link neo1973_dai[] = { .name = "Bluetooth", .stream_name = "Voice", .platform_name = "samsung-audio", - .cpu_dai_name = "bluetooth-dai", + .cpu_dai_name = "dfbmcs320-pcm", .codec_dai_name = "wm8753-voice", .codec_name = "wm8753-codec.0-001a", .ops = &neo1973_voice_ops, }, }; -static struct snd_soc_card neo1973 = { - .name = "neo1973", - .dai_link = neo1973_dai, - .num_links = ARRAY_SIZE(neo1973_dai), +static struct snd_soc_aux_dev neo1973_aux_devs[] = { + { + .name = "dfbmcs320", + .codec_name = "dfbmcs320.0", + }, + { + .name = "lm4857", + .codec_name = "lm4857.0-007c", + .init = neo1973_lm4857_init, + }, }; -static int lm4857_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - pr_debug("Entered %s\n", __func__); - - i2c = client; - - lm4857_write_regs(); - return 0; -} - -static int lm4857_i2c_remove(struct i2c_client *client) -{ - pr_debug("Entered %s\n", __func__); - - i2c = NULL; - - return 0; -} - -static u8 lm4857_state; - -static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) -{ - pr_debug("Entered %s\n", __func__); - - dev_dbg(&dev->dev, "lm4857_suspend\n"); - lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf; - if (lm4857_state) { - lm4857_regs[LM4857_CTRL] &= 0xf0; - lm4857_write_regs(); - } - return 0; -} - -static int lm4857_resume(struct i2c_client *dev) -{ - pr_debug("Entered %s\n", __func__); - - if (lm4857_state) { - lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f); - lm4857_write_regs(); - } - return 0; -} - -static void lm4857_shutdown(struct i2c_client *dev) -{ - pr_debug("Entered %s\n", __func__); - - dev_dbg(&dev->dev, "lm4857_shutdown\n"); - lm4857_regs[LM4857_CTRL] &= 0xf0; - lm4857_write_regs(); -} +static struct snd_soc_codec_conf neo1973_codec_conf[] = { + { + .dev_name = "lm4857.0-007c", + .name_prefix = "Amp", + }, +}; -static const struct i2c_device_id lm4857_i2c_id[] = { - { "neo1973_lm4857", 0 }, - { } +#ifdef CONFIG_MACH_NEO1973_GTA02 +static const struct gpio neo1973_gta02_gpios[] = { + { GTA02_GPIO_HP_IN, GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" }, + { GTA02_GPIO_AMP_SHUT, GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" }, }; +#else +static const struct gpio neo1973_gta02_gpios[] = {}; +#endif -static struct i2c_driver lm4857_i2c_driver = { - .driver = { - .name = "LM4857 I2C Amp", - .owner = THIS_MODULE, - }, - .suspend = lm4857_suspend, - .resume = lm4857_resume, - .shutdown = lm4857_shutdown, - .probe = lm4857_i2c_probe, - .remove = lm4857_i2c_remove, - .id_table = lm4857_i2c_id, +static struct snd_soc_card neo1973 = { + .name = "neo1973", + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), + .aux_dev = neo1973_aux_devs, + .num_aux_devs = ARRAY_SIZE(neo1973_aux_devs), + .codec_conf = neo1973_codec_conf, + .num_configs = ARRAY_SIZE(neo1973_codec_conf), }; static struct platform_device *neo1973_snd_device; @@ -661,46 +484,56 @@ static int __init neo1973_init(void) { int ret; - pr_debug("Entered %s\n", __func__); - - if (!machine_is_neo1973_gta01()) { - printk(KERN_INFO - "Only GTA01 hardware supported by ASoC driver\n"); + if (!machine_is_neo1973_gta01() && !machine_is_neo1973_gta02()) return -ENODEV; + + if (machine_is_neo1973_gta02()) { + neo1973.name = "neo1973gta02"; + neo1973.num_aux_devs = 1; + + ret = gpio_request_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + if (ret) + return ret; } neo1973_snd_device = platform_device_alloc("soc-audio", -1); - if (!neo1973_snd_device) - return -ENOMEM; + if (!neo1973_snd_device) { + ret = -ENOMEM; + goto err_gpio_free; + } platform_set_drvdata(neo1973_snd_device, &neo1973); ret = platform_device_add(neo1973_snd_device); - if (ret) { - platform_device_put(neo1973_snd_device); - return ret; - } - - ret = i2c_add_driver(&lm4857_i2c_driver); + if (ret) + goto err_put_device; - if (ret != 0) - platform_device_unregister(neo1973_snd_device); + return 0; +err_put_device: + platform_device_put(neo1973_snd_device); +err_gpio_free: + if (machine_is_neo1973_gta02()) { + gpio_free_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + } return ret; } +module_init(neo1973_init); static void __exit neo1973_exit(void) { - pr_debug("Entered %s\n", __func__); - - i2c_del_driver(&lm4857_i2c_driver); platform_device_unregister(neo1973_snd_device); -} -module_init(neo1973_init); + if (machine_is_neo1973_gta02()) { + gpio_free_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + } +} module_exit(neo1973_exit); /* Module information */ MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org"); -MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 48d0b750406..38aac7d57a5 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -11,20 +11,11 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> -#include <linux/delay.h> #include <linux/clk.h> -#include <linux/kernel.h> -#include <linux/gpio.h> #include <linux/io.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> #include <plat/dma.h> @@ -32,6 +23,113 @@ #include "dma.h" #include "pcm.h" +/*Register Offsets */ +#define S3C_PCM_CTL 0x00 +#define S3C_PCM_CLKCTL 0x04 +#define S3C_PCM_TXFIFO 0x08 +#define S3C_PCM_RXFIFO 0x0C +#define S3C_PCM_IRQCTL 0x10 +#define S3C_PCM_IRQSTAT 0x14 +#define S3C_PCM_FIFOSTAT 0x18 +#define S3C_PCM_CLRINT 0x20 + +/* PCM_CTL Bit-Fields */ +#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13 +#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7 +#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6) +#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5) +#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4) +#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3) +#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2) +#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1) +#define S3C_PCM_CTL_ENABLE (0x1 << 0) + +/* PCM_CLKCTL Bit-Fields */ +#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19) +#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18) +#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9 +#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0 + +/* PCM_TXFIFO Bit-Fields */ +#define S3C_PCM_TXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_RXFIFO Bit-Fields */ +#define S3C_PCM_RXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_IRQCTL Bit-Fields */ +#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14) +#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12) +#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11) +#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10) +#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9) +#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8) +#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7) +#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6) +#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5) +#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4) +#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3) +#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2) +#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1) +#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0) + +/* PCM_IRQSTAT Bit-Fields */ +#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13) +#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12) +#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11) +#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10) +#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9) +#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8) +#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7) +#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6) +#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5) +#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4) +#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3) +#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2) +#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1) +#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0) + +/* PCM_FIFOSTAT Bit-Fields */ +#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14) +#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12) +#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10) +#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4) +#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2) +#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0) + +/** + * struct s3c_pcm_info - S3C PCM Controller information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + */ +struct s3c_pcm_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + + unsigned int sclk_per_fs; + + /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ + unsigned int idleclk; + + struct clk *pclk; + struct clk *cclk; + + struct s3c_dma_params *dma_playback; + struct s3c_dma_params *dma_capture; +}; + static struct s3c2410_dma_client s3c_pcm_dma_client_out = { .name = "PCM Stereo out" }; diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h index 03393dcf852..726baf81461 100644 --- a/sound/soc/samsung/pcm.h +++ b/sound/soc/samsung/pcm.h @@ -9,116 +9,9 @@ #ifndef __S3C_PCM_H #define __S3C_PCM_H __FILE__ -/*Register Offsets */ -#define S3C_PCM_CTL (0x00) -#define S3C_PCM_CLKCTL (0x04) -#define S3C_PCM_TXFIFO (0x08) -#define S3C_PCM_RXFIFO (0x0C) -#define S3C_PCM_IRQCTL (0x10) -#define S3C_PCM_IRQSTAT (0x14) -#define S3C_PCM_FIFOSTAT (0x18) -#define S3C_PCM_CLRINT (0x20) - -/* PCM_CTL Bit-Fields */ -#define S3C_PCM_CTL_TXDIPSTICK_MASK (0x3f) -#define S3C_PCM_CTL_TXDIPSTICK_SHIFT (13) -#define S3C_PCM_CTL_RXDIPSTICK_MASK (0x3f) -#define S3C_PCM_CTL_RXDIPSTICK_SHIFT (7) -#define S3C_PCM_CTL_TXDMA_EN (0x1<<6) -#define S3C_PCM_CTL_RXDMA_EN (0x1<<5) -#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1<<4) -#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1<<3) -#define S3C_PCM_CTL_TXFIFO_EN (0x1<<2) -#define S3C_PCM_CTL_RXFIFO_EN (0x1<<1) -#define S3C_PCM_CTL_ENABLE (0x1<<0) - -/* PCM_CLKCTL Bit-Fields */ -#define S3C_PCM_CLKCTL_SERCLK_EN (0x1<<19) -#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1<<18) -#define S3C_PCM_CLKCTL_SCLKDIV_MASK (0x1ff) -#define S3C_PCM_CLKCTL_SYNCDIV_MASK (0x1ff) -#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT (9) -#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT (0) - -/* PCM_TXFIFO Bit-Fields */ -#define S3C_PCM_TXFIFO_DVALID (0x1<<16) -#define S3C_PCM_TXFIFO_DATA_MSK (0xffff<<0) - -/* PCM_RXFIFO Bit-Fields */ -#define S3C_PCM_RXFIFO_DVALID (0x1<<16) -#define S3C_PCM_RXFIFO_DATA_MSK (0xffff<<0) - -/* PCM_IRQCTL Bit-Fields */ -#define S3C_PCM_IRQCTL_IRQEN (0x1<<14) -#define S3C_PCM_IRQCTL_WRDEN (0x1<<12) -#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1<<11) -#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1<<10) -#define S3C_PCM_IRQCTL_TXFULLEN (0x1<<9) -#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1<<8) -#define S3C_PCM_IRQCTL_TXSTARVEN (0x1<<7) -#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1<<6) -#define S3C_PCM_IRQCTL_RXEMPTEN (0x1<<5) -#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1<<4) -#define S3C_PCM_IRQCTL_RXFULLEN (0x1<<3) -#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1<<2) -#define S3C_PCM_IRQCTL_RXSTARVEN (0x1<<1) -#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1<<0) - -/* PCM_IRQSTAT Bit-Fields */ -#define S3C_PCM_IRQSTAT_IRQPND (0x1<<13) -#define S3C_PCM_IRQSTAT_WRD_XFER (0x1<<12) -#define S3C_PCM_IRQSTAT_TXEMPTY (0x1<<11) -#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1<<10) -#define S3C_PCM_IRQSTAT_TXFULL (0x1<<9) -#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1<<8) -#define S3C_PCM_IRQSTAT_TXSTARV (0x1<<7) -#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1<<6) -#define S3C_PCM_IRQSTAT_RXEMPT (0x1<<5) -#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1<<4) -#define S3C_PCM_IRQSTAT_RXFULL (0x1<<3) -#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1<<2) -#define S3C_PCM_IRQSTAT_RXSTARV (0x1<<1) -#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1<<0) - -/* PCM_FIFOSTAT Bit-Fields */ -#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f<<14) -#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1<<13) -#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1<<12) -#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1<<11) -#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1<<10) -#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f<<4) -#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1<<3) -#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1<<2) -#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1<<1) -#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1<<0) - #define S3C_PCM_CLKSRC_PCLK 0 #define S3C_PCM_CLKSRC_MUX 1 #define S3C_PCM_SCLK_PER_FS 0 -/** - * struct s3c_pcm_info - S3C PCM Controller information - * @dev: The parent device passed to use from the probe. - * @regs: The pointer to the device register block. - * @dma_playback: DMA information for playback channel. - * @dma_capture: DMA information for capture channel. - */ -struct s3c_pcm_info { - spinlock_t lock; - struct device *dev; - void __iomem *regs; - - unsigned int sclk_per_fs; - - /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ - unsigned int idleclk; - - struct clk *pclk; - struct clk *cclk; - - struct s3c_dma_params *dma_playback; - struct s3c_dma_params *dma_capture; -}; - #endif /* __S3C_PCM_H */ diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c index f40027445dd..1e574a5d440 100644 --- a/sound/soc/samsung/rx1950_uda1380.c +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -17,26 +17,15 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> -#include <linux/i2c.h> #include <linux/gpio.h> -#include <linux/clk.h> #include <sound/soc.h> -#include <sound/uda1380.h> #include <sound/jack.h> #include <plat/regs-iis.h> - -#include <mach/regs-clock.h> - #include <asm/mach-types.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda1380.h" static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd); static int rx1950_startup(struct snd_pcm_substream *substream); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index 094f36e41e8..52074a2b069 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -20,9 +20,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <mach/dma.h> diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c index 7ea83786712..841ab14c110 100644 --- a/sound/soc/samsung/s3c2412-i2s.c +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -16,21 +16,13 @@ * option) any later version. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/delay.h> #include <linux/gpio.h> #include <linux/clk.h> -#include <linux/kernel.h> #include <linux/io.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> -#include <mach/hardware.h> +#include <sound/pcm_params.h> #include <mach/regs-gpio.h> #include <mach/dma.h> @@ -39,8 +31,6 @@ #include "regs-i2s-v2.h" #include "s3c2412-i2s.h" -#define S3C2412_I2S_DEBUG 0 - static struct s3c2410_dma_client s3c2412_dma_client_out = { .name = "I2S PCM Stereo out" }; diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c index 13e41ed8e22..63d8849d80b 100644 --- a/sound/soc/samsung/s3c24xx-i2s.c +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -14,28 +14,16 @@ * option) any later version. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/delay.h> #include <linux/clk.h> -#include <linux/jiffies.h> #include <linux/io.h> #include <linux/gpio.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pcm_params.h> -#include <mach/hardware.h> #include <mach/regs-gpio.h> -#include <mach/regs-clock.h> - -#include <asm/dma.h> #include <mach/dma.h> - #include <plat/regs-iis.h> #include "dma.h" diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c index a434032d183..349566f0686 100644 --- a/sound/soc/samsung/s3c24xx_simtec.c +++ b/sound/soc/samsung/s3c24xx_simtec.c @@ -7,20 +7,13 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/clk.h> -#include <linux/i2c.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> #include <plat/audio-simtec.h> -#include "dma.h" #include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c index 08fcaaa6690..ce6aef60417 100644 --- a/sound/soc/samsung/s3c24xx_simtec_hermes.c +++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c @@ -7,18 +7,8 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/clk.h> -#include <linux/platform_device.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include <plat/audio-simtec.h> - -#include "dma.h" -#include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" static const struct snd_soc_dapm_widget dapm_widgets[] = { diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c index 116e3e67016..a7ef7db5468 100644 --- a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c +++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c @@ -7,22 +7,10 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/clk.h> -#include <linux/platform_device.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include <plat/audio-simtec.h> - -#include "dma.h" -#include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" -#include "../codecs/tlv320aic23.h" - /* supported machines: * * Machine Connections AMP diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c index 2c09e93dd56..3cb70075107 100644 --- a/sound/soc/samsung/s3c24xx_uda134x.c +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -11,22 +11,15 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> #include <linux/clk.h> -#include <linux/mutex.h> #include <linux/gpio.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> + #include <sound/soc.h> #include <sound/s3c24xx_uda134x.h> -#include <sound/uda134x.h> #include <plat/regs-iis.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda134x.h" - /* #define ENFORCE_RATES 1 */ /* diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c index 61e2b5219d4..0a2c4f22303 100644 --- a/sound/soc/samsung/smartq_wm8987.c +++ b/sound/soc/samsung/smartq_wm8987.c @@ -13,20 +13,14 @@ * */ -#include <linux/module.h> -#include <linux/platform_device.h> #include <linux/gpio.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/jack.h> #include <asm/mach-types.h> -#include "dma.h" #include "i2s.h" - #include "../codecs/wm8750.h" /* diff --git a/sound/soc/samsung/smdk2443_wm9710.c b/sound/soc/samsung/smdk2443_wm9710.c index 3be7e7e92d6..3a0dbfc793f 100644 --- a/sound/soc/samsung/smdk2443_wm9710.c +++ b/sound/soc/samsung/smdk2443_wm9710.c @@ -12,15 +12,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card smdk2443; static struct snd_soc_dai_link smdk2443_dai[] = { diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c index b5c3fad01bb..e8ac961c6ba 100644 --- a/sound/soc/samsung/smdk_spdif.c +++ b/sound/soc/samsung/smdk_spdif.c @@ -10,15 +10,10 @@ * */ -#include <linux/module.h> -#include <linux/device.h> #include <linux/clk.h> -#include <plat/devs.h> - #include <sound/soc.h> -#include "dma.h" #include "spdif.h" /* Audio clock settings are belonged to board specific part. Every diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index b2cff1a44ae..8aacf23d6f3 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -10,17 +10,12 @@ * option) any later version. */ -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <asm/mach-types.h> #include "../codecs/wm8580.h" -#include "dma.h" #include "i2s.h" /* diff --git a/sound/soc/samsung/smdk_wm9713.c b/sound/soc/samsung/smdk_wm9713.c index ae5fed6f772..fffe3c1dd1b 100644 --- a/sound/soc/samsung/smdk_wm9713.c +++ b/sound/soc/samsung/smdk_wm9713.c @@ -11,13 +11,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card smdk; /* diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index f0816404ea3..28c491dacf7 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -13,9 +13,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> #include <mach/dma.h> diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index a14820ac966..d6f4703b3c0 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -18,18 +18,26 @@ struct fsi_ak4642_data { const char *cpu_dai; const char *codec; const char *platform; + int id; }; static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; int ret; - ret = snd_soc_dai_set_fmt(dai, SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(codec, SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_CBM_CFM); if (ret < 0) return ret; - ret = snd_soc_dai_set_sysclk(dai, 0, 11289600, 0); + ret = snd_soc_dai_set_sysclk(codec, 0, 11289600, 0); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_CBS_CFS); return ret; } @@ -60,7 +68,7 @@ static int fsi_ak4642_probe(struct platform_device *pdev) pdata = (struct fsi_ak4642_data *)id_entry->driver_data; - fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A); + fsi_snd_device = platform_device_alloc("soc-audio", pdata->id); if (!fsi_snd_device) goto out; @@ -93,6 +101,7 @@ static struct fsi_ak4642_data fsi_a_ak4642 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi.0", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi_b_ak4642 = { @@ -101,6 +110,7 @@ static struct fsi_ak4642_data fsi_b_ak4642 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi.0", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi_a_ak4643 = { @@ -109,6 +119,7 @@ static struct fsi_ak4642_data fsi_a_ak4643 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi.0", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi_b_ak4643 = { @@ -117,6 +128,7 @@ static struct fsi_ak4642_data fsi_b_ak4643 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi.0", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi2_a_ak4642 = { @@ -125,6 +137,7 @@ static struct fsi_ak4642_data fsi2_a_ak4642 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi2", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi2_b_ak4642 = { @@ -133,6 +146,7 @@ static struct fsi_ak4642_data fsi2_b_ak4642 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi2", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi2_a_ak4643 = { @@ -141,6 +155,7 @@ static struct fsi_ak4642_data fsi2_a_ak4643 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi2", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi2_b_ak4643 = { @@ -149,6 +164,7 @@ static struct fsi_ak4642_data fsi2_b_ak4643 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi2", + .id = FSI_PORT_B, }; static struct platform_device_id fsi_id_table[] = { diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c index e8df9da92f7..dbafd7ac559 100644 --- a/sound/soc/sh/fsi-da7210.c +++ b/sound/soc/sh/fsi-da7210.c @@ -15,11 +15,20 @@ static int fsi_da7210_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + int ret; - return snd_soc_dai_set_fmt(dai, + ret = snd_soc_dai_set_fmt(codec, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFS); + + return ret; } static struct snd_soc_dai_link fsi_da7210_dai = { diff --git a/sound/soc/sh/fsi-hdmi.c b/sound/soc/sh/fsi-hdmi.c index a52dd8ec71d..9719985eb82 100644 --- a/sound/soc/sh/fsi-hdmi.c +++ b/sound/soc/sh/fsi-hdmi.c @@ -12,31 +12,59 @@ #include <linux/platform_device.h> #include <sound/sh_fsi.h> +struct fsi_hdmi_data { + const char *cpu_dai; + const char *card; + int id; +}; + +static int fsi_hdmi_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBM_CFM); + + return ret; +} + static struct snd_soc_dai_link fsi_dai_link = { .name = "HDMI", .stream_name = "HDMI", - .cpu_dai_name = "fsib-dai", /* fsi B */ .codec_dai_name = "sh_mobile_hdmi-hifi", .platform_name = "sh_fsi2", .codec_name = "sh-mobile-hdmi", + .init = fsi_hdmi_dai_init, }; static struct snd_soc_card fsi_soc_card = { - .name = "FSI (SH MOBILE HDMI)", .dai_link = &fsi_dai_link, .num_links = 1, }; static struct platform_device *fsi_snd_device; -static int __init fsi_hdmi_init(void) +static int fsi_hdmi_probe(struct platform_device *pdev) { int ret = -ENOMEM; + const struct platform_device_id *id_entry; + struct fsi_hdmi_data *pdata; + + id_entry = pdev->id_entry; + if (!id_entry) { + dev_err(&pdev->dev, "unknown fsi hdmi\n"); + return -ENODEV; + } - fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B); + pdata = (struct fsi_hdmi_data *)id_entry->driver_data; + + fsi_snd_device = platform_device_alloc("soc-audio", pdata->id); if (!fsi_snd_device) goto out; + fsi_dai_link.cpu_dai_name = pdata->cpu_dai; + fsi_soc_card.name = pdata->card; + platform_set_drvdata(fsi_snd_device, &fsi_soc_card); ret = platform_device_add(fsi_snd_device); @@ -47,9 +75,48 @@ out: return ret; } -static void __exit fsi_hdmi_exit(void) +static int fsi_hdmi_remove(struct platform_device *pdev) { platform_device_unregister(fsi_snd_device); + return 0; +} + +static struct fsi_hdmi_data fsi2_a_hdmi = { + .cpu_dai = "fsia-dai", + .card = "FSI2A (SH MOBILE HDMI)", + .id = FSI_PORT_A, +}; + +static struct fsi_hdmi_data fsi2_b_hdmi = { + .cpu_dai = "fsib-dai", + .card = "FSI2B (SH MOBILE HDMI)", + .id = FSI_PORT_B, +}; + +static struct platform_device_id fsi_id_table[] = { + /* FSI 2 */ + { "sh_fsi2_a_hdmi", (kernel_ulong_t)&fsi2_a_hdmi }, + { "sh_fsi2_b_hdmi", (kernel_ulong_t)&fsi2_b_hdmi }, + {}, +}; + +static struct platform_driver fsi_hdmi = { + .driver = { + .name = "fsi-hdmi-audio", + }, + .probe = fsi_hdmi_probe, + .remove = fsi_hdmi_remove, + .id_table = fsi_id_table, +}; + +static int __init fsi_hdmi_init(void) +{ + return platform_driver_register(&fsi_hdmi); +} + +static void __exit fsi_hdmi_exit(void) +{ + platform_driver_unregister(&fsi_hdmi); } module_init(fsi_hdmi_init); diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 2b06402801e..0c9997e2d8c 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -78,6 +78,8 @@ /* CKG1 */ #define ACKMD_MASK 0x00007000 #define BPFMD_MASK 0x00000700 +#define DIMD (1 << 4) +#define DOMD (1 << 0) /* A/B MST_CTLR */ #define BP (1 << 4) /* Fix the signal of Biphase output */ @@ -111,6 +113,8 @@ #define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) +typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int enable); + /* * FSI driver use below type name for variable * @@ -128,7 +132,6 @@ struct fsi_stream { struct snd_pcm_substream *substream; int fifo_max_num; - int chan_num; int buff_offset; int buff_len; @@ -143,6 +146,7 @@ struct fsi_priv { void __iomem *base; struct fsi_master *master; + int chan_num; struct fsi_stream playback; struct fsi_stream capture; @@ -252,9 +256,8 @@ static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream) return rtd->cpu_dai; } -static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream) +static struct fsi_priv *fsi_get_priv_frm_dai(struct snd_soc_dai *dai) { - struct snd_soc_dai *dai = fsi_get_dai(substream); struct fsi_master *master = snd_soc_dai_get_drvdata(dai); if (dai->id == 0) @@ -263,11 +266,27 @@ static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream) return &master->fsib; } +static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream) +{ + return fsi_get_priv_frm_dai(fsi_get_dai(substream)); +} + +static set_rate_func fsi_get_info_set_rate(struct fsi_master *master) +{ + if (!master->info) + return NULL; + + return master->info->set_rate; +} + static u32 fsi_get_info_flags(struct fsi_priv *fsi) { int is_porta = fsi_is_port_a(fsi); struct fsi_master *master = fsi_get_master(fsi); + if (!master->info) + return 0; + return is_porta ? master->info->porta_flags : master->info->portb_flags; } @@ -288,21 +307,6 @@ static inline struct fsi_stream *fsi_get_stream(struct fsi_priv *fsi, return is_play ? &fsi->playback : &fsi->capture; } -static int fsi_is_master_mode(struct fsi_priv *fsi, int is_play) -{ - u32 mode; - u32 flags = fsi_get_info_flags(fsi); - - mode = is_play ? SH_FSI_OUT_SLAVE_MODE : SH_FSI_IN_SLAVE_MODE; - - /* return - * 1 : master mode - * 0 : slave mode - */ - - return (mode & flags) != mode; -} - static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play) { int is_porta = fsi_is_port_a(fsi); @@ -357,7 +361,6 @@ static void fsi_stream_pop(struct fsi_priv *fsi, int is_play) static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play) { u32 status; - struct fsi_stream *io = fsi_get_stream(fsi, is_play); int data_num; status = is_play ? @@ -365,7 +368,7 @@ static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play) fsi_reg_read(fsi, DIFF_ST); data_num = 0x1ff & (status >> 8); - data_num *= io->chan_num; + data_num *= fsi->chan_num; return data_num; } @@ -387,7 +390,7 @@ static int fsi_get_frame_width(struct fsi_priv *fsi, int is_play) struct snd_pcm_substream *substream = io->substream; struct snd_pcm_runtime *runtime = substream->runtime; - return frames_to_bytes(runtime, 1) / io->chan_num; + return frames_to_bytes(runtime, 1) / fsi->chan_num; } static void fsi_count_fifo_err(struct fsi_priv *fsi) @@ -580,10 +583,10 @@ static void fsi_fifo_init(struct fsi_priv *fsi, * 7 channels: 32 ( 32 x 7 = 224) * 8 channels: 32 ( 32 x 8 = 256) */ - for (i = 1; i < io->chan_num; i <<= 1) + for (i = 1; i < fsi->chan_num; i <<= 1) io->fifo_max_num >>= 1; dev_dbg(dai->dev, "%d channel %d store\n", - io->chan_num, io->fifo_max_num); + fsi->chan_num, io->fifo_max_num); /* * set interrupt generation factor @@ -659,7 +662,7 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream) * data_num_max : number of FSI fifo free space * data_num : number of ALSA residue data */ - data_num_max = io->fifo_max_num * io->chan_num; + data_num_max = io->fifo_max_num * fsi->chan_num; data_num_max -= fsi_get_fifo_data_num(fsi, is_play); data_num = data_residue_num; @@ -754,25 +757,12 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsi_priv *fsi = fsi_get_priv(substream); - struct fsi_master *master = fsi_get_master(fsi); - struct fsi_stream *io; u32 flags = fsi_get_info_flags(fsi); - u32 fmt; u32 data; int is_play = fsi_is_play(substream); - int is_master; - - io = fsi_get_stream(fsi, is_play); pm_runtime_get_sync(dai->dev); - /* CKG1 */ - data = is_play ? (1 << 0) : (1 << 4); - is_master = fsi_is_master_mode(fsi, is_play); - if (is_master) - fsi_reg_mask_set(fsi, CKG1, data, data); - else - fsi_reg_mask_set(fsi, CKG1, data, 0); /* clock inversion (CKG2) */ data = 0; @@ -787,54 +777,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, fsi_reg_write(fsi, CKG2, data); - /* do fmt, di fmt */ - data = 0; - fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags); - switch (fmt) { - case SH_FSI_FMT_MONO: - data = CR_MONO; - io->chan_num = 1; - break; - case SH_FSI_FMT_MONO_DELAY: - data = CR_MONO_D; - io->chan_num = 1; - break; - case SH_FSI_FMT_PCM: - data = CR_PCM; - io->chan_num = 2; - break; - case SH_FSI_FMT_I2S: - data = CR_I2S; - io->chan_num = 2; - break; - case SH_FSI_FMT_TDM: - io->chan_num = is_play ? - SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); - data = CR_TDM | (io->chan_num - 1); - break; - case SH_FSI_FMT_TDM_DELAY: - io->chan_num = is_play ? - SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); - data = CR_TDM_D | (io->chan_num - 1); - break; - case SH_FSI_FMT_SPDIF: - if (master->core->ver < 2) { - dev_err(dai->dev, "This FSI can not use SPDIF\n"); - return -EINVAL; - } - data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM; - io->chan_num = 2; - fsi_spdif_clk_ctrl(fsi, 1); - fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD); - break; - default: - dev_err(dai->dev, "unknown format.\n"); - return -EINVAL; - } - is_play ? - fsi_reg_write(fsi, DO_FMT, data) : - fsi_reg_write(fsi, DI_FMT, data); - /* irq clear */ fsi_irq_disable(fsi, is_play); fsi_irq_clear_status(fsi); @@ -851,12 +793,12 @@ static void fsi_dai_shutdown(struct snd_pcm_substream *substream, struct fsi_priv *fsi = fsi_get_priv(substream); int is_play = fsi_is_play(substream); struct fsi_master *master = fsi_get_master(fsi); - int (*set_rate)(struct device *dev, int is_porta, int rate, int enable); + set_rate_func set_rate; fsi_irq_disable(fsi, is_play); fsi_clk_ctrl(fsi, 0); - set_rate = master->info->set_rate; + set_rate = fsi_get_info_set_rate(master); if (set_rate && fsi->rate) set_rate(dai->dev, fsi_is_port_a(fsi), fsi->rate, 0); fsi->rate = 0; @@ -889,18 +831,100 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, return ret; } +static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt) +{ + u32 data = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + data = CR_I2S; + fsi->chan_num = 2; + break; + case SND_SOC_DAIFMT_LEFT_J: + data = CR_PCM; + fsi->chan_num = 2; + break; + default: + return -EINVAL; + } + + fsi_reg_write(fsi, DO_FMT, data); + fsi_reg_write(fsi, DI_FMT, data); + + return 0; +} + +static int fsi_set_fmt_spdif(struct fsi_priv *fsi) +{ + struct fsi_master *master = fsi_get_master(fsi); + u32 data = 0; + + if (master->core->ver < 2) + return -EINVAL; + + data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM; + fsi->chan_num = 2; + fsi_spdif_clk_ctrl(fsi, 1); + fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD); + + fsi_reg_write(fsi, DO_FMT, data); + fsi_reg_write(fsi, DI_FMT, data); + + return 0; +} + +static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsi_priv *fsi = fsi_get_priv_frm_dai(dai); + u32 flags = fsi_get_info_flags(fsi); + u32 data = 0; + int ret; + + pm_runtime_get_sync(dai->dev); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + data = DIMD | DOMD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + ret = -EINVAL; + goto set_fmt_exit; + } + fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data); + + /* set format */ + switch (flags & SH_FSI_FMT_MASK) { + case SH_FSI_FMT_DAI: + ret = fsi_set_fmt_dai(fsi, fmt & SND_SOC_DAIFMT_FORMAT_MASK); + break; + case SH_FSI_FMT_SPDIF: + ret = fsi_set_fmt_spdif(fsi); + break; + default: + ret = -EINVAL; + } + +set_fmt_exit: + pm_runtime_put_sync(dai->dev); + + return ret; +} + static int fsi_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct fsi_priv *fsi = fsi_get_priv(substream); struct fsi_master *master = fsi_get_master(fsi); - int (*set_rate)(struct device *dev, int is_porta, int rate, int enable); + set_rate_func set_rate; int fsi_ver = master->core->ver; long rate = params_rate(params); int ret; - set_rate = master->info->set_rate; + set_rate = fsi_get_info_set_rate(master); if (!set_rate) return 0; @@ -975,6 +999,7 @@ static struct snd_soc_dai_ops fsi_dai_ops = { .startup = fsi_dai_startup, .shutdown = fsi_dai_shutdown, .trigger = fsi_dai_trigger, + .set_fmt = fsi_dai_set_fmt, .hw_params = fsi_dai_hw_params, }; diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 8c2a21a978a..5d76da43b14 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -18,6 +18,8 @@ #include <linux/bitmap.h> #include <linux/rbtree.h> +#include <trace/events/asoc.h> + static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec, unsigned int reg) { @@ -25,7 +27,8 @@ static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec, unsigned int val; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -49,7 +52,8 @@ static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, data[1] = value & 0x00ff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -106,7 +110,8 @@ static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, unsigned int val; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -130,7 +135,8 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, data[1] = value & 0x00ff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -191,7 +197,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, data[1] = value & 0xff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -216,7 +223,8 @@ static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, reg &= 0xff; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -271,7 +279,8 @@ static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, data[2] = value & 0xff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -295,7 +304,8 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, unsigned int val; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -450,7 +460,8 @@ static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec, reg &= 0xff; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -476,7 +487,8 @@ static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, reg &= 0xff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -568,7 +580,8 @@ static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec, unsigned int val; if (reg >= codec->driver->reg_cache_size || - snd_soc_codec_volatile_register(codec, reg)) { + snd_soc_codec_volatile_register(codec, reg) || + codec->cache_bypass) { if (codec->cache_only) return -1; @@ -595,7 +608,8 @@ static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, data[3] = value & 0xff; if (!snd_soc_codec_volatile_register(codec, reg) && - reg < codec->driver->reg_cache_size) { + reg < codec->driver->reg_cache_size && + !codec->cache_bypass) { ret = snd_soc_cache_write(codec, reg, value); if (ret < 0) return -1; @@ -761,6 +775,49 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); +static bool snd_soc_set_cache_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size) +{ + switch (word_size) { + case 1: { + u8 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + case 2: { + u16 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + default: + BUG(); + } + return false; +} + +static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, + unsigned int word_size) +{ + switch (word_size) { + case 1: { + const u8 *cache = base; + return cache[idx]; + } + case 2: { + const u16 *cache = base; + return cache[idx]; + } + default: + BUG(); + } + /* unreachable */ + return -1; +} + struct snd_soc_rbtree_node { struct rb_node node; unsigned int reg; @@ -835,7 +892,9 @@ static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec) ret = snd_soc_cache_read(codec, rbnode->reg, &val); if (ret) return ret; + codec->cache_bypass = 1; ret = snd_soc_write(codec, rbnode->reg, val); + codec->cache_bypass = 0; if (ret) return ret; dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", @@ -924,7 +983,12 @@ static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec) static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) { + struct snd_soc_rbtree_node *rbtree_node; struct snd_soc_rbtree_ctx *rbtree_ctx; + unsigned int val; + unsigned int word_size; + int i; + int ret; codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); if (!codec->reg_cache) @@ -936,53 +1000,25 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) if (!codec->reg_def_copy) return 0; -/* - * populate the rbtree with the initialized registers. All other - * registers will be inserted into the tree when they are first written. - * - * The reasoning behind this, is that we need to step through and - * dereference the cache in u8/u16 increments without sacrificing - * portability. This could also be done using memcpy() but that would - * be slightly more cryptic. - */ -#define snd_soc_rbtree_populate(cache) \ -({ \ - int ret, i; \ - struct snd_soc_rbtree_node *rbtree_node; \ - \ - ret = 0; \ - cache = codec->reg_def_copy; \ - for (i = 0; i < codec->driver->reg_cache_size; ++i) { \ - if (!cache[i]) \ - continue; \ - rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); \ - if (!rbtree_node) { \ - ret = -ENOMEM; \ - snd_soc_cache_exit(codec); \ - break; \ - } \ - rbtree_node->reg = i; \ - rbtree_node->value = cache[i]; \ - rbtree_node->defval = cache[i]; \ - snd_soc_rbtree_insert(&rbtree_ctx->root, \ - rbtree_node); \ - } \ - ret; \ -}) - - switch (codec->driver->reg_word_size) { - case 1: { - const u8 *cache; - - return snd_soc_rbtree_populate(cache); - } - case 2: { - const u16 *cache; - - return snd_soc_rbtree_populate(cache); - } - default: - BUG(); + /* + * populate the rbtree with the initialized registers. All other + * registers will be inserted when they are first modified. + */ + word_size = codec->driver->reg_word_size; + for (i = 0; i < codec->driver->reg_cache_size; ++i) { + val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size); + if (!val) + continue; + rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); + if (!rbtree_node) { + ret = -ENOMEM; + snd_soc_cache_exit(codec); + break; + } + rbtree_node->reg = i; + rbtree_node->value = val; + rbtree_node->defval = val; + snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node); } return 0; @@ -1080,34 +1116,28 @@ static inline int snd_soc_lzo_get_blkindex(struct snd_soc_codec *codec, unsigned int reg) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; return (reg * codec_drv->reg_word_size) / - DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()); + DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); } static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec, unsigned int reg) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - return reg % (DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()) / + return reg % (DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()) / codec_drv->reg_word_size); } static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - return DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()); + return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); } static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec) @@ -1122,7 +1152,9 @@ static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec) ret = snd_soc_cache_read(codec, i, &val); if (ret) return ret; + codec->cache_bypass = 1; ret = snd_soc_write(codec, i, val); + codec->cache_bypass = 0; if (ret) return ret; dev_dbg(codec->dev, "Synced register %#x, value = %#x\n", @@ -1165,29 +1197,10 @@ static int snd_soc_lzo_cache_write(struct snd_soc_codec *codec, } /* write the new value to the cache */ - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - cache = lzo_block->dst; - if (cache[blkpos] == value) { - kfree(lzo_block->dst); - goto out; - } - cache[blkpos] = value; - } - break; - case 2: { - u16 *cache; - cache = lzo_block->dst; - if (cache[blkpos] == value) { - kfree(lzo_block->dst); - goto out; - } - cache[blkpos] = value; - } - break; - default: - BUG(); + if (snd_soc_set_cache_val(lzo_block->dst, blkpos, value, + codec->driver->reg_word_size)) { + kfree(lzo_block->dst); + goto out; } /* prepare the source to be the decompressed block */ @@ -1241,25 +1254,10 @@ static int snd_soc_lzo_cache_read(struct snd_soc_codec *codec, /* decompress the block */ ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block); - if (ret >= 0) { + if (ret >= 0) /* fetch the value from the cache */ - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - cache = lzo_block->dst; - *value = cache[blkpos]; - } - break; - case 2: { - u16 *cache; - cache = lzo_block->dst; - *value = cache[blkpos]; - } - break; - default: - BUG(); - } - } + *value = snd_soc_get_cache_val(lzo_block->dst, blkpos, + codec->driver->reg_word_size); kfree(lzo_block->dst); /* restore the pointer and length of the compressed block */ @@ -1301,7 +1299,7 @@ static int snd_soc_lzo_cache_exit(struct snd_soc_codec *codec) static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) { struct snd_soc_lzo_ctx **lzo_blocks; - size_t reg_size, bmp_size; + size_t bmp_size; const struct snd_soc_codec_driver *codec_drv; int ret, tofree, i, blksize, blkcount; const char *p, *end; @@ -1309,7 +1307,6 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) ret = 0; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; /* * If we have not been given a default register cache @@ -1321,8 +1318,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) tofree = 1; if (!codec->reg_def_copy) { - codec->reg_def_copy = kzalloc(reg_size, - GFP_KERNEL); + codec->reg_def_copy = kzalloc(codec->reg_size, GFP_KERNEL); if (!codec->reg_def_copy) return -ENOMEM; } @@ -1370,7 +1366,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) blksize = snd_soc_lzo_get_blksize(codec); p = codec->reg_def_copy; - end = codec->reg_def_copy + reg_size; + end = codec->reg_def_copy + codec->reg_size; /* compress the register map and fill the lzo blocks */ for (i = 0; i < blkcount; ++i, p += blksize) { lzo_blocks[i]->src = p; @@ -1414,28 +1410,10 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) ret = snd_soc_cache_read(codec, i, &val); if (ret) return ret; - if (codec_drv->reg_cache_default) { - switch (codec_drv->reg_word_size) { - case 1: { - const u8 *cache; - - cache = codec_drv->reg_cache_default; - if (cache[i] == val) - continue; - } - break; - case 2: { - const u16 *cache; - - cache = codec_drv->reg_cache_default; - if (cache[i] == val) - continue; - } - break; - default: - BUG(); - } - } + if (codec->reg_def_copy) + if (snd_soc_get_cache_val(codec->reg_def_copy, + i, codec_drv->reg_word_size) == val) + continue; ret = snd_soc_write(codec, i, val); if (ret) return ret; @@ -1448,50 +1426,16 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) static int snd_soc_flat_cache_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - - cache = codec->reg_cache; - cache[reg] = value; - } - break; - case 2: { - u16 *cache; - - cache = codec->reg_cache; - cache[reg] = value; - } - break; - default: - BUG(); - } - + snd_soc_set_cache_val(codec->reg_cache, reg, value, + codec->driver->reg_word_size); return 0; } static int snd_soc_flat_cache_read(struct snd_soc_codec *codec, unsigned int reg, unsigned int *value) { - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - - cache = codec->reg_cache; - *value = cache[reg]; - } - break; - case 2: { - u16 *cache; - - cache = codec->reg_cache; - *value = cache[reg]; - } - break; - default: - BUG(); - } - + *value = snd_soc_get_cache_val(codec->reg_cache, reg, + codec->driver->reg_word_size); return 0; } @@ -1507,24 +1451,14 @@ static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec) static int snd_soc_flat_cache_init(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - /* - * for flat compression, we don't need to keep a copy of the - * original defaults register cache as it will definitely not - * be marked as __devinitconst - */ - kfree(codec->reg_def_copy); - codec->reg_def_copy = NULL; - - if (codec_drv->reg_cache_default) - codec->reg_cache = kmemdup(codec_drv->reg_cache_default, - reg_size, GFP_KERNEL); + if (codec->reg_def_copy) + codec->reg_cache = kmemdup(codec->reg_def_copy, + codec->reg_size, GFP_KERNEL); else - codec->reg_cache = kzalloc(reg_size, GFP_KERNEL); + codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL); if (!codec->reg_cache) return -ENOMEM; @@ -1669,21 +1603,77 @@ EXPORT_SYMBOL_GPL(snd_soc_cache_write); int snd_soc_cache_sync(struct snd_soc_codec *codec) { int ret; + const char *name; if (!codec->cache_sync) { return 0; } - if (codec->cache_ops && codec->cache_ops->sync) { - if (codec->cache_ops->name) - dev_dbg(codec->dev, "Syncing %s cache for %s codec\n", - codec->cache_ops->name, codec->name); - ret = codec->cache_ops->sync(codec); - if (!ret) - codec->cache_sync = 0; - return ret; - } + if (!codec->cache_ops || !codec->cache_ops->sync) + return -EINVAL; - return -EINVAL; + if (codec->cache_ops->name) + name = codec->cache_ops->name; + else + name = "unknown"; + + if (codec->cache_ops->name) + dev_dbg(codec->dev, "Syncing %s cache for %s codec\n", + codec->cache_ops->name, codec->name); + trace_snd_soc_cache_sync(codec, name, "start"); + ret = codec->cache_ops->sync(codec); + if (!ret) + codec->cache_sync = 0; + trace_snd_soc_cache_sync(codec, name, "end"); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_cache_sync); + +static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec, + unsigned int reg) +{ + const struct snd_soc_codec_driver *codec_drv; + unsigned int min, max, index; + + codec_drv = codec->driver; + min = 0; + max = codec_drv->reg_access_size - 1; + do { + index = (min + max) / 2; + if (codec_drv->reg_access_default[index].reg == reg) + return index; + if (codec_drv->reg_access_default[index].reg < reg) + min = index + 1; + else + max = index; + } while (min <= max); + return -1; +} + +int snd_soc_default_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + int index; + + if (reg >= codec->driver->reg_cache_size) + return 1; + index = snd_soc_get_reg_access_index(codec, reg); + if (index < 0) + return 0; + return codec->driver->reg_access_default[index].vol; +} +EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register); + +int snd_soc_default_readable_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + int index; + + if (reg >= codec->driver->reg_cache_size) + return 1; + index = snd_soc_get_reg_access_index(codec, reg); + if (index < 0) + return 0; + return codec->driver->reg_access_default[index].read; +} +EXPORT_SYMBOL_GPL(snd_soc_default_readable_register); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c3f6f1e7279..17efacdb248 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -48,7 +48,8 @@ static DEFINE_MUTEX(pcm_mutex); static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq); #ifdef CONFIG_DEBUG_FS -static struct dentry *debugfs_root; +struct dentry *snd_soc_debugfs_root; +EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); #endif static DEFINE_MUTEX(client_mutex); @@ -57,8 +58,6 @@ static LIST_HEAD(dai_list); static LIST_HEAD(platform_list); static LIST_HEAD(codec_list); -static int snd_soc_register_card(struct snd_soc_card *card); -static int snd_soc_unregister_card(struct snd_soc_card *card); static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num); /* @@ -70,10 +69,73 @@ static int pmdown_time = 5000; module_param(pmdown_time, int, 0); MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)"); +/* returns the minimum number of bytes needed to represent + * a particular given value */ +static int min_bytes_needed(unsigned long val) +{ + int c = 0; + int i; + + for (i = (sizeof val * 8) - 1; i >= 0; --i, ++c) + if (val & (1UL << i)) + break; + c = (sizeof val * 8) - c; + if (!c || (c % 8)) + c = (c + 8) / 8; + else + c /= 8; + return c; +} + +/* fill buf which is 'len' bytes with a formatted + * string of the form 'reg: value\n' */ +static int format_register_str(struct snd_soc_codec *codec, + unsigned int reg, char *buf, size_t len) +{ + int wordsize = codec->driver->reg_word_size * 2; + int regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2; + int ret; + char tmpbuf[len + 1]; + char regbuf[regsize + 1]; + + /* since tmpbuf is allocated on the stack, warn the callers if they + * try to abuse this function */ + WARN_ON(len > 63); + + /* +2 for ': ' and + 1 for '\n' */ + if (wordsize + regsize + 2 + 1 != len) + return -EINVAL; + + ret = snd_soc_read(codec , reg); + if (ret < 0) { + memset(regbuf, 'X', regsize); + regbuf[regsize] = '\0'; + } else { + snprintf(regbuf, regsize + 1, "%.*x", regsize, ret); + } + + /* prepare the buffer */ + snprintf(tmpbuf, len + 1, "%.*x: %s\n", wordsize, reg, regbuf); + /* copy it back to the caller without the '\0' */ + memcpy(buf, tmpbuf, len); + + return 0; +} + /* codec register dump */ -static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) +static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf, + size_t count, loff_t pos) { - int ret, i, step = 1, count = 0; + int i, step = 1; + int wordsize, regsize; + int len; + size_t total = 0; + loff_t p = 0; + + wordsize = codec->driver->reg_word_size * 2; + regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2; + + len = wordsize + regsize + 2 + 1; if (!codec->driver->reg_cache_size) return 0; @@ -81,55 +143,37 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) if (codec->driver->reg_cache_step) step = codec->driver->reg_cache_step; - count += sprintf(buf, "%s registers\n", codec->name); for (i = 0; i < codec->driver->reg_cache_size; i += step) { - if (codec->driver->readable_register && !codec->driver->readable_register(i)) + if (codec->readable_register && !codec->readable_register(codec, i)) continue; - - count += sprintf(buf + count, "%2x: ", i); - if (count >= PAGE_SIZE - 1) - break; - if (codec->driver->display_register) { count += codec->driver->display_register(codec, buf + count, PAGE_SIZE - count, i); } else { - /* If the read fails it's almost certainly due to - * the register being volatile and the device being - * powered off. - */ - ret = snd_soc_read(codec, i); - if (ret >= 0) - count += snprintf(buf + count, - PAGE_SIZE - count, - "%4x", ret); - else - count += snprintf(buf + count, - PAGE_SIZE - count, - "<no data: %d>", ret); + /* only support larger than PAGE_SIZE bytes debugfs + * entries for the default case */ + if (p >= pos) { + if (total + len >= count - 1) + break; + format_register_str(codec, i, buf + total, len); + total += len; + } + p += len; } - - if (count >= PAGE_SIZE - 1) - break; - - count += snprintf(buf + count, PAGE_SIZE - count, "\n"); - if (count >= PAGE_SIZE - 1) - break; } - /* Truncate count; min() would cause a warning */ - if (count >= PAGE_SIZE) - count = PAGE_SIZE - 1; + total = min(total, count - 1); - return count; + return total; } + static ssize_t codec_reg_show(struct device *dev, struct device_attribute *attr, char *buf) { struct snd_soc_pcm_runtime *rtd = container_of(dev, struct snd_soc_pcm_runtime, dev); - return soc_codec_reg_show(rtd->codec, buf); + return soc_codec_reg_show(rtd->codec, buf, PAGE_SIZE, 0); } static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL); @@ -168,16 +212,28 @@ static int codec_reg_open_file(struct inode *inode, struct file *file) } static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) + size_t count, loff_t *ppos) { ssize_t ret; struct snd_soc_codec *codec = file->private_data; - char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + char *buf; + + if (*ppos < 0 || !count) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); if (!buf) return -ENOMEM; - ret = soc_codec_reg_show(codec, buf); - if (ret >= 0) - ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + ret = soc_codec_reg_show(codec, buf, count, *ppos); + if (ret >= 0) { + if (copy_to_user(user_buf, buf, ret)) { + kfree(buf); + return -EFAULT; + } + *ppos += ret; + } + kfree(buf); return ret; } @@ -209,6 +265,10 @@ static ssize_t codec_reg_write_file(struct file *file, start++; if (strict_strtoul(start, 16, &value)) return -EINVAL; + + /* Userspace has been fiddling around behind the kernel's back */ + add_taint(TAINT_USER); + snd_soc_write(codec, reg, value); return buf_size; } @@ -232,6 +292,11 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) return; } + debugfs_create_bool("cache_sync", 0444, codec->debugfs_codec_root, + &codec->cache_sync); + debugfs_create_bool("cache_only", 0444, codec->debugfs_codec_root, + &codec->cache_only); + codec->debugfs_reg = debugfs_create_file("codec_reg", 0644, codec->debugfs_codec_root, codec, &codec_reg_fops); @@ -356,7 +421,7 @@ static const struct file_operations platform_list_fops = { static void soc_init_card_debugfs(struct snd_soc_card *card) { card->debugfs_card_root = debugfs_create_dir(card->name, - debugfs_root); + snd_soc_debugfs_root); if (!card->debugfs_card_root) { dev_warn(card->dev, "ASoC: Failed to create codec debugfs directory\n"); @@ -435,20 +500,30 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret; - if (codec_dai->driver->symmetric_rates || cpu_dai->driver->symmetric_rates || - rtd->dai_link->symmetric_rates) { - dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", - rtd->rate); + if (!codec_dai->driver->symmetric_rates && + !cpu_dai->driver->symmetric_rates && + !rtd->dai_link->symmetric_rates) + return 0; - ret = snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - rtd->rate, - rtd->rate); - if (ret < 0) { - dev_err(&rtd->dev, - "Unable to apply rate symmetry constraint: %d\n", ret); - return ret; - } + /* This can happen if multiple streams are starting simultaneously - + * the second can need to get its constraints before the first has + * picked a rate. Complain and allow the application to carry on. + */ + if (!rtd->rate) { + dev_warn(&rtd->dev, + "Not enforcing symmetric_rates due to race\n"); + return 0; + } + + dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate); + + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + rtd->rate, rtd->rate); + if (ret < 0) { + dev_err(&rtd->dev, + "Unable to apply rate symmetry constraint: %d\n", ret); + return ret; } return 0; @@ -962,12 +1037,11 @@ static struct snd_pcm_ops soc_pcm_ops = { .pointer = soc_pcm_pointer, }; -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP /* powers down audio subsystem for suspend */ -static int soc_suspend(struct device *dev) +int snd_soc_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_card *card = dev_get_drvdata(dev); struct snd_soc_codec *codec; int i; @@ -1008,7 +1082,7 @@ static int soc_suspend(struct device *dev) } if (card->suspend_pre) - card->suspend_pre(pdev, PMSG_SUSPEND); + card->suspend_pre(card); for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; @@ -1075,10 +1149,11 @@ static int soc_suspend(struct device *dev) } if (card->suspend_post) - card->suspend_post(pdev, PMSG_SUSPEND); + card->suspend_post(card); return 0; } +EXPORT_SYMBOL_GPL(snd_soc_suspend); /* deferred resume work, so resume can complete before we finished * setting our codec back up, which can be very slow on I2C @@ -1087,7 +1162,6 @@ static void soc_resume_deferred(struct work_struct *work) { struct snd_soc_card *card = container_of(work, struct snd_soc_card, deferred_resume_work); - struct platform_device *pdev = to_platform_device(card->dev); struct snd_soc_codec *codec; int i; @@ -1101,7 +1175,7 @@ static void soc_resume_deferred(struct work_struct *work) snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); if (card->resume_pre) - card->resume_pre(pdev); + card->resume_pre(card); /* resume AC97 DAIs */ for (i = 0; i < card->num_rtd; i++) { @@ -1176,7 +1250,7 @@ static void soc_resume_deferred(struct work_struct *work) } if (card->resume_post) - card->resume_post(pdev); + card->resume_post(card); dev_dbg(card->dev, "resume work completed\n"); @@ -1185,10 +1259,9 @@ static void soc_resume_deferred(struct work_struct *work) } /* powers up audio subsystem after a suspend */ -static int soc_resume(struct device *dev) +int snd_soc_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_card *card = dev_get_drvdata(dev); int i; /* AC97 devices might have other drivers hanging off them so @@ -1210,9 +1283,10 @@ static int soc_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_resume); #else -#define soc_suspend NULL -#define soc_resume NULL +#define snd_soc_suspend NULL +#define snd_soc_resume NULL #endif static struct snd_soc_dai_ops null_dai_ops = { @@ -1400,31 +1474,44 @@ static int soc_probe_codec(struct snd_soc_card *card, struct snd_soc_codec *codec) { int ret = 0; + const struct snd_soc_codec_driver *driver = codec->driver; codec->card = card; codec->dapm.card = card; soc_set_name_prefix(card, codec); - if (codec->driver->probe) { - ret = codec->driver->probe(codec); + if (!try_module_get(codec->dev->driver->owner)) + return -ENODEV; + + if (driver->probe) { + ret = driver->probe(codec); if (ret < 0) { dev_err(codec->dev, "asoc: failed to probe CODEC %s: %d\n", codec->name, ret); - return ret; + goto err_probe; } } + if (driver->dapm_widgets) + snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets, + driver->num_dapm_widgets); + if (driver->dapm_routes) + snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes, + driver->num_dapm_routes); + soc_init_codec_debugfs(codec); /* mark codec as probed and add to card codec list */ - if (!try_module_get(codec->dev->driver->owner)) - return -ENODEV; - codec->probed = 1; list_add(&codec->card_list, &card->codec_dev_list); list_add(&codec->dapm.list, &card->dapm_list); + return 0; + +err_probe: + module_put(codec->dev->driver->owner); + return ret; } @@ -1468,7 +1555,6 @@ static int soc_post_component_init(struct snd_soc_card *card, /* Make sure all DAPM widgets are instantiated */ snd_soc_dapm_new_widgets(&codec->dapm); - snd_soc_dapm_sync(&codec->dapm); /* register the rtd device */ rtd->codec = codec; @@ -1543,19 +1629,19 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num) /* probe the platform */ if (!platform->probed) { + if (!try_module_get(platform->dev->driver->owner)) + return -ENODEV; + if (platform->driver->probe) { ret = platform->driver->probe(platform); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe platform %s\n", platform->name); + module_put(platform->dev->driver->owner); return ret; } } /* mark platform as probed and add to card platform list */ - - if (!try_module_get(platform->dev->driver->owner)) - return -ENODEV; - platform->probed = 1; list_add(&platform->card_list, &card->platform_dev_list); } @@ -1713,7 +1799,6 @@ static int snd_soc_init_codec_cache(struct snd_soc_codec *codec, static void snd_soc_instantiate_card(struct snd_soc_card *card) { - struct platform_device *pdev = to_platform_device(card->dev); struct snd_soc_codec *codec; struct snd_soc_codec_conf *codec_conf; enum snd_soc_compress_type compress_type; @@ -1740,6 +1825,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) list_for_each_entry(codec, &codec_list, list) { if (codec->cache_init) continue; + /* by default we don't override the compress_type */ + compress_type = 0; /* check to see if we need to override the compress_type */ for (i = 0; i < card->num_configs; ++i) { codec_conf = &card->codec_conf[i]; @@ -1750,18 +1837,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) break; } } - if (i == card->num_configs) { - /* no need to override the compress_type so - * go ahead and do the standard thing */ - ret = snd_soc_init_codec_cache(codec, 0); - if (ret < 0) { - mutex_unlock(&card->mutex); - return; - } - continue; - } - /* override the compress_type with the one supplied in - * the machine driver */ ret = snd_soc_init_codec_cache(codec, compress_type); if (ret < 0) { mutex_unlock(&card->mutex); @@ -1780,14 +1855,19 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) } card->snd_card->dev = card->dev; -#ifdef CONFIG_PM + card->dapm.bias_level = SND_SOC_BIAS_OFF; + card->dapm.dev = card->dev; + card->dapm.card = card; + list_add(&card->dapm.list, &card->dapm_list); + +#ifdef CONFIG_PM_SLEEP /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); #endif /* initialise the sound card only once */ if (card->probe) { - ret = card->probe(pdev); + ret = card->probe(card); if (ret < 0) goto card_probe_error; } @@ -1810,11 +1890,37 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) } } + if (card->dapm_widgets) + snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, + card->num_dapm_widgets); + if (card->dapm_routes) + snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, + card->num_dapm_routes); + +#ifdef CONFIG_DEBUG_FS + card->dapm.debugfs_dapm = debugfs_create_dir("dapm", + card->debugfs_card_root); + if (!card->dapm.debugfs_dapm) + printk(KERN_WARNING + "Failed to create card DAPM debugfs directory\n"); + + snd_soc_dapm_debugfs_init(&card->dapm); +#endif + snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), "%s", card->name); snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), "%s", card->name); + if (card->late_probe) { + ret = card->late_probe(card); + if (ret < 0) { + dev_err(card->dev, "%s late_probe() failed: %d\n", + card->name, ret); + goto probe_aux_dev_err; + } + } + ret = snd_card_register(card->snd_card); if (ret < 0) { printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name); @@ -1848,7 +1954,7 @@ probe_dai_err: card_probe_error: if (card->remove) - card->remove(pdev); + card->remove(card); snd_card_free(card->snd_card); @@ -1872,16 +1978,15 @@ static int soc_probe(struct platform_device *pdev) struct snd_soc_card *card = platform_get_drvdata(pdev); int ret = 0; + /* + * no card, so machine driver should be registering card + * we should not be here in that case so ret error + */ + if (!card) + return -EINVAL; + /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; - INIT_LIST_HEAD(&card->dai_dev_list); - INIT_LIST_HEAD(&card->codec_dev_list); - INIT_LIST_HEAD(&card->platform_dev_list); - INIT_LIST_HEAD(&card->widgets); - INIT_LIST_HEAD(&card->paths); - INIT_LIST_HEAD(&card->dapm_list); - - soc_init_card_debugfs(card); ret = snd_soc_register_card(card); if (ret != 0) { @@ -1892,45 +1997,48 @@ static int soc_probe(struct platform_device *pdev) return 0; } -/* removes a socdev */ -static int soc_remove(struct platform_device *pdev) +static int soc_cleanup_card_resources(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(pdev); int i; - if (card->instantiated) { + /* make sure any delayed work runs */ + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + flush_delayed_work_sync(&rtd->delayed_work); + } - /* make sure any delayed work runs */ - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; - flush_delayed_work_sync(&rtd->delayed_work); - } + /* remove auxiliary devices */ + for (i = 0; i < card->num_aux_devs; i++) + soc_remove_aux_dev(card, i); - /* remove auxiliary devices */ - for (i = 0; i < card->num_aux_devs; i++) - soc_remove_aux_dev(card, i); + /* remove and free each DAI */ + for (i = 0; i < card->num_rtd; i++) + soc_remove_dai_link(card, i); - /* remove and free each DAI */ - for (i = 0; i < card->num_rtd; i++) - soc_remove_dai_link(card, i); + soc_cleanup_card_debugfs(card); - soc_cleanup_card_debugfs(card); + /* remove the card */ + if (card->remove) + card->remove(card); + + kfree(card->rtd); + snd_card_free(card->snd_card); + return 0; - /* remove the card */ - if (card->remove) - card->remove(pdev); +} + +/* removes a socdev */ +static int soc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); - kfree(card->rtd); - snd_card_free(card->snd_card); - } snd_soc_unregister_card(card); return 0; } -static int soc_poweroff(struct device *dev) +int snd_soc_poweroff(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_card *card = dev_get_drvdata(dev); int i; if (!card->instantiated) @@ -1947,11 +2055,12 @@ static int soc_poweroff(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_poweroff); -static const struct dev_pm_ops soc_pm_ops = { - .suspend = soc_suspend, - .resume = soc_resume, - .poweroff = soc_poweroff, +const struct dev_pm_ops snd_soc_pm_ops = { + .suspend = snd_soc_suspend, + .resume = snd_soc_resume, + .poweroff = snd_soc_poweroff, }; /* ASoC platform driver */ @@ -1959,7 +2068,7 @@ static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, - .pm = &soc_pm_ops, + .pm = &snd_soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, @@ -2029,10 +2138,11 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) * * Boolean function indiciating if a CODEC register is volatile. */ -int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg) +int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) { - if (codec->driver->volatile_register) - return codec->driver->volatile_register(reg); + if (codec->volatile_register) + return codec->volatile_register(codec, reg); else return 0; } @@ -2129,19 +2239,27 @@ EXPORT_SYMBOL_GPL(snd_soc_write); * * Writes new register value. * - * Returns 1 for change else 0. + * Returns 1 for change, 0 for no change, or negative error code. */ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, unsigned int mask, unsigned int value) { int change; unsigned int old, new; + int ret; - old = snd_soc_read(codec, reg); + ret = snd_soc_read(codec, reg); + if (ret < 0) + return ret; + + old = ret; new = (old & ~mask) | value; change = old != new; - if (change) - snd_soc_write(codec, reg, new); + if (change) { + ret = snd_soc_write(codec, reg, new); + if (ret < 0) + return ret; + } return change; } @@ -2226,22 +2344,45 @@ EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); * @_template: control template * @data: control private data * @long_name: control long name + * @prefix: control name prefix * * Create a new mixer control from a template control. * * Returns 0 for success, else error. */ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, - void *data, char *long_name) + void *data, char *long_name, + const char *prefix) { struct snd_kcontrol_new template; + struct snd_kcontrol *kcontrol; + char *name = NULL; + int name_len; memcpy(&template, _template, sizeof(template)); - if (long_name) - template.name = long_name; template.index = 0; - return snd_ctl_new1(&template, data); + if (!long_name) + long_name = template.name; + + if (prefix) { + name_len = strlen(long_name) + strlen(prefix) + 2; + name = kmalloc(name_len, GFP_ATOMIC); + if (!name) + return NULL; + + snprintf(name, name_len, "%s %s", prefix, long_name); + + template.name = name; + } else { + template.name = long_name; + } + + kcontrol = snd_ctl_new1(&template, data); + + kfree(name); + + return kcontrol; } EXPORT_SYMBOL_GPL(snd_soc_cnew); @@ -2260,22 +2401,16 @@ int snd_soc_add_controls(struct snd_soc_codec *codec, const struct snd_kcontrol_new *controls, int num_controls) { struct snd_card *card = codec->card->snd_card; - char prefixed_name[44], *name; int err, i; for (i = 0; i < num_controls; i++) { const struct snd_kcontrol_new *control = &controls[i]; - if (codec->name_prefix) { - snprintf(prefixed_name, sizeof(prefixed_name), "%s %s", - codec->name_prefix, control->name); - name = prefixed_name; - } else { - name = control->name; - } - err = snd_ctl_add(card, snd_soc_cnew(control, codec, name)); + err = snd_ctl_add(card, snd_soc_cnew(control, codec, + control->name, + codec->name_prefix)); if (err < 0) { dev_err(codec->dev, "%s: Failed to add %s: %d\n", - codec->name, name, err); + codec->name, control->name, err); return err; } } @@ -2956,12 +3091,34 @@ int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, { if (dai->driver && dai->driver->ops->set_sysclk) return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); + else if (dai->codec && dai->codec->driver->set_sysclk) + return dai->codec->driver->set_sysclk(dai->codec, clk_id, + freq, dir); else return -EINVAL; } EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); /** + * snd_soc_codec_set_sysclk - configure CODEC system or master clock. + * @codec: CODEC + * @clk_id: DAI specific clock ID + * @freq: new clock frequency in Hz + * @dir: new clock direction - input/output. + * + * Configures the CODEC master (MCLK) or system (SYSCLK) clocking. + */ +int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id, + unsigned int freq, int dir) +{ + if (codec->driver->set_sysclk) + return codec->driver->set_sysclk(codec, clk_id, freq, dir); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_sysclk); + +/** * snd_soc_dai_set_clkdiv - configure DAI clock dividers. * @dai: DAI * @div_id: DAI specific clock divider ID @@ -2997,11 +3154,35 @@ int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, if (dai->driver && dai->driver->ops->set_pll) return dai->driver->ops->set_pll(dai, pll_id, source, freq_in, freq_out); + else if (dai->codec && dai->codec->driver->set_pll) + return dai->codec->driver->set_pll(dai->codec, pll_id, source, + freq_in, freq_out); else return -EINVAL; } EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); +/* + * snd_soc_codec_set_pll - configure codec PLL. + * @codec: CODEC + * @pll_id: DAI specific PLL ID + * @source: DAI specific source for the PLL + * @freq_in: PLL input clock frequency in Hz + * @freq_out: requested PLL output clock frequency in Hz + * + * Configures and enables PLL to generate output clock based on input clock. + */ +int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + if (codec->driver->set_pll) + return codec->driver->set_pll(codec, pll_id, source, + freq_in, freq_out); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_pll); + /** * snd_soc_dai_set_fmt - configure DAI hardware audio format. * @dai: DAI @@ -3101,17 +3282,18 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); * * @card: Card to register * - * Note that currently this is an internal only function: it will be - * exposed to machine drivers after further backporting of ASoC v2 - * registration APIs. */ -static int snd_soc_register_card(struct snd_soc_card *card) +int snd_soc_register_card(struct snd_soc_card *card) { int i; if (!card->name || !card->dev) return -EINVAL; + snd_soc_initialize_card_lists(card); + + soc_init_card_debugfs(card); + card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), GFP_KERNEL); @@ -3135,18 +3317,18 @@ static int snd_soc_register_card(struct snd_soc_card *card) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_register_card); /** * snd_soc_unregister_card - Unregister a card with the ASoC core * * @card: Card to unregister * - * Note that currently this is an internal only function: it will be - * exposed to machine drivers after further backporting of ASoC v2 - * registration APIs. */ -static int snd_soc_unregister_card(struct snd_soc_card *card) +int snd_soc_unregister_card(struct snd_soc_card *card) { + if (card->instantiated) + soc_cleanup_card_resources(card); mutex_lock(&client_mutex); list_del(&card->list); mutex_unlock(&client_mutex); @@ -3154,6 +3336,7 @@ static int snd_soc_unregister_card(struct snd_soc_card *card) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_unregister_card); /* * Simplify DAI link configuration by removing ".-1" from device names @@ -3483,9 +3666,12 @@ int snd_soc_register_codec(struct device *dev, codec->write = codec_drv->write; codec->read = codec_drv->read; + codec->volatile_register = codec_drv->volatile_register; + codec->readable_register = codec_drv->readable_register; codec->dapm.bias_level = SND_SOC_BIAS_OFF; codec->dapm.dev = dev; codec->dapm.codec = codec; + codec->dapm.seq_notifier = codec_drv->seq_notifier; codec->dev = dev; codec->driver = codec_drv; codec->num_dai = num_dai; @@ -3494,20 +3680,30 @@ int snd_soc_register_codec(struct device *dev, /* allocate CODEC register cache */ if (codec_drv->reg_cache_size && codec_drv->reg_word_size) { reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; + codec->reg_size = reg_size; /* it is necessary to make a copy of the default register cache * because in the case of using a compression type that requires * the default register cache to be marked as __devinitconst the * kernel might have freed the array by the time we initialize * the cache. */ - codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default, - reg_size, GFP_KERNEL); - if (!codec->reg_def_copy) { - ret = -ENOMEM; - goto fail; + if (codec_drv->reg_cache_default) { + codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default, + reg_size, GFP_KERNEL); + if (!codec->reg_def_copy) { + ret = -ENOMEM; + goto fail; + } } } + if (codec_drv->reg_access_size && codec_drv->reg_access_default) { + if (!codec->volatile_register) + codec->volatile_register = snd_soc_default_volatile_register; + if (!codec->readable_register) + codec->readable_register = snd_soc_default_readable_register; + } + for (i = 0; i < num_dai; i++) { fixup_codec_formats(&dai_drv[i].playback); fixup_codec_formats(&dai_drv[i].capture); @@ -3574,22 +3770,22 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec); static int __init snd_soc_init(void) { #ifdef CONFIG_DEBUG_FS - debugfs_root = debugfs_create_dir("asoc", NULL); - if (IS_ERR(debugfs_root) || !debugfs_root) { + snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); + if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) { printk(KERN_WARNING "ASoC: Failed to create debugfs directory\n"); - debugfs_root = NULL; + snd_soc_debugfs_root = NULL; } - if (!debugfs_create_file("codecs", 0444, debugfs_root, NULL, + if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL, &codec_list_fops)) pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); - if (!debugfs_create_file("dais", 0444, debugfs_root, NULL, + if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, &dai_list_fops)) pr_warn("ASoC: Failed to create DAI list debugfs file\n"); - if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL, + if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL, &platform_list_fops)) pr_warn("ASoC: Failed to create platform list debugfs file\n"); #endif @@ -3601,7 +3797,7 @@ module_init(snd_soc_init); static void __exit snd_soc_exit(void) { #ifdef CONFIG_DEBUG_FS - debugfs_remove_recursive(debugfs_root); + debugfs_remove_recursive(snd_soc_debugfs_root); #endif platform_driver_unregister(&soc_driver); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 25e54230cc6..81c4052c127 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -32,6 +32,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> +#include <linux/async.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/bitops.h> @@ -125,17 +126,17 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( /** * snd_soc_dapm_set_bias_level - set the bias level for the system - * @card: audio device + * @dapm: DAPM context * @level: level to configure * * Configure the bias (power) levels for the SoC audio device. * * Returns 0 for success else error. */ -static int snd_soc_dapm_set_bias_level(struct snd_soc_card *card, - struct snd_soc_dapm_context *dapm, +static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) { + struct snd_soc_card *card = dapm->card; int ret = 0; switch (level) { @@ -365,9 +366,20 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *w) { int i, ret = 0; - size_t name_len; + size_t name_len, prefix_len; struct snd_soc_dapm_path *path; - struct snd_card *card = dapm->codec->card->snd_card; + struct snd_card *card = dapm->card->snd_card; + const char *prefix; + + if (dapm->codec) + prefix = dapm->codec->name_prefix; + else + prefix = NULL; + + if (prefix) + prefix_len = strlen(prefix) + 1; + else + prefix_len = 0; /* add kcontrol */ for (i = 0; i < w->num_kcontrols; i++) { @@ -396,8 +408,15 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, switch (w->id) { default: + /* The control will get a prefix from + * the control creation process but + * we're also using the same prefix + * for widgets so cut the prefix off + * the front of the widget name. + */ snprintf(path->long_name, name_len, "%s %s", - w->name, w->kcontrols[i].name); + w->name + prefix_len, + w->kcontrols[i].name); break; case snd_soc_dapm_mixer_named_ctl: snprintf(path->long_name, name_len, "%s", @@ -408,7 +427,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, path->long_name[name_len - 1] = '\0'; path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w, - path->long_name); + path->long_name, prefix); ret = snd_ctl_add(card, path->kcontrol); if (ret < 0) { dev_err(dapm->dev, @@ -429,7 +448,9 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, { struct snd_soc_dapm_path *path = NULL; struct snd_kcontrol *kcontrol; - struct snd_card *card = dapm->codec->card->snd_card; + struct snd_card *card = dapm->card->snd_card; + const char *prefix; + size_t prefix_len; int ret = 0; if (!w->num_kcontrols) { @@ -437,7 +458,22 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, return -EINVAL; } - kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name); + if (dapm->codec) + prefix = dapm->codec->name_prefix; + else + prefix = NULL; + + if (prefix) + prefix_len = strlen(prefix) + 1; + else + prefix_len = 0; + + /* The control will get a prefix from the control creation + * process but we're also using the same prefix for widgets so + * cut the prefix off the front of the widget name. + */ + kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name + prefix_len, + prefix); ret = snd_ctl_add(card, kcontrol); if (ret < 0) @@ -479,7 +515,7 @@ static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm) */ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) { - int level = snd_power_get_state(widget->dapm->codec->card->snd_card); + int level = snd_power_get_state(widget->dapm->card->snd_card); switch (level) { case SNDRV_CTL_POWER_D3hot: @@ -734,10 +770,23 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, - int sort[]) + bool power_up) { + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; + if (sort[a->id] != sort[b->id]) return sort[a->id] - sort[b->id]; + if (a->subseq != b->subseq) { + if (power_up) + return a->subseq - b->subseq; + else + return b->subseq - a->subseq; + } if (a->reg != b->reg) return a->reg - b->reg; if (a->dapm != b->dapm) @@ -749,12 +798,12 @@ static int dapm_seq_compare(struct snd_soc_dapm_widget *a, /* Insert a widget in order into a DAPM power sequence. */ static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, struct list_head *list, - int sort[]) + bool power_up) { struct snd_soc_dapm_widget *w; list_for_each_entry(w, list, power_list) - if (dapm_seq_compare(new_widget, w, sort) < 0) { + if (dapm_seq_compare(new_widget, w, power_up) < 0) { list_add_tail(&new_widget->power_list, &w->power_list); return; } @@ -865,26 +914,42 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm, * handled. */ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, - struct list_head *list, int event, int sort[]) + struct list_head *list, int event, bool power_up) { struct snd_soc_dapm_widget *w, *n; LIST_HEAD(pending); int cur_sort = -1; + int cur_subseq = -1; int cur_reg = SND_SOC_NOPM; struct snd_soc_dapm_context *cur_dapm = NULL; - int ret; + int ret, i; + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; list_for_each_entry_safe(w, n, list, power_list) { ret = 0; /* Do we need to apply any queued changes? */ if (sort[w->id] != cur_sort || w->reg != cur_reg || - w->dapm != cur_dapm) { + w->dapm != cur_dapm || w->subseq != cur_subseq) { if (!list_empty(&pending)) dapm_seq_run_coalesced(cur_dapm, &pending); + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i, + cur_subseq); + } + INIT_LIST_HEAD(&pending); cur_sort = -1; + cur_subseq = -1; cur_reg = SND_SOC_NOPM; cur_dapm = NULL; } @@ -929,6 +994,7 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, default: /* Queue it up for application */ cur_sort = sort[w->id]; + cur_subseq = w->subseq; cur_reg = w->reg; cur_dapm = w->dapm; list_move(&w->power_list, &pending); @@ -941,7 +1007,14 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, } if (!list_empty(&pending)) - dapm_seq_run_coalesced(dapm, &pending); + dapm_seq_run_coalesced(cur_dapm, &pending); + + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i, cur_subseq); + } } static void dapm_widget_update(struct snd_soc_dapm_context *dapm) @@ -977,7 +1050,62 @@ static void dapm_widget_update(struct snd_soc_dapm_context *dapm) } } +/* Async callback run prior to DAPM sequences - brings to _PREPARE if + * they're changing state. + */ +static void dapm_pre_sequence_async(void *data, async_cookie_t cookie) +{ + struct snd_soc_dapm_context *d = data; + int ret; + if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); + if (ret != 0) + dev_err(d->dev, + "Failed to turn on bias: %d\n", ret); + } + + /* If we're changing to all on or all off then prepare */ + if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) || + (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE); + if (ret != 0) + dev_err(d->dev, + "Failed to prepare bias: %d\n", ret); + } +} + +/* Async callback run prior to DAPM sequences - brings to their final + * state. + */ +static void dapm_post_sequence_async(void *data, async_cookie_t cookie) +{ + struct snd_soc_dapm_context *d = data; + int ret; + + /* If we just powered the last thing off drop to standby bias */ + if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); + if (ret != 0) + dev_err(d->dev, "Failed to apply standby bias: %d\n", + ret); + } + + /* If we're in standby and can support bias off then do that */ + if (d->bias_level == SND_SOC_BIAS_STANDBY && d->idle_bias_off) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF); + if (ret != 0) + dev_err(d->dev, "Failed to turn off bias: %d\n", ret); + } + + /* If we just powered up then move to active bias */ + if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON); + if (ret != 0) + dev_err(d->dev, "Failed to apply active bias: %d\n", + ret); + } +} /* * Scan each dapm widget for complete audio path. @@ -990,12 +1118,12 @@ static void dapm_widget_update(struct snd_soc_dapm_context *dapm) */ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) { - struct snd_soc_card *card = dapm->codec->card; + struct snd_soc_card *card = dapm->card; struct snd_soc_dapm_widget *w; struct snd_soc_dapm_context *d; LIST_HEAD(up_list); LIST_HEAD(down_list); - int ret = 0; + LIST_HEAD(async_domain); int power; trace_snd_soc_dapm_start(card); @@ -1010,10 +1138,10 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) list_for_each_entry(w, &card->widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); break; case snd_soc_dapm_post: - dapm_seq_insert(w, &up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, true); break; default: @@ -1033,9 +1161,9 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) trace_snd_soc_dapm_widget_power(w, power); if (power) - dapm_seq_insert(w, &up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, true); else - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); w->power = power; break; @@ -1073,65 +1201,25 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } } - list_for_each_entry(d, &dapm->card->dapm_list, list) { - if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) { - ret = snd_soc_dapm_set_bias_level(card, d, - SND_SOC_BIAS_STANDBY); - if (ret != 0) - dev_err(d->dev, - "Failed to turn on bias: %d\n", ret); - } - - /* If we're changing to all on or all off then prepare */ - if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) || - (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) { - ret = snd_soc_dapm_set_bias_level(card, d, - SND_SOC_BIAS_PREPARE); - if (ret != 0) - dev_err(d->dev, - "Failed to prepare bias: %d\n", ret); - } - } + /* Run all the bias changes in parallel */ + list_for_each_entry(d, &dapm->card->dapm_list, list) + async_schedule_domain(dapm_pre_sequence_async, d, + &async_domain); + async_synchronize_full_domain(&async_domain); /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(dapm, &down_list, event, dapm_down_seq); + dapm_seq_run(dapm, &down_list, event, false); dapm_widget_update(dapm); /* Now power up. */ - dapm_seq_run(dapm, &up_list, event, dapm_up_seq); - - list_for_each_entry(d, &dapm->card->dapm_list, list) { - /* If we just powered the last thing off drop to standby bias */ - if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) { - ret = snd_soc_dapm_set_bias_level(card, d, - SND_SOC_BIAS_STANDBY); - if (ret != 0) - dev_err(d->dev, - "Failed to apply standby bias: %d\n", - ret); - } + dapm_seq_run(dapm, &up_list, event, true); - /* If we're in standby and can support bias off then do that */ - if (d->bias_level == SND_SOC_BIAS_STANDBY && - d->idle_bias_off) { - ret = snd_soc_dapm_set_bias_level(card, d, - SND_SOC_BIAS_OFF); - if (ret != 0) - dev_err(d->dev, - "Failed to turn off bias: %d\n", ret); - } - - /* If we just powered up then move to active bias */ - if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) { - ret = snd_soc_dapm_set_bias_level(card, d, - SND_SOC_BIAS_ON); - if (ret != 0) - dev_err(d->dev, - "Failed to apply active bias: %d\n", - ret); - } - } + /* Run all the bias changes in parallel */ + list_for_each_entry(d, &dapm->card->dapm_list, list) + async_schedule_domain(dapm_post_sequence_async, d, + &async_domain); + async_synchronize_full_domain(&async_domain); pop_dbg(dapm->dev, card->pop_time, "DAPM sequencing finished, waiting %dms\n", card->pop_time); @@ -1189,7 +1277,7 @@ static ssize_t dapm_widget_power_read_file(struct file *file, if (p->connect) ret += snprintf(buf + ret, PAGE_SIZE - ret, - " in %s %s\n", + " in \"%s\" \"%s\"\n", p->name ? p->name : "static", p->source->name); } @@ -1199,7 +1287,7 @@ static ssize_t dapm_widget_power_read_file(struct file *file, if (p->connect) ret += snprintf(buf + ret, PAGE_SIZE - ret, - " out %s %s\n", + " out \"%s\" \"%s\"\n", p->name ? p->name : "static", p->sink->name); } @@ -1464,7 +1552,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, char prefixed_source[80]; int ret = 0; - if (dapm->codec->name_prefix) { + if (dapm->codec && dapm->codec->name_prefix) { snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", dapm->codec->name_prefix, route->sink); sink = prefixed_sink; @@ -2114,14 +2202,14 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, return -ENOMEM; name_len = strlen(widget->name) + 1; - if (dapm->codec->name_prefix) + if (dapm->codec && dapm->codec->name_prefix) name_len += 1 + strlen(dapm->codec->name_prefix); w->name = kmalloc(name_len, GFP_KERNEL); if (w->name == NULL) { kfree(w); return -ENOMEM; } - if (dapm->codec->name_prefix) + if (dapm->codec && dapm->codec->name_prefix) snprintf(w->name, name_len, "%s %s", dapm->codec->name_prefix, widget->name); else @@ -2226,7 +2314,6 @@ int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, mutex_unlock(&codec->mutex); return 0; } -EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); /** * snd_soc_dapm_enable_pin - enable pin. @@ -2393,7 +2480,7 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm) if (w->dapm != dapm) continue; if (w->power) { - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); w->power = 0; powerdown = 1; } @@ -2403,9 +2490,9 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm) * standby. */ if (powerdown) { - snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_PREPARE); - dapm_seq_run(dapm, &down_list, 0, dapm_down_seq); - snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_STANDBY); + snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_PREPARE); + dapm_seq_run(dapm, &down_list, 0, false); + snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY); } } @@ -2418,7 +2505,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card) list_for_each_entry(codec, &card->codec_dev_list, list) { soc_dapm_shutdown_codec(&codec->dapm); - snd_soc_dapm_set_bias_level(card, &codec->dapm, SND_SOC_BIAS_OFF); + snd_soc_dapm_set_bias_level(&codec->dapm, SND_SOC_BIAS_OFF); } } diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index ac5a5bc7375..fcab80b36a3 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -37,6 +37,7 @@ int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, { jack->codec = codec; INIT_LIST_HEAD(&jack->pins); + INIT_LIST_HEAD(&jack->jack_zones); BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); return snd_jack_new(codec->card->snd_card, id, type, &jack->jack); @@ -100,7 +101,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) } /* Report before the DAPM sync to help users updating micbias status */ - blocking_notifier_call_chain(&jack->notifier, status, NULL); + blocking_notifier_call_chain(&jack->notifier, status, jack); snd_soc_dapm_sync(dapm); @@ -112,6 +113,51 @@ out: EXPORT_SYMBOL_GPL(snd_soc_jack_report); /** + * snd_soc_jack_add_zones - Associate voltage zones with jack + * + * @jack: ASoC jack + * @count: Number of zones + * @zone: Array of zones + * + * After this function has been called the zones specified in the + * array will be associated with the jack. + */ +int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_zone *zones) +{ + int i; + + for (i = 0; i < count; i++) { + INIT_LIST_HEAD(&zones[i].list); + list_add(&(zones[i].list), &jack->jack_zones); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones); + +/** + * snd_soc_jack_get_type - Based on the mic bias value, this function returns + * the type of jack from the zones delcared in the jack type + * + * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in + * + * Based on the mic bias value passed, this function helps identify + * the type of jack from the already delcared jack zones + */ +int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage) +{ + struct snd_soc_jack_zone *zone; + + list_for_each_entry(zone, &jack->jack_zones, list) { + if (micbias_voltage >= zone->min_mv && + micbias_voltage < zone->max_mv) + return zone->jack_type; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_get_type); + +/** * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack * * @jack: ASoC jack @@ -194,7 +240,7 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) int enable; int report; - enable = gpio_get_value(gpio->gpio); + enable = gpio_get_value_cansleep(gpio->gpio); if (gpio->invert) enable = !enable; @@ -284,6 +330,14 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, if (ret) goto err; + if (gpios[i].wake) { + ret = set_irq_wake(gpio_to_irq(gpios[i].gpio), 1); + if (ret != 0) + printk(KERN_ERR + "Failed to mark GPIO %d as wake source: %d\n", + gpios[i].gpio, ret); + } + #ifdef CONFIG_GPIO_SYSFS /* Expose GPIO value over sysfs for diagnostic purposes */ gpio_export(gpios[i].gpio, false); diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c index 1d07b931f3d..3f45e6a439b 100644 --- a/sound/soc/soc-utils.c +++ b/sound/soc/soc-utils.c @@ -28,26 +28,9 @@ int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params) { int sample_size; - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - case SNDRV_PCM_FORMAT_S16_BE: - sample_size = 16; - break; - case SNDRV_PCM_FORMAT_S20_3LE: - case SNDRV_PCM_FORMAT_S20_3BE: - sample_size = 20; - break; - case SNDRV_PCM_FORMAT_S24_LE: - case SNDRV_PCM_FORMAT_S24_BE: - sample_size = 24; - break; - case SNDRV_PCM_FORMAT_S32_LE: - case SNDRV_PCM_FORMAT_S32_BE: - sample_size = 32; - break; - default: - return -ENOTSUPP; - } + sample_size = snd_pcm_format_width(params_format(params)); + if (sample_size < 0) + return sample_size; return snd_soc_calc_frame_size(sample_size, params_channels(params), 1); diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 00000000000..66b504f06c2 --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,26 @@ +config SND_TEGRA_SOC + tristate "SoC Audio for the Tegra System-on-Chip" + depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA + default m + help + Say Y or M here if you want support for SoC audio on Tegra. + +config SND_TEGRA_SOC_I2S + tristate + depends on SND_TEGRA_SOC + default m + help + Say Y or M if you want to add support for codecs attached to the + Tegra I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_TEGRA_SOC_HARMONY + tristate "SoC Audio support for Tegra Harmony reference board" + depends on SND_TEGRA_SOC && MACH_HARMONY && I2C + default m + select SND_TEGRA_SOC_I2S + select SND_SOC_WM8903 + help + Say Y or M here if you want to add support for SoC audio on the + Tegra Harmony reference board. + diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 00000000000..fd183d3ab4f --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,15 @@ +# Tegra platform Support +snd-soc-tegra-das-objs := tegra_das.o +snd-soc-tegra-pcm-objs := tegra_pcm.o +snd-soc-tegra-i2s-objs := tegra_i2s.o +snd-soc-tegra-utils-objs += tegra_asoc_utils.o + +obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-utils.o +obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-das.o +obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-pcm.o +obj-$(CONFIG_SND_TEGRA_SOC_I2S) += snd-soc-tegra-i2s.o + +# Tegra machine Support +snd-soc-tegra-harmony-objs := harmony.o + +obj-$(CONFIG_SND_TEGRA_SOC_HARMONY) += snd-soc-tegra-harmony.o diff --git a/sound/soc/tegra/harmony.c b/sound/soc/tegra/harmony.c new file mode 100644 index 00000000000..8585957477e --- /dev/null +++ b/sound/soc/tegra/harmony.c @@ -0,0 +1,393 @@ +/* + * harmony.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2011 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <asm/mach-types.h> + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/gpio.h> + +#include <mach/harmony_audio.h> + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/wm8903.h" + +#include "tegra_das.h" +#include "tegra_i2s.h" +#include "tegra_pcm.h" +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-harmony" + +#define GPIO_SPKR_EN BIT(0) +#define GPIO_INT_MIC_EN BIT(1) +#define GPIO_EXT_MIC_EN BIT(2) + +struct tegra_harmony { + struct tegra_asoc_utils_data util_data; + struct harmony_audio_platform_data *pdata; + int gpio_requested; +}; + +static int harmony_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card); + int srate, mclk, mclk_change; + int err; + + srate = params_rate(params); + switch (srate) { + case 64000: + case 88200: + case 96000: + mclk = 128 * srate; + break; + default: + mclk = 256 * srate; + break; + } + /* FIXME: Codec only requires >= 3MHz if OSR==0 */ + while (mclk < 6000000) + mclk *= 2; + + err = tegra_asoc_utils_set_rate(&harmony->util_data, srate, mclk, + &mclk_change); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + dev_err(card->dev, "codec_dai fmt not set\n"); + return err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + dev_err(card->dev, "cpu_dai fmt not set\n"); + return err; + } + + if (mclk_change) { + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + } + + return 0; +} + +static struct snd_soc_ops harmony_asoc_ops = { + .hw_params = harmony_asoc_hw_params, +}; + +static struct snd_soc_jack harmony_hp_jack; + +static struct snd_soc_jack_pin harmony_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio harmony_hp_jack_gpios[] = { + { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, + } +}; + +static struct snd_soc_jack harmony_mic_jack; + +static struct snd_soc_jack_pin harmony_mic_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int harmony_event_int_spk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct snd_soc_card *card = codec->card; + struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card); + struct harmony_audio_platform_data *pdata = harmony->pdata; + + gpio_set_value_cansleep(pdata->gpio_spkr_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget harmony_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Int Spk", harmony_event_int_spk), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route harmony_audio_map[] = { + {"Headphone Jack", NULL, "HPOUTR"}, + {"Headphone Jack", NULL, "HPOUTL"}, + {"Int Spk", NULL, "ROP"}, + {"Int Spk", NULL, "RON"}, + {"Int Spk", NULL, "LOP"}, + {"Int Spk", NULL, "LON"}, + {"Mic Bias", NULL, "Mic Jack"}, + {"IN1L", NULL, "Mic Bias"}, +}; + +static const struct snd_kcontrol_new harmony_controls[] = { + SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int harmony_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_card *card = codec->card; + struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card); + struct harmony_audio_platform_data *pdata = harmony->pdata; + int ret; + + ret = gpio_request(pdata->gpio_spkr_en, "spkr_en"); + if (ret) { + dev_err(card->dev, "cannot get spkr_en gpio\n"); + return ret; + } + harmony->gpio_requested |= GPIO_SPKR_EN; + + gpio_direction_output(pdata->gpio_spkr_en, 0); + + ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get int_mic_en gpio\n"); + return ret; + } + harmony->gpio_requested |= GPIO_INT_MIC_EN; + + /* Disable int mic; enable signal is active-high */ + gpio_direction_output(pdata->gpio_int_mic_en, 0); + + ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get ext_mic_en gpio\n"); + return ret; + } + harmony->gpio_requested |= GPIO_EXT_MIC_EN; + + /* Enable ext mic; enable signal is active-low */ + gpio_direction_output(pdata->gpio_ext_mic_en, 0); + + ret = snd_soc_add_controls(codec, harmony_controls, + ARRAY_SIZE(harmony_controls)); + if (ret < 0) + return ret; + + snd_soc_dapm_new_controls(dapm, harmony_dapm_widgets, + ARRAY_SIZE(harmony_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, harmony_audio_map, + ARRAY_SIZE(harmony_audio_map)); + + harmony_hp_jack_gpios[0].gpio = pdata->gpio_hp_det; + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, + &harmony_hp_jack); + snd_soc_jack_add_pins(&harmony_hp_jack, + ARRAY_SIZE(harmony_hp_jack_pins), + harmony_hp_jack_pins); + snd_soc_jack_add_gpios(&harmony_hp_jack, + ARRAY_SIZE(harmony_hp_jack_gpios), + harmony_hp_jack_gpios); + + snd_soc_jack_new(codec, "Mic Jack", SND_JACK_MICROPHONE, + &harmony_mic_jack); + snd_soc_jack_add_pins(&harmony_mic_jack, + ARRAY_SIZE(harmony_mic_jack_pins), + harmony_mic_jack_pins); + wm8903_mic_detect(codec, &harmony_mic_jack, SND_JACK_MICROPHONE, 0); + + snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + + snd_soc_dapm_nc_pin(dapm, "IN3L"); + snd_soc_dapm_nc_pin(dapm, "IN3R"); + snd_soc_dapm_nc_pin(dapm, "LINEOUTL"); + snd_soc_dapm_nc_pin(dapm, "LINEOUTR"); + + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link harmony_wm8903_dai = { + .name = "WM8903", + .stream_name = "WM8903 PCM", + .codec_name = "wm8903.0-001a", + .platform_name = "tegra-pcm-audio", + .cpu_dai_name = "tegra-i2s.0", + .codec_dai_name = "wm8903-hifi", + .init = harmony_asoc_init, + .ops = &harmony_asoc_ops, +}; + +static struct snd_soc_card snd_soc_harmony = { + .name = "tegra-harmony", + .dai_link = &harmony_wm8903_dai, + .num_links = 1, +}; + +static __devinit int tegra_snd_harmony_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_harmony; + struct tegra_harmony *harmony; + struct harmony_audio_platform_data *pdata; + int ret; + + if (!machine_is_harmony()) { + dev_err(&pdev->dev, "Not running on Tegra Harmony!\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + harmony = kzalloc(sizeof(struct tegra_harmony), GFP_KERNEL); + if (!harmony) { + dev_err(&pdev->dev, "Can't allocate tegra_harmony\n"); + return -ENOMEM; + } + + harmony->pdata = pdata; + + ret = tegra_asoc_utils_init(&harmony->util_data, &pdev->dev); + if (ret) + goto err_free_harmony; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, harmony); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_clear_drvdata; + } + + return 0; + +err_clear_drvdata: + snd_soc_card_set_drvdata(card, NULL); + platform_set_drvdata(pdev, NULL); + card->dev = NULL; + tegra_asoc_utils_fini(&harmony->util_data); +err_free_harmony: + kfree(harmony); + return ret; +} + +static int __devexit tegra_snd_harmony_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card); + struct harmony_audio_platform_data *pdata = harmony->pdata; + + snd_soc_unregister_card(card); + + snd_soc_card_set_drvdata(card, NULL); + platform_set_drvdata(pdev, NULL); + card->dev = NULL; + + tegra_asoc_utils_fini(&harmony->util_data); + + if (harmony->gpio_requested & GPIO_EXT_MIC_EN) + gpio_free(pdata->gpio_ext_mic_en); + if (harmony->gpio_requested & GPIO_INT_MIC_EN) + gpio_free(pdata->gpio_int_mic_en); + if (harmony->gpio_requested & GPIO_SPKR_EN) + gpio_free(pdata->gpio_spkr_en); + + kfree(harmony); + + return 0; +} + +static struct platform_driver tegra_snd_harmony_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra_snd_harmony_probe, + .remove = __devexit_p(tegra_snd_harmony_remove), +}; + +static int __init snd_tegra_harmony_init(void) +{ + return platform_driver_register(&tegra_snd_harmony_driver); +} +module_init(snd_tegra_harmony_init); + +static void __exit snd_tegra_harmony_exit(void) +{ + platform_driver_unregister(&tegra_snd_harmony_driver); +} +module_exit(snd_tegra_harmony_exit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Harmony machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 00000000000..52f0a3f9ce4 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -0,0 +1,155 @@ +/* + * tegra_asoc_utils.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> + +#include "tegra_asoc_utils.h" + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk, int *mclk_change) +{ + int new_baseclock; + int err; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + new_baseclock = 56448000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + new_baseclock = 73728000; + break; + default: + return -EINVAL; + } + + *mclk_change = ((new_baseclock != data->set_baseclock) || + (mclk != data->set_mclk)); + if (!*mclk_change) + return 0; + + data->set_baseclock = 0; + data->set_mclk = 0; + + clk_disable(data->clk_cdev1); + clk_disable(data->clk_pll_a_out0); + clk_disable(data->clk_pll_a); + + err = clk_set_rate(data->clk_pll_a, new_baseclock); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, mclk); + if (err) { + dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1 rate; its locked to pll_a_out0 */ + + err = clk_enable(data->clk_pll_a); + if (err) { + dev_err(data->dev, "Can't enable pll_a: %d\n", err); + return err; + } + + err = clk_enable(data->clk_pll_a_out0); + if (err) { + dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err); + return err; + } + + err = clk_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + + data->set_baseclock = new_baseclock; + data->set_mclk = mclk; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate); + +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev) +{ + int ret; + + data->dev = dev; + + data->clk_pll_a = clk_get_sys(NULL, "pll_a"); + if (IS_ERR(data->clk_pll_a)) { + dev_err(data->dev, "Can't retrieve clk pll_a\n"); + ret = PTR_ERR(data->clk_pll_a); + goto err; + } + + data->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR(data->clk_pll_a_out0)) { + dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); + ret = PTR_ERR(data->clk_pll_a_out0); + goto err_put_pll_a; + } + + data->clk_cdev1 = clk_get_sys(NULL, "cdev1"); + if (IS_ERR(data->clk_cdev1)) { + dev_err(data->dev, "Can't retrieve clk cdev1\n"); + ret = PTR_ERR(data->clk_cdev1); + goto err_put_pll_a_out0; + } + + return 0; + +err_put_pll_a_out0: + clk_put(data->clk_pll_a_out0); +err_put_pll_a: + clk_put(data->clk_pll_a); +err: + return ret; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_init); + +void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data) +{ + clk_put(data->clk_cdev1); + clk_put(data->clk_pll_a_out0); + clk_put(data->clk_pll_a); +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra ASoC utility code"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 00000000000..bbba7afdfc2 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -0,0 +1,45 @@ +/* + * tegra_asoc_utils.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_ASOC_UTILS_H__ +#define __TEGRA_ASOC_UTILS_H_ + +struct clk; +struct device; + +struct tegra_asoc_utils_data { + struct device *dev; + struct clk *clk_pll_a; + struct clk *clk_pll_a_out0; + struct clk *clk_cdev1; + int set_baseclock; + int set_mclk; +}; + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk, int *mclk_change); +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev); +void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data); + +#endif + diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c new file mode 100644 index 00000000000..9f24ef73f2c --- /dev/null +++ b/sound/soc/tegra/tegra_das.c @@ -0,0 +1,265 @@ +/* + * tegra_das.c - Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <mach/iomap.h> +#include <sound/soc.h> +#include "tegra_das.h" + +#define DRV_NAME "tegra-das" + +static struct tegra_das *das; + +static inline void tegra_das_write(u32 reg, u32 val) +{ + __raw_writel(val, das->regs + reg); +} + +static inline u32 tegra_das_read(u32 reg) +{ + return __raw_readl(das->regs + reg); +} + +int tegra_das_connect_dap_to_dac(int dap, int dac) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAP_CTRL_SEL + + (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac); + +int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master, + int sdata1rx, int sdata2rx) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAP_CTRL_SEL + + (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | + !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | + !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | + !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap); + +int tegra_das_connect_dac_to_dap(int dac, int dap) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + + (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | + dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | + dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap); + +#ifdef CONFIG_DEBUG_FS +static int tegra_das_show(struct seq_file *s, void *unused) +{ + int i; + u32 addr; + u32 reg; + + for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) { + addr = TEGRA_DAS_DAP_CTRL_SEL + + (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = tegra_das_read(addr); + seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg); + } + + for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) { + addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + + (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = tegra_das_read(addr); + seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n", + i, reg); + } + + return 0; +} + +static int tegra_das_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, tegra_das_show, inode->i_private); +} + +static const struct file_operations tegra_das_debug_fops = { + .open = tegra_das_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void tegra_das_debug_add(struct tegra_das *das) +{ + das->debug = debugfs_create_file(DRV_NAME, S_IRUGO, + snd_soc_debugfs_root, das, + &tegra_das_debug_fops); +} + +static void tegra_das_debug_remove(struct tegra_das *das) +{ + if (das->debug) + debugfs_remove(das->debug); +} +#else +static inline void tegra_das_debug_add(struct tegra_das *das) +{ +} + +static inline void tegra_das_debug_remove(struct tegra_das *das) +{ +} +#endif + +static int __devinit tegra_das_probe(struct platform_device *pdev) +{ + struct resource *res, *region; + int ret = 0; + + if (das) + return -ENODEV; + + das = kzalloc(sizeof(struct tegra_das), GFP_KERNEL); + if (!das) { + dev_err(&pdev->dev, "Can't allocate tegra_das\n"); + ret = -ENOMEM; + goto exit; + } + das->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_free; + } + + region = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!region) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_free; + } + + das->regs = ioremap(res->start, resource_size(res)); + if (!das->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release; + } + + tegra_das_debug_add(das); + + platform_set_drvdata(pdev, das); + + return 0; + +err_release: + release_mem_region(res->start, resource_size(res)); +err_free: + kfree(das); + das = 0; +exit: + return ret; +} + +static int __devexit tegra_das_remove(struct platform_device *pdev) +{ + struct resource *res; + + if (!das) + return -ENODEV; + + platform_set_drvdata(pdev, NULL); + + tegra_das_debug_remove(das); + + iounmap(das->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(das); + das = 0; + + return 0; +} + +static struct platform_driver tegra_das_driver = { + .probe = tegra_das_probe, + .remove = __devexit_p(tegra_das_remove), + .driver = { + .name = DRV_NAME, + }, +}; + +static int __init tegra_das_modinit(void) +{ + return platform_driver_register(&tegra_das_driver); +} +module_init(tegra_das_modinit); + +static void __exit tegra_das_modexit(void) +{ + platform_driver_unregister(&tegra_das_driver); +} +module_exit(tegra_das_modexit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h new file mode 100644 index 00000000000..2c96c7b3c45 --- /dev/null +++ b/sound/soc/tegra/tegra_das.h @@ -0,0 +1,135 @@ +/* + * tegra_das.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_DAS_H__ +#define __TEGRA_DAS_H__ + +/* Register TEGRA_DAS_DAP_CTRL_SEL */ +#define TEGRA_DAS_DAP_CTRL_SEL 0x00 +#define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5 +#define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 + +/* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA_DAS_DAP_SEL_DAC1 0 +#define TEGRA_DAS_DAP_SEL_DAC2 1 +#define TEGRA_DAS_DAP_SEL_DAC3 2 +#define TEGRA_DAS_DAP_SEL_DAP1 16 +#define TEGRA_DAS_DAP_SEL_DAP2 17 +#define TEGRA_DAS_DAP_SEL_DAP3 18 +#define TEGRA_DAS_DAP_SEL_DAP4 19 +#define TEGRA_DAS_DAP_SEL_DAP5 20 + +/* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 + +/* + * Values for: + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA_DAS_DAC_SEL_DAP1 0 +#define TEGRA_DAS_DAC_SEL_DAP2 1 +#define TEGRA_DAS_DAC_SEL_DAP3 2 +#define TEGRA_DAS_DAC_SEL_DAP4 3 +#define TEGRA_DAS_DAC_SEL_DAP5 4 + +/* + * Names/IDs of the DACs/DAPs. + */ + +#define TEGRA_DAS_DAP_ID_1 0 +#define TEGRA_DAS_DAP_ID_2 1 +#define TEGRA_DAS_DAP_ID_3 2 +#define TEGRA_DAS_DAP_ID_4 3 +#define TEGRA_DAS_DAP_ID_5 4 + +#define TEGRA_DAS_DAC_ID_1 0 +#define TEGRA_DAS_DAC_ID_2 1 +#define TEGRA_DAS_DAC_ID_3 2 + +struct tegra_das { + struct device *dev; + void __iomem *regs; + struct dentry *debug; +}; + +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ + +/* + * Connect a DAP to to a DAC + * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* + * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC* + */ +extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel); + +/* + * Connect a DAP to to another DAP + * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* + * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP* + * master: Is this DAP the master (1) or slave (0) + * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) + * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) + */ +extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel, + int master, int sdata1rx, + int sdata2rx); + +/* + * Connect a DAC's input to a DAP + * (DAC outputs are selected by the DAP) + * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_* + * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP* + */ +extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel); + +#endif diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c new file mode 100644 index 00000000000..4f5e2c90b02 --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.c @@ -0,0 +1,503 @@ +/* + * tegra_i2s.c - Tegra I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <mach/iomap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra_das.h" +#include "tegra_i2s.h" + +#define DRV_NAME "tegra-i2s" + +static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val) +{ + __raw_writel(val, i2s->regs + reg); +} + +static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg) +{ + return __raw_readl(i2s->regs + reg); +} + +#ifdef CONFIG_DEBUG_FS +static int tegra_i2s_show(struct seq_file *s, void *unused) +{ +#define REG(r) { r, #r } + static const struct { + int offset; + const char *name; + } regs[] = { + REG(TEGRA_I2S_CTRL), + REG(TEGRA_I2S_STATUS), + REG(TEGRA_I2S_TIMING), + REG(TEGRA_I2S_FIFO_SCR), + REG(TEGRA_I2S_PCM_CTRL), + REG(TEGRA_I2S_NW_CTRL), + REG(TEGRA_I2S_TDM_CTRL), + REG(TEGRA_I2S_TDM_TX_RX_CTRL), + }; +#undef REG + + struct tegra_i2s *i2s = s->private; + int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + u32 val = tegra_i2s_read(i2s, regs[i].offset); + seq_printf(s, "%s = %08x\n", regs[i].name, val); + } + + return 0; +} + +static int tegra_i2s_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, tegra_i2s_show, inode->i_private); +} + +static const struct file_operations tegra_i2s_debug_fops = { + .open = tegra_i2s_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id) +{ + char name[] = DRV_NAME ".0"; + + snprintf(name, sizeof(name), DRV_NAME".%1d", id); + i2s->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root, + i2s, &tegra_i2s_debug_fops); +} + +static void tegra_i2s_debug_remove(struct tegra_i2s *i2s) +{ + if (i2s->debug) + debugfs_remove(i2s->debug); +} +#else +static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s) +{ +} + +static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s) +{ +} +#endif + +static int tegra_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK | + TEGRA_I2S_CTRL_LRCK_MASK); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = substream->pcm->card->dev; + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg; + int ret, sample_size, srate, i2sclock, bitcnt; + + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + return -EINVAL; + } + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE; + + tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg); + + tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR, + TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | + TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + + return 0; +} + +static void tegra_i2s_start_playback(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_stop_playback(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_start_capture(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_stop_capture(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (!i2s->clk_refs) + clk_enable(i2s->clk_i2s); + i2s->clk_refs++; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra_i2s_start_playback(i2s); + else + tegra_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra_i2s_stop_playback(i2s); + else + tegra_i2s_stop_capture(i2s); + i2s->clk_refs--; + if (!i2s->clk_refs) + clk_disable(i2s->clk_i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_ops tegra_i2s_dai_ops = { + .set_fmt = tegra_i2s_set_fmt, + .hw_params = tegra_i2s_hw_params, + .trigger = tegra_i2s_trigger, +}; + +struct snd_soc_dai_driver tegra_i2s_dai[] = { + { + .name = DRV_NAME ".0", + .probe = tegra_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_i2s_dai_ops, + .symmetric_rates = 1, + }, + { + .name = DRV_NAME ".1", + .probe = tegra_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_i2s_dai_ops, + .symmetric_rates = 1, + }, +}; + +static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra_i2s * i2s; + char clk_name[12]; /* tegra-i2s.0 */ + struct resource *mem, *memregion, *dmareq; + int ret; + + if ((pdev->id < 0) || + (pdev->id >= ARRAY_SIZE(tegra_i2s_dai))) { + dev_err(&pdev->dev, "ID %d out of range\n", pdev->id); + return -EINVAL; + } + + /* + * FIXME: Until a codec driver exists for the tegra DAS, hard-code a + * 1:1 mapping between audio controllers and audio ports. + */ + ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1 + pdev->id, + TEGRA_DAS_DAP_SEL_DAC1 + pdev->id); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAP connection\n"); + return ret; + } + ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1 + pdev->id, + TEGRA_DAS_DAC_SEL_DAP1 + pdev->id); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAC connection\n"); + return ret; + } + + i2s = kzalloc(sizeof(struct tegra_i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate tegra_i2s\n"); + ret = -ENOMEM; + goto exit; + } + dev_set_drvdata(&pdev->dev, i2s); + + snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id); + i2s->clk_i2s = clk_get_sys(clk_name, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err_free; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = request_mem_region(mem->start, resource_size(mem), + DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + i2s->regs = ioremap(mem->start, resource_size(mem)); + if (!i2s->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release; + } + + i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2; + i2s->capture_dma_data.wrap = 4; + i2s->capture_dma_data.width = 32; + i2s->capture_dma_data.req_sel = dmareq->start; + + i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1; + i2s->playback_dma_data.wrap = 4; + i2s->playback_dma_data.width = 32; + i2s->playback_dma_data.req_sel = dmareq->start; + + i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED; + + ret = snd_soc_register_dai(&pdev->dev, &tegra_i2s_dai[pdev->id]); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_unmap; + } + + tegra_i2s_debug_add(i2s, pdev->id); + + return 0; + +err_unmap: + iounmap(i2s->regs); +err_release: + release_mem_region(mem->start, resource_size(mem)); +err_clk_put: + clk_put(i2s->clk_i2s); +err_free: + kfree(i2s); +exit: + return ret; +} + +static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev); + struct resource *res; + + snd_soc_unregister_dai(&pdev->dev); + + tegra_i2s_debug_remove(i2s); + + iounmap(i2s->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + clk_put(i2s->clk_i2s); + + kfree(i2s); + + return 0; +} + +static struct platform_driver tegra_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra_i2s_platform_probe, + .remove = __devexit_p(tegra_i2s_platform_remove), +}; + +static int __init snd_tegra_i2s_init(void) +{ + return platform_driver_register(&tegra_i2s_driver); +} +module_init(snd_tegra_i2s_init); + +static void __exit snd_tegra_i2s_exit(void) +{ + platform_driver_unregister(&tegra_i2s_driver); +} +module_exit(snd_tegra_i2s_exit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h new file mode 100644 index 00000000000..2b38a096f46 --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.h @@ -0,0 +1,165 @@ +/* + * tegra_i2s.h - Definitions for Tegra I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_I2S_H__ +#define __TEGRA_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */ + +#define TEGRA_I2S_CTRL 0x00 +#define TEGRA_I2S_STATUS 0x04 +#define TEGRA_I2S_TIMING 0x08 +#define TEGRA_I2S_FIFO_SCR 0x0c +#define TEGRA_I2S_PCM_CTRL 0x10 +#define TEGRA_I2S_NW_CTRL 0x14 +#define TEGRA_I2S_TDM_CTRL 0x20 +#define TEGRA_I2S_TDM_TX_RX_CTRL 0x24 +#define TEGRA_I2S_FIFO1 0x40 +#define TEGRA_I2S_FIFO2 0x80 + +/* Fields in TEGRA_I2S_CTRL */ + +#define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) +#define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29) +#define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28) +#define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) +#define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) +#define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25) + +#define TEGRA_I2S_LRCK_LEFT_LOW 0 +#define TEGRA_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA_I2S_CTRL_LRCK_SHIFT 24 +#define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT) +#define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) +#define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA_I2S_BIT_FORMAT_I2S 0 +#define TEGRA_I2S_BIT_FORMAT_RJM 1 +#define TEGRA_I2S_BIT_FORMAT_LJM 2 +#define TEGRA_I2S_BIT_FORMAT_DSP 3 + +#define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10 +#define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) + +#define TEGRA_I2S_BIT_SIZE_16 0 +#define TEGRA_I2S_BIT_SIZE_20 1 +#define TEGRA_I2S_BIT_SIZE_24 2 +#define TEGRA_I2S_BIT_SIZE_32 3 + +#define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8 +#define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) + +#define TEGRA_I2S_FIFO_16_LSB 0 +#define TEGRA_I2S_FIFO_20_LSB 1 +#define TEGRA_I2S_FIFO_24_LSB 2 +#define TEGRA_I2S_FIFO_32 3 +#define TEGRA_I2S_FIFO_PACKED 7 + +#define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4 +#define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) + +#define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3) +#define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2) +#define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1) +#define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0) + +/* Fields in TEGRA_I2S_STATUS */ + +#define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31) +#define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30) +#define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29) +#define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28) +#define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3) +#define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2) +#define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1) +#define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0) + +/* Fields in TEGRA_I2S_TIMING */ + +#define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA_I2S_FIFO_SCR */ + +#define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 +#define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 +#define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f + +#define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) +#define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) + +#define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0 +#define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 +#define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 +#define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 + +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) + +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) + +struct tegra_i2s { + struct clk *clk_i2s; + int clk_refs; + struct tegra_pcm_dma_params capture_dma_data; + struct tegra_pcm_dma_params playback_dma_data; + void __iomem *regs; + struct dentry *debug; + u32 reg_ctrl; +}; + +#endif diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 00000000000..3c271f95358 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,404 @@ +/* + * tegra_pcm.c - Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * Vijay Mali <vmali@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra_pcm.h" + +#define DRV_NAME "tegra-pcm-audio" + +static const struct snd_pcm_hardware tegra_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 1024, + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, + .buffer_bytes_max = PAGE_SIZE * 8, + .fifo_size = 4, +}; + +static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd) +{ + struct snd_pcm_substream *substream = prtd->substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct tegra_dma_req *dma_req; + unsigned long addr; + + dma_req = &prtd->dma_req[prtd->dma_req_idx]; + prtd->dma_req_idx = 1 - prtd->dma_req_idx; + + addr = buf->addr + prtd->dma_pos; + prtd->dma_pos += dma_req->size; + if (prtd->dma_pos >= prtd->dma_pos_end) + prtd->dma_pos = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_req->source_addr = addr; + else + dma_req->dest_addr = addr; + + tegra_dma_enqueue_req(prtd->dma_chan, dma_req); +} + +static void dma_complete_callback(struct tegra_dma_req *req) +{ + struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + spin_lock(&prtd->lock); + + if (!prtd->running) { + spin_unlock(&prtd->lock); + return; + } + + if (++prtd->period_index >= runtime->periods) + prtd->period_index = 0; + + tegra_pcm_queue_dma(prtd); + + spin_unlock(&prtd->lock); + + snd_pcm_period_elapsed(substream); +} + +static void setup_dma_tx_request(struct tegra_dma_req *req, + struct tegra_pcm_dma_params * dmap) +{ + req->complete = dma_complete_callback; + req->to_memory = false; + req->dest_addr = dmap->addr; + req->dest_wrap = dmap->wrap; + req->source_bus_width = 32; + req->source_wrap = 0; + req->dest_bus_width = dmap->width; + req->req_sel = dmap->req_sel; +} + +static void setup_dma_rx_request(struct tegra_dma_req *req, + struct tegra_pcm_dma_params * dmap) +{ + req->complete = dma_complete_callback; + req->to_memory = true; + req->source_addr = dmap->addr; + req->dest_wrap = 0; + req->source_bus_width = dmap->width; + req->source_wrap = dmap->wrap; + req->dest_bus_width = 32; + req->req_sel = dmap->req_sel; +} + +static int tegra_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct tegra_pcm_dma_params * dmap; + int ret = 0; + + prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + prtd->substream = substream; + + spin_lock_init(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + setup_dma_tx_request(&prtd->dma_req[0], dmap); + setup_dma_tx_request(&prtd->dma_req[1], dmap); + } else { + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + setup_dma_rx_request(&prtd->dma_req[0], dmap); + setup_dma_rx_request(&prtd->dma_req[1], dmap); + } + + prtd->dma_req[0].dev = prtd; + prtd->dma_req[1].dev = prtd; + + prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); + if (prtd->dma_chan == NULL) { + ret = -ENOMEM; + goto err; + } + + /* Set HW params now that initialization is complete */ + snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto err; + + return 0; + +err: + if (prtd->dma_chan) { + tegra_dma_free_channel(prtd->dma_chan); + } + + kfree(prtd); + + return ret; +} + +static int tegra_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + tegra_dma_free_channel(prtd->dma_chan); + + kfree(prtd); + + return 0; +} + +static int tegra_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + prtd->dma_req[0].size = params_period_bytes(params); + prtd->dma_req[1].size = prtd->dma_req[0].size; + + return 0; +} + +static int tegra_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->dma_pos = 0; + prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size); + prtd->period_index = 0; + prtd->dma_req_idx = 0; + /* Fall-through */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&prtd->lock, flags); + prtd->running = 1; + spin_unlock_irqrestore(&prtd->lock, flags); + tegra_pcm_queue_dma(prtd); + tegra_pcm_queue_dma(prtd); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&prtd->lock, flags); + prtd->running = 0; + spin_unlock_irqrestore(&prtd->lock, flags); + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]); + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + return prtd->period_index * runtime->period_size; +} + + +static int tegra_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops tegra_pcm_ops = { + .open = tegra_pcm_open, + .close = tegra_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tegra_pcm_hw_params, + .hw_free = tegra_pcm_hw_free, + .trigger = tegra_pcm_trigger, + .pointer = tegra_pcm_pointer, + .mmap = tegra_pcm_mmap, +}; + +static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = tegra_pcm_hardware.buffer_bytes_max; + + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->bytes = size; + + return 0; +} + +static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + if (!buf->area) + return; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; +} + +static u64 tegra_dma_mask = DMA_BIT_MASK(32); + +static int tegra_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &tegra_dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->driver->playback.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; + } + + if (dai->driver->capture.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto err_free_play; + } + + return 0; + +err_free_play: + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +err: + return ret; +} + +static void tegra_pcm_free(struct snd_pcm *pcm) +{ + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +} + +struct snd_soc_platform_driver tegra_pcm_platform = { + .ops = &tegra_pcm_ops, + .pcm_new = tegra_pcm_new, + .pcm_free = tegra_pcm_free, +}; + +static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform); +} + +static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver tegra_pcm_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra_pcm_platform_probe, + .remove = __devexit_p(tegra_pcm_platform_remove), +}; + +static int __init snd_tegra_pcm_init(void) +{ + return platform_driver_register(&tegra_pcm_driver); +} +module_init(snd_tegra_pcm_init); + +static void __exit snd_tegra_pcm_exit(void) +{ + platform_driver_unregister(&tegra_pcm_driver); +} +module_exit(snd_tegra_pcm_exit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra PCM ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 00000000000..dbb90339fe0 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h @@ -0,0 +1,55 @@ +/* + * tegra_pcm.h - Definitions for Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_PCM_H__ +#define __TEGRA_PCM_H__ + +#include <mach/dma.h> + +struct tegra_pcm_dma_params { + unsigned long addr; + unsigned long wrap; + unsigned long width; + unsigned long req_sel; +}; + +struct tegra_runtime_data { + struct snd_pcm_substream *substream; + spinlock_t lock; + int running; + int dma_pos; + int dma_pos_end; + int period_index; + int dma_req_idx; + struct tegra_dma_req dma_req[2]; + struct tegra_dma_channel *dma_chan; +}; + +#endif |