diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-10-20 00:10:27 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-10-20 00:10:27 -0300 |
commit | d0b2f91bede3bd5e3d24dd6803e56eee959c1797 (patch) | |
tree | 7fee4ab0509879c373c4f2cbd5b8a5be5b4041ee /sound | |
parent | e914f8eb445e8f74b00303c19c2ffceaedd16a05 (diff) |
Linux-libre 4.8.2-gnupck-4.8.2-gnu
Diffstat (limited to 'sound')
219 files changed, 20631 insertions, 2435 deletions
diff --git a/sound/Makefile b/sound/Makefile index 77320709f..c41bdf5fd 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -2,7 +2,6 @@ # obj-$(CONFIG_SOUND) += soundcore.o -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/ \ diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig index e04062117..65171f665 100644 --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@ -9,14 +9,6 @@ menuconfig SND_ARM Drivers that are implemented on ASoC can be found in "ALSA for SoC audio support" section. -config SND_PXA2XX_LIB - tristate - select SND_AC97_CODEC if SND_PXA2XX_LIB_AC97 - select SND_DMAENGINE_PCM - -config SND_PXA2XX_LIB_AC97 - bool - if SND_ARM config SND_ARMAACI @@ -42,3 +34,10 @@ config SND_PXA2XX_AC97 endif # SND_ARM +config SND_PXA2XX_LIB + tristate + select SND_AC97_CODEC if SND_PXA2XX_LIB_AC97 + select SND_DMAENGINE_PCM + +config SND_PXA2XX_LIB_AC97 + bool diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 9b3334be9..2c498488a 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -67,6 +67,8 @@ struct snd_compr_file { struct snd_compr_stream stream; }; +static void error_delayed_work(struct work_struct *work); + /* * a note on stream states used: * we use following states in the compressed core @@ -123,6 +125,9 @@ static int snd_compr_open(struct inode *inode, struct file *f) snd_card_unref(compr->card); return -ENOMEM; } + + INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); + data->stream.ops = compr->ops; data->stream.direction = dirn; data->stream.private_data = compr->private_data; @@ -153,6 +158,8 @@ static int snd_compr_free(struct inode *inode, struct file *f) struct snd_compr_file *data = f->private_data; struct snd_compr_runtime *runtime = data->stream.runtime; + cancel_delayed_work_sync(&data->stream.error_work); + switch (runtime->state) { case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_DRAINING: @@ -237,6 +244,15 @@ snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) avail = snd_compr_calc_avail(stream, &ioctl_avail); ioctl_avail.avail = avail; + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + if (copy_to_user((__u64 __user *)arg, &ioctl_avail, sizeof(ioctl_avail))) return -EFAULT; @@ -346,11 +362,13 @@ static ssize_t snd_compr_read(struct file *f, char __user *buf, switch (stream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_PREPARED: - case SNDRV_PCM_STATE_XRUN: case SNDRV_PCM_STATE_SUSPENDED: case SNDRV_PCM_STATE_DISCONNECTED: retval = -EBADFD; goto out; + case SNDRV_PCM_STATE_XRUN: + retval = -EPIPE; + goto out; } avail = snd_compr_get_avail(stream); @@ -399,10 +417,16 @@ static unsigned int snd_compr_poll(struct file *f, poll_table *wait) stream = &data->stream; mutex_lock(&stream->device->lock); - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_XRUN: retval = snd_compr_get_poll(stream) | POLLERR; goto out; + default: + break; } + poll_wait(f, &stream->runtime->sleep, wait); avail = snd_compr_get_avail(stream); @@ -697,6 +721,45 @@ static int snd_compr_stop(struct snd_compr_stream *stream) return retval; } +static void error_delayed_work(struct work_struct *work) +{ + struct snd_compr_stream *stream; + + stream = container_of(work, struct snd_compr_stream, error_work.work); + + mutex_lock(&stream->device->lock); + + stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + wake_up(&stream->runtime->sleep); + + mutex_unlock(&stream->device->lock); +} + +/* + * snd_compr_stop_error: Report a fatal error on a stream + * @stream: pointer to stream + * @state: state to transition the stream to + * + * Stop the stream and set its state. + * + * Should be called with compressed device lock held. + */ +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state) +{ + if (stream->runtime->state == state) + return 0; + + stream->runtime->state = state; + + pr_debug("Changing state to: %d\n", state); + + queue_delayed_work(system_power_efficient_wq, &stream->error_work, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_compr_stop_error); + static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) { int ret; diff --git a/sound/core/control.c b/sound/core/control.c index b4fe9b002..fb096cb20 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -807,6 +807,36 @@ static int snd_ctl_elem_list(struct snd_card *card, return 0; } +static bool validate_element_member_dimension(struct snd_ctl_elem_info *info) +{ + unsigned int members; + unsigned int i; + + if (info->dimen.d[0] == 0) + return true; + + members = 1; + for (i = 0; i < ARRAY_SIZE(info->dimen.d); ++i) { + if (info->dimen.d[i] == 0) + break; + members *= info->dimen.d[i]; + + /* + * info->count should be validated in advance, to guarantee + * calculation soundness. + */ + if (members > info->count) + return false; + } + + for (++i; i < ARRAY_SIZE(info->dimen.d); ++i) { + if (info->dimen.d[i] > 0) + return false; + } + + return members == info->count; +} + static int snd_ctl_elem_info(struct snd_ctl_file *ctl, struct snd_ctl_elem_info *info) { @@ -1274,6 +1304,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, if (info->count < 1 || info->count > max_value_counts[info->type]) return -EINVAL; + if (!validate_element_member_dimension(info)) + return -EINVAL; private_size = value_sizes[info->type] * info->count; /* diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c index b16dbef04..cd0e0ebbf 100644 --- a/sound/core/seq/oss/seq_oss_synth.c +++ b/sound/core/seq/oss/seq_oss_synth.c @@ -70,11 +70,11 @@ struct seq_oss_synth { static int max_synth_devs; static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; static struct seq_oss_synth midi_synth_dev = { - -1, /* seq_device */ - SYNTH_TYPE_MIDI, /* synth_type */ - 0, /* synth_subtype */ - 16, /* nr_voices */ - "MIDI", /* name */ + .seq_device = -1, + .synth_type = SYNTH_TYPE_MIDI, + .synth_subtype = 0, + .nr_voices = 16, + .name = "MIDI", }; static DEFINE_SPINLOCK(register_lock); diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c index 293104926..dcc102813 100644 --- a/sound/core/seq/seq_timer.c +++ b/sound/core/seq/seq_timer.c @@ -165,7 +165,7 @@ static void snd_seq_timer_interrupt(struct snd_timer_instance *timeri, snd_seq_timer_update_tick(&tmr->tick, resolution); /* register actual time of this timer update */ - do_gettimeofday(&tmr->last_update); + ktime_get_ts64(&tmr->last_update); spin_unlock_irqrestore(&tmr->lock, flags); @@ -392,7 +392,7 @@ static int seq_timer_start(struct snd_seq_timer *tmr) return -EINVAL; snd_timer_start(tmr->timeri, tmr->ticks); tmr->running = 1; - do_gettimeofday(&tmr->last_update); + ktime_get_ts64(&tmr->last_update); return 0; } @@ -420,7 +420,7 @@ static int seq_timer_continue(struct snd_seq_timer *tmr) } snd_timer_start(tmr->timeri, tmr->ticks); tmr->running = 1; - do_gettimeofday(&tmr->last_update); + ktime_get_ts64(&tmr->last_update); return 0; } @@ -444,17 +444,12 @@ snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr) spin_lock_irqsave(&tmr->lock, flags); cur_time = tmr->cur_time; if (tmr->running) { - struct timeval tm; - int usec; - do_gettimeofday(&tm); - usec = (int)(tm.tv_usec - tmr->last_update.tv_usec); - if (usec < 0) { - cur_time.tv_nsec += (1000000 + usec) * 1000; - cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec - 1; - } else { - cur_time.tv_nsec += usec * 1000; - cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec; - } + struct timespec64 tm; + + ktime_get_ts64(&tm); + tm = timespec64_sub(tm, tmr->last_update); + cur_time.tv_nsec = tm.tv_nsec; + cur_time.tv_sec = tm.tv_sec; snd_seq_sanity_real_time(&cur_time); } spin_unlock_irqrestore(&tmr->lock, flags); diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h index 88dfb7180..9506b661f 100644 --- a/sound/core/seq/seq_timer.h +++ b/sound/core/seq/seq_timer.h @@ -52,7 +52,7 @@ struct snd_seq_timer { unsigned int skew; unsigned int skew_base; - struct timeval last_update; /* time of last clock update, used for interpolation */ + struct timespec64 last_update; /* time of last clock update, used for interpolation */ spinlock_t lock; }; diff --git a/sound/hda/hdmi_chmap.c b/sound/hda/hdmi_chmap.c index c6c75e7e0..81acc20c2 100644 --- a/sound/hda/hdmi_chmap.c +++ b/sound/hda/hdmi_chmap.c @@ -353,7 +353,8 @@ static void hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap, int hdmi_slot = 0; /* fill actual channel mappings in ALSA channel (i) order */ for (i = 0; i < ch_alloc->channels; i++) { - while (!ch_alloc->speakers[7 - hdmi_slot] && !WARN_ON(hdmi_slot >= 8)) + while (!WARN_ON(hdmi_slot >= 8) && + !ch_alloc->speakers[7 - hdmi_slot]) hdmi_slot++; /* skip zero slots */ hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++; @@ -430,6 +431,12 @@ static int to_cea_slot(int ordered_ca, unsigned char pos) int mask = snd_hdac_chmap_to_spk_mask(pos); int i; + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (ordered_ca >= ARRAY_SIZE(channel_allocations)) + return -1; + if (mask) { for (i = 0; i < 8; i++) { if (channel_allocations[ordered_ca].speakers[7 - i] == mask) @@ -456,7 +463,15 @@ EXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap); /* from CEA slot to ALSA API channel position */ static int from_cea_slot(int ordered_ca, unsigned char slot) { - int mask = channel_allocations[ordered_ca].speakers[7 - slot]; + int mask; + + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (slot >= 8) + return 0; + + mask = channel_allocations[ordered_ca].speakers[7 - slot]; return snd_hdac_spk_to_chmap(mask); } @@ -523,7 +538,8 @@ static void hdmi_setup_fake_chmap(unsigned char *map, int ca) int ordered_ca = get_channel_allocation_order(ca); for (i = 0; i < 8; i++) { - if (i < channel_allocations[ordered_ca].channels) + if (ordered_ca < ARRAY_SIZE(channel_allocations) && + i < channel_allocations[ordered_ca].channels) map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f); else map[i] = 0; @@ -551,6 +567,12 @@ int snd_hdac_get_active_channels(int ca) { int ordered_ca = get_channel_allocation_order(ca); + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (ordered_ca >= ARRAY_SIZE(channel_allocations)) + ordered_ca = 0; + return channel_allocations[ordered_ca].channels; } EXPORT_SYMBOL_GPL(snd_hdac_get_active_channels); diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index 5a4cf3fab..d53c9bb36 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -121,7 +121,7 @@ int snd_ak4114_create(struct snd_card *card, __fail: snd_ak4114_free(chip); - return err < 0 ? err : -EIO; + return err; } EXPORT_SYMBOL(snd_ak4114_create); diff --git a/sound/i2c/other/ak4117.c b/sound/i2c/other/ak4117.c index 48848909a..0702f0552 100644 --- a/sound/i2c/other/ak4117.c +++ b/sound/i2c/other/ak4117.c @@ -110,7 +110,7 @@ int snd_ak4117_create(struct snd_card *card, ak4117_read_t *read, ak4117_write_t __fail: snd_ak4117_free(chip); - return err < 0 ? err : -EIO; + return err; } void snd_ak4117_reg_write(struct ak4117 *chip, unsigned char reg, unsigned char mask, unsigned char val) diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c index f159da4ec..a302d1f8d 100644 --- a/sound/isa/ad1848/ad1848.c +++ b/sound/isa/ad1848/ad1848.c @@ -170,15 +170,4 @@ static struct isa_driver snd_ad1848_driver = { } }; -static int __init alsa_card_ad1848_init(void) -{ - return isa_register_driver(&snd_ad1848_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_ad1848_exit(void) -{ - isa_unregister_driver(&snd_ad1848_driver); -} - -module_init(alsa_card_ad1848_init); -module_exit(alsa_card_ad1848_exit); +module_isa_driver(snd_ad1848_driver, SNDRV_CARDS); diff --git a/sound/isa/adlib.c b/sound/isa/adlib.c index 120c524bb..8d3060fd7 100644 --- a/sound/isa/adlib.c +++ b/sound/isa/adlib.c @@ -112,15 +112,4 @@ static struct isa_driver snd_adlib_driver = { } }; -static int __init alsa_card_adlib_init(void) -{ - return isa_register_driver(&snd_adlib_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_adlib_exit(void) -{ - isa_unregister_driver(&snd_adlib_driver); -} - -module_init(alsa_card_adlib_init); -module_exit(alsa_card_adlib_exit); +module_isa_driver(snd_adlib_driver, SNDRV_CARDS); diff --git a/sound/isa/cmi8328.c b/sound/isa/cmi8328.c index 2c89d95da..787475084 100644 --- a/sound/isa/cmi8328.c +++ b/sound/isa/cmi8328.c @@ -469,15 +469,4 @@ static struct isa_driver snd_cmi8328_driver = { }, }; -static int __init alsa_card_cmi8328_init(void) -{ - return isa_register_driver(&snd_cmi8328_driver, CMI8328_MAX); -} - -static void __exit alsa_card_cmi8328_exit(void) -{ - isa_unregister_driver(&snd_cmi8328_driver); -} - -module_init(alsa_card_cmi8328_init) -module_exit(alsa_card_cmi8328_exit) +module_isa_driver(snd_cmi8328_driver, CMI8328_MAX); diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c index 282cd75d2..ef7448e9f 100644 --- a/sound/isa/cs423x/cs4231.c +++ b/sound/isa/cs423x/cs4231.c @@ -186,15 +186,4 @@ static struct isa_driver snd_cs4231_driver = { } }; -static int __init alsa_card_cs4231_init(void) -{ - return isa_register_driver(&snd_cs4231_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_cs4231_exit(void) -{ - isa_unregister_driver(&snd_cs4231_driver); -} - -module_init(alsa_card_cs4231_init); -module_exit(alsa_card_cs4231_exit); +module_isa_driver(snd_cs4231_driver, SNDRV_CARDS); diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c index 322788478..379abe2cb 100644 --- a/sound/isa/galaxy/galaxy.c +++ b/sound/isa/galaxy/galaxy.c @@ -634,15 +634,4 @@ static struct isa_driver snd_galaxy_driver = { } }; -static int __init alsa_card_galaxy_init(void) -{ - return isa_register_driver(&snd_galaxy_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_galaxy_exit(void) -{ - isa_unregister_driver(&snd_galaxy_driver); -} - -module_init(alsa_card_galaxy_init); -module_exit(alsa_card_galaxy_exit); +module_isa_driver(snd_galaxy_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c index f0019715d..c169be49e 100644 --- a/sound/isa/gus/gusclassic.c +++ b/sound/isa/gus/gusclassic.c @@ -229,15 +229,4 @@ static struct isa_driver snd_gusclassic_driver = { } }; -static int __init alsa_card_gusclassic_init(void) -{ - return isa_register_driver(&snd_gusclassic_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_gusclassic_exit(void) -{ - isa_unregister_driver(&snd_gusclassic_driver); -} - -module_init(alsa_card_gusclassic_init); -module_exit(alsa_card_gusclassic_exit); +module_isa_driver(snd_gusclassic_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c index 693d95f46..77ac2fd72 100644 --- a/sound/isa/gus/gusextreme.c +++ b/sound/isa/gus/gusextreme.c @@ -358,15 +358,4 @@ static struct isa_driver snd_gusextreme_driver = { } }; -static int __init alsa_card_gusextreme_init(void) -{ - return isa_register_driver(&snd_gusextreme_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_gusextreme_exit(void) -{ - isa_unregister_driver(&snd_gusextreme_driver); -} - -module_init(alsa_card_gusextreme_init); -module_exit(alsa_card_gusextreme_exit); +module_isa_driver(snd_gusextreme_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c index 8216e8d8f..dd88c9d33 100644 --- a/sound/isa/gus/gusmax.c +++ b/sound/isa/gus/gusmax.c @@ -370,15 +370,4 @@ static struct isa_driver snd_gusmax_driver = { }, }; -static int __init alsa_card_gusmax_init(void) -{ - return isa_register_driver(&snd_gusmax_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_gusmax_exit(void) -{ - isa_unregister_driver(&snd_gusmax_driver); -} - -module_init(alsa_card_gusmax_init) -module_exit(alsa_card_gusmax_exit) +module_isa_driver(snd_gusmax_driver, SNDRV_CARDS); diff --git a/sound/isa/sb/jazz16.c b/sound/isa/sb/jazz16.c index 6b4884d05..4d909971e 100644 --- a/sound/isa/sb/jazz16.c +++ b/sound/isa/sb/jazz16.c @@ -387,15 +387,4 @@ static struct isa_driver snd_jazz16_driver = { }, }; -static int __init alsa_card_jazz16_init(void) -{ - return isa_register_driver(&snd_jazz16_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_jazz16_exit(void) -{ - isa_unregister_driver(&snd_jazz16_driver); -} - -module_init(alsa_card_jazz16_init) -module_exit(alsa_card_jazz16_exit) +module_isa_driver(snd_jazz16_driver, SNDRV_CARDS); diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c index b8e2391c3..ad42d2364 100644 --- a/sound/isa/sb/sb8.c +++ b/sound/isa/sb/sb8.c @@ -251,15 +251,4 @@ static struct isa_driver snd_sb8_driver = { }, }; -static int __init alsa_card_sb8_init(void) -{ - return isa_register_driver(&snd_sb8_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_sb8_exit(void) -{ - isa_unregister_driver(&snd_sb8_driver); -} - -module_init(alsa_card_sb8_init) -module_exit(alsa_card_sb8_exit) +module_isa_driver(snd_sb8_driver, SNDRV_CARDS); diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 51cfa7615..b61a6633d 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -711,15 +711,4 @@ static struct isa_driver snd_sc6000_driver = { }; -static int __init alsa_card_sc6000_init(void) -{ - return isa_register_driver(&snd_sc6000_driver, SNDRV_CARDS); -} - -static void __exit alsa_card_sc6000_exit(void) -{ - isa_unregister_driver(&snd_sc6000_driver); -} - -module_init(alsa_card_sc6000_init) -module_exit(alsa_card_sc6000_exit) +module_isa_driver(snd_sc6000_driver, SNDRV_CARDS); diff --git a/sound/oss/ad1848.c b/sound/oss/ad1848.c index 10c8de1f8..6368e5c7d 100644 --- a/sound/oss/ad1848.c +++ b/sound/oss/ad1848.c @@ -254,7 +254,7 @@ static void ad_write(ad1848_info * devc, int reg, int data) static void wait_for_calibration(ad1848_info * devc) { - int timeout = 0; + int timeout; /* * Wait until the auto calibration process has finished. diff --git a/sound/oss/aedsp16.c b/sound/oss/aedsp16.c index 35b5912cf..bb477d5c8 100644 --- a/sound/oss/aedsp16.c +++ b/sound/oss/aedsp16.c @@ -482,13 +482,13 @@ static struct orVals orDMA[] __initdata = { }; static struct aedsp16_info ae_config = { - DEF_AEDSP16_IOB, - DEF_AEDSP16_IRQ, - DEF_AEDSP16_MRQ, - DEF_AEDSP16_DMA, - -1, - -1, - INIT_NONE + .base_io = DEF_AEDSP16_IOB, + .irq = DEF_AEDSP16_IRQ, + .mpu_irq = DEF_AEDSP16_MRQ, + .dma = DEF_AEDSP16_DMA, + .mss_base = -1, + .mpu_base = -1, + .init = INIT_NONE }; /* diff --git a/sound/oss/sound_firmware.h b/sound/oss/sound_firmware.h index 0a0cbfdfb..da4c67e00 100644 --- a/sound/oss/sound_firmware.h +++ b/sound/oss/sound_firmware.h @@ -1,2 +1,29 @@ -extern int mod_firmware_load(const char *fn, char **fp); +#include <linux/fs.h> +/** + * mod_firmware_load - load sound driver firmware + * @fn: filename + * @fp: return for the buffer. + * + * Load the firmware for a sound module (up to 128K) into a buffer. + * The buffer is returned in *fp. It is allocated with vmalloc so is + * virtually linear and not DMAable. The caller should free it with + * vfree when finished. + * + * The length of the buffer is returned on a successful load, the + * value zero on a failure. + * + * Caution: This API is not recommended. Firmware should be loaded via + * request_firmware. + */ +static inline int mod_firmware_load(const char *fn, char **fp) +{ + loff_t size; + int err; + + err = kernel_read_file_from_path((char *)fn, (void **)fp, &size, + 131072, READING_FIRMWARE); + if (err < 0) + return 0; + return size; +} diff --git a/sound/oss/sound_timer.c b/sound/oss/sound_timer.c index 8021c85f0..3a444a6f1 100644 --- a/sound/oss/sound_timer.c +++ b/sound/oss/sound_timer.c @@ -17,7 +17,7 @@ #include "sound_config.h" static volatile int initialized, opened, tmr_running; -static volatile time_t tmr_offs, tmr_ctr; +static volatile unsigned int tmr_offs, tmr_ctr; static volatile unsigned long ticks_offs; static volatile int curr_tempo, curr_timebase; static volatile unsigned long curr_ticks; diff --git a/sound/oss/sys_timer.c b/sound/oss/sys_timer.c index 2226dda0e..d17019d25 100644 --- a/sound/oss/sys_timer.c +++ b/sound/oss/sys_timer.c @@ -19,7 +19,7 @@ #include "sound_config.h" static volatile int opened, tmr_running; -static volatile time_t tmr_offs, tmr_ctr; +static volatile unsigned int tmr_offs, tmr_ctr; static volatile unsigned long ticks_offs; static volatile int curr_tempo, curr_timebase; static volatile unsigned long curr_ticks; diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 36470af7e..92b819e4f 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -1408,6 +1408,7 @@ snd_ali_playback_pointer(struct snd_pcm_substream *substream) spin_unlock(&codec->reg_lock); dev_dbg(codec->card->dev, "playback pointer returned cso=%xh.\n", cso); + cso %= runtime->buffer_size; return cso; } @@ -1428,6 +1429,7 @@ static snd_pcm_uframes_t snd_ali_pointer(struct snd_pcm_substream *substream) cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2)); spin_unlock(&codec->reg_lock); + cso %= runtime->buffer_size; return cso; } diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c index 9dc2950e1..6414ecf93 100644 --- a/sound/pci/ctxfi/cthw20k2.c +++ b/sound/pci/ctxfi/cthw20k2.c @@ -1615,23 +1615,23 @@ static int hw_dac_init(struct hw *hw, const struct dac_conf *info) int i; struct regs_cs4382 cs_read = {0}; struct regs_cs4382 cs_def = { - 0x00000001, /* Mode Control 1 */ - 0x00000000, /* Mode Control 2 */ - 0x00000084, /* Mode Control 3 */ - 0x00000000, /* Filter Control */ - 0x00000000, /* Invert Control */ - 0x00000024, /* Mixing Control Pair 1 */ - 0x00000000, /* Vol Control A1 */ - 0x00000000, /* Vol Control B1 */ - 0x00000024, /* Mixing Control Pair 2 */ - 0x00000000, /* Vol Control A2 */ - 0x00000000, /* Vol Control B2 */ - 0x00000024, /* Mixing Control Pair 3 */ - 0x00000000, /* Vol Control A3 */ - 0x00000000, /* Vol Control B3 */ - 0x00000024, /* Mixing Control Pair 4 */ - 0x00000000, /* Vol Control A4 */ - 0x00000000 /* Vol Control B4 */ + .mode_control_1 = 0x00000001, /* Mode Control 1 */ + .mode_control_2 = 0x00000000, /* Mode Control 2 */ + .mode_control_3 = 0x00000084, /* Mode Control 3 */ + .filter_control = 0x00000000, /* Filter Control */ + .invert_control = 0x00000000, /* Invert Control */ + .mix_control_P1 = 0x00000024, /* Mixing Control Pair 1 */ + .vol_control_A1 = 0x00000000, /* Vol Control A1 */ + .vol_control_B1 = 0x00000000, /* Vol Control B1 */ + .mix_control_P2 = 0x00000024, /* Mixing Control Pair 2 */ + .vol_control_A2 = 0x00000000, /* Vol Control A2 */ + .vol_control_B2 = 0x00000000, /* Vol Control B2 */ + .mix_control_P3 = 0x00000024, /* Mixing Control Pair 3 */ + .vol_control_A3 = 0x00000000, /* Vol Control A3 */ + .vol_control_B3 = 0x00000000, /* Vol Control B3 */ + .mix_control_P4 = 0x00000024, /* Mixing Control Pair 4 */ + .vol_control_A4 = 0x00000000, /* Vol Control A4 */ + .vol_control_B4 = 0x00000000 /* Vol Control B4 */ }; if (hw->model == CTSB1270) { diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 53e748ba1..9c72b744d 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1272,11 +1272,11 @@ static int snd_echo_mixer_info(struct snd_kcontrol *kcontrol, chip = snd_kcontrol_chip(kcontrol); uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 1; uinfo->value.integer.min = ECHOGAIN_MINOUT; uinfo->value.integer.max = ECHOGAIN_MAXOUT; uinfo->dimen.d[0] = num_busses_out(chip); uinfo->dimen.d[1] = num_busses_in(chip); + uinfo->count = uinfo->dimen.d[0] * uinfo->dimen.d[1]; return 0; } @@ -1344,11 +1344,11 @@ static int snd_echo_vmixer_info(struct snd_kcontrol *kcontrol, chip = snd_kcontrol_chip(kcontrol); uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 1; uinfo->value.integer.min = ECHOGAIN_MINOUT; uinfo->value.integer.max = ECHOGAIN_MAXOUT; uinfo->dimen.d[0] = num_busses_out(chip); uinfo->dimen.d[1] = num_pipes_out(chip); + uinfo->count = uinfo->dimen.d[0] * uinfo->dimen.d[1]; return 0; } @@ -1728,7 +1728,6 @@ static int snd_echo_vumeters_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 96; uinfo->value.integer.min = ECHOGAIN_MINOUT; uinfo->value.integer.max = 0; #ifdef ECHOCARD_HAS_VMIXER @@ -1738,6 +1737,7 @@ static int snd_echo_vumeters_info(struct snd_kcontrol *kcontrol, #endif uinfo->dimen.d[1] = 16; /* 16 channels */ uinfo->dimen.d[2] = 2; /* 0=level, 1=peak */ + uinfo->count = uinfo->dimen.d[0] * uinfo->dimen.d[1] * uinfo->dimen.d[2]; return 0; } diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 83741887f..9913be853 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -3584,6 +3584,12 @@ static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid, bool reset; spdif = snd_hda_spdif_out_of_nid(codec, nid); + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (WARN_ON(spdif == NULL)) + return; + curr_fmt = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_STREAM_FORMAT, 0); reset = codec->spdif_status_reset && @@ -3768,7 +3774,7 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, spdif = snd_hda_spdif_out_of_nid(codec, mout->dig_out_nid); if (mout->dig_out_nid && mout->share_spdif && mout->dig_out_used != HDA_DIG_EXCLUSIVE) { - if (chs == 2 && + if (chs == 2 && spdif != NULL && snd_hda_is_supported_format(codec, mout->dig_out_nid, format) && !(spdif->status & IEC958_AES0_NONAUDIO)) { diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 79c7b340a..e7c8f4f07 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -2492,10 +2492,6 @@ static int create_loopback_mixing_ctl(struct hda_codec *codec) if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum)) return -ENOMEM; spec->have_aamix_ctl = 1; - /* if no explicit aamix path is present (e.g. for Realtek codecs), - * enable aamix as default -- just for compatibility - */ - spec->aamix_mode = !has_aamix_out_paths(spec); return 0; } diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 56fefbd85..ed62748a6 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -261,6 +261,7 @@ enum { CXT_FIXUP_HP_530, CXT_FIXUP_CAP_MIX_AMP_5047, CXT_FIXUP_MUTE_LED_EAPD, + CXT_FIXUP_HP_SPECTRE, }; /* for hda_fixup_thinkpad_acpi() */ @@ -765,6 +766,14 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_mute_led_eapd, }, + [CXT_FIXUP_HP_SPECTRE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* enable NID 0x1d for the speaker on top */ + { 0x1d, 0x91170111 }, + { } + } + }, }; static const struct snd_pci_quirk cxt5045_fixups[] = { @@ -814,6 +823,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), + SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index d0d5ad8be..56e5204ac 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1680,6 +1680,11 @@ static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) mutex_lock(&codec->spdif_mutex); spdif = snd_hda_spdif_out_of_nid(codec, cvt_nid); + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (WARN_ON(spdif == NULL)) + return true; non_pcm = !!(spdif->status & IEC958_AES0_NONAUDIO); mutex_unlock(&codec->spdif_mutex); return non_pcm; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index eaee626ab..bd481ac23 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -3718,6 +3718,9 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) case 0x10ec0295: alc_process_coef_fw(codec, coef0225); break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; } codec_dbg(codec, "Headset jack set to unplugged mode.\n"); } @@ -3805,6 +3808,9 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, alc_process_coef_fw(codec, coef0293); snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 0, 1<<14); + /* fallthru */ case 0x10ec0662: snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); @@ -3899,6 +3905,9 @@ static void alc_headset_mode_default(struct hda_codec *codec) case 0x10ec0668: alc_process_coef_fw(codec, coef0688); break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; } codec_dbg(codec, "Headset jack set to headphone (default) mode.\n"); } @@ -3989,6 +3998,9 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) case 0x10ec0295: alc_process_coef_fw(codec, coef0225); break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; } codec_dbg(codec, "Headset jack set to iPhone-style headset mode.\n"); } @@ -4166,6 +4178,9 @@ static void alc_determine_headset_type(struct hda_codec *codec) val = alc_read_coef_idx(codec, 0x46); is_ctia = (val & 0x00f0) == 0x00f0; break; + case 0x10ec0867: + is_ctia = true; + break; } codec_dbg(codec, "Headset jack detected iPhone-style headset: %s\n", @@ -4813,7 +4828,7 @@ enum { ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, ALC292_FIXUP_TPT440_DOCK, ALC292_FIXUP_TPT440, - ALC283_FIXUP_BXBT2807_MIC, + ALC283_FIXUP_HEADSET_MIC, ALC255_FIXUP_DELL_WMI_MIC_MUTE_LED, ALC282_FIXUP_ASPIRE_V5_PINS, ALC280_FIXUP_HP_GPIO4, @@ -5307,7 +5322,7 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC292_FIXUP_TPT440_DOCK, }, - [ALC283_FIXUP_BXBT2807_MIC] = { + [ALC283_FIXUP_HEADSET_MIC] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { { 0x19, 0x04a110f0 }, @@ -5647,7 +5662,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), SND_PCI_QUIRK(0x144d, 0xc109, "Samsung Ativ book 9 (NP900X3G)", ALC269_FIXUP_INV_DMIC), - SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_BXBT2807_MIC), + SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE), @@ -5790,6 +5806,13 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {0x14, 0x90170110}, \ {0x15, 0x0221401f} +#define ALC295_STANDARD_PINS \ + {0x12, 0xb7a60130}, \ + {0x14, 0x90170110}, \ + {0x17, 0x21014020}, \ + {0x18, 0x21a19030}, \ + {0x21, 0x04211020} + #define ALC298_STANDARD_PINS \ {0x12, 0x90a60130}, \ {0x21, 0x03211020} @@ -5830,6 +5853,10 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x14, 0x90170120}, {0x21, 0x02211030}), SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x02011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, {0x14, 0x90170130}, {0x1b, 0x01014020}, {0x21, 0x0221103f}), @@ -5895,6 +5922,10 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x14, 0x90170120}, {0x21, 0x02211030}), SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0xb7a60130}, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC256_STANDARD_PINS), SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4, {0x12, 0x90a60130}, @@ -6005,6 +6036,8 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, ALC292_STANDARD_PINS, {0x13, 0x90a60140}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_STANDARD_PINS), SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, ALC298_STANDARD_PINS, {0x17, 0x90170110}), @@ -6575,6 +6608,9 @@ enum { ALC668_FIXUP_DELL_XPS13, ALC662_FIXUP_ASUS_Nx50, ALC668_FIXUP_ASUS_Nx51, + ALC891_FIXUP_HEADSET_MODE, + ALC891_FIXUP_DELL_MIC_NO_PRESENCE, + ALC662_FIXUP_ACER_VERITON, }; static const struct hda_fixup alc662_fixups[] = { @@ -6830,6 +6866,27 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_BASS_CHMAP, }, + [ALC891_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC891_FIXUP_DELL_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC891_FIXUP_HEADSET_MODE + }, + [ALC662_FIXUP_ACER_VERITON] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x50170120 }, /* no internal speaker */ + { } + } + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -6868,6 +6925,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x19da, 0xa130, "Zotac Z68", ALC662_FIXUP_ZOTAC_Z68), + SND_PCI_QUIRK(0x1b0a, 0x01b8, "ACER Veriton", ALC662_FIXUP_ACER_VERITON), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), #if 0 @@ -6946,6 +7004,11 @@ static const struct hda_model_fixup alc662_fixup_models[] = { }; static const struct snd_hda_pin_quirk alc662_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0867, 0x1028, "Dell", ALC891_FIXUP_DELL_MIC_NO_PRESENCE, + {0x17, 0x02211010}, + {0x18, 0x01a19030}, + {0x1a, 0x01813040}, + {0x21, 0x01014020}), SND_HDA_PIN_QUIRK(0x10ec0662, 0x1028, "Dell", ALC662_FIXUP_DELL_MIC_NO_PRESENCE, {0x14, 0x01014010}, {0x18, 0x01a19020}, @@ -7134,7 +7197,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = { HDA_CODEC_ENTRY(0x10ec0700, "ALC700", patch_alc269), HDA_CODEC_ENTRY(0x10ec0701, "ALC701", patch_alc269), HDA_CODEC_ENTRY(0x10ec0703, "ALC703", patch_alc269), - HDA_CODEC_ENTRY(0x10ec0867, "ALC891", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0867, "ALC891", patch_alc662), HDA_CODEC_ENTRY(0x10ec0880, "ALC880", patch_alc880), HDA_CODEC_ENTRY(0x10ec0882, "ALC882", patch_alc882), HDA_CODEC_ENTRY(0x10ec0883, "ALC883", patch_alc882), diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c index 58fd79eba..51e53497f 100644 --- a/sound/pci/mixart/mixart_mixer.c +++ b/sound/pci/mixart/mixart_mixer.c @@ -965,7 +965,7 @@ static int mixart_update_monitoring(struct snd_mixart* chip, int channel) int err; struct mixart_msg request; struct mixart_set_out_audio_level audio_level; - u32 resp; + u32 resp = 0; if(chip->pipe_out_ana.status == PIPE_UNDEFINED) return -EINVAL; /* no pipe defined */ diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 27f490de5..381a7ef92 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -1496,7 +1496,7 @@ static int snd_riptide_prepare(struct snd_pcm_substream *substream) f = PAGE_SIZE; while ((size + (f >> 1) - 1) <= (f << 7) && (f << 1) > period) f = f >> 1; - pages = (size + f - 1) / f; + pages = DIV_ROUND_UP(size, f); data->size = size; data->pages = pages; snd_printdd diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c index 09da7b52b..1468e4b7b 100644 --- a/sound/ppc/awacs.c +++ b/sound/ppc/awacs.c @@ -991,6 +991,7 @@ snd_pmac_awacs_init(struct snd_pmac *chip) if (err < 0) return err; } + master_vol = NULL; if (pm7500) err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers_pmac7500), diff --git a/sound/sh/aica.c b/sound/sh/aica.c index cad3ee974..7d63617d2 100644 --- a/sound/sh/aica.c +++ b/sound/sh/aica.c @@ -63,9 +63,6 @@ MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); module_param(enable, bool, 0644); MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); -/* Use workqueue */ -static struct workqueue_struct *aica_queue; - /* Simple platform device */ static struct platform_device *pd; static struct resource aica_memory_space[2] = { @@ -327,7 +324,7 @@ static void aica_period_elapsed(unsigned long timer_var) dreamcastcard->current_period = play_period; if (unlikely(dreamcastcard->dma_check == 0)) dreamcastcard->dma_check = 1; - queue_work(aica_queue, &(dreamcastcard->spu_dma_work)); + schedule_work(&(dreamcastcard->spu_dma_work)); } static void spu_begin_dma(struct snd_pcm_substream *substream) @@ -337,7 +334,7 @@ static void spu_begin_dma(struct snd_pcm_substream *substream) runtime = substream->runtime; dreamcastcard = substream->pcm->private_data; /*get the queue to do the work */ - queue_work(aica_queue, &(dreamcastcard->spu_dma_work)); + schedule_work(&(dreamcastcard->spu_dma_work)); /* Timer may already be running */ if (unlikely(dreamcastcard->timer.data)) { mod_timer(&dreamcastcard->timer, jiffies + 4); @@ -381,7 +378,7 @@ static int snd_aicapcm_pcm_close(struct snd_pcm_substream *substream) { struct snd_card_aica *dreamcastcard = substream->pcm->private_data; - flush_workqueue(aica_queue); + flush_work(&(dreamcastcard->spu_dma_work)); if (dreamcastcard->timer.data) del_timer(&dreamcastcard->timer); kfree(dreamcastcard->channel); @@ -633,9 +630,6 @@ static int snd_aica_probe(struct platform_device *devptr) if (unlikely(err < 0)) goto freedreamcast; platform_set_drvdata(devptr, dreamcastcard); - aica_queue = create_workqueue(CARD_NAME); - if (unlikely(!aica_queue)) - goto freedreamcast; snd_printk ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n"); return 0; @@ -671,10 +665,6 @@ static int __init aica_init(void) static void __exit aica_exit(void) { - /* Destroy the aica kernel thread * - * being extra cautious to check if it exists*/ - if (likely(aica_queue)) - destroy_workqueue(aica_queue); platform_device_unregister(pd); platform_driver_unregister(&snd_aica_driver); /* Kill any sound still playing and reset ARM7 to safe state */ diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 06e099e80..22aec9a1e 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -10,6 +10,7 @@ if SND_ATMEL_SOC config SND_ATMEL_SOC_PDC tristate + depends on HAS_DMA default m if SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=m default y if SND_ATMEL_SOC_SSC_PDC=y || (SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=y) diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c index 6107de9c5..6d9b8b44e 100644 --- a/sound/soc/atmel/atmel-classd.c +++ b/sound/soc/atmel/atmel-classd.c @@ -593,11 +593,6 @@ static int atmel_classd_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "no memory resource\n"); - return -ENXIO; - } - io_base = devm_ioremap_resource(dev, res); if (IS_ERR(io_base)) { ret = PTR_ERR(io_base); diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c index aee4787a0..5f56da60c 100644 --- a/sound/soc/atmel/atmel-pdmic.c +++ b/sound/soc/atmel/atmel-pdmic.c @@ -624,11 +624,6 @@ static int atmel_pdmic_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "no memory resource\n"); - return -ENXIO; - } - io_base = devm_ioremap_resource(dev, res); if (IS_ERR(io_base)) { ret = PTR_ERR(io_base); diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 633d54ca7..16e459aed 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -322,7 +322,7 @@ static int atmel_ssc_startup(struct snd_pcm_substream *substream, return ret; } - dma_params = &ssc_dma_params[dai->id][dir]; + dma_params = &ssc_dma_params[pdev->id][dir]; dma_params->ssc = ssc_p->ssc; dma_params->substream = substream; diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 6a834e109..d528aacea 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -7,3 +7,12 @@ config SND_BCM2835_SOC_I2S Say Y or M if you want to add support for codecs attached to the BCM2835 I2S interface. You will also need to select the audio interfaces to support below. + +config SND_SOC_CYGNUS + tristate "SoC platform audio for Broadcom Cygnus chips" + depends on ARCH_BCM_CYGNUS || COMPILE_TEST + help + Say Y if you want to add support for ASoC audio on Broadcom + Cygnus chips (bcm958300, bcm958305, bcm911360) + + If you don't know what to do here, say N.
\ No newline at end of file diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index bc816b71e..fc739d007 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -3,3 +3,8 @@ snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o +# CYGNUS Platform Support +snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o + +obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o + diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c new file mode 100644 index 000000000..d616e0964 --- /dev/null +++ b/sound/soc/bcm/cygnus-pcm.c @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "cygnus-ssp.h" + +/* Register offset needed for ASoC PCM module */ + +#define INTH_R5F_STATUS_OFFSET 0x040 +#define INTH_R5F_CLEAR_OFFSET 0x048 +#define INTH_R5F_MASK_SET_OFFSET 0x050 +#define INTH_R5F_MASK_CLEAR_OFFSET 0x054 + +#define BF_REARM_FREE_MARK_OFFSET 0x344 +#define BF_REARM_FULL_MARK_OFFSET 0x348 + +/* Ring Buffer Ctrl Regs --- Start */ +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */ +#define SRC_RBUF_0_RDADDR_OFFSET 0x500 +#define SRC_RBUF_1_RDADDR_OFFSET 0x518 +#define SRC_RBUF_2_RDADDR_OFFSET 0x530 +#define SRC_RBUF_3_RDADDR_OFFSET 0x548 +#define SRC_RBUF_4_RDADDR_OFFSET 0x560 +#define SRC_RBUF_5_RDADDR_OFFSET 0x578 +#define SRC_RBUF_6_RDADDR_OFFSET 0x590 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */ +#define SRC_RBUF_0_WRADDR_OFFSET 0x504 +#define SRC_RBUF_1_WRADDR_OFFSET 0x51c +#define SRC_RBUF_2_WRADDR_OFFSET 0x534 +#define SRC_RBUF_3_WRADDR_OFFSET 0x54c +#define SRC_RBUF_4_WRADDR_OFFSET 0x564 +#define SRC_RBUF_5_WRADDR_OFFSET 0x57c +#define SRC_RBUF_6_WRADDR_OFFSET 0x594 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */ +#define SRC_RBUF_0_BASEADDR_OFFSET 0x508 +#define SRC_RBUF_1_BASEADDR_OFFSET 0x520 +#define SRC_RBUF_2_BASEADDR_OFFSET 0x538 +#define SRC_RBUF_3_BASEADDR_OFFSET 0x550 +#define SRC_RBUF_4_BASEADDR_OFFSET 0x568 +#define SRC_RBUF_5_BASEADDR_OFFSET 0x580 +#define SRC_RBUF_6_BASEADDR_OFFSET 0x598 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */ +#define SRC_RBUF_0_ENDADDR_OFFSET 0x50c +#define SRC_RBUF_1_ENDADDR_OFFSET 0x524 +#define SRC_RBUF_2_ENDADDR_OFFSET 0x53c +#define SRC_RBUF_3_ENDADDR_OFFSET 0x554 +#define SRC_RBUF_4_ENDADDR_OFFSET 0x56c +#define SRC_RBUF_5_ENDADDR_OFFSET 0x584 +#define SRC_RBUF_6_ENDADDR_OFFSET 0x59c + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */ +#define SRC_RBUF_0_FREE_MARK_OFFSET 0x510 +#define SRC_RBUF_1_FREE_MARK_OFFSET 0x528 +#define SRC_RBUF_2_FREE_MARK_OFFSET 0x540 +#define SRC_RBUF_3_FREE_MARK_OFFSET 0x558 +#define SRC_RBUF_4_FREE_MARK_OFFSET 0x570 +#define SRC_RBUF_5_FREE_MARK_OFFSET 0x588 +#define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */ +#define DST_RBUF_0_RDADDR_OFFSET 0x5c0 +#define DST_RBUF_1_RDADDR_OFFSET 0x5d8 +#define DST_RBUF_2_RDADDR_OFFSET 0x5f0 +#define DST_RBUF_3_RDADDR_OFFSET 0x608 +#define DST_RBUF_4_RDADDR_OFFSET 0x620 +#define DST_RBUF_5_RDADDR_OFFSET 0x638 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */ +#define DST_RBUF_0_WRADDR_OFFSET 0x5c4 +#define DST_RBUF_1_WRADDR_OFFSET 0x5dc +#define DST_RBUF_2_WRADDR_OFFSET 0x5f4 +#define DST_RBUF_3_WRADDR_OFFSET 0x60c +#define DST_RBUF_4_WRADDR_OFFSET 0x624 +#define DST_RBUF_5_WRADDR_OFFSET 0x63c + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */ +#define DST_RBUF_0_BASEADDR_OFFSET 0x5c8 +#define DST_RBUF_1_BASEADDR_OFFSET 0x5e0 +#define DST_RBUF_2_BASEADDR_OFFSET 0x5f8 +#define DST_RBUF_3_BASEADDR_OFFSET 0x610 +#define DST_RBUF_4_BASEADDR_OFFSET 0x628 +#define DST_RBUF_5_BASEADDR_OFFSET 0x640 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */ +#define DST_RBUF_0_ENDADDR_OFFSET 0x5cc +#define DST_RBUF_1_ENDADDR_OFFSET 0x5e4 +#define DST_RBUF_2_ENDADDR_OFFSET 0x5fc +#define DST_RBUF_3_ENDADDR_OFFSET 0x614 +#define DST_RBUF_4_ENDADDR_OFFSET 0x62c +#define DST_RBUF_5_ENDADDR_OFFSET 0x644 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */ +#define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0 +#define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8 +#define DST_RBUF_2_FULL_MARK_OFFSET 0x600 +#define DST_RBUF_3_FULL_MARK_OFFSET 0x618 +#define DST_RBUF_4_FULL_MARK_OFFSET 0x630 +#define DST_RBUF_5_FULL_MARK_OFFSET 0x648 +/* Ring Buffer Ctrl Regs --- End */ + +/* Error Status Regs --- Start */ +/* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */ +#define ESR0_STATUS_OFFSET 0x900 +#define ESR1_STATUS_OFFSET 0x918 +#define ESR2_STATUS_OFFSET 0x930 +#define ESR3_STATUS_OFFSET 0x948 +#define ESR4_STATUS_OFFSET 0x960 + +/* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */ +#define ESR0_STATUS_CLR_OFFSET 0x908 +#define ESR1_STATUS_CLR_OFFSET 0x920 +#define ESR2_STATUS_CLR_OFFSET 0x938 +#define ESR3_STATUS_CLR_OFFSET 0x950 +#define ESR4_STATUS_CLR_OFFSET 0x968 + +/* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */ +#define ESR0_MASK_STATUS_OFFSET 0x90c +#define ESR1_MASK_STATUS_OFFSET 0x924 +#define ESR2_MASK_STATUS_OFFSET 0x93c +#define ESR3_MASK_STATUS_OFFSET 0x954 +#define ESR4_MASK_STATUS_OFFSET 0x96c + +/* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */ +#define ESR0_MASK_SET_OFFSET 0x910 +#define ESR1_MASK_SET_OFFSET 0x928 +#define ESR2_MASK_SET_OFFSET 0x940 +#define ESR3_MASK_SET_OFFSET 0x958 +#define ESR4_MASK_SET_OFFSET 0x970 + +/* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */ +#define ESR0_MASK_CLR_OFFSET 0x914 +#define ESR1_MASK_CLR_OFFSET 0x92c +#define ESR2_MASK_CLR_OFFSET 0x944 +#define ESR3_MASK_CLR_OFFSET 0x95c +#define ESR4_MASK_CLR_OFFSET 0x974 +/* Error Status Regs --- End */ + +#define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */ +#define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */ +#define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */ +#define R5F_ESR3_SHIFT 3 /* esr3 = freemark */ +#define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */ + + +/* Mask for R5F register. Set all relevant interrupt for playback handler */ +#define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \ + BIT(R5F_ESR1_SHIFT) | \ + BIT(R5F_ESR3_SHIFT)) + +/* Mask for R5F register. Set all relevant interrupt for capture handler */ +#define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT)) + +/* + * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick. + * This number should be a multiple of 256. Minimum value is 256 + */ +#define PERIOD_BYTES_MIN 0x100 + +static const struct snd_pcm_hardware cygnus_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + /* A period is basically an interrupt */ + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = 0x10000, + + /* period_min/max gives range of approx interrupts per buffer */ + .periods_min = 2, + .periods_max = 8, + + /* + * maximum buffer size in bytes = period_bytes_max * periods_max + * We allocate this amount of data for each enabled channel + */ + .buffer_bytes_max = 4 * 0x8000, +}; + +static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32); + +static struct cygnus_aio_port *cygnus_dai_get_dma_data( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + + return snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); +} + +static void ringbuf_set_initial(void __iomem *audio_io, + struct ringbuf_regs *p_rbuf, + bool is_playback, + u32 start, + u32 periodsize, + u32 bufsize) +{ + u32 initial_rd; + u32 initial_wr; + u32 end; + u32 fmark_val; /* free or full mark */ + + p_rbuf->period_bytes = periodsize; + p_rbuf->buf_size = bufsize; + + if (is_playback) { + /* Set the pointers to indicate full (flip uppermost bit) */ + initial_rd = start; + initial_wr = initial_rd ^ BIT(31); + } else { + /* Set the pointers to indicate empty */ + initial_wr = start; + initial_rd = initial_wr; + } + + end = start + bufsize - 1; + + /* + * The interrupt will fire when free/full mark is *exceeded* + * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark + * to be PERIOD_BYTES_MIN less than the period size. + */ + fmark_val = periodsize - PERIOD_BYTES_MIN; + + writel(start, audio_io + p_rbuf->baseaddr); + writel(end, audio_io + p_rbuf->endaddr); + writel(fmark_val, audio_io + p_rbuf->fmark); + writel(initial_rd, audio_io + p_rbuf->rdaddr); + writel(initial_wr, audio_io + p_rbuf->wraddr); +} + +static int configure_ringbuf_regs(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf; + int status = 0; + + aio = cygnus_dai_get_dma_data(substream); + + /* Map the ssp portnum to a set of ring buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + p_rbuf = &aio->play_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_PLAYBACK(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_PLAYBACK(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_PLAYBACK(4); + break; + case 3: /* SPDIF */ + *p_rbuf = RINGBUF_REG_PLAYBACK(6); + break; + default: + status = -EINVAL; + } + } else { + p_rbuf = &aio->capture_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_CAPTURE(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_CAPTURE(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_CAPTURE(4); + break; + default: + status = -EINVAL; + } + } + + return status; +} + +static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_rbuf = &aio->play_rb_regs; + else + p_rbuf = &aio->capture_rb_regs; + + return p_rbuf; +} + +static void enable_intr(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + u32 clear_mask; + + aio = cygnus_dai_get_dma_data(substream); + + /* The port number maps to the bit position to be cleared */ + clear_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Clear interrupt status before enabling them */ + writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET); + /* Unmask the interrupts of the given port*/ + writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET); + + writel(ANY_PLAYBACK_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } else { + writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET); + + writel(ANY_CAPTURE_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } + +} + +static void disable_intr(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + u32 set_mask; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s on port %d\n", __func__, aio->portnum); + + /* The port number maps to the bit position to be set */ + set_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Mask the interrupts of the given port*/ + writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET); + } else { + writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET); + } + +} + +static int cygnus_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + enable_intr(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + disable_intr(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + u32 regval; + + aio = cygnus_dai_get_dma_data(substream); + + p_rbuf = get_ringbuf(substream); + + /* + * If free/full mark interrupt occurs, provide timestamp + * to ALSA and update appropriate idx by period_bytes + */ + snd_pcm_period_elapsed(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Set the ring buffer to full */ + regval = readl(aio->cygaud->audio + p_rbuf->rdaddr); + regval = regval ^ BIT(31); + writel(regval, aio->cygaud->audio + p_rbuf->wraddr); + } else { + /* Set the ring buffer to empty */ + regval = readl(aio->cygaud->audio + p_rbuf->wraddr); + writel(regval, aio->cygaud->audio + p_rbuf->rdaddr); + } +} + +/* + * ESR0/1/3 status Description + * 0x1 I2S0_out port caused interrupt + * 0x2 I2S1_out port caused interrupt + * 0x4 I2S2_out port caused interrupt + * 0x8 SPDIF_out port caused interrupt + */ +static void handle_playback_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status0, esr_status1, esr_status3; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET); + esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET); + esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET); + esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET); + esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET); + esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO underflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the freemark interrupt. + * Log a debug message. The freemark handler below will + * handle getting everything going again. + */ + if ((esrmask & esr_status1) || (esrmask & esr_status0)) { + dev_dbg(cygaud->dev, + "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n", + esr_status0, esr_status1, esr_status3); + } + + /* + * Freemark is hit. This is the normal interrupt. + * In typical operation the read and write regs will be equal + */ + if (esrmask & esr_status3) { + struct snd_pcm_substream *playstr; + + playstr = cygaud->portinfo[port].play_stream; + cygnus_pcm_period_elapsed(playstr); + } + } + + /* Clear ESR interrupt */ + writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET); + writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET); + writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET); + /* Rearm freemark logic by writing 1 to the correct bit */ + writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET); +} + +/* + * ESR2/4 status Description + * 0x1 I2S0_in port caused interrupt + * 0x2 I2S1_in port caused interrupt + * 0x4 I2S2_in port caused interrupt + */ +static void handle_capture_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status2, esr_status4; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET); + esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET); + esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET); + esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO overflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the fullmark interrupt. + * Log a debug message. The fullmark handler below will + * handle getting everything going again. + */ + if (esrmask & esr_status2) + dev_dbg(cygaud->dev, + "Overflow: esr2=0x%x\n", esr_status2); + + if (esrmask & esr_status4) { + struct snd_pcm_substream *capstr; + + capstr = cygaud->portinfo[port].capture_stream; + cygnus_pcm_period_elapsed(capstr); + } + } + + writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET); + writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET); + /* Rearm fullmark logic by writing 1 to the correct bit */ + writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET); +} + +static irqreturn_t cygnus_dma_irq(int irq, void *data) +{ + u32 r5_status; + struct cygnus_audio *cygaud = data; + + /* + * R5 status bits Description + * 0 ESR0 (playback FIFO interrupt) + * 1 ESR1 (playback rbuf interrupt) + * 2 ESR2 (capture rbuf interrupt) + * 3 ESR3 (Freemark play. interrupt) + * 4 ESR4 (Fullmark capt. interrupt) + */ + r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET); + + if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ))) + return IRQ_NONE; + + /* If playback interrupt happened */ + if (ANY_PLAYBACK_IRQ & r5_status) { + handle_playback_irq(cygaud); + writel(ANY_PLAYBACK_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + /* If capture interrupt happened */ + if (ANY_CAPTURE_IRQ & r5_status) { + handle_capture_irq(cygaud); + writel(ANY_CAPTURE_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + return IRQ_HANDLED; +} + +static int cygnus_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret; + + aio = cygnus_dai_get_dma_data(substream); + if (!aio) + return -ENODEV; + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw); + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + /* + * Keep track of which substream belongs to which port. + * This info is needed by snd_pcm_period_elapsed() in irq_handler + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = substream; + else + aio->capture_stream = substream; + + return 0; +} + +static int cygnus_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = NULL; + else + aio->capture_stream = NULL; + + if (!aio->play_stream && !aio->capture_stream) + dev_dbg(rtd->cpu_dai->dev, "freed port %d\n", aio->portnum); + + return 0; +} + +static int cygnus_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret = 0; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return ret; +} + +static int cygnus_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int cygnus_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + unsigned long bufsize, periodsize; + int ret = 0; + bool is_play; + u32 start; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + bufsize = snd_pcm_lib_buffer_bytes(substream); + periodsize = snd_pcm_lib_period_bytes(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s (buf_size %lu) (period_size %lu)\n", + __func__, bufsize, periodsize); + + configure_ringbuf_regs(substream); + + p_rbuf = get_ringbuf(substream); + + start = runtime->dma_addr; + + is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; + + ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start, + periodsize, bufsize); + + return ret; +} + +static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + unsigned int res = 0, cur = 0, base = 0; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + /* + * Get the offset of the current read (for playack) or write + * index (for capture). Report this value back to the asoc framework. + */ + p_rbuf = get_ringbuf(substream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cur = readl(aio->cygaud->audio + p_rbuf->rdaddr); + else + cur = readl(aio->cygaud->audio + p_rbuf->wraddr); + + base = readl(aio->cygaud->audio + p_rbuf->baseaddr); + + /* + * Mask off the MSB of the rdaddr,wraddr and baseaddr + * since MSB is not part of the address + */ + res = (cur & 0x7fffffff) - (base & 0x7fffffff); + + return bytes_to_frames(substream->runtime, res); +} + +static int cygnus_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + + size = cygnus_pcm_hw.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + dev_dbg(rtd->cpu_dai->dev, "%s: size 0x%zx @ %pK\n", + __func__, size, buf->area); + + if (!buf->area) { + dev_err(rtd->cpu_dai->dev, "%s: dma_alloc failed\n", __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + + +static const struct snd_pcm_ops cygnus_pcm_ops = { + .open = cygnus_pcm_open, + .close = cygnus_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = cygnus_pcm_hw_params, + .hw_free = cygnus_pcm_hw_free, + .prepare = cygnus_pcm_prepare, + .trigger = cygnus_pcm_trigger, + .pointer = cygnus_pcm_pointer, +}; + +static void cygnus_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } +} + +static int cygnus_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &cygnus_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + cygnus_dma_free_dma_buffers(pcm); + return ret; + } + } + + return 0; +} + +static struct snd_soc_platform_driver cygnus_soc_platform = { + .ops = &cygnus_pcm_ops, + .pcm_new = cygnus_dma_new, + .pcm_free = cygnus_dma_free_dma_buffers, +}; + +int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud) +{ + int rc = 0; + + dev_dbg(dev, "%s Enter\n", __func__); + + rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq, + IRQF_SHARED, "cygnus-audio", cygaud); + if (rc) { + dev_err(dev, "%s request_irq error %d\n", __func__, rc); + return rc; + } + + rc = snd_soc_register_platform(dev, &cygnus_soc_platform); + if (rc) { + dev_err(dev, "%s failed\n", __func__); + return rc; + } + + return 0; +} + +int cygnus_soc_platform_unregister(struct device *dev) +{ + snd_soc_unregister_platform(dev); + + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC PCM module"); diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c new file mode 100644 index 000000000..e710bb0c5 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.c @@ -0,0 +1,1529 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_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-dai.h> + +#include "cygnus-ssp.h" + +#define DEFAULT_VCO 1354750204 + +#define CYGNUS_TDM_RATE \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define CAPTURE_FCI_ID_BASE 0x180 +#define CYGNUS_SSP_TRISTATE_MASK 0x001fff +#define CYGNUS_PLLCLKSEL_MASK 0xf + +/* Used with stream_on field to indicate which streams are active */ +#define PLAYBACK_STREAM_MASK BIT(0) +#define CAPTURE_STREAM_MASK BIT(1) + +#define I2S_STREAM_CFG_MASK 0xff003ff +#define I2S_CAP_STREAM_CFG_MASK 0xf0 +#define SPDIF_STREAM_CFG_MASK 0x3ff +#define CH_GRP_STEREO 0x1 + +/* Begin register offset defines */ +#define AUD_MISC_SEROUT_OE_REG_BASE 0x01c +#define AUD_MISC_SEROUT_SPDIF_OE 12 +#define AUD_MISC_SEROUT_MCLK_OE 3 +#define AUD_MISC_SEROUT_LRCK_OE 2 +#define AUD_MISC_SEROUT_SCLK_OE 1 +#define AUD_MISC_SEROUT_SDAT_OE 0 + +/* AUD_FMM_BF_CTRL_xxx regs */ +#define BF_DST_CFG0_OFFSET 0x100 +#define BF_DST_CFG1_OFFSET 0x104 +#define BF_DST_CFG2_OFFSET 0x108 + +#define BF_DST_CTRL0_OFFSET 0x130 +#define BF_DST_CTRL1_OFFSET 0x134 +#define BF_DST_CTRL2_OFFSET 0x138 + +#define BF_SRC_CFG0_OFFSET 0x148 +#define BF_SRC_CFG1_OFFSET 0x14c +#define BF_SRC_CFG2_OFFSET 0x150 +#define BF_SRC_CFG3_OFFSET 0x154 + +#define BF_SRC_CTRL0_OFFSET 0x1c0 +#define BF_SRC_CTRL1_OFFSET 0x1c4 +#define BF_SRC_CTRL2_OFFSET 0x1c8 +#define BF_SRC_CTRL3_OFFSET 0x1cc + +#define BF_SRC_GRP0_OFFSET 0x1fc +#define BF_SRC_GRP1_OFFSET 0x200 +#define BF_SRC_GRP2_OFFSET 0x204 +#define BF_SRC_GRP3_OFFSET 0x208 + +#define BF_SRC_GRP_EN_OFFSET 0x320 +#define BF_SRC_GRP_FLOWON_OFFSET 0x324 +#define BF_SRC_GRP_SYNC_DIS_OFFSET 0x328 + +/* AUD_FMM_IOP_OUT_I2S_xxx regs */ +#define OUT_I2S_0_STREAM_CFG_OFFSET 0xa00 +#define OUT_I2S_0_CFG_OFFSET 0xa04 +#define OUT_I2S_0_MCLK_CFG_OFFSET 0xa0c + +#define OUT_I2S_1_STREAM_CFG_OFFSET 0xa40 +#define OUT_I2S_1_CFG_OFFSET 0xa44 +#define OUT_I2S_1_MCLK_CFG_OFFSET 0xa4c + +#define OUT_I2S_2_STREAM_CFG_OFFSET 0xa80 +#define OUT_I2S_2_CFG_OFFSET 0xa84 +#define OUT_I2S_2_MCLK_CFG_OFFSET 0xa8c + +/* AUD_FMM_IOP_OUT_SPDIF_xxx regs */ +#define SPDIF_STREAM_CFG_OFFSET 0xac0 +#define SPDIF_CTRL_OFFSET 0xac4 +#define SPDIF_FORMAT_CFG_OFFSET 0xad8 +#define SPDIF_MCLK_CFG_OFFSET 0xadc + +/* AUD_FMM_IOP_PLL_0_xxx regs */ +#define IOP_PLL_0_MACRO_OFFSET 0xb00 +#define IOP_PLL_0_MDIV_Ch0_OFFSET 0xb14 +#define IOP_PLL_0_MDIV_Ch1_OFFSET 0xb18 +#define IOP_PLL_0_MDIV_Ch2_OFFSET 0xb1c + +#define IOP_PLL_0_ACTIVE_MDIV_Ch0_OFFSET 0xb30 +#define IOP_PLL_0_ACTIVE_MDIV_Ch1_OFFSET 0xb34 +#define IOP_PLL_0_ACTIVE_MDIV_Ch2_OFFSET 0xb38 + +/* AUD_FMM_IOP_xxx regs */ +#define IOP_PLL_0_CONTROL_OFFSET 0xb04 +#define IOP_PLL_0_USER_NDIV_OFFSET 0xb08 +#define IOP_PLL_0_ACTIVE_NDIV_OFFSET 0xb20 +#define IOP_PLL_0_RESET_OFFSET 0xb5c + +/* AUD_FMM_IOP_IN_I2S_xxx regs */ +#define IN_I2S_0_STREAM_CFG_OFFSET 0x00 +#define IN_I2S_0_CFG_OFFSET 0x04 +#define IN_I2S_1_STREAM_CFG_OFFSET 0x40 +#define IN_I2S_1_CFG_OFFSET 0x44 +#define IN_I2S_2_STREAM_CFG_OFFSET 0x80 +#define IN_I2S_2_CFG_OFFSET 0x84 + +/* AUD_FMM_IOP_MISC_xxx regs */ +#define IOP_SW_INIT_LOGIC 0x1c0 + +/* End register offset defines */ + + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_0_REG */ +#define I2S_OUT_MCLKRATE_SHIFT 16 + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_REG */ +#define I2S_OUT_PLLCLKSEL_SHIFT 0 + +/* AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG */ +#define I2S_OUT_STREAM_ENA 31 +#define I2S_OUT_STREAM_CFG_GROUP_ID 20 +#define I2S_OUT_STREAM_CFG_CHANNEL_GROUPING 24 + +/* AUD_FMM_IOP_IN_I2S_x_CAP */ +#define I2S_IN_STREAM_CFG_CAP_ENA 31 +#define I2S_IN_STREAM_CFG_0_GROUP_ID 4 + +/* AUD_FMM_IOP_OUT_I2S_x_I2S_CFG_REG */ +#define I2S_OUT_CFGX_CLK_ENA 0 +#define I2S_OUT_CFGX_DATA_ENABLE 1 +#define I2S_OUT_CFGX_DATA_ALIGNMENT 6 +#define I2S_OUT_CFGX_BITS_PER_SLOT 13 +#define I2S_OUT_CFGX_VALID_SLOT 14 +#define I2S_OUT_CFGX_FSYNC_WIDTH 18 +#define I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32 26 +#define I2S_OUT_CFGX_SLAVE_MODE 30 +#define I2S_OUT_CFGX_TDM_MODE 31 + +/* AUD_FMM_BF_CTRL_SOURCECH_CFGx_REG */ +#define BF_SRC_CFGX_SFIFO_ENA 0 +#define BF_SRC_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_SRC_CFGX_SAMPLE_CH_MODE 2 +#define BF_SRC_CFGX_SFIFO_SZ_DOUBLE 5 +#define BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY 10 +#define BF_SRC_CFGX_BIT_RES 20 +#define BF_SRC_CFGX_PROCESS_SEQ_ID_VALID 31 + +/* AUD_FMM_BF_CTRL_DESTCH_CFGx_REG */ +#define BF_DST_CFGX_CAP_ENA 0 +#define BF_DST_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_DST_CFGX_DFIFO_SZ_DOUBLE 2 +#define BF_DST_CFGX_NOT_PAUSE_WHEN_FULL 11 +#define BF_DST_CFGX_FCI_ID 12 +#define BF_DST_CFGX_CAP_MODE 24 +#define BF_DST_CFGX_PROC_SEQ_ID_VALID 31 + +/* AUD_FMM_IOP_OUT_SPDIF_xxx */ +#define SPDIF_0_OUT_DITHER_ENA 3 +#define SPDIF_0_OUT_STREAM_ENA 31 + +/* AUD_FMM_IOP_PLL_0_USER */ +#define IOP_PLL_0_USER_NDIV_FRAC 10 + +/* AUD_FMM_IOP_PLL_0_ACTIVE */ +#define IOP_PLL_0_ACTIVE_NDIV_FRAC 10 + + +#define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \ + .i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cap_stream_cfg = IN_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cfg = OUT_I2S_ ##num## _CFG_OFFSET, \ + .i2s_cap_cfg = IN_I2S_ ##num## _CFG_OFFSET, \ + .i2s_mclk_cfg = OUT_I2S_ ##num## _MCLK_CFG_OFFSET, \ + .bf_destch_ctrl = BF_DST_CTRL ##num## _OFFSET, \ + .bf_destch_cfg = BF_DST_CFG ##num## _OFFSET, \ + .bf_sourcech_ctrl = BF_SRC_CTRL ##num## _OFFSET, \ + .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET, \ + .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \ +} + +struct pll_macro_entry { + u32 mclk; + u32 pll_ch_num; +}; + +/* + * PLL has 3 output channels (1x, 2x, and 4x). Below are + * the common MCLK frequencies used by audio driver + */ +static const struct pll_macro_entry pll_predef_mclk[] = { + { 4096000, 0}, + { 8192000, 1}, + {16384000, 2}, + + { 5644800, 0}, + {11289600, 1}, + {22579200, 2}, + + { 6144000, 0}, + {12288000, 1}, + {24576000, 2}, + + {12288000, 0}, + {24576000, 1}, + {49152000, 2}, + + {22579200, 0}, + {45158400, 1}, + {90316800, 2}, + + {24576000, 0}, + {49152000, 1}, + {98304000, 2}, +}; + +/* List of valid frame sizes for tdm mode */ +static const int ssp_valid_tdm_framesize[] = {32, 64, 128, 256, 512}; + +/* + * Use this relationship to derive the sampling rate (lrclk) + * lrclk = (mclk) / ((2*mclk_to_sclk_ratio) * (32 * SCLK))). + * + * Use mclk and pll_ch from the table above + * + * Valid SCLK = 0/1/2/4/8/12 + * + * mclk_to_sclk_ratio = number of MCLK per SCLK. Division is twice the + * value programmed in this field. + * Valid mclk_to_sclk_ratio = 1 through to 15 + * + * eg: To set lrclk = 48khz, set mclk = 12288000, mclk_to_sclk_ratio = 2, + * SCLK = 64 + */ +struct _ssp_clk_coeff { + u32 mclk; + u32 sclk_rate; + u32 rate; + u32 mclk_rate; +}; + +static const struct _ssp_clk_coeff ssp_clk_coeff[] = { + { 4096000, 32, 16000, 4}, + { 4096000, 32, 32000, 2}, + { 4096000, 64, 8000, 4}, + { 4096000, 64, 16000, 2}, + { 4096000, 64, 32000, 1}, + { 4096000, 128, 8000, 2}, + { 4096000, 128, 16000, 1}, + { 4096000, 256, 8000, 1}, + + { 6144000, 32, 16000, 6}, + { 6144000, 32, 32000, 3}, + { 6144000, 32, 48000, 2}, + { 6144000, 32, 96000, 1}, + { 6144000, 64, 8000, 6}, + { 6144000, 64, 16000, 3}, + { 6144000, 64, 48000, 1}, + { 6144000, 128, 8000, 3}, + + { 8192000, 32, 32000, 4}, + { 8192000, 64, 16000, 4}, + { 8192000, 64, 32000, 2}, + { 8192000, 128, 8000, 4}, + { 8192000, 128, 16000, 2}, + { 8192000, 128, 32000, 1}, + { 8192000, 256, 8000, 2}, + { 8192000, 256, 16000, 1}, + { 8192000, 512, 8000, 1}, + + {12288000, 32, 32000, 6}, + {12288000, 32, 48000, 4}, + {12288000, 32, 96000, 2}, + {12288000, 32, 192000, 1}, + {12288000, 64, 16000, 6}, + {12288000, 64, 32000, 3}, + {12288000, 64, 48000, 2}, + {12288000, 64, 96000, 1}, + {12288000, 128, 8000, 6}, + {12288000, 128, 16000, 3}, + {12288000, 128, 48000, 1}, + {12288000, 256, 8000, 3}, + + {16384000, 64, 32000, 4}, + {16384000, 128, 16000, 4}, + {16384000, 128, 32000, 2}, + {16384000, 256, 8000, 4}, + {16384000, 256, 16000, 2}, + {16384000, 256, 32000, 1}, + {16384000, 512, 8000, 2}, + {16384000, 512, 16000, 1}, + + {24576000, 32, 96000, 4}, + {24576000, 32, 192000, 2}, + {24576000, 64, 32000, 6}, + {24576000, 64, 48000, 4}, + {24576000, 64, 96000, 2}, + {24576000, 64, 192000, 1}, + {24576000, 128, 16000, 6}, + {24576000, 128, 32000, 3}, + {24576000, 128, 48000, 2}, + {24576000, 256, 8000, 6}, + {24576000, 256, 16000, 3}, + {24576000, 256, 48000, 1}, + {24576000, 512, 8000, 3}, + + {49152000, 32, 192000, 4}, + {49152000, 64, 96000, 4}, + {49152000, 64, 192000, 2}, + {49152000, 128, 32000, 6}, + {49152000, 128, 48000, 4}, + {49152000, 128, 96000, 2}, + {49152000, 128, 192000, 1}, + {49152000, 256, 16000, 6}, + {49152000, 256, 32000, 3}, + {49152000, 256, 48000, 2}, + {49152000, 256, 96000, 1}, + {49152000, 512, 8000, 6}, + {49152000, 512, 16000, 3}, + {49152000, 512, 48000, 1}, + + { 5644800, 32, 22050, 4}, + { 5644800, 32, 44100, 2}, + { 5644800, 32, 88200, 1}, + { 5644800, 64, 11025, 4}, + { 5644800, 64, 22050, 2}, + { 5644800, 64, 44100, 1}, + + {11289600, 32, 44100, 4}, + {11289600, 32, 88200, 2}, + {11289600, 32, 176400, 1}, + {11289600, 64, 22050, 4}, + {11289600, 64, 44100, 2}, + {11289600, 64, 88200, 1}, + {11289600, 128, 11025, 4}, + {11289600, 128, 22050, 2}, + {11289600, 128, 44100, 1}, + + {22579200, 32, 88200, 4}, + {22579200, 32, 176400, 2}, + {22579200, 64, 44100, 4}, + {22579200, 64, 88200, 2}, + {22579200, 64, 176400, 1}, + {22579200, 128, 22050, 4}, + {22579200, 128, 44100, 2}, + {22579200, 128, 88200, 1}, + {22579200, 256, 11025, 4}, + {22579200, 256, 22050, 2}, + {22579200, 256, 44100, 1}, + + {45158400, 32, 176400, 4}, + {45158400, 64, 88200, 4}, + {45158400, 64, 176400, 2}, + {45158400, 128, 44100, 4}, + {45158400, 128, 88200, 2}, + {45158400, 128, 176400, 1}, + {45158400, 256, 22050, 4}, + {45158400, 256, 44100, 2}, + {45158400, 256, 88200, 1}, + {45158400, 512, 11025, 4}, + {45158400, 512, 22050, 2}, + {45158400, 512, 44100, 1}, +}; + +static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai) +{ + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + return &cygaud->portinfo[dai->id]; +} + +static int audio_ssp_init_portregs(struct cygnus_aio_port *aio) +{ + u32 value, fci_id; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~I2S_STREAM_CFG_MASK; + + /* Set Group ID */ + writel(aio->portnum, + aio->cygaud->audio + aio->regs.bf_sourcech_grp); + + /* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */ + value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID; + value |= aio->portnum; /* FCI ID is the port num */ + value |= CH_GRP_STEREO << I2S_OUT_STREAM_CFG_CHANNEL_GROUPING; + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */ + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Configure the AUD_FMM_IOP_IN_I2S_x_CAP_STREAM_CFG_0 reg */ + value = readl(aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + value &= ~I2S_CAP_STREAM_CFG_MASK; + value |= aio->portnum << I2S_IN_STREAM_CFG_0_GROUP_ID; + writel(value, aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_DESTCH_CFGX_REG_BASE reg */ + fci_id = CAPTURE_FCI_ID_BASE + aio->portnum; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_DFIFO_SZ_DOUBLE); + value &= ~BIT(BF_DST_CFGX_NOT_PAUSE_WHEN_FULL); + value |= (fci_id << BF_DST_CFGX_FCI_ID); + value |= BIT(BF_DST_CFGX_PROC_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + /* Enable the transmit pin for this port */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT((aio->portnum * 4) + AUD_MISC_SEROUT_SDAT_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + case PORT_SPDIF: + writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET); + + value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET); + value |= BIT(SPDIF_0_OUT_DITHER_ENA); + writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET); + + /* Enable and set the FCI ID for the SPDIF channel */ + value = readl(aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + value &= ~SPDIF_STREAM_CFG_MASK; + value |= aio->portnum; /* FCI ID is the port num */ + value |= BIT(SPDIF_0_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Enable the spdif output pin */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT(AUD_MISC_SEROUT_SPDIF_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + default: + dev_err(aio->cygaud->dev, "Port not supported\n"); + status = -EINVAL; + } + + return status; +} + +static void audio_ssp_in_enable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + writel(0x1, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on |= CAPTURE_STREAM_MASK; +} + +static void audio_ssp_in_disable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on &= ~CAPTURE_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); +} + +static int audio_ssp_out_enable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value |= BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + aio->streams_on |= PLAYBACK_STREAM_MASK; + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value |= 0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int audio_ssp_out_disable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + aio->streams_on &= ~PLAYBACK_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + /* set group_sync_dis = 1 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* set group_sync_dis = 0 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* IOP SW INIT on OUT_I2S_x */ + value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value &= ~0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk, + struct cygnus_aio_port *aio) +{ + int i = 0, error; + bool found = false; + const struct pll_macro_entry *p_entry; + struct clk *ch_clk; + + for (i = 0; i < ARRAY_SIZE(pll_predef_mclk); i++) { + p_entry = &pll_predef_mclk[i]; + if (p_entry->mclk == mclk) { + found = true; + break; + } + } + if (!found) { + dev_err(cygaud->dev, + "%s No valid mclk freq (%u) found!\n", __func__, mclk); + return -EINVAL; + } + + ch_clk = cygaud->audio_clk[p_entry->pll_ch_num]; + + if ((aio->clk_trace.cap_en) && (!aio->clk_trace.cap_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.cap_clk_en = true; + } + + if ((aio->clk_trace.play_en) && (!aio->clk_trace.play_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.play_clk_en = true; + } + + error = clk_set_rate(ch_clk, mclk); + if (error) { + dev_err(cygaud->dev, "%s Set MCLK rate failed: %d\n", + __func__, error); + return error; + } + + return p_entry->pll_ch_num; +} + +static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio, + struct cygnus_audio *cygaud) +{ + u32 value, i = 0; + u32 mask = 0xf; + u32 sclk; + bool found = false; + const struct _ssp_clk_coeff *p_entry = NULL; + + for (i = 0; i < ARRAY_SIZE(ssp_clk_coeff); i++) { + p_entry = &ssp_clk_coeff[i]; + if ((p_entry->rate == aio->lrclk) && + (p_entry->sclk_rate == aio->bit_per_frame) && + (p_entry->mclk == aio->mclk)) { + found = true; + break; + } + } + if (!found) { + dev_err(aio->cygaud->dev, + "No valid match found in ssp_clk_coeff array\n"); + dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n", + aio->lrclk, aio->bit_per_frame, aio->mclk); + return -EINVAL; + } + + sclk = aio->bit_per_frame; + if (sclk == 512) + sclk = 0; + /* sclks_per_1fs_div = sclk cycles/32 */ + sclk /= 32; + /* Set sclk rate */ + switch (aio->port_type) { + case PORT_TDM: + /* Set number of bitclks per frame */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32); + value |= sclk << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32; + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + dev_dbg(aio->cygaud->dev, + "SCLKS_PER_1FS_DIV32 = 0x%x\n", value); + break; + case PORT_SPDIF: + break; + default: + dev_err(aio->cygaud->dev, "Unknown port type\n"); + return -EINVAL; + } + + /* Set MCLK_RATE ssp port (spdif and ssp are the same) */ + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT); + value |= (p_entry->mclk_rate << I2S_OUT_MCLKRATE_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value); + dev_dbg(aio->cygaud->dev, "bits per frame = %u, mclk = %u Hz, lrclk = %u Hz\n", + aio->bit_per_frame, aio->mclk, aio->lrclk); + return 0; +} + +static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + int rate, bitres; + u32 value; + u32 mask = 0x1f; + int ret = 0; + + dev_dbg(aio->cygaud->dev, "%s port = %d\n", __func__, aio->portnum); + dev_dbg(aio->cygaud->dev, "params_channels %d\n", + params_channels(params)); + dev_dbg(aio->cygaud->dev, "rate %d\n", params_rate(params)); + dev_dbg(aio->cygaud->dev, "format %d\n", params_format(params)); + + rate = params_rate(params); + + switch (aio->mode) { + case CYGNUS_SSPMODE_TDM: + if ((rate == 192000) && (params_channels(params) > 4)) { + dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n", + params_channels(params), rate); + return -EINVAL; + } + break; + case CYGNUS_SSPMODE_I2S: + aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */ + break; + default: + dev_err(aio->cygaud->dev, + "%s port running in unknown mode\n", __func__); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_BUFFER_PAIR_ENABLE); + /* Configure channels as mono or stereo/TDM */ + if (params_channels(params) == 1) + value |= BIT(BF_SRC_CFGX_SAMPLE_CH_MODE); + else + value &= ~BIT(BF_SRC_CFGX_SAMPLE_CH_MODE); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + if (aio->port_type == PORT_SPDIF) { + dev_err(aio->cygaud->dev, + "SPDIF does not support 8bit format\n"); + return -EINVAL; + } + bitres = 8; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + bitres = 16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* 32 bit mode is coded as 0 */ + bitres = 0; + break; + + default: + return -EINVAL; + } + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~(mask << BF_SRC_CFGX_BIT_RES); + value |= (bitres << BF_SRC_CFGX_BIT_RES); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + } else { + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + default: + return -EINVAL; + } + } + + aio->lrclk = rate; + + if (!aio->is_slave) + ret = cygnus_ssp_set_clocks(aio, cygaud); + + return ret; +} + +/* + * This function sets the mclk frequency for pll clock + */ +static int cygnus_ssp_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int sel; + u32 value; + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s Enter port = %d\n", __func__, aio->portnum); + sel = pll_configure_mclk(cygaud, freq, aio); + if (sel < 0) { + dev_err(aio->cygaud->dev, + "%s Setting mclk failed.\n", __func__); + return -EINVAL; + } + + aio->mclk = freq; + + dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel); + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_PLLCLKSEL_SHIFT); + value |= (sel << I2S_OUT_PLLCLKSEL_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + return 0; +} + +static int cygnus_ssp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + snd_soc_dai_set_dma_data(dai, substream, aio); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = true; + else + aio->clk_trace.cap_en = true; + + return 0; +} + +static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = false; + else + aio->clk_trace.cap_en = false; + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (aio->clk_trace.play_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.play_clk_en = false; + } + } else { + if (aio->clk_trace.cap_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.cap_clk_en = false; + } + } + } +} + +/* + * Bit Update Notes + * 31 Yes TDM Mode (1 = TDM, 0 = i2s) + * 30 Yes Slave Mode (1 = Slave, 0 = Master) + * 29:26 No Sclks per frame + * 25:18 Yes FS Width + * 17:14 No Valid Slots + * 13 No Bits (1 = 16 bits, 0 = 32 bits) + * 12:08 No Bits per samp + * 07 Yes Justifcation (1 = LSB, 0 = MSB) + * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay + * 05 Yes SCLK polarity (1 = Rising, 0 = Falling) + * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left) + * 03:02 Yes Reserved - write as zero + * 01 No Data Enable + * 00 No CLK Enable + */ +#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03 + +/* Input cfg is same as output, but the FS width is not a valid field */ +#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000) + +int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if ((len > 0) && (len < 256)) { + aio->fsync_width = len; + return 0; + } else { + return -EINVAL; + } +} + +static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 ssp_curcfg; + u32 ssp_newcfg; + u32 ssp_outcfg; + u32 ssp_incfg; + u32 val; + u32 mask; + + dev_dbg(aio->cygaud->dev, "%s Enter fmt: %x\n", __func__, fmt); + + if (aio->port_type == PORT_SPDIF) + return -EINVAL; + + ssp_newcfg = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + aio->mode = CYGNUS_SSPMODE_I2S; + break; + + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE); + + /* DSP_A = data after FS, DSP_B = data during FS */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + + if ((aio->fsync_width > 0) && (aio->fsync_width < 256)) + ssp_newcfg |= + (aio->fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH); + else + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + + aio->mode = CYGNUS_SSPMODE_TDM; + break; + + default: + return -EINVAL; + } + + /* + * SSP out cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg); + + /* + * SSP in cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + /* + * Configure the word clk and bit clk as output or tristate + * Each port has 4 bits for controlling its pins. + * Shift the mask based upon port number. + */ + mask = BIT(AUD_MISC_SEROUT_LRCK_OE) + | BIT(AUD_MISC_SEROUT_SCLK_OE) + | BIT(AUD_MISC_SEROUT_MCLK_OE); + mask = mask << (aio->portnum * 4); + if (aio->is_slave) + /* Set bit for tri-state */ + val |= mask; + else + /* Clear bit for drive */ + val &= ~mask; + + dev_dbg(aio->cygaud->dev, "%s Set OE bits 0x%x\n", __func__, val); + writel(val, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + return 0; +} + +static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_enable(aio); + else + audio_ssp_in_enable(aio); + cygaud->active_ports++; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_disable(aio); + else + audio_ssp_in_disable(aio); + cygaud->active_ports--; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 value; + int bits_per_slot = 0; /* default to 32-bits per slot */ + int frame_bits; + unsigned int active_slots; + bool found = false; + int i; + + if (tx_mask != rx_mask) { + dev_err(aio->cygaud->dev, + "%s tx_mask must equal rx_mask\n", __func__); + return -EINVAL; + } + + active_slots = hweight32(tx_mask); + + if ((active_slots < 0) || (active_slots > 16)) + return -EINVAL; + + /* Slot value must be even */ + if (active_slots % 2) + return -EINVAL; + + /* We encode 16 slots as 0 in the reg */ + if (active_slots == 16) + active_slots = 0; + + /* Slot Width is either 16 or 32 */ + switch (slot_width) { + case 16: + bits_per_slot = 1; + break; + case 32: + bits_per_slot = 0; + break; + default: + bits_per_slot = 0; + dev_warn(aio->cygaud->dev, + "%s Defaulting Slot Width to 32\n", __func__); + } + + frame_bits = slots * slot_width; + + for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) { + if (ssp_valid_tdm_framesize[i] == frame_bits) { + found = true; + break; + } + } + + if (!found) { + dev_err(aio->cygaud->dev, + "%s In TDM mode, frame bits INVALID (%d)\n", + __func__, frame_bits); + return -EINVAL; + } + + aio->bit_per_frame = frame_bits; + + dev_dbg(aio->cygaud->dev, "%s active_slots %u, bits per frame %d\n", + __func__, active_slots, frame_bits); + + /* Set capture side of ssp port */ + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + /* Set playback side of ssp port */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cygnus_ssp_suspend(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return -EINVAL; + } + + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + if (aio->clk_trace.play_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + + aio->pll_clk_num = val; + } + + return 0; +} + +static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + int error; + + if (!aio->is_slave) { + if (aio->clk_trace.cap_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + if (aio->clk_trace.play_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + } + + return 0; +} +#else +#define cygnus_ssp_suspend NULL +#define cygnus_ssp_resume NULL +#endif + +static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, + .trigger = cygnus_ssp_trigger, + .hw_params = cygnus_ssp_hw_params, + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, +}; + + +#define INIT_CPU_DAI(num) { \ + .name = "cygnus-ssp" #num, \ + .playback = { \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 16, \ + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &cygnus_ssp_dai_ops, \ + .suspend = cygnus_ssp_suspend, \ + .resume = cygnus_ssp_resume, \ +} + +static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = { + INIT_CPU_DAI(0), + INIT_CPU_DAI(1), + INIT_CPU_DAI(2), +}; + +static struct snd_soc_dai_driver cygnus_spdif_dai_info = { + .name = "cygnus-spdif", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &cygnus_ssp_dai_ops, + .suspend = cygnus_ssp_suspend, + .resume = cygnus_ssp_resume, +}; + +static struct snd_soc_dai_driver cygnus_ssp_dai[CYGNUS_MAX_PORTS]; + +static const struct snd_soc_component_driver cygnus_ssp_component = { + .name = "cygnus-audio", +}; + +/* + * Return < 0 if error + * Return 0 if disabled + * Return 1 if enabled and node is parsed successfully + */ +static int parse_ssp_child_node(struct platform_device *pdev, + struct device_node *dn, + struct cygnus_audio *cygaud, + struct snd_soc_dai_driver *p_dai) +{ + struct cygnus_aio_port *aio; + struct cygnus_ssp_regs ssp_regs[3]; + u32 rawval; + int portnum = -1; + enum cygnus_audio_port_type port_type; + + if (of_property_read_u32(dn, "reg", &rawval)) { + dev_err(&pdev->dev, "Missing reg property\n"); + return -EINVAL; + } + + portnum = rawval; + switch (rawval) { + case 0: + ssp_regs[0] = INIT_SSP_REGS(0); + port_type = PORT_TDM; + break; + case 1: + ssp_regs[1] = INIT_SSP_REGS(1); + port_type = PORT_TDM; + break; + case 2: + ssp_regs[2] = INIT_SSP_REGS(2); + port_type = PORT_TDM; + break; + case 3: + port_type = PORT_SPDIF; + break; + default: + dev_err(&pdev->dev, "Bad value for reg %u\n", rawval); + return -EINVAL; + } + + aio = &cygaud->portinfo[portnum]; + aio->cygaud = cygaud; + aio->portnum = portnum; + aio->port_type = port_type; + aio->fsync_width = -1; + + switch (port_type) { + case PORT_TDM: + aio->regs = ssp_regs[portnum]; + *p_dai = cygnus_ssp_dai_info[portnum]; + aio->mode = CYGNUS_SSPMODE_UNKNOWN; + break; + + case PORT_SPDIF: + aio->regs.bf_sourcech_cfg = BF_SRC_CFG3_OFFSET; + aio->regs.bf_sourcech_ctrl = BF_SRC_CTRL3_OFFSET; + aio->regs.i2s_mclk_cfg = SPDIF_MCLK_CFG_OFFSET; + aio->regs.i2s_stream_cfg = SPDIF_STREAM_CFG_OFFSET; + *p_dai = cygnus_spdif_dai_info; + + /* For the purposes of this code SPDIF can be I2S mode */ + aio->mode = CYGNUS_SSPMODE_I2S; + break; + default: + dev_err(&pdev->dev, "Bad value for port_type %d\n", port_type); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "%s portnum = %d\n", __func__, aio->portnum); + aio->streams_on = 0; + aio->cygaud->dev = &pdev->dev; + aio->clk_trace.play_en = false; + aio->clk_trace.cap_en = false; + + audio_ssp_init_portregs(aio); + return 0; +} + +static int audio_clk_init(struct platform_device *pdev, + struct cygnus_audio *cygaud) +{ + int i; + char clk_name[PROP_LEN_MAX]; + + for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) { + snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i); + + cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(cygaud->audio_clk[i])) + return PTR_ERR(cygaud->audio_clk[i]); + } + + return 0; +} + +static int cygnus_ssp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child_node; + struct resource *res = pdev->resource; + struct cygnus_audio *cygaud; + int err = -EINVAL; + int node_count; + int active_port_count; + + cygaud = devm_kzalloc(dev, sizeof(struct cygnus_audio), GFP_KERNEL); + if (!cygaud) + return -ENOMEM; + + dev_set_drvdata(dev, cygaud); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud"); + cygaud->audio = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->audio)) + return PTR_ERR(cygaud->audio); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "i2s_in"); + cygaud->i2s_in = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->i2s_in)) + return PTR_ERR(cygaud->i2s_in); + + /* Tri-state all controlable pins until we know that we need them */ + writel(CYGNUS_SSP_TRISTATE_MASK, + cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + node_count = of_get_child_count(pdev->dev.of_node); + if ((node_count < 1) || (node_count > CYGNUS_MAX_PORTS)) { + dev_err(dev, "child nodes is %d. Must be between 1 and %d\n", + node_count, CYGNUS_MAX_PORTS); + return -EINVAL; + } + + active_port_count = 0; + + for_each_available_child_of_node(pdev->dev.of_node, child_node) { + err = parse_ssp_child_node(pdev, child_node, cygaud, + &cygnus_ssp_dai[active_port_count]); + + /* negative is err, 0 is active and good, 1 is disabled */ + if (err < 0) + return err; + else if (!err) { + dev_dbg(dev, "Activating DAI: %s\n", + cygnus_ssp_dai[active_port_count].name); + active_port_count++; + } + } + + cygaud->dev = dev; + cygaud->active_ports = 0; + + dev_dbg(dev, "Registering %d DAIs\n", active_port_count); + err = snd_soc_register_component(dev, &cygnus_ssp_component, + cygnus_ssp_dai, active_port_count); + if (err) { + dev_err(dev, "snd_soc_register_dai failed\n"); + return err; + } + + cygaud->irq_num = platform_get_irq(pdev, 0); + if (cygaud->irq_num <= 0) { + dev_err(dev, "platform_get_irq failed\n"); + err = cygaud->irq_num; + goto err_irq; + } + + err = audio_clk_init(pdev, cygaud); + if (err) { + dev_err(dev, "audio clock initialization failed\n"); + goto err_irq; + } + + err = cygnus_soc_platform_register(dev, cygaud); + if (err) { + dev_err(dev, "platform reg error %d\n", err); + goto err_irq; + } + + return 0; + +err_irq: + snd_soc_unregister_component(dev); + return err; +} + +static int cygnus_ssp_remove(struct platform_device *pdev) +{ + cygnus_soc_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id cygnus_ssp_of_match[] = { + { .compatible = "brcm,cygnus-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cygnus_ssp_of_match); + +static struct platform_driver cygnus_ssp_driver = { + .probe = cygnus_ssp_probe, + .remove = cygnus_ssp_remove, + .driver = { + .name = "cygnus-ssp", + .of_match_table = cygnus_ssp_of_match, + }, +}; + +module_platform_driver(cygnus_ssp_driver); + +MODULE_ALIAS("platform:cygnus-ssp"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC SSP Interface"); diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h new file mode 100644 index 000000000..33dd34305 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __CYGNUS_SSP_H__ +#define __CYGNUS_SSP_H__ + +#define CYGNUS_TDM_DAI_MAX_SLOTS 16 + +#define CYGNUS_MAX_PLAYBACK_PORTS 4 +#define CYGNUS_MAX_CAPTURE_PORTS 3 +#define CYGNUS_MAX_I2S_PORTS 3 +#define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS +#define CYGNUS_AUIDO_MAX_NUM_CLKS 3 + +#define CYGNUS_SSP_FRAMEBITS_DIV 1 + +#define CYGNUS_SSPMODE_I2S 0 +#define CYGNUS_SSPMODE_TDM 1 +#define CYGNUS_SSPMODE_UNKNOWN -1 + +#define CYGNUS_SSP_CLKSRC_PLL 0 + +/* Max string length of our dt property names */ +#define PROP_LEN_MAX 40 + +struct ringbuf_regs { + unsigned rdaddr; + unsigned wraddr; + unsigned baseaddr; + unsigned endaddr; + unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned period_bytes; + unsigned buf_size; +}; + +#define RINGBUF_REG_PLAYBACK(num) ((struct ringbuf_regs) { \ + .rdaddr = SRC_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = SRC_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = SRC_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = SRC_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = SRC_RBUF_ ##num## _FREE_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +#define RINGBUF_REG_CAPTURE(num) ((struct ringbuf_regs) { \ + .rdaddr = DST_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = DST_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = DST_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = DST_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = DST_RBUF_ ##num## _FULL_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +enum cygnus_audio_port_type { + PORT_TDM, + PORT_SPDIF, +}; + +struct cygnus_ssp_regs { + u32 i2s_stream_cfg; + u32 i2s_cfg; + u32 i2s_cap_stream_cfg; + u32 i2s_cap_cfg; + u32 i2s_mclk_cfg; + + u32 bf_destch_ctrl; + u32 bf_destch_cfg; + u32 bf_sourcech_ctrl; + u32 bf_sourcech_cfg; + u32 bf_sourcech_grp; +}; + +struct cygnus_track_clk { + bool cap_en; + bool play_en; + bool cap_clk_en; + bool play_clk_en; +}; + +struct cygnus_aio_port { + int portnum; + int mode; + bool is_slave; + int streams_on; /* will be 0 if both capture and play are off */ + int fsync_width; + int port_type; + + u32 mclk; + u32 lrclk; + u32 bit_per_frame; + u32 pll_clk_num; + + struct cygnus_audio *cygaud; + struct cygnus_ssp_regs regs; + + struct ringbuf_regs play_rb_regs; + struct ringbuf_regs capture_rb_regs; + + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *capture_stream; + + struct cygnus_track_clk clk_trace; +}; + + +struct cygnus_audio { + struct cygnus_aio_port portinfo[CYGNUS_MAX_PORTS]; + + int irq_num; + void __iomem *audio; + struct device *dev; + void __iomem *i2s_in; + + struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS]; + int active_ports; + unsigned long vco_rate; +}; + +extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai); +extern int cygnus_ssp_add_pll_tweak_controls(struct snd_soc_pcm_runtime *rtd); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +extern int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud); +extern int cygnus_soc_platform_unregister(struct device *dev); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f3fb98f0a..1cd6ab344 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ADAU1977_SPI if SPI_MASTER select SND_SOC_ADAU1977_I2C if I2C select SND_SOC_ADAU1701 if I2C + select SND_SOC_ADAU7002 select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C @@ -46,6 +47,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_BT_SCO select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS35L32 if I2C + select SND_SOC_CS35L33 if I2C select SND_SOC_CS42L51_I2C if I2C select SND_SOC_CS42L52 if I2C && INPUT select SND_SOC_CS42L56 if I2C && INPUT @@ -57,6 +59,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS42XX8_I2C if I2C select SND_SOC_CS4349 if I2C select SND_SOC_CS47L24 if MFD_CS47L24 + select SND_SOC_CS53L30 if I2C select SND_SOC_CX20442 if TTY select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI select SND_SOC_DA7213 if I2C @@ -84,6 +87,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C select SND_SOC_MAX9850 if I2C + select SND_SOC_MAX9860 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX @@ -269,8 +273,12 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_ADAU_UTILS + tristate + config SND_SOC_ADAU1373 tristate + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1701 tristate "Analog Devices ADAU1701 CODEC" @@ -280,6 +288,7 @@ config SND_SOC_ADAU1701 config SND_SOC_ADAU17X1 tristate select SND_SOC_SIGMADSP_REGMAP + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1761 tristate @@ -322,6 +331,9 @@ config SND_SOC_ADAU1977_I2C select SND_SOC_ADAU1977 select REGMAP_I2C +config SND_SOC_ADAU7002 + tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter" + config SND_SOC_ADAV80X tristate @@ -371,7 +383,7 @@ config SND_SOC_ALC5632 tristate config SND_SOC_BT_SCO - tristate + tristate "Dummy BT SCO codec driver" config SND_SOC_CQ0093VC tristate @@ -380,6 +392,10 @@ config SND_SOC_CS35L32 tristate "Cirrus Logic CS35L32 CODEC" depends on I2C +config SND_SOC_CS35L33 + tristate "Cirrus Logic CS35L33 CODEC" + depends on I2C + config SND_SOC_CS42L51 tristate @@ -450,6 +466,11 @@ config SND_SOC_CS4349 config SND_SOC_CS47L24 tristate +# Cirrus Logic Quad-Channel ADC +config SND_SOC_CS53L30 + tristate "Cirrus Logic CS53L30 CODEC" + depends on I2C + config SND_SOC_CX20442 tristate depends on TTY @@ -536,6 +557,10 @@ config SND_SOC_MAX98357A config SND_SOC_MAX98371 tristate +config SND_SOC_MAX98504 + tristate "Maxim MAX98504 speaker amplifier" + depends on I2C + config SND_SOC_MAX9867 tristate @@ -548,6 +573,11 @@ config SND_SOC_MAX98926 config SND_SOC_MAX9850 tristate +config SND_SOC_MAX9860 + tristate "Maxim MAX9860 Mono Audio Voice Codec" + depends on I2C + select REGMAP_I2C + config SND_SOC_PCM1681 tristate "Texas Instruments PCM1681 CODEC" depends on I2C @@ -644,6 +674,9 @@ config SND_SOC_RT298 config SND_SOC_RT5514 tristate +config SND_SOC_RT5514_SPI + tristate + config SND_SOC_RT5616 tristate "Realtek RT5616 CODEC" depends on I2C @@ -969,7 +1002,8 @@ config SND_SOC_WM8983 tristate config SND_SOC_WM8985 - tristate + tristate "Wolfson Microelectronics WM8985 and WM8758 codec driver" + depends on SND_SOC_I2C_AND_SPI config SND_SOC_WM8988 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0f548fd34..58036af2c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -7,6 +7,7 @@ snd-soc-ad193x-spi-objs := ad193x-spi.o snd-soc-ad193x-i2c-objs := ad193x-i2c.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-adau-utils-objs := adau-utils.o snd-soc-adau1373-objs := adau1373.o snd-soc-adau1701-objs := adau1701.o snd-soc-adau17x1-objs := adau17x1.o @@ -19,6 +20,7 @@ snd-soc-adau1781-spi-objs := adau1781-spi.o snd-soc-adau1977-objs := adau1977.o snd-soc-adau1977-spi-objs := adau1977-spi.o snd-soc-adau1977-i2c-objs := adau1977-i2c.o +snd-soc-adau7002-objs := adau7002.o snd-soc-adav80x-objs := adav80x.o snd-soc-adav801-objs := adav801.o snd-soc-adav803-objs := adav803.o @@ -35,6 +37,7 @@ snd-soc-arizona-objs := arizona.o snd-soc-bt-sco-objs := bt-sco.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o +snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o snd-soc-cs42l52-objs := cs42l52.o @@ -49,6 +52,7 @@ snd-soc-cs42xx8-objs := cs42xx8.o snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o snd-soc-cs4349-objs := cs4349.o snd-soc-cs47l24-objs := cs47l24.o +snd-soc-cs53l30-objs := cs53l30.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o snd-soc-da7213-objs := da7213.o @@ -79,6 +83,7 @@ snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o snd-soc-max9850-objs := max9850.o +snd-soc-max9860-objs := max9860.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o snd-soc-nau8825-objs := nau8825.o @@ -100,6 +105,7 @@ snd-soc-rl6347a-objs := rl6347a.o snd-soc-rt286-objs := rt286.o snd-soc-rt298-objs := rt298.o snd-soc-rt5514-objs := rt5514.o +snd-soc-rt5514-spi-objs := rt5514-spi.o snd-soc-rt5616-objs := rt5616.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o @@ -208,6 +214,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o # Amp snd-soc-max9877-objs := max9877.o +snd-soc-max98504-objs := max98504.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-tas2552-objs := tas2552.o @@ -220,6 +227,7 @@ obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o @@ -232,6 +240,7 @@ obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o +obj-$(CONFIG_SND_SOC_ADAU7002) += snd-soc-adau7002.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o @@ -250,6 +259,7 @@ obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o +obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o @@ -264,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o +obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o @@ -293,6 +304,7 @@ obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o +obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o @@ -314,6 +326,7 @@ obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_RT298) += snd-soc-rt298.o obj-$(CONFIG_SND_SOC_RT5514) += snd-soc-rt5514.o +obj-$(CONFIG_SND_SOC_RT5514_SPI) += snd-soc-rt5514-spi.o obj-$(CONFIG_SND_SOC_RT5616) += snd-soc-rt5616.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o @@ -419,4 +432,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o +obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 8b1d0c1a7..2fc89155f 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -2464,45 +2464,20 @@ static int ab8500_codec_probe(struct snd_soc_codec *codec) struct device *dev = codec->dev; struct device_node *np = dev->of_node; struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); - struct ab8500_platform_data *pdata; + struct ab8500_codec_platform_data codec_pdata; struct filter_control *fc; int status; dev_dbg(dev, "%s: Enter.\n", __func__); - /* Setup AB8500 according to board-settings */ - pdata = dev_get_platdata(dev->parent); + ab8500_codec_of_probe(dev, np, &codec_pdata); - if (np) { - if (!pdata) - pdata = devm_kzalloc(dev, - sizeof(struct ab8500_platform_data), - GFP_KERNEL); - - if (pdata && !pdata->codec) - pdata->codec - = devm_kzalloc(dev, - sizeof(struct ab8500_codec_platform_data), - GFP_KERNEL); - - if (!(pdata && pdata->codec)) - return -ENOMEM; - - ab8500_codec_of_probe(dev, np, pdata->codec); - - } else { - if (!(pdata && pdata->codec)) { - dev_err(dev, "No codec platform data or DT found\n"); - return -EINVAL; - } - } - - status = ab8500_audio_setup_mics(codec, &pdata->codec->amics); + status = ab8500_audio_setup_mics(codec, &codec_pdata.amics); if (status < 0) { pr_err("%s: Failed to setup mics (%d)!\n", __func__, status); return status; } - status = ab8500_audio_set_ear_cmv(codec, pdata->codec->ear_cmv); + status = ab8500_audio_set_ear_cmv(codec, codec_pdata.ear_cmv); if (status < 0) { pr_err("%s: Failed to set earpiece CM-voltage (%d)!\n", __func__, status); diff --git a/sound/soc/codecs/adau-utils.c b/sound/soc/codecs/adau-utils.c new file mode 100644 index 000000000..19d6a6f41 --- /dev/null +++ b/sound/soc/codecs/adau-utils.c @@ -0,0 +1,61 @@ +/* + * Shared helper functions for devices from the ADAU family + * + * Copyright 2011-2016 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/gcd.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "adau-utils.h" + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]) +{ + unsigned int r, n, m, i, j; + unsigned int div; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + regs[0] = m >> 8; + regs[1] = m & 0xff; + regs[2] = n >> 8; + regs[3] = n & 0xff; + regs[4] = (r << 3) | (div << 1); + if (m != 0) + regs[4] |= 1; /* Fractional mode */ + + return 0; +} +EXPORT_SYMBOL_GPL(adau_calc_pll_cfg); + +MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/adau-utils.h b/sound/soc/codecs/adau-utils.h new file mode 100644 index 000000000..939b5f377 --- /dev/null +++ b/sound/soc/codecs/adau-utils.h @@ -0,0 +1,7 @@ +#ifndef SOUND_SOC_CODECS_ADAU_PLL_H +#define SOUND_SOC_CODECS_ADAU_PLL_H + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]); + +#endif diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index fe1353a79..1556b360f 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -23,6 +23,7 @@ #include <sound/adau1373.h> #include "adau1373.h" +#include "adau-utils.h" struct adau1373_dai { unsigned int clk_src; @@ -1254,7 +1255,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, { struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int dpll_div = 0; - unsigned int x, r, n, m, i, j, mode; + uint8_t pll_regs[5]; + int ret; switch (pll_id) { case ADAU1373_PLL1: @@ -1295,27 +1297,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, dpll_div++; } - if (freq_out % freq_in != 0) { - /* fout = fin * (r + (n/m)) / x */ - x = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= x; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - x--; - mode = 1; - } else { - /* fout = fin / r */ - r = freq_out / freq_in; - n = 0; - m = 0; - x = 0; - mode = 0; - } - - if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff) + ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs); + if (ret) return -EINVAL; if (dpll_div) { @@ -1330,12 +1313,11 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id), (source << 4) | dpll_div); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), m & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), n & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), - (r << 3) | (x << 1) | mode); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]); /* Set sysclk to pll_rate / 4 */ regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09); diff --git a/sound/soc/codecs/adau1761-i2c.c b/sound/soc/codecs/adau1761-i2c.c index 8de010f75..9e7f257f1 100644 --- a/sound/soc/codecs/adau1761-i2c.c +++ b/sound/soc/codecs/adau1761-i2c.c @@ -31,7 +31,7 @@ static int adau1761_i2c_probe(struct i2c_client *client, static int adau1761_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1761-spi.c b/sound/soc/codecs/adau1761-spi.c index d9171245b..a0b214be7 100644 --- a/sound/soc/codecs/adau1761-spi.c +++ b/sound/soc/codecs/adau1761-spi.c @@ -48,7 +48,7 @@ static int adau1761_spi_probe(struct spi_device *spi) static int adau1761_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-i2c.c b/sound/soc/codecs/adau1781-i2c.c index 06cbca84c..7b9d1802d 100644 --- a/sound/soc/codecs/adau1781-i2c.c +++ b/sound/soc/codecs/adau1781-i2c.c @@ -31,7 +31,7 @@ static int adau1781_i2c_probe(struct i2c_client *client, static int adau1781_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-spi.c b/sound/soc/codecs/adau1781-spi.c index 3d965a01b..9b233544d 100644 --- a/sound/soc/codecs/adau1781-spi.c +++ b/sound/soc/codecs/adau1781-spi.c @@ -48,7 +48,7 @@ static int adau1781_spi_probe(struct spi_device *spi) static int adau1781_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index fcf05b254..439aa3ff1 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -9,6 +9,7 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/clk.h> #include <linux/delay.h> #include <linux/slab.h> #include <sound/core.h> @@ -23,6 +24,7 @@ #include "sigmadsp.h" #include "adau17x1.h" +#include "adau-utils.h" static const char * const adau17x1_capture_mixer_boost_text[] = { "Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3", @@ -302,6 +304,116 @@ bool adau17x1_has_dsp(struct adau *adau) } EXPORT_SYMBOL_GPL(adau17x1_has_dsp); +static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = dai->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs); + if (ret < 0) + return ret; + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + if (ret) + return ret; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + bool is_pll; + bool was_pll; + + switch (clk_id) { + case ADAU17X1_CLK_SRC_MCLK: + is_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL_AUTO: + if (!adau->mclk) + return -EINVAL; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: + is_pll = true; + break; + default: + return -EINVAL; + } + + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_MCLK: + was_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL: + case ADAU17X1_CLK_SRC_PLL_AUTO: + was_pll = true; + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (is_pll != was_pll) { + if (is_pll) { + snd_soc_dapm_add_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau17x1_auto_pll(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct adau *adau = snd_soc_dai_get_drvdata(dai); + unsigned int pll_rate; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + return adau17x1_set_dai_pll(dai, ADAU17X1_PLL, ADAU17X1_PLL_SRC_MCLK, + clk_get_rate(adau->mclk), pll_rate); +} + static int adau17x1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -311,10 +423,19 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, unsigned int freq; int ret; - if (adau->clk_src == ADAU17X1_CLK_SRC_PLL) + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_PLL_AUTO: + ret = adau17x1_auto_pll(dai, params); + if (ret) + return ret; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: freq = adau->pll_freq; - else + break; + default: freq = adau->sysclk; + break; + } if (freq % params_rate(params) != 0) return -EINVAL; @@ -386,93 +507,6 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, ADAU17X1_SERIAL_PORT1_DELAY_MASK, val); } -static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, - int source, unsigned int freq_in, unsigned int freq_out) -{ - struct snd_soc_codec *codec = dai->codec; - struct adau *adau = snd_soc_codec_get_drvdata(codec); - unsigned int r, n, m, i, j; - unsigned int div; - int ret; - - if (freq_in < 8000000 || freq_in > 27000000) - return -EINVAL; - - if (!freq_out) { - r = 0; - n = 0; - m = 0; - div = 0; - } else { - if (freq_out % freq_in != 0) { - div = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= div; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - div--; - } else { - r = freq_out / freq_in; - n = 0; - m = 0; - div = 0; - } - if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) - return -EINVAL; - } - - adau->pll_regs[0] = m >> 8; - adau->pll_regs[1] = m & 0xff; - adau->pll_regs[2] = n >> 8; - adau->pll_regs[3] = n & 0xff; - adau->pll_regs[4] = (r << 3) | (div << 1); - if (m != 0) - adau->pll_regs[4] |= 1; /* Fractional mode */ - - /* The PLL register is 6 bytes long and can only be written at once. */ - ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, - adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); - if (ret) - return ret; - - adau->pll_freq = freq_out; - - return 0; -} - -static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, - int clk_id, unsigned int freq, int dir) -{ - struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); - struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); - - switch (clk_id) { - case ADAU17X1_CLK_SRC_MCLK: - case ADAU17X1_CLK_SRC_PLL: - break; - default: - return -EINVAL; - } - - adau->sysclk = freq; - - if (adau->clk_src != clk_id) { - if (clk_id == ADAU17X1_CLK_SRC_PLL) { - snd_soc_dapm_add_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } else { - snd_soc_dapm_del_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } - } - - adau->clk_src = clk_id; - - return 0; -} - static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { @@ -857,6 +891,10 @@ int adau17x1_add_routes(struct snd_soc_codec *codec) ret = snd_soc_dapm_add_routes(dapm, adau17x1_no_dsp_dapm_routes, ARRAY_SIZE(adau17x1_no_dsp_dapm_routes)); } + + if (adau->clk_src != ADAU17X1_CLK_SRC_MCLK) + snd_soc_dapm_add_routes(dapm, &adau17x1_dapm_pll_route, 1); + return ret; } EXPORT_SYMBOL_GPL(adau17x1_add_routes); @@ -879,6 +917,7 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, const char *firmware_name) { struct adau *adau; + int ret; if (IS_ERR(regmap)) return PTR_ERR(regmap); @@ -887,6 +926,30 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, if (!adau) return -ENOMEM; + adau->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(adau->mclk)) { + if (PTR_ERR(adau->mclk) != -ENOENT) + return PTR_ERR(adau->mclk); + /* Clock is optional (for the driver) */ + adau->mclk = NULL; + } else if (adau->mclk) { + adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO; + + /* + * Any valid PLL output rate will work at this point, use one + * that is likely to be chosen later as well. The register will + * be written when the PLL is powered up for the first time. + */ + ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024, + adau->pll_regs); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(adau->mclk); + if (ret) + return ret; + } + adau->regmap = regmap; adau->switch_mode = switch_mode; adau->type = type; @@ -910,6 +973,16 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, } EXPORT_SYMBOL_GPL(adau17x1_probe); +void adau17x1_remove(struct device *dev) +{ + struct adau *adau = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + if (adau->mclk) + clk_disable_unprepare(adau->mclk); +} +EXPORT_SYMBOL_GPL(adau17x1_remove); + MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code"); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h index 5ae87a084..bf04b7efe 100644 --- a/sound/soc/codecs/adau17x1.h +++ b/sound/soc/codecs/adau17x1.h @@ -22,13 +22,18 @@ enum adau17x1_pll_src { }; enum adau17x1_clk_src { + /* Automatically configure PLL based on the sample rate */ + ADAU17X1_CLK_SRC_PLL_AUTO, ADAU17X1_CLK_SRC_MCLK, ADAU17X1_CLK_SRC_PLL, }; +struct clk; + struct adau { unsigned int sysclk; unsigned int pll_freq; + struct clk *mclk; enum adau17x1_clk_src clk_src; enum adau17x1_type type; @@ -52,6 +57,7 @@ int adau17x1_add_routes(struct snd_soc_codec *codec); int adau17x1_probe(struct device *dev, struct regmap *regmap, enum adau17x1_type type, void (*switch_mode)(struct device *dev), const char *firmware_name); +void adau17x1_remove(struct device *dev); int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, enum adau17x1_micbias_voltage micbias); bool adau17x1_readable_register(struct device *dev, unsigned int reg); diff --git a/sound/soc/codecs/adau7002.c b/sound/soc/codecs/adau7002.c new file mode 100644 index 000000000..9df72c6ad --- /dev/null +++ b/sound/soc/codecs/adau7002.c @@ -0,0 +1,80 @@ +/* + * ADAU7002 Stereo PDM-to-I2S/TDM converter driver + * + * Copyright 2014-2016 Analog Devices + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> + +static const struct snd_soc_dapm_widget adau7002_widgets[] = { + SND_SOC_DAPM_INPUT("PDM_DAT"), + SND_SOC_DAPM_REGULATOR_SUPPLY("IOVDD", 0, 0), +}; + +static const struct snd_soc_dapm_route adau7002_routes[] = { + { "Capture", NULL, "PDM_DAT" }, + { "Capture", NULL, "IOVDD" }, +}; + +static struct snd_soc_dai_driver adau7002_dai = { + .name = "adau7002-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 20, + }, +}; + +static const struct snd_soc_codec_driver adau7002_codec_driver = { + .dapm_widgets = adau7002_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau7002_widgets), + .dapm_routes = adau7002_routes, + .num_dapm_routes = ARRAY_SIZE(adau7002_routes), +}; + +static int adau7002_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &adau7002_codec_driver, + &adau7002_dai, 1); +} + +static int adau7002_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id adau7002_dt_ids[] = { + { .compatible = "adi,adau7002", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau7002_dt_ids); +#endif + +static struct platform_driver adau7002_driver = { + .driver = { + .name = "adau7002", + .of_match_table = of_match_ptr(adau7002_dt_ids), + }, + .probe = adau7002_probe, + .remove = adau7002_remove, +}; +module_platform_driver(adau7002_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ADAU7002 Stereo PDM-to-I2S/TDM Converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index 5013d2ba0..97798d250 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -437,15 +437,25 @@ static struct snd_soc_dai_driver ak4613_dai = { .symmetric_rates = 1, }; -static int ak4613_resume(struct snd_soc_codec *codec) +static int ak4613_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); + return 0; +} + +static int ak4613_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); return regcache_sync(regmap); } static struct snd_soc_codec_driver soc_codec_dev_ak4613 = { + .suspend = ak4613_suspend, .resume = ak4613_resume, .set_bias_level = ak4613_set_bias_level, .controls = ak4613_snd_controls, diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 4d8b9e49e..cc941d66e 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -523,15 +523,23 @@ static struct snd_soc_dai_driver ak4642_dai = { .symmetric_rates = 1, }; -static int ak4642_resume(struct snd_soc_codec *codec) +static int ak4642_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); - regcache_sync(regmap); return 0; } +static int ak4642_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + return 0; +} static int ak4642_probe(struct snd_soc_codec *codec) { struct ak4642_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -544,6 +552,7 @@ static int ak4642_probe(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_ak4642 = { .probe = ak4642_probe, + .suspend = ak4642_suspend, .resume = ak4642_resume, .set_bias_level = ak4642_set_bias_level, .controls = ak4642_snd_controls, diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 664a8c044..ecfdbfcae 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -85,30 +85,9 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w, { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct arizona *arizona = dev_get_drvdata(codec->dev->parent); - struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); - bool manual_ena = false; int val; - switch (arizona->type) { - case WM5102: - switch (arizona->rev) { - case 0: - break; - default: - manual_ena = true; - break; - } - default: - break; - } - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - if (!priv->spk_ena && manual_ena) { - regmap_write_async(arizona->regmap, 0x4f5, 0x25a); - priv->spk_ena_pending = true; - } - break; case SND_SOC_DAPM_POST_PMU: val = snd_soc_read(codec, ARIZONA_INTERRUPT_RAW_STATUS_3); if (val & ARIZONA_SPK_OVERHEAT_STS) { @@ -120,33 +99,12 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w, regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, 1 << w->shift, 1 << w->shift); - - if (priv->spk_ena_pending) { - msleep(75); - regmap_write_async(arizona->regmap, 0x4f5, 0xda); - priv->spk_ena_pending = false; - priv->spk_ena++; - } break; case SND_SOC_DAPM_PRE_PMD: - if (manual_ena) { - priv->spk_ena--; - if (!priv->spk_ena) - regmap_write_async(arizona->regmap, - 0x4f5, 0x25a); - } - regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, 1 << w->shift, 0); break; - case SND_SOC_DAPM_POST_PMD: - if (manual_ena) { - if (!priv->spk_ena) - regmap_write_async(arizona->regmap, - 0x4f5, 0x0da); - } - break; default: break; } @@ -324,6 +282,17 @@ int arizona_init_gpio(struct snd_soc_codec *codec) } EXPORT_SYMBOL_GPL(arizona_init_gpio); +int arizona_init_notifiers(struct snd_soc_codec *codec) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + BLOCKING_INIT_NOTIFIER_HEAD(&arizona->notifier); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_notifiers); + const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = { "None", "Tone Generator 1", @@ -619,7 +588,7 @@ const struct soc_enum arizona_asrc_rate1 = arizona_rate_text, arizona_rate_val); EXPORT_SYMBOL_GPL(arizona_asrc_rate1); -static const char *arizona_vol_ramp_text[] = { +static const char * const arizona_vol_ramp_text[] = { "0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB", "15ms/6dB", "30ms/6dB", }; @@ -648,7 +617,7 @@ SOC_ENUM_SINGLE_DECL(arizona_out_vi_ramp, arizona_vol_ramp_text); EXPORT_SYMBOL_GPL(arizona_out_vi_ramp); -static const char *arizona_lhpf_mode_text[] = { +static const char * const arizona_lhpf_mode_text[] = { "Low-pass", "High-pass" }; @@ -676,7 +645,7 @@ SOC_ENUM_SINGLE_DECL(arizona_lhpf4_mode, arizona_lhpf_mode_text); EXPORT_SYMBOL_GPL(arizona_lhpf4_mode); -static const char *arizona_ng_hold_text[] = { +static const char * const arizona_ng_hold_text[] = { "30ms", "120ms", "250ms", "500ms", }; @@ -810,6 +779,14 @@ const struct soc_enum arizona_output_anc_src[] = { }; EXPORT_SYMBOL_GPL(arizona_output_anc_src); +const struct snd_kcontrol_new arizona_voice_trigger_switch[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 1, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 2, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 3, 1, 0), +}; +EXPORT_SYMBOL_GPL(arizona_voice_trigger_switch); + static void arizona_in_set_vu(struct snd_soc_codec *codec, int ena) { struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -2573,6 +2550,30 @@ int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put); +int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, void *data)) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + nb->notifier_call = notify; + + return blocking_notifier_chain_register(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_register_notifier); + +int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + return blocking_notifier_chain_unregister(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_unregister_notifier); + MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support"); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index ce0531b8c..69da1ef3a 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -63,6 +63,9 @@ #define ARIZONA_DVFS_SR1_RQ 0x001 #define ARIZONA_DVFS_ADSP1_RQ 0x100 +/* Notifier events */ +#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1 + struct arizona; struct wm_adsp; @@ -87,14 +90,15 @@ struct arizona_priv { unsigned int out_down_pending; unsigned int out_down_delay; - unsigned int spk_ena:2; - unsigned int spk_ena_pending:1; - unsigned int dvfs_reqs; struct mutex dvfs_lock; bool dvfs_cached; }; +struct arizona_voice_trigger_info { + int core; +}; + #define ARIZONA_NUM_MIXER_INPUTS 104 extern const unsigned int arizona_mixer_tlv[]; @@ -248,6 +252,8 @@ extern const struct soc_enum arizona_anc_input_src[]; extern const struct soc_enum arizona_anc_ng_enum; extern const struct soc_enum arizona_output_anc_src[]; +extern const struct snd_kcontrol_new arizona_voice_trigger_switch[]; + extern int arizona_in_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); @@ -306,6 +312,7 @@ extern int arizona_set_fll(struct arizona_fll *fll, int source, extern int arizona_init_spk(struct snd_soc_codec *codec); extern int arizona_init_gpio(struct snd_soc_codec *codec); extern int arizona_init_mono(struct snd_soc_codec *codec); +extern int arizona_init_notifiers(struct snd_soc_codec *codec); extern int arizona_free_spk(struct snd_soc_codec *codec); @@ -317,4 +324,13 @@ int arizona_set_output_mode(struct snd_soc_codec *codec, int output, extern bool arizona_input_analog(struct snd_soc_codec *codec, int shift); extern const char *arizona_sample_rate_val_to_name(unsigned int rate_val); + +extern int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, + void *data)); +extern int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb); + #endif diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c index b084ad113..2a8d0ee14 100644 --- a/sound/soc/codecs/bt-sco.c +++ b/sound/soc/codecs/bt-sco.c @@ -25,22 +25,41 @@ static const struct snd_soc_dapm_route bt_sco_routes[] = { { "TX", NULL, "Playback" }, }; -static struct snd_soc_dai_driver bt_sco_dai = { - .name = "bt-sco-pcm", - .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 1, - .rates = SNDRV_PCM_RATE_8000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, +static struct snd_soc_dai_driver bt_sco_dai[] = { + { + .name = "bt-sco-pcm", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, }, + { + .name = "bt-sco-pcm-wb", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } }; static struct snd_soc_codec_driver soc_codec_dev_bt_sco = { @@ -53,7 +72,7 @@ static struct snd_soc_codec_driver soc_codec_dev_bt_sco = { static int bt_sco_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_bt_sco, - &bt_sco_dai, 1); + bt_sco_dai, ARRAY_SIZE(bt_sco_dai)); } static int bt_sco_remove(struct platform_device *pdev) @@ -77,6 +96,7 @@ MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); #if defined(CONFIG_OF) static const struct of_device_id bt_sco_codec_of_match[] = { { .compatible = "delta,dfbmcs320", }, + { .compatible = "linux,bt-sco", }, {}, }; MODULE_DEVICE_TABLE(of, bt_sco_codec_of_match); diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c new file mode 100644 index 000000000..6f9c1addc --- /dev/null +++ b/sound/soc/codecs/cs35l33.c @@ -0,0 +1,1303 @@ +/* + * cs35l33.c -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <paul.handrigan@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <sound/cs35l33.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/machine.h> +#include <linux/of_gpio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> + +#include "cs35l33.h" + +#define CS35L33_BOOT_DELAY 50 + +struct cs35l33_private { + struct snd_soc_codec *codec; + struct cs35l33_pdata pdata; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + bool amp_cal; + int mclk_int; + struct regulator_bulk_data core_supplies[2]; + int num_core_supplies; + bool is_tdm_mode; + bool enable_soft_ramp; +}; + +static const struct reg_default cs35l33_reg[] = { + {CS35L33_PWRCTL1, 0x85}, + {CS35L33_PWRCTL2, 0xFE}, + {CS35L33_CLK_CTL, 0x0C}, + {CS35L33_BST_PEAK_CTL, 0x90}, + {CS35L33_PROTECT_CTL, 0x55}, + {CS35L33_BST_CTL1, 0x00}, + {CS35L33_BST_CTL2, 0x01}, + {CS35L33_ADSP_CTL, 0x00}, + {CS35L33_ADC_CTL, 0xC8}, + {CS35L33_DAC_CTL, 0x14}, + {CS35L33_DIG_VOL_CTL, 0x00}, + {CS35L33_CLASSD_CTL, 0x04}, + {CS35L33_AMP_CTL, 0x90}, + {CS35L33_INT_MASK_1, 0xFF}, + {CS35L33_INT_MASK_2, 0xFF}, + {CS35L33_DIAG_LOCK, 0x00}, + {CS35L33_DIAG_CTRL_1, 0x40}, + {CS35L33_DIAG_CTRL_2, 0x00}, + {CS35L33_HG_MEMLDO_CTL, 0x62}, + {CS35L33_HG_REL_RATE, 0x03}, + {CS35L33_LDO_DEL, 0x12}, + {CS35L33_HG_HEAD, 0x0A}, + {CS35L33_HG_EN, 0x05}, + {CS35L33_TX_VMON, 0x00}, + {CS35L33_TX_IMON, 0x03}, + {CS35L33_TX_VPMON, 0x02}, + {CS35L33_TX_VBSTMON, 0x05}, + {CS35L33_TX_FLAG, 0x06}, + {CS35L33_TX_EN1, 0x00}, + {CS35L33_TX_EN2, 0x00}, + {CS35L33_TX_EN3, 0x00}, + {CS35L33_TX_EN4, 0x00}, + {CS35L33_RX_AUD, 0x40}, + {CS35L33_RX_SPLY, 0x03}, + {CS35L33_RX_ALIVE, 0x04}, + {CS35L33_BST_CTL4, 0x63}, +}; + +static const struct reg_sequence cs35l33_patch[] = { + { 0x00, 0x99, 0 }, + { 0x59, 0x02, 0 }, + { 0x52, 0x30, 0 }, + { 0x39, 0x45, 0 }, + { 0x57, 0x30, 0 }, + { 0x2C, 0x68, 0 }, + { 0x00, 0x00, 0 }, +}; + +static bool cs35l33_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l33_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* these are read only registers */ + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return false; + default: + return true; + } +} + +static bool cs35l33_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_PWRCTL1: + case CS35L33_PWRCTL2: + case CS35L33_CLK_CTL: + case CS35L33_BST_PEAK_CTL: + case CS35L33_PROTECT_CTL: + case CS35L33_BST_CTL1: + case CS35L33_BST_CTL2: + case CS35L33_ADSP_CTL: + case CS35L33_ADC_CTL: + case CS35L33_DAC_CTL: + case CS35L33_DIG_VOL_CTL: + case CS35L33_CLASSD_CTL: + case CS35L33_AMP_CTL: + case CS35L33_INT_MASK_1: + case CS35L33_INT_MASK_2: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_DIAG_LOCK: + case CS35L33_DIAG_CTRL_1: + case CS35L33_DIAG_CTRL_2: + case CS35L33_HG_MEMLDO_CTL: + case CS35L33_HG_REL_RATE: + case CS35L33_LDO_DEL: + case CS35L33_HG_HEAD: + case CS35L33_HG_EN: + case CS35L33_TX_VMON: + case CS35L33_TX_IMON: + case CS35L33_TX_VPMON: + case CS35L33_TX_VBSTMON: + case CS35L33_TX_FLAG: + case CS35L33_TX_EN1: + case CS35L33_TX_EN2: + case CS35L33_TX_EN3: + case CS35L33_TX_EN4: + case CS35L33_RX_AUD: + case CS35L33_RX_SPLY: + case CS35L33_RX_ALIVE: + case CS35L33_BST_CTL4: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 100, 0); +static DECLARE_TLV_DB_SCALE(dac_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l33_snd_controls[] = { + + SOC_SINGLE_TLV("SPK Amp Volume", CS35L33_AMP_CTL, + 4, 0x09, 0, classd_ctl_tlv), + SOC_SINGLE_SX_TLV("DAC Volume", CS35L33_DIG_VOL_CTL, + 0, 0x34, 0xE4, dac_tlv), +}; + +static int cs35l33_spkrdrv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!priv->amp_cal) { + usleep_range(8000, 9000); + priv->amp_cal = true; + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, 0); + dev_dbg(codec->dev, "Amp calibration done\n"); + } + dev_dbg(codec->dev, "Amp turned on\n"); + break; + case SND_SOC_DAPM_POST_PMD: + dev_dbg(codec->dev, "Amp turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + break; + } + + return 0; +} + +static int cs35l33_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, 0); + val = priv->is_tdm_mode ? 0 : CS35L33_PDN_TDM; + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, val); + dev_dbg(codec->dev, "BST turned on\n"); + break; + case SND_SOC_DAPM_POST_PMU: + dev_dbg(codec->dev, "SDIN turned on\n"); + if (!priv->amp_cal) { + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, CS35L33_AMP_CAL); + dev_dbg(codec->dev, "Amp calibration started\n"); + usleep_range(10000, 11000); + } + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, CS35L33_PDN_TDM); + usleep_range(4000, 4100); + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, CS35L33_PDN_BST); + dev_dbg(codec->dev, "BST and SDIN turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + + } + + return 0; +} + +static int cs35l33_sdout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int mask = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + unsigned int mask2 = CS35L33_SDOUT_3ST_TDM; + unsigned int val, val2; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (priv->is_tdm_mode) { + /* set sdout_3st_i2s and reset pdn_tdm */ + val = CS35L33_SDOUT_3ST_I2S; + /* reset sdout_3st_tdm */ + val2 = 0; + } else { + /* reset sdout_3st_i2s and set pdn_tdm */ + val = CS35L33_PDN_TDM; + /* set sdout_3st_tdm */ + val2 = CS35L33_SDOUT_3ST_TDM; + } + dev_dbg(codec->dev, "SDOUT turned on\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + val = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + val2 = CS35L33_SDOUT_3ST_TDM; + dev_dbg(codec->dev, "SDOUT turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + return 0; + } + + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + mask, val); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + mask2, val2); + + return 0; +} + +static const struct snd_soc_dapm_widget cs35l33_dapm_widgets[] = { + + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_OUT_DRV_E("SPKDRV", CS35L33_PWRCTL1, 7, 1, NULL, 0, + cs35l33_spkrdrv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L33_PWRCTL2, + 2, 1, cs35l33_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("MON"), + + SND_SOC_DAPM_ADC("VMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VMON_SHIFT, 1), + SND_SOC_DAPM_ADC("IMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_IMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VPMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VPMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VBSTMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VBSTMON_SHIFT, 1), + + SND_SOC_DAPM_AIF_OUT_E("SDOUT", NULL, 0, SND_SOC_NOPM, 0, 0, + cs35l33_sdout_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l33_audio_map[] = { + {"SDIN", NULL, "CS35L33 Playback"}, + {"SPKDRV", NULL, "SDIN"}, + {"SPK", NULL, "SPKDRV"}, + + {"VMON", NULL, "MON"}, + {"IMON", NULL, "MON"}, + + {"SDOUT", NULL, "VMON"}, + {"SDOUT", NULL, "IMON"}, + {"CS35L33 Capture", NULL, "SDOUT"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vphg_auto_route[] = { + {"SPKDRV", NULL, "VPMON"}, + {"VPMON", NULL, "CS35L33 Playback"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vp_vbst_mon_route[] = { + {"SDOUT", NULL, "VPMON"}, + {"VPMON", NULL, "MON"}, + {"SDOUT", NULL, "VBSTMON"}, + {"VBSTMON", NULL, "MON"}, +}; + +static int cs35l33_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int val; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, 0); + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, CS35L33_PDN_ALL); + regmap_read(priv->regmap, CS35L33_INT_STATUS_2, &val); + usleep_range(1000, 1100); + if (val & CS35L33_PDN_DONE) + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, CS35L33_MCLKDIS); + break; + case SND_SOC_BIAS_OFF: + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l33_mclk_div { + int mclk; + int srate; + u8 adsp_rate; + u8 int_fs_ratio; +}; + +static const struct cs35l33_mclk_div cs35l33_mclk_coeffs[] = { + /* MCLK, Sample Rate, adsp_rate, int_fs_ratio */ + {5644800, 11025, 0x4, CS35L33_INT_FS_RATE}, + {5644800, 22050, 0x8, CS35L33_INT_FS_RATE}, + {5644800, 44100, 0xC, CS35L33_INT_FS_RATE}, + + {6000000, 8000, 0x1, 0}, + {6000000, 11025, 0x2, 0}, + {6000000, 11029, 0x3, 0}, + {6000000, 12000, 0x4, 0}, + {6000000, 16000, 0x5, 0}, + {6000000, 22050, 0x6, 0}, + {6000000, 22059, 0x7, 0}, + {6000000, 24000, 0x8, 0}, + {6000000, 32000, 0x9, 0}, + {6000000, 44100, 0xA, 0}, + {6000000, 44118, 0xB, 0}, + {6000000, 48000, 0xC, 0}, + + {6144000, 8000, 0x1, CS35L33_INT_FS_RATE}, + {6144000, 12000, 0x4, CS35L33_INT_FS_RATE}, + {6144000, 16000, 0x5, CS35L33_INT_FS_RATE}, + {6144000, 24000, 0x8, CS35L33_INT_FS_RATE}, + {6144000, 32000, 0x9, CS35L33_INT_FS_RATE}, + {6144000, 48000, 0xC, CS35L33_INT_FS_RATE}, +}; + +static int cs35l33_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l33_mclk_coeffs); i++) { + if (cs35l33_mclk_coeffs[i].mclk == mclk && + cs35l33_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; +} + +static int cs35l33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, CS35L33_MS_MASK); + dev_dbg(codec->dev, "Audio port in master mode\n"); + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, 0); + dev_dbg(codec->dev, "Audio port in slave mode\n"); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + /* + * tdm mode in cs35l33 resembles dsp-a mode very + * closely, it is dsp-a with fsync shifted left by half bclk + */ + priv->is_tdm_mode = true; + dev_dbg(codec->dev, "Audio port in TDM mode\n"); + break; + case SND_SOC_DAIFMT_I2S: + priv->is_tdm_mode = false; + dev_dbg(codec->dev, "Audio port in I2S mode\n"); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs35l33_pcm_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 cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + int sample_size = params_width(params); + int coeff = cs35l33_get_mclk_coeff(priv->mclk_int, params_rate(params)); + + if (coeff < 0) + return coeff; + + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_ADSP_FS | CS35L33_INT_FS_RATE, + cs35l33_mclk_coeffs[coeff].int_fs_ratio + | cs35l33_mclk_coeffs[coeff].adsp_rate); + + if (priv->is_tdm_mode) { + sample_size = (sample_size / 8) - 1; + if (sample_size > 2) + sample_size = 2; + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_AUDIN_RX_DEPTH, + sample_size << CS35L33_AUDIN_RX_DEPTH_SHIFT); + } + + dev_dbg(codec->dev, "sample rate=%d, bits per sample=%d\n", + params_rate(params), params_width(params)); + + return 0; +} + +static const unsigned int cs35l33_src_rates[] = { + 8000, 11025, 11029, 12000, 16000, 22050, + 22059, 24000, 32000, 44100, 44118, 48000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l33_constraints = { + .count = ARRAY_SIZE(cs35l33_src_rates), + .list = cs35l33_src_rates, +}; + +static int cs35l33_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l33_constraints); + return 0; +} + +static int cs35l33_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + if (tristate) { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, CS35L33_SDOUT_3ST_I2S); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, CS35L33_SDOUT_3ST_TDM); + } else { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, 0); + } + + return 0; +} + +static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg, bit_pos, i; + int slot, slot_num; + + if (slot_width != 8) + return -EINVAL; + + /* scan rx_mask for aud slot */ + slot = ffs(rx_mask) - 1; + if (slot >= 0) { + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "Audio starts from slots %d", slot); + } + + /* + * scan tx_mask: vmon(2 slots); imon (2 slots); + * vpmon (1 slot) vbstmon (1 slot) + */ + slot = ffs(tx_mask) - 1; + slot_num = 0; + + for (i = 0; i < 2 ; i++) { + /* disable vpmon/vbstmon: enable later if set in tx_mask */ + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON + i, + CS35L33_X_STATE | CS35L33_X_LOC, CS35L33_X_STATE + | CS35L33_X_LOC); + } + + /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, + ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); + + while (slot >= 0) { + /* configure VMON_TX_LOC */ + if (slot_num == 0) { + regmap_update_bits(priv->regmap, CS35L33_TX_VMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "VMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure IMON_TX_LOC */ + if (slot_num == 3) { + regmap_update_bits(priv->regmap, CS35L33_TX_IMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "IMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure VPMON_TX_LOC */ + if (slot_num == 4) { + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[0], 2); + dev_dbg(codec->dev, "VPMON enabled in slots %d", slot); + } + + /* configure VBSTMON_TX_LOC */ + if (slot_num == 5) { + regmap_update_bits(priv->regmap, CS35L33_TX_VBSTMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[2], 2); + dev_dbg(codec->dev, + "VBSTMON enabled in slots %d", slot); + } + + /* Enable the relevant tx slot */ + reg = CS35L33_TX_EN4 - (slot/8); + bit_pos = slot - ((slot / 8) * (8)); + regmap_update_bits(priv->regmap, reg, + 1 << bit_pos, 1 << bit_pos); + + tx_mask &= ~(1 << slot); + slot = ffs(tx_mask) - 1; + slot_num++; + } + + return 0; +} + +static int cs35l33_codec_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, int dir) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + + switch (freq) { + case CS35L33_MCLK_5644: + case CS35L33_MCLK_6: + case CS35L33_MCLK_6144: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, 0); + cs35l33->mclk_int = freq; + break; + case CS35L33_MCLK_11289: + case CS35L33_MCLK_12: + case CS35L33_MCLK_12288: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, CS35L33_MCLKDIV2); + cs35l33->mclk_int = freq/2; + break; + default: + cs35l33->mclk_int = 0; + return -EINVAL; + } + + dev_dbg(codec->dev, "external mclk freq=%d, internal mclk freq=%d\n", + freq, cs35l33->mclk_int); + + return 0; +} + +static const struct snd_soc_dai_ops cs35l33_ops = { + .startup = cs35l33_pcm_startup, + .set_tristate = cs35l33_set_tristate, + .set_fmt = cs35l33_set_dai_fmt, + .hw_params = cs35l33_pcm_hw_params, + .set_tdm_slot = cs35l33_set_tdm_slot, +}; + +static struct snd_soc_dai_driver cs35l33_dai = { + .name = "cs35l33-dai", + .id = 0, + .playback = { + .stream_name = "CS35L33 Playback", + .channels_min = 1, + .channels_max = 1, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .capture = { + .stream_name = "CS35L33 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .ops = &cs35l33_ops, + .symmetric_rates = 1, +}; + +static int cs35l33_set_hg_data(struct snd_soc_codec *codec, + struct cs35l33_pdata *pdata) +{ + struct cs35l33_hg *hg_config = &pdata->hg_config; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + if (hg_config->enable_hg_algo) { + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_MEM_DEPTH_MASK, + hg_config->mem_depth << CS35L33_MEM_DEPTH_SHIFT); + regmap_write(priv->regmap, CS35L33_HG_REL_RATE, + hg_config->release_rate); + regmap_update_bits(priv->regmap, CS35L33_HG_HEAD, + CS35L33_HD_RM_MASK, + hg_config->hd_rm << CS35L33_HD_RM_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_THLD_MASK, + hg_config->ldo_thld << CS35L33_LDO_THLD_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_DISABLE_MASK, + hg_config->ldo_path_disable << + CS35L33_LDO_DISABLE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_LDO_ENTRY_DELAY_MASK, + hg_config->ldo_entry_delay << + CS35L33_LDO_ENTRY_DELAY_SHIFT); + if (hg_config->vp_hg_auto) { + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_AUTO_MASK, + CS35L33_VP_HG_AUTO_MASK); + snd_soc_dapm_add_routes(dapm, cs35l33_vphg_auto_route, + ARRAY_SIZE(cs35l33_vphg_auto_route)); + } + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_MASK, + hg_config->vp_hg << CS35L33_VP_HG_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_RATE_MASK, + hg_config->vp_hg_rate << CS35L33_VP_HG_RATE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_VA_MASK, + hg_config->vp_hg_va << CS35L33_VP_HG_VA_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_CLASS_HG_EN_MASK, CS35L33_CLASS_HG_EN_MASK); + } + return 0; +} + +static int cs35l33_set_bst_ipk(struct snd_soc_codec *codec, unsigned int bst) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + int ret = 0, steps = 0; + + /* Boost current in uA */ + if (bst > 3600000 || bst < 1850000) { + dev_err(codec->dev, "Invalid boost current %d\n", bst); + ret = -EINVAL; + goto err; + } + + if (bst % 15625) { + dev_err(codec->dev, "Current not a multiple of 15625uA (%d)\n", + bst); + ret = -EINVAL; + goto err; + } + + while (bst > 1850000) { + bst -= 15625; + steps++; + } + + regmap_write(cs35l33->regmap, CS35L33_BST_PEAK_CTL, + steps+0x70); + +err: + return ret; +} + +static int cs35l33_probe(struct snd_soc_codec *codec) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + + cs35l33->codec = codec; + pm_runtime_get_sync(codec->dev); + + regmap_update_bits(cs35l33->regmap, CS35L33_PROTECT_CTL, + CS35L33_ALIVE_WD_DIS, 0x8); + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL2, + CS35L33_ALIVE_WD_DIS2, + CS35L33_ALIVE_WD_DIS2); + + /* Set Platform Data */ + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL1, + CS35L33_BST_CTL_MASK, cs35l33->pdata.boost_ctl); + regmap_update_bits(cs35l33->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_DRV_SEL_MASK, + cs35l33->pdata.amp_drv_sel << CS35L33_AMP_DRV_SEL_SHIFT); + + if (cs35l33->pdata.boost_ipk) + cs35l33_set_bst_ipk(codec, cs35l33->pdata.boost_ipk); + + if (cs35l33->enable_soft_ramp) { + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DIGSFT, CS35L33_DIGSFT); + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DSR_RATE, cs35l33->pdata.ramp_rate); + } else { + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DIGSFT, 0); + } + + /* update IMON scaling rate if different from default of 0x8 */ + if (cs35l33->pdata.imon_adc_scale != 0x8) + snd_soc_update_bits(codec, CS35L33_ADC_CTL, + CS35L33_IMON_SCALE, cs35l33->pdata.imon_adc_scale); + + cs35l33_set_hg_data(codec, &(cs35l33->pdata)); + + /* + * unmask important interrupts that causes the chip to enter + * speaker safe mode and hence deserves user attention + */ + regmap_update_bits(cs35l33->regmap, CS35L33_INT_MASK_1, + CS35L33_M_OTE | CS35L33_M_OTW | CS35L33_M_AMP_SHORT | + CS35L33_M_CAL_ERR, 0); + + pm_runtime_put_sync(codec->dev); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l33 = { + .probe = cs35l33_probe, + + .set_bias_level = cs35l33_set_bias_level, + .set_sysclk = cs35l33_codec_set_sysclk, + + .dapm_widgets = cs35l33_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l33_dapm_widgets), + .dapm_routes = cs35l33_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l33_audio_map), + .controls = cs35l33_snd_controls, + .num_controls = ARRAY_SIZE(cs35l33_snd_controls), + + .idle_bias_off = true, +}; + +static const struct regmap_config cs35l33_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L33_MAX_REGISTER, + .reg_defaults = cs35l33_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l33_reg), + .volatile_reg = cs35l33_volatile_register, + .readable_reg = cs35l33_readable_register, + .writeable_reg = cs35l33_writeable_register, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int __maybe_unused cs35l33_runtime_resume(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s\n", __func__); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, false); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + + ret = regcache_sync(cs35l33->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register cache\n"); + goto err; + } + + return 0; + +err: + regcache_cache_only(cs35l33->regmap, true); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int __maybe_unused cs35l33_runtime_suspend(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + regcache_cache_only(cs35l33->regmap, true); + regcache_mark_dirty(cs35l33->regmap); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct dev_pm_ops cs35l33_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l33_runtime_suspend, + cs35l33_runtime_resume, + NULL) +}; + +static int cs35l33_get_hg_data(const struct device_node *np, + struct cs35l33_pdata *pdata) +{ + struct device_node *hg; + struct cs35l33_hg *hg_config = &pdata->hg_config; + u32 val32; + + hg = of_get_child_by_name(np, "cirrus,hg-algo"); + hg_config->enable_hg_algo = hg ? true : false; + + if (hg_config->enable_hg_algo) { + if (of_property_read_u32(hg, "cirrus,mem-depth", &val32) >= 0) + hg_config->mem_depth = val32; + if (of_property_read_u32(hg, "cirrus,release-rate", + &val32) >= 0) + hg_config->release_rate = val32; + if (of_property_read_u32(hg, "cirrus,ldo-thld", &val32) >= 0) + hg_config->ldo_thld = val32; + if (of_property_read_u32(hg, "cirrus,ldo-path-disable", + &val32) >= 0) + hg_config->ldo_path_disable = val32; + if (of_property_read_u32(hg, "cirrus,ldo-entry-delay", + &val32) >= 0) + hg_config->ldo_entry_delay = val32; + + hg_config->vp_hg_auto = of_property_read_bool(hg, + "cirrus,vp-hg-auto"); + + if (of_property_read_u32(hg, "cirrus,vp-hg", &val32) >= 0) + hg_config->vp_hg = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-rate", &val32) >= 0) + hg_config->vp_hg_rate = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-va", &val32) >= 0) + hg_config->vp_hg_va = val32; + } + + of_node_put(hg); + + return 0; +} + +static irqreturn_t cs35l33_irq_thread(int irq, void *data) +{ + struct cs35l33_private *cs35l33 = data; + struct snd_soc_codec *codec = cs35l33->codec; + unsigned int sticky_val1, sticky_val2, current_val, mask1, mask2; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_2, + &sticky_val2); + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + &sticky_val1); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_2, &mask2); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_1, &mask1); + + /* Check to see if the unmasked bits are active, + * if not then exit. + */ + if (!(sticky_val1 & ~mask1) && !(sticky_val2 & ~mask2)) + return IRQ_NONE; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + ¤t_val); + + /* handle the interrupts */ + + if (sticky_val1 & CS35L33_AMP_SHORT) { + dev_crit(codec->dev, "Amp short error\n"); + if (!(current_val & CS35L33_AMP_SHORT)) { + dev_dbg(codec->dev, + "Amp short error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, + CS35L33_AMP_SHORT_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_AMP_SHORT_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_CAL_ERR) { + dev_err(codec->dev, "Cal error\n"); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + if (!(current_val & CS35L33_CAL_ERR)) { + dev_dbg(codec->dev, "Cal error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + CS35L33_CAL_ERR_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_OTE) { + dev_crit(codec->dev, "Over temperature error\n"); + if (!(current_val & CS35L33_OTE)) { + dev_dbg(codec->dev, + "Over temperature error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, + CS35L33_OTE_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + } + } + + if (sticky_val1 & CS35L33_OTW) { + dev_err(codec->dev, "Over temperature warning\n"); + if (!(current_val & CS35L33_OTW)) { + dev_dbg(codec->dev, + "Over temperature warning release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, + CS35L33_OTW_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + } + } + if (CS35L33_ALIVE_ERR & sticky_val1) + dev_err(codec->dev, "ERROR: ADSPCLK Interrupt\n"); + + if (CS35L33_MCLK_ERR & sticky_val1) + dev_err(codec->dev, "ERROR: MCLK Interrupt\n"); + + if (CS35L33_VMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: VMON Overflow Interrupt\n"); + + if (CS35L33_IMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: IMON Overflow Interrupt\n"); + + if (CS35L33_VPMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: VPMON Overflow Interrupt\n"); + + return IRQ_HANDLED; +} + +static const char * const cs35l33_core_supplies[] = { + "VA", + "VP", +}; + +static int cs35l33_of_get_pdata(struct device *dev, + struct cs35l33_private *cs35l33) +{ + struct device_node *np = dev->of_node; + struct cs35l33_pdata *pdata = &cs35l33->pdata; + u32 val32; + + if (!np) + return 0; + + if (of_property_read_u32(np, "cirrus,boost-ctl", &val32) >= 0) { + pdata->boost_ctl = val32; + pdata->amp_drv_sel = 1; + } + + if (of_property_read_u32(np, "cirrus,ramp-rate", &val32) >= 0) { + pdata->ramp_rate = val32; + cs35l33->enable_soft_ramp = true; + } + + if (of_property_read_u32(np, "cirrus,boost-ipk", &val32) >= 0) + pdata->boost_ipk = val32; + + if (of_property_read_u32(np, "cirrus,imon-adc-scale", &val32) >= 0) { + if ((val32 == 0x0) || (val32 == 0x7) || (val32 == 0x6)) + pdata->imon_adc_scale = val32; + else + /* use default value */ + pdata->imon_adc_scale = 0x8; + } else { + /* use default value */ + pdata->imon_adc_scale = 0x8; + } + + cs35l33_get_hg_data(np, pdata); + + return 0; +} + +static int cs35l33_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l33_private *cs35l33; + struct cs35l33_pdata *pdata = dev_get_platdata(&i2c_client->dev); + int ret, devid, i; + unsigned int reg; + + cs35l33 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs35l33_private), + GFP_KERNEL); + if (!cs35l33) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l33); + cs35l33->regmap = devm_regmap_init_i2c(i2c_client, &cs35l33_regmap); + if (IS_ERR(cs35l33->regmap)) { + ret = PTR_ERR(cs35l33->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, true); + + for (i = 0; i < ARRAY_SIZE(cs35l33_core_supplies); i++) + cs35l33->core_supplies[i].supply + = cs35l33_core_supplies[i]; + cs35l33->num_core_supplies = ARRAY_SIZE(cs35l33_core_supplies); + + ret = devm_regulator_bulk_get(&i2c_client->dev, + cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request core supplies: %d\n", + ret); + return ret; + } + + if (pdata) { + cs35l33->pdata = *pdata; + } else { + cs35l33_of_get_pdata(&i2c_client->dev, cs35l33); + pdata = &cs35l33->pdata; + } + + ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, + cs35l33_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l33", cs35l33); + if (ret != 0) + dev_warn(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + + /* We could issue !RST or skip it based on AMP topology */ + cs35l33->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset-gpios", GPIOD_OUT_HIGH); + if (IS_ERR(cs35l33->reset_gpio)) { + dev_err(&i2c_client->dev, "%s ERROR: Can't get reset GPIO\n", + __func__); + return PTR_ERR(cs35l33->reset_gpio); + } + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + regcache_cache_only(cs35l33->regmap, false); + + /* initialize codec */ + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L33_CHIP_ID) { + dev_err(&i2c_client->dev, + "CS35L33 Device ID (%X). Expected ID %X\n", + devid, CS35L33_CHIP_ID); + goto err_enable; + } + + ret = regmap_read(cs35l33->regmap, CS35L33_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + goto err_enable; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35L33, Revision: %02X\n", reg & 0xFF); + + ret = regmap_register_patch(cs35l33->regmap, + cs35l33_patch, ARRAY_SIZE(cs35l33_patch)); + if (ret < 0) { + dev_err(&i2c_client->dev, + "Error in applying regmap patch: %d\n", ret); + goto err_enable; + } + + /* disable mclk and tdm */ + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM); + + pm_runtime_set_autosuspend_delay(&i2c_client->dev, 100); + pm_runtime_use_autosuspend(&i2c_client->dev); + pm_runtime_set_active(&i2c_client->dev); + pm_runtime_enable(&i2c_client->dev); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_dev_cs35l33, &cs35l33_dai, 1); + if (ret < 0) { + dev_err(&i2c_client->dev, "%s: Register codec failed\n", + __func__); + goto err_enable; + } + + return 0; + +err_enable: + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int cs35l33_i2c_remove(struct i2c_client *client) +{ + struct cs35l33_private *cs35l33 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + pm_runtime_disable(&client->dev); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct of_device_id cs35l33_of_match[] = { + { .compatible = "cirrus,cs35l33", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l33_of_match); + +static const struct i2c_device_id cs35l33_id[] = { + {"cs35l33", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l33_id); + +static struct i2c_driver cs35l33_i2c_driver = { + .driver = { + .name = "cs35l33", + .pm = &cs35l33_pm_ops, + .of_match_table = cs35l33_of_match, + + }, + .id_table = cs35l33_id, + .probe = cs35l33_i2c_probe, + .remove = cs35l33_i2c_remove, + +}; +module_i2c_driver(cs35l33_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L33 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <paul.handrigan@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l33.h b/sound/soc/codecs/cs35l33.h new file mode 100644 index 000000000..c045737d1 --- /dev/null +++ b/sound/soc/codecs/cs35l33.h @@ -0,0 +1,221 @@ +/* + * cs35l33.h -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <paul.handrigan@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L33_H__ +#define __CS35L33_H__ + +#define CS35L33_CHIP_ID 0x00035A33 +#define CS35L33_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L33_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L33_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L33_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L33_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L33_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L33_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L33_CLK_CTL 0x08 /* Clock Ctl */ +#define CS35L33_BST_PEAK_CTL 0x09 /* Max Current for Boost */ +#define CS35L33_PROTECT_CTL 0x0A /* Amp Protection Parameters */ +#define CS35L33_BST_CTL1 0x0B /* Boost Converter CTL1 */ +#define CS35L33_BST_CTL2 0x0C /* Boost Converter CTL2 */ +#define CS35L33_ADSP_CTL 0x0D /* Serial Port Control */ +#define CS35L33_ADC_CTL 0x0E /* ADC Control */ +#define CS35L33_DAC_CTL 0x0F /* DAC Control */ +#define CS35L33_DIG_VOL_CTL 0x10 /* Digital Volume CTL */ +#define CS35L33_CLASSD_CTL 0x11 /* Class D Amp CTL */ +#define CS35L33_AMP_CTL 0x12 /* Amp Gain/Protecton Release CTL */ +#define CS35L33_INT_MASK_1 0x13 /* Interrupt Mask 1 */ +#define CS35L33_INT_MASK_2 0x14 /* Interrupt Mask 2 */ +#define CS35L33_INT_STATUS_1 0x15 /* Interrupt Status 1 [RO] */ +#define CS35L33_INT_STATUS_2 0x16 /* Interrupt Status 2 [RO] */ +#define CS35L33_DIAG_LOCK 0x17 /* Diagnostic Mode Register Lock */ +#define CS35L33_DIAG_CTRL_1 0x18 /* Diagnostic Mode Register Control */ +#define CS35L33_DIAG_CTRL_2 0x19 /* Diagnostic Mode Register Control 2 */ +#define CS35L33_HG_MEMLDO_CTL 0x23 /* H/G Memory/LDO CTL */ +#define CS35L33_HG_REL_RATE 0x24 /* H/G Release Rate */ +#define CS35L33_LDO_DEL 0x25 /* LDO Entry Delay/VPhg Control 1 */ +#define CS35L33_HG_HEAD 0x29 /* H/G Headroom */ +#define CS35L33_HG_EN 0x2A /* H/G Enable/VPhg CNT2 */ +#define CS35L33_TX_VMON 0x2D /* TDM TX Control 1 (VMON) */ +#define CS35L33_TX_IMON 0x2E /* TDM TX Control 2 (IMON) */ +#define CS35L33_TX_VPMON 0x2F /* TDM TX Control 3 (VPMON) */ +#define CS35L33_TX_VBSTMON 0x30 /* TDM TX Control 4 (VBSTMON) */ +#define CS35L33_TX_FLAG 0x31 /* TDM TX Control 5 (FLAG) */ +#define CS35L33_TX_EN1 0x32 /* TDM TX Enable 1 */ +#define CS35L33_TX_EN2 0x33 /* TDM TX Enable 2 */ +#define CS35L33_TX_EN3 0x34 /* TDM TX Enable 3 */ +#define CS35L33_TX_EN4 0x35 /* TDM TX Enable 4 */ +#define CS35L33_RX_AUD 0x36 /* TDM RX Control 1 */ +#define CS35L33_RX_SPLY 0x37 /* TDM RX Control 2 */ +#define CS35L33_RX_ALIVE 0x38 /* TDM RX Control 3 */ +#define CS35L33_BST_CTL4 0x39 /* Boost Converter Control 4 */ +#define CS35L33_HG_STATUS 0x3F /* H/G Status */ +#define CS35L33_MAX_REGISTER 0x59 + +#define CS35L33_MCLK_5644 5644800 +#define CS35L33_MCLK_6144 6144000 +#define CS35L33_MCLK_6 6000000 +#define CS35L33_MCLK_11289 11289600 +#define CS35L33_MCLK_12 12000000 +#define CS35L33_MCLK_12288 12288000 + +/* CS35L33_PWRCTL1 */ +#define CS35L33_PDN_AMP (1 << 7) +#define CS35L33_PDN_BST (1 << 2) +#define CS35L33_PDN_ALL 1 + +/* CS35L33_PWRCTL2 */ +#define CS35L33_PDN_VMON_SHIFT 7 +#define CS35L33_PDN_VMON (1 << CS35L33_PDN_VMON_SHIFT) +#define CS35L33_PDN_IMON_SHIFT 6 +#define CS35L33_PDN_IMON (1 << CS35L33_PDN_IMON_SHIFT) +#define CS35L33_PDN_VPMON_SHIFT 5 +#define CS35L33_PDN_VPMON (1 << CS35L33_PDN_VPMON_SHIFT) +#define CS35L33_PDN_VBSTMON_SHIFT 4 +#define CS35L33_PDN_VBSTMON (1 << CS35L33_PDN_VBSTMON_SHIFT) +#define CS35L33_SDOUT_3ST_I2S_SHIFT 3 +#define CS35L33_SDOUT_3ST_I2S (1 << CS35L33_SDOUT_3ST_I2S_SHIFT) +#define CS35L33_PDN_SDIN_SHIFT 2 +#define CS35L33_PDN_SDIN (1 << CS35L33_PDN_SDIN_SHIFT) +#define CS35L33_PDN_TDM_SHIFT 1 +#define CS35L33_PDN_TDM (1 << CS35L33_PDN_TDM_SHIFT) + +/* CS35L33_CLK_CTL */ +#define CS35L33_MCLKDIS (1 << 7) +#define CS35L33_MCLKDIV2 (1 << 6) +#define CS35L33_SDOUT_3ST_TDM (1 << 5) +#define CS35L33_INT_FS_RATE (1 << 4) +#define CS35L33_ADSP_FS 0xF + +/* CS35L33_PROTECT_CTL */ +#define CS35L33_ALIVE_WD_DIS (3 << 2) + +/* CS35L33_BST_CTL1 */ +#define CS35L33_BST_CTL_SRC (1 << 6) +#define CS35L33_BST_CTL_SHIFT (1 << 5) +#define CS35L33_BST_CTL_MASK 0x3F + +/* CS35L33_BST_CTL2 */ +#define CS35L33_TDM_WD_SEL (1 << 4) +#define CS35L33_ALIVE_WD_DIS2 (1 << 3) +#define CS35L33_VBST_SR_STEP 0x3 + +/* CS35L33_ADSP_CTL */ +#define CS35L33_ADSP_DRIVE (1 << 7) +#define CS35L33_MS_MASK (1 << 6) +#define CS35L33_SDIN_LOC (3 << 4) +#define CS35L33_ALIVE_RATE 0x3 + +/* CS35L33_ADC_CTL */ +#define CS35L33_INV_VMON (1 << 7) +#define CS35L33_INV_IMON (1 << 6) +#define CS35L33_ADC_NOTCH_DIS (1 << 5) +#define CS35L33_IMON_SCALE 0xF + +/* CS35L33_DAC_CTL */ +#define CS35L33_INV_DAC (1 << 7) +#define CS35L33_DAC_NOTCH_DIS (1 << 5) +#define CS35L33_DIGSFT (1 << 4) +#define CS35L33_DSR_RATE 0xF + +/* CS35L33_CLASSD_CTL */ +#define CS35L33_AMP_SD (1 << 6) +#define CS35L33_AMP_DRV_SEL_SRC (1 << 5) +#define CS35L33_AMP_DRV_SEL_MASK 0x10 +#define CS35L33_AMP_DRV_SEL_SHIFT 4 +#define CS35L33_AMP_CAL (1 << 3) +#define CS35L33_GAIN_CHG_ZC_MASK 0x04 +#define CS35L33_GAIN_CHG_ZC_SHIFT 2 +#define CS35L33_CLASS_D_CTL_MASK 0x3F + +/* CS35L33_AMP_CTL */ +#define CS35L33_AMP_GAIN 0xF0 +#define CS35L33_CAL_ERR_RLS (1 << 3) +#define CS35L33_AMP_SHORT_RLS (1 << 2) +#define CS35L33_OTW_RLS (1 << 1) +#define CS35L33_OTE_RLS 1 + +/* CS35L33_INT_MASK_1 */ +#define CS35L33_M_CAL_ERR_SHIFT 6 +#define CS35L33_M_CAL_ERR (1 << CS35L33_M_CAL_ERR_SHIFT) +#define CS35L33_M_ALIVE_ERR_SHIFT 5 +#define CS35L33_M_ALIVE_ERR (1 << CS35L33_M_ALIVE_ERR_SHIFT) +#define CS35L33_M_AMP_SHORT_SHIFT 2 +#define CS35L33_M_AMP_SHORT (1 << CS35L33_M_AMP_SHORT_SHIFT) +#define CS35L33_M_OTW_SHIFT 1 +#define CS35L33_M_OTW (1 << CS35L33_M_OTW_SHIFT) +#define CS35L33_M_OTE_SHIFT 0 +#define CS35L33_M_OTE (1 << CS35L33_M_OTE_SHIFT) + +/* CS35L33_INT_STATUS_1 */ +#define CS35L33_CAL_ERR (1 << 6) +#define CS35L33_ALIVE_ERR (1 << 5) +#define CS35L33_ADSPCLK_ERR (1 << 4) +#define CS35L33_MCLK_ERR (1 << 3) +#define CS35L33_AMP_SHORT (1 << 2) +#define CS35L33_OTW (1 << 1) +#define CS35L33_OTE (1 << 0) + +/* CS35L33_INT_STATUS_2 */ +#define CS35L33_VMON_OVFL (1 << 7) +#define CS35L33_IMON_OVFL (1 << 6) +#define CS35L33_VPMON_OVFL (1 << 5) +#define CS35L33_VBSTMON_OVFL (1 << 4) +#define CS35L33_PDN_DONE 1 + +/* CS35L33_BST_CTL4 */ +#define CS35L33_BST_RGS 0x70 +#define CS35L33_BST_COEFF3 0xF + +/* CS35L33_HG_MEMLDO_CTL */ +#define CS35L33_MEM_DEPTH_SHIFT 5 +#define CS35L33_MEM_DEPTH_MASK (0x3 << CS35L33_MEM_DEPTH_SHIFT) +#define CS35L33_LDO_THLD_SHIFT 1 +#define CS35L33_LDO_THLD_MASK (0xF << CS35L33_LDO_THLD_SHIFT) +#define CS35L33_LDO_DISABLE_SHIFT 0 +#define CS35L33_LDO_DISABLE_MASK (0x1 << CS35L33_LDO_DISABLE_SHIFT) + +/* CS35L33_LDO_DEL */ +#define CS35L33_VP_HG_VA_SHIFT 5 +#define CS35L33_VP_HG_VA_MASK (0x7 << CS35L33_VP_HG_VA_SHIFT) +#define CS35L33_LDO_ENTRY_DELAY_SHIFT 2 +#define CS35L33_LDO_ENTRY_DELAY_MASK (0x7 << CS35L33_LDO_ENTRY_DELAY_SHIFT) +#define CS35L33_VP_HG_RATE_SHIFT 0 +#define CS35L33_VP_HG_RATE_MASK (0x3 << CS35L33_VP_HG_RATE_SHIFT) + +/* CS35L33_HG_HEAD */ +#define CS35L33_HD_RM_SHIFT 0 +#define CS35L33_HD_RM_MASK (0x7F << CS35L33_HD_RM_SHIFT) + +/* CS35L33_HG_EN */ +#define CS35L33_CLASS_HG_ENA_SHIFT 7 +#define CS35L33_CLASS_HG_EN_MASK (0x1 << CS35L33_CLASS_HG_ENA_SHIFT) +#define CS35L33_VP_HG_AUTO_SHIFT 6 +#define CS35L33_VP_HG_AUTO_MASK (0x1 << 6) +#define CS35L33_VP_HG_SHIFT 0 +#define CS35L33_VP_HG_MASK (0x1F << CS35L33_VP_HG_SHIFT) + +#define CS35L33_RATES (SNDRV_PCM_RATE_8000_48000) +#define CS35L33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* CS35L33_{RX,TX}_X */ +#define CS35L33_X_STATE_SHIFT 7 +#define CS35L33_X_STATE (1 << CS35L33_X_STATE_SHIFT) +#define CS35L33_X_LOC_SHIFT 0 +#define CS35L33_X_LOC (0x1F << CS35L33_X_LOC_SHIFT) + +/* CS35L33_RX_AUD */ +#define CS35L33_AUDIN_RX_DEPTH_SHIFT 5 +#define CS35L33_AUDIN_RX_DEPTH (0x7 << CS35L33_AUDIN_RX_DEPTH_SHIFT) + +#endif diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index 5ec5a682d..954a4f5d3 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -359,6 +359,11 @@ SND_SOC_DAPM_INPUT("IN2R"), SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, 0, NULL, 0, arizona_in_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | @@ -899,10 +904,16 @@ static const struct snd_soc_dapm_route cs47l24_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, { "DRC2 Signal Activity", NULL, "DRC2L" }, { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, }; static int cs47l24_set_fll(struct snd_soc_codec *codec, int fll_id, int source, @@ -1067,6 +1078,7 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) { struct cs47l24_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -1074,6 +1086,12 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -1096,6 +1114,7 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", cs47l24_adsp2_irq, diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c new file mode 100644 index 000000000..2c0d9c430 --- /dev/null +++ b/sound/soc/codecs/cs53l30.c @@ -0,0 +1,1143 @@ +/* + * cs53l30.c -- CS53l30 ALSA Soc Audio driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Authors: Paul Handrigan <Paul.Handrigan@cirrus.com>, + * Tim Howe <Tim.Howe@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "cs53l30.h" + +#define CS53L30_NUM_SUPPLIES 2 +static const char *const cs53l30_supply_names[CS53L30_NUM_SUPPLIES] = { + "VA", + "VP", +}; + +struct cs53l30_private { + struct regulator_bulk_data supplies[CS53L30_NUM_SUPPLIES]; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct gpio_desc *mute_gpio; + struct clk *mclk; + bool use_sdout2; + u32 mclk_rate; +}; + +static const struct reg_default cs53l30_reg_defaults[] = { + { CS53L30_PWRCTL, CS53L30_PWRCTL_DEFAULT }, + { CS53L30_MCLKCTL, CS53L30_MCLKCTL_DEFAULT }, + { CS53L30_INT_SR_CTL, CS53L30_INT_SR_CTL_DEFAULT }, + { CS53L30_MICBIAS_CTL, CS53L30_MICBIAS_CTL_DEFAULT }, + { CS53L30_ASPCFG_CTL, CS53L30_ASPCFG_CTL_DEFAULT }, + { CS53L30_ASP_CTL1, CS53L30_ASP_CTL1_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL1, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL2, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL3, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL4, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN1, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN2, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN3, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN4, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN5, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN6, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_CTL2, CS53L30_ASP_CTL2_DEFAULT }, + { CS53L30_SFT_RAMP, CS53L30_SFT_RMP_DEFAULT }, + { CS53L30_LRCK_CTL1, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_LRCK_CTL2, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_MUTEP_CTL1, CS53L30_MUTEP_CTL1_DEFAULT }, + { CS53L30_MUTEP_CTL2, CS53L30_MUTEP_CTL2_DEFAULT }, + { CS53L30_INBIAS_CTL1, CS53L30_INBIAS_CTL1_DEFAULT }, + { CS53L30_INBIAS_CTL2, CS53L30_INBIAS_CTL2_DEFAULT }, + { CS53L30_DMIC1_STR_CTL, CS53L30_DMIC1_STR_CTL_DEFAULT }, + { CS53L30_DMIC2_STR_CTL, CS53L30_DMIC2_STR_CTL_DEFAULT }, + { CS53L30_ADCDMIC1_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC1_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC1_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC1A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC1B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADCDMIC2_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC2_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC2_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC2A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC2B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_INT_MASK, CS53L30_DEVICE_INT_MASK }, +}; + +static bool cs53l30_volatile_register(struct device *dev, unsigned int reg) +{ + if (reg == CS53L30_IS) + return true; + else + return false; +} + +static bool cs53l30_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_IS: + return false; + default: + return true; + } +} + +static bool cs53l30_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_PWRCTL: + case CS53L30_MCLKCTL: + case CS53L30_INT_SR_CTL: + case CS53L30_MICBIAS_CTL: + case CS53L30_ASPCFG_CTL: + case CS53L30_ASP_CTL1: + case CS53L30_ASP_TDMTX_CTL1: + case CS53L30_ASP_TDMTX_CTL2: + case CS53L30_ASP_TDMTX_CTL3: + case CS53L30_ASP_TDMTX_CTL4: + case CS53L30_ASP_TDMTX_EN1: + case CS53L30_ASP_TDMTX_EN2: + case CS53L30_ASP_TDMTX_EN3: + case CS53L30_ASP_TDMTX_EN4: + case CS53L30_ASP_TDMTX_EN5: + case CS53L30_ASP_TDMTX_EN6: + case CS53L30_ASP_CTL2: + case CS53L30_SFT_RAMP: + case CS53L30_LRCK_CTL1: + case CS53L30_LRCK_CTL2: + case CS53L30_MUTEP_CTL1: + case CS53L30_MUTEP_CTL2: + case CS53L30_INBIAS_CTL1: + case CS53L30_INBIAS_CTL2: + case CS53L30_DMIC1_STR_CTL: + case CS53L30_DMIC2_STR_CTL: + case CS53L30_ADCDMIC1_CTL1: + case CS53L30_ADCDMIC1_CTL2: + case CS53L30_ADC1_CTL3: + case CS53L30_ADC1_NG_CTL: + case CS53L30_ADC1A_AFE_CTL: + case CS53L30_ADC1B_AFE_CTL: + case CS53L30_ADC1A_DIG_VOL: + case CS53L30_ADC1B_DIG_VOL: + case CS53L30_ADCDMIC2_CTL1: + case CS53L30_ADCDMIC2_CTL2: + case CS53L30_ADC2_CTL3: + case CS53L30_ADC2_NG_CTL: + case CS53L30_ADC2A_AFE_CTL: + case CS53L30_ADC2B_AFE_CTL: + case CS53L30_ADC2A_DIG_VOL: + case CS53L30_ADC2B_DIG_VOL: + case CS53L30_INT_MASK: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2000, 0); +static DECLARE_TLV_DB_SCALE(adc_ng_boost_tlv, 0, 3000, 0); +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); +static DECLARE_TLV_DB_SCALE(dig_tlv, -9600, 100, 1); +static DECLARE_TLV_DB_SCALE(pga_preamp_tlv, 0, 10000, 0); + +static const char * const input1_sel_text[] = { + "DMIC1 On AB In", + "DMIC1 On A In", + "DMIC1 On B In", + "ADC1 On AB In", + "ADC1 On A In", + "ADC1 On B In", + "DMIC1 Off ADC1 Off", +}; + +static unsigned int const input1_sel_values[] = { + CS53L30_CH_TYPE, + CS53L30_ADCxB_PDN | CS53L30_CH_TYPE, + CS53L30_ADCxA_PDN | CS53L30_CH_TYPE, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input2_sel_text[] = { + "DMIC2 On AB In", + "DMIC2 On A In", + "DMIC2 On B In", + "ADC2 On AB In", + "ADC2 On A In", + "ADC2 On B In", + "DMIC2 Off ADC2 Off", +}; + +static unsigned int const input2_sel_values[] = { + 0x0, + CS53L30_ADCxB_PDN, + CS53L30_ADCxA_PDN, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input1_route_sel_text[] = { + "ADC1_SEL", "DMIC1_SEL", +}; + +static const struct soc_enum input1_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, CS53L30_CH_TYPE_SHIFT, + ARRAY_SIZE(input1_route_sel_text), + input1_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input1_sel_enum, CS53L30_ADCDMIC1_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input1_sel_text, + input1_sel_values); + +static const struct snd_kcontrol_new input1_route_sel_mux = + SOC_DAPM_ENUM("Input 1 Route", input1_route_sel_enum); + +static const char * const input2_route_sel_text[] = { + "ADC2_SEL", "DMIC2_SEL", +}; + +/* Note: CS53L30_ADCDMIC1_CTL1 CH_TYPE controls inputs 1 and 2 */ +static const struct soc_enum input2_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0, + ARRAY_SIZE(input2_route_sel_text), + input2_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input2_sel_enum, CS53L30_ADCDMIC2_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input2_sel_text, + input2_sel_values); + +static const struct snd_kcontrol_new input2_route_sel_mux = + SOC_DAPM_ENUM("Input 2 Route", input2_route_sel_enum); + +/* + * TB = 6144*(MCLK(int) scaling factor)/MCLK(internal) + * TB - Time base + * NOTE: If MCLK_INT_SCALE = 0, then TB=1 + */ +static const char * const cs53l30_ng_delay_text[] = { + "TB*50ms", "TB*100ms", "TB*150ms", "TB*200ms", +}; + +static const struct soc_enum adc1_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +static const struct soc_enum adc2_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +/* The noise gate threshold selected will depend on NG Boost */ +static const char * const cs53l30_ng_thres_text[] = { + "-64dB/-34dB", "-66dB/-36dB", "-70dB/-40dB", "-73dB/-43dB", + "-76dB/-46dB", "-82dB/-52dB", "-58dB", "-64dB", +}; + +static const struct soc_enum adc1_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +static const struct soc_enum adc2_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +/* Corner frequencies are with an Fs of 48kHz. */ +static const char * const hpf_corner_freq_text[] = { + "1.86Hz", "120Hz", "235Hz", "466Hz", +}; + +static const struct soc_enum adc1_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct soc_enum adc2_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct snd_kcontrol_new cs53l30_snd_controls[] = { + SOC_SINGLE("Digital Soft-Ramp Switch", CS53L30_SFT_RAMP, + CS53L30_DIGSFT_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Noise Gate Ganging Switch", CS53L30_ADC1_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC2 Noise Gate Ganging Switch", CS53L30_ADC2_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC1A Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Notch Filter Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC2 Notch Filter Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC1A Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + + SOC_SINGLE_TLV("ADC1A Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1B Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2A Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2B Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1 NG Boost Volume", CS53L30_ADC1_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + SOC_SINGLE_TLV("ADC2 NG Boost Volume", CS53L30_ADC2_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + + SOC_DOUBLE_R_TLV("ADC1 Preamplifier Volume", CS53L30_ADC1A_AFE_CTL, + CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + SOC_DOUBLE_R_TLV("ADC2 Preamplifier Volume", CS53L30_ADC2A_AFE_CTL, + CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + + SOC_ENUM("Input 1 Channel Select", input1_sel_enum), + SOC_ENUM("Input 2 Channel Select", input2_sel_enum), + + SOC_ENUM("ADC1 HPF Select", adc1_hpf_enum), + SOC_ENUM("ADC2 HPF Select", adc2_hpf_enum), + SOC_ENUM("ADC1 NG Threshold", adc1_ng_thres_enum), + SOC_ENUM("ADC2 NG Threshold", adc2_ng_thres_enum), + SOC_ENUM("ADC1 NG Delay", adc1_ng_delay_enum), + SOC_ENUM("ADC2 NG Delay", adc2_ng_delay_enum), + + SOC_SINGLE_SX_TLV("ADC1A PGA Volume", + CS53L30_ADC1A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC1B PGA Volume", + CS53L30_ADC1B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2A PGA Volume", + CS53L30_ADC2A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2B PGA Volume", + CS53L30_ADC2B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + + SOC_SINGLE_SX_TLV("ADC1A Digital Volume", + CS53L30_ADC1A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC1B Digital Volume", + CS53L30_ADC1B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2A Digital Volume", + CS53L30_ADC2A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2B Digital Volume", + CS53L30_ADC2B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), +}; + +static const struct snd_soc_dapm_widget cs53l30_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN1_DMIC1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3_DMIC2"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC1_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC2_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC3 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC3_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC4 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC4_BIAS_PDN_SHIFT, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT1", NULL, 0, CS53L30_ASP_CTL1, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT2", NULL, 0, CS53L30_ASP_CTL2, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + + SND_SOC_DAPM_MUX("Input Mux 1", SND_SOC_NOPM, 0, 0, + &input1_route_sel_mux), + SND_SOC_DAPM_MUX("Input Mux 2", SND_SOC_NOPM, 0, 0, + &input2_route_sel_mux), + + SND_SOC_DAPM_ADC("ADC1A", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC1B", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2A", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2B", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC1", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC2", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes[] = { + /* ADC Input Paths */ + {"ADC1A", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "ADC1_SEL", "ADC1A"}, + {"ADC1B", NULL, "IN2"}, + + {"ADC2A", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "ADC2_SEL", "ADC2A"}, + {"ADC2B", NULL, "IN4"}, + + /* MIC Bias Paths */ + {"ADC1A", NULL, "MIC1 Bias"}, + {"ADC1B", NULL, "MIC2 Bias"}, + {"ADC2A", NULL, "MIC3 Bias"}, + {"ADC2B", NULL, "MIC4 Bias"}, + + /* DMIC Paths */ + {"DMIC1", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "DMIC1_SEL", "DMIC1"}, + + {"DMIC2", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "DMIC2_SEL", "DMIC2"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout1[] = { + /* Output Paths when using SDOUT1 only */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT1", NULL, "ADC2A"}, + {"ASP_SDOUT1", NULL, "Input Mux 2"}, + {"ASP_SDOUT1", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout2[] = { + /* Output Paths when using both SDOUT1 and SDOUT2 */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT2", NULL, "ADC2A"}, + {"ASP_SDOUT2", NULL, "Input Mux 2"}, + {"ASP_SDOUT2", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, + {"Capture", NULL, "ASP_SDOUT2"}, +}; + +struct cs53l30_mclk_div { + u32 mclk_rate; + u32 srate; + u8 asp_rate; + u8 internal_fs_ratio; + u8 mclk_int_scale; +}; + +static struct cs53l30_mclk_div cs53l30_mclk_coeffs[] = { + /* NOTE: Enable MCLK_INT_SCALE to save power. */ + + /* MCLK, Sample Rate, asp_rate, internal_fs_ratio, mclk_int_scale */ + {5644800, 11025, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 22050, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 44100, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6000000, 8000, 0x1, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 11025, 0x2, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 12000, 0x4, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 16000, 0x5, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 22050, 0x6, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 24000, 0x8, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 32000, 0x9, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 44100, 0xA, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 48000, 0xC, 0, CS53L30_MCLK_INT_SCALE}, + + {6144000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6400000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, +}; + +struct cs53l30_mclkx_div { + u32 mclkx; + u8 ratio; + u8 mclkdiv; +}; + +static struct cs53l30_mclkx_div cs53l30_mclkx_coeffs[] = { + {5644800, 1, CS53L30_MCLK_DIV_BY_1}, + {6000000, 1, CS53L30_MCLK_DIV_BY_1}, + {6144000, 1, CS53L30_MCLK_DIV_BY_1}, + {11289600, 2, CS53L30_MCLK_DIV_BY_2}, + {12288000, 2, CS53L30_MCLK_DIV_BY_2}, + {12000000, 2, CS53L30_MCLK_DIV_BY_2}, + {19200000, 3, CS53L30_MCLK_DIV_BY_3}, +}; + +static int cs53l30_get_mclkx_coeff(int mclkx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclkx_coeffs); i++) { + if (cs53l30_mclkx_coeffs[i].mclkx == mclkx) + return i; + } + + return -EINVAL; +} + +static int cs53l30_get_mclk_coeff(int mclk_rate, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclk_coeffs); i++) { + if (cs53l30_mclk_coeffs[i].mclk_rate == mclk_rate && + cs53l30_mclk_coeffs[i].srate == srate) + return i; + } + + return -EINVAL; +} + +static int cs53l30_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + int mclkx_coeff; + u32 mclk_rate; + + /* MCLKX -> MCLK */ + mclkx_coeff = cs53l30_get_mclkx_coeff(freq); + if (mclkx_coeff < 0) + return mclkx_coeff; + + mclk_rate = cs53l30_mclkx_coeffs[mclkx_coeff].mclkx / + cs53l30_mclkx_coeffs[mclkx_coeff].ratio; + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIV_MASK, + cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv); + + priv->mclk_rate = mclk_rate; + + return 0; +} + +static int cs53l30_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 aspcfg = 0, aspctl1 = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aspcfg |= CS53L30_ASP_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* Set TDM_PDN to turn off TDM mode -- Reset default */ + aspctl1 |= CS53L30_ASP_TDM_PDN; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Clear TDM_PDN to turn on TDM mode; Use ASP_SCLK_INV = 0 + * with SHIFT_LEFT = 1 combination as Figure 4-13 shows in + * the CS53L30 datasheet + */ + aspctl1 |= CS53L30_SHIFT_LEFT; + break; + default: + return -EINVAL; + } + + /* Check to see if the SCLK is inverted */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + aspcfg ^= CS53L30_ASP_SCLK_INV; + break; + default: + break; + } + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_MS | CS53L30_ASP_SCLK_INV, aspcfg); + + regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_TDM_PDN | CS53L30_SHIFT_LEFT, aspctl1); + + return 0; +} + +static int cs53l30_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + int srate = params_rate(params); + int mclk_coeff; + + /* MCLK -> srate */ + mclk_coeff = cs53l30_get_mclk_coeff(priv->mclk_rate, srate); + if (mclk_coeff < 0) + return -EINVAL; + + regmap_update_bits(priv->regmap, CS53L30_INT_SR_CTL, + CS53L30_INTRNL_FS_RATIO_MASK, + cs53l30_mclk_coeffs[mclk_coeff].internal_fs_ratio); + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_INT_SCALE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].mclk_int_scale); + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_RATE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].asp_rate); + + return 0; +} + +static int cs53l30_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg; + int i, inter_max_check, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_LP_MASK, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_OFF) { + ret = clk_prepare_enable(priv->mclk); + if (ret) { + dev_err(codec->dev, + "failed to enable MCLK: %d\n", ret); + return ret; + } + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, 0); + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, 0); + msleep(50); + } else { + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + } + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, 0); + /* + * If digital softramp is set, the amount of time required + * for power down increases and depends on the digital + * volume setting. + */ + + /* Set the max possible time if digsft is set */ + regmap_read(priv->regmap, CS53L30_SFT_RAMP, ®); + if (reg & CS53L30_DIGSFT_MASK) + inter_max_check = CS53L30_PDN_POLL_MAX; + else + inter_max_check = 10; + + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + /* PDN_DONE will take a min of 20ms to be set.*/ + msleep(20); + /* Clr status */ + regmap_read(priv->regmap, CS53L30_IS, ®); + for (i = 0; i < inter_max_check; i++) { + if (inter_max_check < 10) { + usleep_range(1000, 1100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } else { + usleep_range(10000, 10100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } + } + /* PDN_DONE is set. We now can disable the MCLK */ + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, CS53L30_PDN_DONE); + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, + CS53L30_MCLK_DIS); + clk_disable_unprepare(priv->mclk); + break; + } + + return 0; +} + +static int cs53l30_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 val = tristate ? CS53L30_ASP_3ST : 0; + + return regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_3ST_MASK, val); +} + +static unsigned int const cs53l30_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list src_constraints = { + .count = ARRAY_SIZE(cs53l30_src_rates), + .list = cs53l30_src_rates, +}; + +static int cs53l30_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &src_constraints); + + return 0; +} + +/* + * Note: CS53L30 counts the slot number per byte while ASoC counts the slot + * number per slot_width. So there is a difference between the slots of ASoC + * and the slots of CS53L30. + */ +static int cs53l30_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + unsigned int loc[CS53L30_TDM_SLOT_MAX] = {48, 48, 48, 48}; + unsigned int slot_next, slot_step; + u64 tx_enable = 0; + int i; + + if (!rx_mask) { + dev_err(dai->dev, "rx masks must not be 0\n"); + return -EINVAL; + } + + /* Assuming slot_width is not supposed to be greater than 64 */ + if (slots <= 0 || slot_width <= 0 || slot_width > 64) { + dev_err(dai->dev, "invalid slot number or slot width\n"); + return -EINVAL; + } + + if (slot_width & 0x7) { + dev_err(dai->dev, "slot width must count in byte\n"); + return -EINVAL; + } + + /* How many bytes in each ASoC slot */ + slot_step = slot_width >> 3; + + for (i = 0; rx_mask && i < CS53L30_TDM_SLOT_MAX; i++) { + /* Find the first slot from LSB */ + slot_next = __ffs(rx_mask); + /* Save the slot location by converting to CS53L30 slot */ + loc[i] = slot_next * slot_step; + /* Create the mask of CS53L30 slot */ + tx_enable |= (u64)((u64)(1 << slot_step) - 1) << (u64)loc[i]; + /* Clear this slot from rx_mask */ + rx_mask &= ~(1 << slot_next); + } + + /* Error out to avoid slot shift */ + if (rx_mask && i == CS53L30_TDM_SLOT_MAX) { + dev_err(dai->dev, "rx_mask exceeds max slot number: %d\n", + CS53L30_TDM_SLOT_MAX); + return -EINVAL; + } + + /* Validate the last active CS53L30 slot */ + slot_next = loc[i - 1] + slot_step - 1; + if (slot_next > 47) { + dev_err(dai->dev, "slot selection out of bounds: %u\n", + slot_next); + return -EINVAL; + } + + for (i = 0; i < CS53L30_TDM_SLOT_MAX && loc[i] != 48; i++) { + regmap_update_bits(priv->regmap, CS53L30_ASP_TDMTX_CTL(i), + CS53L30_ASP_CHx_TX_LOC_MASK, loc[i]); + dev_dbg(dai->dev, "loc[%d]=%x\n", i, loc[i]); + } + + for (i = 0; i < CS53L30_ASP_TDMTX_ENx_MAX && tx_enable; i++) { + regmap_write(priv->regmap, CS53L30_ASP_TDMTX_ENx(i), + tx_enable & 0xff); + tx_enable >>= 8; + dev_dbg(dai->dev, "en_reg=%x, tx_enable=%llx\n", + CS53L30_ASP_TDMTX_ENx(i), tx_enable & 0xff); + } + + return 0; +} + +static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + + if (priv->mute_gpio) + gpiod_set_value_cansleep(priv->mute_gpio, mute); + + return 0; +} + +/* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */ +#define CS53L30_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +#define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops cs53l30_ops = { + .startup = cs53l30_pcm_startup, + .hw_params = cs53l30_pcm_hw_params, + .set_fmt = cs53l30_set_dai_fmt, + .set_sysclk = cs53l30_set_sysclk, + .set_tristate = cs53l30_set_tristate, + .set_tdm_slot = cs53l30_set_dai_tdm_slot, + .mute_stream = cs53l30_mute_stream, +}; + +static struct snd_soc_dai_driver cs53l30_dai = { + .name = "cs53l30", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = CS53L30_RATES, + .formats = CS53L30_FORMATS, + }, + .ops = &cs53l30_ops, + .symmetric_rates = 1, +}; + +static int cs53l30_codec_probe(struct snd_soc_codec *codec) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + if (priv->use_sdout2) + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout2, + ARRAY_SIZE(cs53l30_dapm_routes_sdout2)); + else + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout1, + ARRAY_SIZE(cs53l30_dapm_routes_sdout1)); + + return 0; +} + +static struct snd_soc_codec_driver cs53l30_driver = { + .probe = cs53l30_codec_probe, + .set_bias_level = cs53l30_set_bias_level, + .idle_bias_off = true, + + .dapm_widgets = cs53l30_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs53l30_dapm_widgets), + .dapm_routes = cs53l30_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs53l30_dapm_routes), + + .controls = cs53l30_snd_controls, + .num_controls = ARRAY_SIZE(cs53l30_snd_controls), +}; + +static struct regmap_config cs53l30_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS53L30_MAX_REGISTER, + .reg_defaults = cs53l30_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs53l30_reg_defaults), + .volatile_reg = cs53l30_volatile_register, + .writeable_reg = cs53l30_writeable_register, + .readable_reg = cs53l30_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs53l30_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct device_node *np = client->dev.of_node; + struct device *dev = &client->dev; + struct cs53l30_private *cs53l30; + unsigned int devid = 0; + unsigned int reg; + int ret = 0, i; + u8 val; + + cs53l30 = devm_kzalloc(dev, sizeof(*cs53l30), GFP_KERNEL); + if (!cs53l30) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cs53l30->supplies); i++) + cs53l30->supplies[i].supply = cs53l30_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs53l30->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs53l30->reset_gpio)) { + ret = PTR_ERR(cs53l30->reset_gpio); + goto error; + } + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + i2c_set_clientdata(client, cs53l30); + + cs53l30->mclk_rate = 0; + + cs53l30->regmap = devm_regmap_init_i2c(client, &cs53l30_regmap); + if (IS_ERR(cs53l30->regmap)) { + ret = PTR_ERR(cs53l30->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto error; + } + + /* Initialize codec */ + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_AB, ®); + devid = reg << 12; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, ®); + devid |= reg << 4; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS53L30_DEVID) { + ret = -ENODEV; + dev_err(dev, "Device ID (%X). Expected %X\n", + devid, CS53L30_DEVID); + goto error; + } + + ret = regmap_read(cs53l30->regmap, CS53L30_REVID, ®); + if (ret < 0) { + dev_err(dev, "failed to get Revision ID: %d\n", ret); + goto error; + } + + /* Check if MCLK provided */ + cs53l30->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(cs53l30->mclk)) { + if (PTR_ERR(cs53l30->mclk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto error; + } + /* Otherwise mark the mclk pointer to NULL */ + cs53l30->mclk = NULL; + } + + /* Fetch the MUTE control */ + cs53l30->mute_gpio = devm_gpiod_get_optional(dev, "mute", + GPIOD_OUT_HIGH); + if (IS_ERR(cs53l30->mute_gpio)) { + ret = PTR_ERR(cs53l30->mute_gpio); + goto error; + } + + if (cs53l30->mute_gpio) { + /* Enable MUTE controls via MUTE pin */ + regmap_write(cs53l30->regmap, CS53L30_MUTEP_CTL1, + CS53L30_MUTEP_CTL1_MUTEALL); + /* Flip the polarity of MUTE pin */ + if (gpiod_is_active_low(cs53l30->mute_gpio)) + regmap_update_bits(cs53l30->regmap, CS53L30_MUTEP_CTL2, + CS53L30_MUTE_PIN_POLARITY, 0); + } + + if (!of_property_read_u8(np, "cirrus,micbias-lvl", &val)) + regmap_update_bits(cs53l30->regmap, CS53L30_MICBIAS_CTL, + CS53L30_MIC_BIAS_CTRL_MASK, val); + + if (of_property_read_bool(np, "cirrus,use-sdout2")) + cs53l30->use_sdout2 = true; + + dev_info(dev, "Cirrus Logic CS53L30, Revision: %02X\n", reg & 0xFF); + + ret = snd_soc_register_codec(dev, &cs53l30_driver, &cs53l30_dai, 1); + if (ret) { + dev_err(dev, "failed to register codec: %d\n", ret); + goto error; + } + + return 0; + +error: + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + return ret; +} + +static int cs53l30_i2c_remove(struct i2c_client *client) +{ + struct cs53l30_private *cs53l30 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +#ifdef CONFIG_PM +static int cs53l30_runtime_suspend(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + + regcache_cache_only(cs53l30->regmap, true); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +static int cs53l30_runtime_resume(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + regcache_cache_only(cs53l30->regmap, false); + ret = regcache_sync(cs53l30->regmap); + if (ret) { + dev_err(dev, "failed to synchronize regcache: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops cs53l30_runtime_pm = { + SET_RUNTIME_PM_OPS(cs53l30_runtime_suspend, cs53l30_runtime_resume, + NULL) +}; + +static const struct of_device_id cs53l30_of_match[] = { + { .compatible = "cirrus,cs53l30", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs53l30_of_match); + +static const struct i2c_device_id cs53l30_id[] = { + { "cs53l30", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs53l30_id); + +static struct i2c_driver cs53l30_i2c_driver = { + .driver = { + .name = "cs53l30", + .pm = &cs53l30_runtime_pm, + }, + .id_table = cs53l30_id, + .probe = cs53l30_i2c_probe, + .remove = cs53l30_i2c_remove, +}; + +module_i2c_driver(cs53l30_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS53L30 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <Paul.Handrigan@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs53l30.h b/sound/soc/codecs/cs53l30.h new file mode 100644 index 000000000..5e39da568 --- /dev/null +++ b/sound/soc/codecs/cs53l30.h @@ -0,0 +1,459 @@ +/* + * ALSA SoC CS53L30 codec driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <Paul.Handrigan@cirrus.com>, + * Tim Howe <Tim.Howe@cirrus.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS53L30_H__ +#define __CS53L30_H__ + +/* I2C Registers */ +#define CS53L30_DEVID_AB 0x01 /* Device ID A & B [RO]. */ +#define CS53L30_DEVID_CD 0x02 /* Device ID C & D [RO]. */ +#define CS53L30_DEVID_E 0x03 /* Device ID E [RO]. */ +#define CS53L30_REVID 0x05 /* Revision ID [RO]. */ +#define CS53L30_PWRCTL 0x06 /* Power Control. */ +#define CS53L30_MCLKCTL 0x07 /* MCLK Control. */ +#define CS53L30_INT_SR_CTL 0x08 /* Internal Sample Rate Control. */ +#define CS53L30_MICBIAS_CTL 0x0A /* Mic Bias Control. */ +#define CS53L30_ASPCFG_CTL 0x0C /* ASP Config Control. */ +#define CS53L30_ASP_CTL1 0x0D /* ASP1 Control. */ +#define CS53L30_ASP_TDMTX_CTL1 0x0E /* ASP1 TDM TX Control 1 */ +#define CS53L30_ASP_TDMTX_CTL2 0x0F /* ASP1 TDM TX Control 2 */ +#define CS53L30_ASP_TDMTX_CTL3 0x10 /* ASP1 TDM TX Control 3 */ +#define CS53L30_ASP_TDMTX_CTL4 0x11 /* ASP1 TDM TX Control 4 */ +#define CS53L30_ASP_TDMTX_EN1 0x12 /* ASP1 TDM TX Enable 1 */ +#define CS53L30_ASP_TDMTX_EN2 0x13 /* ASP1 TDM TX Enable 2 */ +#define CS53L30_ASP_TDMTX_EN3 0x14 /* ASP1 TDM TX Enable 3 */ +#define CS53L30_ASP_TDMTX_EN4 0x15 /* ASP1 TDM TX Enable 4 */ +#define CS53L30_ASP_TDMTX_EN5 0x16 /* ASP1 TDM TX Enable 5 */ +#define CS53L30_ASP_TDMTX_EN6 0x17 /* ASP1 TDM TX Enable 6 */ +#define CS53L30_ASP_CTL2 0x18 /* ASP2 Control. */ +#define CS53L30_SFT_RAMP 0x1A /* Soft Ramp Control. */ +#define CS53L30_LRCK_CTL1 0x1B /* LRCK Control 1. */ +#define CS53L30_LRCK_CTL2 0x1C /* LRCK Control 2. */ +#define CS53L30_MUTEP_CTL1 0x1F /* Mute Pin Control 1. */ +#define CS53L30_MUTEP_CTL2 0x20 /* Mute Pin Control 2. */ +#define CS53L30_INBIAS_CTL1 0x21 /* Input Bias Control 1. */ +#define CS53L30_INBIAS_CTL2 0x22 /* Input Bias Control 2. */ +#define CS53L30_DMIC1_STR_CTL 0x23 /* DMIC1 Stereo Control. */ +#define CS53L30_DMIC2_STR_CTL 0x24 /* DMIC2 Stereo Control. */ +#define CS53L30_ADCDMIC1_CTL1 0x25 /* ADC1/DMIC1 Control 1. */ +#define CS53L30_ADCDMIC1_CTL2 0x26 /* ADC1/DMIC1 Control 2. */ +#define CS53L30_ADC1_CTL3 0x27 /* ADC1 Control 3. */ +#define CS53L30_ADC1_NG_CTL 0x28 /* ADC1 Noise Gate Control. */ +#define CS53L30_ADC1A_AFE_CTL 0x29 /* ADC1A AFE Control. */ +#define CS53L30_ADC1B_AFE_CTL 0x2A /* ADC1B AFE Control. */ +#define CS53L30_ADC1A_DIG_VOL 0x2B /* ADC1A Digital Volume. */ +#define CS53L30_ADC1B_DIG_VOL 0x2C /* ADC1B Digital Volume. */ +#define CS53L30_ADCDMIC2_CTL1 0x2D /* ADC2/DMIC2 Control 1. */ +#define CS53L30_ADCDMIC2_CTL2 0x2E /* ADC2/DMIC2 Control 2. */ +#define CS53L30_ADC2_CTL3 0x2F /* ADC2 Control 3. */ +#define CS53L30_ADC2_NG_CTL 0x30 /* ADC2 Noise Gate Control. */ +#define CS53L30_ADC2A_AFE_CTL 0x31 /* ADC2A AFE Control. */ +#define CS53L30_ADC2B_AFE_CTL 0x32 /* ADC2B AFE Control. */ +#define CS53L30_ADC2A_DIG_VOL 0x33 /* ADC2A Digital Volume. */ +#define CS53L30_ADC2B_DIG_VOL 0x34 /* ADC2B Digital Volume. */ +#define CS53L30_INT_MASK 0x35 /* Interrupt Mask. */ +#define CS53L30_IS 0x36 /* Interrupt Status. */ +#define CS53L30_MAX_REGISTER 0x36 + +#define CS53L30_TDM_SLOT_MAX 4 +#define CS53L30_ASP_TDMTX_CTL(x) (CS53L30_ASP_TDMTX_CTL1 + (x)) +/* x : index for registers; n : index for slot; 8 slots per register */ +#define CS53L30_ASP_TDMTX_ENx(x) (CS53L30_ASP_TDMTX_EN6 - (x)) +#define CS53L30_ASP_TDMTX_ENn(n) CS53L30_ASP_TDMTX_ENx((n) >> 3) +#define CS53L30_ASP_TDMTX_ENx_MAX 6 + +/* Device ID */ +#define CS53L30_DEVID 0x53A30 + +/* PDN_DONE Poll Maximum + * If soft ramp is set it will take much longer to power down + * the system. + */ +#define CS53L30_PDN_POLL_MAX 90 + +/* Bitfield Definitions */ + +/* R6 (0x06) CS53L30_PWRCTL - Power Control */ +#define CS53L30_PDN_ULP_SHIFT 7 +#define CS53L30_PDN_ULP_MASK (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_ULP (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_LP_SHIFT 6 +#define CS53L30_PDN_LP_MASK (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_PDN_LP (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_DISCHARGE_FILT_SHIFT 5 +#define CS53L30_DISCHARGE_FILT_MASK (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_DISCHARGE_FILT (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_THMS_PDN_SHIFT 4 +#define CS53L30_THMS_PDN_MASK (1 << CS53L30_THMS_PDN_SHIFT) +#define CS53L30_THMS_PDN (1 << CS53L30_THMS_PDN_SHIFT) + +#define CS53L30_PWRCTL_DEFAULT (CS53L30_THMS_PDN) + +/* R7 (0x07) CS53L30_MCLKCTL - MCLK Control */ +#define CS53L30_MCLK_DIS_SHIFT 7 +#define CS53L30_MCLK_DIS_MASK (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_DIS (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_INT_SCALE_SHIFT 6 +#define CS53L30_MCLK_INT_SCALE_MASK (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_MCLK_INT_SCALE (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_DMIC_DRIVE_SHIFT 5 +#define CS53L30_DMIC_DRIVE_MASK (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_DMIC_DRIVE (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_MCLK_DIV_SHIFT 2 +#define CS53L30_MCLK_DIV_WIDTH 2 +#define CS53L30_MCLK_DIV_MASK (((1 << CS53L30_MCLK_DIV_WIDTH) - 1) << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_1 (0x0 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_2 (0x1 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_3 (0x2 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_SYNC_EN_SHIFT 1 +#define CS53L30_SYNC_EN_MASK (1 << CS53L30_SYNC_EN_SHIFT) +#define CS53L30_SYNC_EN (1 << CS53L30_SYNC_EN_SHIFT) + +#define CS53L30_MCLKCTL_DEFAULT (CS53L30_MCLK_DIV_BY_2) + +/* R8 (0x08) CS53L30_INT_SR_CTL - Internal Sample Rate Control */ +#define CS53L30_INTRNL_FS_RATIO_SHIFT 4 +#define CS53L30_INTRNL_FS_RATIO_MASK (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_INTRNL_FS_RATIO (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_MCLK_19MHZ_EN_SHIFT 0 +#define CS53L30_MCLK_19MHZ_EN_MASK (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) +#define CS53L30_MCLK_19MHZ_EN (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) + +/* 0x6 << 1 is reserved bits */ +#define CS53L30_INT_SR_CTL_DEFAULT (CS53L30_INTRNL_FS_RATIO | 0x6 << 1) + +/* R10 (0x0A) CS53L30_MICBIAS_CTL - Mic Bias Control */ +#define CS53L30_MIC4_BIAS_PDN_SHIFT 7 +#define CS53L30_MIC4_BIAS_PDN_MASK (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC4_BIAS_PDN (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN_SHIFT 6 +#define CS53L30_MIC3_BIAS_PDN_MASK (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN_SHIFT 5 +#define CS53L30_MIC2_BIAS_PDN_MASK (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN_SHIFT 4 +#define CS53L30_MIC1_BIAS_PDN_MASK (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MICx_BIAS_PDN (0xf << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_VP_MIN_SHIFT 2 +#define CS53L30_VP_MIN_MASK (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_VP_MIN (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_SHIFT 0 +#define CS53L30_MIC_BIAS_CTRL_WIDTH 2 +#define CS53L30_MIC_BIAS_CTRL_MASK (((1 << CS53L30_MIC_BIAS_CTRL_WIDTH) - 1) << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_HIZ (0 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_1V8 (1 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_2V75 (2 << CS53L30_MIC_BIAS_CTRL_SHIFT) + +#define CS53L30_MICBIAS_CTL_DEFAULT (CS53L30_MICx_BIAS_PDN | CS53L30_VP_MIN) + +/* R12 (0x0C) CS53L30_ASPCFG_CTL - ASP Configuration Control */ +#define CS53L30_ASP_MS_SHIFT 7 +#define CS53L30_ASP_MS_MASK (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_MS (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_SCLK_INV_SHIFT 4 +#define CS53L30_ASP_SCLK_INV_MASK (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_SCLK_INV (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_RATE_SHIFT 0 +#define CS53L30_ASP_RATE_WIDTH 4 +#define CS53L30_ASP_RATE_MASK (((1 << CS53L30_ASP_RATE_WIDTH) - 1) << CS53L30_ASP_RATE_SHIFT) +#define CS53L30_ASP_RATE_48K (0xc << CS53L30_ASP_RATE_SHIFT) + +#define CS53L30_ASPCFG_CTL_DEFAULT (CS53L30_ASP_RATE_48K) + +/* R13/R24 (0x0D/0x18) CS53L30_ASP_CTL1 & CS53L30_ASP_CTL2 - ASP Control 1~2 */ +#define CS53L30_ASP_TDM_PDN_SHIFT 7 +#define CS53L30_ASP_TDM_PDN_MASK (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_TDM_PDN (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN_SHIFT 6 +#define CS53L30_ASP_SDOUTx_PDN_MASK (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_3ST_SHIFT 5 +#define CS53L30_ASP_3ST_MASK (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_ASP_3ST (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_SHIFT_LEFT_SHIFT 4 +#define CS53L30_SHIFT_LEFT_MASK (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_SHIFT_LEFT (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE_SHIFT 0 +#define CS53L30_ASP_SDOUTx_DRIVE_MASK (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) + +#define CS53L30_ASP_CTL1_DEFAULT (CS53L30_ASP_TDM_PDN) +#define CS53L30_ASP_CTL2_DEFAULT (0) + +/* R14 (0x0E) ~ R17 (0x11) CS53L30_ASP_TDMTX_CTLx - ASP TDM TX Control 1~4 */ +#define CS53L30_ASP_CHx_TX_STATE_SHIFT 7 +#define CS53L30_ASP_CHx_TX_STATE_MASK (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_STATE (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_SHIFT 0 +#define CS53L30_ASP_CHx_TX_LOC_WIDTH 6 +#define CS53L30_ASP_CHx_TX_LOC_MASK (((1 << CS53L30_ASP_CHx_TX_LOC_WIDTH) - 1) << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_MAX (47 << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC(x) ((x) << CS53L30_ASP_CHx_TX_LOC_SHIFT) + +#define CS53L30_ASP_TDMTX_CTLx_DEFAULT (CS53L30_ASP_CHx_TX_LOC_MAX) + +/* R18 (0x12) ~ R23 (0x17) CS53L30_ASP_TDMTX_ENx - ASP TDM TX Enable 1~6 */ +#define CS53L30_ASP_TDMTX_ENx_DEFAULT (0) + +/* R26 (0x1A) CS53L30_SFT_RAMP - Soft Ramp Control */ +#define CS53L30_DIGSFT_SHIFT 5 +#define CS53L30_DIGSFT_MASK (1 << CS53L30_DIGSFT_SHIFT) +#define CS53L30_DIGSFT (1 << CS53L30_DIGSFT_SHIFT) + +#define CS53L30_SFT_RMP_DEFAULT (0) + +/* R28 (0x1C) CS53L30_LRCK_CTL2 - LRCK Control 2 */ +#define CS53L30_LRCK_50_NPW_SHIFT 3 +#define CS53L30_LRCK_50_NPW_MASK (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_50_NPW (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_TPWH_SHIFT 0 +#define CS53L30_LRCK_TPWH_WIDTH 3 +#define CS53L30_LRCK_TPWH_MASK (((1 << CS53L30_LRCK_TPWH_WIDTH) - 1) << CS53L30_LRCK_TPWH_SHIFT) +#define CS53L30_LRCK_TPWH(x) (((x) << CS53L30_LRCK_TPWH_SHIFT) & CS53L30_LRCK_TPWH_MASK) + +#define CS53L30_LRCK_CTLx_DEFAULT (0) + +/* R31 (0x1F) CS53L30_MUTEP_CTL1 - MUTE Pin Control 1 */ +#define CS53L30_MUTE_PDN_ULP_SHIFT 7 +#define CS53L30_MUTE_PDN_ULP_MASK (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_ULP (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_LP_SHIFT 6 +#define CS53L30_MUTE_PDN_LP_MASK (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_PDN_LP (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_M4B_PDN_SHIFT 4 +#define CS53L30_MUTE_M4B_PDN_MASK (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M4B_PDN (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN_SHIFT 3 +#define CS53L30_MUTE_M3B_PDN_MASK (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN_SHIFT 2 +#define CS53L30_MUTE_M2B_PDN_MASK (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN_SHIFT 1 +#define CS53L30_MUTE_M1B_PDN_MASK (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_MxB_PDN_SHIFT(x) (CS53L30_MUTE_M1B_PDN_SHIFT + (x)) +#define CS53L30_MUTE_MxB_PDN_MASK(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MxB_PDN(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MB_ALL_PDN_SHIFT 0 +#define CS53L30_MUTE_MB_ALL_PDN_MASK (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) +#define CS53L30_MUTE_MB_ALL_PDN (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL1_MUTEALL (0xdf) +#define CS53L30_MUTEP_CTL1_DEFAULT (0) + +/* R32 (0x20) CS53L30_MUTEP_CTL2 - MUTE Pin Control 2 */ +#define CS53L30_MUTE_PIN_POLARITY_SHIFT 7 +#define CS53L30_MUTE_PIN_POLARITY_MASK (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_PIN_POLARITY (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN_SHIFT 6 +#define CS53L30_MUTE_ASP_TDM_PDN_MASK (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT 5 +#define CS53L30_MUTE_ASP_SDOUT2_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT 4 +#define CS53L30_MUTE_ASP_SDOUT1_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x) ((x) + CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUTx_PDN_MASK(x) (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ASP_SDOUTx_PDN (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ADC2B_PDN_SHIFT 3 +#define CS53L30_MUTE_ADC2B_PDN_MASK (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2B_PDN (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN_SHIFT 2 +#define CS53L30_MUTE_ADC2A_PDN_MASK (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN_SHIFT 1 +#define CS53L30_MUTE_ADC1B_PDN_MASK (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN_SHIFT 0 +#define CS53L30_MUTE_ADC1A_PDN_MASK (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL2_DEFAULT (CS53L30_MUTE_PIN_POLARITY) + +/* R33 (0x21) CS53L30_INBIAS_CTL1 - Input Bias Control 1 */ +#define CS53L30_IN4M_BIAS_SHIFT 6 +#define CS53L30_IN4M_BIAS_WIDTH 2 +#define CS53L30_IN4M_BIAS_MASK (((1 << CS53L30_IN4M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_OPEN (0 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_PULL_DOWN (1 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_VCM (2 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_SHIFT 4 +#define CS53L30_IN4P_BIAS_WIDTH 2 +#define CS53L30_IN4P_BIAS_MASK (((1 << CS53L30_IN4P_BIAS_WIDTH) - 1) << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_OPEN (0 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_PULL_DOWN (1 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_VCM (2 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_SHIFT 2 +#define CS53L30_IN3M_BIAS_WIDTH 2 +#define CS53L30_IN3M_BIAS_MASK (((1 << CS53L30_IN3M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_OPEN (0 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_PULL_DOWN (1 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_VCM (2 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_SHIFT 0 +#define CS53L30_IN3P_BIAS_WIDTH 2 +#define CS53L30_IN3P_BIAS_MASK (((1 << CS53L30_IN3P_BIAS_WIDTH) - 1) << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_OPEN (0 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_PULL_DOWN (1 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_VCM (2 << CS53L30_IN3P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL1_DEFAULT (CS53L30_IN4M_BIAS_VCM | CS53L30_IN4P_BIAS_VCM |\ + CS53L30_IN3M_BIAS_VCM | CS53L30_IN3P_BIAS_VCM) + +/* R34 (0x22) CS53L30_INBIAS_CTL2 - Input Bias Control 2 */ +#define CS53L30_IN2M_BIAS_SHIFT 6 +#define CS53L30_IN2M_BIAS_WIDTH 2 +#define CS53L30_IN2M_BIAS_MASK (((1 << CS53L30_IN2M_BIAS_WIDTH) - 1) << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_OPEN (0 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_PULL_DOWN (1 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_VCM (2 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_SHIFT 4 +#define CS53L30_IN2P_BIAS_WIDTH 2 +#define CS53L30_IN2P_BIAS_MASK (((1 << CS53L30_IN2P_BIAS_WIDTH) - 1) << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_OPEN (0 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_PULL_DOWN (1 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_VCM (2 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_SHIFT 2 +#define CS53L30_IN1M_BIAS_WIDTH 2 +#define CS53L30_IN1M_BIAS_MASK (((1 << CS53L30_IN1M_BIAS_WIDTH) - 1) << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_OPEN (0 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_PULL_DOWN (1 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_VCM (2 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_SHIFT 0 +#define CS53L30_IN1P_BIAS_WIDTH 2 +#define CS53L30_IN1P_BIAS_MASK (((1 << CS53L30_IN1P_BIAS_WIDTH) - 1) << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_OPEN (0 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_PULL_DOWN (1 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_VCM (2 << CS53L30_IN1P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL2_DEFAULT (CS53L30_IN2M_BIAS_VCM | CS53L30_IN2P_BIAS_VCM |\ + CS53L30_IN1M_BIAS_VCM | CS53L30_IN1P_BIAS_VCM) + +/* R35 (0x23) & R36 (0x24) CS53L30_DMICx_STR_CTL - DMIC1 & DMIC2 Stereo Control */ +#define CS53L30_DMICx_STEREO_ENB_SHIFT 5 +#define CS53L30_DMICx_STEREO_ENB_MASK (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) +#define CS53L30_DMICx_STEREO_ENB (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) + +/* 0x88 and 0xCC are reserved bits */ +#define CS53L30_DMIC1_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0x88) +#define CS53L30_DMIC2_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0xCC) + +/* R37/R45 (0x25/0x2D) CS53L30_ADCDMICx_CTL1 - ADC1/DMIC1 & ADC2/DMIC2 Control 1 */ +#define CS53L30_ADCxB_PDN_SHIFT 7 +#define CS53L30_ADCxB_PDN_MASK (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxB_PDN (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxA_PDN_SHIFT 6 +#define CS53L30_ADCxA_PDN_MASK (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_ADCxA_PDN (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_DMICx_PDN_SHIFT 2 +#define CS53L30_DMICx_PDN_MASK (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_PDN (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_SCLK_DIV_SHIFT 1 +#define CS53L30_DMICx_SCLK_DIV_MASK (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_DMICx_SCLK_DIV (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_CH_TYPE_SHIFT 0 +#define CS53L30_CH_TYPE_MASK (1 << CS53L30_CH_TYPE_SHIFT) +#define CS53L30_CH_TYPE (1 << CS53L30_CH_TYPE_SHIFT) + +#define CS53L30_ADCDMICx_PDN_MASK 0xFF +#define CS53L30_ADCDMICx_CTL1_DEFAULT (CS53L30_DMICx_PDN) + +/* R38/R46 (0x26/0x2E) CS53L30_ADCDMICx_CTL2 - ADC1/DMIC1 & ADC2/DMIC2 Control 2 */ +#define CS53L30_ADCx_NOTCH_DIS_SHIFT 7 +#define CS53L30_ADCx_NOTCH_DIS_MASK (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCx_NOTCH_DIS (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCxB_INV_SHIFT 5 +#define CS53L30_ADCxB_INV_MASK (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxB_INV (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxA_INV_SHIFT 4 +#define CS53L30_ADCxA_INV_MASK (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxA_INV (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST_SHIFT 1 +#define CS53L30_ADCxB_DIG_BOOST_MASK (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST_SHIFT 0 +#define CS53L30_ADCxA_DIG_BOOST_MASK (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) + +#define CS53L30_ADCDMIC1_CTL2_DEFAULT (0) + +/* R39/R47 (0x27/0x2F) CS53L30_ADCx_CTL3 - ADC1/ADC2 Control 3 */ +#define CS53L30_ADCx_HPF_EN_SHIFT 3 +#define CS53L30_ADCx_HPF_EN_MASK (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_EN (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_CF_SHIFT 1 +#define CS53L30_ADCx_HPF_CF_WIDTH 2 +#define CS53L30_ADCx_HPF_CF_MASK (((1 << CS53L30_ADCx_HPF_CF_WIDTH) - 1) << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_1HZ86 (0 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_120HZ (1 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_235HZ (2 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_466HZ (3 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_NG_ALL_SHIFT 0 +#define CS53L30_ADCx_NG_ALL_MASK (1 << CS53L30_ADCx_NG_ALL_SHIFT) +#define CS53L30_ADCx_NG_ALL (1 << CS53L30_ADCx_NG_ALL_SHIFT) + +#define CS53L30_ADCx_CTL3_DEFAULT (CS53L30_ADCx_HPF_EN) + +/* R40/R48 (0x28/0x30) CS53L30_ADCx_NG_CTL - ADC1/ADC2 Noise Gate Control */ +#define CS53L30_ADCxB_NG_SHIFT 7 +#define CS53L30_ADCxB_NG_MASK (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxB_NG (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxA_NG_SHIFT 6 +#define CS53L30_ADCxA_NG_MASK (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCxA_NG (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCx_NG_BOOST_SHIFT 5 +#define CS53L30_ADCx_NG_BOOST_MASK (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_BOOST (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_THRESH_SHIFT 2 +#define CS53L30_ADCx_NG_THRESH_WIDTH 3 +#define CS53L30_ADCx_NG_THRESH_MASK (((1 << CS53L30_ADCx_NG_THRESH_WIDTH) - 1) << CS53L30_ADCx_NG_THRESH_SHIFT) +#define CS53L30_ADCx_NG_DELAY_SHIFT 0 +#define CS53L30_ADCx_NG_DELAY_WIDTH 2 +#define CS53L30_ADCx_NG_DELAY_MASK (((1 << CS53L30_ADCx_NG_DELAY_WIDTH) - 1) << CS53L30_ADCx_NG_DELAY_SHIFT) + +#define CS53L30_ADCx_NG_CTL_DEFAULT (0) + +/* R41/R42/R49/R50 (0x29/0x2A/0x31/0x32) CS53L30_ADCxy_AFE_CTL - ADC1A/1B/2A/2B AFE Control */ +#define CS53L30_ADCxy_PREAMP_SHIFT 6 +#define CS53L30_ADCxy_PREAMP_WIDTH 2 +#define CS53L30_ADCxy_PREAMP_MASK (((1 << CS53L30_ADCxy_PREAMP_WIDTH) - 1) << CS53L30_ADCxy_PREAMP_SHIFT) +#define CS53L30_ADCxy_PGA_VOL_SHIFT 0 +#define CS53L30_ADCxy_PGA_VOL_WIDTH 6 +#define CS53L30_ADCxy_PGA_VOL_MASK (((1 << CS53L30_ADCxy_PGA_VOL_WIDTH) - 1) << CS53L30_ADCxy_PGA_VOL_SHIFT) + +#define CS53L30_ADCxy_AFE_CTL_DEFAULT (0) + +/* R43/R44/R51/R52 (0x2B/0x2C/0x33/0x34) CS53L30_ADCxy_DIG_VOL - ADC1A/1B/2A/2B Digital Volume */ +#define CS53L30_ADCxy_VOL_MUTE (0x80) + +#define CS53L30_ADCxy_DIG_VOL_DEFAULT (0x0) + +/* CS53L30_INT */ +#define CS53L30_PDN_DONE (1 << 7) +#define CS53L30_THMS_TRIP (1 << 6) +#define CS53L30_SYNC_DONE (1 << 5) +#define CS53L30_ADC2B_OVFL (1 << 4) +#define CS53L30_ADC2A_OVFL (1 << 3) +#define CS53L30_ADC1B_OVFL (1 << 2) +#define CS53L30_ADC1A_OVFL (1 << 1) +#define CS53L30_MUTE_PIN (1 << 0) +#define CS53L30_DEVICE_INT_MASK 0xFF + +#endif /* __CS53L30_H__ */ diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index e5527bc57..bcf1834c5 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -1247,8 +1247,8 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return -EINVAL; } - /* By default only 32 BCLK per WCLK is supported */ - dai_clk_mode |= DA7213_DAI_BCLKS_PER_WCLK_32; + /* By default only 64 BCLK per WCLK is supported */ + dai_clk_mode |= DA7213_DAI_BCLKS_PER_WCLK_64; snd_soc_write(codec, DA7213_DAI_CLK_MODE, dai_clk_mode); snd_soc_update_bits(codec, DA7213_DAI_CTRL, DA7213_DAI_FORMAT_MASK, diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c index 9459593ee..f0057cd22 100644 --- a/sound/soc/codecs/da7219-aad.c +++ b/sound/soc/codecs/da7219-aad.c @@ -13,8 +13,8 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/of_device.h> -#include <linux/of_irq.h> +#include <linux/i2c.h> +#include <linux/property.h> #include <linux/pm_wakeirq.h> #include <linux/slab.h> #include <linux/delay.h> @@ -382,11 +382,11 @@ static irqreturn_t da7219_aad_irq_thread(int irq, void *data) } /* - * DT to pdata conversion + * DT/ACPI to pdata conversion */ static enum da7219_aad_micbias_pulse_lvl - da7219_aad_of_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val) { switch (val) { case 2800: @@ -400,7 +400,7 @@ static enum da7219_aad_micbias_pulse_lvl } static enum da7219_aad_btn_cfg - da7219_aad_of_btn_cfg(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_btn_cfg(struct snd_soc_codec *codec, u32 val) { switch (val) { case 2: @@ -424,7 +424,7 @@ static enum da7219_aad_btn_cfg } static enum da7219_aad_mic_det_thr - da7219_aad_of_mic_det_thr(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_mic_det_thr(struct snd_soc_codec *codec, u32 val) { switch (val) { case 200: @@ -442,7 +442,7 @@ static enum da7219_aad_mic_det_thr } static enum da7219_aad_jack_ins_deb - da7219_aad_of_jack_ins_deb(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_jack_ins_deb(struct snd_soc_codec *codec, u32 val) { switch (val) { case 5: @@ -468,7 +468,7 @@ static enum da7219_aad_jack_ins_deb } static enum da7219_aad_jack_det_rate - da7219_aad_of_jack_det_rate(struct snd_soc_codec *codec, const char *str) + da7219_aad_fw_jack_det_rate(struct snd_soc_codec *codec, const char *str) { if (!strcmp(str, "32ms_64ms")) { return DA7219_AAD_JACK_DET_RATE_32_64MS; @@ -485,7 +485,7 @@ static enum da7219_aad_jack_det_rate } static enum da7219_aad_jack_rem_deb - da7219_aad_of_jack_rem_deb(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_jack_rem_deb(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -503,7 +503,7 @@ static enum da7219_aad_jack_rem_deb } static enum da7219_aad_btn_avg - da7219_aad_of_btn_avg(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_btn_avg(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -521,7 +521,7 @@ static enum da7219_aad_btn_avg } static enum da7219_aad_adc_1bit_rpt - da7219_aad_of_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -538,97 +538,96 @@ static enum da7219_aad_adc_1bit_rpt } } -static struct da7219_aad_pdata *da7219_aad_of_to_pdata(struct snd_soc_codec *codec) +static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct snd_soc_codec *codec) { - struct device_node *np = codec->dev->of_node; - struct device_node *aad_np = of_find_node_by_name(np, "da7219_aad"); + struct device *dev = codec->dev; + struct i2c_client *i2c = to_i2c_client(dev); + struct fwnode_handle *aad_np; struct da7219_aad_pdata *aad_pdata; - const char *of_str; - u32 of_val32; + const char *fw_str; + u32 fw_val32; + aad_np = device_get_named_child_node(dev, "da7219_aad"); if (!aad_np) return NULL; aad_pdata = devm_kzalloc(codec->dev, sizeof(*aad_pdata), GFP_KERNEL); if (!aad_pdata) - goto out; + return NULL; - aad_pdata->irq = irq_of_parse_and_map(np, 0); + aad_pdata->irq = i2c->irq; - if (of_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", - &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", + &fw_val32) >= 0) aad_pdata->micbias_pulse_lvl = - da7219_aad_of_micbias_pulse_lvl(codec, of_val32); + da7219_aad_fw_micbias_pulse_lvl(codec, fw_val32); else aad_pdata->micbias_pulse_lvl = DA7219_AAD_MICBIAS_PULSE_LVL_OFF; - if (of_property_read_u32(aad_np, "dlg,micbias-pulse-time", - &of_val32) >= 0) - aad_pdata->micbias_pulse_time = of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-time", + &fw_val32) >= 0) + aad_pdata->micbias_pulse_time = fw_val32; - if (of_property_read_u32(aad_np, "dlg,btn-cfg", &of_val32) >= 0) - aad_pdata->btn_cfg = da7219_aad_of_btn_cfg(codec, of_val32); + if (fwnode_property_read_u32(aad_np, "dlg,btn-cfg", &fw_val32) >= 0) + aad_pdata->btn_cfg = da7219_aad_fw_btn_cfg(codec, fw_val32); else aad_pdata->btn_cfg = DA7219_AAD_BTN_CFG_10MS; - if (of_property_read_u32(aad_np, "dlg,mic-det-thr", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,mic-det-thr", &fw_val32) >= 0) aad_pdata->mic_det_thr = - da7219_aad_of_mic_det_thr(codec, of_val32); + da7219_aad_fw_mic_det_thr(codec, fw_val32); else aad_pdata->mic_det_thr = DA7219_AAD_MIC_DET_THR_500_OHMS; - if (of_property_read_u32(aad_np, "dlg,jack-ins-deb", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,jack-ins-deb", &fw_val32) >= 0) aad_pdata->jack_ins_deb = - da7219_aad_of_jack_ins_deb(codec, of_val32); + da7219_aad_fw_jack_ins_deb(codec, fw_val32); else aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS; - if (!of_property_read_string(aad_np, "dlg,jack-det-rate", &of_str)) + if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str)) aad_pdata->jack_det_rate = - da7219_aad_of_jack_det_rate(codec, of_str); + da7219_aad_fw_jack_det_rate(codec, fw_str); else aad_pdata->jack_det_rate = DA7219_AAD_JACK_DET_RATE_256_512MS; - if (of_property_read_u32(aad_np, "dlg,jack-rem-deb", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,jack-rem-deb", &fw_val32) >= 0) aad_pdata->jack_rem_deb = - da7219_aad_of_jack_rem_deb(codec, of_val32); + da7219_aad_fw_jack_rem_deb(codec, fw_val32); else aad_pdata->jack_rem_deb = DA7219_AAD_JACK_REM_DEB_1MS; - if (of_property_read_u32(aad_np, "dlg,a-d-btn-thr", &of_val32) >= 0) - aad_pdata->a_d_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,a-d-btn-thr", &fw_val32) >= 0) + aad_pdata->a_d_btn_thr = (u8) fw_val32; else aad_pdata->a_d_btn_thr = 0xA; - if (of_property_read_u32(aad_np, "dlg,d-b-btn-thr", &of_val32) >= 0) - aad_pdata->d_b_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,d-b-btn-thr", &fw_val32) >= 0) + aad_pdata->d_b_btn_thr = (u8) fw_val32; else aad_pdata->d_b_btn_thr = 0x16; - if (of_property_read_u32(aad_np, "dlg,b-c-btn-thr", &of_val32) >= 0) - aad_pdata->b_c_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,b-c-btn-thr", &fw_val32) >= 0) + aad_pdata->b_c_btn_thr = (u8) fw_val32; else aad_pdata->b_c_btn_thr = 0x21; - if (of_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &of_val32) >= 0) - aad_pdata->c_mic_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &fw_val32) >= 0) + aad_pdata->c_mic_btn_thr = (u8) fw_val32; else aad_pdata->c_mic_btn_thr = 0x3E; - if (of_property_read_u32(aad_np, "dlg,btn-avg", &of_val32) >= 0) - aad_pdata->btn_avg = da7219_aad_of_btn_avg(codec, of_val32); + if (fwnode_property_read_u32(aad_np, "dlg,btn-avg", &fw_val32) >= 0) + aad_pdata->btn_avg = da7219_aad_fw_btn_avg(codec, fw_val32); else aad_pdata->btn_avg = DA7219_AAD_BTN_AVG_2; - if (of_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &fw_val32) >= 0) aad_pdata->adc_1bit_rpt = - da7219_aad_of_adc_1bit_rpt(codec, of_val32); + da7219_aad_fw_adc_1bit_rpt(codec, fw_val32); else aad_pdata->adc_1bit_rpt = DA7219_AAD_ADC_1BIT_RPT_1; -out: - of_node_put(aad_np); - return aad_pdata; } @@ -769,9 +768,9 @@ int da7219_aad_init(struct snd_soc_codec *codec) da7219->aad = da7219_aad; da7219_aad->codec = codec; - /* Handle any DT/platform data */ - if ((codec->dev->of_node) && (da7219->pdata)) - da7219->pdata->aad_pdata = da7219_aad_of_to_pdata(codec); + /* Handle any DT/ACPI/platform data */ + if (da7219->pdata && !da7219->pdata->aad_pdata) + da7219->pdata->aad_pdata = da7219_aad_fw_to_pdata(codec); da7219_aad_handle_pdata(codec); diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index 5c93899f1..50ea94317 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -15,6 +15,7 @@ #include <linux/clk.h> #include <linux/i2c.h> #include <linux/of_device.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/pm.h> @@ -1418,7 +1419,7 @@ static struct snd_soc_dai_driver da7219_dai = { /* - * DT + * DT/ACPI */ static const struct of_device_id da7219_of_match[] = { @@ -1434,7 +1435,7 @@ static const struct acpi_device_id da7219_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, da7219_acpi_match); static enum da7219_micbias_voltage - da7219_of_micbias_lvl(struct snd_soc_codec *codec, u32 val) + da7219_fw_micbias_lvl(struct device *dev, u32 val) { switch (val) { case 1600: @@ -1450,13 +1451,13 @@ static enum da7219_micbias_voltage case 2600: return DA7219_MICBIAS_2_6V; default: - dev_warn(codec->dev, "Invalid micbias level"); + dev_warn(dev, "Invalid micbias level"); return DA7219_MICBIAS_2_2V; } } static enum da7219_mic_amp_in_sel - da7219_of_mic_amp_in_sel(struct snd_soc_codec *codec, const char *str) + da7219_fw_mic_amp_in_sel(struct device *dev, const char *str) { if (!strcmp(str, "diff")) { return DA7219_MIC_AMP_IN_SEL_DIFF; @@ -1465,29 +1466,29 @@ static enum da7219_mic_amp_in_sel } else if (!strcmp(str, "se_n")) { return DA7219_MIC_AMP_IN_SEL_SE_N; } else { - dev_warn(codec->dev, "Invalid mic input type selection"); + dev_warn(dev, "Invalid mic input type selection"); return DA7219_MIC_AMP_IN_SEL_DIFF; } } -static struct da7219_pdata *da7219_of_to_pdata(struct snd_soc_codec *codec) +static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_codec *codec) { - struct device_node *np = codec->dev->of_node; + struct device *dev = codec->dev; struct da7219_pdata *pdata; const char *of_str; u32 of_val32; - pdata = devm_kzalloc(codec->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; - if (of_property_read_u32(np, "dlg,micbias-lvl", &of_val32) >= 0) - pdata->micbias_lvl = da7219_of_micbias_lvl(codec, of_val32); + if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0) + pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32); else pdata->micbias_lvl = DA7219_MICBIAS_2_2V; - if (!of_property_read_string(np, "dlg,mic-amp-in-sel", &of_str)) - pdata->mic_amp_in_sel = da7219_of_mic_amp_in_sel(codec, of_str); + if (!device_property_read_string(dev, "dlg,mic-amp-in-sel", &of_str)) + pdata->mic_amp_in_sel = da7219_fw_mic_amp_in_sel(dev, of_str); else pdata->mic_amp_in_sel = DA7219_MIC_AMP_IN_SEL_DIFF; @@ -1662,11 +1663,10 @@ static int da7219_probe(struct snd_soc_codec *codec) break; } - /* Handle DT/Platform data */ - if (codec->dev->of_node) - da7219->pdata = da7219_of_to_pdata(codec); - else - da7219->pdata = dev_get_platdata(codec->dev); + /* Handle DT/ACPI/Platform data */ + da7219->pdata = dev_get_platdata(codec->dev); + if (!da7219->pdata) + da7219->pdata = da7219_fw_to_pdata(codec); da7219_handle_pdata(codec); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index 2abb742fc..4e181b270 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -1124,8 +1124,10 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll) } hdac_hdmi_parse_eld(edev, pin); - print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET, - pin->eld.eld_buffer, pin->eld.eld_size); + print_hex_dump_debug("ELD: ", + DUMP_PREFIX_OFFSET, 16, 1, + pin->eld.eld_buffer, pin->eld.eld_size, + true); } else { pin->eld.monitor_present = false; pin->eld.eld_valid = false; @@ -1816,6 +1818,7 @@ static const struct dev_pm_ops hdac_hdmi_pm = { static const struct hda_device_id hdmi_list[] = { HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0), HDA_CODEC_EXT_ENTRY(0x8086280a, 0x100000, "Broxton HDMI", 0), + HDA_CODEC_EXT_ENTRY(0x8086280b, 0x100000, "Kabylake HDMI", 0), {} }; diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 8e36e883e..f27d11562 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -112,7 +112,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, return ret; if (hcp->hcd.ops->audio_startup) { - ret = hcp->hcd.ops->audio_startup(dai->dev->parent); + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); if (ret) { mutex_lock(&hcp->current_stream_lock); hcp->current_stream = NULL; @@ -122,8 +122,8 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, } if (hcp->hcd.ops->get_eld) { - ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->eld, - sizeof(hcp->eld)); + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); if (!ret) { ret = snd_pcm_hw_constraint_eld(substream->runtime, @@ -144,7 +144,7 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, WARN_ON(hcp->current_stream != substream); - hcp->hcd.ops->audio_shutdown(dai->dev->parent); + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); mutex_lock(&hcp->current_stream_lock); hcp->current_stream = NULL; @@ -195,8 +195,8 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, hp.sample_rate = params_rate(params); hp.channels = params_channels(params); - return hcp->hcd.ops->hw_params(dai->dev->parent, &hcp->daifmt[dai->id], - &hp); + return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data, + &hcp->daifmt[dai->id], &hp); } static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, @@ -280,7 +280,8 @@ static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) dev_dbg(dai->dev, "%s()\n", __func__); if (hcp->hcd.ops->digital_mute) - return hcp->hcd.ops->digital_mute(dai->dev->parent, mute); + return hcp->hcd.ops->digital_mute(dai->dev->parent, + hcp->hcd.data, mute); return 0; } diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index cf0a39bb6..02352ed89 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -412,6 +412,7 @@ static int max98371_i2c_remove(struct i2c_client *client) static const struct i2c_device_id max98371_i2c_id[] = { { "max98371", 0 }, + { } }; MODULE_DEVICE_TABLE(i2c, max98371_i2c_id); diff --git a/sound/soc/codecs/max98504.c b/sound/soc/codecs/max98504.c new file mode 100644 index 000000000..a7320e709 --- /dev/null +++ b/sound/soc/codecs/max98504.c @@ -0,0 +1,383 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2013 - 2014 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., Ltd. + * + * 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/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <sound/soc.h> + +#include "max98504.h" + +static const char * const max98504_supply_names[] = { + "DVDD", + "DIOVDD", + "PVDD", +}; +#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names) + +struct max98504_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES]; + unsigned int pcm_rx_channels; + bool brownout_enable; + unsigned int brownout_threshold; + unsigned int brownout_attenuation; + unsigned int brownout_attack_hold; + unsigned int brownout_timed_hold; + unsigned int brownout_release_rate; +}; + +static struct reg_default max98504_reg_defaults[] = { + { 0x01, 0}, + { 0x02, 0}, + { 0x03, 0}, + { 0x04, 0}, + { 0x10, 0}, + { 0x11, 0}, + { 0x12, 0}, + { 0x13, 0}, + { 0x14, 0}, + { 0x15, 0}, + { 0x16, 0}, + { 0x17, 0}, + { 0x18, 0}, + { 0x19, 0}, + { 0x1A, 0}, + { 0x20, 0}, + { 0x21, 0}, + { 0x22, 0}, + { 0x23, 0}, + { 0x24, 0}, + { 0x25, 0}, + { 0x26, 0}, + { 0x27, 0}, + { 0x28, 0}, + { 0x30, 0}, + { 0x31, 0}, + { 0x32, 0}, + { 0x33, 0}, + { 0x34, 0}, + { 0x35, 0}, + { 0x36, 0}, + { 0x37, 0}, + { 0x38, 0}, + { 0x39, 0}, + { 0x40, 0}, + { 0x41, 0}, +}; + +static bool max98504_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_INTERRUPT_STATUS: + case MAX98504_INTERRUPT_FLAGS: + case MAX98504_INTERRUPT_FLAG_CLEARS: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_GLOBAL_ENABLE: + case MAX98504_SOFTWARE_RESET: + return true; + default: + return false; + } +} + +static bool max98504_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_SOFTWARE_RESET: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_INTERRUPT_FLAG_CLEARS: + return false; + default: + return true; + } +} + +static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, + max98504->pcm_rx_channels); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0); + break; + } + + return 0; +} + +static int max98504_component_probe(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + struct regmap *map = max98504->regmap; + int ret; + + ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies); + if (ret < 0) + return ret; + + regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1); + msleep(20); + + if (!max98504->brownout_enable) + return 0; + + regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1, + (max98504->brownout_threshold & 0x1f) << 3 | + (max98504->brownout_attenuation & 0x3)); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2, + max98504->brownout_attack_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3, + max98504->brownout_timed_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4, + max98504->brownout_release_rate & 0xff); + + return 0; +} + +static void max98504_component_remove(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies); +} + +static const char *spk_source_mux_text[] = { + "PCM Monomix", "Analog In", "PDM Left", "PDM Right" +}; + +static const struct soc_enum spk_source_mux_enum = + SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT, + 0, ARRAY_SIZE(spk_source_mux_text), + spk_source_mux_text); + +static const struct snd_kcontrol_new spk_source_mux = + SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum); + +static const struct snd_soc_dapm_route max98504_dapm_routes[] = { + { "SPKOUT", NULL, "Global Enable" }, + { "SPK Source", "PCM Monomix", "DAC PCM" }, + { "SPK Source", "Analog In", "AIN" }, + { "SPK Source", "PDM Left", "DAC PDM" }, + { "SPK Source", "PDM Right", "DAC PDM" }, +}; + +static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_INPUT("AIN"), + SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0, + max98504_pcm_rx_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0), + SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux), + SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT", + MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0), +}; + +static int max98504_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask); + max98504->pcm_rx_channels = rx_mask; + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask); + break; + default: + WARN_ON(1); + } + + return 0; +} +static int max98504_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + unsigned int i, sources = 0; + + for (i = 0; i < tx_num; i++) + if (tx_slot[i]) + sources |= (1 << i); + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES, + sources); + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_CONTROL, sources); + break; + default: + WARN_ON(1); + } + + regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01); + + return 0; +} + +static const struct snd_soc_dai_ops max98504_dai_ops = { + .set_tdm_slot = max98504_set_tdm_slot, + .set_channel_map = max98504_set_channel_map, +}; + +#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\ + SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE) +#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\ + SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\ + SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai_driver max98504_dai[] = { + /* TODO: Add the PCM interface definitions */ + { + .name = "max98504-aif2", + .id = MAX98504_DAI_ID_PDM, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .ops = &max98504_dai_ops, + }, +}; + +static const struct snd_soc_component_driver max98504_component_driver = { + .probe = max98504_component_probe, + .remove = max98504_component_remove, + .dapm_widgets = max98504_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets), + .dapm_routes = max98504_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes), +}; + +static const struct regmap_config max98504_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98504_MAX_REGISTER, + .reg_defaults = max98504_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults), + .volatile_reg = max98504_volatile_register, + .readable_reg = max98504_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98504_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct max98504_priv *max98504; + int i, ret; + + max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL); + if (!max98504) + return -ENOMEM; + + if (node) { + if (!of_property_read_u32(node, "maxim,brownout-threshold", + &max98504->brownout_threshold)) + max98504->brownout_enable = true; + + of_property_read_u32(node, "maxim,brownout-attenuation", + &max98504->brownout_attenuation); + of_property_read_u32(node, "maxim,brownout-attack-hold-ms", + &max98504->brownout_attack_hold); + of_property_read_u32(node, "maxim,brownout-timed-hold-ms", + &max98504->brownout_timed_hold); + of_property_read_u32(node, "maxim,brownout-release-rate-ms", + &max98504->brownout_release_rate); + } + + max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap); + if (IS_ERR(max98504->regmap)) { + ret = PTR_ERR(max98504->regmap); + dev_err(&client->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + for (i = 0; i < MAX98504_NUM_SUPPLIES; i++) + max98504->supplies[i].supply = max98504_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES, + max98504->supplies); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, max98504); + + return devm_snd_soc_register_component(dev, &max98504_component_driver, + max98504_dai, ARRAY_SIZE(max98504_dai)); +} + +#ifdef CONFIG_OF +static const struct of_device_id max98504_of_match[] = { + { .compatible = "maxim,max98504" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max98504_of_match); +#endif + +static const struct i2c_device_id max98504_i2c_id[] = { + { "max98504" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98504_i2c_id); + +static struct i2c_driver max98504_i2c_driver = { + .driver = { + .name = "max98504", + .of_match_table = of_match_ptr(max98504_of_match), + }, + .probe = max98504_i2c_probe, + .id_table = max98504_i2c_id, +}; +module_i2c_driver(max98504_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX98504 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98504.h b/sound/soc/codecs/max98504.h new file mode 100644 index 000000000..afbefad2d --- /dev/null +++ b/sound/soc/codecs/max98504.h @@ -0,0 +1,59 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2011 - 2012 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., Ltd. + * + * 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 MAX98504_H_ +#define MAX98504_H_ + +/* + * MAX98504 Register Definitions + */ +#define MAX98504_INTERRUPT_STATUS 0x01 +#define MAX98504_INTERRUPT_FLAGS 0x02 +#define MAX98504_INTERRUPT_ENABLE 0x03 +#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04 +#define MAX98504_GPIO_ENABLE 0x10 +#define MAX98504_GPIO_CONFIG 0x11 +#define MAX98504_WATCHDOG_ENABLE 0x12 +#define MAX98504_WATCHDOG_CONFIG 0x13 +#define MAX98504_WATCHDOG_CLEAR 0x14 +#define MAX98504_CLOCK_MONITOR_ENABLE 0x15 +#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16 +#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17 +#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18 +#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19 +#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a +#define MAX98504_PCM_RX_ENABLE 0x20 +#define MAX98504_PCM_TX_ENABLE 0x21 +#define MAX98504_PCM_TX_HIZ_CONTROL 0x22 +#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23 +#define MAX98504_PCM_MODE_CONFIG 0x24 +#define MAX98504_PCM_DSP_CONFIG 0x25 +#define MAX98504_PCM_CLOCK_SETUP 0x26 +#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27 +#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28 +#define MAX98504_PDM_TX_ENABLE 0x30 +#define MAX98504_PDM_TX_HIZ_CONTROL 0x31 +#define MAX98504_PDM_TX_CONTROL 0x32 +#define MAX98504_PDM_RX_ENABLE 0x33 +#define MAX98504_SPEAKER_ENABLE 0x34 +#define MAX98504_SPEAKER_SOURCE_SELECT 0x35 +#define MAX98504_MEASUREMENT_ENABLE 0x36 +#define MAX98504_ANALOGUE_INPUT_GAIN 0x37 +#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38 +#define MAX98504_GLOBAL_ENABLE 0x40 +#define MAX98504_SOFTWARE_RESET 0x41 +#define MAX98504_REV_ID 0x7fff + +#define MAX98504_MAX_REGISTER 0x7fff + +#define MAX98504_DAI_ID_PCM 1 +#define MAX98504_DAI_ID_PDM 2 + +#endif /* MAX98504_H_ */ diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c new file mode 100644 index 000000000..68074c92a --- /dev/null +++ b/sound/soc/codecs/max9860.c @@ -0,0 +1,753 @@ +/* + * Driver for the MAX9860 Mono Audio Voice Codec + * + * https://datasheets.maximintegrated.com/en/ds/MAX9860.pdf + * + * The driver does not support sidetone since the DVST register field is + * backwards with the mute near the maximum level instead of the minimum. + * + * Author: Peter Rosin <peda@axentia.s> + * Copyright 2016 Axentia Technologies + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "max9860.h" + +struct max9860_priv { + struct regmap *regmap; + struct regulator *dvddio; + struct notifier_block dvddio_nb; + u8 psclk; + unsigned long pclk_rate; + int fmt; +}; + +static int max9860_dvddio_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct max9860_priv *max9860 = container_of(nb, struct max9860_priv, + dvddio_nb); + if (event & REGULATOR_EVENT_DISABLE) { + regcache_mark_dirty(max9860->regmap); + regcache_cache_only(max9860->regmap, true); + } + + return 0; +} + +static const struct reg_default max9860_reg_defaults[] = { + { MAX9860_PWRMAN, 0x00 }, + { MAX9860_INTEN, 0x00 }, + { MAX9860_SYSCLK, 0x00 }, + { MAX9860_AUDIOCLKHIGH, 0x00 }, + { MAX9860_AUDIOCLKLOW, 0x00 }, + { MAX9860_IFC1A, 0x00 }, + { MAX9860_IFC1B, 0x00 }, + { MAX9860_VOICEFLTR, 0x00 }, + { MAX9860_DACATTN, 0x00 }, + { MAX9860_ADCLEVEL, 0x00 }, + { MAX9860_DACGAIN, 0x00 }, + { MAX9860_MICGAIN, 0x00 }, + { MAX9860_MICADC, 0x00 }, + { MAX9860_NOISEGATE, 0x00 }, +}; + +static bool max9860_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + case MAX9860_REVISION: + return true; + } + + return false; +} + +static bool max9860_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTEN ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + return true; + } + + return false; +} + +static bool max9860_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + case MAX9860_MICREADBACK: + return true; + } + + return false; +} + +static bool max9860_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + return true; + } + + return false; +} + +static const struct regmap_config max9860_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = max9860_readable, + .writeable_reg = max9860_writeable, + .volatile_reg = max9860_volatile, + .precious_reg = max9860_precious, + + .max_register = MAX9860_MAX_REGISTER, + .reg_defaults = max9860_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max9860_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static const DECLARE_TLV_DB_SCALE(dva_tlv, -9100, 100, 1); +static const DECLARE_TLV_DB_SCALE(dvg_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_RANGE(pam_tlv, + 0, MAX9860_PAM_MAX - 1, TLV_DB_SCALE_ITEM(-2000, 2000, 1), + MAX9860_PAM_MAX, MAX9860_PAM_MAX, TLV_DB_SCALE_ITEM(3000, 0, 0)); +static const DECLARE_TLV_DB_SCALE(pgam_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(anth_tlv, -7600, 400, 1); +static const DECLARE_TLV_DB_SCALE(agcth_tlv, -1800, 100, 0); + +static const char * const agchld_text[] = { + "AGC Disabled", "50ms", "100ms", "400ms" +}; + +static SOC_ENUM_SINGLE_DECL(agchld_enum, MAX9860_MICADC, + MAX9860_AGCHLD_SHIFT, agchld_text); + +static const char * const agcsrc_text[] = { + "Left ADC", "Left/Right ADC" +}; + +static SOC_ENUM_SINGLE_DECL(agcsrc_enum, MAX9860_MICADC, + MAX9860_AGCSRC_SHIFT, agcsrc_text); + +static const char * const agcatk_text[] = { + "3ms", "12ms", "50ms", "200ms" +}; + +static SOC_ENUM_SINGLE_DECL(agcatk_enum, MAX9860_MICADC, + MAX9860_AGCATK_SHIFT, agcatk_text); + +static const char * const agcrls_text[] = { + "78ms", "156ms", "312ms", "625ms", + "1.25s", "2.5s", "5s", "10s" +}; + +static SOC_ENUM_SINGLE_DECL(agcrls_enum, MAX9860_MICADC, + MAX9860_AGCRLS_SHIFT, agcrls_text); + +static const char * const filter_text[] = { + "Disabled", + "Elliptical HP 217Hz notch (16kHz)", + "Butterworth HP 500Hz (16kHz)", + "Elliptical HP 217Hz notch (8kHz)", + "Butterworth HP 500Hz (8kHz)", + "Butterworth HP 200Hz (48kHz)" +}; + +static SOC_ENUM_SINGLE_DECL(avflt_enum, MAX9860_VOICEFLTR, + MAX9860_AVFLT_SHIFT, filter_text); + +static SOC_ENUM_SINGLE_DECL(dvflt_enum, MAX9860_VOICEFLTR, + MAX9860_DVFLT_SHIFT, filter_text); + +static const struct snd_kcontrol_new max9860_controls[] = { +SOC_SINGLE_TLV("Master Playback Volume", MAX9860_DACATTN, + MAX9860_DVA_SHIFT, MAX9860_DVA_MUTE, 1, dva_tlv), +SOC_SINGLE_TLV("DAC Gain Volume", MAX9860_DACGAIN, + MAX9860_DVG_SHIFT, MAX9860_DVG_MAX, 0, dvg_tlv), +SOC_DOUBLE_TLV("Line Capture Volume", MAX9860_ADCLEVEL, + MAX9860_ADCLL_SHIFT, MAX9860_ADCRL_SHIFT, MAX9860_ADCxL_MIN, 1, + adc_tlv), + +SOC_ENUM("AGC Hold Time", agchld_enum), +SOC_ENUM("AGC/Noise Gate Source", agcsrc_enum), +SOC_ENUM("AGC Attack Time", agcatk_enum), +SOC_ENUM("AGC Release Time", agcrls_enum), + +SOC_SINGLE_TLV("Noise Gate Threshold Volume", MAX9860_NOISEGATE, + MAX9860_ANTH_SHIFT, MAX9860_ANTH_MAX, 0, anth_tlv), +SOC_SINGLE_TLV("AGC Signal Threshold Volume", MAX9860_NOISEGATE, + MAX9860_AGCTH_SHIFT, MAX9860_AGCTH_MIN, 1, agcth_tlv), + +SOC_SINGLE_TLV("Mic PGA Volume", MAX9860_MICGAIN, + MAX9860_PGAM_SHIFT, MAX9860_PGAM_MIN, 1, pgam_tlv), +SOC_SINGLE_TLV("Mic Preamp Volume", MAX9860_MICGAIN, + MAX9860_PAM_SHIFT, MAX9860_PAM_MAX, 0, pam_tlv), + +SOC_ENUM("ADC Filter", avflt_enum), +SOC_ENUM("DAC Filter", dvflt_enum), +}; + +static const struct snd_soc_dapm_widget max9860_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MICL"), +SND_SOC_DAPM_INPUT("MICR"), + +SND_SOC_DAPM_ADC("ADCL", NULL, MAX9860_PWRMAN, MAX9860_ADCLEN_SHIFT, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, MAX9860_PWRMAN, MAX9860_ADCREN_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_DAC("DAC", NULL, MAX9860_PWRMAN, MAX9860_DACEN_SHIFT, 0), + +SND_SOC_DAPM_OUTPUT("OUT"), + +SND_SOC_DAPM_SUPPLY("Supply", SND_SOC_NOPM, 0, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DVDD", 0, 0), +SND_SOC_DAPM_CLOCK_SUPPLY("mclk"), +}; + +static const struct snd_soc_dapm_route max9860_dapm_routes[] = { + { "ADCL", NULL, "MICL" }, + { "ADCR", NULL, "MICR" }, + { "AIFOUTL", NULL, "ADCL" }, + { "AIFOUTR", NULL, "ADCR" }, + + { "DAC", NULL, "AIFINL" }, + { "DAC", NULL, "AIFINR" }, + { "OUT", NULL, "DAC" }, + + { "Supply", NULL, "AVDD" }, + { "Supply", NULL, "DVDD" }, + { "Supply", NULL, "mclk" }, + + { "DAC", NULL, "Supply" }, + { "ADCL", NULL, "Supply" }, + { "ADCR", NULL, "Supply" }, +}; + +static int max9860_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 max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec); + u8 master; + u8 ifc1a = 0; + u8 ifc1b = 0; + u8 sysclk = 0; + unsigned long n; + int ret; + + dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), + params_channels(params)); + + if (params_channels(params) == 2) + ifc1b |= MAX9860_ST; + + switch (max9860->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + master = MAX9860_MASTER; + break; + default: + return -EINVAL; + } + ifc1a |= master; + + if (master) { + if (params_width(params) * params_channels(params) > 48) + ifc1b |= MAX9860_BSEL_64X; + else + ifc1b |= MAX9860_BSEL_48X; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ifc1a |= MAX9860_DDLY; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + ifc1a |= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_DSP_A: + if (params_width(params) != 16) { + dev_err(codec->dev, + "DSP_A works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_DDLY | MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_DSP_B: + if (params_width(params) != 16) { + dev_err(codec->dev, + "DSP_B works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + break; + default: + return -EINVAL; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_IB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + /* fall through */ + case SND_SOC_DAIFMT_IB_NF: + ifc1a ^= MAX9860_DBCI; + ifc1b ^= MAX9860_ABCI; + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "IFC1A %02x\n", ifc1a); + ret = regmap_write(max9860->regmap, MAX9860_IFC1A, ifc1a); + if (ret) { + dev_err(codec->dev, "Failed to set IFC1A: %d\n", ret); + return ret; + } + dev_dbg(codec->dev, "IFC1B %02x\n", ifc1b); + ret = regmap_write(max9860->regmap, MAX9860_IFC1B, ifc1b); + if (ret) { + dev_err(codec->dev, "Failed to set IFC1B: %d\n", ret); + return ret; + } + + /* + * Check if Integer Clock Mode is possible, but avoid it in slave mode + * since we then do not know if lrclk is derived from pclk and the + * datasheet mentions that the frequencies have to match exactly in + * order for this to work. + */ + if (params_rate(params) == 8000 || params_rate(params) == 16000) { + if (master) { + switch (max9860->pclk_rate) { + case 12000000: + sysclk = MAX9860_FREQ_12MHZ; + break; + case 13000000: + sysclk = MAX9860_FREQ_13MHZ; + break; + case 19200000: + sysclk = MAX9860_FREQ_19_2MHZ; + break; + default: + /* + * Integer Clock Mode not possible. Leave + * sysclk at zero and fall through to the + * code below for PLL mode. + */ + break; + } + + if (sysclk && params_rate(params) == 16000) + sysclk |= MAX9860_16KHZ; + } + } + + /* + * Largest possible n: + * 65536 * 96 * 48kHz / 10MHz -> 30199 + * Smallest possible n: + * 65536 * 96 * 8kHz / 20MHz -> 2517 + * Both fit nicely in the available 15 bits, no need to apply any mask. + */ + n = DIV_ROUND_CLOSEST_ULL(65536ULL * 96 * params_rate(params), + max9860->pclk_rate); + + if (!sysclk) { + /* PLL mode */ + if (params_rate(params) > 24000) + sysclk |= MAX9860_16KHZ; + + if (!master) + n |= 1; /* trigger rapid pll lock mode */ + } + + sysclk |= max9860->psclk; + dev_dbg(codec->dev, "SYSCLK %02x\n", sysclk); + ret = regmap_write(max9860->regmap, + MAX9860_SYSCLK, sysclk); + if (ret) { + dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); + return ret; + } + dev_dbg(codec->dev, "N %lu\n", n); + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKHIGH, n >> 8); + if (ret) { + dev_err(codec->dev, "Failed to set NHI: %d\n", ret); + return ret; + } + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKLOW, n & 0xff); + if (ret) { + dev_err(codec->dev, "Failed to set NLO: %d\n", ret); + return ret; + } + + if (!master) { + dev_dbg(codec->dev, "Enable PLL\n"); + ret = regmap_update_bits(max9860->regmap, MAX9860_AUDIOCLKHIGH, + MAX9860_PLL, MAX9860_PLL); + if (ret) { + dev_err(codec->dev, "Failed to enable PLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + max9860->fmt = fmt; + return 0; + + default: + return -EINVAL; + } +} + +static const struct snd_soc_dai_ops max9860_dai_ops = { + .hw_params = max9860_hw_params, + .set_fmt = max9860_set_fmt, +}; + +static struct snd_soc_dai_driver max9860_dai = { + .name = "max9860-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &max9860_dai_ops, + .symmetric_rates = 1, +}; + +static int max9860_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct max9860_priv *max9860 = dev_get_drvdata(codec->dev); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, MAX9860_SHDN); + if (ret) { + dev_err(codec->dev, "Failed to remove SHDN: %d\n", ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, 0); + if (ret) { + dev_err(codec->dev, "Failed to request SHDN: %d\n", + ret); + return ret; + } + break; + } + + return 0; +} + +static struct snd_soc_codec_driver max9860_codec_driver = { + .set_bias_level = max9860_set_bias_level, + .idle_bias_off = true, + + .controls = max9860_controls, + .num_controls = ARRAY_SIZE(max9860_controls), + .dapm_widgets = max9860_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9860_dapm_widgets), + .dapm_routes = max9860_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9860_dapm_routes), +}; + +#ifdef CONFIG_PM +static int max9860_suspend(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, MAX9860_PSCLK_OFF); + if (ret) { + dev_err(dev, "Failed to disable clock: %d\n", ret); + return ret; + } + + regulator_disable(max9860->dvddio); + + return 0; +} + +static int max9860_resume(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(max9860->dvddio); + if (ret) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + regcache_cache_only(max9860->regmap, false); + ret = regcache_sync(max9860->regmap); + if (ret) { + dev_err(dev, "Failed to sync cache: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, max9860->psclk); + if (ret) { + dev_err(dev, "Failed to enable clock: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops max9860_pm_ops = { + SET_RUNTIME_PM_OPS(max9860_suspend, max9860_resume, NULL) +}; + +static int max9860_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860; + int ret; + struct clk *mclk; + unsigned long mclk_rate; + int i; + int intr; + + max9860 = devm_kzalloc(dev, sizeof(struct max9860_priv), GFP_KERNEL); + if (!max9860) + return -ENOMEM; + + max9860->dvddio = devm_regulator_get(dev, "DVDDIO"); + if (IS_ERR(max9860->dvddio)) { + ret = PTR_ERR(max9860->dvddio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get DVDDIO supply: %d\n", ret); + return ret; + } + + max9860->dvddio_nb.notifier_call = max9860_dvddio_event; + + ret = regulator_register_notifier(max9860->dvddio, &max9860->dvddio_nb); + if (ret) + dev_err(dev, "Failed to register DVDDIO notifier: %d\n", ret); + + ret = regulator_enable(max9860->dvddio); + if (ret != 0) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + max9860->regmap = devm_regmap_init_i2c(i2c, &max9860_regmap); + if (IS_ERR(max9860->regmap)) { + ret = PTR_ERR(max9860->regmap); + goto err_regulator; + } + + dev_set_drvdata(dev, max9860); + + /* + * mclk has to be in the 10MHz to 60MHz range. + * psclk is used to scale mclk into pclk so that + * pclk is in the 10MHz to 20MHz range. + */ + mclk = clk_get(dev, "mclk"); + + if (IS_ERR(mclk)) { + ret = PTR_ERR(mclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get MCLK: %d\n", ret); + goto err_regulator; + } + + mclk_rate = clk_get_rate(mclk); + clk_put(mclk); + + if (mclk_rate > 60000000 || mclk_rate < 10000000) { + dev_err(dev, "Bad mclk %luHz (needs 10MHz - 60MHz)\n", + mclk_rate); + ret = -EINVAL; + goto err_regulator; + } + if (mclk_rate >= 40000000) + max9860->psclk = 3; + else if (mclk_rate >= 20000000) + max9860->psclk = 2; + else + max9860->psclk = 1; + max9860->pclk_rate = mclk_rate >> (max9860->psclk - 1); + max9860->psclk <<= MAX9860_PSCLK_SHIFT; + dev_dbg(dev, "mclk %lu pclk %lu\n", mclk_rate, max9860->pclk_rate); + + regcache_cache_bypass(max9860->regmap, true); + for (i = 0; i < max9860_regmap.num_reg_defaults; ++i) { + ret = regmap_write(max9860->regmap, + max9860_regmap.reg_defaults[i].reg, + max9860_regmap.reg_defaults[i].def); + if (ret) { + dev_err(dev, "Failed to initialize register %u: %d\n", + max9860_regmap.reg_defaults[i].reg, ret); + goto err_regulator; + } + } + regcache_cache_bypass(max9860->regmap, false); + + ret = regmap_read(max9860->regmap, MAX9860_INTRSTATUS, &intr); + if (ret) { + dev_err(dev, "Failed to clear INTRSTATUS: %d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = snd_soc_register_codec(dev, &max9860_codec_driver, + &max9860_dai, 1); + if (ret) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_disable(dev); +err_regulator: + regulator_disable(max9860->dvddio); + return ret; +} + +static int max9860_remove(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860 = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + pm_runtime_disable(dev); + regulator_disable(max9860->dvddio); + return 0; +} + +static const struct i2c_device_id max9860_i2c_id[] = { + { "max9860", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9860_i2c_id); + +static const struct of_device_id max9860_of_match[] = { + { .compatible = "maxim,max9860", }, + { } +}; +MODULE_DEVICE_TABLE(of, max9860_of_match); + +static struct i2c_driver max9860_i2c_driver = { + .probe = max9860_probe, + .remove = max9860_remove, + .id_table = max9860_i2c_id, + .driver = { + .name = "max9860", + .of_match_table = max9860_of_match, + .pm = &max9860_pm_ops, + }, +}; + +module_i2c_driver(max9860_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX9860 Mono Audio Voice Codec driver"); +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/max9860.h b/sound/soc/codecs/max9860.h new file mode 100644 index 000000000..22041bd67 --- /dev/null +++ b/sound/soc/codecs/max9860.h @@ -0,0 +1,162 @@ +/* + * Driver for the MAX9860 Mono Audio Voice Codec + * + * Author: Peter Rosin <peda@axentia.s> + * Copyright 2016 Axentia Technologies + * + * 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. + */ + +#ifndef _SND_SOC_MAX9860 +#define _SND_SOC_MAX9860 + +#define MAX9860_INTRSTATUS 0x00 +#define MAX9860_MICREADBACK 0x01 +#define MAX9860_INTEN 0x02 +#define MAX9860_SYSCLK 0x03 +#define MAX9860_AUDIOCLKHIGH 0x04 +#define MAX9860_AUDIOCLKLOW 0x05 +#define MAX9860_IFC1A 0x06 +#define MAX9860_IFC1B 0x07 +#define MAX9860_VOICEFLTR 0x08 +#define MAX9860_DACATTN 0x09 +#define MAX9860_ADCLEVEL 0x0a +#define MAX9860_DACGAIN 0x0b +#define MAX9860_MICGAIN 0x0c +#define MAX9860_RESERVED 0x0d +#define MAX9860_MICADC 0x0e +#define MAX9860_NOISEGATE 0x0f +#define MAX9860_PWRMAN 0x10 +#define MAX9860_REVISION 0xff + +#define MAX9860_MAX_REGISTER 0xff + +/* INTRSTATUS */ +#define MAX9860_CLD 0x80 +#define MAX9860_SLD 0x40 +#define MAX9860_ULK 0x20 + +/* MICREADBACK */ +#define MAX9860_NG 0xe0 +#define MAX9860_AGC 0x1f + +/* INTEN */ +#define MAX9860_ICLD 0x80 +#define MAX9860_ISLD 0x40 +#define MAX9860_IULK 0x20 + +/* SYSCLK */ +#define MAX9860_PSCLK 0x30 +#define MAX9860_PSCLK_OFF 0x00 +#define MAX9860_PSCLK_SHIFT 4 +#define MAX9860_FREQ 0x06 +#define MAX9860_FREQ_NORMAL 0x00 +#define MAX9860_FREQ_12MHZ 0x02 +#define MAX9860_FREQ_13MHZ 0x04 +#define MAX9860_FREQ_19_2MHZ 0x06 +#define MAX9860_16KHZ 0x01 + +/* AUDIOCLKHIGH */ +#define MAX9860_PLL 0x80 +#define MAX9860_NHI 0x7f + +/* AUDIOCLKLOW */ +#define MAX9860_NLO 0xff + +/* IFC1A */ +#define MAX9860_MASTER 0x80 +#define MAX9860_WCI 0x40 +#define MAX9860_DBCI 0x20 +#define MAX9860_DDLY 0x10 +#define MAX9860_HIZ 0x08 +#define MAX9860_TDM 0x04 + +/* IFC1B */ +#define MAX9860_ABCI 0x20 +#define MAX9860_ADLY 0x10 +#define MAX9860_ST 0x08 +#define MAX9860_BSEL 0x07 +#define MAX9860_BSEL_OFF 0x00 +#define MAX9860_BSEL_64X 0x01 +#define MAX9860_BSEL_48X 0x02 +#define MAX9860_BSEL_PCLK_2 0x04 +#define MAX9860_BSEL_PCLK_4 0x05 +#define MAX9860_BSEL_PCLK_8 0x06 +#define MAX9860_BSEL_PCLK_16 0x07 + +/* VOICEFLTR */ +#define MAX9860_AVFLT 0xf0 +#define MAX9860_AVFLT_SHIFT 4 +#define MAX9860_AVFLT_COUNT 6 +#define MAX9860_DVFLT 0x0f +#define MAX9860_DVFLT_SHIFT 0 +#define MAX9860_DVFLT_COUNT 6 + +/* DACATTN */ +#define MAX9860_DVA 0xfe +#define MAX9860_DVA_SHIFT 1 +#define MAX9860_DVA_MUTE 0x5e + +/* ADCLEVEL */ +#define MAX9860_ADCRL 0xf0 +#define MAX9860_ADCRL_SHIFT 4 +#define MAX9860_ADCLL 0x0f +#define MAX9860_ADCLL_SHIFT 0 +#define MAX9860_ADCxL_MIN 15 + +/* DACGAIN */ +#define MAX9860_DVG 0x60 +#define MAX9860_DVG_SHIFT 5 +#define MAX9860_DVG_MAX 3 +#define MAX9860_DVST 0x1f +#define MAX9860_DVST_SHIFT 0 +#define MAX9860_DVST_MIN 31 + +/* MICGAIN */ +#define MAX9860_PAM 0x60 +#define MAX9860_PAM_SHIFT 5 +#define MAX9860_PAM_MAX 3 +#define MAX9860_PGAM 0x1f +#define MAX9860_PGAM_SHIFT 0 +#define MAX9860_PGAM_MIN 20 + +/* MICADC */ +#define MAX9860_AGCSRC 0x80 +#define MAX9860_AGCSRC_SHIFT 7 +#define MAX9860_AGCSRC_COUNT 2 +#define MAX9860_AGCRLS 0x70 +#define MAX9860_AGCRLS_SHIFT 4 +#define MAX9860_AGCRLS_COUNT 8 +#define MAX9860_AGCATK 0x0c +#define MAX9860_AGCATK_SHIFT 2 +#define MAX9860_AGCATK_COUNT 4 +#define MAX9860_AGCHLD 0x03 +#define MAX9860_AGCHLD_OFF 0x00 +#define MAX9860_AGCHLD_SHIFT 0 +#define MAX9860_AGCHLD_COUNT 4 + +/* NOISEGATE */ +#define MAX9860_ANTH 0xf0 +#define MAX9860_ANTH_SHIFT 4 +#define MAX9860_ANTH_MAX 15 +#define MAX9860_AGCTH 0x0f +#define MAX9860_AGCTH_SHIFT 0 +#define MAX9860_AGCTH_MIN 15 + +/* PWRMAN */ +#define MAX9860_SHDN 0x80 +#define MAX9860_DACEN 0x08 +#define MAX9860_DACEN_SHIFT 3 +#define MAX9860_ADCLEN 0x02 +#define MAX9860_ADCLEN_SHIFT 1 +#define MAX9860_ADCREN 0x01 +#define MAX9860_ADCREN_SHIFT 0 + +#endif /* _SND_SOC_MAX9860 */ diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 2a22fddeb..2a22fddeb 100755..100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c diff --git a/sound/soc/codecs/max9867.h b/sound/soc/codecs/max9867.h index 65590b4ad..65590b4ad 100755..100644 --- a/sound/soc/codecs/max9867.h +++ b/sound/soc/codecs/max9867.h diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h index 6da72290a..368343f29 100644 --- a/sound/soc/codecs/max9877.h +++ b/sound/soc/codecs/max9877.h @@ -32,6 +32,4 @@ #define MAX9877_BYPASS (1 << 6) #define MAX9877_SHDN (1 << 7) -extern int max9877_add_controls(struct snd_soc_codec *codec); - #endif diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 683769f0f..2e59a85e3 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -18,6 +18,7 @@ #include <linux/clk.h> #include <linux/acpi.h> #include <linux/math64.h> +#include <linux/semaphore.h> #include <sound/initval.h> #include <sound/tlv.h> @@ -30,10 +31,22 @@ #include "nau8825.h" + +#define NUVOTON_CODEC_DAI "nau8825-hifi" + #define NAU_FREF_MAX 13500000 -#define NAU_FVCO_MAX 100000000 +#define NAU_FVCO_MAX 124000000 #define NAU_FVCO_MIN 90000000 +/* cross talk suppression detection */ +#define LOG10_MAGIC 646456993 +#define GAIN_AUGMENT 22500 +#define SIDETONE_BASE 207000 + + +static int nau8825_configure_sysclk(struct nau8825 *nau8825, + int clk_id, unsigned int freq); + struct nau8825_fll { int mclk_src; int ratio; @@ -156,6 +169,643 @@ static const struct reg_default nau8825_reg_defaults[] = { { NAU8825_REG_CHARGE_PUMP, 0x0 }, }; +/* register backup table when cross talk detection */ +static struct reg_default nau8825_xtalk_baktab[] = { + { NAU8825_REG_ADC_DGAIN_CTRL, 0 }, + { NAU8825_REG_HSVOL_CTRL, 0 }, + { NAU8825_REG_DACL_CTRL, 0 }, + { NAU8825_REG_DACR_CTRL, 0 }, +}; + +static const unsigned short logtable[256] = { + 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7, + 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508, + 0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6, + 0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37, + 0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f, + 0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41, + 0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1, + 0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142, + 0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68, + 0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355, + 0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c, + 0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490, + 0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3, + 0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507, + 0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe, + 0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca, + 0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c, + 0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7, + 0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c, + 0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c, + 0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a, + 0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065, + 0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730, + 0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc, + 0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469, + 0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9, + 0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c, + 0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765, + 0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83, + 0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387, + 0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973, + 0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47 +}; + +/** + * nau8825_sema_acquire - acquire the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * @timeout: how long in jiffies to wait before failure or zero to wait + * until release + * + * Attempts to acquire the semaphore with number of jiffies. If no more + * tasks are allowed to acquire the semaphore, calling this function will + * put the task to sleep. If the semaphore is not released within the + * specified number of jiffies, this function returns. + * Acquires the semaphore without jiffies. If no more tasks are allowed + * to acquire the semaphore, calling this function will put the task to + * sleep until the semaphore is released. + * If the semaphore is not released within the specified number of jiffies, + * this function returns -ETIME. + * If the sleep is interrupted by a signal, this function will return -EINTR. + * It returns 0 if the semaphore was acquired successfully. + */ +static int nau8825_sema_acquire(struct nau8825 *nau8825, long timeout) +{ + int ret; + + if (timeout) { + ret = down_timeout(&nau8825->xtalk_sem, timeout); + if (ret < 0) + dev_warn(nau8825->dev, "Acquire semaphone timeout\n"); + } else { + ret = down_interruptible(&nau8825->xtalk_sem); + if (ret < 0) + dev_warn(nau8825->dev, "Acquire semaphone fail\n"); + } + + return ret; +} + +/** + * nau8825_sema_release - release the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * + * Release the semaphore which may be called from any context and + * even by tasks which have never called down(). + */ +static inline void nau8825_sema_release(struct nau8825 *nau8825) +{ + up(&nau8825->xtalk_sem); +} + +/** + * nau8825_sema_reset - reset the semaphore for nau88l25 + * @nau8825: component to register the codec private data with + * + * Reset the counter of the semaphore. Call this function to restart + * a new round task management. + */ +static inline void nau8825_sema_reset(struct nau8825 *nau8825) +{ + nau8825->xtalk_sem.count = 1; +} + +/** + * Ramp up the headphone volume change gradually to target level. + * + * @nau8825: component to register the codec private data with + * @vol_from: the volume to start up + * @vol_to: the target volume + * @step: the volume span to move on + * + * The headphone volume is from 0dB to minimum -54dB and -1dB per step. + * If the volume changes sharp, there is a pop noise heard in headphone. We + * provide the function to ramp up the volume up or down by delaying 10ms + * per step. + */ +static void nau8825_hpvol_ramp(struct nau8825 *nau8825, + unsigned int vol_from, unsigned int vol_to, unsigned int step) +{ + unsigned int value, volume, ramp_up, from, to; + + if (vol_from == vol_to || step == 0) { + return; + } else if (vol_from < vol_to) { + ramp_up = true; + from = vol_from; + to = vol_to; + } else { + ramp_up = false; + from = vol_to; + to = vol_from; + } + /* only handle volume from 0dB to minimum -54dB */ + if (to > NAU8825_HP_VOL_MIN) + to = NAU8825_HP_VOL_MIN; + + for (volume = from; volume < to; volume += step) { + if (ramp_up) + value = volume; + else + value = to - volume + from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); + usleep_range(10000, 10500); + } + if (ramp_up) + value = to; + else + value = from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); +} + +/** + * Computes log10 of a value; the result is round off to 3 decimal. This func- + * tion takes reference to dvb-math. The source code locates as the following. + * Linux/drivers/media/dvb-core/dvb_math.c + * + * return log10(value) * 1000 + */ +static u32 nau8825_intlog10_dec3(u32 value) +{ + u32 msb, logentry, significand, interpolation, log10val; + u64 log2val; + + /* first detect the msb (count begins at 0) */ + msb = fls(value) - 1; + /** + * now we use a logtable after the following method: + * + * log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24 + * where x = msb and therefore 1 <= y < 2 + * first y is determined by shifting the value left + * so that msb is bit 31 + * 0x00231f56 -> 0x8C7D5800 + * the result is y * 2^31 -> "significand" + * then the highest 9 bits are used for a table lookup + * the highest bit is discarded because it's always set + * the highest nine bits in our example are 100011000 + * so we would use the entry 0x18 + */ + significand = value << (31 - msb); + logentry = (significand >> 23) & 0xff; + /** + * last step we do is interpolation because of the + * limitations of the log table the error is that part of + * the significand which isn't used for lookup then we + * compute the ratio between the error and the next table entry + * and interpolate it between the log table entry used and the + * next one the biggest error possible is 0x7fffff + * (in our example it's 0x7D5800) + * needed value for next table entry is 0x800000 + * so the interpolation is + * (error / 0x800000) * (logtable_next - logtable_current) + * in the implementation the division is moved to the end for + * better accuracy there is also an overflow correction if + * logtable_next is 256 + */ + interpolation = ((significand & 0x7fffff) * + ((logtable[(logentry + 1) & 0xff] - + logtable[logentry]) & 0xffff)) >> 15; + + log2val = ((msb << 24) + (logtable[logentry] << 8) + interpolation); + /** + * log10(x) = log2(x) * log10(2) + */ + log10val = (log2val * LOG10_MAGIC) >> 31; + /** + * the result is round off to 3 decimal + */ + return log10val / ((1 << 24) / 1000); +} + +/** + * computes cross talk suppression sidetone gain. + * + * @sig_org: orignal signal level + * @sig_cros: cross talk signal level + * + * The orignal and cross talk signal vlues need to be characterized. + * Once these values have been characterized, this sidetone value + * can be converted to decibel with the equation below. + * sidetone = 20 * log (original signal level / crosstalk signal level) + * + * return cross talk sidetone gain + */ +static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros) +{ + u32 gain, sidetone; + + if (unlikely(sig_org == 0) || unlikely(sig_cros == 0)) { + WARN_ON(1); + return 0; + } + + sig_org = nau8825_intlog10_dec3(sig_org); + sig_cros = nau8825_intlog10_dec3(sig_cros); + if (sig_org >= sig_cros) + gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT; + else + gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT; + sidetone = SIDETONE_BASE - gain * 2; + sidetone /= 1000; + + return sidetone; +} + +static int nau8825_xtalk_baktab_index_by_reg(unsigned int reg) +{ + int index; + + for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++) + if (nau8825_xtalk_baktab[index].reg == reg) + return index; + return -EINVAL; +} + +static void nau8825_xtalk_backup(struct nau8825 *nau8825) +{ + int i; + + /* Backup some register values to backup table */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) + regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + &nau8825_xtalk_baktab[i].def); +} + +static void nau8825_xtalk_restore(struct nau8825 *nau8825) +{ + int i, volume; + + /* Restore register values from backup table; When the driver restores + * the headphone volumem, it needs recover to original level gradually + * with 3dB per step for less pop noise. + */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) { + if (nau8825_xtalk_baktab[i].reg == NAU8825_REG_HSVOL_CTRL) { + /* Ramping up the volume change to reduce pop noise */ + volume = nau8825_xtalk_baktab[i].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, 0, volume, 3); + continue; + } + regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + nau8825_xtalk_baktab[i].def); + } +} + +static void nau8825_xtalk_prepare_dac(struct nau8825 *nau8825) +{ + /* Enable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK | + NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR | + NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK); + /* Prevent startup click by letting charge pump to ramp up and + * change bump enable + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN); + /* Enable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN | + NAU8825_RDAC_FS_BCLK_ENB, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN); + /* Power up output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L); + /* HP outputs not shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0); + /* Enable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS); + /* Enable class G compare path to supply 1.8V or 0.9V. */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN); +} + +static void nau8825_xtalk_prepare_adc(struct nau8825 *nau8825) +{ + /* Power up left ADC and raise 5dB than Vmid for Vref */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB); +} + +static void nau8825_xtalk_clock(struct nau8825 *nau8825) +{ + /* Recover FLL default value */ + regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126); + regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008); + regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010); + regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000); + /* Enable internal VCO clock for detection signal generated */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, + NAU8825_DCO_EN); + /* Given specific clock frequency of internal clock to + * generate signal. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK, 0x10); +} + +static void nau8825_xtalk_prepare(struct nau8825 *nau8825) +{ + int volume, index; + + /* Backup those registers changed by cross talk detection */ + nau8825_xtalk_backup(nau8825); + /* Config IIS as master to output signal by codec */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER | + (0x2 << NAU8825_I2S_DRV_SFT) | 0x1); + /* Ramp up headphone volume to 0dB to get better performance and + * avoid pop noise in headphone. + */ + index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL); + if (index != -EINVAL) { + volume = nau8825_xtalk_baktab[index].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, volume, 0, 3); + } + nau8825_xtalk_clock(nau8825); + nau8825_xtalk_prepare_dac(nau8825); + nau8825_xtalk_prepare_adc(nau8825); + /* Config channel path and digital gain */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK, + NAU8825_DACL_CH_SEL_L | 0xab); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK, + NAU8825_DACR_CH_SEL_R | 0xab); + /* Config cross talk parameters and generate the 23Hz sine wave with + * 1/16 full scale of signal level for impedance measurement. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK | + NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK, + (0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th | + NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN); + /* RMS intrruption enable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0); + /* Power up left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); +} + +static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825) +{ + /* Disable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, 0); + /* HP outputs shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); + /* Power down left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + /* Enable the TESTDAC and disable L/R HP impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP | + NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + /* Power down output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0); + /* Disable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0); + /* Disable charge pump ramp up function and change bump */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0); + /* Disable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0); + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); +} + +static void nau8825_xtalk_clean_adc(struct nau8825 *nau8825) +{ + /* Power down left ADC and restore voltage to Vmid */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0); +} + +static void nau8825_xtalk_clean(struct nau8825 *nau8825) +{ + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + nau8825_xtalk_clean_dac(nau8825); + nau8825_xtalk_clean_adc(nau8825); + /* Clear cross talk parameters and disable */ + regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0); + /* RMS intrruption disable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN); + /* Recover default value for IIS */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE); + /* Restore value of specific register for cross talk */ + nau8825_xtalk_restore(nau8825); +} + +static void nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol) +{ + /* Apply ADC volume for better cross talk performance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL, + NAU8825_ADC_DIG_VOL_MASK, vol); + /* Disables JKTIP(HPL) DAC channel for right to left measurement. + * Do it before sending signal in order to erase pop noise. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN, + NAU8825_BIAS_TESTDACL_EN); + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_HPR_R2L: + /* Enable right headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPR_IMP); + break; + case NAU8825_XTALK_HPL_R2L: + /* Enable left headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPL_IMP); + break; + default: + break; + } + msleep(100); + /* Impedance measurement mode enable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_EN, NAU8825_IMM_EN); +} + +static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) +{ + /* Impedance measurement mode disable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_IMM_MODE_CTRL, NAU8825_IMM_EN, 0); +} + +/* The cross talk measurement function can reduce cross talk across the + * JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal + * level to determine what cross talk reduction gain is. This system works by + * sending a 23Hz -24dBV sine wave into the headset output DAC and through + * the PGA. The output of the PGA is then connected to an internal current + * sense which measures the attenuated 23Hz signal and passing the output to + * an ADC which converts the measurement to a binary code. With two separated + * measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data + * can be separated read in IMM_RMS_L for HSR and HSL after each measurement. + * Thus, the measurement function has four states to complete whole sequence. + * 1. Prepare state : Prepare the resource for detection and transfer to HPR + * IMM stat to make JKR1(HPR) impedance measure. + * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * to HPL IMM state to make JKTIP(HPL) impedance measure. + * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and + * transfer to IMM state to determine suppression sidetone gain. + * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * and cross talk signal level. Apply this gain and then restore codec + * configuration. Then transfer to Done state for ending. + */ +static void nau8825_xtalk_measure(struct nau8825 *nau8825) +{ + u32 sidetone; + + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_PREPARE: + /* In prepare state, set up clock, intrruption, DAC path, ADC + * path and cross talk detection parameters for preparation. + */ + nau8825_xtalk_prepare(nau8825); + msleep(280); + /* Trigger right headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00d2); + break; + case NAU8825_XTALK_HPR_R2L: + /* In right headphone IMM state, read out right headphone + * impedance measure result, and then start up left side. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + /* Disable then re-enable IMM mode to update */ + nau8825_xtalk_imm_stop(nau8825); + /* Trigger left headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00ff); + break; + case NAU8825_XTALK_HPL_R2L: + /* In left headphone IMM state, read out left headphone + * impedance measure result, and delay some time to wait + * detection sine wave output finish. Then, we can calculate + * the cross talk suppresstion side tone according to the L/R + * headphone imedance. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + nau8825_xtalk_imm_stop(nau8825); + msleep(150); + nau8825->xtalk_state = NAU8825_XTALK_IMM; + break; + case NAU8825_XTALK_IMM: + /* In impedance measure state, the orignal and cross talk + * signal level vlues are ready. The side tone gain is deter- + * mined with these signal level. After all, restore codec + * configuration. + */ + sidetone = nau8825_xtalk_sidetone( + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L], + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone); + regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL, + (sidetone << 8) | sidetone); + nau8825_xtalk_clean(nau8825); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + break; + default: + break; + } +} + +static void nau8825_xtalk_work(struct work_struct *work) +{ + struct nau8825 *nau8825 = container_of( + work, struct nau8825, xtalk_work); + + nau8825_xtalk_measure(nau8825); + /* To determine the cross talk side tone gain when reach + * the impedance measure state. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_IMM) + nau8825_xtalk_measure(nau8825); + + /* Delay jack report until cross talk detection process + * completed. It can avoid application to do playback + * preparation before cross talk detection is still working. + * Meanwhile, the protection of the cross talk detection + * is released. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_DONE) { + snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event, + nau8825->xtalk_event_mask); + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } +} + +static void nau8825_xtalk_cancel(struct nau8825 *nau8825) +{ + /* If the xtalk_protect is true, that means the process is still + * on going. The driver forces to cancel the cross talk task and + * restores the configuration to original status. + */ + if (nau8825->xtalk_protect) { + cancel_work_sync(&nau8825->xtalk_work); + nau8825_xtalk_clean(nau8825); + } + /* Reset parameters for cross talk suppression function */ + nau8825_sema_reset(nau8825); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; +} + static bool nau8825_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -217,12 +867,36 @@ static bool nau8825_volatile_reg(struct device *dev, unsigned int reg) case NAU8825_REG_SARDOUT_RAM_STATUS: case NAU8825_REG_CHARGE_PUMP_INPUT_READ: case NAU8825_REG_GENERAL_STATUS: + case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10: return true; default: return false; } } +static int nau8825_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + break; + case SND_SOC_DAPM_POST_PMD: + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + static int nau8825_pump_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -270,6 +944,54 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w, return 0; } +static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + if (!component->regmap) + return -EINVAL; + + regmap_raw_read(component->regmap, NAU8825_REG_BIQ_COF1, + ucontrol->value.bytes.data, params->max); + return 0; +} + +static int nau8825_biq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + void *data; + + if (!component->regmap) + return -EINVAL; + + data = kmemdup(ucontrol->value.bytes.data, + params->max, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, 0); + regmap_raw_write(component->regmap, NAU8825_REG_BIQ_COF1, + data, params->max); + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, NAU8825_BIQ_WRT_EN); + + kfree(data); + return 0; +} + +static const char * const nau8825_biq_path[] = { + "ADC", "DAC" +}; + +static const struct soc_enum nau8825_biq_path_enum = + SOC_ENUM_SINGLE(NAU8825_REG_BIQ_CTRL, NAU8825_BIQ_PATH_SFT, + ARRAY_SIZE(nau8825_biq_path), nau8825_biq_path); + static const char * const nau8825_adc_decimation[] = { "32", "64", "128", "256" }; @@ -306,6 +1028,10 @@ static const struct snd_kcontrol_new nau8825_controls[] = { SOC_ENUM("ADC Decimation Rate", nau8825_adc_decimation_enum), SOC_ENUM("DAC Oversampling Rate", nau8825_dac_oversampl_enum), + /* programmable biquad filter */ + SOC_ENUM("BIQ Path Select", nau8825_biq_path_enum), + SND_SOC_BYTES_EXT("BIQ Coefficients", 20, + nau8825_biq_coeff_get, nau8825_biq_coeff_put), }; /* DAC Mux 0x33[9] and 0x34[9] */ @@ -338,7 +1064,9 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, NULL, 0), - SND_SOC_DAPM_ADC("ADC", NULL, NAU8825_REG_ENA_CTRL, 8, 0), + SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, + nau8825_adc_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("ADC Power", NAU8825_REG_ANALOG_ADC_2, 6, 0, NULL, 0), @@ -459,6 +1187,8 @@ static int nau8825_hw_params(struct snd_pcm_substream *substream, struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); unsigned int val_len = 0; + nau8825_sema_acquire(nau8825, 2 * HZ); + switch (params_width(params)) { case 16: val_len |= NAU8825_I2S_DL_16; @@ -479,6 +1209,9 @@ static int nau8825_hw_params(struct snd_pcm_substream *substream, regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1, NAU8825_I2S_DL_MASK, val_len); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); + return 0; } @@ -488,6 +1221,8 @@ static int nau8825_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); unsigned int ctrl1_val = 0, ctrl2_val = 0; + nau8825_sema_acquire(nau8825, 2 * HZ); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: ctrl2_val |= NAU8825_I2S_MS_MASTER; @@ -536,6 +1271,9 @@ static int nau8825_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, NAU8825_I2S_MS_MASK, ctrl2_val); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); + return 0; } @@ -592,9 +1330,6 @@ int nau8825_enable_jack_detect(struct snd_soc_codec *codec, NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); - regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, - NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0); - return 0; } EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); @@ -602,24 +1337,21 @@ EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); static bool nau8825_is_jack_inserted(struct regmap *regmap) { - int status; + bool active_high, is_high; + int status, jkdet; + regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet); + active_high = jkdet & NAU8825_JACK_POLARITY; regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status); - return !(status & NAU8825_GPIO2JD1); + is_high = status & NAU8825_GPIO2JD1; + /* return jack connection status according to jack insertion logic + * active high or active low. + */ + return active_high == is_high; } static void nau8825_restart_jack_detection(struct regmap *regmap) { - /* Chip needs one FSCLK cycle in order to generate interrupts, - * as we cannot guarantee one will be provided by the system. Turning - * master mode on then off enables us to generate that FSCLK cycle - * with a minimum of contention on the clock bus. - */ - regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, - NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER); - regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, - NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE); - /* this will restart the entire jack detection process including MIC/GND * switching and create interrupts. We have to go from 0 to 1 and back * to 0 to restart. @@ -630,11 +1362,30 @@ static void nau8825_restart_jack_detection(struct regmap *regmap) NAU8825_JACK_DET_RESTART, 0); } +static void nau8825_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq); + for (i = 0; i < NAU8825_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); + } +} + static void nau8825_eject_jack(struct nau8825 *nau8825) { struct snd_soc_dapm_context *dapm = nau8825->dapm; struct regmap *regmap = nau8825->regmap; + /* Force to cancel the cross talk detection process */ + nau8825_xtalk_cancel(nau8825); + snd_soc_dapm_disable_pin(dapm, "SAR"); snd_soc_dapm_disable_pin(dapm, "MICBIAS"); /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */ @@ -644,6 +1395,69 @@ static void nau8825_eject_jack(struct nau8825 *nau8825) regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf); snd_soc_dapm_sync(dapm); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable the insertion interruption, disable the ejection inter- + * ruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_EJECT_DIS | NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); + + /* Close clock for jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); +} + +/* Enable audo mode interruptions with internal clock. */ +static void nau8825_setup_auto_irq(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Enable headset jack type detection complete interruption and + * jack ejection interruption. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0); + + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + + /* Enable ADC needed for interruptions */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + + /* Chip needs one FSCLK cycle in order to generate interruptions, + * as we cannot guarantee one will be provided by the system. Turning + * master mode on then off enables us to generate that FSCLK cycle + * with a minimum of contention on the clock bus. + */ + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER); + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE); + + /* Not bypass de-bounce circuit */ + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, 0); + + /* Unmask all interruptions */ + regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0); + + /* Restart the jack detection process at auto mode */ + nau8825_restart_jack_detection(regmap); } static int nau8825_button_decode(int value) @@ -676,6 +1490,11 @@ static int nau8825_jack_insert(struct nau8825 *nau8825) regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg); mic_detected = (jack_status_reg >> 10) & 3; + /* The JKSLV and JKR2 all detected in high impedance headset */ + if (mic_detected == 0x3) + nau8825->high_imped = true; + else + nau8825->high_imped = false; switch (mic_detected) { case 0: @@ -773,6 +1592,36 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) } else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) { if (nau8825_is_jack_inserted(regmap)) { event |= nau8825_jack_insert(nau8825); + if (!nau8825->high_imped) { + /* Apply the cross talk suppression in the + * headset without high impedance. + */ + if (!nau8825->xtalk_protect) { + /* Raise protection for cross talk de- + * tection if no protection before. + * The driver has to cancel the pro- + * cess and restore changes if process + * is ongoing when ejection. + */ + int ret; + nau8825->xtalk_protect = true; + ret = nau8825_sema_acquire(nau8825, 0); + if (ret < 0) + nau8825->xtalk_protect = false; + } + /* Startup cross talk detection process */ + nau8825->xtalk_state = NAU8825_XTALK_PREPARE; + schedule_work(&nau8825->xtalk_work); + } else { + /* The cross talk suppression shouldn't apply + * in the headset with high impedance. Thus, + * relieve the protection raised before. + */ + if (nau8825->xtalk_protect) { + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } + } } else { dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n"); nau8825_eject_jack(nau8825); @@ -780,6 +1629,37 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) event_mask |= SND_JACK_HEADSET; clear_irq = NAU8825_HEADSET_COMPLETION_IRQ; + /* Record the interruption report event for driver to report + * the event later. The jack report will delay until cross + * talk detection process is done. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) { + nau8825->xtalk_event = event; + nau8825->xtalk_event_mask = event_mask; + } + } else if (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) { + schedule_work(&nau8825->xtalk_work); + clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ; + } else if ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) == + NAU8825_JACK_INSERTION_DETECTED) { + /* One more step to check GPIO status directly. Thus, the + * driver can confirm the real insertion interruption because + * the intrruption at manual mode has bypassed debounce + * circuit which can get rid of unstable status. + */ + if (nau8825_is_jack_inserted(regmap)) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_INSERT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN); + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8825_setup_auto_irq(nau8825); + } } if (!clear_irq) @@ -787,7 +1667,12 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) /* clears the rightmost interruption */ regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); - if (event_mask) + /* Delay jack report until cross talk detection is done. It can avoid + * application to do playback preparation when cross talk detection + * process is still working. Otherwise, the resource like clock and + * power will be issued by them at the same time and conflict happens. + */ + if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE) snd_soc_jack_report(nau8825->jack, event, event_mask); return IRQ_HANDLED; @@ -921,11 +1806,16 @@ static void nau8825_init_regs(struct nau8825 *nau8825) NAU8825_RDAC_CLK_DELAY_MASK | NAU8825_RDAC_VREF_MASK, (0x2 << NAU8825_RDAC_CLK_DELAY_SFT) | (0x3 << NAU8825_RDAC_VREF_SFT)); + /* Config L/R channel */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_R); } static const struct regmap_config nau8825_regmap_config = { - .val_bits = 16, - .reg_bits = 16, + .val_bits = NAU8825_REG_DATA_LEN, + .reg_bits = NAU8825_REG_ADDR_LEN, .max_register = NAU8825_REG_MAX, .readable_reg = nau8825_readable_reg, @@ -944,18 +1834,15 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec) nau8825->dapm = dapm; - /* The interrupt clock is gated by x1[10:8], - * one of them needs to be enabled all the time for - * interrupts to happen. - */ - snd_soc_dapm_force_enable_pin(dapm, "DDACR"); - snd_soc_dapm_sync(dapm); + return 0; +} - /* Unmask interruptions. Handler uses dapm object so we can enable - * interruptions only after dapm is fully initialized. - */ - regmap_write(nau8825->regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0); - nau8825_restart_jack_detection(nau8825->regmap); +static int nau8825_codec_remove(struct snd_soc_codec *codec) +{ + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + + /* Cancel and reset cross tak suppresstion detection funciton */ + nau8825_xtalk_cancel(nau8825); return 0; } @@ -973,8 +1860,8 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec) static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, struct nau8825_fll *fll_param) { - u64 fvco; - unsigned int fref, i; + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. @@ -999,18 +1886,23 @@ static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, fll_param->ratio = fll_ratio[i].val; /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. - * FDCO must be within the 90MHz - 100MHz or the FFL cannot be + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be * guaranteed across the full range of operation. * FDCO = freq_out * 2 * mclk_src_scaling */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { fvco = 256 * fs * 2 * mclk_src_scaling[i].param; - if (NAU_FVCO_MIN < fvco && fvco < NAU_FVCO_MAX) - break; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } } - if (i == ARRAY_SIZE(mclk_src_scaling)) + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) return -EINVAL; - fll_param->mclk_src = mclk_src_scaling[i].val; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional * input based on FDCO, FREF and FLL ratio. @@ -1025,7 +1917,8 @@ static void nau8825_fll_apply(struct nau8825 *nau8825, struct nau8825_fll *fll_param) { regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_MCLK_SRC_MASK, fll_param->mclk_src); + NAU8825_CLK_SRC_MASK | NAU8825_CLK_MCLK_SRC_MASK, + NAU8825_CLK_SRC_MCLK | fll_param->mclk_src); regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, NAU8825_FLL_RATIO_MASK, fll_param->ratio); /* FLL 16-bit fractional input */ @@ -1038,10 +1931,25 @@ static void nau8825_fll_apply(struct nau8825 *nau8825, NAU8825_FLL_REF_DIV_MASK, fll_param->clk_ref_div); /* select divided VCO input */ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, - NAU8825_FLL_FILTER_SW_MASK, 0x0000); - /* FLL sigma delta modulator enable */ - regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, - NAU8825_SDM_EN_MASK, NAU8825_SDM_EN); + NAU8825_FLL_CLK_SW_MASK, NAU8825_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_FLL6, NAU8825_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_FILTER); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN, NAU8825_SDM_EN); + } else { + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, NAU8825_FLL_FTR_SW_ACCU); + regmap_update_bits(nau8825->regmap, + NAU8825_REG_FLL6, NAU8825_SDM_EN, 0); + } } /* freq_out must be 256*Fs in order to achieve the best performance */ @@ -1069,6 +1977,45 @@ static int nau8825_set_pll(struct snd_soc_codec *codec, int pll_id, int source, return 0; } +static int nau8825_mclk_prepare(struct nau8825 *nau8825, unsigned int freq) +{ + int ret = 0; + + nau8825->mclk = devm_clk_get(nau8825->dev, "mclk"); + if (IS_ERR(nau8825->mclk)) { + dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally"); + return 0; + } + + if (!nau8825->mclk_freq) { + ret = clk_prepare_enable(nau8825->mclk); + if (ret) { + dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); + return ret; + } + } + + if (nau8825->mclk_freq != freq) { + freq = clk_round_rate(nau8825->mclk, freq); + ret = clk_set_rate(nau8825->mclk, freq); + if (ret) { + dev_err(nau8825->dev, "Unable to set mclk rate\n"); + return ret; + } + nau8825->mclk_freq = freq; + } + + return 0; +} + +static void nau8825_configure_mclk_as_sysclk(struct regmap *regmap) +{ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, 0); +} + static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, unsigned int freq) { @@ -1076,40 +2023,106 @@ static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, int ret; switch (clk_id) { + case NAU8825_CLK_DIS: + /* Clock provided externally and disable internal VCO clock */ + nau8825_configure_mclk_as_sysclk(regmap); + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; case NAU8825_CLK_MCLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + nau8825_configure_mclk_as_sysclk(regmap); + /* MCLK not changed by clock tree */ regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK); - regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, 0); + NAU8825_CLK_MCLK_SRC_MASK, 0); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); - /* We selected MCLK source but the clock itself managed externally */ - if (!nau8825->mclk) - break; + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; - if (!nau8825->mclk_freq) { - ret = clk_prepare_enable(nau8825->mclk); - if (ret) { - dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); - return ret; - } + break; + case NAU8825_CLK_INTERNAL: + if (nau8825_is_jack_inserted(nau8825->regmap)) { + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, NAU8825_DCO_EN); + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + /* Decrease the VCO frequency for power saving */ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK, 0x10); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN, NAU8825_SDM_EN); + } else { + /* The clock turns off intentionally for power saving + * when no headset connected. + */ + nau8825_configure_mclk_as_sysclk(regmap); + dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n"); + } + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; } - if (nau8825->mclk_freq != freq) { - nau8825->mclk_freq = freq; + break; + case NAU8825_CLK_FLL_MCLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_MCLK); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); - freq = clk_round_rate(nau8825->mclk, freq); - ret = clk_set_rate(nau8825->mclk, freq); - if (ret) { - dev_err(nau8825->dev, "Unable to set mclk rate\n"); - return ret; - } + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; + + break; + case NAU8825_CLK_FLL_BLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_BLK); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); + + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; } break; - case NAU8825_CLK_INTERNAL: - regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, - NAU8825_DCO_EN); - regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + case NAU8825_CLK_FLL_FS: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_FS); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); if (nau8825->mclk_freq) { clk_disable_unprepare(nau8825->mclk); @@ -1135,6 +2148,31 @@ static int nau8825_set_sysclk(struct snd_soc_codec *codec, int clk_id, return nau8825_configure_sysclk(nau8825, clk_id, freq); } +static int nau8825_resume_setup(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Close clock when jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable both insertion and ejection interruptions, and then + * bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN | + NAU8825_IRQ_EJECT_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS | NAU8825_IRQ_EJECT_DIS, 0); + + return 0; +} + static int nau8825_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -1157,10 +2195,22 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec, return ret; } } + /* Setup codec configuration after resume */ + nau8825_resume_setup(nau8825); } break; case SND_SOC_BIAS_OFF: + /* Cancel and reset cross talk detection funciton */ + nau8825_xtalk_cancel(nau8825); + /* Turn off all interruptions before system shutdown. Keep the + * interruption quiet before resume setup completes. + */ + regmap_write(nau8825->regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff); + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); if (nau8825->mclk_freq) clk_disable_unprepare(nau8825->mclk); break; @@ -1168,57 +2218,37 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec, return 0; } -#ifdef CONFIG_PM -static int nau8825_suspend(struct snd_soc_codec *codec) +static int __maybe_unused nau8825_suspend(struct snd_soc_codec *codec) { struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); disable_irq(nau8825->irq); + snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF); regcache_cache_only(nau8825->regmap, true); regcache_mark_dirty(nau8825->regmap); return 0; } -static int nau8825_resume(struct snd_soc_codec *codec) +static int __maybe_unused nau8825_resume(struct snd_soc_codec *codec) { struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + int ret; - /* The chip may lose power and reset in S3. regcache_sync restores - * register values including configurations for sysclk, irq, and - * jack/button detection. - */ regcache_cache_only(nau8825->regmap, false); regcache_sync(nau8825->regmap); - - /* Check the jack plug status directly. If the headset is unplugged - * during S3 when the chip has no power, there will be no jack - * detection irq even after the nau8825_restart_jack_detection below, - * because the chip just thinks no headset has ever been plugged in. - */ - if (!nau8825_is_jack_inserted(nau8825->regmap)) { - nau8825_eject_jack(nau8825); - snd_soc_jack_report(nau8825->jack, 0, SND_JACK_HEADSET); - } - + nau8825->xtalk_protect = true; + ret = nau8825_sema_acquire(nau8825, 0); + if (ret < 0) + nau8825->xtalk_protect = false; enable_irq(nau8825->irq); - /* Run jack detection to check the type (OMTP or CTIA) of the headset - * if there is one. This handles the case where a different type of - * headset is plugged in during S3. This triggers an IRQ iff a headset - * is already plugged in. - */ - nau8825_restart_jack_detection(nau8825->regmap); - return 0; } -#else -#define nau8825_suspend NULL -#define nau8825_resume NULL -#endif static struct snd_soc_codec_driver nau8825_codec_driver = { .probe = nau8825_codec_probe, + .remove = nau8825_codec_remove, .set_sysclk = nau8825_set_sysclk, .set_pll = nau8825_set_pll, .set_bias_level = nau8825_set_bias_level, @@ -1318,22 +2348,8 @@ static int nau8825_read_device_properties(struct device *dev, static int nau8825_setup_irq(struct nau8825 *nau8825) { - struct regmap *regmap = nau8825->regmap; int ret; - /* IRQ Output Enable */ - regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, - NAU8825_IRQ_OUTPUT_EN, NAU8825_IRQ_OUTPUT_EN); - - /* Enable internal VCO needed for interruptions */ - nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); - - /* Enable DDACR needed for interrupts - * It is the same as force_enable_pin("DDACR") we do later - */ - regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, - NAU8825_ENABLE_DACR, NAU8825_ENABLE_DACR); - ret = devm_request_threaded_irq(nau8825->dev, nau8825->irq, NULL, nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "nau8825", nau8825); @@ -1370,6 +2386,13 @@ static int nau8825_i2c_probe(struct i2c_client *i2c, return PTR_ERR(nau8825->regmap); nau8825->dev = dev; nau8825->irq = i2c->irq; + /* Initiate parameters, semaphone and work queue which are needed in + * cross talk suppression measurment function. + */ + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; + sema_init(&nau8825->xtalk_sem, 1); + INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work); nau8825_print_device_properties(nau8825); @@ -1405,6 +2428,7 @@ static const struct i2c_device_id nau8825_i2c_ids[] = { { "nau8825", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids); #ifdef CONFIG_OF static const struct of_device_id nau8825_of_ids[] = { diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h index 8ceb5f385..1c63e2aba 100644 --- a/sound/soc/codecs/nau8825.h +++ b/sound/soc/codecs/nau8825.h @@ -93,12 +93,21 @@ #define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81 #define NAU8825_REG_GENERAL_STATUS 0x82 #define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8825_REG_ADDR_LEN 16 +#define NAU8825_REG_DATA_LEN 16 /* ENA_CTRL (0x1) */ #define NAU8825_ENABLE_DACR_SFT 10 #define NAU8825_ENABLE_DACR (1 << NAU8825_ENABLE_DACR_SFT) #define NAU8825_ENABLE_DACL_SFT 9 +#define NAU8825_ENABLE_DACL (1 << NAU8825_ENABLE_DACL_SFT) #define NAU8825_ENABLE_ADC_SFT 8 +#define NAU8825_ENABLE_ADC (1 << NAU8825_ENABLE_ADC_SFT) +#define NAU8825_ENABLE_ADC_CLK_SFT 7 +#define NAU8825_ENABLE_ADC_CLK (1 << NAU8825_ENABLE_ADC_CLK_SFT) +#define NAU8825_ENABLE_DAC_CLK_SFT 6 +#define NAU8825_ENABLE_DAC_CLK (1 << NAU8825_ENABLE_DAC_CLK_SFT) #define NAU8825_ENABLE_SAR_SFT 1 /* CLK_DIVIDER (0x3) */ @@ -113,20 +122,28 @@ /* FLL3 (0x06) */ #define NAU8825_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8825_FLL_CLK_SRC_SFT 10 +#define NAU8825_FLL_CLK_SRC_MASK (0x3 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_MCLK (0 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_BLK (0x2 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_FS (0x3 << NAU8825_FLL_CLK_SRC_SFT) /* FLL4 (0x07) */ #define NAU8825_FLL_REF_DIV_MASK (0x3 << 10) /* FLL5 (0x08) */ -#define NAU8825_FLL_FILTER_SW_MASK (0x1 << 14) +#define NAU8825_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8825_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8825_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8825_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8825_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8825_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8825_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8825_FLL_FTR_SW_FILTER (0x0 << 12) /* FLL6 (0x9) */ -#define NAU8825_DCO_EN_MASK (0x1 << 15) #define NAU8825_DCO_EN (0x1 << 15) -#define NAU8825_DCO_DIS (0x0 << 15) -#define NAU8825_SDM_EN_MASK (0x1 << 14) #define NAU8825_SDM_EN (0x1 << 14) -#define NAU8825_SDM_DIS (0x0 << 14) /* HSD_CTRL (0xc) */ #define NAU8825_HSD_AUTO_MODE (1 << 6) @@ -136,6 +153,7 @@ /* JACK_DET_CTRL (0xd) */ #define NAU8825_JACK_DET_RESTART (1 << 9) +#define NAU8825_JACK_DET_DB_BYPASS (1 << 8) #define NAU8825_JACK_INSERT_DEBOUNCE_SFT 5 #define NAU8825_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_INSERT_DEBOUNCE_SFT) #define NAU8825_JACK_EJECT_DEBOUNCE_SFT 2 @@ -145,9 +163,11 @@ /* INTERRUPT_MASK (0xf) */ #define NAU8825_IRQ_OUTPUT_EN (1 << 11) #define NAU8825_IRQ_HEADSET_COMPLETE_EN (1 << 10) +#define NAU8825_IRQ_RMS_EN (1 << 8) #define NAU8825_IRQ_KEY_RELEASE_EN (1 << 7) #define NAU8825_IRQ_KEY_SHORT_PRESS_EN (1 << 5) #define NAU8825_IRQ_EJECT_EN (1 << 2) +#define NAU8825_IRQ_INSERT_EN (1 << 0) /* IRQ_STATUS (0x10) */ #define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10) @@ -168,6 +188,7 @@ #define NAU8825_IRQ_KEY_RELEASE_DIS (1 << 7) #define NAU8825_IRQ_KEY_SHORT_PRESS_DIS (1 << 5) #define NAU8825_IRQ_EJECT_DIS (1 << 2) +#define NAU8825_IRQ_INSERT_DIS (1 << 0) /* SAR_CTRL (0x13) */ #define NAU8825_SAR_ADC_EN_SFT 12 @@ -217,10 +238,21 @@ /* I2S_PCM_CTRL2 (0x1d) */ #define NAU8825_I2S_TRISTATE (1 << 15) /* 0 - normal mode, 1 - Hi-Z output */ +#define NAU8825_I2S_DRV_SFT 12 +#define NAU8825_I2S_DRV_MASK (0x3 << NAU8825_I2S_DRV_SFT) #define NAU8825_I2S_MS_SFT 3 #define NAU8825_I2S_MS_MASK (1 << NAU8825_I2S_MS_SFT) #define NAU8825_I2S_MS_MASTER (1 << NAU8825_I2S_MS_SFT) #define NAU8825_I2S_MS_SLAVE (0 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_BLK_DIV_MASK 0x7 + +/* BIQ_CTRL (0x20) */ +#define NAU8825_BIQ_WRT_SFT 4 +#define NAU8825_BIQ_WRT_EN (1 << NAU8825_BIQ_WRT_SFT) +#define NAU8825_BIQ_PATH_SFT 0 +#define NAU8825_BIQ_PATH_MASK (1 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_ADC (0 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_DAC (1 << NAU8825_BIQ_PATH_SFT) /* ADC_RATE (0x2b) */ #define NAU8825_ADC_SYNC_DOWN_SFT 0 @@ -239,22 +271,72 @@ #define NAU8825_DAC_OVERSAMPLE_128 2 #define NAU8825_DAC_OVERSAMPLE_32 4 +/* ADC_DGAIN_CTRL (0x30) */ +#define NAU8825_ADC_DIG_VOL_MASK 0xff + /* MUTE_CTRL (0x31) */ #define NAU8825_DAC_ZERO_CROSSING_EN (1 << 9) #define NAU8825_DAC_SOFT_MUTE (1 << 9) /* HSVOL_CTRL (0x32) */ #define NAU8825_HP_MUTE (1 << 15) +#define NAU8825_HP_MUTE_AUTO (1 << 14) +#define NAU8825_HPL_MUTE (1 << 13) +#define NAU8825_HPR_MUTE (1 << 12) +#define NAU8825_HPL_VOL_SFT 6 +#define NAU8825_HPL_VOL_MASK (0x3f << NAU8825_HPL_VOL_SFT) +#define NAU8825_HPR_VOL_SFT 0 +#define NAU8825_HPR_VOL_MASK (0x3f << NAU8825_HPR_VOL_SFT) +#define NAU8825_HP_VOL_MIN 0x36 /* DACL_CTRL (0x33) */ #define NAU8825_DACL_CH_SEL_SFT 9 +#define NAU8825_DACL_CH_SEL_MASK (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_L (0x0 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_R (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_VOL_MASK 0xff /* DACR_CTRL (0x34) */ #define NAU8825_DACR_CH_SEL_SFT 9 +#define NAU8825_DACR_CH_SEL_MASK (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_L (0x0 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_R (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_VOL_MASK 0xff + +/* IMM_MODE_CTRL (0x4C) */ +#define NAU8825_IMM_THD_SFT 8 +#define NAU8825_IMM_THD_MASK (0x3f << NAU8825_IMM_THD_SFT) +#define NAU8825_IMM_GEN_VOL_SFT 6 +#define NAU8825_IMM_GEN_VOL_MASK (0x3 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_2nd (0x0 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_4th (0x1 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_8th (0x2 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_16th (0x3 << NAU8825_IMM_GEN_VOL_SFT) + +#define NAU8825_IMM_CYC_SFT 4 +#define NAU8825_IMM_CYC_MASK (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_1024 (0x0 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_2048 (0x1 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_4096 (0x2 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_8192 (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_EN (1 << 3) +#define NAU8825_IMM_DAC_SRC_MASK 0x7 +#define NAU8825_IMM_DAC_SRC_BIQ 0x0 +#define NAU8825_IMM_DAC_SRC_DRC 0x1 +#define NAU8825_IMM_DAC_SRC_MIX 0x2 +#define NAU8825_IMM_DAC_SRC_SIN 0x3 /* CLASSG_CTRL (0x50) */ #define NAU8825_CLASSG_TIMER_SFT 8 #define NAU8825_CLASSG_TIMER_MASK (0x3f << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_1ms (0x1 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_2ms (0x2 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_8ms (0x4 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_16ms (0x8 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_32ms (0x10 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_64ms (0x20 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_LDAC_EN (0x1 << 2) +#define NAU8825_CLASSG_RDAC_EN (0x1 << 1) #define NAU8825_CLASSG_EN (1 << 0) /* I2C_DEVICE_ID (0x58) */ @@ -263,7 +345,12 @@ #define NAU8825_SOFTWARE_ID_NAU8825 0x0 /* BIAS_ADJ (0x66) */ -#define NAU8825_BIAS_TESTDAC_EN (0x3 << 8) +#define NAU8825_BIAS_HPR_IMP (1 << 15) +#define NAU8825_BIAS_HPL_IMP (1 << 14) +#define NAU8825_BIAS_TESTDAC_SFT 8 +#define NAU8825_BIAS_TESTDAC_EN (0x3 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACR_EN (0x2 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACL_EN (0x1 << NAU8825_BIAS_TESTDAC_SFT) #define NAU8825_BIAS_VMID (1 << 6) #define NAU8825_BIAS_VMID_SEL_SFT 4 #define NAU8825_BIAS_VMID_SEL_MASK (3 << NAU8825_BIAS_VMID_SEL_SFT) @@ -282,6 +369,11 @@ #define NAU8825_POWERUP_ADCL (1 << 6) /* RDAC (0x73) */ +#define NAU8825_RDAC_FS_BCLK_ENB (1 << 15) +#define NAU8825_RDAC_EN_SFT 12 +#define NAU8825_RDAC_EN (0x3 << NAU8825_RDAC_EN_SFT) +#define NAU8825_RDAC_CLK_EN_SFT 8 +#define NAU8825_RDAC_CLK_EN (0x3 << NAU8825_RDAC_CLK_EN_SFT) #define NAU8825_RDAC_CLK_DELAY_SFT 4 #define NAU8825_RDAC_CLK_DELAY_MASK (0x7 << NAU8825_RDAC_CLK_DELAY_SFT) #define NAU8825_RDAC_VREF_SFT 2 @@ -318,8 +410,21 @@ /* System Clock Source */ enum { - NAU8825_CLK_MCLK = 0, + NAU8825_CLK_DIS = 0, + NAU8825_CLK_MCLK, NAU8825_CLK_INTERNAL, + NAU8825_CLK_FLL_MCLK, + NAU8825_CLK_FLL_BLK, + NAU8825_CLK_FLL_FS, +}; + +/* Cross talk detection state */ +enum { + NAU8825_XTALK_PREPARE = 0, + NAU8825_XTALK_HPR_R2L, + NAU8825_XTALK_HPL_R2L, + NAU8825_XTALK_IMM, + NAU8825_XTALK_DONE, }; struct nau8825 { @@ -328,6 +433,8 @@ struct nau8825 { struct snd_soc_dapm_context *dapm; struct snd_soc_jack *jack; struct clk *mclk; + struct work_struct xtalk_work; + struct semaphore xtalk_sem; int irq; int mclk_freq; /* 0 - mclk is disabled */ int button_pressed; @@ -346,6 +453,12 @@ struct nau8825 { int key_debounce; int jack_insert_debounce; int jack_eject_debounce; + int high_imped; + int xtalk_state; + int xtalk_event; + int xtalk_event_mask; + bool xtalk_protect; + int imp_rms[NAU8825_XTALK_IMM]; }; int nau8825_enable_jack_detect(struct snd_soc_codec *codec, diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index 583252342..33e1fc2d1 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -73,7 +73,7 @@ static bool pcm1681_accessible_reg(struct device *dev, unsigned int reg) return !((reg == 0x00) || (reg == 0x0f)); } -static bool pcm1681_writeable_reg(struct device *dev, unsigned register reg) +static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg) { return pcm1681_accessible_reg(dev, reg) && (reg != PCM1681_ZERO_DETECT_STATUS); diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c index 06a66579c..88fbdd184 100644 --- a/sound/soc/codecs/pcm179x.c +++ b/sound/soc/codecs/pcm179x.c @@ -59,7 +59,7 @@ static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg) return reg >= 0x10 && reg <= 0x17; } -static bool pcm179x_writeable_reg(struct device *dev, unsigned register reg) +static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg) { bool accessible; diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c index ed5156774..8ba322a00 100644 --- a/sound/soc/codecs/pcm5102a.c +++ b/sound/soc/codecs/pcm5102a.c @@ -57,7 +57,6 @@ static struct platform_driver pcm5102a_codec_driver = { .remove = pcm5102a_remove, .driver = { .name = "pcm5102a-codec", - .owner = THIS_MODULE, .of_match_table = pcm5102a_of_match, }, }; diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 1bd31644a..74c0e4eb3 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -1100,6 +1100,13 @@ static const struct dmi_system_id force_combo_jack_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Skylake Client platform") } }, + { + .ident = "Intel Kabylake RVP", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Kabylake Client platform") + } + }, + { } }; diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c new file mode 100644 index 000000000..77ff8ebe6 --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.c @@ -0,0 +1,447 @@ +/* + * rt5514-spi.c -- RT5514 SPI driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/spi/spi.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_qos.h> +#include <linux/sysfs.h> +#include <linux/clk.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 "rt5514-spi.h" + +static struct spi_device *rt5514_spi; + +struct rt5514_dsp { + struct device *dev; + struct delayed_work copy_work; + struct mutex dma_lock; + struct snd_pcm_substream *substream; + unsigned int buf_base, buf_limit, buf_rp; + size_t buf_size; + size_t dma_offset; + size_t dsp_offset; +}; + +static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = 0x20000 / 8, + .periods_min = 8, + .periods_max = 8, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 0x20000, +}; + +static struct snd_soc_dai_driver rt5514_spi_dai = { + .name = "rt5514-dsp-cpu-dai", + .id = 0, + .capture = { + .stream_name = "DSP Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static void rt5514_spi_copy_work(struct work_struct *work) +{ + struct rt5514_dsp *rt5514_dsp = + container_of(work, struct rt5514_dsp, copy_work.work); + struct snd_pcm_runtime *runtime; + size_t period_bytes, truncated_bytes = 0; + + mutex_lock(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); + goto done; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + + if (rt5514_dsp->buf_size - rt5514_dsp->dsp_offset < period_bytes) + period_bytes = rt5514_dsp->buf_size - rt5514_dsp->dsp_offset; + + if (rt5514_dsp->buf_rp + period_bytes <= rt5514_dsp->buf_limit) { + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + period_bytes); + + if (rt5514_dsp->buf_rp + period_bytes == rt5514_dsp->buf_limit) + rt5514_dsp->buf_rp = rt5514_dsp->buf_base; + else + rt5514_dsp->buf_rp += period_bytes; + } else { + truncated_bytes = rt5514_dsp->buf_limit - rt5514_dsp->buf_rp; + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + truncated_bytes); + + rt5514_spi_burst_read(rt5514_dsp->buf_base, + runtime->dma_area + rt5514_dsp->dma_offset + + truncated_bytes, period_bytes - truncated_bytes); + + rt5514_dsp->buf_rp = rt5514_dsp->buf_base + + period_bytes - truncated_bytes; + } + + rt5514_dsp->dma_offset += period_bytes; + if (rt5514_dsp->dma_offset >= runtime->dma_bytes) + rt5514_dsp->dma_offset = 0; + + rt5514_dsp->dsp_offset += period_bytes; + + snd_pcm_period_elapsed(rt5514_dsp->substream); + + if (rt5514_dsp->dsp_offset < rt5514_dsp->buf_size) + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +done: + mutex_unlock(&rt5514_dsp->dma_lock); +} + +/* PCM for streaming audio from the DSP buffer */ +static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream) +{ + snd_soc_set_runtime_hwparams(substream, &rt5514_spi_pcm_hardware); + + return 0; +} + +static int rt5514_spi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + int ret; + + mutex_lock(&rt5514_dsp->dma_lock); + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + rt5514_dsp->substream = substream; + mutex_unlock(&rt5514_dsp->dma_lock); + + return ret; +} + +static int rt5514_spi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + mutex_lock(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = NULL; + mutex_unlock(&rt5514_dsp->dma_lock); + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int rt5514_spi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + u8 buf[8]; + + rt5514_dsp->dma_offset = 0; + rt5514_dsp->dsp_offset = 0; + + /** + * The address area x1800XXXX is the register address, and it cannot + * support spi burst read perfectly. So we use the spi burst read + * individually to make sure the data correctly. + */ + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_BASE, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_base = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_LIMIT, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_limit = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_RP, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_rp = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_SIZE, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_size = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + return 0; +} + +static int rt5514_spi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (rt5514_dsp->buf_base && rt5514_dsp->buf_limit && + rt5514_dsp->buf_rp && rt5514_dsp->buf_size) + schedule_delayed_work(&rt5514_dsp->copy_work, 0); + } + + return 0; +} + +static snd_pcm_uframes_t rt5514_spi_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + return bytes_to_frames(runtime, rt5514_dsp->dma_offset); +} + +static struct snd_pcm_ops rt5514_spi_pcm_ops = { + .open = rt5514_spi_pcm_open, + .hw_params = rt5514_spi_hw_params, + .hw_free = rt5514_spi_hw_free, + .trigger = rt5514_spi_trigger, + .prepare = rt5514_spi_prepare, + .pointer = rt5514_spi_pcm_pointer, + .mmap = snd_pcm_lib_mmap_vmalloc, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int rt5514_spi_pcm_probe(struct snd_soc_platform *platform) +{ + struct rt5514_dsp *rt5514_dsp; + + rt5514_dsp = devm_kzalloc(platform->dev, sizeof(*rt5514_dsp), + GFP_KERNEL); + + rt5514_dsp->dev = &rt5514_spi->dev; + mutex_init(&rt5514_dsp->dma_lock); + INIT_DELAYED_WORK(&rt5514_dsp->copy_work, rt5514_spi_copy_work); + snd_soc_platform_set_drvdata(platform, rt5514_dsp); + + return 0; +} + +static struct snd_soc_platform_driver rt5514_spi_platform = { + .probe = rt5514_spi_pcm_probe, + .ops = &rt5514_spi_pcm_ops, +}; + +static const struct snd_soc_component_driver rt5514_spi_dai_component = { + .name = "rt5514-spi-dai", +}; + +/** + * rt5514_spi_burst_read - Read data from SPI by rt5514 address. + * @addr: Start address. + * @rxbuf: Data Buffer for reading. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_READ; + int status; + u8 write_buf[8]; + unsigned int i, end, offset = 0; + + struct spi_message message; + struct spi_transfer x[3]; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + spi_message_init(&message); + memset(x, 0, sizeof(x)); + + x[0].len = 5; + x[0].tx_buf = write_buf; + spi_message_add_tail(&x[0], &message); + + x[1].len = 4; + x[1].tx_buf = write_buf; + spi_message_add_tail(&x[1], &message); + + x[2].len = end; + x[2].rx_buf = rxbuf + offset; + spi_message_add_tail(&x[2], &message); + + status = spi_sync(rt5514_spi, &message); + + if (status) + return false; + + offset += RT5514_SPI_BUF_LEN; + } + + for (i = 0; i < len; i += 8) { + write_buf[0] = rxbuf[i + 0]; + write_buf[1] = rxbuf[i + 1]; + write_buf[2] = rxbuf[i + 2]; + write_buf[3] = rxbuf[i + 3]; + write_buf[4] = rxbuf[i + 4]; + write_buf[5] = rxbuf[i + 5]; + write_buf[6] = rxbuf[i + 6]; + write_buf[7] = rxbuf[i + 7]; + + rxbuf[i + 0] = write_buf[7]; + rxbuf[i + 1] = write_buf[6]; + rxbuf[i + 2] = write_buf[5]; + rxbuf[i + 3] = write_buf[4]; + rxbuf[i + 4] = write_buf[3]; + rxbuf[i + 5] = write_buf[2]; + rxbuf[i + 6] = write_buf[1]; + rxbuf[i + 7] = write_buf[0]; + } + + return true; +} + +/** + * rt5514_spi_burst_write - Write data to SPI by rt5514 address. + * @addr: Start address. + * @txbuf: Data Buffer for writng. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_WRITE; + u8 *write_buf; + unsigned int i, end, offset = 0; + + write_buf = kmalloc(RT5514_SPI_BUF_LEN + 6, GFP_KERNEL); + + if (write_buf == NULL) + return -ENOMEM; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + for (i = 0; i < end; i += 8) { + write_buf[i + 12] = txbuf[offset + i + 0]; + write_buf[i + 11] = txbuf[offset + i + 1]; + write_buf[i + 10] = txbuf[offset + i + 2]; + write_buf[i + 9] = txbuf[offset + i + 3]; + write_buf[i + 8] = txbuf[offset + i + 4]; + write_buf[i + 7] = txbuf[offset + i + 5]; + write_buf[i + 6] = txbuf[offset + i + 6]; + write_buf[i + 5] = txbuf[offset + i + 7]; + } + + write_buf[end + 5] = spi_cmd; + + spi_write(rt5514_spi, write_buf, end + 6); + + offset += RT5514_SPI_BUF_LEN; + } + + kfree(write_buf); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5514_spi_burst_write); + +static int rt5514_spi_probe(struct spi_device *spi) +{ + int ret; + + rt5514_spi = spi; + + ret = devm_snd_soc_register_platform(&spi->dev, &rt5514_spi_platform); + if (ret < 0) { + dev_err(&spi->dev, "Failed to register platform.\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&spi->dev, + &rt5514_spi_dai_component, + &rt5514_spi_dai, 1); + if (ret < 0) { + dev_err(&spi->dev, "Failed to register component.\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id rt5514_of_match[] = { + { .compatible = "realtek,rt5514", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5514_of_match); + +static struct spi_driver rt5514_spi_driver = { + .driver = { + .name = "rt5514", + .of_match_table = of_match_ptr(rt5514_of_match), + }, + .probe = rt5514_spi_probe, +}; +module_spi_driver(rt5514_spi_driver); + +MODULE_DESCRIPTION("RT5514 SPI driver"); +MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5514-spi.h b/sound/soc/codecs/rt5514-spi.h new file mode 100644 index 000000000..f69b1cdf2 --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.h @@ -0,0 +1,38 @@ +/* + * rt5514-spi.h -- RT5514 driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5514_SPI_H__ +#define __RT5514_SPI_H__ + +/** + * RT5514_SPI_BUF_LEN is the buffer size of SPI master controller. +*/ +#define RT5514_SPI_BUF_LEN 240 + +#define RT5514_BUFFER_VOICE_BASE 0x18001034 +#define RT5514_BUFFER_VOICE_LIMIT 0x18001038 +#define RT5514_BUFFER_VOICE_RP 0x1800103c +#define RT5514_BUFFER_VOICE_SIZE 0x18001040 + +/* SPI Command */ +enum { + RT5514_SPI_CMD_16_READ = 0, + RT5514_SPI_CMD_16_WRITE, + RT5514_SPI_CMD_32_READ, + RT5514_SPI_CMD_32_WRITE, + RT5514_SPI_CMD_BURST_READ, + RT5514_SPI_CMD_BURST_WRITE, +}; + +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len); +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len); + +#endif /* __RT5514_SPI_H__ */ diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 879bf60f4..a1bf1c0d7 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -30,6 +30,9 @@ #include "rl6231.h" #include "rt5514.h" +#if defined(CONFIG_SND_SOC_RT5514_SPI) +#include "rt5514-spi.h" +#endif static const struct reg_sequence rt5514_i2c_patch[] = { {0x1800101c, 0x00000000}, @@ -110,6 +113,35 @@ static const struct reg_default rt5514_reg[] = { {RT5514_VENDOR_ID2, 0x10ec5514}, }; +static void rt5514_enable_dsp_prepare(struct rt5514_priv *rt5514) +{ + /* Reset */ + regmap_write(rt5514->i2c_regmap, 0x18002000, 0x000010ec); + /* LDO_I_limit */ + regmap_write(rt5514->i2c_regmap, 0x18002200, 0x00028604); + /* I2C bypass enable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000001); + /* mini-core reset */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x0005514b); + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x00055149); + /* I2C bypass disable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000000); + /* PIN config */ + regmap_write(rt5514->i2c_regmap, 0x18002070, 0x00000040); + /* PLL3(QN)=RCOSC*(10+2) */ + regmap_write(rt5514->i2c_regmap, 0x18002240, 0x0000000a); + /* PLL3 source=RCOSC, fsi=rt_clk */ + regmap_write(rt5514->i2c_regmap, 0x18002100, 0x0000000b); + /* Power on RCOSC, pll3 */ + regmap_write(rt5514->i2c_regmap, 0x18002004, 0x00808b81); + /* DSP clk source = pll3, ENABLE DSP clk */ + regmap_write(rt5514->i2c_regmap, 0x18002f08, 0x00000005); + /* Enable DSP clk auto switch */ + regmap_write(rt5514->i2c_regmap, 0x18001114, 0x00000001); + /* Reduce DSP power */ + regmap_write(rt5514->i2c_regmap, 0x18001118, 0x00000001); +} + static bool rt5514_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { @@ -248,6 +280,74 @@ static const DECLARE_TLV_DB_RANGE(bst_tlv, static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static int rt5514_dsp_voice_wake_up_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5514->dsp_enabled; + + return 0; +} + +static int rt5514_dsp_voice_wake_up_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + struct snd_soc_codec *codec = rt5514->codec; + const struct firmware *fw = NULL; + + if (ucontrol->value.integer.value[0] == rt5514->dsp_enabled) + return 0; + + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + rt5514->dsp_enabled = ucontrol->value.integer.value[0]; + + if (rt5514->dsp_enabled) { + rt5514_enable_dsp_prepare(rt5514); + + reject_firmware(&fw, RT5514_FIRMWARE1, codec->dev); + if (fw) { +#if defined(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ff60000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(codec->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + reject_firmware(&fw, RT5514_FIRMWARE2, codec->dev); + if (fw) { +#if defined(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ffc0000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(codec->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + /* DSP run */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, + 0x00055148); + } else { + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + } + + return 0; +} + static const struct snd_kcontrol_new rt5514_snd_controls[] = { SOC_DOUBLE_TLV("MIC Boost Volume", RT5514_ANA_CTRL_MICBST, RT5514_SEL_BSTL_SFT, RT5514_SEL_BSTR_SFT, 8, 0, bst_tlv), @@ -257,6 +357,8 @@ static const struct snd_kcontrol_new rt5514_snd_controls[] = { SOC_DOUBLE_R_TLV("ADC2 Capture Volume", RT5514_DOWNFILTER1_CTRL1, RT5514_DOWNFILTER1_CTRL2, RT5514_AD_GAIN_SFT, 127, 0, adc_vol_tlv), + SOC_SINGLE_EXT("DSP Voice Wake Up", SND_SOC_NOPM, 0, 1, 0, + rt5514_dsp_voice_wake_up_get, rt5514_dsp_voice_wake_up_put), }; /* ADC Mixer*/ @@ -365,6 +467,35 @@ static int rt5514_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, return 0; } +static int rt5514_pre_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /** + * If the DSP is enabled in start of recording, the DSP + * should be disabled, and sync back to normal recording + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { + rt5514->dsp_enabled = 0; + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + break; + + default: + return 0; + } + + return 0; +} + static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = { /* Input Lines */ SND_SOC_DAPM_INPUT("DMIC1L"), @@ -472,6 +603,8 @@ static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = { /* Audio Interface */ SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PRE("DAPM Pre", rt5514_pre_event), }; static const struct snd_soc_dapm_route rt5514_dapm_routes[] = { @@ -799,10 +932,41 @@ static int rt5514_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static int rt5514_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + int ret; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (IS_ERR(rt5514->mclk)) + break; + + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5514->mclk); + } else { + ret = clk_prepare_enable(rt5514->mclk); + if (ret) + return ret; + } + break; + + default: + break; + } + + return 0; +} + static int rt5514_probe(struct snd_soc_codec *codec) { struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + rt5514->mclk = devm_clk_get(codec->dev, "mclk"); + if (PTR_ERR(rt5514->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + rt5514->codec = codec; return 0; @@ -858,6 +1022,7 @@ struct snd_soc_dai_driver rt5514_dai[] = { static struct snd_soc_codec_driver soc_codec_dev_rt5514 = { .probe = rt5514_probe, .idle_bias_off = true, + .set_bias_level = rt5514_set_bias_level, .controls = rt5514_snd_controls, .num_controls = ARRAY_SIZE(rt5514_snd_controls), .dapm_widgets = rt5514_dapm_widgets, @@ -871,7 +1036,6 @@ static const struct regmap_config rt5514_i2c_regmap = { .reg_bits = 32, .val_bits = 32, - .max_register = RT5514_DSP_MAPPING | RT5514_VENDOR_ID2, .readable_reg = rt5514_i2c_readable_register, .cache_type = REGCACHE_NONE, @@ -944,7 +1108,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c, return -ENODEV; } - ret = regmap_register_patch(rt5514->i2c_regmap, rt5514_i2c_patch, + ret = regmap_multi_reg_write(rt5514->i2c_regmap, rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); if (ret != 0) dev_warn(&i2c->dev, "Failed to apply i2c_regmap patch: %d\n", diff --git a/sound/soc/codecs/rt5514.h b/sound/soc/codecs/rt5514.h index 6ad8a612f..832f5eeb9 100644 --- a/sound/soc/codecs/rt5514.h +++ b/sound/soc/codecs/rt5514.h @@ -12,6 +12,8 @@ #ifndef __RT5514_H__ #define __RT5514_H__ +#include <linux/clk.h> + #define RT5514_DEVICE_ID 0x10ec5514 #define RT5514_RESET 0x2000 @@ -225,6 +227,9 @@ #define RT5514_PLL_INP_MAX 40000000 #define RT5514_PLL_INP_MIN 256000 +#define RT5514_FIRMWARE1 "/*(DEBLOBBED)*/" +#define RT5514_FIRMWARE2 "/*(DEBLOBBED)*/" + /* System Clock Source */ enum { RT5514_SCLK_S_MCLK, @@ -240,6 +245,7 @@ enum { struct rt5514_priv { struct snd_soc_codec *codec; struct regmap *i2c_regmap, *regmap; + struct clk *mclk; int sysclk; int sysclk_src; int lrck; @@ -247,6 +253,7 @@ struct rt5514_priv { int pll_src; int pll_in; int pll_out; + int dsp_enabled; }; #endif /* __RT5514_H__ */ diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index d70847c9e..490bfe661 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -63,6 +63,7 @@ static const struct reg_sequence init_list[] = { {RT5645_PR_BASE + 0x20, 0x611f}, {RT5645_PR_BASE + 0x21, 0x4040}, {RT5645_PR_BASE + 0x23, 0x0004}, + {RT5645_ASRC_4, 0x0120}, }; static const struct reg_sequence rt5650_init_list[] = { @@ -157,7 +158,7 @@ static const struct reg_default rt5645_reg[] = { { 0x83, 0x0000 }, { 0x84, 0x0000 }, { 0x85, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0120 }, { 0x8e, 0x0004 }, { 0x8f, 0x1100 }, { 0x90, 0x0646 }, @@ -314,7 +315,7 @@ static const struct reg_default rt5650_reg[] = { { 0x83, 0x0000 }, { 0x84, 0x0000 }, { 0x85, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0120 }, { 0x8e, 0x0004 }, { 0x8f, 0x1100 }, { 0x90, 0x0646 }, @@ -440,6 +441,7 @@ static bool rt5645_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case RT5645_RESET: + case RT5645_PRIV_INDEX: case RT5645_PRIV_DATA: case RT5645_IN1_CTRL1: case RT5645_IN1_CTRL2: @@ -740,6 +742,14 @@ static int rt5645_spk_put_volsw(struct snd_kcontrol *kcontrol, return ret; } +static const char * const rt5645_dac1_vol_ctrl_mode_text[] = { + "immediately", "zero crossing", "soft ramp" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac1_vol_ctrl_mode, RT5645_PR_BASE, + RT5645_DA1_ZDET_SFT, rt5645_dac1_vol_ctrl_mode_text); + static const struct snd_kcontrol_new rt5645_snd_controls[] = { /* Speaker Output Volume */ SOC_DOUBLE("Speaker Channel Switch", RT5645_SPK_VOL, @@ -806,6 +816,9 @@ static const struct snd_kcontrol_new rt5645_snd_controls[] = { SOC_SINGLE("I2S2 Func Switch", RT5645_GPIO_CTRL1, RT5645_I2S2_SEL_SFT, 1, 1), RT5645_HWEQ("Speaker HWEQ"), + + /* Digital Soft Volume Control */ + SOC_ENUM("DAC1 Digital Volume Control Func", rt5645_dac1_vol_ctrl_mode), }; /** @@ -3531,6 +3544,7 @@ MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); static const struct acpi_device_id rt5645_acpi_match[] = { { "10EC5645", 0 }, { "10EC5650", 0 }, + { "10EC5640", 0 }, {}, }; MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match); @@ -3561,6 +3575,12 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Setzer"), }, }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, { } }; diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h index 205e0715c..cfc5f9754 100644 --- a/sound/soc/codecs/rt5645.h +++ b/sound/soc/codecs/rt5645.h @@ -2018,6 +2018,9 @@ /* Codec Private Register definition */ +/* DAC ADC Digital Volume (0x00) */ +#define RT5645_DA1_ZDET_SFT 6 + /* 3D Speaker Control (0x63) */ #define RT5645_3D_SPK_MASK (0x1 << 15) #define RT5645_3D_SPK_SFT 15 diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 0af5ddbef..8ef467f64 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -55,6 +55,7 @@ static const struct reg_sequence init_list[] = { { RT5670_PR_BASE + 0x14, 0x9a8a }, { RT5670_PR_BASE + 0x38, 0x3ba1 }, { RT5670_PR_BASE + 0x3d, 0x3640 }, + { 0x8a, 0x0123 }, }; static const struct reg_default rt5670_reg[] = { @@ -131,7 +132,7 @@ static const struct reg_default rt5670_reg[] = { { 0x87, 0x0000 }, { 0x88, 0x0000 }, { 0x89, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0123 }, { 0x8b, 0x0000 }, { 0x8c, 0x0003 }, { 0x8d, 0x0000 }, diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 08b404606..527b759c1 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -38,7 +38,6 @@ /* default value of sgtl5000 registers */ static const struct reg_default sgtl5000_reg_defaults[] = { { SGTL5000_CHIP_DIG_POWER, 0x0000 }, - { SGTL5000_CHIP_CLK_CTRL, 0x0008 }, { SGTL5000_CHIP_I2S_CTRL, 0x0010 }, { SGTL5000_CHIP_SSS_CTRL, 0x0010 }, { SGTL5000_CHIP_ADCDAC_CTRL, 0x020c }, @@ -47,12 +46,10 @@ static const struct reg_default sgtl5000_reg_defaults[] = { { SGTL5000_CHIP_ANA_ADC_CTRL, 0x0000 }, { SGTL5000_CHIP_ANA_HP_CTRL, 0x1818 }, { SGTL5000_CHIP_ANA_CTRL, 0x0111 }, - { SGTL5000_CHIP_LINREG_CTRL, 0x0000 }, { SGTL5000_CHIP_REF_CTRL, 0x0000 }, { SGTL5000_CHIP_MIC_CTRL, 0x0000 }, { SGTL5000_CHIP_LINE_OUT_CTRL, 0x0000 }, { SGTL5000_CHIP_LINE_OUT_VOL, 0x0404 }, - { SGTL5000_CHIP_ANA_POWER, 0x7060 }, { SGTL5000_CHIP_PLL_CTRL, 0x5000 }, { SGTL5000_CHIP_CLK_TOP_CTRL, 0x0000 }, { SGTL5000_CHIP_ANA_STATUS, 0x0000 }, @@ -92,35 +89,8 @@ static const char *supply_names[SGTL5000_SUPPLY_NUM] = { "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 = 1200000, - .max_uV = 1200000, - .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; -}; +#define LINREG_VDDD ((1600 - LDO_VOLTAGE / 1000) / 50) enum sgtl5000_micbias_resistor { SGTL5000_MICBIAS_OFF = 0, @@ -135,7 +105,7 @@ struct sgtl5000_priv { int master; /* i2s master or not */ int fmt; /* i2s data format */ struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM]; - struct ldo_regulator *ldo; + int num_supplies; struct regmap *regmap; struct clk *mclk; int revision; @@ -415,6 +385,9 @@ static const DECLARE_TLV_DB_RANGE(mic_gain_tlv, /* tlv for hp volume, -51.5db to 12.0db, step .5db */ static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); +/* tlv for lineout volume, 31 steps of .5db each */ +static const DECLARE_TLV_DB_SCALE(lineout_volume, -1550, 50, 0); + static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { /* SOC_DOUBLE_S8_TLV with invert */ { @@ -443,6 +416,13 @@ static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, 0, 3, 0, mic_gain_tlv), + + SOC_DOUBLE_TLV("Lineout Playback Volume", + SGTL5000_CHIP_LINE_OUT_VOL, + SGTL5000_LINE_OUT_VOL_LEFT_SHIFT, + SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT, + 0x1f, 1, + lineout_volume), }; /* mute the codec used by alsa core */ @@ -778,155 +758,6 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -#ifdef CONFIG_REGULATOR -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, - SGTL5000_LINREG_VDDD_MASK, reg); - - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINEREG_D_POWERUP, - SGTL5000_LINEREG_D_POWERUP); - - /* when internal ldo is 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, - SGTL5000_LINREG_VDDD_MASK, 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; - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - struct regulator_config config = { }; - - ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL); - - if (!ldo) - 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; - - config.dev = codec->dev; - config.driver_data = ldo; - config.init_data = init_data; - - ldo->dev = regulator_register(&ldo->desc, &config); - 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; - } - sgtl5000->ldo = ldo; - - 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; -} -#else -static int ldo_regulator_register(struct snd_soc_codec *codec, - struct regulator_init_data *init_data, - int voltage) -{ - dev_err(codec->dev, "this setup needs regulator support in the kernel\n"); - return -EINVAL; -} - -static int ldo_regulator_remove(struct snd_soc_codec *codec) -{ - return 0; -} -#endif - /* * set dac bias * common state changes: @@ -940,42 +771,17 @@ static int ldo_regulator_remove(struct snd_soc_codec *codec) 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 (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { - ret = regulator_bulk_enable( - ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - if (ret) - return ret; - udelay(10); - - regcache_cache_only(sgtl5000->regmap, false); - - ret = regcache_sync(sgtl5000->regmap); - if (ret != 0) { - dev_err(codec->dev, - "Failed to restore cache: %d\n", ret); - - regcache_cache_only(sgtl5000->regmap, true); - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - - return ret; - } - } - + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, + SGTL5000_REFTOP_POWERUP); break; case SND_SOC_BIAS_OFF: - regcache_cache_only(sgtl5000->regmap, true); - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, 0); break; } @@ -1113,7 +919,6 @@ static const u8 vol_quot_table[] = { * 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) { @@ -1131,7 +936,9 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *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); + vddd = (sgtl5000->num_supplies > VDDD) + ? regulator_get_voltage(sgtl5000->supplies[VDDD].consumer) + : LDO_VOLTAGE; vdda = vdda / 1000; vddio = vddio / 1000; @@ -1178,25 +985,6 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); - /* set voltage to register */ - snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, - SGTL5000_LINREG_VDDD_MASK, 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) @@ -1256,78 +1044,43 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) return 0; } -static int sgtl5000_replace_vddd_with_ldo(struct snd_soc_codec *codec) -{ - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - int ret; - - /* set internal ldo to 1.2v */ - ret = ldo_regulator_register(codec, &ldo_init_data, LDO_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; - - dev_info(codec->dev, "Using internal LDO instead of VDDD\n"); - return 0; -} - -static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) +static int sgtl5000_enable_regulators(struct i2c_client *client) { int ret; int i; int external_vddd = 0; - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); struct regulator *vddd; + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++) sgtl5000->supplies[i].supply = supply_names[i]; - /* External VDDD only works before revision 0x11 */ - if (sgtl5000->revision < 0x11) { - vddd = regulator_get_optional(codec->dev, "VDDD"); - if (IS_ERR(vddd)) { - /* See if it's just not registered yet */ - if (PTR_ERR(vddd) == -EPROBE_DEFER) - return -EPROBE_DEFER; - } else { - external_vddd = 1; - regulator_put(vddd); - } - } - - if (!external_vddd) { - ret = sgtl5000_replace_vddd_with_ldo(codec); - if (ret) - return ret; + vddd = regulator_get_optional(&client->dev, "VDDD"); + if (IS_ERR(vddd)) { + /* See if it's just not registered yet */ + if (PTR_ERR(vddd) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + external_vddd = 1; + regulator_put(vddd); } - ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->num_supplies = ARRAY_SIZE(sgtl5000->supplies) + - 1 + external_vddd; + ret = regulator_bulk_get(&client->dev, sgtl5000->num_supplies, sgtl5000->supplies); if (ret) - goto err_ldo_remove; - - 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 ret; - return 0; + ret = regulator_bulk_enable(sgtl5000->num_supplies, + sgtl5000->supplies); + if (!ret) + usleep_range(10, 20); + else + regulator_bulk_free(sgtl5000->num_supplies, + sgtl5000->supplies); -err_regulator_free: - regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); -err_ldo_remove: - if (!external_vddd) - ldo_regulator_remove(codec); return ret; - } static int sgtl5000_probe(struct snd_soc_codec *codec) @@ -1335,10 +1088,6 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) int ret; struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - ret = sgtl5000_enable_regulators(codec); - if (ret) - return ret; - /* power up sgtl5000 */ ret = sgtl5000_set_power_regs(codec); if (ret) @@ -1389,25 +1138,11 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) 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); - - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - ldo_regulator_remove(codec); - return 0; } @@ -1448,8 +1183,9 @@ static const struct regmap_config sgtl5000_regmap = { * and avoid problems like, not being able to probe after an audio playback * followed by a system reset or a 'reboot' command in Linux */ -static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000) +static void sgtl5000_fill_defaults(struct i2c_client *client) { + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); int i, ret, val, index; for (i = 0; i < ARRAY_SIZE(sgtl5000_reg_defaults); i++) { @@ -1457,10 +1193,10 @@ static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000) index = sgtl5000_reg_defaults[i].reg; ret = regmap_write(sgtl5000->regmap, index, val); if (ret) - return ret; + dev_err(&client->dev, + "%s: error %d setting reg 0x%02x to 0x%04x\n", + __func__, ret, index, val); } - - return 0; } static int sgtl5000_i2c_probe(struct i2c_client *client, @@ -1470,16 +1206,23 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, int ret, reg, rev; struct device_node *np = client->dev.of_node; u32 value; + u16 ana_pwr; sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL); if (!sgtl5000) return -ENOMEM; + i2c_set_clientdata(client, sgtl5000); + + ret = sgtl5000_enable_regulators(client); + if (ret) + return ret; + sgtl5000->regmap = devm_regmap_init_i2c(client, &sgtl5000_regmap); if (IS_ERR(sgtl5000->regmap)) { ret = PTR_ERR(sgtl5000->regmap); dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); - return ret; + goto disable_regs; } sgtl5000->mclk = devm_clk_get(&client->dev, NULL); @@ -1488,21 +1231,25 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, dev_err(&client->dev, "Failed to get mclock: %d\n", ret); /* Defer the probe to see if the clk will be provided later */ if (ret == -ENOENT) - return -EPROBE_DEFER; - return ret; + ret = -EPROBE_DEFER; + goto disable_regs; } ret = clk_prepare_enable(sgtl5000->mclk); - if (ret) - return ret; + if (ret) { + dev_err(&client->dev, "Error enabling clock %d\n", ret); + goto disable_regs; + } /* Need 8 clocks before I2C accesses */ udelay(1); /* read chip information */ ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ID, ®); - if (ret) + if (ret) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); goto disable_clk; + } if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != SGTL5000_PARTID_PART_ID) { @@ -1516,6 +1263,44 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev); sgtl5000->revision = rev; + /* reconfigure the clocks in case we're using the PLL */ + ret = regmap_write(sgtl5000->regmap, + SGTL5000_CHIP_CLK_CTRL, + SGTL5000_CHIP_CLK_CTRL_DEFAULT); + if (ret) + dev_err(&client->dev, + "Error %d initializing CHIP_CLK_CTRL\n", ret); + + /* Follow section 2.2.1.1 of AN3663 */ + ana_pwr = SGTL5000_ANA_POWER_DEFAULT; + if (sgtl5000->num_supplies <= VDDD) { + /* internal VDDD at 1.2V */ + ret = regmap_update_bits(sgtl5000->regmap, + SGTL5000_CHIP_LINREG_CTRL, + SGTL5000_LINREG_VDDD_MASK, + LINREG_VDDD); + if (ret) + dev_err(&client->dev, + "Error %d setting LINREG_VDDD\n", ret); + + ana_pwr |= SGTL5000_LINEREG_D_POWERUP; + dev_info(&client->dev, + "Using internal LDO instead of VDDD: check ER1\n"); + } else { + /* using external LDO for VDDD + * Clear startup powerup and simple powerup + * bits to save power + */ + ana_pwr &= ~(SGTL5000_STARTUP_POWERUP + | SGTL5000_LINREG_SIMPLE_POWERUP); + dev_dbg(&client->dev, "Using external VDDD\n"); + } + ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, ana_pwr); + if (ret) + dev_err(&client->dev, + "Error %d setting CHIP_ANA_POWER to %04x\n", + ret, ana_pwr); + if (np) { if (!of_property_read_u32(np, "micbias-resistor-k-ohms", &value)) { @@ -1557,12 +1342,8 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, } } - i2c_set_clientdata(client, sgtl5000); - /* Ensure sgtl5000 will start with sane register values */ - ret = sgtl5000_fill_defaults(sgtl5000); - if (ret) - goto disable_clk; + sgtl5000_fill_defaults(client); ret = snd_soc_register_codec(&client->dev, &sgtl5000_driver, &sgtl5000_dai, 1); @@ -1573,6 +1354,11 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, disable_clk: clk_disable_unprepare(sgtl5000->mclk); + +disable_regs: + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + return ret; } @@ -1582,6 +1368,9 @@ static int sgtl5000_i2c_remove(struct i2c_client *client) snd_soc_unregister_codec(&client->dev); clk_disable_unprepare(sgtl5000->mclk); + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + return 0; } diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h index 1c317de26..22f3442af 100644 --- a/sound/soc/codecs/sgtl5000.h +++ b/sound/soc/codecs/sgtl5000.h @@ -92,6 +92,7 @@ /* * SGTL5000_CHIP_CLK_CTRL */ +#define SGTL5000_CHIP_CLK_CTRL_DEFAULT 0x0008 #define SGTL5000_RATE_MODE_MASK 0x0030 #define SGTL5000_RATE_MODE_SHIFT 4 #define SGTL5000_RATE_MODE_WIDTH 2 @@ -325,6 +326,7 @@ /* * SGTL5000_CHIP_ANA_POWER */ +#define SGTL5000_ANA_POWER_DEFAULT 0x7060 #define SGTL5000_DAC_STEREO 0x4000 #define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 #define SGTL5000_STARTUP_POWERUP 0x1000 diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index b8d19b77b..d8baca3f8 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -28,6 +28,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/tlv.h> +#include <asm/unaligned.h> #include "tas571x.h" @@ -63,6 +64,10 @@ static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg) case TAS571X_INPUT_MUX_REG: case TAS571X_CH4_SRC_SELECT_REG: case TAS571X_PWM_MUX_REG: + case TAS5717_CH1_RIGHT_CH_MIX_REG: + case TAS5717_CH1_LEFT_CH_MIX_REG: + case TAS5717_CH2_LEFT_CH_MIX_REG: + case TAS5717_CH2_RIGHT_CH_MIX_REG: return 4; default: return 1; @@ -135,6 +140,129 @@ static int tas571x_reg_read(void *context, unsigned int reg, return 0; } +/* + * register write for 8- and 20-byte registers + */ +static int tas571x_reg_write_multiword(struct i2c_client *client, + unsigned int reg, const long values[], size_t len) +{ + size_t i; + uint8_t *buf, *p; + int ret; + size_t send_size = 1 + len * sizeof(uint32_t); + + buf = kzalloc(send_size, GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + buf[0] = reg; + + for (i = 0, p = buf + 1; i < len; i++, p += sizeof(uint32_t)) + put_unaligned_be32(values[i], p); + + ret = i2c_master_send(client, buf, send_size); + + kfree(buf); + + if (ret == send_size) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +/* + * register read for 8- and 20-byte registers + */ +static int tas571x_reg_read_multiword(struct i2c_client *client, + unsigned int reg, long values[], size_t len) +{ + unsigned int i; + uint8_t send_buf; + uint8_t *recv_buf, *p; + struct i2c_msg msgs[2]; + unsigned int recv_size = len * sizeof(uint32_t); + int ret; + + recv_buf = kzalloc(recv_size, GFP_KERNEL | GFP_DMA); + if (!recv_buf) + return -ENOMEM; + + send_buf = reg; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = &send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = recv_size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + goto err_ret; + else if (ret != ARRAY_SIZE(msgs)) { + ret = -EIO; + goto err_ret; + } + + for (i = 0, p = recv_buf; i < len; i++, p += sizeof(uint32_t)) + values[i] = get_unaligned_be32(p); + +err_ret: + kfree(recv_buf); + return ret; +} + +/* + * Integer array controls for setting biquad, mixer, DRC coefficients. + * According to the datasheet each coefficient is effectively 26bits, + * i.e. stored as 32bits, where bits [31:26] are ignored. + * TI's TAS57xx Graphical Development Environment tool however produces + * coefficients with more than 26 bits. For this reason we allow values + * in the full 32-bits reange. + * The coefficients are ordered as given in the TAS571x data sheet: + * b0, b1, b2, a1, a2 + */ + +static int tas571x_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = numcoef; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffffffff; + return 0; +} + +static int tas571x_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct i2c_client *i2c = to_i2c_client(codec->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_read_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + +static int tas571x_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct i2c_client *i2c = to_i2c_client(codec->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_write_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + static int tas571x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format) { struct tas571x_private *priv = snd_soc_codec_get_drvdata(dai->codec); @@ -241,6 +369,15 @@ static const struct snd_soc_dai_ops tas571x_dai_ops = { .digital_mute = tas571x_mute, }; + +#define BIQUAD_COEFS(xname, reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = tas571x_coefficient_info, \ + .get = tas571x_coefficient_get,\ + .put = tas571x_coefficient_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = reg | (5 << 16) } + static const char *const tas5711_supply_names[] = { "AVDD", "DVDD", @@ -264,6 +401,16 @@ static const struct snd_kcontrol_new tas5711_controls[] = { TAS571X_SOFT_MUTE_REG, TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, 1, 1), + + SOC_DOUBLE_R_RANGE("CH1 Mixer Volume", + TAS5717_CH1_LEFT_CH_MIX_REG, + TAS5717_CH1_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), + + SOC_DOUBLE_R_RANGE("CH2 Mixer Volume", + TAS5717_CH2_LEFT_CH_MIX_REG, + TAS5717_CH2_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), }; static const struct regmap_range tas571x_readonly_regs_range[] = { @@ -340,6 +487,43 @@ static const struct snd_kcontrol_new tas5717_controls[] = { TAS571X_SOFT_MUTE_REG, TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, 1, 1), + + /* + * The biquads are named according to the register names. + * Please note that TI's TAS57xx Graphical Development Environment + * tool names them different. + */ + BIQUAD_COEFS("CH1 - Biquad 0", TAS5717_CH1_BQ0_REG), + BIQUAD_COEFS("CH1 - Biquad 1", TAS5717_CH1_BQ1_REG), + BIQUAD_COEFS("CH1 - Biquad 2", TAS5717_CH1_BQ2_REG), + BIQUAD_COEFS("CH1 - Biquad 3", TAS5717_CH1_BQ3_REG), + BIQUAD_COEFS("CH1 - Biquad 4", TAS5717_CH1_BQ4_REG), + BIQUAD_COEFS("CH1 - Biquad 5", TAS5717_CH1_BQ5_REG), + BIQUAD_COEFS("CH1 - Biquad 6", TAS5717_CH1_BQ6_REG), + BIQUAD_COEFS("CH1 - Biquad 7", TAS5717_CH1_BQ7_REG), + BIQUAD_COEFS("CH1 - Biquad 8", TAS5717_CH1_BQ8_REG), + BIQUAD_COEFS("CH1 - Biquad 9", TAS5717_CH1_BQ9_REG), + BIQUAD_COEFS("CH1 - Biquad 10", TAS5717_CH1_BQ10_REG), + BIQUAD_COEFS("CH1 - Biquad 11", TAS5717_CH1_BQ11_REG), + + BIQUAD_COEFS("CH2 - Biquad 0", TAS5717_CH2_BQ0_REG), + BIQUAD_COEFS("CH2 - Biquad 1", TAS5717_CH2_BQ1_REG), + BIQUAD_COEFS("CH2 - Biquad 2", TAS5717_CH2_BQ2_REG), + BIQUAD_COEFS("CH2 - Biquad 3", TAS5717_CH2_BQ3_REG), + BIQUAD_COEFS("CH2 - Biquad 4", TAS5717_CH2_BQ4_REG), + BIQUAD_COEFS("CH2 - Biquad 5", TAS5717_CH2_BQ5_REG), + BIQUAD_COEFS("CH2 - Biquad 6", TAS5717_CH2_BQ6_REG), + BIQUAD_COEFS("CH2 - Biquad 7", TAS5717_CH2_BQ7_REG), + BIQUAD_COEFS("CH2 - Biquad 8", TAS5717_CH2_BQ8_REG), + BIQUAD_COEFS("CH2 - Biquad 9", TAS5717_CH2_BQ9_REG), + BIQUAD_COEFS("CH2 - Biquad 10", TAS5717_CH2_BQ10_REG), + BIQUAD_COEFS("CH2 - Biquad 11", TAS5717_CH2_BQ11_REG), + + BIQUAD_COEFS("CH3 - Biquad 0", TAS5717_CH3_BQ0_REG), + BIQUAD_COEFS("CH3 - Biquad 1", TAS5717_CH3_BQ1_REG), + + BIQUAD_COEFS("CH4 - Biquad 0", TAS5717_CH4_BQ0_REG), + BIQUAD_COEFS("CH4 - Biquad 1", TAS5717_CH4_BQ1_REG), }; static const struct reg_default tas5717_reg_defaults[] = { @@ -350,6 +534,10 @@ static const struct reg_default tas5717_reg_defaults[] = { { 0x08, 0x00c0 }, { 0x09, 0x00c0 }, { 0x1b, 0x82 }, + { TAS5717_CH1_RIGHT_CH_MIX_REG, 0x0 }, + { TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000}, + { TAS5717_CH2_LEFT_CH_MIX_REG, 0x0 }, + { TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000}, }; static const struct regmap_config tas5717_regmap_config = { diff --git a/sound/soc/codecs/tas571x.h b/sound/soc/codecs/tas571x.h index cf800c364..c45677bc2 100644 --- a/sound/soc/codecs/tas571x.h +++ b/sound/soc/codecs/tas571x.h @@ -52,4 +52,44 @@ #define TAS571X_CH4_SRC_SELECT_REG 0x21 #define TAS571X_PWM_MUX_REG 0x25 +/* 20-byte biquad registers */ +#define TAS5717_CH1_BQ0_REG 0x26 +#define TAS5717_CH1_BQ1_REG 0x27 +#define TAS5717_CH1_BQ2_REG 0x28 +#define TAS5717_CH1_BQ3_REG 0x29 +#define TAS5717_CH1_BQ4_REG 0x2a +#define TAS5717_CH1_BQ5_REG 0x2b +#define TAS5717_CH1_BQ6_REG 0x2c +#define TAS5717_CH1_BQ7_REG 0x2d +#define TAS5717_CH1_BQ8_REG 0x2e +#define TAS5717_CH1_BQ9_REG 0x2f + +#define TAS5717_CH2_BQ0_REG 0x30 +#define TAS5717_CH2_BQ1_REG 0x31 +#define TAS5717_CH2_BQ2_REG 0x32 +#define TAS5717_CH2_BQ3_REG 0x33 +#define TAS5717_CH2_BQ4_REG 0x34 +#define TAS5717_CH2_BQ5_REG 0x35 +#define TAS5717_CH2_BQ6_REG 0x36 +#define TAS5717_CH2_BQ7_REG 0x37 +#define TAS5717_CH2_BQ8_REG 0x38 +#define TAS5717_CH2_BQ9_REG 0x39 + +#define TAS5717_CH1_BQ10_REG 0x58 +#define TAS5717_CH1_BQ11_REG 0x59 + +#define TAS5717_CH4_BQ0_REG 0x5a +#define TAS5717_CH4_BQ1_REG 0x5b + +#define TAS5717_CH2_BQ10_REG 0x5c +#define TAS5717_CH2_BQ11_REG 0x5d + +#define TAS5717_CH3_BQ0_REG 0x5e +#define TAS5717_CH3_BQ1_REG 0x5f + +#define TAS5717_CH1_RIGHT_CH_MIX_REG 0x72 +#define TAS5717_CH1_LEFT_CH_MIX_REG 0x73 +#define TAS5717_CH2_LEFT_CH_MIX_REG 0x76 +#define TAS5717_CH2_RIGHT_CH_MIX_REG 0x77 + #endif /* _TAS571X_H */ diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index fe16c3460..ac9b14652 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -38,141 +38,143 @@ struct aic31xx_pdata { int micbias_vg; }; +#define AIC31XX_REG(page, reg) ((page * 128) + reg) + /* Page Control Register */ -#define AIC31XX_PAGECTL 0x00 +#define AIC31XX_PAGECTL AIC31XX_REG(0, 0) /* Page 0 Registers */ /* Software reset register */ -#define AIC31XX_RESET 0x01 +#define AIC31XX_RESET AIC31XX_REG(0, 1) /* OT FLAG register */ -#define AIC31XX_OT_FLAG 0x03 +#define AIC31XX_OT_FLAG AIC31XX_REG(0, 3) /* Clock clock Gen muxing, Multiplexers*/ -#define AIC31XX_CLKMUX 0x04 +#define AIC31XX_CLKMUX AIC31XX_REG(0, 4) /* PLL P and R-VAL register */ -#define AIC31XX_PLLPR 0x05 +#define AIC31XX_PLLPR AIC31XX_REG(0, 5) /* PLL J-VAL register */ -#define AIC31XX_PLLJ 0x06 +#define AIC31XX_PLLJ AIC31XX_REG(0, 6) /* PLL D-VAL MSB register */ -#define AIC31XX_PLLDMSB 0x07 +#define AIC31XX_PLLDMSB AIC31XX_REG(0, 7) /* PLL D-VAL LSB register */ -#define AIC31XX_PLLDLSB 0x08 +#define AIC31XX_PLLDLSB AIC31XX_REG(0, 8) /* DAC NDAC_VAL register*/ -#define AIC31XX_NDAC 0x0B +#define AIC31XX_NDAC AIC31XX_REG(0, 11) /* DAC MDAC_VAL register */ -#define AIC31XX_MDAC 0x0C +#define AIC31XX_MDAC AIC31XX_REG(0, 12) /* DAC OSR setting register 1, MSB value */ -#define AIC31XX_DOSRMSB 0x0D +#define AIC31XX_DOSRMSB AIC31XX_REG(0, 13) /* DAC OSR setting register 2, LSB value */ -#define AIC31XX_DOSRLSB 0x0E -#define AIC31XX_MINI_DSP_INPOL 0x10 +#define AIC31XX_DOSRLSB AIC31XX_REG(0, 14) +#define AIC31XX_MINI_DSP_INPOL AIC31XX_REG(0, 16) /* Clock setting register 8, PLL */ -#define AIC31XX_NADC 0x12 +#define AIC31XX_NADC AIC31XX_REG(0, 18) /* Clock setting register 9, PLL */ -#define AIC31XX_MADC 0x13 +#define AIC31XX_MADC AIC31XX_REG(0, 19) /* ADC Oversampling (AOSR) Register */ -#define AIC31XX_AOSR 0x14 +#define AIC31XX_AOSR AIC31XX_REG(0, 20) /* Clock setting register 9, Multiplexers */ -#define AIC31XX_CLKOUTMUX 0x19 +#define AIC31XX_CLKOUTMUX AIC31XX_REG(0, 25) /* Clock setting register 10, CLOCKOUT M divider value */ -#define AIC31XX_CLKOUTMVAL 0x1A +#define AIC31XX_CLKOUTMVAL AIC31XX_REG(0, 26) /* Audio Interface Setting Register 1 */ -#define AIC31XX_IFACE1 0x1B +#define AIC31XX_IFACE1 AIC31XX_REG(0, 27) /* Audio Data Slot Offset Programming */ -#define AIC31XX_DATA_OFFSET 0x1C +#define AIC31XX_DATA_OFFSET AIC31XX_REG(0, 28) /* Audio Interface Setting Register 2 */ -#define AIC31XX_IFACE2 0x1D +#define AIC31XX_IFACE2 AIC31XX_REG(0, 29) /* Clock setting register 11, BCLK N Divider */ -#define AIC31XX_BCLKN 0x1E +#define AIC31XX_BCLKN AIC31XX_REG(0, 30) /* Audio Interface Setting Register 3, Secondary Audio Interface */ -#define AIC31XX_IFACESEC1 0x1F +#define AIC31XX_IFACESEC1 AIC31XX_REG(0, 31) /* Audio Interface Setting Register 4 */ -#define AIC31XX_IFACESEC2 0x20 +#define AIC31XX_IFACESEC2 AIC31XX_REG(0, 32) /* Audio Interface Setting Register 5 */ -#define AIC31XX_IFACESEC3 0x21 +#define AIC31XX_IFACESEC3 AIC31XX_REG(0, 33) /* I2C Bus Condition */ -#define AIC31XX_I2C 0x22 +#define AIC31XX_I2C AIC31XX_REG(0, 34) /* ADC FLAG */ -#define AIC31XX_ADCFLAG 0x24 +#define AIC31XX_ADCFLAG AIC31XX_REG(0, 36) /* DAC Flag Registers */ -#define AIC31XX_DACFLAG1 0x25 -#define AIC31XX_DACFLAG2 0x26 +#define AIC31XX_DACFLAG1 AIC31XX_REG(0, 37) +#define AIC31XX_DACFLAG2 AIC31XX_REG(0, 38) /* Sticky Interrupt flag (overflow) */ -#define AIC31XX_OFFLAG 0x27 +#define AIC31XX_OFFLAG AIC31XX_REG(0, 39) /* Sticy DAC Interrupt flags */ -#define AIC31XX_INTRDACFLAG 0x2C +#define AIC31XX_INTRDACFLAG AIC31XX_REG(0, 44) /* Sticy ADC Interrupt flags */ -#define AIC31XX_INTRADCFLAG 0x2D +#define AIC31XX_INTRADCFLAG AIC31XX_REG(0, 45) /* DAC Interrupt flags 2 */ -#define AIC31XX_INTRDACFLAG2 0x2E +#define AIC31XX_INTRDACFLAG2 AIC31XX_REG(0, 46) /* ADC Interrupt flags 2 */ -#define AIC31XX_INTRADCFLAG2 0x2F +#define AIC31XX_INTRADCFLAG2 AIC31XX_REG(0, 47) /* INT1 interrupt control */ -#define AIC31XX_INT1CTRL 0x30 +#define AIC31XX_INT1CTRL AIC31XX_REG(0, 48) /* INT2 interrupt control */ -#define AIC31XX_INT2CTRL 0x31 +#define AIC31XX_INT2CTRL AIC31XX_REG(0, 49) /* GPIO1 control */ -#define AIC31XX_GPIO1 0x33 +#define AIC31XX_GPIO1 AIC31XX_REG(0, 50) -#define AIC31XX_DACPRB 0x3C +#define AIC31XX_DACPRB AIC31XX_REG(0, 60) /* ADC Instruction Set Register */ -#define AIC31XX_ADCPRB 0x3D +#define AIC31XX_ADCPRB AIC31XX_REG(0, 61) /* DAC channel setup register */ -#define AIC31XX_DACSETUP 0x3F +#define AIC31XX_DACSETUP AIC31XX_REG(0, 63) /* DAC Mute and volume control register */ -#define AIC31XX_DACMUTE 0x40 +#define AIC31XX_DACMUTE AIC31XX_REG(0, 64) /* Left DAC channel digital volume control */ -#define AIC31XX_LDACVOL 0x41 +#define AIC31XX_LDACVOL AIC31XX_REG(0, 65) /* Right DAC channel digital volume control */ -#define AIC31XX_RDACVOL 0x42 +#define AIC31XX_RDACVOL AIC31XX_REG(0, 66) /* Headset detection */ -#define AIC31XX_HSDETECT 0x43 +#define AIC31XX_HSDETECT AIC31XX_REG(0, 67) /* ADC Digital Mic */ -#define AIC31XX_ADCSETUP 0x51 +#define AIC31XX_ADCSETUP AIC31XX_REG(0, 81) /* ADC Digital Volume Control Fine Adjust */ -#define AIC31XX_ADCFGA 0x52 +#define AIC31XX_ADCFGA AIC31XX_REG(0, 82) /* ADC Digital Volume Control Coarse Adjust */ -#define AIC31XX_ADCVOL 0x53 +#define AIC31XX_ADCVOL AIC31XX_REG(0, 83) /* Page 1 Registers */ /* Headphone drivers */ -#define AIC31XX_HPDRIVER 0x9F +#define AIC31XX_HPDRIVER AIC31XX_REG(1, 31) /* Class-D Speakear Amplifier */ -#define AIC31XX_SPKAMP 0xA0 +#define AIC31XX_SPKAMP AIC31XX_REG(1, 32) /* HP Output Drivers POP Removal Settings */ -#define AIC31XX_HPPOP 0xA1 +#define AIC31XX_HPPOP AIC31XX_REG(1, 33) /* Output Driver PGA Ramp-Down Period Control */ -#define AIC31XX_SPPGARAMP 0xA2 +#define AIC31XX_SPPGARAMP AIC31XX_REG(1, 34) /* DAC_L and DAC_R Output Mixer Routing */ -#define AIC31XX_DACMIXERROUTE 0xA3 +#define AIC31XX_DACMIXERROUTE AIC31XX_REG(1, 35) /* Left Analog Vol to HPL */ -#define AIC31XX_LANALOGHPL 0xA4 +#define AIC31XX_LANALOGHPL AIC31XX_REG(1, 36) /* Right Analog Vol to HPR */ -#define AIC31XX_RANALOGHPR 0xA5 +#define AIC31XX_RANALOGHPR AIC31XX_REG(1, 37) /* Left Analog Vol to SPL */ -#define AIC31XX_LANALOGSPL 0xA6 +#define AIC31XX_LANALOGSPL AIC31XX_REG(1, 38) /* Right Analog Vol to SPR */ -#define AIC31XX_RANALOGSPR 0xA7 +#define AIC31XX_RANALOGSPR AIC31XX_REG(1, 39) /* HPL Driver */ -#define AIC31XX_HPLGAIN 0xA8 +#define AIC31XX_HPLGAIN AIC31XX_REG(1, 40) /* HPR Driver */ -#define AIC31XX_HPRGAIN 0xA9 +#define AIC31XX_HPRGAIN AIC31XX_REG(1, 41) /* SPL Driver */ -#define AIC31XX_SPLGAIN 0xAA +#define AIC31XX_SPLGAIN AIC31XX_REG(1, 42) /* SPR Driver */ -#define AIC31XX_SPRGAIN 0xAB +#define AIC31XX_SPRGAIN AIC31XX_REG(1, 43) /* HP Driver Control */ -#define AIC31XX_HPCONTROL 0xAC +#define AIC31XX_HPCONTROL AIC31XX_REG(1, 44) /* MIC Bias Control */ -#define AIC31XX_MICBIAS 0xAE +#define AIC31XX_MICBIAS AIC31XX_REG(1, 46) /* MIC PGA*/ -#define AIC31XX_MICPGA 0xAF +#define AIC31XX_MICPGA AIC31XX_REG(1, 47) /* Delta-Sigma Mono ADC Channel Fine-Gain Input Selection for P-Terminal */ -#define AIC31XX_MICPGAPI 0xB0 +#define AIC31XX_MICPGAPI AIC31XX_REG(1, 48) /* ADC Input Selection for M-Terminal */ -#define AIC31XX_MICPGAMI 0xB1 +#define AIC31XX_MICPGAMI AIC31XX_REG(1, 49) /* Input CM Settings */ -#define AIC31XX_MICPGACM 0xB2 +#define AIC31XX_MICPGACM AIC31XX_REG(1, 50) /* Bits, masks and shifts */ diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 11d85c5c7..f1ea052a8 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -32,6 +32,7 @@ #include <sound/tlv.h> #include <linux/of.h> #include <linux/of_gpio.h> +#include <linux/regmap.h> #include "tpa6130a2.h" @@ -40,219 +41,72 @@ enum tpa_model { TPA6140A2, }; -static struct i2c_client *tpa6130a2_client; - /* This struct is used to save the context */ struct tpa6130a2_data { - struct mutex mutex; - unsigned char regs[TPA6130A2_CACHEREGNUM]; + struct device *dev; + struct regmap *regmap; struct regulator *supply; int power_gpio; - u8 power_state:1; enum tpa_model id; }; -static int tpa6130a2_i2c_read(int reg) -{ - struct tpa6130a2_data *data; - int val; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - /* If powered off, return the cached value */ - if (data->power_state) { - val = i2c_smbus_read_byte_data(tpa6130a2_client, reg); - if (val < 0) - dev_err(&tpa6130a2_client->dev, "Read failed\n"); - else - data->regs[reg] = val; - } else { - val = data->regs[reg]; - } - - return val; -} - -static int tpa6130a2_i2c_write(int reg, u8 value) -{ - struct tpa6130a2_data *data; - int val = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - if (data->power_state) { - val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value); - if (val < 0) { - dev_err(&tpa6130a2_client->dev, "Write failed\n"); - return val; - } - } - - /* Either powered on or off, we save the context */ - data->regs[reg] = value; - - return val; -} - -static u8 tpa6130a2_read(int reg) -{ - struct tpa6130a2_data *data; - - if (WARN_ON(!tpa6130a2_client)) - return 0; - data = i2c_get_clientdata(tpa6130a2_client); - - return data->regs[reg]; -} - -static int tpa6130a2_initialize(void) -{ - struct tpa6130a2_data *data; - int i, ret = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - for (i = 1; i < TPA6130A2_REG_VERSION; i++) { - ret = tpa6130a2_i2c_write(i, data->regs[i]); - if (ret < 0) - break; - } - - return ret; -} - -static int tpa6130a2_power(u8 power) +static int tpa6130a2_power(struct tpa6130a2_data *data, bool enable) { - struct tpa6130a2_data *data; - u8 val; - int ret = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - mutex_lock(&data->mutex); - if (power == data->power_state) - goto exit; + int ret; - if (power) { + if (enable) { ret = regulator_enable(data->supply); if (ret != 0) { - dev_err(&tpa6130a2_client->dev, + dev_err(data->dev, "Failed to enable supply: %d\n", ret); - goto exit; + return ret; } /* Power on */ if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 1); - - data->power_state = 1; - ret = tpa6130a2_initialize(); - if (ret < 0) { - dev_err(&tpa6130a2_client->dev, - "Failed to initialize chip\n"); - if (data->power_gpio >= 0) - gpio_set_value(data->power_gpio, 0); - regulator_disable(data->supply); - data->power_state = 0; - goto exit; - } } else { - /* set SWS */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val |= TPA6130A2_SWS; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - /* Power off */ if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 0); ret = regulator_disable(data->supply); if (ret != 0) { - dev_err(&tpa6130a2_client->dev, + dev_err(data->dev, "Failed to disable supply: %d\n", ret); - goto exit; + return ret; } - data->power_state = 0; + /* device regs does not match the cache state anymore */ + regcache_mark_dirty(data->regmap); } -exit: - mutex_unlock(&data->mutex); return ret; } -static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int tpa6130a2_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctrl, int event) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct tpa6130a2_data *data; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - mutex_lock(&data->mutex); - - ucontrol->value.integer.value[0] = - (tpa6130a2_read(reg) >> shift) & mask; - - if (invert) - ucontrol->value.integer.value[0] = - max - ucontrol->value.integer.value[0]; - - mutex_unlock(&data->mutex); - return 0; -} + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(c); + int ret; -static int tpa6130a2_put_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct tpa6130a2_data *data; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - unsigned int val = (ucontrol->value.integer.value[0] & mask); - unsigned int val_reg; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - if (invert) - val = max - val; - - mutex_lock(&data->mutex); - - val_reg = tpa6130a2_read(reg); - if (((val_reg >> shift) & mask) == val) { - mutex_unlock(&data->mutex); - return 0; + /* before widget power up */ + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Turn on the chip */ + tpa6130a2_power(data, true); + /* Sync the registers */ + ret = regcache_sync(data->regmap); + if (ret < 0) { + dev_err(c->dev, "Failed to initialize chip\n"); + tpa6130a2_power(data, false); + return ret; + } + /* after widget power down */ + } else { + tpa6130a2_power(data, false); } - val_reg &= ~(mask << shift); - val_reg |= val << shift; - tpa6130a2_i2c_write(reg, val_reg); - - mutex_unlock(&data->mutex); - - return 1; + return 0; } /* @@ -273,9 +127,8 @@ static const DECLARE_TLV_DB_RANGE(tpa6130_tlv, ); static const struct snd_kcontrol_new tpa6130a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", + SOC_SINGLE_TLV("Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6130_tlv), }; @@ -286,85 +139,79 @@ static const DECLARE_TLV_DB_RANGE(tpa6140_tlv, ); static const struct snd_kcontrol_new tpa6140a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + SOC_SINGLE_TLV("Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, - tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6140_tlv), }; -/* - * Enable or disable channel (left or right) - * The bit number for mute and amplifier are the same per channel: - * bit 6: Right channel - * bit 7: Left channel - * in both registers. - */ -static void tpa6130a2_channel_enable(u8 channel, int enable) +static int tpa6130a2_component_probe(struct snd_soc_component *component) { - u8 val; + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(component); - if (enable) { - /* Enable channel */ - /* Enable amplifier */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val |= channel; - val &= ~TPA6130A2_SWS; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - - /* Unmute channel */ - val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE); - val &= ~channel; - tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val); - } else { - /* Disable channel */ - /* Mute channel */ - val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE); - val |= channel; - tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val); - - /* Disable amplifier */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val &= ~channel; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - } + if (data->id == TPA6140A2) + return snd_soc_add_component_controls(component, + tpa6140a2_controls, ARRAY_SIZE(tpa6140a2_controls)); + else + return snd_soc_add_component_controls(component, + tpa6130a2_controls, ARRAY_SIZE(tpa6130a2_controls)); } -int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable) -{ - int ret = 0; - if (enable) { - ret = tpa6130a2_power(1); - if (ret < 0) - return ret; - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, - 1); - } else { - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, - 0); - ret = tpa6130a2_power(0); - } +static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LEFTIN"), + SND_SOC_DAPM_INPUT("RIGHTIN"), + SND_SOC_DAPM_OUTPUT("HPLEFT"), + SND_SOC_DAPM_OUTPUT("HPRIGHT"), + + SND_SOC_DAPM_PGA("Left Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_L_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_R_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_L_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_R_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Power", TPA6130A2_REG_CONTROL, + TPA6130A2_SWS_SHIFT, 1, tpa6130a2_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; - return ret; -} -EXPORT_SYMBOL_GPL(tpa6130a2_stereo_enable); +static const struct snd_soc_dapm_route tpa6130a2_dapm_routes[] = { + { "Left PGA", NULL, "LEFTIN" }, + { "Right PGA", NULL, "RIGHTIN" }, -int tpa6130a2_add_controls(struct snd_soc_codec *codec) -{ - struct tpa6130a2_data *data; + { "Left Mute", NULL, "Left PGA" }, + { "Right Mute", NULL, "Right PGA" }, - if (tpa6130a2_client == NULL) - return -ENODEV; + { "HPLEFT", NULL, "Left Mute" }, + { "HPRIGHT", NULL, "Right Mute" }, - data = i2c_get_clientdata(tpa6130a2_client); + { "Left PGA", NULL, "Power" }, + { "Right PGA", NULL, "Power" }, +}; - if (data->id == TPA6140A2) - return snd_soc_add_codec_controls(codec, tpa6140a2_controls, - ARRAY_SIZE(tpa6140a2_controls)); - else - return snd_soc_add_codec_controls(codec, tpa6130a2_controls, - ARRAY_SIZE(tpa6130a2_controls)); -} -EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); +struct snd_soc_component_driver tpa6130a2_component_driver = { + .name = "tpa6130a2", + .probe = tpa6130a2_component_probe, + .dapm_widgets = tpa6130a2_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tpa6130a2_dapm_widgets), + .dapm_routes = tpa6130a2_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tpa6130a2_dapm_routes), +}; + +static const struct reg_default tpa6130a2_reg_defaults[] = { + { TPA6130A2_REG_CONTROL, TPA6130A2_SWS }, + { TPA6130A2_REG_VOL_MUTE, TPA6130A2_MUTE_R | TPA6130A2_MUTE_L }, +}; + +static const struct regmap_config tpa6130a2_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPA6130A2_REG_VERSION, + .reg_defaults = tpa6130a2_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tpa6130a2_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; static int tpa6130a2_probe(struct i2c_client *client, const struct i2c_device_id *id) @@ -374,6 +221,7 @@ static int tpa6130a2_probe(struct i2c_client *client, struct tpa6130a2_platform_data *pdata = client->dev.platform_data; struct device_node *np = client->dev.of_node; const char *regulator; + unsigned int version; int ret; dev = &client->dev; @@ -382,6 +230,12 @@ static int tpa6130a2_probe(struct i2c_client *client, if (!data) return -ENOMEM; + data->dev = dev; + + data->regmap = devm_regmap_init_i2c(client, &tpa6130a2_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + if (pdata) { data->power_gpio = pdata->power_gpio; } else if (np) { @@ -392,26 +246,17 @@ static int tpa6130a2_probe(struct i2c_client *client, return -ENODEV; } - tpa6130a2_client = client; - - i2c_set_clientdata(tpa6130a2_client, data); + i2c_set_clientdata(client, data); data->id = id->driver_data; - mutex_init(&data->mutex); - - /* Set default register values */ - data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS; - data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_MUTE_R | - TPA6130A2_MUTE_L; - if (data->power_gpio >= 0) { ret = devm_gpio_request(dev, data->power_gpio, "tpa6130a2 enable"); if (ret < 0) { dev_err(dev, "Failed to request power GPIO (%d)\n", data->power_gpio); - goto err_gpio; + return ret; } gpio_direction_output(data->power_gpio, 0); } @@ -432,39 +277,27 @@ static int tpa6130a2_probe(struct i2c_client *client, if (IS_ERR(data->supply)) { ret = PTR_ERR(data->supply); dev_err(dev, "Failed to request supply: %d\n", ret); - goto err_gpio; + return ret; } - ret = tpa6130a2_power(1); + ret = tpa6130a2_power(data, true); if (ret != 0) - goto err_gpio; + return ret; /* Read version */ - ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) & - TPA6130A2_VERSION_MASK; - if ((ret != 1) && (ret != 2)) - dev_warn(dev, "UNTESTED version detected (%d)\n", ret); + regmap_read(data->regmap, TPA6130A2_REG_VERSION, &version); + version &= TPA6130A2_VERSION_MASK; + if ((version != 1) && (version != 2)) + dev_warn(dev, "UNTESTED version detected (%d)\n", version); /* Disable the chip */ - ret = tpa6130a2_power(0); + ret = tpa6130a2_power(data, false); if (ret != 0) - goto err_gpio; - - return 0; - -err_gpio: - tpa6130a2_client = NULL; + return ret; - return ret; -} - -static int tpa6130a2_remove(struct i2c_client *client) -{ - tpa6130a2_power(0); - tpa6130a2_client = NULL; - - return 0; + return devm_snd_soc_register_component(&client->dev, + &tpa6130a2_component_driver, NULL, 0); } static const struct i2c_device_id tpa6130a2_id[] = { @@ -489,7 +322,6 @@ static struct i2c_driver tpa6130a2_i2c_driver = { .of_match_table = of_match_ptr(tpa6130a2_of_match), }, .probe = tpa6130a2_probe, - .remove = tpa6130a2_remove, .id_table = tpa6130a2_id, }; diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h index 417444020..f19cad5d4 100644 --- a/sound/soc/codecs/tpa6130a2.h +++ b/sound/soc/codecs/tpa6130a2.h @@ -30,19 +30,20 @@ #define TPA6130A2_REG_OUT_IMPEDANCE 0x03 #define TPA6130A2_REG_VERSION 0x04 -#define TPA6130A2_CACHEREGNUM (TPA6130A2_REG_VERSION + 1) - /* Register bits */ /* TPA6130A2_REG_CONTROL (0x01) */ -#define TPA6130A2_SWS (0x01 << 0) +#define TPA6130A2_SWS_SHIFT 0 +#define TPA6130A2_SWS (0x01 << TPA6130A2_SWS_SHIFT) #define TPA6130A2_TERMAL (0x01 << 1) #define TPA6130A2_MODE(x) (x << 4) #define TPA6130A2_MODE_STEREO (0x00) #define TPA6130A2_MODE_DUAL_MONO (0x01) #define TPA6130A2_MODE_BRIDGE (0x02) #define TPA6130A2_MODE_MASK (0x03) -#define TPA6130A2_HP_EN_R (0x01 << 6) -#define TPA6130A2_HP_EN_L (0x01 << 7) +#define TPA6130A2_HP_EN_R_SHIFT 6 +#define TPA6130A2_HP_EN_R (0x01 << TPA6130A2_HP_EN_R_SHIFT) +#define TPA6130A2_HP_EN_L_SHIFT 7 +#define TPA6130A2_HP_EN_L (0x01 << TPA6130A2_HP_EN_L_SHIFT) /* TPA6130A2_REG_VOL_MUTE (0x02) */ #define TPA6130A2_VOLUME(x) ((x & 0x3f) << 0) @@ -56,7 +57,4 @@ /* TPA6130A2_REG_VERSION (0x04) */ #define TPA6130A2_VERSION_MASK (0x0f) -extern int tpa6130a2_add_controls(struct snd_soc_codec *codec); -extern int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable); - #endif /* __TPA6130A2_H__ */ diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 95b10422a..706134fb1 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -571,7 +571,7 @@ static int wm2000_anc_transition(struct wm2000_priv *wm2000, if (anc_transitions[i].dest == ANC_OFF) clk_disable_unprepare(wm2000->mclk); - return ret; + return 0; } static int wm2000_anc_set_mode(struct wm2000_priv *wm2000) diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index e7fe6b7b9..846deed6a 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -1713,6 +1713,7 @@ static const struct snd_soc_dapm_route wm5102_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, }; diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index d54f1b46c..156547026 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -1104,6 +1104,11 @@ SND_SOC_DAPM_INPUT("IN4R"), SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, 0, NULL, 0, wm5110_in_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | @@ -1998,10 +2003,16 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, { "DRC2 Signal Activity", NULL, "DRC2L" }, { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, }; static int wm5110_set_fll(struct snd_soc_codec *codec, int fll_id, int source, @@ -2223,6 +2234,7 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) { struct wm5110_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -2230,6 +2242,12 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -2252,6 +2270,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", wm5110_adsp2_irq, diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 4bcf5f8ec..d18261a44 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -358,6 +358,9 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, case 24: iface |= 0x0008; break; + case 32: + iface |= 0x000c; + break; } wm8731_set_deemph(codec); @@ -541,7 +544,7 @@ static int wm8731_startup(struct snd_pcm_substream *substream, #define WM8731_RATES SNDRV_PCM_RATE_8000_96000 #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ - SNDRV_PCM_FMTBIT_S24_LE) + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8731_dai_ops = { .startup = wm8731_startup, diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 6f1024f48..cdcc91282 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -32,7 +32,6 @@ */ #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> @@ -486,7 +485,7 @@ SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0), SND_SOC_DAPM_OUTPUT("MONO1"), SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls), SND_SOC_DAPM_OUTPUT("MONO2"), -SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Out3 Left + Right", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls), SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("OUT3"), diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 6ac76fe11..7347abff4 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1,10 +1,13 @@ /* - * wm8985.c -- WM8985 ALSA SoC Audio driver + * wm8985.c -- WM8985 / WM8758 ALSA SoC Audio driver * * Copyright 2010 Wolfson Microelectronics plc - * * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> * + * WM8758 support: + * Copyright: 2016 Barix AG + * Author: Petr Kulhavy <petr@barix.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. @@ -40,6 +43,11 @@ static const char *wm8985_supply_names[WM8985_NUM_SUPPLIES] = { "AVDD2" }; +enum wm8985_type { + WM8985, + WM8758, +}; + static const struct reg_default wm8985_reg_defaults[] = { { 1, 0x0000 }, /* R1 - Power management 1 */ { 2, 0x0000 }, /* R2 - Power management 2 */ @@ -181,6 +189,7 @@ static const int volume_update_regs[] = { struct wm8985_priv { struct regmap *regmap; struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES]; + enum wm8985_type dev_type; unsigned int sysclk; unsigned int bclk; }; @@ -289,7 +298,7 @@ static const char *depth_3d_text[] = { }; static SOC_ENUM_SINGLE_DECL(depth_3d, WM8985_3D_CONTROL, 0, depth_3d_text); -static const struct snd_kcontrol_new wm8985_snd_controls[] = { +static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { SOC_SINGLE("Digital Loopback Switch", WM8985_COMPANDING_CONTROL, 0, 1, 0), @@ -355,10 +364,6 @@ static const struct snd_kcontrol_new wm8985_snd_controls[] = { SOC_ENUM("High Pass Filter Mode", filter_mode), SOC_SINGLE("High Pass Filter Cutoff", WM8985_ADC_CONTROL, 4, 7, 0), - SOC_DOUBLE_R_TLV("Aux Bypass Volume", - WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0, - aux_tlv), - SOC_DOUBLE_R_TLV("Input PGA Bypass Volume", WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 2, 7, 0, bypass_tlv), @@ -379,20 +384,30 @@ static const struct snd_kcontrol_new wm8985_snd_controls[] = { SOC_SINGLE_TLV("EQ5 Volume", WM8985_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv), SOC_ENUM("3D Depth", depth_3d), +}; + +static const struct snd_kcontrol_new wm8985_specific_snd_controls[] = { + SOC_DOUBLE_R_TLV("Aux Bypass Volume", + WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0, + aux_tlv), SOC_ENUM("Speaker Mode", speaker_mode) }; static const struct snd_kcontrol_new left_out_mixer[] = { SOC_DAPM_SINGLE("Line Switch", WM8985_LEFT_MIXER_CTRL, 1, 1, 0), - SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0), SOC_DAPM_SINGLE("PCM Switch", WM8985_LEFT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0), }; static const struct snd_kcontrol_new right_out_mixer[] = { SOC_DAPM_SINGLE("Line Switch", WM8985_RIGHT_MIXER_CTRL, 1, 1, 0), - SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0), SOC_DAPM_SINGLE("PCM Switch", WM8985_RIGHT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0), }; static const struct snd_kcontrol_new left_input_mixer[] = { @@ -410,6 +425,8 @@ static const struct snd_kcontrol_new right_input_mixer[] = { static const struct snd_kcontrol_new left_boost_mixer[] = { SOC_DAPM_SINGLE_TLV("L2 Volume", WM8985_LEFT_ADC_BOOST_CTRL, 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8985_LEFT_ADC_BOOST_CTRL, 0, 7, 0, boost_tlv) }; @@ -417,11 +434,13 @@ static const struct snd_kcontrol_new left_boost_mixer[] = { static const struct snd_kcontrol_new right_boost_mixer[] = { SOC_DAPM_SINGLE_TLV("R2 Volume", WM8985_RIGHT_ADC_BOOST_CTRL, 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8985_RIGHT_ADC_BOOST_CTRL, 0, 7, 0, boost_tlv) }; -static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { +static const struct snd_soc_dapm_widget wm8985_common_dapm_widgets[] = { SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8985_POWER_MANAGEMENT_3, 0, 0), SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8985_POWER_MANAGEMENT_3, @@ -431,21 +450,11 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8985_POWER_MANAGEMENT_2, 1, 0), - SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, - 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), - SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, - 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), - SND_SOC_DAPM_MIXER("Left Input Mixer", WM8985_POWER_MANAGEMENT_2, 2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)), SND_SOC_DAPM_MIXER("Right Input Mixer", WM8985_POWER_MANAGEMENT_2, 3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)), - SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, - 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), - SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, - 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), - SND_SOC_DAPM_PGA("Left Capture PGA", WM8985_LEFT_INP_PGA_GAIN_CTRL, 6, 1, NULL, 0), SND_SOC_DAPM_PGA("Right Capture PGA", WM8985_RIGHT_INP_PGA_GAIN_CTRL, @@ -468,8 +477,6 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LIP"), SND_SOC_DAPM_INPUT("RIN"), SND_SOC_DAPM_INPUT("RIP"), - SND_SOC_DAPM_INPUT("AUXL"), - SND_SOC_DAPM_INPUT("AUXR"), SND_SOC_DAPM_INPUT("L2"), SND_SOC_DAPM_INPUT("R2"), SND_SOC_DAPM_OUTPUT("HPL"), @@ -478,13 +485,42 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("SPKR") }; -static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { +static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), + + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), +}; + +static const struct snd_soc_dapm_widget wm8758_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, + ARRAY_SIZE(left_out_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, + ARRAY_SIZE(right_out_mixer) - 1), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, + ARRAY_SIZE(left_boost_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, + ARRAY_SIZE(right_boost_mixer) - 1), +}; + +static const struct snd_soc_dapm_route wm8985_common_dapm_routes[] = { { "Right Output Mixer", "PCM Switch", "Right DAC" }, - { "Right Output Mixer", "Aux Switch", "AUXR" }, { "Right Output Mixer", "Line Switch", "Right Boost Mixer" }, { "Left Output Mixer", "PCM Switch", "Left DAC" }, - { "Left Output Mixer", "Aux Switch", "AUXL" }, { "Left Output Mixer", "Line Switch", "Left Boost Mixer" }, { "Right Headphone Out", NULL, "Right Output Mixer" }, @@ -501,13 +537,11 @@ static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { { "Right ADC", NULL, "Right Boost Mixer" }, - { "Right Boost Mixer", "AUXR Volume", "AUXR" }, { "Right Boost Mixer", NULL, "Right Capture PGA" }, { "Right Boost Mixer", "R2 Volume", "R2" }, { "Left ADC", NULL, "Left Boost Mixer" }, - { "Left Boost Mixer", "AUXL Volume", "AUXL" }, { "Left Boost Mixer", NULL, "Left Capture PGA" }, { "Left Boost Mixer", "L2 Volume", "L2" }, @@ -522,6 +556,38 @@ static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { { "Left Input Mixer", "MicN Switch", "LIN" }, { "Left Input Mixer", "MicP Switch", "LIP" }, }; +static const struct snd_soc_dapm_route wm8985_aux_dapm_routes[] = { + { "Right Output Mixer", "Aux Switch", "AUXR" }, + { "Left Output Mixer", "Aux Switch", "AUXL" }, + + { "Right Boost Mixer", "AUXR Volume", "AUXR" }, + { "Left Boost Mixer", "AUXL Volume", "AUXL" }, +}; + +static int wm8985_add_widgets(struct snd_soc_codec *codec) +{ + struct wm8985_priv *wm8985 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + switch (wm8985->dev_type) { + case WM8758: + snd_soc_dapm_new_controls(dapm, wm8758_dapm_widgets, + ARRAY_SIZE(wm8758_dapm_widgets)); + break; + + case WM8985: + snd_soc_add_codec_controls(codec, wm8985_specific_snd_controls, + ARRAY_SIZE(wm8985_specific_snd_controls)); + + snd_soc_dapm_new_controls(dapm, wm8985_dapm_widgets, + ARRAY_SIZE(wm8985_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, wm8985_aux_dapm_routes, + ARRAY_SIZE(wm8985_aux_dapm_routes)); + break; + } + + return 0; +} static int eqmode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -999,6 +1065,8 @@ static int wm8985_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8985_BIAS_CTRL, WM8985_BIASCUT, WM8985_BIASCUT); + wm8985_add_widgets(codec); + return 0; err_reg_enable: @@ -1042,12 +1110,12 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8985 = { .set_bias_level = wm8985_set_bias_level, .suspend_bias_off = true, - .controls = wm8985_snd_controls, - .num_controls = ARRAY_SIZE(wm8985_snd_controls), - .dapm_widgets = wm8985_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(wm8985_dapm_widgets), - .dapm_routes = wm8985_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(wm8985_dapm_routes), + .controls = wm8985_common_snd_controls, + .num_controls = ARRAY_SIZE(wm8985_common_snd_controls), + .dapm_widgets = wm8985_common_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8985_common_dapm_widgets), + .dapm_routes = wm8985_common_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8985_common_dapm_routes), }; static const struct regmap_config wm8985_regmap = { @@ -1074,6 +1142,8 @@ static int wm8985_spi_probe(struct spi_device *spi) spi_set_drvdata(spi, wm8985); + wm8985->dev_type = WM8985; + wm8985->regmap = devm_regmap_init_spi(spi, &wm8985_regmap); if (IS_ERR(wm8985->regmap)) { ret = PTR_ERR(wm8985->regmap); @@ -1115,6 +1185,8 @@ static int wm8985_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8985); + wm8985->dev_type = id->driver_data; + wm8985->regmap = devm_regmap_init_i2c(i2c, &wm8985_regmap); if (IS_ERR(wm8985->regmap)) { ret = PTR_ERR(wm8985->regmap); @@ -1135,7 +1207,8 @@ static int wm8985_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id wm8985_i2c_id[] = { - { "wm8985", 0 }, + { "wm8985", WM8985 }, + { "wm8758", WM8758 }, { } }; MODULE_DEVICE_TABLE(i2c, wm8985_i2c_id); @@ -1183,6 +1256,6 @@ static void __exit wm8985_exit(void) } module_exit(wm8985_exit); -MODULE_DESCRIPTION("ASoC WM8985 driver"); +MODULE_DESCRIPTION("ASoC WM8985 / WM8758 driver"); MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8985.h b/sound/soc/codecs/wm8985.h index 2e71ff507..41b1048e3 100644 --- a/sound/soc/codecs/wm8985.h +++ b/sound/soc/codecs/wm8985.h @@ -290,6 +290,9 @@ #define WM8985_GPIO1GPD_MASK 0x0040 /* GPIO1GPD */ #define WM8985_GPIO1GPD_SHIFT 6 /* GPIO1GPD */ #define WM8985_GPIO1GPD_WIDTH 1 /* GPIO1GPD */ +#define WM8758_OPCLKDIV_MASK 0x0030 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_SHIFT 4 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_WIDTH 2 /* OPCLKDIV - [1:0] */ #define WM8985_GPIO1POL 0x0008 /* GPIO1POL */ #define WM8985_GPIO1POL_MASK 0x0008 /* GPIO1POL */ #define WM8985_GPIO1POL_SHIFT 3 /* GPIO1POL */ @@ -301,6 +304,12 @@ /* * R9 (0x09) - Jack Detect Control 1 */ +#define WM8758_JD_VMID1_MASK 0x0100 /* JD_VMID1 */ +#define WM8758_JD_VMID1_SHIFT 8 /* JD_VMID1 */ +#define WM8758_JD_VMID1_WIDTH 1 /* JD_VMID1 */ +#define WM8758_JD_VMID0_MASK 0x0080 /* JD_VMID0 */ +#define WM8758_JD_VMID0_SHIFT 7 /* JD_VMID0 */ +#define WM8758_JD_VMID0_WIDTH 1 /* JD_VMID0 */ #define WM8985_JD_EN 0x0040 /* JD_EN */ #define WM8985_JD_EN_MASK 0x0040 /* JD_EN */ #define WM8985_JD_EN_SHIFT 6 /* JD_EN */ @@ -649,6 +658,12 @@ #define WM8985_OUT4_2LNR_MASK 0x0020 /* OUT4_2LNR */ #define WM8985_OUT4_2LNR_SHIFT 5 /* OUT4_2LNR */ #define WM8985_OUT4_2LNR_WIDTH 1 /* OUT4_2LNR */ +#define WM8758_VMIDTOG_MASK 0x0010 /* VMIDTOG */ +#define WM8758_VMIDTOG_SHIFT 4 /* VMIDTOG */ +#define WM8758_VMIDTOG_WIDTH 1 /* VMIDTOG */ +#define WM8758_OUT2DEL_MASK 0x0008 /* OUT2DEL */ +#define WM8758_OUT2DEL_SHIFT 3 /* OUT2DEL */ +#define WM8758_OUT2DEL_WIDTH 1 /* OUT2DEL */ #define WM8985_POBCTRL 0x0004 /* POBCTRL */ #define WM8985_POBCTRL_MASK 0x0004 /* POBCTRL */ #define WM8985_POBCTRL_SHIFT 2 /* POBCTRL */ @@ -684,6 +699,9 @@ #define WM8985_BEEPVOL_MASK 0x000E /* BEEPVOL - [3:1] */ #define WM8985_BEEPVOL_SHIFT 1 /* BEEPVOL - [3:1] */ #define WM8985_BEEPVOL_WIDTH 3 /* BEEPVOL - [3:1] */ +#define WM8758_DELEN2_MASK 0x0004 /* DELEN2 */ +#define WM8758_DELEN2_SHIFT 2 /* DELEN2 */ +#define WM8758_DELEN2_WIDTH 1 /* DELEN2 */ #define WM8985_BEEPEN 0x0001 /* BEEPEN */ #define WM8985_BEEPEN_MASK 0x0001 /* BEEPEN */ #define WM8985_BEEPEN_SHIFT 0 /* BEEPEN */ @@ -790,6 +808,14 @@ /* * R49 (0x31) - Output ctrl */ +#define WM8758_HP_COM 0x0100 /* HP_COM */ +#define WM8758_HP_COM_MASK 0x0100 /* HP_COM */ +#define WM8758_HP_COM_SHIFT 8 /* HP_COM */ +#define WM8758_HP_COM_WIDTH 1 /* HP_COM */ +#define WM8758_LINE_COM 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_MASK 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_SHIFT 7 /* LINE_COM */ +#define WM8758_LINE_COM_WIDTH 1 /* LINE_COM */ #define WM8985_DACL2RMIX 0x0040 /* DACL2RMIX */ #define WM8985_DACL2RMIX_MASK 0x0040 /* DACL2RMIX */ #define WM8985_DACL2RMIX_SHIFT 6 /* DACL2RMIX */ @@ -806,6 +832,14 @@ #define WM8985_OUT3BOOST_MASK 0x0008 /* OUT3BOOST */ #define WM8985_OUT3BOOST_SHIFT 3 /* OUT3BOOST */ #define WM8985_OUT3BOOST_WIDTH 1 /* OUT3BOOST */ +#define WM8758_OUT4ENDEL 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_MASK 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_SHIFT 4 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_WIDTH 1 /* OUT4ENDEL */ +#define WM8758_OUT3ENDEL 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_MASK 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_SHIFT 3 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_WIDTH 1 /* OUT3ENDEL */ #define WM8985_TSOPCTRL 0x0004 /* TSOPCTRL */ #define WM8985_TSOPCTRL_MASK 0x0004 /* TSOPCTRL */ #define WM8985_TSOPCTRL_SHIFT 2 /* TSOPCTRL */ @@ -1021,6 +1055,10 @@ #define WM8985_HALFIPBIAS_MASK 0x0080 /* HALFIPBIAS */ #define WM8985_HALFIPBIAS_SHIFT 7 /* HALFIPBIAS */ #define WM8985_HALFIPBIAS_WIDTH 1 /* HALFIPBIAS */ +#define WM8758_HALFIPBIAS 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_MASK 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_SHIFT 6 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_WIDTH 1 /* HALFI_IPGA */ #define WM8985_VBBIASTST_MASK 0x0060 /* VBBIASTST - [6:5] */ #define WM8985_VBBIASTST_SHIFT 5 /* VBBIASTST - [6:5] */ #define WM8985_VBBIASTST_WIDTH 2 /* VBBIASTST - [6:5] */ diff --git a/sound/soc/codecs/wm8998.c b/sound/soc/codecs/wm8998.c index 449f66636..3a5c896a2 100644 --- a/sound/soc/codecs/wm8998.c +++ b/sound/soc/codecs/wm8998.c @@ -1166,6 +1166,7 @@ static const struct snd_soc_dapm_route wm8998_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, }; diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 78832c218..cbf5b153f 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -394,6 +394,7 @@ static const struct { int compr_direction; int num_caps; const struct wm_adsp_fw_caps *caps; + bool voice_trigger; } wm_adsp_fw[WM_ADSP_NUM_FW] = { [WM_ADSP_FW_MBC_VSS] = { .file = "mbc-vss" }, [WM_ADSP_FW_HIFI] = { .file = "hifi" }, @@ -406,6 +407,7 @@ static const struct { .compr_direction = SND_COMPRESS_CAPTURE, .num_caps = ARRAY_SIZE(ctrl_caps), .caps = ctrl_caps, + .voice_trigger = true, }, [WM_ADSP_FW_ASR] = { .file = "asr" }, [WM_ADSP_FW_TRACE] = { @@ -2366,13 +2368,15 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, dsp->running = false; regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | - ADSP2_START, 0); + ADSP2_CORE_ENA | ADSP2_START, 0); /* Make sure DMAs are quiesced */ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); list_for_each_entry(ctl, &dsp->ctl_list, list) ctl->enabled = 0; @@ -2998,6 +3002,9 @@ int wm_adsp_compr_handle_irq(struct wm_adsp *dsp) goto out; } + if (wm_adsp_fw[dsp->fw].voice_trigger && buf->irq_count == 2) + ret = WM_ADSP_COMPR_VOICE_TRIGGER; + out_notify: if (compr && compr->stream) snd_compr_fragment_elapsed(compr->stream); @@ -3037,12 +3044,8 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, buf = compr->buf; - if (!compr->buf) { - ret = -ENXIO; - goto out; - } - - if (compr->buf->error) { + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); ret = -EIO; goto out; } @@ -3060,8 +3063,12 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, */ if (buf->avail < wm_adsp_compr_frag_words(compr)) { ret = wm_adsp_buffer_get_error(buf); - if (ret < 0) + if (ret < 0) { + if (compr->buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); goto out; + } ret = wm_adsp_buffer_reenable_irq(buf); if (ret < 0) { @@ -3156,11 +3163,10 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr, adsp_dbg(dsp, "Requested read of %zu bytes\n", count); - if (!compr->buf) - return -ENXIO; - - if (compr->buf->error) + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN); return -EIO; + } count /= WM_ADSP_DATA_WORD_SIZE; diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index feb61e2c4..be3b5bcb7 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -19,6 +19,10 @@ #include "wmfw.h" +/* Return values for wm_adsp_compr_handle_irq */ +#define WM_ADSP_COMPR_OK 0 +#define WM_ADSP_COMPR_VOICE_TRIGGER 1 + struct wm_adsp_region { int type; unsigned int base; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 237dc6700..05c2d33aa 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1599,7 +1599,14 @@ static struct davinci_mcasp_pdata *davinci_mcasp_set_pdata_from_of( pdata = pdev->dev.platform_data; return pdata; } else if (match) { - pdata = (struct davinci_mcasp_pdata*) match->data; + pdata = devm_kmemdup(&pdev->dev, match->data, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "Failed to allocate memory for pdata\n"); + ret = -ENOMEM; + return pdata; + } } else { /* control shouldn't reach here. something is wrong */ ret = -EINVAL; diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index d50e08517..c297efe43 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -7,4 +7,13 @@ config SND_DESIGNWARE_I2S Synopsys desigwnware I2S device. The device supports upto maximum of 8 channels each for play and record. +config SND_DESIGNWARE_PCM + tristate "PCM PIO extension for I2S driver" + depends on SND_DESIGNWARE_I2S + help + Say Y, M or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + + This functionality is specially suited for I2S devices that don't have + DMA support. diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 319371f69..38f1ca31c 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,3 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o - +ifdef CONFIG_SND_DESIGNWARE_PCM +obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o +endif diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 0db69b7e9..dc97f4349 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -24,90 +24,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> - -/* common register for all channel */ -#define IER 0x000 -#define IRER 0x004 -#define ITER 0x008 -#define CER 0x00C -#define CCR 0x010 -#define RXFFR 0x014 -#define TXFFR 0x018 - -/* I2STxRxRegisters for all channels */ -#define LRBR_LTHR(x) (0x40 * x + 0x020) -#define RRBR_RTHR(x) (0x40 * x + 0x024) -#define RER(x) (0x40 * x + 0x028) -#define TER(x) (0x40 * x + 0x02C) -#define RCR(x) (0x40 * x + 0x030) -#define TCR(x) (0x40 * x + 0x034) -#define ISR(x) (0x40 * x + 0x038) -#define IMR(x) (0x40 * x + 0x03C) -#define ROR(x) (0x40 * x + 0x040) -#define TOR(x) (0x40 * x + 0x044) -#define RFCR(x) (0x40 * x + 0x048) -#define TFCR(x) (0x40 * x + 0x04C) -#define RFF(x) (0x40 * x + 0x050) -#define TFF(x) (0x40 * x + 0x054) - -/* I2SCOMPRegisters */ -#define I2S_COMP_PARAM_2 0x01F0 -#define I2S_COMP_PARAM_1 0x01F4 -#define I2S_COMP_VERSION 0x01F8 -#define I2S_COMP_TYPE 0x01FC - -/* - * Component parameter register fields - define the I2S block's - * configuration. - */ -#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) -#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) -#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) -#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) -#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) -#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) -#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) -#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) -#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) -#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) -#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) - -#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) -#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) -#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) -#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) - -/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ -#define COMP_MAX_WORDSIZE (1 << 3) -#define COMP_MAX_DATA_WIDTH (1 << 2) - -#define MAX_CHANNEL_NUM 8 -#define MIN_CHANNEL_NUM 2 - -union dw_i2s_snd_dma_data { - struct i2s_dma_data pd; - struct snd_dmaengine_dai_dma_data dt; -}; - -struct dw_i2s_dev { - void __iomem *i2s_base; - struct clk *clk; - int active; - unsigned int capability; - unsigned int quirks; - unsigned int i2s_reg_comp1; - unsigned int i2s_reg_comp2; - struct device *dev; - u32 ccr; - u32 xfer_resolution; - u32 fifo_th; - - /* data related to DMA transfers b/w i2s and DMAC */ - union dw_i2s_snd_dma_data play_dma_data; - union dw_i2s_snd_dma_data capture_dma_data; - struct i2s_clk_config_data config; - int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); -}; +#include "local.h" static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) { @@ -145,51 +62,115 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) } } -static void i2s_start(struct dw_i2s_dev *dev, - struct snd_pcm_substream *substream) +static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + } + } +} + +static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) { - struct i2s_clk_config_data *config = &dev->config; u32 i, irq; - i2s_write_reg(dev->i2s_base, IER, 1); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - for (i = 0; i < (config->chan_nr / 2); i++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30); } - i2s_write_reg(dev->i2s_base, ITER, 1); } else { - for (i = 0; i < (config->chan_nr / 2); i++) { + for (i = 0; i < (chan_nr / 2); i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03); } - i2s_write_reg(dev->i2s_base, IRER, 1); + } +} + +static irqreturn_t i2s_irq_handler(int irq, void *dev_id) +{ + struct dw_i2s_dev *dev = dev_id; + bool irq_valid = false; + u32 isr[4]; + int i; + + for (i = 0; i < 4; i++) + isr[i] = i2s_read_reg(dev->i2s_base, ISR(i)); + + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK); + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE); + + for (i = 0; i < 4; i++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) { + dw_pcm_push_tx(dev); + irq_valid = true; + } + + /* Data available. Record mode not supported in PIO mode */ + if (isr[i] & ISR_RXDA) + irq_valid = true; + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_RXFO) { + dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i); + irq_valid = true; + } } + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void i2s_start(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &dev->config; + + i2s_write_reg(dev->i2s_base, IER, 1); + i2s_enable_irqs(dev, substream->stream, config->chan_nr); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 1); + else + i2s_write_reg(dev->i2s_base, IRER, 1); + i2s_write_reg(dev->i2s_base, CER, 1); } static void i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { - u32 i = 0, irq; i2s_clear_irqs(dev, substream->stream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) i2s_write_reg(dev->i2s_base, ITER, 0); - - for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); - } - } else { + else i2s_write_reg(dev->i2s_base, IRER, 0); - for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); - } - } + i2s_disable_irqs(dev, substream->stream, 8); if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); @@ -223,7 +204,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) { - u32 ch_reg, irq; + u32 ch_reg; struct i2s_clk_config_data *config = &dev->config; @@ -235,16 +216,12 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) dev->xfer_resolution); i2s_write_reg(dev->i2s_base, TFCR(ch_reg), dev->fifo_th - 1); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30); i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); } else { i2s_write_reg(dev->i2s_base, RCR(ch_reg), dev->xfer_resolution); i2s_write_reg(dev->i2s_base, RFCR(ch_reg), dev->fifo_th - 1); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03); i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); } @@ -278,7 +255,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, break; default: - dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt"); + dev_err(dev->dev, "designware-i2s: unsupported PCM fmt"); return -EINVAL; } @@ -626,7 +603,7 @@ static int dw_i2s_probe(struct platform_device *pdev) const struct i2s_platform_data *pdata = pdev->dev.platform_data; struct dw_i2s_dev *dev; struct resource *res; - int ret; + int ret, irq; struct snd_soc_dai_driver *dw_i2s_dai; const char *clk_id; @@ -651,6 +628,16 @@ static int dw_i2s_probe(struct platform_device *pdev) dev->dev = &pdev->dev; + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; if (pdata) { @@ -697,12 +684,24 @@ static int dw_i2s_probe(struct platform_device *pdev) if (!pdata) { ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { + if (ret == -EPROBE_DEFER) { + dev_err(&pdev->dev, + "failed to register PCM, deferring probe\n"); + return ret; + } else if (ret) { dev_err(&pdev->dev, - "Could not register PCM: %d\n", ret); - goto err_clk_disable; + "Could not register DMA PCM: %d\n" + "falling back to PIO mode\n", ret); + ret = dw_pcm_register(pdev); + if (ret) { + dev_err(&pdev->dev, + "Could not register PIO PCM: %d\n", + ret); + goto err_clk_disable; + } } } + pm_runtime_enable(&pdev->dev); return 0; diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/designware_pcm.c new file mode 100644 index 000000000..4a83a22fa --- /dev/null +++ b/sound/soc/dwc/designware_pcm.c @@ -0,0 +1,225 @@ +/* + * ALSA SoC Synopsys PIO PCM for I2S driver + * + * sound/soc/dwc/designware_pcm.c + * + * Copyright (C) 2016 Synopsys + * Jose Abreu <joabreu@synopsys.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/io.h> +#include <linux/rcupdate.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "local.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +#define dw_pcm_tx_fn(sample_bits) \ +static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ + bool *period_elapsed) \ +{ \ + const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = tx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ + iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return tx_ptr; \ +} + +dw_pcm_tx_fn(16); +dw_pcm_tx_fn(32); + +#undef dw_pcm_tx_fn + +static const struct snd_pcm_hardware dw_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +void dw_pcm_push_tx(struct dw_i2s_dev *dev) +{ + struct snd_pcm_substream *tx_substream; + bool tx_active, period_elapsed; + + rcu_read_lock(); + tx_substream = rcu_dereference(dev->tx_substream); + tx_active = tx_substream && snd_pcm_running(tx_substream); + if (tx_active) { + unsigned int tx_ptr = READ_ONCE(dev->tx_ptr); + unsigned int new_tx_ptr = dev->tx_fn(dev, tx_substream->runtime, + tx_ptr, &period_elapsed); + cmpxchg(&dev->tx_ptr, tx_ptr, new_tx_ptr); + + if (period_elapsed) + snd_pcm_period_elapsed(tx_substream); + } + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(dw_pcm_push_tx); + +static int dw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int dw_pcm_close(struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int dw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = dw_pcm_tx_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + dev->tx_fn = dw_pcm_tx_32; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) { + dev_err(dev->dev, "only playback is available\n"); + return -EINVAL; + } + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + else + return 0; +} + +static int dw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + rcu_assign_pointer(dev->tx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos = READ_ONCE(dev->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size = dw_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), size, size); +} + +static void dw_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_pcm_ops dw_pcm_ops = { + .open = dw_pcm_open, + .close = dw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dw_pcm_hw_params, + .hw_free = dw_pcm_hw_free, + .trigger = dw_pcm_trigger, + .pointer = dw_pcm_pointer, +}; + +static const struct snd_soc_platform_driver dw_pcm_platform = { + .pcm_new = dw_pcm_new, + .pcm_free = dw_pcm_free, + .ops = &dw_pcm_ops, +}; + +int dw_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); +} +EXPORT_SYMBOL_GPL(dw_pcm_register); diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h new file mode 100644 index 000000000..68afd7577 --- /dev/null +++ b/sound/soc/dwc/local.h @@ -0,0 +1,128 @@ +/* + * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com) + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __DESIGNWARE_LOCAL_H +#define __DESIGNWARE_LOCAL_H + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/types.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/designware_i2s.h> + +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2STxRxRegisters for all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) +#define TER(x) (0x40 * x + 0x02C) +#define RCR(x) (0x40 * x + 0x030) +#define TCR(x) (0x40 * x + 0x034) +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) +#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) +#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) +#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) +#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) +#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) +#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) +#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) +#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) +#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) +#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) + +#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) +#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) +#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) +#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE (1 << 3) +#define COMP_MAX_DATA_WIDTH (1 << 2) + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 + +union dw_i2s_snd_dma_data { + struct i2s_dma_data pd; + struct snd_dmaengine_dai_dma_data dt; +}; + +struct dw_i2s_dev { + void __iomem *i2s_base; + struct clk *clk; + int active; + unsigned int capability; + unsigned int quirks; + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct device *dev; + u32 ccr; + u32 xfer_resolution; + u32 fifo_th; + + /* data related to DMA transfers b/w i2s and DMAC */ + union dw_i2s_snd_dma_data play_dma_data; + union dw_i2s_snd_dma_data capture_dma_data; + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); + + /* data related to PIO transfers (TX) */ + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + unsigned int (*tx_fn)(struct dw_i2s_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; +}; + +#if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM) +void dw_pcm_push_tx(struct dw_i2s_dev *dev); +int dw_pcm_register(struct platform_device *pdev); +#else +void dw_pcm_push_tx(struct dw_i2s_dev *dev) { } +int dw_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 35aabf9dc..19bdcac71 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -4,6 +4,7 @@ comment "Common SoC Audio options for Freescale CPUs:" config SND_SOC_FSL_ASRC tristate "Asynchronous Sample Rate Converter (ASRC) module support" + depends on HAS_DMA select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 151849f79..beec7934a 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -172,7 +172,7 @@ static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name) if (*pos >= size * 2) { *pos = 0; } else if (unlikely((*pos % size) + 3 > size)) { - dev_err(&pdev->dev, "User bit receivce buffer overflow\n"); + dev_err(&pdev->dev, "User bit receive buffer overflow\n"); return; } diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index 610f61251..c01c5dd68 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -1,4 +1,8 @@ +config SND_SIMPLE_CARD_UTILS + tristate + config SND_SIMPLE_CARD tristate "ASoC Simple sound card support" + select SND_SIMPLE_CARD_UTILS help This option enables generic simple sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 9c3b24679..2d53c8d70 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,3 +1,5 @@ +snd-soc-simple-card-utils-objs := simple-card-utils.o snd-soc-simple-card-objs := simple-card.o -obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o +obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o +obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c new file mode 100644 index 000000000..9599de69a --- /dev/null +++ b/sound/soc/generic/simple-card-utils.c @@ -0,0 +1,103 @@ +/* + * simple-card-core.c + * + * Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/of.h> +#include <sound/simple_card_utils.h> + +int asoc_simple_card_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + int prefix_len = prefix ? strlen(prefix) : 0; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, prefix, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (prefix_len && !bitclkmaster && !framemaster) { + /* + * No dai-link level and master setting was not found from + * sound node level, revert back to legacy DT parsing and + * take the settings from codec node. + */ + dev_dbg(dev, "Revert to legacy daifmt parsing\n"); + + daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | + (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); + } else { + if (codec == bitclkmaster) + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + *retfmt = daifmt; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); + +int asoc_simple_card_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) +{ + va_list ap; + char *name = NULL; + int ret = -ENOMEM; + + va_start(ap, fmt); + name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap); + va_end(ap); + + if (name) { + ret = 0; + + dai_link->name = name; + dai_link->stream_name = name; + } + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); + +int asoc_simple_card_parse_card_name(struct snd_soc_card *card, + char *prefix) +{ + char prop[128]; + int ret; + + snprintf(prop, sizeof(prop), "%sname", prefix); + + /* Parse the card name from DT */ + ret = snd_soc_of_parse_card_name(card, prop); + if (ret < 0) + return ret; + + if (!card->name && card->dai_link) + card->name = card->dai_link->name; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); + +/* Module information */ +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); +MODULE_DESCRIPTION("ALSA SoC Simple Card Utils"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 466492b7d..43295f024 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -21,6 +21,12 @@ #include <sound/soc-dai.h> #include <sound/soc.h> +struct asoc_simple_jack { + struct snd_soc_jack jack; + struct snd_soc_jack_pin pin; + struct snd_soc_jack_gpio gpio; +}; + struct simple_card_data { struct snd_soc_card snd_card; struct simple_dai_props { @@ -29,10 +35,8 @@ struct simple_card_data { unsigned int mclk_fs; } *dai_props; unsigned int mclk_fs; - int gpio_hp_det; - int gpio_hp_det_invert; - int gpio_mic_det; - int gpio_mic_det_invert; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ }; @@ -40,6 +44,69 @@ struct simple_card_data { #define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i) #define simple_priv_to_props(priv, i) ((priv)->dai_props + i) +#define PREFIX "simple-audio-card," + +#define asoc_simple_card_init_hp(card, sjack, prefix)\ + asoc_simple_card_init_jack(card, sjack, 1, prefix) +#define asoc_simple_card_init_mic(card, sjack, prefix)\ + asoc_simple_card_init_jack(card, sjack, 0, prefix) +static int asoc_simple_card_init_jack(struct snd_soc_card *card, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) +{ + struct device *dev = card->dev; + enum of_gpio_flags flags; + char prop[128]; + char *pin_name; + char *gpio_name; + int mask; + int det; + + sjack->gpio.gpio = -ENOENT; + + if (is_hp) { + snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); + pin_name = "Headphones"; + gpio_name = "Headphone detection"; + mask = SND_JACK_HEADPHONE; + } else { + snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); + pin_name = "Mic Jack"; + gpio_name = "Mic detection"; + mask = SND_JACK_MICROPHONE; + } + + det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags); + if (det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (gpio_is_valid(det)) { + sjack->pin.pin = pin_name; + sjack->pin.mask = mask; + + sjack->gpio.name = gpio_name; + sjack->gpio.report = mask; + sjack->gpio.gpio = det; + sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + sjack->gpio.debounce_time = 150; + + snd_soc_card_jack_new(card, pin_name, mask, + &sjack->jack, + &sjack->pin, 1); + + snd_soc_jack_add_gpios(&sjack->jack, 1, + &sjack->gpio); + } + + return 0; +} + +static void asoc_simple_card_remove_jack(struct asoc_simple_jack *sjack) +{ + if (gpio_is_valid(sjack->gpio.gpio)) + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); +} + static int asoc_simple_card_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -110,32 +177,6 @@ static struct snd_soc_ops asoc_simple_card_ops = { .hw_params = asoc_simple_card_hw_params, }; -static struct snd_soc_jack simple_card_hp_jack; -static struct snd_soc_jack_pin simple_card_hp_jack_pins[] = { - { - .pin = "Headphones", - .mask = SND_JACK_HEADPHONE, - }, -}; -static struct snd_soc_jack_gpio simple_card_hp_jack_gpio = { - .name = "Headphone detection", - .report = SND_JACK_HEADPHONE, - .debounce_time = 150, -}; - -static struct snd_soc_jack simple_card_mic_jack; -static struct snd_soc_jack_pin simple_card_mic_jack_pins[] = { - { - .pin = "Mic Jack", - .mask = SND_JACK_MICROPHONE, - }, -}; -static struct snd_soc_jack_gpio simple_card_mic_jack_gpio = { - .name = "Mic detection", - .report = SND_JACK_MICROPHONE, - .debounce_time = 150, -}; - static int __asoc_simple_card_dai_init(struct snd_soc_dai *dai, struct asoc_simple_dai *set) { @@ -184,30 +225,14 @@ static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) if (ret < 0) return ret; - if (gpio_is_valid(priv->gpio_hp_det)) { - snd_soc_card_jack_new(rtd->card, "Headphones", - SND_JACK_HEADPHONE, - &simple_card_hp_jack, - simple_card_hp_jack_pins, - ARRAY_SIZE(simple_card_hp_jack_pins)); - - simple_card_hp_jack_gpio.gpio = priv->gpio_hp_det; - simple_card_hp_jack_gpio.invert = priv->gpio_hp_det_invert; - snd_soc_jack_add_gpios(&simple_card_hp_jack, 1, - &simple_card_hp_jack_gpio); - } + ret = asoc_simple_card_init_hp(rtd->card, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(rtd->card, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; - if (gpio_is_valid(priv->gpio_mic_det)) { - snd_soc_card_jack_new(rtd->card, "Mic Jack", - SND_JACK_MICROPHONE, - &simple_card_mic_jack, - simple_card_mic_jack_pins, - ARRAY_SIZE(simple_card_mic_jack_pins)); - simple_card_mic_jack_gpio.gpio = priv->gpio_mic_det; - simple_card_mic_jack_gpio.invert = priv->gpio_mic_det_invert; - snd_soc_jack_add_gpios(&simple_card_mic_jack, 1, - &simple_card_mic_jack_gpio); - } return 0; } @@ -223,6 +248,9 @@ asoc_simple_card_sub_parse_of(struct device_node *np, u32 val; int ret; + if (!np) + return 0; + /* * Get node via "sound-dai = <&phandle port>" * it will be used as xxx_of_node on soc_bind_dai_link() @@ -238,9 +266,14 @@ asoc_simple_card_sub_parse_of(struct device_node *np, *args_count = args.args_count; /* Get dai->name */ - ret = snd_soc_of_get_dai_name(np, name); - if (ret < 0) - return ret; + if (name) { + ret = snd_soc_of_get_dai_name(np, name); + if (ret < 0) + return ret; + } + + if (!dai) + return 0; /* Parse TDM slot */ ret = snd_soc_of_parse_tdm_slot(np, &dai->tx_slot_mask, @@ -275,48 +308,6 @@ asoc_simple_card_sub_parse_of(struct device_node *np, return 0; } -static int asoc_simple_card_parse_daifmt(struct device_node *node, - struct simple_card_data *priv, - struct device_node *codec, - char *prefix, int idx) -{ - struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); - struct device *dev = simple_priv_to_dev(priv); - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; - unsigned int daifmt; - - daifmt = snd_soc_of_parse_daifmt(node, prefix, - &bitclkmaster, &framemaster); - daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; - - if (strlen(prefix) && !bitclkmaster && !framemaster) { - /* - * No dai-link level and master setting was not found from - * sound node level, revert back to legacy DT parsing and - * take the settings from codec node. - */ - dev_dbg(dev, "Revert to legacy daifmt parsing\n"); - - daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | - (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); - } else { - if (codec == bitclkmaster) - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; - else - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; - } - - dai_link->dai_fmt = daifmt; - - of_node_put(bitclkmaster); - of_node_put(framemaster); - - return 0; -} - static int asoc_simple_card_dai_link_of(struct device_node *node, struct simple_card_data *priv, int idx, @@ -328,7 +319,6 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, struct device_node *cpu = NULL; struct device_node *plat = NULL; struct device_node *codec = NULL; - char *name; char prop[128]; char *prefix = ""; int ret, cpu_args; @@ -336,7 +326,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, /* For single DAI link & old style of DT node */ if (is_top_level_node) - prefix = "simple-audio-card,"; + prefix = PREFIX; snprintf(prop, sizeof(prop), "%scpu", prefix); cpu = of_get_child_by_name(node, prop); @@ -353,8 +343,8 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, goto dai_link_of_err; } - ret = asoc_simple_card_parse_daifmt(node, priv, - codec, prefix, idx); + ret = asoc_simple_card_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) goto dai_link_of_err; @@ -374,35 +364,28 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, if (ret < 0) goto dai_link_of_err; + ret = asoc_simple_card_sub_parse_of(plat, NULL, + &dai_link->platform_of_node, + NULL, NULL); + if (ret < 0) + goto dai_link_of_err; + if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) { ret = -EINVAL; goto dai_link_of_err; } - if (plat) { - struct of_phandle_args args; - - ret = of_parse_phandle_with_args(plat, "sound-dai", - "#sound-dai-cells", 0, &args); - dai_link->platform_of_node = args.np; - } else { - /* Assumes platform == cpu */ + /* Assumes platform == cpu */ + if (!dai_link->platform_of_node) dai_link->platform_of_node = dai_link->cpu_of_node; - } - /* DAI link name is created from CPU/CODEC dai name */ - name = devm_kzalloc(dev, - strlen(dai_link->cpu_dai_name) + - strlen(dai_link->codec_dai_name) + 2, - GFP_KERNEL); - if (!name) { - ret = -ENOMEM; + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) goto dai_link_of_err; - } - sprintf(name, "%s-%s", dai_link->cpu_dai_name, - dai_link->codec_dai_name); - dai_link->name = dai_link->stream_name = name; dai_link->ops = &asoc_simple_card_ops; dai_link->init = asoc_simple_card_dai_init; @@ -438,42 +421,35 @@ static int asoc_simple_card_parse_of(struct device_node *node, struct simple_card_data *priv) { struct device *dev = simple_priv_to_dev(priv); - enum of_gpio_flags flags; u32 val; int ret; if (!node) return -EINVAL; - /* Parse the card name from DT */ - snd_soc_of_parse_card_name(&priv->snd_card, "simple-audio-card,name"); - /* The off-codec widgets */ - if (of_property_read_bool(node, "simple-audio-card,widgets")) { + if (of_property_read_bool(node, PREFIX "widgets")) { ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card, - "simple-audio-card,widgets"); + PREFIX "widgets"); if (ret) return ret; } /* DAPM routes */ - if (of_property_read_bool(node, "simple-audio-card,routing")) { + if (of_property_read_bool(node, PREFIX "routing")) { ret = snd_soc_of_parse_audio_routing(&priv->snd_card, - "simple-audio-card,routing"); + PREFIX "routing"); if (ret) return ret; } /* Factor to mclk, used in hw_params() */ - ret = of_property_read_u32(node, "simple-audio-card,mclk-fs", &val); + ret = of_property_read_u32(node, PREFIX "mclk-fs", &val); if (ret == 0) priv->mclk_fs = val; - dev_dbg(dev, "New simple-card: %s\n", priv->snd_card.name ? - priv->snd_card.name : ""); - /* Single/Muti DAI link(s) & New style of DT node */ - if (of_get_child_by_name(node, "simple-audio-card,dai-link")) { + if (of_get_child_by_name(node, PREFIX "dai-link")) { struct device_node *np = NULL; int i = 0; @@ -494,20 +470,9 @@ static int asoc_simple_card_parse_of(struct device_node *node, return ret; } - priv->gpio_hp_det = of_get_named_gpio_flags(node, - "simple-audio-card,hp-det-gpio", 0, &flags); - priv->gpio_hp_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); - if (priv->gpio_hp_det == -EPROBE_DEFER) - return -EPROBE_DEFER; - - priv->gpio_mic_det = of_get_named_gpio_flags(node, - "simple-audio-card,mic-det-gpio", 0, &flags); - priv->gpio_mic_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); - if (priv->gpio_mic_det == -EPROBE_DEFER) - return -EPROBE_DEFER; - - if (!priv->snd_card.name) - priv->snd_card.name = priv->snd_card.dai_link->name; + ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + if (ret) + return ret; return 0; } @@ -536,7 +501,7 @@ static int asoc_simple_card_probe(struct platform_device *pdev) int num_links, ret; /* Get the number of DAI links */ - if (np && of_get_child_by_name(np, "simple-audio-card,dai-link")) + if (np && of_get_child_by_name(np, PREFIX "dai-link")) num_links = of_get_child_count(np); else num_links = 1; @@ -555,9 +520,6 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->snd_card.dai_link = dai_link; priv->snd_card.num_links = num_links; - priv->gpio_hp_det = -ENOENT; - priv->gpio_mic_det = -ENOENT; - /* Get room for the other properties */ priv->dai_props = devm_kzalloc(dev, sizeof(*priv->dai_props) * num_links, @@ -624,12 +586,8 @@ static int asoc_simple_card_remove(struct platform_device *pdev) struct snd_soc_card *card = platform_get_drvdata(pdev); struct simple_card_data *priv = snd_soc_card_get_drvdata(card); - if (gpio_is_valid(priv->gpio_hp_det)) - snd_soc_jack_free_gpios(&simple_card_hp_jack, 1, - &simple_card_hp_jack_gpio); - if (gpio_is_valid(priv->gpio_mic_det)) - snd_soc_jack_free_gpios(&simple_card_mic_jack, 1, - &simple_card_mic_jack_gpio); + asoc_simple_card_remove_jack(&priv->hp_jack); + asoc_simple_card_remove_jack(&priv->mic_jack); return asoc_simple_card_unref(card); } diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index 91c15abb6..a20c3dfbc 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -7,7 +7,7 @@ config SND_MFLD_MACHINE 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 + Say Y if you have such a device. If unsure select "N". config SND_SST_MFLD_PLATFORM @@ -25,7 +25,6 @@ config SND_SST_IPC_ACPI tristate select SND_SST_IPC select SND_SOC_INTEL_SST - depends on ACPI config SND_SOC_INTEL_SST tristate @@ -33,6 +32,12 @@ config SND_SOC_INTEL_SST select SND_SOC_INTEL_SST_MATCH if ACPI depends on (X86 || COMPILE_TEST) +# firmware stuff depends DW_DMAC_CORE; since there is no depends-on from +# the reverse selection, each machine driver needs to select +# SND_SOC_INTEL_SST_FIRMWARE carefully depending on DW_DMAC_CORE +config SND_SOC_INTEL_SST_FIRMWARE + tristate + config SND_SOC_INTEL_SST_ACPI tristate @@ -48,16 +53,33 @@ config SND_SOC_INTEL_BAYTRAIL config SND_SOC_INTEL_HASWELL_MACH tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM - depends on DW_DMAC_CORE=y + depends on DW_DMAC_CORE select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_HASWELL select SND_SOC_RT5640 help This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell Ultrabook platforms. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". +config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH + tristate "ASoC Audio driver for Broxton with DA7219 and MAX98357A in I2S Mode" + depends on X86 && ACPI && I2C + select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SKYLAKE + select SND_SOC_DA7219 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_HDA_DSP_LOADER + help + This adds support for ASoC machine driver for Broxton-P platforms + with DA7219 + MAX98357A I2S audio codec. + Say Y if you have such a device. + If unsure select "N". + config SND_SOC_INTEL_BXT_RT298_MACH tristate "ASoC Audio driver for Broxton with RT298 I2S mode" depends on X86 && ACPI && I2C @@ -70,26 +92,28 @@ config SND_SOC_INTEL_BXT_RT298_MACH help This adds support for ASoC machine driver for Broxton platforms with RT286 I2S audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYT_RT5640_MACH tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec" depends on X86_INTEL_LPSS && I2C - depends on DW_DMAC_CORE=y && (SND_SST_IPC_ACPI = n) + depends on DW_DMAC_CORE && (SND_SST_IPC_ACPI = n) select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_BAYTRAIL select SND_SOC_RT5640 help This adds audio driver for Intel Baytrail platform based boards with the RT5640 audio codec. This driver is deprecated, use - SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality + SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality. config SND_SOC_INTEL_BYT_MAX98090_MACH tristate "ASoC Audio driver for Intel Baytrail with MAX98090 codec" depends on X86_INTEL_LPSS && I2C - depends on DW_DMAC_CORE=y && (SND_SST_IPC_ACPI = n) + depends on DW_DMAC_CORE && (SND_SST_IPC_ACPI = n) select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_BAYTRAIL select SND_SOC_MAX98090 help @@ -100,19 +124,20 @@ config SND_SOC_INTEL_BROADWELL_MACH tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint" depends on X86_INTEL_LPSS && I2C && DW_DMAC && \ I2C_DESIGNWARE_PLATFORM - depends on DW_DMAC_CORE=y + depends on DW_DMAC_CORE select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_HASWELL select SND_SOC_RT286 help This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell Ultrabook platforms. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYTCR_RT5640_MACH tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5640 codec" - depends on X86 && I2C + depends on X86 && I2C && ACPI select SND_SOC_RT5640 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -120,12 +145,12 @@ config SND_SOC_INTEL_BYTCR_RT5640_MACH help This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR platforms with RT5640 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYTCR_RT5651_MACH tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5651 codec" - depends on X86 && I2C + depends on X86 && I2C && ACPI select SND_SOC_RT5651 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -133,12 +158,12 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH help This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR platforms with RT5651 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_CHT_BSW_RT5672_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_RT5670 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -146,12 +171,12 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH help This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell platforms with RT5672 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5645/5650 codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_RT5645 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -163,16 +188,16 @@ config SND_SOC_INTEL_CHT_BSW_RT5645_MACH config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with MAX98090 & TI codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_MAX98090 select SND_SOC_TS3A227E select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI select SND_SOC_INTEL_SST_MATCH if ACPI help - This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell - platforms with MAX98090 audio codec it also can support TI jack chip as aux device. - If unsure select "N". + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with MAX98090 audio codec it also can support TI jack chip as aux device. + If unsure select "N". config SND_SOC_INTEL_SKYLAKE tristate @@ -192,7 +217,7 @@ config SND_SOC_INTEL_SKL_RT286_MACH help This adds support for ASoC machine driver for Skylake platforms with RT286 I2S audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH @@ -207,7 +232,7 @@ config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH help This adds support for ASoC Onboard Codec I2S machine driver. This will create an alsa sound card for NAU88L25 + SSM4567. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH @@ -222,5 +247,5 @@ config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH help This adds support for ASoC Onboard Codec I2S machine driver. This will create an alsa sound card for NAU88L25 + MAX98357A. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index cbdda5d7d..aaf6d6579 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -28,6 +28,7 @@ #include <linux/firmware.h> #include <linux/pm_runtime.h> #include <linux/pm_qos.h> +#include <linux/dmi.h> #include <linux/acpi.h> #include <asm/platform_sst_audio.h> #include <sound/core.h> @@ -237,6 +238,9 @@ static int sst_acpi_probe(struct platform_device *pdev) dev_err(dev, "No matching machine driver found\n"); return -ENODEV; } + if (mach->machine_quirk) + mach = mach->machine_quirk(mach); + pdata = mach->pdata; ret = kstrtouint(id->id, 16, &dev_id); @@ -320,6 +324,44 @@ static int sst_acpi_remove(struct platform_device *pdev) return 0; } +static unsigned long cht_machine_id; + +#define CHT_SURFACE_MACH 1 + +static int cht_surface_quirk_cb(const struct dmi_system_id *id) +{ + cht_machine_id = CHT_SURFACE_MACH; + return 1; +} + + +static const struct dmi_system_id cht_table[] = { + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, +}; + + +static struct sst_acpi_mach cht_surface_mach = { + "10EC5640", "cht-bsw-rt5645", "/*(DEBLOBBED)*/", "cht-bsw", NULL, + &chv_platform_data }; + +static struct sst_acpi_mach *cht_quirk(void *arg) +{ + struct sst_acpi_mach *mach = arg; + + dmi_check_system(cht_table); + + if (cht_machine_id == CHT_SURFACE_MACH) + return &cht_surface_mach; + else + return mach; +} + static struct sst_acpi_mach sst_acpi_bytcr[] = { {"10EC5640", "bytcr_rt5640", "/*(DEBLOBBED)*/", "bytcr_rt5640", NULL, &byt_rvp_platform_data }, @@ -343,7 +385,7 @@ static struct sst_acpi_mach sst_acpi_chv[] = { {"193C9890", "cht-bsw-max98090", "/*(DEBLOBBED)*/", "cht-bsw", NULL, &chv_platform_data }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ - {"10EC5640", "bytcr_rt5640", "/*(DEBLOBBED)*/", "bytcr_rt5640", NULL, + {"10EC5640", "bytcr_rt5640", "/*(DEBLOBBED)*/", "bytcr_rt5640", cht_quirk, &chv_platform_data }, {}, diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index a8506774f..dac03a06b 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -2,6 +2,7 @@ snd-soc-sst-haswell-objs := haswell.o snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o snd-soc-sst-broadwell-objs := broadwell.o +snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o snd-soc-sst-bxt-rt298-objs := bxt_rt298.o snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o @@ -15,6 +16,7 @@ snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH) += snd-soc-sst-bxt-da7219_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c new file mode 100644 index 000000000..3774b117d --- /dev/null +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -0,0 +1,460 @@ +/* + * Intel Broxton-P I2S Machine Driver + * + * Copyright (C) 2016, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Skylake I2S Machine driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../codecs/hdac_hdmi.h" +#include "../../codecs/da7219.h" +#include "../../codecs/da7219-aad.h" + +#define BXT_DIALOG_CODEC_DAI "da7219-hifi" +#define BXT_MAXIM_CODEC_DAI "HiFi" +#define DUAL_CHANNEL 2 + +static struct snd_soc_jack broxton_headset; + +enum { + BXT_DPCM_AUDIO_PB = 0, + BXT_DPCM_AUDIO_CP, + BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_HDMI1_PB, + BXT_DPCM_AUDIO_HDMI2_PB, + BXT_DPCM_AUDIO_HDMI3_PB, +}; + +static const struct snd_kcontrol_new broxton_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget broxton_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), +}; + +static const struct snd_soc_dapm_route broxton_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* speaker */ + {"Spk", NULL, "Speaker"}, + + /* other jacks */ + {"MIC", NULL, "Headset Mic"}, + + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, + + /* CODEC BE connections */ + {"HiFi Playback", NULL, "ssp5 Tx"}, + {"ssp5 Tx", NULL, "codec0_out"}, + + {"Playback", NULL, "ssp1 Tx"}, + {"ssp1 Tx", NULL, "codec1_out"}, + + {"codec0_in", NULL, "ssp1 Rx"}, + {"ssp1 Rx", NULL, "Capture"}, + + {"HDMI1", NULL, "hif5 Output"}, + {"HDMI2", NULL, "hif6 Output"}, + {"HDMI3", NULL, "hif7 Output"}, + + {"hifi3", NULL, "iDisp3 Tx"}, + {"iDisp3 Tx", NULL, "iDisp3_out"}, + {"hifi2", NULL, "iDisp2 Tx"}, + {"iDisp2 Tx", NULL, "iDisp2_out"}, + {"hifi1", NULL, "iDisp1 Tx"}, + {"iDisp1 Tx", NULL, "iDisp1_out"}, + + /* DMIC */ + {"dmic01_hifi", NULL, "DMIC01 Rx"}, + {"DMIC01 Rx", NULL, "DMIC AIF"}, +}; + +static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + 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); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = DUAL_CHANNEL; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_codec *codec = rtd->codec; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &broxton_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + da7219_aad_jack_det(codec, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return ret; +} + +static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->codec_dai; + + return hdac_hdmi_jack_init(dai, BXT_DPCM_AUDIO_HDMI1_PB + dai->id); +} + +static int broxton_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static unsigned int rates[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_da7219_fe_ops = { + .startup = bxt_fe_startup, +}; + +static int broxton_da7219_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; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int broxton_da7219_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; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static struct snd_soc_ops broxton_da7219_ops = { + .hw_params = broxton_da7219_hw_params, + .hw_free = broxton_da7219_hw_free, +}; + +/* broxton digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broxton_dais[] = { + /* Front End DAI links */ + [BXT_DPCM_AUDIO_PB] + { + .name = "Bxt Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = broxton_da7219_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_CP] + { + .name = "Bxt Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_REF_CP] + { + .name = "Bxt Audio Reference cap", + .stream_name = "Refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .ignore_suspend = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI1_PB] + { + .name = "Bxt HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI2_PB] + { + .name = "Bxt HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI3_PB] + { + .name = "Bxt HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + { + /* SSP5 - Codec */ + .name = "SSP5-Codec", + .id = 0, + .cpu_dai_name = "SSP5 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "MX98357A:00", + .codec_dai_name = BXT_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "i2c-DLGS7219:00", + .codec_dai_name = BXT_DIALOG_CODEC_DAI, + .init = broxton_da7219_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .ops = &broxton_da7219_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +/* broxton audio machine driver for SPT + da7219 */ +static struct snd_soc_card broxton_audio_card = { + .name = "bxtda7219max", + .owner = THIS_MODULE, + .dai_link = broxton_dais, + .num_links = ARRAY_SIZE(broxton_dais), + .controls = broxton_controls, + .num_controls = ARRAY_SIZE(broxton_controls), + .dapm_widgets = broxton_widgets, + .num_dapm_widgets = ARRAY_SIZE(broxton_widgets), + .dapm_routes = broxton_map, + .num_dapm_routes = ARRAY_SIZE(broxton_map), + .fully_routed = true, +}; + +static int broxton_audio_probe(struct platform_device *pdev) +{ + broxton_audio_card.dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, &broxton_audio_card); +} + +static struct platform_driver broxton_audio = { + .probe = broxton_audio_probe, + .driver = { + .name = "bxt_da7219_max98357a_i2s", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(broxton_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Sathyanarayana Nujella <sathyanarayana.nujella@intel.com>"); +MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com>"); +MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); +MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bxt_da7219_max98357a_i2s"); diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index f4787515c..253d7bfbf 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -33,6 +33,7 @@ enum { BXT_DPCM_AUDIO_PB = 0, BXT_DPCM_AUDIO_CP, BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_DMIC_CP, BXT_DPCM_AUDIO_HDMI1_PB, BXT_DPCM_AUDIO_HDMI2_PB, BXT_DPCM_AUDIO_HDMI3_PB, @@ -88,6 +89,7 @@ static const struct snd_soc_dapm_route broxton_rt298_map[] = { /* CODEC BE connections */ { "AIF1 Playback", NULL, "ssp5 Tx"}, { "ssp5 Tx", NULL, "codec0_out"}, + { "ssp5 Tx", NULL, "codec1_out"}, { "codec0_in", NULL, "ssp5 Rx" }, { "ssp5 Rx", NULL, "AIF1 Capture" }, @@ -104,6 +106,17 @@ static const struct snd_soc_dapm_route broxton_rt298_map[] = { }; +static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; @@ -118,6 +131,9 @@ static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; rt298_mic_detect(codec, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + return 0; } @@ -169,6 +185,89 @@ static struct snd_soc_ops broxton_rt298_ops = { .hw_params = broxton_rt298_hw_params, }; +static unsigned int rates[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + if (params_channels(params) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static unsigned int channels_dmic[] = { + 2, 4, +}; + +static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static int broxton_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_dmic_channels); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static struct snd_soc_ops broxton_dmic_ops = { + .startup = broxton_dmic_startup, +}; + +static unsigned int channels[] = { + 2, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * on this platform for PCM device we support: + * 48Khz + * stereo + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_rt286_fe_ops = { + .startup = bxt_fe_startup, +}; + /* broxton digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link broxton_rt298_dais[] = { /* Front End DAI links */ @@ -182,8 +281,10 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .dynamic = 1, .codec_name = "snd-soc-dummy", .codec_dai_name = "snd-soc-dummy-dai", + .init = broxton_rt298_fe_init, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_playback = 1, + .ops = &broxton_rt286_fe_ops, }, [BXT_DPCM_AUDIO_CP] { @@ -197,6 +298,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .codec_dai_name = "snd-soc-dummy-dai", .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_capture = 1, + .ops = &broxton_rt286_fe_ops, }, [BXT_DPCM_AUDIO_REF_CP] { @@ -211,6 +313,20 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .nonatomic = 1, .dynamic = 1, }, + [BXT_DPCM_AUDIO_DMIC_CP] + { + .name = "Bxt Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &broxton_dmic_ops, + }, [BXT_DPCM_AUDIO_HDMI1_PB] { .name = "Bxt HDMI Port1", @@ -276,6 +392,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .codec_name = "dmic-codec", .codec_dai_name = "dmic-hifi", .platform_name = "0000:00:0e.0", + .be_hw_params_fixup = broxton_dmic_fixup, .ignore_suspend = 1, .dpcm_capture = 1, .no_pcm = 1, @@ -341,6 +458,7 @@ static struct platform_driver broxton_audio = { .probe = broxton_audio_probe, .driver = { .name = "bxt_alc298s_i2s", + .pm = &snd_soc_pm_ops, }, }; module_platform_driver(broxton_audio) diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index d7ef292c4..56056ed7f 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -30,6 +30,7 @@ #include <sound/jack.h> #include "../../codecs/rt5645.h" #include "../atom/sst-atom-controls.h" +#include "../common/sst-acpi.h" #define CHT_PLAT_CLK_3_HZ 19200000 #define CHT_CODEC_DAI "rt5645-aif1" @@ -340,10 +341,13 @@ static struct snd_soc_card snd_soc_card_chtrt5650 = { }; static struct cht_acpi_card snd_soc_cards[] = { + {"10EC5640", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, {"10EC5645", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, {"10EC5650", CODEC_TYPE_RT5650, &snd_soc_card_chtrt5650}, }; +static char cht_rt5640_codec_name[16]; /* i2c-<HID>:00 with HID being 8 chars */ + static int snd_cht_mc_probe(struct platform_device *pdev) { int ret_val = 0; @@ -351,6 +355,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) struct cht_mc_private *drv; struct snd_soc_card *card = snd_soc_cards[0].soc_card; char codec_name[16]; + struct sst_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); if (!drv) @@ -366,12 +373,23 @@ static int snd_cht_mc_probe(struct platform_device *pdev) } } card->dev = &pdev->dev; + mach = card->dev->platform_data; sprintf(codec_name, "i2c-%s:00", drv->acpi_card->codec_id); /* set correct codec name */ for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) - if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) + if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) { card->dai_link[i].codec_name = kstrdup(codec_name, GFP_KERNEL); + dai_index = i; + } + + /* fixup codec name based on HID */ + i2c_name = sst_acpi_find_name_from_hid(mach->id); + if (i2c_name != NULL) { + snprintf(cht_rt5640_codec_name, sizeof(cht_rt5640_codec_name), + "%s%s", "i2c-", i2c_name); + cht_dailink[dai_index].codec_name = cht_rt5640_codec_name; + } snd_soc_card_set_drvdata(card, drv); ret_val = devm_snd_soc_register_card(&pdev->dev, card); diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c index d2808652b..25db5be7f 100644 --- a/sound/soc/intel/boards/skl_nau88l25_max98357a.c +++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c @@ -23,12 +23,15 @@ #include <sound/soc.h> #include "../../codecs/nau8825.h" #include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" #define SKL_MAXIM_CODEC_DAI "HiFi" +#define DMIC_CH(p) p->list[p->count-1] static struct snd_soc_jack skylake_headset; static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; struct skl_hdmi_pcm { struct list_head head; @@ -339,7 +342,7 @@ static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - if (params_channels(params) == 2) + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) channels->min = channels->max = 2; else channels->min = channels->max = 4; @@ -357,13 +360,23 @@ static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { .mask = 0, }; +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + static int skylake_dmic_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - runtime->hw.channels_max = 4; + runtime->hw.channels_max = DMIC_CH(dmic_constraints); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - &constraints_dmic_channels); + dmic_constraints); return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); @@ -382,8 +395,22 @@ static struct snd_pcm_hw_constraint_list constraints_16000 = { .list = rates_16000, }; +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + static int skylake_refcap_startup(struct snd_pcm_substream *substream) { + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_16000); @@ -610,6 +637,7 @@ static struct snd_soc_card skylake_audio_card = { static int skylake_audio_probe(struct platform_device *pdev) { struct skl_nau8825_private *ctx; + struct skl_machine_pdata *pdata; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); if (!ctx) @@ -620,15 +648,27 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_m98357a" }, + { .name = "kbl_n88l25_m98357a" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { - .name = "skl_nau88l25_max98357a_i2s", + .name = "skl_n88l25_m98357a", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, }; module_platform_driver(skylake_audio) @@ -637,4 +677,5 @@ module_platform_driver(skylake_audio) MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode"); MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_nau88l25_max98357a_i2s"); +MODULE_ALIAS("platform:skl_n88l25_m98357a"); +MODULE_ALIAS("platform:kbl_n88l25_m98357a"); diff --git a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c index e19aa99c4..69c5d5da4 100644 --- a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c +++ b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c @@ -27,12 +27,15 @@ #include <sound/pcm_params.h> #include "../../codecs/nau8825.h" #include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" #define SKL_SSM_CODEC_DAI "ssm4567-hifi" +#define DMIC_CH(p) p->list[p->count-1] static struct snd_soc_jack skylake_headset; static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; struct skl_hdmi_pcm { struct list_head head; @@ -367,7 +370,7 @@ static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, { struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - if (params_channels(params) == 2) + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) channels->min = channels->max = 2; else channels->min = channels->max = 4; @@ -405,13 +408,23 @@ static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { .mask = 0, }; +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + static int skylake_dmic_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - runtime->hw.channels_max = 4; + runtime->hw.channels_max = DMIC_CH(dmic_constraints); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - &constraints_dmic_channels); + dmic_constraints); return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); @@ -430,8 +443,22 @@ static struct snd_pcm_hw_constraint_list constraints_16000 = { .list = rates_16000, }; +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + static int skylake_refcap_startup(struct snd_pcm_substream *substream) { + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_16000); @@ -662,6 +689,7 @@ static struct snd_soc_card skylake_audio_card = { static int skylake_audio_probe(struct platform_device *pdev) { struct skl_nau88125_private *ctx; + struct skl_machine_pdata *pdata; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); if (!ctx) @@ -672,15 +700,27 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_s4567" }, + { .name = "kbl_n88l25_s4567" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { - .name = "skl_nau88l25_ssm4567_i2s", + .name = "skl_n88l25_s4567", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, }; module_platform_driver(skylake_audio) @@ -693,4 +733,5 @@ MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_nau88l25_ssm4567_i2s"); +MODULE_ALIAS("platform:skl_n88l25_s4567"); +MODULE_ALIAS("platform:kbl_n88l25_s4567"); diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c index 426b48233..88c61e8cb 100644 --- a/sound/soc/intel/boards/skl_rt286.c +++ b/sound/soc/intel/boards/skl_rt286.c @@ -505,12 +505,20 @@ static int skylake_audio_probe(struct platform_device *pdev) return devm_snd_soc_register_card(&pdev->dev, &skylake_rt286); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_alc286s_i2s" }, + { .name = "kbl_alc286s_i2s" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { .name = "skl_alc286s_i2s", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, + }; module_platform_driver(skylake_audio) @@ -520,3 +528,4 @@ MODULE_AUTHOR("Omair Mohammed Abdullah <omair.m.abdullah@intel.com>"); MODULE_DESCRIPTION("Intel SST Audio for Skylake"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:skl_alc286s_i2s"); +MODULE_ALIAS("platform:kbl_alc286s_i2s"); diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile index fbbb25c2c..1a35149bc 100644 --- a/sound/soc/intel/common/Makefile +++ b/sound/soc/intel/common/Makefile @@ -2,9 +2,9 @@ snd-soc-sst-dsp-objs := sst-dsp.o snd-soc-sst-acpi-objs := sst-acpi.o snd-soc-sst-match-objs := sst-match-acpi.o snd-soc-sst-ipc-objs := sst-ipc.o - -snd-soc-sst-dsp-$(CONFIG_DW_DMAC_CORE) += sst-firmware.o +snd-soc-sst-firmware-objs := sst-firmware.o obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o obj-$(CONFIG_SND_SOC_INTEL_SST_MATCH) += snd-soc-sst-match.o +obj-$(CONFIG_SND_SOC_INTEL_SST_FIRMWARE) += snd-soc-sst-firmware.o diff --git a/sound/soc/intel/common/sst-acpi.h b/sound/soc/intel/common/sst-acpi.h index 8398cb227..5d2949324 100644 --- a/sound/soc/intel/common/sst-acpi.h +++ b/sound/soc/intel/common/sst-acpi.h @@ -20,7 +20,7 @@ #if IS_ENABLED(CONFIG_ACPI) const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]); #else -inline const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]) +static inline const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]) { return NULL; } @@ -40,6 +40,6 @@ struct sst_acpi_mach { /* board name */ const char *board; - void (*machine_quirk)(void); + struct sst_acpi_mach * (*machine_quirk)(void *arg); void *pdata; }; diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h index 97dc1ae05..d13c84364 100644 --- a/sound/soc/intel/common/sst-dsp-priv.h +++ b/sound/soc/intel/common/sst-dsp-priv.h @@ -383,10 +383,6 @@ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, u32 index, void *private); void sst_mem_block_unregister_all(struct sst_dsp *dsp); -/* Create/Free DMA resources */ -int sst_dma_new(struct sst_dsp *sst); -void sst_dma_free(struct sst_dma *dma); - u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, enum sst_mem_type type); #endif diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c index b5bbdf4fe..c00ede4ea 100644 --- a/sound/soc/intel/common/sst-dsp.c +++ b/sound/soc/intel/common/sst-dsp.c @@ -285,7 +285,7 @@ int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask, } reg = sst_dsp_shim_read_unlocked(ctx, offset); - dev_info(ctx->dev, "FW Poll Status: reg=%#x %s %s\n", reg, operation, + dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s %s\n", reg, operation, (time < timeout) ? "successful" : "timedout"); ret = time < timeout ? 0 : -ETIME; @@ -420,73 +420,6 @@ void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) } EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); -#ifdef CONFIG_DW_DMAC_CORE -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) -{ - struct sst_dsp *sst; - int err; - - dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); - - sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); - if (sst == NULL) - return NULL; - - spin_lock_init(&sst->spinlock); - mutex_init(&sst->mutex); - sst->dev = dev; - sst->dma_dev = pdata->dma_dev; - sst->thread_context = sst_dev->thread_context; - sst->sst_dev = sst_dev; - sst->id = pdata->id; - sst->irq = pdata->irq; - sst->ops = sst_dev->ops; - sst->pdata = pdata; - INIT_LIST_HEAD(&sst->used_block_list); - INIT_LIST_HEAD(&sst->free_block_list); - INIT_LIST_HEAD(&sst->module_list); - INIT_LIST_HEAD(&sst->fw_list); - INIT_LIST_HEAD(&sst->scratch_block_list); - - /* Initialise SST Audio DSP */ - if (sst->ops->init) { - err = sst->ops->init(sst, pdata); - if (err < 0) - return NULL; - } - - /* Register the ISR */ - err = request_threaded_irq(sst->irq, sst->ops->irq_handler, - sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); - if (err) - goto irq_err; - - err = sst_dma_new(sst); - if (err) - dev_warn(dev, "sst_dma_new failed %d\n", err); - - return sst; - -irq_err: - if (sst->ops->free) - sst->ops->free(sst); - - return NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_new); - -void sst_dsp_free(struct sst_dsp *sst) -{ - free_irq(sst->irq, sst); - if (sst->ops->free) - sst->ops->free(sst); - - sst_dma_free(sst->dma); -} -EXPORT_SYMBOL_GPL(sst_dsp_free); -#endif - /* Module information */ MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Intel SST Core"); diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h index 0b84c719e..859f0de00 100644 --- a/sound/soc/intel/common/sst-dsp.h +++ b/sound/soc/intel/common/sst-dsp.h @@ -216,7 +216,7 @@ struct sst_pdata { void *dsp; }; -#ifdef CONFIG_DW_DMAC_CORE +#if IS_ENABLED(CONFIG_DW_DMAC_CORE) /* Initialization */ struct sst_dsp *sst_dsp_new(struct device *dev, struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c index 259935273..a086c35f9 100644 --- a/sound/soc/intel/common/sst-firmware.c +++ b/sound/soc/intel/common/sst-firmware.c @@ -1211,3 +1211,71 @@ u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, } } EXPORT_SYMBOL_GPL(sst_dsp_get_offset); + +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) +{ + struct sst_dsp *sst; + int err; + + dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); + + sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); + if (sst == NULL) + return NULL; + + spin_lock_init(&sst->spinlock); + mutex_init(&sst->mutex); + sst->dev = dev; + sst->dma_dev = pdata->dma_dev; + sst->thread_context = sst_dev->thread_context; + sst->sst_dev = sst_dev; + sst->id = pdata->id; + sst->irq = pdata->irq; + sst->ops = sst_dev->ops; + sst->pdata = pdata; + INIT_LIST_HEAD(&sst->used_block_list); + INIT_LIST_HEAD(&sst->free_block_list); + INIT_LIST_HEAD(&sst->module_list); + INIT_LIST_HEAD(&sst->fw_list); + INIT_LIST_HEAD(&sst->scratch_block_list); + + /* Initialise SST Audio DSP */ + if (sst->ops->init) { + err = sst->ops->init(sst, pdata); + if (err < 0) + return NULL; + } + + /* Register the ISR */ + err = request_threaded_irq(sst->irq, sst->ops->irq_handler, + sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); + if (err) + goto irq_err; + + err = sst_dma_new(sst); + if (err) + dev_warn(dev, "sst_dma_new failed %d\n", err); + + return sst; + +irq_err: + if (sst->ops->free) + sst->ops->free(sst); + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_new); + +void sst_dsp_free(struct sst_dsp *sst) +{ + free_irq(sst->irq, sst); + if (sst->ops->free) + sst->ops->free(sst); + + sst_dma_free(sst->dma); +} +EXPORT_SYMBOL_GPL(sst_dsp_free); + +MODULE_DESCRIPTION("Intel SST Firmware Loader"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c index 994256b39..3154525c2 100644 --- a/sound/soc/intel/haswell/sst-haswell-pcm.c +++ b/sound/soc/intel/haswell/sst-haswell-pcm.c @@ -819,7 +819,6 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) mutex_lock(&pcm_data->mutex); pm_runtime_get_sync(pdata->dev); - snd_soc_pcm_set_drvdata(rtd, pcm_data); pcm_data->substream = substream; snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile index c28f5d0e1..60fbc9bbe 100644 --- a/sound/soc/intel/skylake/Makefile +++ b/sound/soc/intel/skylake/Makefile @@ -5,6 +5,6 @@ obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl.o # Skylake IPC Support snd-soc-skl-ipc-objs := skl-sst-ipc.o skl-sst-dsp.o skl-sst-cldma.o \ - skl-sst.o bxt-sst.o + skl-sst.o bxt-sst.o skl-sst-utils.o obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl-ipc.o diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c index 76ba82a9c..b6a090126 100644 --- a/sound/soc/intel/skylake/bxt-sst.c +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -37,11 +37,19 @@ #define BXT_ADSP_SRAM1_BASE 0xA0000 +#define BXT_INSTANCE_ID 0 +#define BXT_BASE_FW_MODULE_ID 0 + static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) { return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); } +/* + * First boot sequence has some extra steps. Core 0 waits for power + * status on core 1, so power up core 1 also momentarily, keep it in + * reset/stall and then turn it off + */ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, const void *fwdata, u32 fwsize) { @@ -49,7 +57,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, u32 reg; stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab); - if (stream_tag < 0) { + if (stream_tag <= 0) { dev_err(ctx->dev, "Failed to prepare DMA FW loading err: %x\n", stream_tag); return stream_tag; @@ -58,17 +66,27 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, ctx->dsp_ops.stream_tag = stream_tag; memcpy(ctx->dmab.area, fwdata, fwsize); - /* Purge FW request */ + /* Step 1: Power up core 0 and core1 */ + ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK | + SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0/1 power up failed\n"); + goto base_fw_load_failed; + } + + /* Step 2: Purge FW request */ sst_dsp_shim_write(ctx, SKL_ADSP_REG_HIPCI, SKL_ADSP_REG_HIPCI_BUSY | - BXT_IPC_PURGE_FW | (stream_tag - 1)); + (BXT_IPC_PURGE_FW | ((stream_tag - 1) << 9))); - ret = skl_dsp_enable_core(ctx); + /* Step 3: Unset core0 reset state & unstall/run core0 */ + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "Boot dsp core failed ret: %d\n", ret); + dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret); ret = -EIO; goto base_fw_load_failed; } + /* Step 4: Wait for DONE Bit */ for (i = BXT_INIT_TIMEOUT; i > 0; --i) { reg = sst_dsp_shim_read(ctx, SKL_ADSP_REG_HIPCIE); @@ -88,10 +106,18 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, SKL_ADSP_REG_HIPCIE_DONE); } - /* enable Interrupt */ + /* Step 5: power down core1 */ + ret = skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core1 power down failed\n"); + goto base_fw_load_failed; + } + + /* Step 6: Enable Interrupt */ skl_ipc_int_enable(ctx); skl_ipc_op_int_enable(ctx); + /* Step 7: Wait for ROM init */ for (i = BXT_INIT_TIMEOUT; i > 0; --i) { if (SKL_FW_INIT == (sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS) & @@ -112,7 +138,8 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, base_fw_load_failed: ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag); - skl_dsp_disable_core(ctx); + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); return ret; } @@ -130,23 +157,41 @@ static int sst_transfer_fw_host_dma(struct sst_dsp *ctx) return ret; } +#define BXT_ADSP_FW_BIN_HDR_OFFSET 0x2000 + static int bxt_load_base_firmware(struct sst_dsp *ctx) { - const struct firmware *fw = NULL; + struct firmware stripped_fw; struct skl_sst *skl = ctx->thread_context; int ret; - ret = reject_firmware(&fw, ctx->fw_name, ctx->dev); + ret = reject_firmware(&ctx->fw, ctx->fw_name, ctx->dev); if (ret < 0) { dev_err(ctx->dev, "Request firmware failed %d\n", ret); goto sst_load_base_firmware_failed; } - ret = sst_bxt_prepare_fw(ctx, fw->data, fw->size); + /* check for extended manifest */ + if (ctx->fw == NULL) + goto sst_load_base_firmware_failed; + + ret = snd_skl_parse_uuids(ctx, BXT_ADSP_FW_BIN_HDR_OFFSET); + if (ret < 0) + goto sst_load_base_firmware_failed; + + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + skl_dsp_strip_extended_manifest(&stripped_fw); + + ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); /* Retry Enabling core and ROM load. Retry seemed to help */ if (ret < 0) { - ret = sst_bxt_prepare_fw(ctx, fw->data, fw->size); + ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); if (ret < 0) { + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); goto sst_load_base_firmware_failed; } @@ -159,83 +204,135 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); } else { dev_dbg(ctx->dev, "Firmware download successful\n"); ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); if (ret == 0) { dev_err(ctx->dev, "DSP boot fail, FW Ready timeout\n"); - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); ret = -EIO; } else { - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); ret = 0; + skl->fw_loaded = true; } } sst_load_base_firmware_failed: - release_firmware(fw); + release_firmware(ctx->fw); return ret; } -static int bxt_set_dsp_D0(struct sst_dsp *ctx) +static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) { struct skl_sst *skl = ctx->thread_context; int ret; + struct skl_ipc_dxstate_info dx; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - skl->boot_complete = false; - - ret = skl_dsp_enable_core(ctx); - if (ret < 0) { - dev_err(ctx->dev, "enable dsp core failed ret: %d\n", ret); + if (skl->fw_loaded == false) { + skl->boot_complete = false; + ret = bxt_load_base_firmware(ctx); + if (ret < 0) + dev_err(ctx->dev, "reload fw failed: %d\n", ret); return ret; } - /* enable interrupt */ - skl_ipc_int_enable(ctx); - skl_ipc_op_int_enable(ctx); + /* If core 0 is being turned on, turn on core 1 as well */ + if (core_id == SKL_DSP_CORE0_ID) + ret = skl_dsp_core_power_up(ctx, core_mask | + SKL_DSP_CORE_MASK(1)); + else + ret = skl_dsp_core_power_up(ctx, core_mask); + + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + + /* + * Enable interrupt after SPA is set and before + * DSP is unstalled + */ + skl_ipc_int_enable(ctx); + skl_ipc_op_int_enable(ctx); + skl->boot_complete = false; + } - ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, - msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); - if (ret == 0) { - dev_err(ctx->dev, "ipc: error DSP boot timeout\n"); - dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", - sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), - sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - return -EIO; + ret = skl_dsp_start_core(ctx, core_mask); + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + ret = wait_event_timeout(skl->boot_wait, + skl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + + /* If core 1 was turned on for booting core 0, turn it off */ + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret == 0) { + dev_err(ctx->dev, "%s: DSP boot timeout\n", __func__); + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + dev_err(ctx->dev, "Failed to set core0 to D0 state\n"); + ret = -EIO; + goto err; + } + } + + /* Tell FW if additional core in now On */ + + if (core_id != SKL_DSP_CORE0_ID) { + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "IPC set_dx for core %d fail: %d\n", + core_id, ret); + goto err; + } } - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + skl->cores.state[core_id] = SKL_DSP_RUNNING; return 0; +err: + if (core_id == SKL_DSP_CORE0_ID) + core_mask |= SKL_DSP_CORE_MASK(1); + skl_dsp_disable_core(ctx, core_mask); + + return ret; } -static int bxt_set_dsp_D3(struct sst_dsp *ctx) +static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) { + int ret; struct skl_ipc_dxstate_info dx; struct skl_sst *skl = ctx->thread_context; - int ret = 0; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - if (!is_skl_dsp_running(ctx)) - return ret; - - dx.core_mask = SKL_DSP_CORE0_MASK; + dx.core_mask = core_mask; dx.dx_mask = SKL_IPC_D3_MASK; - ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, - SKL_BASE_FW_MODULE_ID, &dx); - if (ret < 0) { - dev_err(ctx->dev, "Failed to set DSP to D3 state: %d\n", ret); - return ret; - } + dev_dbg(ctx->dev, "core mask=%x dx_mask=%x\n", + dx.core_mask, dx.dx_mask); + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) + dev_err(ctx->dev, + "Failed to set DSP to D3:core id = %d;Continue reset\n", + core_id); - ret = skl_dsp_disable_core(ctx); + ret = skl_dsp_disable_core(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "disbale dsp core failed: %d\n", ret); - ret = -EIO; + dev_err(ctx->dev, "Failed to disable core %d", ret); + return ret; } - - skl_dsp_set_state_locked(ctx, SKL_DSP_RESET); + skl->cores.state[core_id] = SKL_DSP_RESET; return 0; } @@ -274,6 +371,7 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, skl->dev = dev; skl_dev.thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); if (!skl->dsp) { @@ -296,6 +394,7 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, if (ret) return ret; + skl->cores.count = 2; skl->boot_complete = false; init_waitqueue_head(&skl->boot_wait); @@ -305,6 +404,8 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, return ret; } + skl_dsp_init_core_state(sst); + if (dsp) *dsp = skl; @@ -315,6 +416,7 @@ EXPORT_SYMBOL_GPL(bxt_sst_dsp_init); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { + skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index 226db84ba..44ab595ce 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -206,6 +206,12 @@ static const struct skl_dsp_ops dsp_ops[] = { .cleanup = skl_sst_dsp_cleanup }, { + .id = 0x9d71, + .loader_ops = skl_get_loader_ops, + .init = skl_sst_dsp_init, + .cleanup = skl_sst_dsp_cleanup + }, + { .id = 0x5a98, .loader_ops = bxt_get_loader_ops, .init = bxt_sst_dsp_init, @@ -730,7 +736,7 @@ static int skl_set_module_format(struct skl_sst *ctx, dev_dbg(ctx->dev, "Module type=%d config size: %d bytes\n", module_config->id.module_id, param_size); - print_hex_dump(KERN_DEBUG, "Module params:", DUMP_PREFIX_OFFSET, 8, 4, + print_hex_dump_debug("Module params:", DUMP_PREFIX_OFFSET, 8, 4, *param_data, param_size, false); return 0; } @@ -1046,7 +1052,7 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); - /* If pipe is not started, do not try to stop the pipe in FW. */ + /* If pipe is started, do stop the pipe in FW. */ if (pipe->state > SKL_PIPE_STARTED) { ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); if (ret < 0) { @@ -1055,18 +1061,20 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) } pipe->state = SKL_PIPE_PAUSED; - } else { - /* If pipe was not created in FW, do not try to delete it */ - if (pipe->state < SKL_PIPE_CREATED) - return 0; + } - ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id); - if (ret < 0) - dev_err(ctx->dev, "Failed to delete pipeline\n"); + /* If pipe was not created in FW, do not try to delete it */ + if (pipe->state < SKL_PIPE_CREATED) + return 0; - pipe->state = SKL_PIPE_INVALID; + ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id); + if (ret < 0) { + dev_err(ctx->dev, "Failed to delete pipeline\n"); + return ret; } + pipe->state = SKL_PIPE_INVALID; + return ret; } @@ -1125,7 +1133,30 @@ int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) return ret; } - pipe->state = SKL_PIPE_CREATED; + pipe->state = SKL_PIPE_PAUSED; + + return 0; +} + +/* + * Reset the pipeline by sending set pipe state IPC this will reset the DMA + * from the DSP side + */ +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + /* If pipe was not created in FW, do not try to pause or delete */ + if (pipe->state < SKL_PIPE_PAUSED) + return 0; + + ret = skl_set_pipe_state(ctx, pipe, PPL_RESET); + if (ret < 0) { + dev_dbg(ctx->dev, "Failed to reset pipe ret=%d\n", ret); + return ret; + } + + pipe->state = SKL_PIPE_RESET; return 0; } diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index c54f54f38..46ad6bf0c 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -17,6 +17,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * */ +#include <linux/pci.h> #include "skl.h" /* Unique identification for getting NHLT blobs */ @@ -149,6 +150,45 @@ struct nhlt_specific_cfg return NULL; } +int skl_get_dmic_geo(struct skl *skl) +{ + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + struct nhlt_endpoint *epnt; + struct nhlt_dmic_array_config *cfg; + struct device *dev = &skl->pci->dev; + unsigned int dmic_geo = 0; + u8 j; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + + for (j = 0; j < nhlt->endpoint_count; j++) { + if (epnt->linktype == NHLT_LINK_DMIC) { + cfg = (struct nhlt_dmic_array_config *) + (epnt->config.caps); + switch (cfg->array_type) { + case NHLT_MIC_ARRAY_2CH_SMALL: + case NHLT_MIC_ARRAY_2CH_BIG: + dmic_geo |= MIC_ARRAY_2CH; + break; + + case NHLT_MIC_ARRAY_4CH_1ST_GEOM: + case NHLT_MIC_ARRAY_4CH_L_SHAPED: + case NHLT_MIC_ARRAY_4CH_2ND_GEOM: + dmic_geo |= MIC_ARRAY_4CH; + break; + + default: + dev_warn(dev, "undefined DMIC array_type 0x%0x\n", + cfg->array_type); + + } + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return dmic_geo; +} + static void skl_nhlt_trim_space(struct skl *skl) { char *s = skl->tplg_name; diff --git a/sound/soc/intel/skylake/skl-nhlt.h b/sound/soc/intel/skylake/skl-nhlt.h index 3769f9fef..116534e7b 100644 --- a/sound/soc/intel/skylake/skl-nhlt.h +++ b/sound/soc/intel/skylake/skl-nhlt.h @@ -103,4 +103,26 @@ struct nhlt_resource_desc { u64 length; } __packed; +#define MIC_ARRAY_2CH 2 +#define MIC_ARRAY_4CH 4 + +struct nhlt_tdm_config { + u8 virtual_slot; + u8 config_type; +} __packed; + +struct nhlt_dmic_array_config { + struct nhlt_tdm_config tdm_config; + u8 array_type; +} __packed; + +enum { + NHLT_MIC_ARRAY_2CH_SMALL = 0xa, + NHLT_MIC_ARRAY_2CH_BIG = 0xb, + NHLT_MIC_ARRAY_4CH_1ST_GEOM = 0xc, + NHLT_MIC_ARRAY_4CH_L_SHAPED = 0xd, + NHLT_MIC_ARRAY_4CH_2ND_GEOM = 0xe, + NHLT_MIC_ARRAY_VENDOR_DEFINED = 0xf, +}; + #endif diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index 7c81b3174..6e05bf862 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -227,16 +227,25 @@ static int skl_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct skl *skl = get_skl_ctx(dai->dev); unsigned int format_val; int err; + struct skl_module_cfg *mconfig; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + format_val = skl_get_format(substream, dai); dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d\n", hdac_stream(stream)->stream_tag, format_val); snd_hdac_stream_reset(hdac_stream(stream)); + /* In case of XRUN recovery, reset the FW pipe to clean state */ + if (mconfig && (substream->runtime->status->state == + SNDRV_PCM_STATE_XRUN)) + skl_reset_pipe(skl->skl_sst, mconfig->pipe); + err = snd_hdac_stream_set_params(hdac_stream(stream), format_val); if (err < 0) return err; @@ -521,6 +530,8 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, struct skl_dma_params *dma_params; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct hdac_ext_link *link; + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig = NULL; dma_params = (struct skl_dma_params *) snd_soc_dai_get_dma_data(codec_dai, substream); @@ -535,6 +546,12 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, snd_hdac_ext_link_stream_reset(link_dev); + /* In case of XRUN recovery, reset the FW pipe to clean state */ + mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); + if (mconfig && (substream->runtime->status->state == + SNDRV_PCM_STATE_XRUN)) + skl_reset_pipe(skl->skl_sst, mconfig->pipe); + snd_hdac_ext_link_stream_setup(link_dev, format_val); snd_hdac_ext_link_set_stream_id(link, hdac_stream(link_dev)->stream_tag); @@ -1009,51 +1026,11 @@ static int skl_platform_pcm_trigger(struct snd_pcm_substream *substream, return 0; } -/* calculate runtime delay from LPIB */ -static int skl_get_delay_from_lpib(struct hdac_ext_bus *ebus, - struct hdac_ext_stream *sstream, - unsigned int pos) -{ - struct hdac_bus *bus = ebus_to_hbus(ebus); - struct hdac_stream *hstream = hdac_stream(sstream); - struct snd_pcm_substream *substream = hstream->substream; - int stream = substream->stream; - unsigned int lpib_pos = snd_hdac_stream_get_pos_lpib(hstream); - int delay; - - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - delay = pos - lpib_pos; - else - delay = lpib_pos - pos; - - if (delay < 0) { - if (delay >= hstream->delay_negative_threshold) - delay = 0; - else - delay += hstream->bufsize; - } - - if (hstream->bufsize == delay) - delay = 0; - - if (delay >= hstream->period_bytes) { - dev_info(bus->dev, - "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", - delay, hstream->period_bytes); - delay = 0; - } - - return bytes_to_frames(substream->runtime, delay); -} - -static unsigned int skl_get_position(struct hdac_ext_stream *hstream, - int codec_delay) +static snd_pcm_uframes_t skl_platform_pcm_pointer + (struct snd_pcm_substream *substream) { - struct hdac_stream *hstr = hdac_stream(hstream); - struct snd_pcm_substream *substream = hstr->substream; - struct hdac_ext_bus *ebus; + struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream); unsigned int pos; - int delay; /* use the position buffer as default */ pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); @@ -1061,23 +1038,7 @@ static unsigned int skl_get_position(struct hdac_ext_stream *hstream, if (pos >= hdac_stream(hstream)->bufsize) pos = 0; - if (substream->runtime) { - ebus = get_bus_ctx(substream); - delay = skl_get_delay_from_lpib(ebus, hstream, pos) - + codec_delay; - substream->runtime->delay += delay; - } - - return pos; -} - -static snd_pcm_uframes_t skl_platform_pcm_pointer - (struct snd_pcm_substream *substream) -{ - struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream); - - return bytes_to_frames(substream->runtime, - skl_get_position(hstream, 0)); + return bytes_to_frames(substream->runtime, pos); } static u64 skl_adjust_codec_delay(struct snd_pcm_substream *substream, @@ -1180,9 +1141,17 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) static int skl_platform_soc_probe(struct snd_soc_platform *platform) { struct hdac_ext_bus *ebus = dev_get_drvdata(platform->dev); + struct skl *skl = ebus_to_skl(ebus); + int ret; - if (ebus->ppcap) - return skl_tplg_init(platform, ebus); + if (ebus->ppcap) { + ret = skl_tplg_init(platform, ebus); + if (ret < 0) { + dev_err(platform->dev, "Failed to init topology!\n"); + return ret; + } + skl->platform = platform; + } return 0; } diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c index 13c19855e..c3deefab6 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.c +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -34,33 +34,84 @@ void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state) mutex_unlock(&ctx->mutex); } -static int skl_dsp_core_set_reset_state(struct sst_dsp *ctx) +/* + * Initialize core power state and usage count. To be called after + * successful first boot. Hence core 0 will be running and other cores + * will be reset + */ +void skl_dsp_init_core_state(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + int i; + + skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING; + skl->cores.usage_count[SKL_DSP_CORE0_ID] = 1; + + for (i = SKL_DSP_CORE0_ID + 1; i < SKL_DSP_CORES_MAX; i++) { + skl->cores.state[i] = SKL_DSP_RESET; + skl->cores.usage_count[i] = 0; + } +} + +/* Get the mask for all enabled cores */ +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask, en_cores_mask; + u32 val; + + core_mask = SKL_DSP_CORES_MASK(skl->cores.count); + + val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); + + /* Cores having CPA bit set */ + en_cores_mask = (val & SKL_ADSPCS_CPA_MASK(core_mask)) >> + SKL_ADSPCS_CPA_SHIFT; + + /* And cores having CRST bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CRST_MASK(core_mask)) >> + SKL_ADSPCS_CRST_SHIFT; + + /* And cores having CSTALL bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CSTALL_MASK(core_mask)) >> + SKL_ADSPCS_CSTALL_SHIFT; + en_cores_mask &= core_mask; + + dev_dbg(ctx->dev, "DSP enabled cores mask = %x\n", en_cores_mask); + + return en_cores_mask; +} + +static int +skl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, - SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK, - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)); + SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask)); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK), + SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask), SKL_DSP_RESET_TO, "Set reset"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) != - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) { - dev_err(ctx->dev, "Set reset state failed\n"); + SKL_ADSPCS_CRST_MASK(core_mask)) != + SKL_ADSPCS_CRST_MASK(core_mask)) { + dev_err(ctx->dev, "Set reset state failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx) +int skl_dsp_core_unset_reset_state( + struct sst_dsp *ctx, unsigned int core_mask) { int ret; @@ -68,152 +119,160 @@ static int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx) /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, 0); + SKL_ADSPCS_CRST_MASK(core_mask), 0); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, + SKL_ADSPCS_CRST_MASK(core_mask), 0, SKL_DSP_RESET_TO, "Unset reset"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) != 0) { - dev_err(ctx->dev, "Unset reset state failed\n"); + SKL_ADSPCS_CRST_MASK(core_mask)) != 0) { + dev_err(ctx->dev, "Unset reset state failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static bool is_skl_dsp_core_enable(struct sst_dsp *ctx) +static bool +is_skl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask) { int val; bool is_enable; val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); - is_enable = ((val & SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) && - (val & SKL_ADSPCS_SPA(SKL_DSP_CORES_MASK)) && - !(val & SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) && - !(val & SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK))); + is_enable = ((val & SKL_ADSPCS_CPA_MASK(core_mask)) && + (val & SKL_ADSPCS_SPA_MASK(core_mask)) && + !(val & SKL_ADSPCS_CRST_MASK(core_mask)) && + !(val & SKL_ADSPCS_CSTALL_MASK(core_mask))); + + dev_dbg(ctx->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); - dev_dbg(ctx->dev, "DSP core is enabled=%d\n", is_enable); return is_enable; } -static int skl_dsp_reset_core(struct sst_dsp *ctx) +static int skl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask) { /* stall core */ - sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK)); + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), + SKL_ADSPCS_CSTALL_MASK(core_mask)); /* set reset state */ - return skl_dsp_core_set_reset_state(ctx); + return skl_dsp_core_set_reset_state(ctx, core_mask); } -static int skl_dsp_start_core(struct sst_dsp *ctx) +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* unset reset state */ - ret = skl_dsp_core_unset_reset_state(ctx); - if (ret < 0) { - dev_dbg(ctx->dev, "dsp unset reset fails\n"); + ret = skl_dsp_core_unset_reset_state(ctx, core_mask); + if (ret < 0) return ret; - } /* run core */ - dev_dbg(ctx->dev, "run core...\n"); - sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - ~SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK)); - - if (!is_skl_dsp_core_enable(ctx)) { - skl_dsp_reset_core(ctx); - dev_err(ctx->dev, "DSP core enable failed\n"); + dev_dbg(ctx->dev, "unstall/run core: core_mask = %x\n", core_mask); + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), 0); + + if (!is_skl_dsp_core_enable(ctx, core_mask)) { + skl_dsp_reset_core(ctx, core_mask); + dev_err(ctx->dev, "DSP start core failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_power_up(struct sst_dsp *ctx) +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_SPA_MASK, SKL_ADSPCS_SPA(SKL_DSP_CORES_MASK)); + SKL_ADSPCS_SPA_MASK(core_mask), + SKL_ADSPCS_SPA_MASK(core_mask)); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CPA_MASK, - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK), + SKL_ADSPCS_CPA_MASK(core_mask), + SKL_ADSPCS_CPA_MASK(core_mask), SKL_DSP_PU_TO, "Power up"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) != - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) { - dev_err(ctx->dev, "DSP core power up failed\n"); + SKL_ADSPCS_CPA_MASK(core_mask)) != + SKL_ADSPCS_CPA_MASK(core_mask)) { + dev_err(ctx->dev, "DSP core power up failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_power_down(struct sst_dsp *ctx) +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask) { /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_SPA_MASK, 0); + SKL_ADSPCS_SPA_MASK(core_mask), 0); /* poll with timeout to check if operation successful */ return sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CPA_MASK, + SKL_ADSPCS_CPA_MASK(core_mask), 0, SKL_DSP_PD_TO, "Power down"); } -int skl_dsp_enable_core(struct sst_dsp *ctx) +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* power up */ - ret = skl_dsp_core_power_up(ctx); + ret = skl_dsp_core_power_up(ctx, core_mask); if (ret < 0) { - dev_dbg(ctx->dev, "dsp core power up failed\n"); + dev_err(ctx->dev, "dsp core power up failed: core_mask %x\n", + core_mask); return ret; } - return skl_dsp_start_core(ctx); + return skl_dsp_start_core(ctx, core_mask); } -int skl_dsp_disable_core(struct sst_dsp *ctx) +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; - ret = skl_dsp_reset_core(ctx); + ret = skl_dsp_reset_core(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "dsp core reset failed\n"); + dev_err(ctx->dev, "dsp core reset failed: core_mask %x\n", + core_mask); return ret; } /* power down core*/ - ret = skl_dsp_core_power_down(ctx); + ret = skl_dsp_core_power_down(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "dsp core power down failed\n"); + dev_err(ctx->dev, "dsp core power down fail mask %x: %d\n", + core_mask, ret); return ret; } - if (is_skl_dsp_core_enable(ctx)) { - dev_err(ctx->dev, "DSP core disable failed\n"); + if (is_skl_dsp_core_enable(ctx, core_mask)) { + dev_err(ctx->dev, "dsp core disable fail mask %x: %d\n", + core_mask, ret); ret = -EIO; } @@ -224,28 +283,25 @@ int skl_dsp_boot(struct sst_dsp *ctx) { int ret; - if (is_skl_dsp_core_enable(ctx)) { - dev_dbg(ctx->dev, "dsp core is already enabled, so reset the dap core\n"); - ret = skl_dsp_reset_core(ctx); + if (is_skl_dsp_core_enable(ctx, SKL_DSP_CORE0_MASK)) { + ret = skl_dsp_reset_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp reset failed\n"); + dev_err(ctx->dev, "dsp core0 reset fail: %d\n", ret); return ret; } - ret = skl_dsp_start_core(ctx); + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp start failed\n"); + dev_err(ctx->dev, "dsp core0 start fail: %d\n", ret); return ret; } } else { - dev_dbg(ctx->dev, "disable and enable to make sure DSP is invalid state\n"); - ret = skl_dsp_disable_core(ctx); - + ret = skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp disable core failes\n"); + dev_err(ctx->dev, "dsp core0 disable fail: %d\n", ret); return ret; } - ret = skl_dsp_enable_core(ctx); + ret = skl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK); } return ret; @@ -281,16 +337,74 @@ irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id) return result; } +/* + * skl_dsp_get_core/skl_dsp_put_core will be called inside DAPM context + * within the dapm mutex. Hence no separate lock is used. + */ +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + if (skl->cores.state[core_id] == SKL_DSP_RESET) { + ret = ctx->fw_ops.set_state_D0(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to get core%d\n", core_id); + return ret; + } + } + + skl->cores.usage_count[core_id]++; + + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_get_core); + +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + if (--skl->cores.usage_count[core_id] == 0) { + ret = ctx->fw_ops.set_state_D3(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to put core %d: %d\n", + core_id, ret); + skl->cores.usage_count[core_id]++; + } + } + + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_put_core); int skl_dsp_wake(struct sst_dsp *ctx) { - return ctx->fw_ops.set_state_D0(ctx); + return skl_dsp_get_core(ctx, SKL_DSP_CORE0_ID); } EXPORT_SYMBOL_GPL(skl_dsp_wake); int skl_dsp_sleep(struct sst_dsp *ctx) { - return ctx->fw_ops.set_state_D3(ctx); + return skl_dsp_put_core(ctx, SKL_DSP_CORE0_ID); } EXPORT_SYMBOL_GPL(skl_dsp_sleep); @@ -337,9 +451,7 @@ void skl_dsp_free(struct sst_dsp *dsp) free_irq(dsp->irq, dsp); skl_ipc_op_int_disable(dsp); - skl_ipc_int_disable(dsp); - - skl_dsp_disable_core(dsp); + skl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK); } EXPORT_SYMBOL_GPL(skl_dsp_free); diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h index deabe7308..0f8629ef7 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.h +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -19,6 +19,7 @@ #include <linux/interrupt.h> #include <sound/memalloc.h> #include "skl-sst-cldma.h" +#include "skl-tplg-interface.h" struct sst_dsp; struct skl_sst; @@ -76,35 +77,53 @@ struct sst_dsp_device; #define SKL_ADSPIC_IPC 1 #define SKL_ADSPIS_IPC 1 +/* Core ID of core0 */ +#define SKL_DSP_CORE0_ID 0 + +/* Mask for a given core index, c = 0.. number of supported cores - 1 */ +#define SKL_DSP_CORE_MASK(c) BIT(c) + +/* + * Core 0 mask = SKL_DSP_CORE_MASK(0); Defined separately + * since Core0 is primary core and it is used often + */ +#define SKL_DSP_CORE0_MASK BIT(0) + +/* + * Mask for a given number of cores + * nc = number of supported cores + */ +#define SKL_DSP_CORES_MASK(nc) GENMASK((nc - 1), 0) + /* ADSPCS - Audio DSP Control & Status */ -#define SKL_DSP_CORES 1 -#define SKL_DSP_CORE0_MASK 1 -#define SKL_DSP_CORES_MASK ((1 << SKL_DSP_CORES) - 1) - -/* Core Reset - asserted high */ -#define SKL_ADSPCS_CRST_SHIFT 0 -#define SKL_ADSPCS_CRST_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_CRST_SHIFT) -#define SKL_ADSPCS_CRST(x) ((x << SKL_ADSPCS_CRST_SHIFT) & SKL_ADSPCS_CRST_MASK) - -/* Core run/stall - when set to '1' core is stalled */ -#define SKL_ADSPCS_CSTALL_SHIFT 8 -#define SKL_ADSPCS_CSTALL_MASK (SKL_DSP_CORES_MASK << \ - SKL_ADSPCS_CSTALL_SHIFT) -#define SKL_ADSPCS_CSTALL(x) ((x << SKL_ADSPCS_CSTALL_SHIFT) & \ - SKL_ADSPCS_CSTALL_MASK) - -/* Set Power Active - when set to '1' turn cores on */ -#define SKL_ADSPCS_SPA_SHIFT 16 -#define SKL_ADSPCS_SPA_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_SPA_SHIFT) -#define SKL_ADSPCS_SPA(x) ((x << SKL_ADSPCS_SPA_SHIFT) & SKL_ADSPCS_SPA_MASK) - -/* Current Power Active - power status of cores, set by hardware */ -#define SKL_ADSPCS_CPA_SHIFT 24 -#define SKL_ADSPCS_CPA_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_CPA_SHIFT) -#define SKL_ADSPCS_CPA(x) ((x << SKL_ADSPCS_CPA_SHIFT) & SKL_ADSPCS_CPA_MASK) - -#define SST_DSP_POWER_D0 0x0 /* full On */ -#define SST_DSP_POWER_D3 0x3 /* Off */ + +/* + * Core Reset - asserted high + * CRST Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CRST_SHIFT 0 +#define SKL_ADSPCS_CRST_MASK(cm) ((cm) << SKL_ADSPCS_CRST_SHIFT) + +/* + * Core run/stall - when set to '1' core is stalled + * CSTALL Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CSTALL_SHIFT 8 +#define SKL_ADSPCS_CSTALL_MASK(cm) ((cm) << SKL_ADSPCS_CSTALL_SHIFT) + +/* + * Set Power Active - when set to '1' turn cores on + * SPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_SPA_SHIFT 16 +#define SKL_ADSPCS_SPA_MASK(cm) ((cm) << SKL_ADSPCS_SPA_SHIFT) + +/* + * Current Power Active - power status of cores, set by hardware + * CPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CPA_SHIFT 24 +#define SKL_ADSPCS_CPA_MASK(cm) ((cm) << SKL_ADSPCS_CPA_SHIFT) enum skl_dsp_states { SKL_DSP_RUNNING = 1, @@ -115,8 +134,8 @@ struct skl_dsp_fw_ops { int (*load_fw)(struct sst_dsp *ctx); /* FW module parser/loader */ int (*parse_fw)(struct sst_dsp *ctx); - int (*set_state_D0)(struct sst_dsp *ctx); - int (*set_state_D3)(struct sst_dsp *ctx); + int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id); + int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id); unsigned int (*get_fw_errcode)(struct sst_dsp *ctx); int (*load_mod)(struct sst_dsp *ctx, u16 mod_id, u8 *mod_name); int (*unload_mod)(struct sst_dsp *ctx, u16 mod_id); @@ -157,14 +176,26 @@ int skl_cldma_prepare(struct sst_dsp *ctx); void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state); struct sst_dsp *skl_dsp_ctx_init(struct device *dev, struct sst_dsp_device *sst_dev, int irq); -int skl_dsp_enable_core(struct sst_dsp *ctx); -int skl_dsp_disable_core(struct sst_dsp *ctx); bool is_skl_dsp_running(struct sst_dsp *ctx); + +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx); +void skl_dsp_init_core_state(struct sst_dsp *ctx); +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx, + unsigned int core_mask); +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask); + irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id); int skl_dsp_wake(struct sst_dsp *ctx); int skl_dsp_sleep(struct sst_dsp *ctx); void skl_dsp_free(struct sst_dsp *dsp); +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id); +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id); + int skl_dsp_boot(struct sst_dsp *ctx); int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, @@ -175,4 +206,11 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); +int snd_skl_get_module_info(struct skl_sst *ctx, u8 *uuid, + struct skl_dfw_module *dfw_config); +int snd_skl_parse_uuids(struct sst_dsp *ctx, unsigned int offset); +void skl_freeup_uuid_list(struct skl_sst *ctx); + +int skl_dsp_strip_extended_manifest(struct firmware *fw); + #endif /*__SKL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c index 543460293..96f2f6889 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.c +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -363,7 +363,7 @@ static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, /* first process the header */ switch (reply) { case IPC_GLB_REPLY_SUCCESS: - dev_info(ipc->dev, "ipc FW reply %x: success\n", header.primary); + dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary); /* copy the rx data from the mailbox */ sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size); break; @@ -692,7 +692,7 @@ int skl_ipc_init_instance(struct sst_generic_ipc *ipc, /* param_block_size must be in dwords */ u16 param_block_size = msg->param_data_size / sizeof(u32); - print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, + print_hex_dump_debug("Param data:", DUMP_PREFIX_NONE, 16, 4, buffer, param_block_size, false); header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h index d59d1ba62..2e3d4e80e 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.h +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -45,6 +45,14 @@ struct skl_ipc_header { u32 extension; }; +#define SKL_DSP_CORES_MAX 2 + +struct skl_dsp_cores { + unsigned int count; + enum skl_dsp_states state[SKL_DSP_CORES_MAX]; + int usage_count[SKL_DSP_CORES_MAX]; +}; + struct skl_sst { struct device *dev; struct sst_dsp *dsp; @@ -60,6 +68,15 @@ struct skl_sst { void (*enable_miscbdcge)(struct device *dev, bool enable); /*Is CGCTL.MISCBDCGE disabled*/ bool miscbdcg_disabled; + + /* Populate module information */ + struct list_head uuid_list; + + /* Is firmware loaded */ + bool fw_loaded; + + /* multi-core */ + struct skl_dsp_cores cores; }; struct skl_ipc_init_instance_msg { @@ -136,5 +153,6 @@ void skl_ipc_int_disable(struct sst_dsp *dsp); bool skl_ipc_int_status(struct sst_dsp *dsp); void skl_ipc_free(struct sst_generic_ipc *ipc); int skl_ipc_init(struct device *dev, struct skl_sst *skl); +void skl_clear_module_cnt(struct sst_dsp *ctx); #endif /* __SKL_IPC_H */ diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c new file mode 100644 index 000000000..ddcb52a51 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -0,0 +1,261 @@ +/* + * skl-sst-utils.c - SKL sst utils functions + * + * Copyright (C) 2016 Intel Corp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as 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. + */ + +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include "skl-sst-dsp.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl-sst-ipc.h" + + +#define UUID_STR_SIZE 37 +#define DEFAULT_HASH_SHA256_LEN 32 + +/* FW Extended Manifest Header id = $AE1 */ +#define SKL_EXT_MANIFEST_HEADER_MAGIC 0x31454124 + +struct skl_dfw_module_mod { + char name[100]; + struct skl_dfw_module skl_dfw_mod; +}; + +struct UUID { + u8 id[16]; +}; + +union seg_flags { + u32 ul; + struct { + u32 contents : 1; + u32 alloc : 1; + u32 load : 1; + u32 read_only : 1; + u32 code : 1; + u32 data : 1; + u32 _rsvd0 : 2; + u32 type : 4; + u32 _rsvd1 : 4; + u32 length : 16; + } r; +} __packed; + +struct segment_desc { + union seg_flags flags; + u32 v_base_addr; + u32 file_offset; +}; + +struct module_type { + u32 load_type : 4; + u32 auto_start : 1; + u32 domain_ll : 1; + u32 domain_dp : 1; + u32 rsvd : 25; +} __packed; + +struct adsp_module_entry { + u32 struct_id; + u8 name[8]; + struct UUID uuid; + struct module_type type; + u8 hash1[DEFAULT_HASH_SHA256_LEN]; + u32 entry_point; + u16 cfg_offset; + u16 cfg_count; + u32 affinity_mask; + u16 instance_max_count; + u16 instance_bss_size; + struct segment_desc segments[3]; +} __packed; + +struct adsp_fw_hdr { + u32 id; + u32 len; + u8 name[8]; + u32 preload_page_count; + u32 fw_image_flags; + u32 feature_mask; + u16 major; + u16 minor; + u16 hotfix; + u16 build; + u32 num_modules; + u32 hw_buf_base; + u32 hw_buf_length; + u32 load_offset; +} __packed; + +struct uuid_module { + uuid_le uuid; + int id; + int is_loadable; + + struct list_head list; +}; + +struct skl_ext_manifest_hdr { + u32 id; + u32 len; + u16 version_major; + u16 version_minor; + u32 entries; +}; + +int snd_skl_get_module_info(struct skl_sst *ctx, u8 *uuid, + struct skl_dfw_module *dfw_config) +{ + struct uuid_module *module; + uuid_le *uuid_mod; + + uuid_mod = (uuid_le *)uuid; + + if (list_empty(&ctx->uuid_list)) { + dev_err(ctx->dev, "Module list is empty\n"); + return -EINVAL; + } + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + dfw_config->module_id = module->id; + dfw_config->is_loadable = module->is_loadable; + + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_skl_get_module_info); + +/* + * Parse the firmware binary to get the UUID, module id + * and loadable flags + */ +int snd_skl_parse_uuids(struct sst_dsp *ctx, unsigned int offset) +{ + struct adsp_fw_hdr *adsp_hdr; + struct adsp_module_entry *mod_entry; + int i, num_entry; + uuid_le *uuid_bin; + const char *buf; + struct skl_sst *skl = ctx->thread_context; + struct uuid_module *module; + struct firmware stripped_fw; + unsigned int safe_file; + + /* Get the FW pointer to derive ADSP header */ + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + + buf = stripped_fw.data; + + /* check if we have enough space in file to move to header */ + safe_file = sizeof(*adsp_hdr) + offset; + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No space for hdr\n"); + return -EINVAL; + } + + adsp_hdr = (struct adsp_fw_hdr *)(buf + offset); + + /* check 1st module entry is in file */ + safe_file += adsp_hdr->len + sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No module entry\n"); + return -EINVAL; + } + + mod_entry = (struct adsp_module_entry *) + (buf + offset + adsp_hdr->len); + + num_entry = adsp_hdr->num_modules; + + /* check all entries are in file */ + safe_file += num_entry * sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No modules\n"); + return -EINVAL; + } + + + /* + * Read the UUID(GUID) from FW Manifest. + * + * The 16 byte UUID format is: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX + * Populate the UUID table to store module_id and loadable flags + * for the module. + */ + + for (i = 0; i < num_entry; i++, mod_entry++) { + module = kzalloc(sizeof(*module), GFP_KERNEL); + if (!module) + return -ENOMEM; + + uuid_bin = (uuid_le *)mod_entry->uuid.id; + memcpy(&module->uuid, uuid_bin, sizeof(module->uuid)); + + module->id = i; + module->is_loadable = mod_entry->type.load_type; + + list_add_tail(&module->list, &skl->uuid_list); + + dev_dbg(ctx->dev, + "Adding uuid :%pUL mod id: %d Loadable: %d\n", + &module->uuid, module->id, module->is_loadable); + } + + return 0; +} + +void skl_freeup_uuid_list(struct skl_sst *ctx) +{ + struct uuid_module *uuid, *_uuid; + + list_for_each_entry_safe(uuid, _uuid, &ctx->uuid_list, list) { + list_del(&uuid->list); + kfree(uuid); + } +} + +/* + * some firmware binary contains some extended manifest. This needs + * to be stripped in that case before we load and use that image. + * + * Get the module id for the module by checking + * the table for the UUID for the module + */ +int skl_dsp_strip_extended_manifest(struct firmware *fw) +{ + struct skl_ext_manifest_hdr *hdr; + + /* check if fw file is greater than header we are looking */ + if (fw->size < sizeof(hdr)) { + pr_err("%s: Firmware file small, no hdr\n", __func__); + return -EINVAL; + } + + hdr = (struct skl_ext_manifest_hdr *)fw->data; + + if (hdr->id == SKL_EXT_MANIFEST_HEADER_MAGIC) { + fw->size -= hdr->len; + fw->data += hdr->len; + } + + return 0; +} diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c index 463e72a35..4a3c9f604 100644 --- a/sound/soc/intel/skylake/skl-sst.c +++ b/sound/soc/intel/skylake/skl-sst.c @@ -68,10 +68,13 @@ static int skl_transfer_firmware(struct sst_dsp *ctx, return ret; } +#define SKL_ADSP_FW_BIN_HDR_OFFSET 0x284 + static int skl_load_base_firmware(struct sst_dsp *ctx) { int ret = 0, i; struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; u32 reg; skl->boot_complete = false; @@ -81,11 +84,25 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) ret = reject_firmware(&ctx->fw, ctx->fw_name, ctx->dev); if (ret < 0) { dev_err(ctx->dev, "Request firmware failed %d\n", ret); - skl_dsp_disable_core(ctx); return -EIO; } } + ret = snd_skl_parse_uuids(ctx, SKL_ADSP_FW_BIN_HDR_OFFSET); + if (ret < 0) { + dev_err(ctx->dev, + "UUID parsing err: %d\n", ret); + release_firmware(ctx->fw); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + return ret; + } + + /* check for extended manifest */ + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + ret = skl_dsp_boot(ctx); if (ret < 0) { dev_err(ctx->dev, "Boot dsp core failed ret: %d", ret); @@ -119,7 +136,7 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) goto transfer_firmware_failed; } - ret = skl_transfer_firmware(ctx, ctx->fw->data, ctx->fw->size); + ret = skl_transfer_firmware(ctx, stripped_fw.data, stripped_fw.size); if (ret < 0) { dev_err(ctx->dev, "Transfer firmware failed%d\n", ret); goto transfer_firmware_failed; @@ -133,67 +150,87 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) } dev_dbg(ctx->dev, "Download firmware successful%d\n", ret); - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + skl->fw_loaded = true; } return 0; transfer_firmware_failed: ctx->cl_dev.ops.cl_cleanup_controller(ctx); skl_load_base_firmware_failed: - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); release_firmware(ctx->fw); ctx->fw = NULL; return ret; } -static int skl_set_dsp_D0(struct sst_dsp *ctx) +static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) { int ret; + struct skl_ipc_dxstate_info dx; + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - ret = skl_load_base_firmware(ctx); - if (ret < 0) { - dev_err(ctx->dev, "unable to load firmware\n"); - return ret; + /* If core0 is being turned on, we need to load the FW */ + if (core_id == SKL_DSP_CORE0_ID) { + ret = skl_load_base_firmware(ctx); + if (ret < 0) { + dev_err(ctx->dev, "unable to load firmware\n"); + return ret; + } } - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + /* + * If any core other than core 0 is being moved to D0, enable the + * core and send the set dx IPC for the core. + */ + if (core_id != SKL_DSP_CORE0_ID) { + ret = skl_dsp_enable_core(ctx, core_mask); + if (ret < 0) + return ret; + + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, + SKL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set dsp to D0:core id= %d\n", + core_id); + skl_dsp_disable_core(ctx, core_mask); + } + } + + skl->cores.state[core_id] = SKL_DSP_RUNNING; return ret; } -static int skl_set_dsp_D3(struct sst_dsp *ctx) +static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) { int ret; struct skl_ipc_dxstate_info dx; struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - dev_dbg(ctx->dev, "In %s:\n", __func__); - mutex_lock(&ctx->mutex); - if (!is_skl_dsp_running(ctx)) { - mutex_unlock(&ctx->mutex); - return 0; - } - mutex_unlock(&ctx->mutex); - - dx.core_mask = SKL_DSP_CORE0_MASK; + dx.core_mask = core_mask; dx.dx_mask = SKL_IPC_D3_MASK; + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx); if (ret < 0) - dev_err(ctx->dev, - "D3 request to FW failed, continuing reset: %d", ret); - - /* disable Interrupt */ - ctx->cl_dev.ops.cl_cleanup_controller(ctx); - skl_cldma_int_disable(ctx); - skl_ipc_op_int_disable(ctx); - skl_ipc_int_disable(ctx); - - ret = skl_dsp_disable_core(ctx); - if (ret < 0) { - dev_err(ctx->dev, "disable dsp core failed ret: %d\n", ret); - ret = -EIO; + dev_err(ctx->dev, "set Dx core %d fail: %d\n", core_id, ret); + + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + ctx->cl_dev.ops.cl_cleanup_controller(ctx); + skl_cldma_int_disable(ctx); + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); } - skl_dsp_set_state_locked(ctx, SKL_DSP_RESET); + ret = skl_dsp_disable_core(ctx, core_mask); + if (ret < 0) + return ret; + + skl->cores.state[core_id] = SKL_DSP_RESET; return ret; } @@ -359,6 +396,19 @@ static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) return ret; } +void skl_clear_module_cnt(struct sst_dsp *ctx) +{ + struct skl_module_table *module; + + if (list_empty(&ctx->module_list)) + return; + + list_for_each_entry(module, &ctx->module_list, list) { + module->usage_cnt = 0; + } +} +EXPORT_SYMBOL_GPL(skl_clear_module_cnt); + static void skl_clear_module_table(struct sst_dsp *ctx) { struct skl_module_table *module, *tmp; @@ -408,6 +458,7 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, skl->dev = dev; skl_dev.thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); if (!skl->dsp) { @@ -431,12 +482,16 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, if (ret) return ret; + skl->cores.count = 2; + ret = sst->fw_ops.load_fw(sst); if (ret < 0) { dev_err(dev, "Load base fw failed : %d", ret); goto cleanup; } + skl_dsp_init_core_state(sst); + if (dsp) *dsp = skl; @@ -451,6 +506,7 @@ EXPORT_SYMBOL_GPL(skl_sst_dsp_init); void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { skl_clear_module_table(ctx->dsp); + skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->ops->free(ctx->dsp); if (ctx->boot_complete) { diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index bebbb3f2f..91fa6187d 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -379,43 +379,6 @@ static void skl_tplg_update_module_params(struct snd_soc_dapm_widget *w, } /* - * A pipe can have multiple modules, each of them will be a DAPM widget as - * well. While managing a pipeline we need to get the list of all the - * widgets in a pipelines, so this helper - skl_tplg_get_pipe_widget() helps - * to get the SKL type widgets in that pipeline - */ -static int skl_tplg_alloc_pipe_widget(struct device *dev, - struct snd_soc_dapm_widget *w, struct skl_pipe *pipe) -{ - struct skl_module_cfg *src_module = NULL; - struct snd_soc_dapm_path *p = NULL; - struct skl_pipe_module *p_module = NULL; - - p_module = devm_kzalloc(dev, sizeof(*p_module), GFP_KERNEL); - if (!p_module) - return -ENOMEM; - - p_module->w = w; - list_add_tail(&p_module->node, &pipe->w_list); - - snd_soc_dapm_widget_for_each_sink_path(w, p) { - if ((p->sink->priv == NULL) - && (!is_skl_dsp_widget_type(w))) - continue; - - if ((p->sink->priv != NULL) && p->connect - && is_skl_dsp_widget_type(p->sink)) { - - src_module = p->sink->priv; - if (pipe->ppl_id == src_module->pipe->ppl_id) - skl_tplg_alloc_pipe_widget(dev, - p->sink, pipe); - } - } - return 0; -} - -/* * some modules can have multiple params set from user control and * need to be set after module is initialized. If set_param flag is * set module params will be done after module is initialised. @@ -448,7 +411,7 @@ static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w, if (bc->set_params == SKL_PARAM_SET) { ret = skl_set_module_params(ctx, - (u32 *)bc->params, bc->max, + (u32 *)bc->params, bc->size, bc->param_id, mconfig); if (ret < 0) return ret; @@ -483,7 +446,7 @@ static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w) continue; mconfig->formats_config.caps = (u32 *)&bc->params; - mconfig->formats_config.caps_size = bc->max; + mconfig->formats_config.caps_size = bc->size; break; } @@ -514,8 +477,6 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) if (!skl_is_pipe_mcps_avail(skl, mconfig)) return -ENOMEM; - skl_tplg_alloc_pipe_mcps(skl, mconfig); - if (mconfig->is_loadable && ctx->dsp->fw_ops.load_mod) { ret = ctx->dsp->fw_ops.load_mod(ctx->dsp, mconfig->id.module_id, mconfig->guid); @@ -539,6 +500,7 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) if (ret < 0) return ret; + skl_tplg_alloc_pipe_mcps(skl, mconfig); ret = skl_tplg_set_module_params(w, ctx); if (ret < 0) return ret; @@ -591,9 +553,6 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, if (!skl_is_pipe_mem_avail(skl, mconfig)) return -ENOMEM; - skl_tplg_alloc_pipe_mem(skl, mconfig); - skl_tplg_alloc_pipe_mcps(skl, mconfig); - /* * Create a list of modules for pipe. * This list contains modules from source to sink @@ -602,19 +561,8 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - /* - * we create a w_list of all widgets in that pipe. This list is not - * freed on PMD event as widgets within a pipe are static. This - * saves us cycles to get widgets in pipe every time. - * - * So if we have already initialized all the widgets of a pipeline - * we skip, so check for list_empty and create the list if empty - */ - if (list_empty(&s_pipe->w_list)) { - ret = skl_tplg_alloc_pipe_widget(ctx->dev, w, s_pipe); - if (ret < 0) - return ret; - } + skl_tplg_alloc_pipe_mem(skl, mconfig); + skl_tplg_alloc_pipe_mcps(skl, mconfig); /* Init all pipe modules from source to sink */ ret = skl_tplg_init_pipe_modules(skl, s_pipe); @@ -949,13 +897,17 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, struct skl_pipe *s_pipe = mconfig->pipe; int ret = 0; + if (s_pipe->state == SKL_PIPE_INVALID) + return -EINVAL; + skl_tplg_free_pipe_mcps(skl, mconfig); skl_tplg_free_pipe_mem(skl, mconfig); list_for_each_entry(w_module, &s_pipe->w_list, node) { dst_module = w_module->w->priv; - skl_tplg_free_pipe_mcps(skl, dst_module); + if (mconfig->m_state >= SKL_MODULE_INIT_DONE) + skl_tplg_free_pipe_mcps(skl, dst_module); if (src_module == NULL) { src_module = dst_module; continue; @@ -1102,7 +1054,7 @@ static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol, if (w->power) skl_get_module_params(skl->skl_sst, (u32 *)bc->params, - bc->max, bc->param_id, mconfig); + bc->size, bc->param_id, mconfig); /* decrement size for TLV header */ size -= 2 * sizeof(u32); @@ -1136,6 +1088,10 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, struct skl *skl = get_skl_ctx(w->dapm->dev); if (ac->params) { + if (size > ac->max) + return -EINVAL; + + ac->size = size; /* * if the param_is is of type Vendor, firmware expects actual * parameter id and size from the control. @@ -1151,7 +1107,7 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, if (w->power) return skl_set_module_params(skl->skl_sst, - (u32 *)ac->params, ac->max, + (u32 *)ac->params, ac->size, ac->param_id, mconfig); } @@ -1159,6 +1115,39 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, } /* + * Fill the dma id for host and link. In case of passthrough + * pipeline, this will both host and link in the same + * pipeline, so need to copy the link and host based on dev_type + */ +static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg, + struct skl_pipe_params *params) +{ + struct skl_pipe *pipe = mcfg->pipe; + + if (pipe->passthru) { + switch (mcfg->dev_type) { + case SKL_DEVICE_HDALINK: + pipe->p_params->link_dma_id = params->link_dma_id; + break; + + case SKL_DEVICE_HDAHOST: + pipe->p_params->host_dma_id = params->host_dma_id; + break; + + default: + break; + } + pipe->p_params->s_fmt = params->s_fmt; + pipe->p_params->ch = params->ch; + pipe->p_params->s_freq = params->s_freq; + pipe->p_params->stream = params->stream; + + } else { + memcpy(pipe->p_params, params, sizeof(*params)); + } +} + +/* * The FE params are passed by hw_params of the DAI. * On hw_params, the params are stored in Gateway module of the FE and we * need to calculate the format in DSP module configuration, that @@ -1168,10 +1157,9 @@ int skl_tplg_update_pipe_params(struct device *dev, struct skl_module_cfg *mconfig, struct skl_pipe_params *params) { - struct skl_pipe *pipe = mconfig->pipe; struct skl_module_fmt *format = NULL; - memcpy(pipe->p_params, params, sizeof(*params)); + skl_tplg_fill_dma_id(mconfig, params); if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) format = &mconfig->in_fmt[0]; @@ -1358,12 +1346,11 @@ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, struct skl_module_cfg *mconfig, struct skl_pipe_params *params) { - struct skl_pipe *pipe = mconfig->pipe; struct nhlt_specific_cfg *cfg; struct skl *skl = get_skl_ctx(dai->dev); int link_type = skl_tplg_be_link_type(mconfig->dev_type); - memcpy(pipe->p_params, params, sizeof(*params)); + skl_tplg_fill_dma_id(mconfig, params); if (link_type == NHLT_LINK_HDA) return 0; @@ -1554,6 +1541,55 @@ static void skl_tplg_fill_fmt(struct skl_module_fmt *dst_fmt, } } +static void skl_clear_pin_config(struct snd_soc_platform *platform, + struct snd_soc_dapm_widget *w) +{ + int i; + struct skl_module_cfg *mconfig; + struct skl_pipe *pipe; + + if (!strncmp(w->dapm->component->name, platform->component.name, + strlen(platform->component.name))) { + mconfig = w->priv; + pipe = mconfig->pipe; + for (i = 0; i < mconfig->max_in_queue; i++) { + mconfig->m_in_pin[i].in_use = false; + mconfig->m_in_pin[i].pin_state = SKL_PIN_UNBIND; + } + for (i = 0; i < mconfig->max_out_queue; i++) { + mconfig->m_out_pin[i].in_use = false; + mconfig->m_out_pin[i].pin_state = SKL_PIN_UNBIND; + } + pipe->state = SKL_PIPE_INVALID; + mconfig->m_state = SKL_MODULE_UNINIT; + } +} + +void skl_cleanup_resources(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct snd_soc_platform *soc_platform = skl->platform; + struct snd_soc_dapm_widget *w; + struct snd_soc_card *card; + + if (soc_platform == NULL) + return; + + card = soc_platform->component.card; + if (!card || !card->instantiated) + return; + + skl->resource.mem = 0; + skl->resource.mcps = 0; + + list_for_each_entry(w, &card->widgets, list) { + if (is_skl_dsp_widget_type(w) && (w->priv != NULL)) + skl_clear_pin_config(soc_platform, w); + } + + skl_clear_module_cnt(ctx->dsp); +} + /* * Topology core widget load callback * @@ -1585,6 +1621,10 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt, w->priv = mconfig; memcpy(&mconfig->guid, &dfw_config->uuid, 16); + ret = snd_skl_get_module_info(skl->skl_sst, mconfig->guid, dfw_config); + if (ret < 0) + return ret; + mconfig->id.module_id = dfw_config->module_id; mconfig->id.instance_id = dfw_config->instance_id; mconfig->mcps = dfw_config->max_mcps; @@ -1683,6 +1723,7 @@ static int skl_init_algo_data(struct device *dev, struct soc_bytes_ext *be, ac->max = dfw_ac->max; ac->param_id = dfw_ac->param_id; ac->set_params = dfw_ac->set_params; + ac->size = dfw_ac->max; if (ac->max) { ac->params = (char *) devm_kzalloc(dev, ac->max, GFP_KERNEL); @@ -1733,6 +1774,60 @@ static struct snd_soc_tplg_ops skl_tplg_ops = { .bytes_ext_ops_count = ARRAY_SIZE(skl_tlv_ops), }; +/* + * A pipe can have multiple modules, each of them will be a DAPM widget as + * well. While managing a pipeline we need to get the list of all the + * widgets in a pipelines, so this helper - skl_tplg_create_pipe_widget_list() + * helps to get the SKL type widgets in that pipeline + */ +static int skl_tplg_create_pipe_widget_list(struct snd_soc_platform *platform) +{ + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mcfg = NULL; + struct skl_pipe_module *p_module = NULL; + struct skl_pipe *pipe; + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (is_skl_dsp_widget_type(w) && w->priv != NULL) { + mcfg = w->priv; + pipe = mcfg->pipe; + + p_module = devm_kzalloc(platform->dev, + sizeof(*p_module), GFP_KERNEL); + if (!p_module) + return -ENOMEM; + + p_module->w = w; + list_add_tail(&p_module->node, &pipe->w_list); + } + } + + return 0; +} + +static void skl_tplg_set_pipe_type(struct skl *skl, struct skl_pipe *pipe) +{ + struct skl_pipe_module *w_module; + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + bool host_found = false, link_found = false; + + list_for_each_entry(w_module, &pipe->w_list, node) { + w = w_module->w; + mconfig = w->priv; + + if (mconfig->dev_type == SKL_DEVICE_HDAHOST) + host_found = true; + else if (mconfig->dev_type != SKL_DEVICE_NONE) + link_found = true; + } + + if (host_found && link_found) + pipe->passthru = true; + else + pipe->passthru = false; +} + /* This will be read from topology manifest, currently defined here */ #define SKL_MAX_MCPS 30000000 #define SKL_FW_MAX_MEM 1000000 @@ -1746,6 +1841,7 @@ int skl_tplg_init(struct snd_soc_platform *platform, struct hdac_ext_bus *ebus) const struct firmware *fw; struct hdac_bus *bus = ebus_to_hbus(ebus); struct skl *skl = ebus_to_skl(ebus); + struct skl_pipeline *ppl; ret = reject_firmware(&fw, skl->tplg_name, bus->dev); if (ret < 0) { @@ -1775,6 +1871,12 @@ int skl_tplg_init(struct snd_soc_platform *platform, struct hdac_ext_bus *ebus) skl->resource.max_mem = SKL_FW_MAX_MEM; skl->tplg = fw; + ret = skl_tplg_create_pipe_widget_list(platform); + if (ret < 0) + return ret; + + list_for_each_entry(ppl, &skl->ppl_list, node) + skl_tplg_set_pipe_type(skl, ppl->pipe); return 0; } diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h index e4b399cd7..22d3ef838 100644 --- a/sound/soc/intel/skylake/skl-topology.h +++ b/sound/soc/intel/skylake/skl-topology.h @@ -244,7 +244,8 @@ enum skl_pipe_state { SKL_PIPE_INVALID = 0, SKL_PIPE_CREATED = 1, SKL_PIPE_PAUSED = 2, - SKL_PIPE_STARTED = 3 + SKL_PIPE_STARTED = 3, + SKL_PIPE_RESET = 4 }; struct skl_pipe_module { @@ -270,6 +271,7 @@ struct skl_pipe { struct skl_pipe_params *p_params; enum skl_pipe_state state; struct list_head w_list; + bool passthru; }; enum skl_module_state { @@ -319,6 +321,7 @@ struct skl_algo_data { u32 param_id; u32 set_params; u32 max; + u32 size; char *params; }; @@ -357,6 +360,8 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + int skl_init_module(struct skl_sst *ctx, struct skl_module_cfg *module_config); int skl_bind_modules(struct skl_sst *ctx, struct skl_module_cfg diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c index 95bf61ed7..9870963d4 100644 --- a/sound/soc/intel/skylake/skl.c +++ b/sound/soc/intel/skylake/skl.c @@ -35,6 +35,8 @@ #include "skl-sst-dsp.h" #include "skl-sst-ipc.h" +static struct skl_machine_pdata skl_dmic_data; + /* * initialize the PCI registers */ @@ -184,6 +186,7 @@ static int _skl_suspend(struct hdac_ext_bus *ebus) { struct skl *skl = ebus_to_skl(ebus); struct hdac_bus *bus = ebus_to_hbus(ebus); + struct pci_dev *pci = to_pci_dev(bus->dev); int ret; snd_hdac_ext_bus_link_power_down_all(ebus); @@ -193,9 +196,12 @@ static int _skl_suspend(struct hdac_ext_bus *ebus) return ret; snd_hdac_bus_stop_chip(bus); + update_pci_dword(pci, AZX_PCIREG_PGCTL, + AZX_PGCTL_LSRMD_MASK, AZX_PGCTL_LSRMD_MASK); skl_enable_miscbdcge(bus->dev, false); snd_hdac_bus_enter_link_reset(bus); skl_enable_miscbdcge(bus->dev, true); + skl_cleanup_resources(skl); return 0; } @@ -242,6 +248,7 @@ static int skl_suspend(struct device *dev) ret = _skl_suspend(ebus); if (ret < 0) return ret; + skl->skl_sst->fw_loaded = false; } if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { @@ -397,6 +404,10 @@ static int skl_machine_device_register(struct skl *skl, void *driver_data) platform_device_put(pdev); return -EIO; } + + if (mach->pdata) + dev_set_drvdata(&pdev->dev, mach->pdata); + skl->i2s_dev = pdev; return 0; @@ -657,15 +668,21 @@ static int skl_probe(struct pci_dev *pci, skl->pci_id = pci->device; + device_disable_async_suspend(bus->dev); + skl->nhlt = skl_nhlt_init(bus->dev); - if (skl->nhlt == NULL) + if (skl->nhlt == NULL) { + err = -ENODEV; goto out_free; + } skl_nhlt_update_topology_bin(skl); pci_set_drvdata(skl->pci, ebus); + skl_dmic_data.dmic_num = skl_get_dmic_geo(skl); + /* check if dsp is there */ if (ebus->ppcap) { err = skl_machine_device_register(skl, @@ -713,7 +730,7 @@ static int skl_probe(struct pci_dev *pci, list_for_each_entry(hlink, &ebus->hlink_list, list) snd_hdac_ext_bus_link_put(ebus, hlink); - /*configure PM */ + /* configure PM */ pm_runtime_put_noidle(bus->dev); pm_runtime_allow(bus->dev); @@ -766,8 +783,7 @@ static void skl_remove(struct pci_dev *pci) struct hdac_ext_bus *ebus = pci_get_drvdata(pci); struct skl *skl = ebus_to_skl(ebus); - if (skl->tplg) - release_firmware(skl->tplg); + release_firmware(skl->tplg); if (pci_dev_run_wake(pci)) pm_runtime_get_noresume(&pci->dev); @@ -786,15 +802,23 @@ static void skl_remove(struct pci_dev *pci) static struct sst_acpi_mach sst_skl_devdata[] = { { "INT343A", "skl_alc286s_i2s", "/*(DEBLOBBED)*/", NULL, NULL, NULL }, - { "INT343B", "skl_nau88l25_ssm4567_i2s", "/*(DEBLOBBED)*/", - NULL, NULL, NULL }, - { "MX98357A", "skl_nau88l25_max98357a_i2s", "/*(DEBLOBBED)*/", - NULL, NULL, NULL }, + { "INT343B", "skl_n88l25_s4567", "/*(DEBLOBBED)*/", + NULL, NULL, &skl_dmic_data }, + { "MX98357A", "skl_n88l25_m98357a", "/*(DEBLOBBED)*/", + NULL, NULL, &skl_dmic_data }, {} }; static struct sst_acpi_mach sst_bxtp_devdata[] = { { "INT343A", "bxt_alc298s_i2s", "/*(DEBLOBBED)*/", NULL, NULL, NULL }, + { "DLGS7219", "bxt_da7219_max98357a_i2s", "/*(DEBLOBBED)*/", NULL, NULL, NULL }, +}; + +static struct sst_acpi_mach sst_kbl_devdata[] = { + { "INT343A", "kbl_alc286s_i2s", "/*(DEBLOBBED)*/", NULL, NULL, NULL }, + { "INT343B", "kbl_n88l25_s4567", "/*(DEBLOBBED)*/", NULL, NULL, &skl_dmic_data }, + { "MX98357A", "kbl_n88l25_m98357a", "/*(DEBLOBBED)*/", NULL, NULL, &skl_dmic_data }, + {} }; /* PCI IDs */ @@ -805,6 +829,9 @@ static const struct pci_device_id skl_ids[] = { /* BXT-P */ { PCI_DEVICE(0x8086, 0x5a98), .driver_data = (unsigned long)&sst_bxtp_devdata}, + /* KBL */ + { PCI_DEVICE(0x8086, 0x9D71), + .driver_data = (unsigned long)&sst_kbl_devdata}, { 0, } }; MODULE_DEVICE_TABLE(pci, skl_ids); diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index 4b4b3876a..9064e5b0d 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -48,6 +48,8 @@ #define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 #define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 +#define AZX_PCIREG_PGCTL 0x44 +#define AZX_PGCTL_LSRMD_MASK (1 << 4) #define AZX_PCIREG_CGCTL 0x48 #define AZX_CGCTL_MISCBDCGE_MASK (1 << 6) @@ -65,6 +67,7 @@ struct skl { unsigned int init_failed:1; /* delayed init failed */ struct platform_device *dmic_dev; struct platform_device *i2s_dev; + struct snd_soc_platform *platform; struct nhlt_acpi_table *nhlt; /* nhlt ptr */ struct skl_sst *skl_sst; /* sst skl ctx */ @@ -90,6 +93,11 @@ struct skl_dma_params { u8 stream_tag; }; +/* to pass dmic data */ +struct skl_machine_pdata { + u32 dmic_num; +}; + struct skl_dsp_ops { int id; struct skl_dsp_loader_ops (*loader_ops)(void); @@ -108,9 +116,11 @@ void skl_nhlt_free(struct nhlt_acpi_table *addr); struct nhlt_specific_cfg *skl_get_ep_blob(struct skl *skl, u32 instance, u8 link_type, u8 s_fmt, u8 no_ch, u32 s_rate, u8 dirn); +int skl_get_dmic_geo(struct skl *skl); int skl_nhlt_update_topology_bin(struct skl *skl); int skl_init_dsp(struct skl *skl); int skl_free_dsp(struct skl *skl); int skl_suspend_dsp(struct skl *skl); int skl_resume_dsp(struct skl *skl); +void skl_cleanup_resources(struct skl *skl); #endif /* __SOUND_SOC_SKL_H */ diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 3abf51c07..05cf809cf 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -1,15 +1,40 @@ config SND_SOC_MEDIATEK - tristate "ASoC support for Mediatek chip" + tristate + +config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" + depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT2701_CS42448 + tristate "ASoc Audio driver for MT2701 with CS42448 codec" + depends on SND_SOC_MT2701 + select SND_SOC_CS42XX8_I2C + select SND_SOC_BT_SCO + help + This adds ASoC driver for Mediatek MT2701 boards + with the CS42448 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173 + tristate "ASoC support for Mediatek MT8173 chip" depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK help - This adds ASoC platform driver support for Mediatek chip + This adds ASoC platform driver support for Mediatek MT8173 chip that can be used with other codecs. Select Y if you have such device. Ex: MT8173 config SND_SOC_MT8173_MAX98090 tristate "ASoC Audio driver for MT8173 with MAX98090 codec" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_MAX98090 help This adds ASoC driver for Mediatek MT8173 boards @@ -19,8 +44,9 @@ config SND_SOC_MT8173_MAX98090 config SND_SOC_MT8173_RT5650 tristate "ASoC Audio driver for MT8173 with RT5650 codec" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 + select SND_SOC_HDMI_CODEC help This adds ASoC driver for Mediatek MT8173 boards with the RT5650 audio codec. @@ -29,7 +55,7 @@ config SND_SOC_MT8173_RT5650 config SND_SOC_MT8173_RT5650_RT5514 tristate "ASoC Audio driver for MT8173 with RT5650 RT5514 codecs" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 select SND_SOC_RT5514 help @@ -40,7 +66,7 @@ config SND_SOC_MT8173_RT5650_RT5514 config SND_SOC_MT8173_RT5650_RT5676 tristate "ASoC Audio driver for MT8173 with RT5650 RT5676 codecs" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 select SND_SOC_RT5677 select SND_SOC_HDMI_CODEC diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index d486860c0..6bcab35dc 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,7 +1,3 @@ -# MTK Platform Support -obj-$(CONFIG_SND_SOC_MEDIATEK) += mtk-afe-pcm.o -# Machine support -obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o +obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ +obj-$(CONFIG_SND_SOC_MT8173) += mt8173/ diff --git a/sound/soc/mediatek/common/Makefile b/sound/soc/mediatek/common/Makefile new file mode 100644 index 000000000..a55d33bc7 --- /dev/null +++ b/sound/soc/mediatek/common/Makefile @@ -0,0 +1,16 @@ +# +# Copyright (C) 2015 MediaTek 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. +# + +# platform driver +snd-soc-mtk-common-objs := mtk-afe-platform-driver.o mtk-afe-fe-dai.o +obj-$(CONFIG_SND_SOC_MEDIATEK) += snd-soc-mtk-common.o diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c new file mode 100644 index 000000000..b788791b0 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -0,0 +1,379 @@ +/* + * mtk-afe-fe-dais.c -- Mediatek afe fe dai operator + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "mtk-afe-fe-dai.h" +#include "mtk-base-afe.h" + +#define AFE_BASE_END_OFFSET 8 + +int mtk_regmap_update_bits(struct regmap *map, int reg, unsigned int mask, + unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_update_bits(map, reg, mask, val); +} + +int mtk_regmap_write(struct regmap *map, int reg, unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_write(map, reg, val); +} + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct snd_pcm_runtime *runtime = substream->runtime; + int memif_num = rtd->cpu_dai->id; + struct mtk_base_afe_memif *memif = &afe->memif[memif_num]; + const struct snd_pcm_hardware *mtk_afe_hardware = afe->mtk_afe_hardware; + int ret; + + memif->substream = substream; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); + /* enable agent */ + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 0 << memif->data->agent_disable_shift); + + snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware); + + /* + * Capture cannot use ping-pong buffer since hw_ptr at IRQ may be + * smaller than period_size due to AFE's internal buffer. + * This easily leads to overrun when avail_min is period_size. + * One more period can hold the possible unread buffer. + */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + int periods_max = mtk_afe_hardware->periods_max; + + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIODS, + 3, periods_max); + if (ret < 0) { + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + return ret; + } + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + + /* dynamic allocate irq to memif */ + if (memif->irq_usage < 0) { + int irq_id = mtk_dynamic_irq_acquire(afe); + + if (irq_id != afe->irqs_size) { + /* link */ + memif->irq_usage = irq_id; + } else { + dev_err(afe->dev, "%s() error: no more asys irq\n", + __func__); + ret = -EBUSY; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); + +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int irq_id; + + irq_id = memif->irq_usage; + + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 1 << memif->data->agent_disable_shift); + + if (!memif->const_irq) { + mtk_dynamic_irq_release(afe, irq_id); + memif->irq_usage = -1; + memif->substream = NULL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_shutdown); + +int mtk_afe_fe_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 mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int msb_at_bit33 = 0; + int ret, fs = 0; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0; + memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr); + memif->buffer_size = substream->runtime->dma_bytes; + + /* start */ + mtk_regmap_write(afe->regmap, memif->data->reg_ofs_base, + memif->phys_buf_addr); + /* end */ + mtk_regmap_write(afe->regmap, + memif->data->reg_ofs_base + AFE_BASE_END_OFFSET, + memif->phys_buf_addr + memif->buffer_size - 1); + + /* set MSB to 33-bit */ + mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg, + 1 << memif->data->msb_shift, + msb_at_bit33 << memif->data->msb_shift); + + /* set channel */ + if (memif->data->mono_shift >= 0) { + unsigned int mono = (params_channels(params) == 1) ? 1 : 0; + + mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg, + 1 << memif->data->mono_shift, + mono << memif->data->mono_shift); + } + + /* set rate */ + if (memif->data->fs_shift < 0) + return 0; + + fs = afe->memif_fs(substream, params_rate(params)); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg, + memif->data->fs_maskbit << memif->data->fs_shift, + fs << memif->data->fs_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_params); + +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_free); + +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; + const struct mtk_base_irq_data *irq_data = irqs->irq_data; + unsigned int counter = runtime->period_size; + int fs; + + dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (memif->data->enable_shift >= 0) + mtk_regmap_update_bits(afe->regmap, + memif->data->enable_reg, + 1 << memif->data->enable_shift, + 1 << memif->data->enable_shift); + + /* set irq counter */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + irq_data->irq_cnt_maskbit + << irq_data->irq_cnt_shift, + counter << irq_data->irq_cnt_shift); + + /* set irq fs */ + fs = afe->irq_fs(substream, runtime->rate); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + irq_data->irq_fs_maskbit + << irq_data->irq_fs_shift, + fs << irq_data->irq_fs_shift); + + /* enable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 1 << irq_data->irq_en_shift); + + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg, + 1 << memif->data->enable_shift, 0); + /* disable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 0 << irq_data->irq_en_shift); + /* and clear pending IRQ */ + mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + 1 << irq_data->irq_clr_shift); + return 0; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_trigger); + +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int hd_audio = 0; + + /* set hd mode */ + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + hd_audio = 0; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hd_audio = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hd_audio = 1; + break; + default: + dev_err(afe->dev, "%s() error: unsupported format %d\n", + __func__, substream->runtime->format); + break; + } + + mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg, + 1 << memif->data->hd_shift, + hd_audio << memif->data->hd_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_prepare); + +const struct snd_soc_dai_ops mtk_afe_fe_ops = { + .startup = mtk_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; +EXPORT_SYMBOL_GPL(mtk_afe_fe_ops); + +static DEFINE_MUTEX(irqs_lock); +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe) +{ + int i; + + mutex_lock(&afe->irq_alloc_lock); + for (i = 0; i < afe->irqs_size; ++i) { + if (afe->irqs[i].irq_occupyed == 0) { + afe->irqs[i].irq_occupyed = 1; + mutex_unlock(&afe->irq_alloc_lock); + return i; + } + } + mutex_unlock(&afe->irq_alloc_lock); + return afe->irqs_size; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_acquire); + +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id) +{ + mutex_lock(&afe->irq_alloc_lock); + if (irq_id >= 0 && irq_id < afe->irqs_size) { + afe->irqs[irq_id].irq_occupyed = 0; + mutex_unlock(&afe->irq_alloc_lock); + return 0; + } + mutex_unlock(&afe->irq_alloc_lock); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_release); + +int mtk_afe_dai_suspend(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i; + + if (pm_runtime_status_suspended(dev) || afe->suspended) + return 0; + + if (!afe->reg_back_up) + afe->reg_back_up = + devm_kcalloc(dev, afe->reg_back_up_list_num, + sizeof(unsigned int), GFP_KERNEL); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + regmap_read(regmap, afe->reg_back_up_list[i], + &afe->reg_back_up[i]); + + afe->suspended = true; + afe->runtime_suspend(dev); + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_suspend); + +int mtk_afe_dai_resume(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i = 0; + + if (pm_runtime_status_suspended(dev) || !afe->suspended) + return 0; + + afe->runtime_resume(dev); + + if (!afe->reg_back_up) + dev_dbg(dev, "%s no reg_backup\n", __func__); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + mtk_regmap_write(regmap, afe->reg_back_up_list[i], + afe->reg_back_up[i]); + + afe->suspended = false; + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_resume); + +MODULE_DESCRIPTION("Mediatek simple fe dai operator"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.h b/sound/soc/mediatek/common/mtk-afe-fe-dai.h new file mode 100644 index 000000000..28cb17854 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.h @@ -0,0 +1,45 @@ +/* + * mtk-afe-fe-dais.h -- Mediatek afe fe dai operator definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_AFE_FE_DAI_H_ +#define _MTK_AFE_FE_DAI_H_ + +struct snd_soc_dai_ops; +struct mtk_base_afe; +struct mtk_base_afe_memif; + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); + +extern const struct snd_soc_dai_ops mtk_afe_fe_ops; + +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe); +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id); +int mtk_afe_dai_suspend(struct snd_soc_dai *dai); +int mtk_afe_dai_resume(struct snd_soc_dai *dai); + +#endif diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c new file mode 100644 index 000000000..82d439c15 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c @@ -0,0 +1,90 @@ +/* + * mtk-afe-platform-driver.c -- Mediatek afe platform driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <sound/soc.h> + +#include "mtk-afe-platform-driver.h" +#include "mtk-base-afe.h" + +static snd_pcm_uframes_t mtk_afe_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + const struct mtk_base_memif_data *memif_data = memif->data; + struct regmap *regmap = afe->regmap; + struct device *dev = afe->dev; + int reg_ofs_base = memif_data->reg_ofs_base; + int reg_ofs_cur = memif_data->reg_ofs_cur; + unsigned int hw_ptr = 0, hw_base = 0; + int ret, pcm_ptr_bytes; + + ret = regmap_read(regmap, reg_ofs_cur, &hw_ptr); + if (ret || hw_ptr == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + ret = regmap_read(regmap, reg_ofs_base, &hw_base); + if (ret || hw_base == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + pcm_ptr_bytes = hw_ptr - hw_base; + +POINTER_RETURN_FRAMES: + return bytes_to_frames(substream->runtime, pcm_ptr_bytes); +} + +static const struct snd_pcm_ops mtk_afe_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .pointer = mtk_afe_pcm_pointer, +}; + +static int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + + size = afe->mtk_afe_hardware->buffer_bytes_max; + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, size, size); +} + +static void mtk_afe_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +const struct snd_soc_platform_driver mtk_afe_pcm_platform = { + .ops = &mtk_afe_pcm_ops, + .pcm_new = mtk_afe_pcm_new, + .pcm_free = mtk_afe_pcm_free, +}; +EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform); + +MODULE_DESCRIPTION("Mediatek simple platform driver"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.h b/sound/soc/mediatek/common/mtk-afe-platform-driver.h new file mode 100644 index 000000000..a973fc925 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.h @@ -0,0 +1,23 @@ +/* + * mtk-afe-platform-driver.h -- Mediatek afe platform driver definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_AFE_PLATFORM_DRIVER_H_ +#define _MTK_AFE_PLATFORM_DRIVER_H_ + +extern const struct snd_soc_platform_driver mtk_afe_pcm_platform; + +#endif + diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h new file mode 100644 index 000000000..3a78f6f17 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -0,0 +1,104 @@ +/* + * mtk-base-afe.h -- Mediatek base afe structure + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_BASE_AFE_H_ +#define _MTK_BASE_AFE_H_ + +struct mtk_base_memif_data { + int id; + const char *name; + int reg_ofs_base; + int reg_ofs_cur; + int fs_reg; + int fs_shift; + int fs_maskbit; + int mono_reg; + int mono_shift; + int enable_reg; + int enable_shift; + int hd_reg; + int hd_shift; + int msb_reg; + int msb_shift; + int agent_disable_reg; + int agent_disable_shift; +}; + +struct mtk_base_irq_data { + int id; + int irq_cnt_reg; + int irq_cnt_shift; + int irq_cnt_maskbit; + int irq_fs_reg; + int irq_fs_shift; + int irq_fs_maskbit; + int irq_en_reg; + int irq_en_shift; + int irq_clr_reg; + int irq_clr_shift; +}; + +struct device; +struct mtk_base_afe_memif; +struct mtk_base_afe_irq; +struct regmap; +struct snd_pcm_substream; +struct snd_soc_dai; + +struct mtk_base_afe { + void __iomem *base_addr; + struct device *dev; + struct regmap *regmap; + struct mutex irq_alloc_lock; /* dynamic alloc irq lock */ + + unsigned int const *reg_back_up_list; + unsigned int *reg_back_up; + unsigned int reg_back_up_list_num; + + int (*runtime_suspend)(struct device *dev); + int (*runtime_resume)(struct device *dev); + bool suspended; + + struct mtk_base_afe_memif *memif; + int memif_size; + struct mtk_base_afe_irq *irqs; + int irqs_size; + + const struct snd_pcm_hardware *mtk_afe_hardware; + int (*memif_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + int (*irq_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + + void *platform_priv; +}; + +struct mtk_base_afe_memif { + unsigned int phys_buf_addr; + int buffer_size; + struct snd_pcm_substream *substream; + const struct mtk_base_memif_data *data; + int irq_usage; + int const_irq; +}; + +struct mtk_base_afe_irq { + const struct mtk_base_irq_data *irq_data; + int irq_occupyed; +}; + +#endif + diff --git a/sound/soc/mediatek/mt2701/Makefile b/sound/soc/mediatek/mt2701/Makefile new file mode 100644 index 000000000..31c3d04d4 --- /dev/null +++ b/sound/soc/mediatek/mt2701/Makefile @@ -0,0 +1,19 @@ +# +# Copyright (C) 2015 MediaTek 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. +# + +# platform driver +snd-soc-mt2701-afe-objs := mt2701-afe-pcm.o mt2701-afe-clock-ctrl.o +obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o + +# machine driver +obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c new file mode 100644 index 000000000..b815ecc6b --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -0,0 +1,464 @@ +/* + * mt2701-afe-clock-ctrl.c -- Mediatek 2701 afe clock ctrl + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <sound/soc.h> +#include <linux/regmap.h> +#include <linux/pm_runtime.h> + +#include "mt2701-afe-common.h" +#include "mt2701-afe-clock-ctrl.h" + +static const char *aud_clks[MT2701_CLOCK_NUM] = { + [MT2701_AUD_INFRA_SYS_AUDIO] = "infra_sys_audio_clk", + [MT2701_AUD_AUD_MUX1_SEL] = "top_audio_mux1_sel", + [MT2701_AUD_AUD_MUX2_SEL] = "top_audio_mux2_sel", + [MT2701_AUD_AUD_MUX1_DIV] = "top_audio_mux1_div", + [MT2701_AUD_AUD_MUX2_DIV] = "top_audio_mux2_div", + [MT2701_AUD_AUD_48K_TIMING] = "top_audio_48k_timing", + [MT2701_AUD_AUD_44K_TIMING] = "top_audio_44k_timing", + [MT2701_AUD_AUDPLL_MUX_SEL] = "top_audpll_mux_sel", + [MT2701_AUD_APLL_SEL] = "top_apll_sel", + [MT2701_AUD_AUD1PLL_98M] = "top_aud1_pll_98M", + [MT2701_AUD_AUD2PLL_90M] = "top_aud2_pll_90M", + [MT2701_AUD_HADDS2PLL_98M] = "top_hadds2_pll_98M", + [MT2701_AUD_HADDS2PLL_294M] = "top_hadds2_pll_294M", + [MT2701_AUD_AUDPLL] = "top_audpll", + [MT2701_AUD_AUDPLL_D4] = "top_audpll_d4", + [MT2701_AUD_AUDPLL_D8] = "top_audpll_d8", + [MT2701_AUD_AUDPLL_D16] = "top_audpll_d16", + [MT2701_AUD_AUDPLL_D24] = "top_audpll_d24", + [MT2701_AUD_AUDINTBUS] = "top_audintbus_sel", + [MT2701_AUD_CLK_26M] = "clk_26m", + [MT2701_AUD_SYSPLL1_D4] = "top_syspll1_d4", + [MT2701_AUD_AUD_K1_SRC_SEL] = "top_aud_k1_src_sel", + [MT2701_AUD_AUD_K2_SRC_SEL] = "top_aud_k2_src_sel", + [MT2701_AUD_AUD_K3_SRC_SEL] = "top_aud_k3_src_sel", + [MT2701_AUD_AUD_K4_SRC_SEL] = "top_aud_k4_src_sel", + [MT2701_AUD_AUD_K5_SRC_SEL] = "top_aud_k5_src_sel", + [MT2701_AUD_AUD_K6_SRC_SEL] = "top_aud_k6_src_sel", + [MT2701_AUD_AUD_K1_SRC_DIV] = "top_aud_k1_src_div", + [MT2701_AUD_AUD_K2_SRC_DIV] = "top_aud_k2_src_div", + [MT2701_AUD_AUD_K3_SRC_DIV] = "top_aud_k3_src_div", + [MT2701_AUD_AUD_K4_SRC_DIV] = "top_aud_k4_src_div", + [MT2701_AUD_AUD_K5_SRC_DIV] = "top_aud_k5_src_div", + [MT2701_AUD_AUD_K6_SRC_DIV] = "top_aud_k6_src_div", + [MT2701_AUD_AUD_I2S1_MCLK] = "top_aud_i2s1_mclk", + [MT2701_AUD_AUD_I2S2_MCLK] = "top_aud_i2s2_mclk", + [MT2701_AUD_AUD_I2S3_MCLK] = "top_aud_i2s3_mclk", + [MT2701_AUD_AUD_I2S4_MCLK] = "top_aud_i2s4_mclk", + [MT2701_AUD_AUD_I2S5_MCLK] = "top_aud_i2s5_mclk", + [MT2701_AUD_AUD_I2S6_MCLK] = "top_aud_i2s6_mclk", + [MT2701_AUD_ASM_M_SEL] = "top_asm_m_sel", + [MT2701_AUD_ASM_H_SEL] = "top_asm_h_sel", + [MT2701_AUD_UNIVPLL2_D4] = "top_univpll2_d4", + [MT2701_AUD_UNIVPLL2_D2] = "top_univpll2_d2", + [MT2701_AUD_SYSPLL_D5] = "top_syspll_d5", +}; + +int mt2701_init_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i = 0; + + for (i = 0; i < MT2701_CLOCK_NUM; i++) { + afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(aud_clks[i])) { + dev_warn(afe->dev, "%s devm_clk_get %s fail\n", + __func__, aud_clks[i]); + return PTR_ERR(aud_clks[i]); + } + } + + return 0; +} + +int mt2701_afe_enable_clock(struct mtk_base_afe *afe) +{ + int ret = 0; + + ret = mt2701_turn_on_a1sys_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_a1sys_clock fail %d\n", + __func__, ret); + return ret; + } + + ret = mt2701_turn_on_a2sys_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_a2sys_clock fail %d\n", + __func__, ret); + mt2701_turn_off_a1sys_clock(afe); + return ret; + } + + ret = mt2701_turn_on_afe_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_afe_clock fail %d\n", + __func__, ret); + mt2701_turn_off_a1sys_clock(afe); + mt2701_turn_off_a2sys_clock(afe); + return ret; + } + + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, + AFE_DAC_CON0_AFE_ON); + regmap_write(afe->regmap, PWR2_TOP_CON, + PWR2_TOP_CON_INIT_VAL); + regmap_write(afe->regmap, PWR1_ASM_CON1, + PWR1_ASM_CON1_INIT_VAL); + regmap_write(afe->regmap, PWR2_ASM_CON1, + PWR2_ASM_CON1_INIT_VAL); + + return 0; +} + +void mt2701_afe_disable_clock(struct mtk_base_afe *afe) +{ + mt2701_turn_off_afe_clock(afe); + mt2701_turn_off_a1sys_clock(afe); + mt2701_turn_off_a2sys_clock(afe); + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON, 0); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, 0); +} + +int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret = 0; + + /* Set Mux */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret); + goto A1SYS_CLK_AUD_MUX1_SEL_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL], + afe_priv->clocks[MT2701_AUD_AUD1PLL_98M]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX1_SEL], + aud_clks[MT2701_AUD_AUD1PLL_98M], ret); + goto A1SYS_CLK_AUD_MUX1_SEL_ERR; + } + + /* Set Divider */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, + aud_clks[MT2701_AUD_AUD_MUX1_DIV], + ret); + goto A1SYS_CLK_AUD_MUX1_DIV_ERR; + } + + ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV], + MT2701_AUD_AUD_MUX1_DIV_RATE); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX1_DIV], + MT2701_AUD_AUD_MUX1_DIV_RATE, ret); + goto A1SYS_CLK_AUD_MUX1_DIV_ERR; + } + + /* Enable clock gate */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_48K_TIMING], ret); + goto A1SYS_CLK_AUD_48K_ERR; + } + + /* Enable infra audio */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto A1SYS_CLK_INFRA_ERR; + } + + return 0; + +A1SYS_CLK_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); +A1SYS_CLK_AUD_48K_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); +A1SYS_CLK_AUD_MUX1_DIV_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); +A1SYS_CLK_AUD_MUX1_SEL_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + + return ret; +} + +void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); +} + +int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret = 0; + + /* Set Mux */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret); + goto A2SYS_CLK_AUD_MUX2_SEL_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL], + afe_priv->clocks[MT2701_AUD_AUD2PLL_90M]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX2_SEL], + aud_clks[MT2701_AUD_AUD2PLL_90M], ret); + goto A2SYS_CLK_AUD_MUX2_SEL_ERR; + } + + /* Set Divider */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX2_DIV], ret); + goto A2SYS_CLK_AUD_MUX2_DIV_ERR; + } + + ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV], + MT2701_AUD_AUD_MUX2_DIV_RATE); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX2_DIV], + MT2701_AUD_AUD_MUX2_DIV_RATE, ret); + goto A2SYS_CLK_AUD_MUX2_DIV_ERR; + } + + /* Enable clock gate */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_44K_TIMING], ret); + goto A2SYS_CLK_AUD_44K_ERR; + } + + /* Enable infra audio */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto A2SYS_CLK_INFRA_ERR; + } + + return 0; + +A2SYS_CLK_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); +A2SYS_CLK_AUD_44K_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); +A2SYS_CLK_AUD_MUX2_DIV_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); +A2SYS_CLK_AUD_MUX2_SEL_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + + return ret; +} + +void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); +} + +int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + + /* enable INFRA_SYS */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto AFE_AUD_INFRA_ERR; + } + + /* Set MT2701_AUD_AUDINTBUS to MT2701_AUD_SYSPLL1_D4 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUDINTBUS], ret); + goto AFE_AUD_AUDINTBUS_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUDINTBUS], + afe_priv->clocks[MT2701_AUD_SYSPLL1_D4]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUDINTBUS], + aud_clks[MT2701_AUD_SYSPLL1_D4], ret); + goto AFE_AUD_AUDINTBUS_ERR; + } + + /* Set MT2701_AUD_ASM_H_SEL to MT2701_AUD_UNIVPLL2_D2 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_ASM_H_SEL], ret); + goto AFE_AUD_ASM_H_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_H_SEL], + afe_priv->clocks[MT2701_AUD_UNIVPLL2_D2]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_ASM_H_SEL], + aud_clks[MT2701_AUD_UNIVPLL2_D2], ret); + goto AFE_AUD_ASM_H_ERR; + } + + /* Set MT2701_AUD_ASM_M_SEL to MT2701_AUD_UNIVPLL2_D4 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_ASM_M_SEL], ret); + goto AFE_AUD_ASM_M_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_M_SEL], + afe_priv->clocks[MT2701_AUD_UNIVPLL2_D4]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_ASM_M_SEL], + aud_clks[MT2701_AUD_UNIVPLL2_D4], ret); + goto AFE_AUD_ASM_M_ERR; + } + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_AFE, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_APLL_CK, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A1SYS, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A2SYS, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_AFE_CONN, 0); + + return 0; + +AFE_AUD_ASM_M_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); +AFE_AUD_ASM_H_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); +AFE_AUD_AUDINTBUS_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); +AFE_AUD_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + + return ret; +} + +void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_AFE, AUDIO_TOP_CON0_PDN_AFE); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_APLL_CK, + AUDIO_TOP_CON0_PDN_APLL_CK); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A1SYS, + AUDIO_TOP_CON4_PDN_A1SYS); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A2SYS, + AUDIO_TOP_CON4_PDN_A2SYS); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_AFE_CONN, + AUDIO_TOP_CON4_PDN_AFE_CONN); +} + +void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain, + int mclk) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + int aud_src_div_id = MT2701_AUD_AUD_K1_SRC_DIV + id; + int aud_src_clk_id = MT2701_AUD_AUD_K1_SRC_SEL + id; + + /* Set MCLK Kx_SRC_SEL(domain) */ + ret = clk_prepare_enable(afe_priv->clocks[aud_src_clk_id]); + if (ret) + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[aud_src_clk_id], ret); + + if (domain == 0) { + ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id], + afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + if (ret) + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", + __func__, aud_clks[aud_src_clk_id], + aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret); + } else { + ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id], + afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + if (ret) + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", + __func__, aud_clks[aud_src_clk_id], + aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret); + } + clk_disable_unprepare(afe_priv->clocks[aud_src_clk_id]); + + /* Set MCLK Kx_SRC_DIV(divider) */ + ret = clk_prepare_enable(afe_priv->clocks[aud_src_div_id]); + if (ret) + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[aud_src_div_id], ret); + + ret = clk_set_rate(afe_priv->clocks[aud_src_div_id], mclk); + if (ret) + dev_err(afe->dev, "%s clk_set_rate %s-%d fail %d\n", __func__, + aud_clks[aud_src_div_id], mclk, ret); + clk_disable_unprepare(afe_priv->clocks[aud_src_div_id]); +} + +MODULE_DESCRIPTION("MT2701 afe clock control"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h new file mode 100644 index 000000000..6497d570c --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h @@ -0,0 +1,38 @@ +/* + * mt2701-afe-clock-ctrl.h -- Mediatek 2701 afe clock ctrl definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT2701_AFE_CLOCK_CTRL_H_ +#define _MT2701_AFE_CLOCK_CTRL_H_ + +struct mtk_base_afe; + +int mt2701_init_clock(struct mtk_base_afe *afe); +int mt2701_afe_enable_clock(struct mtk_base_afe *afe); +void mt2701_afe_disable_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe); + +void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain, + int mclk); + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-common.h b/sound/soc/mediatek/mt2701/mt2701-afe-common.h new file mode 100644 index 000000000..c19430e98 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-common.h @@ -0,0 +1,172 @@ +/* + * mt2701-afe-common.h -- Mediatek 2701 audio driver definitions + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT_2701_AFE_COMMON_H_ +#define _MT_2701_AFE_COMMON_H_ +#include <sound/soc.h> +#include <linux/clk.h> +#include <linux/regmap.h> +#include "mt2701-reg.h" +#include "../common/mtk-base-afe.h" + +#define MT2701_STREAM_DIR_NUM (SNDRV_PCM_STREAM_LAST + 1) +#define MT2701_PLL_DOMAIN_0_RATE 98304000 +#define MT2701_PLL_DOMAIN_1_RATE 90316800 +#define MT2701_AUD_AUD_MUX1_DIV_RATE (MT2701_PLL_DOMAIN_0_RATE / 2) +#define MT2701_AUD_AUD_MUX2_DIV_RATE (MT2701_PLL_DOMAIN_1_RATE / 2) + +enum { + MT2701_I2S_1, + MT2701_I2S_2, + MT2701_I2S_3, + MT2701_I2S_4, + MT2701_I2S_NUM, +}; + +enum { + MT2701_MEMIF_DL1, + MT2701_MEMIF_DL2, + MT2701_MEMIF_DL3, + MT2701_MEMIF_DL4, + MT2701_MEMIF_DL5, + MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_DLM = MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_UL1, + MT2701_MEMIF_UL2, + MT2701_MEMIF_UL3, + MT2701_MEMIF_UL4, + MT2701_MEMIF_UL5, + MT2701_MEMIF_DLBT, + MT2701_MEMIF_ULBT, + MT2701_MEMIF_NUM, + MT2701_IO_I2S = MT2701_MEMIF_NUM, + MT2701_IO_2ND_I2S, + MT2701_IO_3RD_I2S, + MT2701_IO_4TH_I2S, + MT2701_IO_5TH_I2S, + MT2701_IO_6TH_I2S, + MT2701_IO_MRG, +}; + +enum { + MT2701_IRQ_ASYS_START, + MT2701_IRQ_ASYS_IRQ1 = MT2701_IRQ_ASYS_START, + MT2701_IRQ_ASYS_IRQ2, + MT2701_IRQ_ASYS_IRQ3, + MT2701_IRQ_ASYS_END, +}; + +/* 2701 clock def */ +enum audio_system_clock_type { + MT2701_AUD_INFRA_SYS_AUDIO, + MT2701_AUD_AUD_MUX1_SEL, + MT2701_AUD_AUD_MUX2_SEL, + MT2701_AUD_AUD_MUX1_DIV, + MT2701_AUD_AUD_MUX2_DIV, + MT2701_AUD_AUD_48K_TIMING, + MT2701_AUD_AUD_44K_TIMING, + MT2701_AUD_AUDPLL_MUX_SEL, + MT2701_AUD_APLL_SEL, + MT2701_AUD_AUD1PLL_98M, + MT2701_AUD_AUD2PLL_90M, + MT2701_AUD_HADDS2PLL_98M, + MT2701_AUD_HADDS2PLL_294M, + MT2701_AUD_AUDPLL, + MT2701_AUD_AUDPLL_D4, + MT2701_AUD_AUDPLL_D8, + MT2701_AUD_AUDPLL_D16, + MT2701_AUD_AUDPLL_D24, + MT2701_AUD_AUDINTBUS, + MT2701_AUD_CLK_26M, + MT2701_AUD_SYSPLL1_D4, + MT2701_AUD_AUD_K1_SRC_SEL, + MT2701_AUD_AUD_K2_SRC_SEL, + MT2701_AUD_AUD_K3_SRC_SEL, + MT2701_AUD_AUD_K4_SRC_SEL, + MT2701_AUD_AUD_K5_SRC_SEL, + MT2701_AUD_AUD_K6_SRC_SEL, + MT2701_AUD_AUD_K1_SRC_DIV, + MT2701_AUD_AUD_K2_SRC_DIV, + MT2701_AUD_AUD_K3_SRC_DIV, + MT2701_AUD_AUD_K4_SRC_DIV, + MT2701_AUD_AUD_K5_SRC_DIV, + MT2701_AUD_AUD_K6_SRC_DIV, + MT2701_AUD_AUD_I2S1_MCLK, + MT2701_AUD_AUD_I2S2_MCLK, + MT2701_AUD_AUD_I2S3_MCLK, + MT2701_AUD_AUD_I2S4_MCLK, + MT2701_AUD_AUD_I2S5_MCLK, + MT2701_AUD_AUD_I2S6_MCLK, + MT2701_AUD_ASM_M_SEL, + MT2701_AUD_ASM_H_SEL, + MT2701_AUD_UNIVPLL2_D4, + MT2701_AUD_UNIVPLL2_D2, + MT2701_AUD_SYSPLL_D5, + MT2701_CLOCK_NUM +}; + +static const unsigned int mt2701_afe_backup_list[] = { + AUDIO_TOP_CON0, + AUDIO_TOP_CON4, + AUDIO_TOP_CON5, + ASYS_TOP_CON, + AFE_CONN0, + AFE_CONN1, + AFE_CONN2, + AFE_CONN3, + AFE_CONN15, + AFE_CONN16, + AFE_CONN17, + AFE_CONN18, + AFE_CONN19, + AFE_CONN20, + AFE_CONN21, + AFE_CONN22, + AFE_DAC_CON0, + AFE_MEMIF_PBUF_SIZE, +}; + +struct snd_pcm_substream; +struct mtk_base_irq_data; + +struct mt2701_i2s_data { + int i2s_ctrl_reg; + int i2s_pwn_shift; + int i2s_asrc_fs_shift; + int i2s_asrc_fs_mask; +}; + +enum mt2701_i2s_dir { + I2S_OUT, + I2S_IN, + I2S_DIR_NUM, +}; + +struct mt2701_i2s_path { + int dai_id; + int mclk_rate; + int on[I2S_DIR_NUM]; + int occupied[I2S_DIR_NUM]; + const struct mt2701_i2s_data *i2s_data[2]; +}; + +struct mt2701_afe_private { + struct clk *clocks[MT2701_CLOCK_NUM]; + struct mt2701_i2s_path i2s_path[MT2701_I2S_NUM]; + bool mrg_enable[MT2701_STREAM_DIR_NUM]; +}; + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c new file mode 100644 index 000000000..34a612348 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -0,0 +1,1656 @@ +/* + * Mediatek ALSA SoC AFE platform driver for 2701 + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.com> + * Ir Lian <ir.lian@mediatek.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 and + * only 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. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> + +#include "mt2701-afe-common.h" + +#include "mt2701-afe-clock-ctrl.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +#define AFE_IRQ_STATUS_BITS 0xff + +static const struct snd_pcm_hardware mt2701_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 1024, + .period_bytes_max = 1024 * 256, + .periods_min = 4, + .periods_max = 1024, + .buffer_bytes_max = 1024 * 1024 * 16, + .fifo_size = 0, +}; + +struct mt2701_afe_rate { + unsigned int rate; + unsigned int regvalue; +}; + +static const struct mt2701_afe_rate mt2701_afe_i2s_rates[] = { + { .rate = 8000, .regvalue = 0 }, + { .rate = 12000, .regvalue = 1 }, + { .rate = 16000, .regvalue = 2 }, + { .rate = 24000, .regvalue = 3 }, + { .rate = 32000, .regvalue = 4 }, + { .rate = 48000, .regvalue = 5 }, + { .rate = 96000, .regvalue = 6 }, + { .rate = 192000, .regvalue = 7 }, + { .rate = 384000, .regvalue = 8 }, + { .rate = 7350, .regvalue = 16 }, + { .rate = 11025, .regvalue = 17 }, + { .rate = 14700, .regvalue = 18 }, + { .rate = 22050, .regvalue = 19 }, + { .rate = 29400, .regvalue = 20 }, + { .rate = 44100, .regvalue = 21 }, + { .rate = 88200, .regvalue = 22 }, + { .rate = 176400, .regvalue = 23 }, + { .rate = 352800, .regvalue = 24 }, +}; + +static int mt2701_dai_num_to_i2s(struct mtk_base_afe *afe, int num) +{ + int val = num - MT2701_IO_I2S; + + if (val < 0 || val >= MT2701_I2S_NUM) { + dev_err(afe->dev, "%s, num not available, num %d, val %d\n", + __func__, num, val); + return -EINVAL; + } + return val; +} + +static int mt2701_afe_i2s_fs(unsigned int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt2701_afe_i2s_rates); i++) + if (mt2701_afe_i2s_rates[i].rate == sample_rate) + return mt2701_afe_i2s_rates[i].regvalue; + + return -EINVAL; +} + +static int mt2701_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num; + int ret = 0; + + if (i2s_num < 0) + return i2s_num; + + /* enable mclk */ + ret = clk_prepare_enable(afe_priv->clocks[clk_num]); + if (ret) + dev_err(afe->dev, "Failed to enable mclk for I2S: %d\n", + i2s_num); + + return ret; +} + +static int mt2701_afe_i2s_path_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int dir_invert) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + const struct mt2701_i2s_data *i2s_data; + int stream_dir = substream->stream; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (dir_invert) { + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + stream_dir = SNDRV_PCM_STREAM_CAPTURE; + else + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + } + i2s_data = i2s_path->i2s_data[stream_dir]; + + i2s_path->on[stream_dir]--; + if (i2s_path->on[stream_dir] < 0) { + dev_warn(afe->dev, "i2s_path->on: %d, dir: %d\n", + i2s_path->on[stream_dir], stream_dir); + i2s_path->on[stream_dir] = 0; + } + if (i2s_path->on[stream_dir]) + return 0; + + /* disable i2s */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + 1 << i2s_data->i2s_pwn_shift, + 1 << i2s_data->i2s_pwn_shift); + return 0; +} + +static void mt2701_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num; + + if (i2s_num < 0) + return; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (i2s_path->occupied[substream->stream]) + i2s_path->occupied[substream->stream] = 0; + else + goto I2S_UNSTART; + + mt2701_afe_i2s_path_shutdown(substream, dai, 0); + + /* need to disable i2s-out path when disable i2s-in */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mt2701_afe_i2s_path_shutdown(substream, dai, 1); + +I2S_UNSTART: + /* disable mclk */ + clk_disable_unprepare(afe_priv->clocks[clk_num]); +} + +static int mt2701_i2s_path_prepare_enable(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int dir_invert) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + const struct mt2701_i2s_data *i2s_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + int reg, fs, w_len = 1; /* now we support bck 64bits only */ + int stream_dir = substream->stream; + unsigned int mask = 0, val = 0; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (dir_invert) { + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + stream_dir = SNDRV_PCM_STREAM_CAPTURE; + else + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + } + i2s_data = i2s_path->i2s_data[stream_dir]; + + /* no need to enable if already done */ + i2s_path->on[stream_dir]++; + + if (i2s_path->on[stream_dir] != 1) + return 0; + + fs = mt2701_afe_i2s_fs(runtime->rate); + + mask = ASYS_I2S_CON_FS | + ASYS_I2S_CON_I2S_COUPLE_MODE | /* 0 */ + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE; + + val = ASYS_I2S_CON_FS_SET(fs) | + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE_SET(w_len); + + if (stream_dir == SNDRV_PCM_STREAM_CAPTURE) { + mask |= ASYS_I2S_IN_PHASE_FIX; + val |= ASYS_I2S_IN_PHASE_FIX; + } + + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, mask, val); + + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + reg = ASMO_TIMING_CON1; + else + reg = ASMI_TIMING_CON1; + + regmap_update_bits(afe->regmap, reg, + i2s_data->i2s_asrc_fs_mask + << i2s_data->i2s_asrc_fs_shift, + fs << i2s_data->i2s_asrc_fs_shift); + + /* enable i2s */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + 1 << i2s_data->i2s_pwn_shift, + 0 << i2s_data->i2s_pwn_shift); + + /* reset i2s hw status before enable */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, ASYS_I2S_CON_RESET); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, 0); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, ASYS_I2S_CON_I2S_EN); + return 0; +} + +static int mt2701_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int clk_domain; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + int mclk_rate; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + mclk_rate = i2s_path->mclk_rate; + + if (i2s_path->occupied[substream->stream]) + return -EBUSY; + i2s_path->occupied[substream->stream] = 1; + + if (MT2701_PLL_DOMAIN_0_RATE % mclk_rate == 0) { + clk_domain = 0; + } else if (MT2701_PLL_DOMAIN_1_RATE % mclk_rate == 0) { + clk_domain = 1; + } else { + dev_err(dai->dev, "%s() bad mclk rate %d\n", + __func__, mclk_rate); + return -EINVAL; + } + mt2701_mclk_configuration(afe, i2s_num, clk_domain, mclk_rate); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mt2701_i2s_path_prepare_enable(substream, dai, 0); + } else { + /* need to enable i2s-out path when enable i2s-in */ + /* prepare for another direction "out" */ + mt2701_i2s_path_prepare_enable(substream, dai, 1); + /* prepare for "in" */ + mt2701_i2s_path_prepare_enable(substream, dai, 0); + } + + return 0; +} + +static int mt2701_afe_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + + if (i2s_num < 0) + return i2s_num; + + /* mclk */ + if (dir == SND_SOC_CLOCK_IN) { + dev_warn(dai->dev, + "%s() warning: mt2701 doesn't support mclk input\n", + __func__); + return -EINVAL; + } + afe_priv->i2s_path[i2s_num].mclk_rate = freq; + return 0; +} + +static int mt2701_btmrg_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_MRGIF, 0); + + afe_priv->mrg_enable[substream->stream] = 1; + return 0; +} + +static int mt2701_btmrg_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 mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_fs; + u32 val, msk; + + stream_fs = params_rate(params); + + if ((stream_fs != 8000) && (stream_fs != 16000)) { + dev_err(afe->dev, "%s() btmgr not supprt this stream_fs %d\n", + __func__, stream_fs); + return -EINVAL; + } + + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_I2S_MODE_MASK, + AFE_MRGIF_CON_I2S_MODE_32K); + + val = AFE_DAIBT_CON0_BT_FUNC_EN | AFE_DAIBT_CON0_BT_FUNC_RDY + | AFE_DAIBT_CON0_MRG_USE; + msk = val; + + if (stream_fs == 16000) + val |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + msk |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, msk, val); + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, + AFE_DAIBT_CON0_DAIBT_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, + AFE_MRGIF_CON_MRG_I2S_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, + AFE_MRGIF_CON_MRG_EN); + return 0; +} + +static void mt2701_btmrg_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + /* if the other direction stream is not occupied */ + if (!afe_priv->mrg_enable[!substream->stream]) { + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_MRGIF, + AUDIO_TOP_CON4_PDN_MRGIF); + } + afe_priv->mrg_enable[substream->stream] = 0; +} + +static int mt2701_simple_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_dir = substream->stream; + int memif_num = rtd->cpu_dai->id; + struct mtk_base_afe_memif *memif_tmp; + + /* can't run single DL & DLM at the same time */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) { + memif_tmp = &afe->memif[MT2701_MEMIF_DLM]; + if (memif_tmp->substream) { + dev_warn(afe->dev, "%s memif is not available, stream_dir %d, memif_num %d\n", + __func__, stream_dir, memif_num); + return -EBUSY; + } + } + return mtk_afe_fe_startup(substream, dai); +} + +static int mt2701_simple_fe_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 mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_dir = substream->stream; + + /* single DL use PAIR_INTERLEAVE */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE); + } + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif_tmp; + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_tmp = &afe->memif[i]; + if (memif_tmp->substream) + return -EBUSY; + } + + /* enable agent for all signal DL (due to hw design) */ + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 0 << memif_data->agent_disable_shift); + } + + return mtk_afe_fe_startup(substream, dai); +} + +static void mt2701_dlm_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 1 << memif_data->agent_disable_shift); + } + return mtk_afe_fe_shutdown(substream, dai); +} + +static int mt2701_dlm_fe_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 mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int channels = params_channels(params); + + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_32BYTES); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_CH(channels)); + + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif_tmp = &afe->memif[MT2701_MEMIF_DL1]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, + 1 << memif_tmp->data->enable_shift); + mtk_afe_fe_trigger(substream, cmd, dai); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_afe_fe_trigger(substream, cmd, dai); + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, 0); + + return 0; + default: + return -EINVAL; + } +} + +static int mt2701_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int fs; + + if (rtd->cpu_dai->id != MT2701_MEMIF_ULBT) + fs = mt2701_afe_i2s_fs(rate); + else + fs = (rate == 16000 ? 1 : 0); + return fs; +} + +static int mt2701_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + return mt2701_afe_i2s_fs(rate); +} + +/* FE DAIs */ +static const struct snd_soc_dai_ops mt2701_single_memif_dai_ops = { + .startup = mt2701_simple_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mt2701_simple_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, + +}; + +static const struct snd_soc_dai_ops mt2701_dlm_memif_dai_ops = { + .startup = mt2701_dlm_fe_startup, + .shutdown = mt2701_dlm_fe_shutdown, + .hw_params = mt2701_dlm_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mt2701_dlm_fe_trigger, +}; + +/* I2S BE DAIs */ +static const struct snd_soc_dai_ops mt2701_afe_i2s_ops = { + .startup = mt2701_afe_i2s_startup, + .shutdown = mt2701_afe_i2s_shutdown, + .prepare = mt2701_afe_i2s_prepare, + .set_sysclk = mt2701_afe_i2s_set_sysclk, +}; + +/* MRG BE DAIs */ +static struct snd_soc_dai_ops mt2701_btmrg_ops = { + .startup = mt2701_btmrg_startup, + .shutdown = mt2701_btmrg_shutdown, + .hw_params = mt2701_btmrg_hw_params, +}; + +static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "PCM_multi", + .id = MT2701_MEMIF_DLM, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLM", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_dlm_memif_dai_ops, + }, + { + .name = "PCM0", + .id = MT2701_MEMIF_UL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM1", + .id = MT2701_MEMIF_UL2, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL2", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_DL", + .id = MT2701_MEMIF_DLBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_UL", + .id = MT2701_MEMIF_ULBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "ULBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + /* BE DAIs */ + { + .name = "I2S0", + .id = MT2701_IO_I2S, + .playback = { + .stream_name = "I2S0 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .capture = { + .stream_name = "I2S0 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S1", + .id = MT2701_IO_2ND_I2S, + .playback = { + .stream_name = "I2S1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S2", + .id = MT2701_IO_3RD_I2S, + .playback = { + .stream_name = "I2S2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S3", + .id = MT2701_IO_4TH_I2S, + .playback = { + .stream_name = "I2S3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "MRG BT", + .id = MT2701_IO_MRG, + .playback = { + .stream_name = "BT Playback", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "BT Capture", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_btmrg_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_kcontrol_new mt2701_afe_o00_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I00 Switch", AFE_CONN0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o01_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I01 Switch", AFE_CONN1, 1, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o02_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I02 Switch", AFE_CONN2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o03_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 3, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o14_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I26 Switch", AFE_CONN14, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o15_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I12 Switch", AFE_CONN15, 12, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o16_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I13 Switch", AFE_CONN16, 13, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o17_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I14 Switch", AFE_CONN17, 14, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o18_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I15 Switch", AFE_CONN18, 15, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o19_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I16 Switch", AFE_CONN19, 16, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o20_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN20, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o21_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN21, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o22_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I19 Switch", AFE_CONN22, 19, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o23_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I20 Switch", AFE_CONN23, 20, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o24_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I21 Switch", AFE_CONN24, 21, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o31_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I35 Switch", AFE_CONN41, 9, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_i02_mix[] = { + SOC_DAPM_SINGLE("I2S0 Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s0[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S0 Out Switch", + ASYS_I2SO1_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s1[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S1 Out Switch", + ASYS_I2SO2_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s2[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S2 Out Switch", + PWR2_TOP_CON, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s3[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S3 Out Switch", + PWR2_TOP_CON, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s4[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S4 Out Switch", + PWR2_TOP_CON, 19, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc0[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc0 out Switch", AUDIO_TOP_CON4, 14, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc1[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc1 out Switch", AUDIO_TOP_CON4, 15, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc2[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc2 out Switch", PWR2_TOP_CON, 6, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc3[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc3 out Switch", PWR2_TOP_CON, 7, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc4[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc4 out Switch", PWR2_TOP_CON, 8, 1, + 1), +}; + +static const struct snd_soc_dapm_widget mt2701_afe_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("I00", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I02", SND_SOC_NOPM, 0, 0, mt2701_afe_i02_mix, + ARRAY_SIZE(mt2701_afe_i02_mix)), + SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I12", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I13", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I14", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I15", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I16", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I19", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I26", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I35", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("O00", SND_SOC_NOPM, 0, 0, mt2701_afe_o00_mix, + ARRAY_SIZE(mt2701_afe_o00_mix)), + SND_SOC_DAPM_MIXER("O01", SND_SOC_NOPM, 0, 0, mt2701_afe_o01_mix, + ARRAY_SIZE(mt2701_afe_o01_mix)), + SND_SOC_DAPM_MIXER("O02", SND_SOC_NOPM, 0, 0, mt2701_afe_o02_mix, + ARRAY_SIZE(mt2701_afe_o02_mix)), + SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, mt2701_afe_o03_mix, + ARRAY_SIZE(mt2701_afe_o03_mix)), + SND_SOC_DAPM_MIXER("O14", SND_SOC_NOPM, 0, 0, mt2701_afe_o14_mix, + ARRAY_SIZE(mt2701_afe_o14_mix)), + SND_SOC_DAPM_MIXER("O15", SND_SOC_NOPM, 0, 0, mt2701_afe_o15_mix, + ARRAY_SIZE(mt2701_afe_o15_mix)), + SND_SOC_DAPM_MIXER("O16", SND_SOC_NOPM, 0, 0, mt2701_afe_o16_mix, + ARRAY_SIZE(mt2701_afe_o16_mix)), + SND_SOC_DAPM_MIXER("O17", SND_SOC_NOPM, 0, 0, mt2701_afe_o17_mix, + ARRAY_SIZE(mt2701_afe_o17_mix)), + SND_SOC_DAPM_MIXER("O18", SND_SOC_NOPM, 0, 0, mt2701_afe_o18_mix, + ARRAY_SIZE(mt2701_afe_o18_mix)), + SND_SOC_DAPM_MIXER("O19", SND_SOC_NOPM, 0, 0, mt2701_afe_o19_mix, + ARRAY_SIZE(mt2701_afe_o19_mix)), + SND_SOC_DAPM_MIXER("O20", SND_SOC_NOPM, 0, 0, mt2701_afe_o20_mix, + ARRAY_SIZE(mt2701_afe_o20_mix)), + SND_SOC_DAPM_MIXER("O21", SND_SOC_NOPM, 0, 0, mt2701_afe_o21_mix, + ARRAY_SIZE(mt2701_afe_o21_mix)), + SND_SOC_DAPM_MIXER("O22", SND_SOC_NOPM, 0, 0, mt2701_afe_o22_mix, + ARRAY_SIZE(mt2701_afe_o22_mix)), + SND_SOC_DAPM_MIXER("O31", SND_SOC_NOPM, 0, 0, mt2701_afe_o31_mix, + ARRAY_SIZE(mt2701_afe_o31_mix)), + + SND_SOC_DAPM_MIXER("I12I13", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s0, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s0)), + SND_SOC_DAPM_MIXER("I14I15", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s1, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s1)), + SND_SOC_DAPM_MIXER("I16I17", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s2, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s2)), + SND_SOC_DAPM_MIXER("I18I19", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s3, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s3)), + + SND_SOC_DAPM_MIXER("ASRC_O0", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc0, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc0)), + SND_SOC_DAPM_MIXER("ASRC_O1", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc1, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc1)), + SND_SOC_DAPM_MIXER("ASRC_O2", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc2, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc2)), + SND_SOC_DAPM_MIXER("ASRC_O3", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc3, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc3)), +}; + +static const struct snd_soc_dapm_route mt2701_afe_pcm_routes[] = { + {"I12", NULL, "DL1"}, + {"I13", NULL, "DL1"}, + {"I35", NULL, "DLBT"}, + + {"I2S0 Playback", NULL, "O15"}, + {"I2S0 Playback", NULL, "O16"}, + + {"I2S1 Playback", NULL, "O17"}, + {"I2S1 Playback", NULL, "O18"}, + {"I2S2 Playback", NULL, "O19"}, + {"I2S2 Playback", NULL, "O20"}, + {"I2S3 Playback", NULL, "O21"}, + {"I2S3 Playback", NULL, "O22"}, + {"BT Playback", NULL, "O31"}, + + {"UL1", NULL, "O00"}, + {"UL1", NULL, "O01"}, + {"UL2", NULL, "O02"}, + {"UL2", NULL, "O03"}, + {"ULBT", NULL, "O14"}, + + {"I00", NULL, "I2S0 Capture"}, + {"I01", NULL, "I2S0 Capture"}, + + {"I02", NULL, "I2S1 Capture"}, + {"I03", NULL, "I2S1 Capture"}, + /* I02,03 link to UL2, also need to open I2S0 */ + {"I02", "I2S0 Switch", "I2S0 Capture"}, + + {"I26", NULL, "BT Capture"}, + + {"ASRC_O0", "Asrc0 out Switch", "DLM"}, + {"ASRC_O1", "Asrc1 out Switch", "DLM"}, + {"ASRC_O2", "Asrc2 out Switch", "DLM"}, + {"ASRC_O3", "Asrc3 out Switch", "DLM"}, + + {"I12I13", "Multich I2S0 Out Switch", "ASRC_O0"}, + {"I14I15", "Multich I2S1 Out Switch", "ASRC_O1"}, + {"I16I17", "Multich I2S2 Out Switch", "ASRC_O2"}, + {"I18I19", "Multich I2S3 Out Switch", "ASRC_O3"}, + + { "I12", NULL, "I12I13" }, + { "I13", NULL, "I12I13" }, + { "I14", NULL, "I14I15" }, + { "I15", NULL, "I14I15" }, + { "I16", NULL, "I16I17" }, + { "I17", NULL, "I16I17" }, + { "I18", NULL, "I18I19" }, + { "I19", NULL, "I18I19" }, + + { "O00", "I00 Switch", "I00" }, + { "O01", "I01 Switch", "I01" }, + { "O02", "I02 Switch", "I02" }, + { "O03", "I03 Switch", "I03" }, + { "O14", "I26 Switch", "I26" }, + { "O15", "I12 Switch", "I12" }, + { "O16", "I13 Switch", "I13" }, + { "O17", "I14 Switch", "I14" }, + { "O18", "I15 Switch", "I15" }, + { "O19", "I16 Switch", "I16" }, + { "O20", "I17 Switch", "I17" }, + { "O21", "I18 Switch", "I18" }, + { "O22", "I19 Switch", "I19" }, + { "O31", "I35 Switch", "I35" }, + +}; + +static const struct snd_soc_component_driver mt2701_afe_pcm_dai_component = { + .name = "mt2701-afe-pcm-dai", + .dapm_widgets = mt2701_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_afe_pcm_widgets), + .dapm_routes = mt2701_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt2701_afe_pcm_routes), +}; + +static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { + { + .name = "DL1", + .id = MT2701_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 16, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 1, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 6, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL2", + .id = MT2701_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 17, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 2, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 7, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL3", + .id = MT2701_MEMIF_DL3, + .reg_ofs_base = AFE_DL3_BASE, + .reg_ofs_cur = AFE_DL3_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 18, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 3, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 4, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 8, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL4", + .id = MT2701_MEMIF_DL4, + .reg_ofs_base = AFE_DL4_BASE, + .reg_ofs_cur = AFE_DL4_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 19, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 4, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 9, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL5", + .id = MT2701_MEMIF_DL5, + .reg_ofs_base = AFE_DL5_BASE, + .reg_ofs_cur = AFE_DL5_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 20, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 20, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 5, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 10, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLM", + .id = MT2701_MEMIF_DLM, + .reg_ofs_base = AFE_DLMCH_BASE, + .reg_ofs_cur = AFE_DLMCH_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 7, + .hd_reg = AFE_MEMIF_PBUF_SIZE, + .hd_shift = 28, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 12, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL1", + .id = MT2701_MEMIF_UL1, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 0, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 10, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 0, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL2", + .id = MT2701_MEMIF_UL2, + .reg_ofs_base = AFE_UL2_BASE, + .reg_ofs_cur = AFE_UL2_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 2, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 11, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 1, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL3", + .id = MT2701_MEMIF_UL3, + .reg_ofs_base = AFE_UL3_BASE, + .reg_ofs_cur = AFE_UL3_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 4, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 12, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 2, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL4", + .id = MT2701_MEMIF_UL4, + .reg_ofs_base = AFE_UL4_BASE, + .reg_ofs_cur = AFE_UL4_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 6, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 13, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 3, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL5", + .id = MT2701_MEMIF_UL5, + .reg_ofs_base = AFE_UL5_BASE, + .reg_ofs_cur = AFE_UL5_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 20, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 8, + .fs_maskbit = 0x1f, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 14, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 4, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLBT", + .id = MT2701_MEMIF_DLBT, + .reg_ofs_base = AFE_ARB1_BASE, + .reg_ofs_cur = AFE_ARB1_CUR, + .fs_reg = AFE_DAC_CON3, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 22, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 8, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 14, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 13, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "ULBT", + .id = MT2701_MEMIF_ULBT, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 30, + .fs_maskbit = 0x1, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 17, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 20, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 16, + .msb_reg = -1, + .msb_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT2701_IRQ_ASYS_END] = { + { + .id = MT2701_IRQ_ASYS_IRQ1, + .irq_cnt_reg = ASYS_IRQ1_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ1_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ1_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 0, + }, + { + .id = MT2701_IRQ_ASYS_IRQ2, + .irq_cnt_reg = ASYS_IRQ2_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ2_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ2_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 1, + }, + { + .id = MT2701_IRQ_ASYS_IRQ3, + .irq_cnt_reg = ASYS_IRQ3_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ3_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ3_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 2, + } +}; + +static const struct mt2701_i2s_data mt2701_i2s_data[MT2701_I2S_NUM][2] = { + { + { + .i2s_ctrl_reg = ASYS_I2SO1_CON, + .i2s_pwn_shift = 6, + .i2s_asrc_fs_shift = 0, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN1_CON, + .i2s_pwn_shift = 0, + .i2s_asrc_fs_shift = 0, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO2_CON, + .i2s_pwn_shift = 7, + .i2s_asrc_fs_shift = 5, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN2_CON, + .i2s_pwn_shift = 1, + .i2s_asrc_fs_shift = 5, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO3_CON, + .i2s_pwn_shift = 8, + .i2s_asrc_fs_shift = 10, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN3_CON, + .i2s_pwn_shift = 2, + .i2s_asrc_fs_shift = 10, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO4_CON, + .i2s_pwn_shift = 9, + .i2s_asrc_fs_shift = 15, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN4_CON, + .i2s_pwn_shift = 3, + .i2s_asrc_fs_shift = 15, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, +}; + +static const struct regmap_config mt2701_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_END_ADDR, + .cache_type = REGCACHE_NONE, +}; + +static irqreturn_t mt2701_asys_isr(int irq_id, void *dev) +{ + int id; + struct mtk_base_afe *afe = dev; + struct mtk_base_afe_memif *memif; + struct mtk_base_afe_irq *irq; + u32 status; + + regmap_read(afe->regmap, ASYS_IRQ_STATUS, &status); + regmap_write(afe->regmap, ASYS_IRQ_CLR, status); + + for (id = 0; id < MT2701_MEMIF_NUM; ++id) { + memif = &afe->memif[id]; + if (memif->irq_usage < 0) + continue; + irq = &afe->irqs[memif->irq_usage]; + if (status & 1 << (irq->irq_data->irq_clr_shift)) + snd_pcm_period_elapsed(memif->substream); + } + return IRQ_HANDLED; +} + +static int mt2701_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + mt2701_afe_disable_clock(afe); + return 0; +} + +static int mt2701_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + return mt2701_afe_enable_clock(afe); +} + +static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) +{ + int ret, i; + unsigned int irq_id; + struct mtk_base_afe *afe; + struct mt2701_afe_private *afe_priv; + struct resource *res; + struct device *dev; + + ret = 0; + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + afe_priv = afe->platform_priv; + + afe->dev = &pdev->dev; + dev = afe->dev; + + irq_id = platform_get_irq(pdev, 0); + if (!irq_id) { + dev_err(dev, "%s no irq found\n", dev->of_node->name); + return -ENXIO; + } + ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) { + dev_err(dev, "could not request_irq for asys-isr\n"); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &mt2701_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + mutex_init(&afe->irq_alloc_lock); + + /* memif initialize */ + afe->memif_size = MT2701_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + + if (!afe->memif) + return -ENOMEM; + + for (i = 0; i < afe->memif_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = -1; + } + + /* irq initialize */ + afe->irqs_size = MT2701_IRQ_ASYS_END; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + /* I2S initialize */ + for (i = 0; i < MT2701_I2S_NUM; i++) { + afe_priv->i2s_path[i].i2s_data[I2S_OUT] + = &mt2701_i2s_data[i][I2S_OUT]; + afe_priv->i2s_path[i].i2s_data[I2S_IN] + = &mt2701_i2s_data[i][I2S_IN]; + } + + afe->mtk_afe_hardware = &mt2701_afe_hardware; + afe->memif_fs = mt2701_memif_fs; + afe->irq_fs = mt2701_irq_fs; + + afe->reg_back_up_list = mt2701_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt2701_afe_backup_list); + afe->runtime_resume = mt2701_afe_runtime_resume; + afe->runtime_suspend = mt2701_afe_runtime_suspend; + + /* initial audio related clock */ + ret = mt2701_init_clock(afe); + if (ret) { + dev_err(dev, "init clock error\n"); + return ret; + } + + platform_set_drvdata(pdev, afe); + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) + goto err_pm_disable; + + ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform); + if (ret) { + dev_warn(dev, "err_platform\n"); + goto err_platform; + } + + ret = snd_soc_register_component(&pdev->dev, + &mt2701_afe_pcm_dai_component, + mt2701_afe_pcm_dais, + ARRAY_SIZE(mt2701_afe_pcm_dais)); + if (ret) { + dev_warn(dev, "err_dai_component\n"); + goto err_dai_component; + } + + mt2701_afe_runtime_resume(&pdev->dev); + + return 0; + +err_dai_component: + snd_soc_unregister_component(&pdev->dev); + +err_platform: + snd_soc_unregister_platform(&pdev->dev); + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int mt2701_afe_pcm_dev_remove(struct platform_device *pdev) +{ + struct mtk_base_afe *afe = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt2701_afe_runtime_suspend(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + snd_soc_unregister_platform(&pdev->dev); + /* disable afe clock */ + mt2701_afe_disable_clock(afe); + return 0; +} + +static const struct of_device_id mt2701_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt2701-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt2701_afe_pcm_dt_match); + +static const struct dev_pm_ops mt2701_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt2701_afe_runtime_suspend, + mt2701_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt2701_afe_pcm_driver = { + .driver = { + .name = "mt2701-audio", + .of_match_table = mt2701_afe_pcm_dt_match, +#ifdef CONFIG_PM + .pm = &mt2701_afe_pm_ops, +#endif + }, + .probe = mt2701_afe_pcm_dev_probe, + .remove = mt2701_afe_pcm_dev_remove, +}; + +module_platform_driver(mt2701_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 2701"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c new file mode 100644 index 000000000..1e7e8d43f --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -0,0 +1,412 @@ +/* + * mt2701-cs42448.c -- MT2701 CS42448 ALSA SoC machine driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Ir Lian <ir.lian@mediatek.com> + * Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/pinctrl/consumer.h> +#include <linux/of_gpio.h> + +#include "mt2701-afe-common.h" + +struct mt2701_cs42448_private { + int i2s1_in_mux; + int i2s1_in_mux_gpio_sel_1; + int i2s1_in_mux_gpio_sel_2; +}; + +static const char * const i2sin_mux_switch_text[] = { + "ADC_SDOUT2", + "ADC_SDOUT3", + "I2S_IN_1", + "I2S_IN_2", +}; + +static const struct soc_enum i2sin_mux_enum = + SOC_ENUM_SINGLE_EXT(4, i2sin_mux_switch_text); + +static int mt2701_cs42448_i2sin1_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.integer.value[0] = priv->i2s1_in_mux; + return 0; +} + +static int mt2701_cs42448_i2sin1_mux_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + if (ucontrol->value.integer.value[0] == priv->i2s1_in_mux) + return 0; + + switch (ucontrol->value.integer.value[0]) { + case 0: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 1: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 2: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + case 3: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + default: + dev_warn(card->dev, "%s invalid setting\n", __func__); + } + + priv->i2s1_in_mux = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_soc_dapm_widget + mt2701_cs42448_asoc_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_LINE("Tuner In", NULL), + SND_SOC_DAPM_LINE("Satellite Tuner In", NULL), + SND_SOC_DAPM_LINE("AUX In", NULL), +}; + +static const struct snd_kcontrol_new mt2701_cs42448_controls[] = { + SOC_DAPM_PIN_SWITCH("Line Out Jack"), + SOC_DAPM_PIN_SWITCH("AMIC"), + SOC_DAPM_PIN_SWITCH("Tuner In"), + SOC_DAPM_PIN_SWITCH("Satellite Tuner In"), + SOC_DAPM_PIN_SWITCH("AUX In"), + SOC_ENUM_EXT("I2SIN1_MUX_Switch", i2sin_mux_enum, + mt2701_cs42448_i2sin1_mux_get, + mt2701_cs42448_i2sin1_mux_set), +}; + +static const unsigned int mt2701_cs42448_sampling_rates[] = {48000}; + +static struct snd_pcm_hw_constraint_list mt2701_cs42448_constraints_rates = { + .count = ARRAY_SIZE(mt2701_cs42448_sampling_rates), + .list = mt2701_cs42448_sampling_rates, + .mask = 0, +}; + +static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream) +{ + int err; + + err = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &mt2701_cs42448_constraints_rates); + if (err < 0) { + dev_err(substream->pcm->card->dev, + "%s snd_pcm_hw_constraint_list failed: 0x%x\n", + __func__, err); + return err; + } + return 0; +} + +static struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { + .startup = mt2701_cs42448_fe_ops_startup, +}; + +static int mt2701_cs42448_be_ops_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 *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + /* mt2701 mclk */ + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + + /* codec mclk */ + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_cs42448_be_ops = { + .hw_params = mt2701_cs42448_be_ops_hw_params +}; + +enum { + DAI_LINK_FE_MULTI_CH_OUT, + DAI_LINK_FE_PCM0_IN, + DAI_LINK_FE_PCM1_IN, + DAI_LINK_FE_BT_OUT, + DAI_LINK_FE_BT_IN, + DAI_LINK_BE_I2S0, + DAI_LINK_BE_I2S1, + DAI_LINK_BE_I2S2, + DAI_LINK_BE_I2S3, + DAI_LINK_BE_MRG_BT, +}; + +static struct snd_soc_dai_link mt2701_cs42448_dai_links[] = { + /* FE */ + [DAI_LINK_FE_MULTI_CH_OUT] = { + .name = "mt2701-cs42448-multi-ch-out", + .stream_name = "mt2701-cs42448-multi-ch-out", + .cpu_dai_name = "PCM_multi", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_PCM0_IN] = { + .name = "mt2701-cs42448-pcm0", + .stream_name = "mt2701-cs42448-pcm0-data-UL", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_PCM1_IN] = { + .name = "mt2701-cs42448-pcm1-data-UL", + .stream_name = "mt2701-cs42448-pcm1-data-UL", + .cpu_dai_name = "PCM1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_BT_OUT] = { + .name = "mt2701-cs42448-pcm-BT-out", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_DL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_BT_IN] = { + .name = "mt2701-cs42448-pcm-BT-in", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_UL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + [DAI_LINK_BE_I2S0] = { + .name = "mt2701-cs42448-I2S0", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S1] = { + .name = "mt2701-cs42448-I2S1", + .cpu_dai_name = "I2S1", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S2] = { + .name = "mt2701-cs42448-I2S2", + .cpu_dai_name = "I2S2", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S3] = { + .name = "mt2701-cs42448-I2S3", + .cpu_dai_name = "I2S3", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_MRG_BT] = { + .name = "mt2701-cs42448-MRG-BT", + .cpu_dai_name = "MRG BT", + .no_pcm = 1, + .codec_dai_name = "bt-sco-pcm-wb", + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_cs42448_soc_card = { + .name = "mt2701-cs42448", + .owner = THIS_MODULE, + .dai_link = mt2701_cs42448_dai_links, + .num_links = ARRAY_SIZE(mt2701_cs42448_dai_links), + .controls = mt2701_cs42448_controls, + .num_controls = ARRAY_SIZE(mt2701_cs42448_controls), + .dapm_widgets = mt2701_cs42448_asoc_card_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_cs42448_asoc_card_dapm_widgets), +}; + +static int mt2701_cs42448_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_cs42448_soc_card; + int ret; + int i; + struct device_node *platform_node, *codec_node, *codec_node_bt_mrg; + struct mt2701_cs42448_private *priv = + devm_kzalloc(&pdev->dev, sizeof(struct mt2701_cs42448_private), + GFP_KERNEL); + struct device *dev = &pdev->dev; + + if (!priv) + return -ENOMEM; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].platform_name) + continue; + mt2701_cs42448_dai_links[i].platform_of_node = platform_node; + } + + card->dev = dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].codec_name) + continue; + mt2701_cs42448_dai_links[i].codec_of_node = codec_node; + } + + codec_node_bt_mrg = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec-bt-mrg", 0); + if (!codec_node_bt_mrg) { + dev_err(&pdev->dev, + "Property 'audio-codec-bt-mrg' missing or invalid\n"); + return -EINVAL; + } + mt2701_cs42448_dai_links[DAI_LINK_BE_MRG_BT].codec_of_node + = codec_node_bt_mrg; + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + return ret; + } + + priv->i2s1_in_mux_gpio_sel_1 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio1", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_1)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_1, + "i2s1_in_mux_gpio_sel_1"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_1, 0); + } + + priv->i2s1_in_mux_gpio_sel_2 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio2", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_2)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_2, + "i2s1_in_mux_gpio_sel_2"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail2 %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_2, 0); + } + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_cs42448_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-cs42448-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_cs42448_machine = { + .driver = { + .name = "mt2701-cs42448", + #ifdef CONFIG_OF + .of_match_table = mt2701_cs42448_machine_dt_match, + #endif + }, + .probe = mt2701_cs42448_machine_probe, +}; + +module_platform_driver(mt2701_cs42448_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 CS42448 ALSA SoC machine driver"); +MODULE_AUTHOR("Ir Lian <ir.lian@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 cs42448 soc card"); diff --git a/sound/soc/mediatek/mt2701/mt2701-reg.h b/sound/soc/mediatek/mt2701/mt2701-reg.h new file mode 100644 index 000000000..bb62b1c55 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-reg.h @@ -0,0 +1,186 @@ +/* + * mt2701-reg.h -- Mediatek 2701 audio driver reg definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT2701_REG_H_ +#define _MT2701_REG_H_ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include "mt2701-afe-common.h" + +/***************************************************************************** + * R E G I S T E R D E F I N I T I O N + *****************************************************************************/ +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON4 0x0010 +#define AUDIO_TOP_CON5 0x0014 +#define AFE_DAIBT_CON0 0x001c +#define AFE_MRGIF_CON 0x003c +#define ASMI_TIMING_CON1 0x0100 +#define ASMO_TIMING_CON1 0x0104 +#define PWR1_ASM_CON1 0x0108 +#define ASYS_TOP_CON 0x0600 +#define ASYS_I2SIN1_CON 0x0604 +#define ASYS_I2SIN2_CON 0x0608 +#define ASYS_I2SIN3_CON 0x060c +#define ASYS_I2SIN4_CON 0x0610 +#define ASYS_I2SIN5_CON 0x0614 +#define ASYS_I2SO1_CON 0x061C +#define ASYS_I2SO2_CON 0x0620 +#define ASYS_I2SO3_CON 0x0624 +#define ASYS_I2SO4_CON 0x0628 +#define ASYS_I2SO5_CON 0x062c +#define PWR2_TOP_CON 0x0634 +#define AFE_CONN0 0x06c0 +#define AFE_CONN1 0x06c4 +#define AFE_CONN2 0x06c8 +#define AFE_CONN3 0x06cc +#define AFE_CONN14 0x06f8 +#define AFE_CONN15 0x06fc +#define AFE_CONN16 0x0700 +#define AFE_CONN17 0x0704 +#define AFE_CONN18 0x0708 +#define AFE_CONN19 0x070c +#define AFE_CONN20 0x0710 +#define AFE_CONN21 0x0714 +#define AFE_CONN22 0x0718 +#define AFE_CONN23 0x071c +#define AFE_CONN24 0x0720 +#define AFE_CONN41 0x0764 +#define ASYS_IRQ1_CON 0x0780 +#define ASYS_IRQ2_CON 0x0784 +#define ASYS_IRQ3_CON 0x0788 +#define ASYS_IRQ_CLR 0x07c0 +#define ASYS_IRQ_STATUS 0x07c4 +#define PWR2_ASM_CON1 0x1070 +#define AFE_DAC_CON0 0x1200 +#define AFE_DAC_CON1 0x1204 +#define AFE_DAC_CON2 0x1208 +#define AFE_DAC_CON3 0x120c +#define AFE_DAC_CON4 0x1210 +#define AFE_MEMIF_HD_CON1 0x121c +#define AFE_MEMIF_PBUF_SIZE 0x1238 +#define AFE_MEMIF_HD_CON0 0x123c +#define AFE_DL1_BASE 0x1240 +#define AFE_DL1_CUR 0x1244 +#define AFE_DL2_BASE 0x1250 +#define AFE_DL2_CUR 0x1254 +#define AFE_DL3_BASE 0x1260 +#define AFE_DL3_CUR 0x1264 +#define AFE_DL4_BASE 0x1270 +#define AFE_DL4_CUR 0x1274 +#define AFE_DL5_BASE 0x1280 +#define AFE_DL5_CUR 0x1284 +#define AFE_DLMCH_BASE 0x12a0 +#define AFE_DLMCH_CUR 0x12a4 +#define AFE_ARB1_BASE 0x12b0 +#define AFE_ARB1_CUR 0x12b4 +#define AFE_VUL_BASE 0x1300 +#define AFE_VUL_CUR 0x130c +#define AFE_UL2_BASE 0x1310 +#define AFE_UL2_END 0x1318 +#define AFE_UL2_CUR 0x131c +#define AFE_UL3_BASE 0x1320 +#define AFE_UL3_END 0x1328 +#define AFE_UL3_CUR 0x132c +#define AFE_UL4_BASE 0x1330 +#define AFE_UL4_END 0x1338 +#define AFE_UL4_CUR 0x133c +#define AFE_UL5_BASE 0x1340 +#define AFE_UL5_END 0x1348 +#define AFE_UL5_CUR 0x134c +#define AFE_DAI_BASE 0x1370 +#define AFE_DAI_CUR 0x137c + +/* AUDIO_TOP_CON0 (0x0000) */ +#define AUDIO_TOP_CON0_A1SYS_A2SYS_ON (0x3 << 0) +#define AUDIO_TOP_CON0_PDN_AFE (0x1 << 2) +#define AUDIO_TOP_CON0_PDN_APLL_CK (0x1 << 23) + +/* AUDIO_TOP_CON4 (0x0010) */ +#define AUDIO_TOP_CON4_I2SO1_PWN (0x1 << 6) +#define AUDIO_TOP_CON4_PDN_A1SYS (0x1 << 21) +#define AUDIO_TOP_CON4_PDN_A2SYS (0x1 << 22) +#define AUDIO_TOP_CON4_PDN_AFE_CONN (0x1 << 23) +#define AUDIO_TOP_CON4_PDN_MRGIF (0x1 << 25) + +/* AFE_DAIBT_CON0 (0x001c) */ +#define AFE_DAIBT_CON0_DAIBT_EN (0x1 << 0) +#define AFE_DAIBT_CON0_BT_FUNC_EN (0x1 << 1) +#define AFE_DAIBT_CON0_BT_FUNC_RDY (0x1 << 3) +#define AFE_DAIBT_CON0_BT_WIDE_MODE_EN (0x1 << 9) +#define AFE_DAIBT_CON0_MRG_USE (0x1 << 12) + +/* PWR1_ASM_CON1 (0x0108) */ +#define PWR1_ASM_CON1_INIT_VAL (0x492) + +/* AFE_MRGIF_CON (0x003c) */ +#define AFE_MRGIF_CON_MRG_EN (0x1 << 0) +#define AFE_MRGIF_CON_MRG_I2S_EN (0x1 << 16) +#define AFE_MRGIF_CON_I2S_MODE_MASK (0xf << 20) +#define AFE_MRGIF_CON_I2S_MODE_32K (0x4 << 20) + +/* ASYS_I2SO1_CON (0x061c) */ +#define ASYS_I2SO1_CON_FS (0x1f << 8) +#define ASYS_I2SO1_CON_FS_SET(x) ((x) << 8) +#define ASYS_I2SO1_CON_MULTI_CH (0x1 << 16) +#define ASYS_I2SO1_CON_SIDEGEN (0x1 << 30) +#define ASYS_I2SO1_CON_I2S_EN (0x1 << 0) +/* 0:EIAJ 1:I2S */ +#define ASYS_I2SO1_CON_I2S_MODE (0x1 << 3) +#define ASYS_I2SO1_CON_WIDE_MODE (0x1 << 1) +#define ASYS_I2SO1_CON_WIDE_MODE_SET(x) ((x) << 1) + +/* PWR2_TOP_CON (0x0634) */ +#define PWR2_TOP_CON_INIT_VAL (0xffe1ffff) + +/* ASYS_IRQ_CLR (0x07c0) */ +#define ASYS_IRQ_CLR_ALL (0xffffffff) + +/* PWR2_ASM_CON1 (0x1070) */ +#define PWR2_ASM_CON1_INIT_VAL (0x492492) + +/* AFE_DAC_CON0 (0x1200) */ +#define AFE_DAC_CON0_AFE_ON (0x1 << 0) + +/* AFE_MEMIF_PBUF_SIZE (0x1238) */ +#define AFE_MEMIF_PBUF_SIZE_DLM_MASK (0x1 << 29) +#define AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE (0x0 << 29) +#define AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE (0x1 << 29) +#define DLMCH_BIT_WIDTH_MASK (0x1 << 28) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK (0xf << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH(x) ((x) << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK (0x3 << 12) +#define AFE_MEMIF_PBUF_SIZE_DLM_32BYTES (0x1 << 12) + +/* I2S in/out register bit control */ +#define ASYS_I2S_CON_FS (0x1f << 8) +#define ASYS_I2S_CON_FS_SET(x) ((x) << 8) +#define ASYS_I2S_CON_RESET (0x1 << 30) +#define ASYS_I2S_CON_I2S_EN (0x1 << 0) +#define ASYS_I2S_CON_I2S_COUPLE_MODE (0x1 << 17) +/* 0:EIAJ 1:I2S */ +#define ASYS_I2S_CON_I2S_MODE (0x1 << 3) +#define ASYS_I2S_CON_WIDE_MODE (0x1 << 1) +#define ASYS_I2S_CON_WIDE_MODE_SET(x) ((x) << 1) +#define ASYS_I2S_IN_PHASE_FIX (0x1 << 31) + +#define AFE_END_ADDR 0x15e0 +#endif diff --git a/sound/soc/mediatek/mt8173/Makefile b/sound/soc/mediatek/mt8173/Makefile new file mode 100644 index 000000000..0357b27d2 --- /dev/null +++ b/sound/soc/mediatek/mt8173/Makefile @@ -0,0 +1,7 @@ +# MTK Platform Support +obj-$(CONFIG_SND_SOC_MT8173) += mt8173-afe-pcm.o +# Machine support +obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-common.h b/sound/soc/mediatek/mt8173/mt8173-afe-common.h new file mode 100644 index 000000000..9a4837cc1 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-afe-common.h @@ -0,0 +1,73 @@ +/* + * mt8173_afe_common.h -- Mediatek 8173 audio driver common definitions + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.com> + * Sascha Hauer <s.hauer@pengutronix.de> + * Hidalgo Huang <hidalgo.huang@mediatek.com> + * Ir Lian <ir.lian@mediatek.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 and + * only 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. + */ + +#ifndef _MT8173_AFE_COMMON_H_ +#define _MT8173_AFE_COMMON_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> + +enum { + MT8173_AFE_MEMIF_DL1, + MT8173_AFE_MEMIF_DL2, + MT8173_AFE_MEMIF_VUL, + MT8173_AFE_MEMIF_DAI, + MT8173_AFE_MEMIF_AWB, + MT8173_AFE_MEMIF_MOD_DAI, + MT8173_AFE_MEMIF_HDMI, + MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM1 = MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM2, + MT8173_AFE_IO_PMIC, + MT8173_AFE_IO_I2S, + MT8173_AFE_IO_2ND_I2S, + MT8173_AFE_IO_HW_GAIN1, + MT8173_AFE_IO_HW_GAIN2, + MT8173_AFE_IO_MRG_O, + MT8173_AFE_IO_MRG_I, + MT8173_AFE_IO_DAIBT, + MT8173_AFE_IO_HDMI, +}; + +enum { + MT8173_AFE_IRQ_DL1, + MT8173_AFE_IRQ_DL2, + MT8173_AFE_IRQ_VUL, + MT8173_AFE_IRQ_DAI, + MT8173_AFE_IRQ_AWB, + MT8173_AFE_IRQ_MOD_DAI, + MT8173_AFE_IRQ_HDMI, + MT8173_AFE_IRQ_NUM, +}; + +enum { + MT8173_CLK_INFRASYS_AUD, + MT8173_CLK_TOP_PDN_AUD, + MT8173_CLK_TOP_PDN_AUD_BUS, + MT8173_CLK_I2S0_M, + MT8173_CLK_I2S1_M, + MT8173_CLK_I2S2_M, + MT8173_CLK_I2S3_M, + MT8173_CLK_I2S3_B, + MT8173_CLK_BCK0, + MT8173_CLK_BCK1, + MT8173_CLK_NUM +}; + +#endif diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c new file mode 100644 index 000000000..8a643a35d --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -0,0 +1,1240 @@ +/* + * Mediatek 8173 ALSA SoC AFE platform driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.com> + * Sascha Hauer <s.hauer@pengutronix.de> + * Hidalgo Huang <hidalgo.huang@mediatek.com> + * Ir Lian <ir.lian@mediatek.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 and + * only 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. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include "mt8173-afe-common.h" +#include "../common/mtk-base-afe.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +/***************************************************************************** + * R E G I S T E R D E F I N I T I O N + *****************************************************************************/ +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON1 0x0004 +#define AFE_DAC_CON0 0x0010 +#define AFE_DAC_CON1 0x0014 +#define AFE_I2S_CON1 0x0034 +#define AFE_I2S_CON2 0x0038 +#define AFE_CONN_24BIT 0x006c +#define AFE_MEMIF_MSB 0x00cc + +#define AFE_CONN1 0x0024 +#define AFE_CONN2 0x0028 +#define AFE_CONN3 0x002c +#define AFE_CONN7 0x0460 +#define AFE_CONN8 0x0464 +#define AFE_HDMI_CONN0 0x0390 + +/* Memory interface */ +#define AFE_DL1_BASE 0x0040 +#define AFE_DL1_CUR 0x0044 +#define AFE_DL1_END 0x0048 +#define AFE_DL2_BASE 0x0050 +#define AFE_DL2_CUR 0x0054 +#define AFE_AWB_BASE 0x0070 +#define AFE_AWB_CUR 0x007c +#define AFE_VUL_BASE 0x0080 +#define AFE_VUL_CUR 0x008c +#define AFE_VUL_END 0x0088 +#define AFE_DAI_BASE 0x0090 +#define AFE_DAI_CUR 0x009c +#define AFE_MOD_PCM_BASE 0x0330 +#define AFE_MOD_PCM_CUR 0x033c +#define AFE_HDMI_OUT_BASE 0x0374 +#define AFE_HDMI_OUT_CUR 0x0378 +#define AFE_HDMI_OUT_END 0x037c + +#define AFE_ADDA_TOP_CON0 0x0120 +#define AFE_ADDA2_TOP_CON0 0x0600 + +#define AFE_HDMI_OUT_CON0 0x0370 + +#define AFE_IRQ_MCU_CON 0x03a0 +#define AFE_IRQ_STATUS 0x03a4 +#define AFE_IRQ_CLR 0x03a8 +#define AFE_IRQ_CNT1 0x03ac +#define AFE_IRQ_CNT2 0x03b0 +#define AFE_IRQ_MCU_EN 0x03b4 +#define AFE_IRQ_CNT5 0x03bc +#define AFE_IRQ_CNT7 0x03dc + +#define AFE_TDM_CON1 0x0548 +#define AFE_TDM_CON2 0x054c + +#define AFE_IRQ_STATUS_BITS 0xff + +/* AUDIO_TOP_CON0 (0x0000) */ +#define AUD_TCON0_PDN_SPDF (0x1 << 21) +#define AUD_TCON0_PDN_HDMI (0x1 << 20) +#define AUD_TCON0_PDN_24M (0x1 << 9) +#define AUD_TCON0_PDN_22M (0x1 << 8) +#define AUD_TCON0_PDN_AFE (0x1 << 2) + +/* AFE_I2S_CON1 (0x0034) */ +#define AFE_I2S_CON1_LOW_JITTER_CLK (0x1 << 12) +#define AFE_I2S_CON1_RATE(x) (((x) & 0xf) << 8) +#define AFE_I2S_CON1_FORMAT_I2S (0x1 << 3) +#define AFE_I2S_CON1_EN (0x1 << 0) + +/* AFE_I2S_CON2 (0x0038) */ +#define AFE_I2S_CON2_LOW_JITTER_CLK (0x1 << 12) +#define AFE_I2S_CON2_RATE(x) (((x) & 0xf) << 8) +#define AFE_I2S_CON2_FORMAT_I2S (0x1 << 3) +#define AFE_I2S_CON2_EN (0x1 << 0) + +/* AFE_CONN_24BIT (0x006c) */ +#define AFE_CONN_24BIT_O04 (0x1 << 4) +#define AFE_CONN_24BIT_O03 (0x1 << 3) + +/* AFE_HDMI_CONN0 (0x0390) */ +#define AFE_HDMI_CONN0_O37_I37 (0x7 << 21) +#define AFE_HDMI_CONN0_O36_I36 (0x6 << 18) +#define AFE_HDMI_CONN0_O35_I33 (0x3 << 15) +#define AFE_HDMI_CONN0_O34_I32 (0x2 << 12) +#define AFE_HDMI_CONN0_O33_I35 (0x5 << 9) +#define AFE_HDMI_CONN0_O32_I34 (0x4 << 6) +#define AFE_HDMI_CONN0_O31_I31 (0x1 << 3) +#define AFE_HDMI_CONN0_O30_I30 (0x0 << 0) + +/* AFE_TDM_CON1 (0x0548) */ +#define AFE_TDM_CON1_LRCK_WIDTH(x) (((x) - 1) << 24) +#define AFE_TDM_CON1_32_BCK_CYCLES (0x2 << 12) +#define AFE_TDM_CON1_WLEN_32BIT (0x2 << 8) +#define AFE_TDM_CON1_MSB_ALIGNED (0x1 << 4) +#define AFE_TDM_CON1_1_BCK_DELAY (0x1 << 3) +#define AFE_TDM_CON1_LRCK_INV (0x1 << 2) +#define AFE_TDM_CON1_BCK_INV (0x1 << 1) +#define AFE_TDM_CON1_EN (0x1 << 0) + +enum afe_tdm_ch_start { + AFE_TDM_CH_START_O30_O31 = 0, + AFE_TDM_CH_START_O32_O33, + AFE_TDM_CH_START_O34_O35, + AFE_TDM_CH_START_O36_O37, + AFE_TDM_CH_ZERO, +}; + +static const unsigned int mt8173_afe_backup_list[] = { + AUDIO_TOP_CON0, + AFE_CONN1, + AFE_CONN2, + AFE_CONN7, + AFE_CONN8, + AFE_DAC_CON1, + AFE_DL1_BASE, + AFE_DL1_END, + AFE_VUL_BASE, + AFE_VUL_END, + AFE_HDMI_OUT_BASE, + AFE_HDMI_OUT_END, + AFE_HDMI_CONN0, + AFE_DAC_CON0, +}; + +struct mt8173_afe_private { + struct clk *clocks[MT8173_CLK_NUM]; +}; + +static const struct snd_pcm_hardware mt8173_afe_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .buffer_bytes_max = 256 * 1024, + .period_bytes_min = 512, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 256, + .fifo_size = 0, +}; + +struct mt8173_afe_rate { + unsigned int rate; + unsigned int regvalue; +}; + +static const struct mt8173_afe_rate mt8173_afe_i2s_rates[] = { + { .rate = 8000, .regvalue = 0 }, + { .rate = 11025, .regvalue = 1 }, + { .rate = 12000, .regvalue = 2 }, + { .rate = 16000, .regvalue = 4 }, + { .rate = 22050, .regvalue = 5 }, + { .rate = 24000, .regvalue = 6 }, + { .rate = 32000, .regvalue = 8 }, + { .rate = 44100, .regvalue = 9 }, + { .rate = 48000, .regvalue = 10 }, + { .rate = 88000, .regvalue = 11 }, + { .rate = 96000, .regvalue = 12 }, + { .rate = 174000, .regvalue = 13 }, + { .rate = 192000, .regvalue = 14 }, +}; + +static int mt8173_afe_i2s_fs(unsigned int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt8173_afe_i2s_rates); i++) + if (mt8173_afe_i2s_rates[i].rate == sample_rate) + return mt8173_afe_i2s_rates[i].regvalue; + + return -EINVAL; +} + +static int mt8173_afe_set_i2s(struct mtk_base_afe *afe, unsigned int rate) +{ + unsigned int val; + int fs = mt8173_afe_i2s_fs(rate); + + if (fs < 0) + return -EINVAL; + + /* from external ADC */ + regmap_update_bits(afe->regmap, AFE_ADDA_TOP_CON0, 0x1, 0x1); + regmap_update_bits(afe->regmap, AFE_ADDA2_TOP_CON0, 0x1, 0x1); + + /* set input */ + val = AFE_I2S_CON2_LOW_JITTER_CLK | + AFE_I2S_CON2_RATE(fs) | + AFE_I2S_CON2_FORMAT_I2S; + + regmap_update_bits(afe->regmap, AFE_I2S_CON2, ~AFE_I2S_CON2_EN, val); + + /* set output */ + val = AFE_I2S_CON1_LOW_JITTER_CLK | + AFE_I2S_CON1_RATE(fs) | + AFE_I2S_CON1_FORMAT_I2S; + + regmap_update_bits(afe->regmap, AFE_I2S_CON1, ~AFE_I2S_CON1_EN, val); + return 0; +} + +static void mt8173_afe_set_i2s_enable(struct mtk_base_afe *afe, bool enable) +{ + unsigned int val; + + regmap_read(afe->regmap, AFE_I2S_CON2, &val); + if (!!(val & AFE_I2S_CON2_EN) == enable) + return; + + /* input */ + regmap_update_bits(afe->regmap, AFE_I2S_CON2, 0x1, enable); + + /* output */ + regmap_update_bits(afe->regmap, AFE_I2S_CON1, 0x1, enable); +} + +static int mt8173_afe_dais_enable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) +{ + int ret; + + if (m_ck) { + ret = clk_prepare_enable(m_ck); + if (ret) { + dev_err(afe->dev, "Failed to enable m_ck\n"); + return ret; + } + } + + if (b_ck) { + ret = clk_prepare_enable(b_ck); + if (ret) { + dev_err(afe->dev, "Failed to enable b_ck\n"); + return ret; + } + } + return 0; +} + +static int mt8173_afe_dais_set_clks(struct mtk_base_afe *afe, + struct clk *m_ck, unsigned int mck_rate, + struct clk *b_ck, unsigned int bck_rate) +{ + int ret; + + if (m_ck) { + ret = clk_set_rate(m_ck, mck_rate); + if (ret) { + dev_err(afe->dev, "Failed to set m_ck rate\n"); + return ret; + } + } + + if (b_ck) { + ret = clk_set_rate(b_ck, bck_rate); + if (ret) { + dev_err(afe->dev, "Failed to set b_ck rate\n"); + return ret; + } + } + return 0; +} + +static void mt8173_afe_dais_disable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) +{ + if (m_ck) + clk_disable_unprepare(m_ck); + if (b_ck) + clk_disable_unprepare(b_ck); +} + +static int mt8173_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + + if (dai->active) + return 0; + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, 0); + return 0; +} + +static void mt8173_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + + if (dai->active) + return; + + mt8173_afe_set_i2s_enable(afe, false); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M); +} + +static int mt8173_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + int ret; + + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S1_M], + runtime->rate * 256, NULL, 0); + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S2_M], + runtime->rate * 256, NULL, 0); + /* config I2S */ + ret = mt8173_afe_set_i2s(afe, substream->runtime->rate); + if (ret) + return ret; + + mt8173_afe_set_i2s_enable(afe, true); + + return 0; +} + +static int mt8173_afe_hdmi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + if (dai->active) + return 0; + + mt8173_afe_dais_enable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); + return 0; +} + +static void mt8173_afe_hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + if (dai->active) + return; + + mt8173_afe_dais_disable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); +} + +static int mt8173_afe_hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + unsigned int val; + + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + runtime->rate * 128, + afe_priv->clocks[MT8173_CLK_I2S3_B], + runtime->rate * runtime->channels * 32); + + val = AFE_TDM_CON1_BCK_INV | + AFE_TDM_CON1_LRCK_INV | + AFE_TDM_CON1_1_BCK_DELAY | + AFE_TDM_CON1_MSB_ALIGNED | /* I2S mode */ + AFE_TDM_CON1_WLEN_32BIT | + AFE_TDM_CON1_32_BCK_CYCLES | + AFE_TDM_CON1_LRCK_WIDTH(32); + regmap_update_bits(afe->regmap, AFE_TDM_CON1, ~AFE_TDM_CON1_EN, val); + + /* set tdm2 config */ + switch (runtime->channels) { + case 1: + case 2: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_ZERO << 4); + val |= (AFE_TDM_CH_ZERO << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 3: + case 4: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_ZERO << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 5: + case 6: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_START_O34_O35 << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 7: + case 8: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_START_O34_O35 << 8); + val |= (AFE_TDM_CH_START_O36_O37 << 12); + break; + default: + val = 0; + } + regmap_update_bits(afe->regmap, AFE_TDM_CON2, 0x0000ffff, val); + + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, + 0x000000f0, runtime->channels << 4); + return 0; +} + +static int mt8173_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + + dev_info(afe->dev, "%s cmd=%d %s\n", __func__, cmd, dai->name); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF, 0); + + /* set connections: O30~O37: L/R/LS/RS/C/LFE/CH7/CH8 */ + regmap_write(afe->regmap, AFE_HDMI_CONN0, + AFE_HDMI_CONN0_O30_I30 | + AFE_HDMI_CONN0_O31_I31 | + AFE_HDMI_CONN0_O32_I34 | + AFE_HDMI_CONN0_O33_I35 | + AFE_HDMI_CONN0_O34_I32 | + AFE_HDMI_CONN0_O35_I33 | + AFE_HDMI_CONN0_O36_I36 | + AFE_HDMI_CONN0_O37_I37); + + /* enable Out control */ + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0x1); + + /* enable tdm */ + regmap_update_bits(afe->regmap, AFE_TDM_CON1, 0x1, 0x1); + + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + /* disable tdm */ + regmap_update_bits(afe->regmap, AFE_TDM_CON1, 0x1, 0); + + /* disable Out control */ + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0); + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF); + return 0; + default: + return -EINVAL; + } +} + +static int mt8173_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int fs; + + if (memif->data->id == MT8173_AFE_MEMIF_DAI || + memif->data->id == MT8173_AFE_MEMIF_MOD_DAI) { + switch (rate) { + case 8000: + fs = 0; + break; + case 16000: + fs = 1; + break; + case 32000: + fs = 2; + break; + default: + return -EINVAL; + } + } else { + fs = mt8173_afe_i2s_fs(rate); + } + return fs; +} + +static int mt8173_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + return mt8173_afe_i2s_fs(rate); +} + +/* BE DAIs */ +static const struct snd_soc_dai_ops mt8173_afe_i2s_ops = { + .startup = mt8173_afe_i2s_startup, + .shutdown = mt8173_afe_i2s_shutdown, + .prepare = mt8173_afe_i2s_prepare, +}; + +static const struct snd_soc_dai_ops mt8173_afe_hdmi_ops = { + .startup = mt8173_afe_hdmi_startup, + .shutdown = mt8173_afe_hdmi_shutdown, + .prepare = mt8173_afe_hdmi_prepare, + .trigger = mt8173_afe_hdmi_trigger, +}; + +static struct snd_soc_dai_driver mt8173_afe_pcm_dais[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", /* downlink 1 */ + .id = MT8173_AFE_MEMIF_DL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + .name = "VUL", /* voice uplink */ + .id = MT8173_AFE_MEMIF_VUL, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "VUL", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + /* BE DAIs */ + .name = "I2S", + .id = MT8173_AFE_IO_I2S, + .playback = { + .stream_name = "I2S Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt8173_afe_i2s_ops, + .symmetric_rates = 1, + }, +}; + +static struct snd_soc_dai_driver mt8173_afe_hdmi_dais[] = { + /* FE DAIs */ + { + .name = "HDMI", + .id = MT8173_AFE_MEMIF_HDMI, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "HDMI", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + /* BE DAIs */ + .name = "HDMIO", + .id = MT8173_AFE_IO_HDMI, + .playback = { + .stream_name = "HDMIO Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt8173_afe_hdmi_ops, + }, +}; + +static const struct snd_kcontrol_new mt8173_afe_o03_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 21, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o04_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I06 Switch", AFE_CONN2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o09_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 0, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN7, 30, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o10_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I04 Switch", AFE_CONN3, 3, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN8, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget mt8173_afe_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I04", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I05", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I06", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, + mt8173_afe_o03_mix, ARRAY_SIZE(mt8173_afe_o03_mix)), + SND_SOC_DAPM_MIXER("O04", SND_SOC_NOPM, 0, 0, + mt8173_afe_o04_mix, ARRAY_SIZE(mt8173_afe_o04_mix)), + SND_SOC_DAPM_MIXER("O09", SND_SOC_NOPM, 0, 0, + mt8173_afe_o09_mix, ARRAY_SIZE(mt8173_afe_o09_mix)), + SND_SOC_DAPM_MIXER("O10", SND_SOC_NOPM, 0, 0, + mt8173_afe_o10_mix, ARRAY_SIZE(mt8173_afe_o10_mix)), +}; + +static const struct snd_soc_dapm_route mt8173_afe_pcm_routes[] = { + {"I05", NULL, "DL1"}, + {"I06", NULL, "DL1"}, + {"I2S Playback", NULL, "O03"}, + {"I2S Playback", NULL, "O04"}, + {"VUL", NULL, "O09"}, + {"VUL", NULL, "O10"}, + {"I03", NULL, "I2S Capture"}, + {"I04", NULL, "I2S Capture"}, + {"I17", NULL, "I2S Capture"}, + {"I18", NULL, "I2S Capture"}, + { "O03", "I05 Switch", "I05" }, + { "O04", "I06 Switch", "I06" }, + { "O09", "I17 Switch", "I17" }, + { "O09", "I03 Switch", "I03" }, + { "O10", "I18 Switch", "I18" }, + { "O10", "I04 Switch", "I04" }, +}; + +static const struct snd_soc_dapm_route mt8173_afe_hdmi_routes[] = { + {"HDMIO Playback", NULL, "HDMI"}, +}; + +static const struct snd_soc_component_driver mt8173_afe_pcm_dai_component = { + .name = "mt8173-afe-pcm-dai", + .dapm_widgets = mt8173_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_afe_pcm_widgets), + .dapm_routes = mt8173_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_pcm_routes), +}; + +static const struct snd_soc_component_driver mt8173_afe_hdmi_dai_component = { + .name = "mt8173-afe-hdmi-dai", + .dapm_routes = mt8173_afe_hdmi_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_hdmi_routes), +}; + +static const char *aud_clks[MT8173_CLK_NUM] = { + [MT8173_CLK_INFRASYS_AUD] = "infra_sys_audio_clk", + [MT8173_CLK_TOP_PDN_AUD] = "top_pdn_audio", + [MT8173_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus", + [MT8173_CLK_I2S0_M] = "i2s0_m", + [MT8173_CLK_I2S1_M] = "i2s1_m", + [MT8173_CLK_I2S2_M] = "i2s2_m", + [MT8173_CLK_I2S3_M] = "i2s3_m", + [MT8173_CLK_I2S3_B] = "i2s3_b", + [MT8173_CLK_BCK0] = "bck0", + [MT8173_CLK_BCK1] = "bck1", +}; + +static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { + { + .name = "DL1", + .id = MT8173_AFE_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 21, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 1, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 0, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "DL2", + .id = MT8173_AFE_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 4, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 22, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 2, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "VUL", + .id = MT8173_AFE_MEMIF_VUL, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 16, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 27, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 3, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 6, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "DAI", + .id = MT8173_AFE_MEMIF_DAI, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON0, + .fs_shift = 24, + .fs_maskbit = 0x3, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 4, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 5, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "AWB", + .id = MT8173_AFE_MEMIF_AWB, + .reg_ofs_base = AFE_AWB_BASE, + .reg_ofs_cur = AFE_AWB_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 12, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 24, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 6, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 3, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "MOD_DAI", + .id = MT8173_AFE_MEMIF_MOD_DAI, + .reg_ofs_base = AFE_MOD_PCM_BASE, + .reg_ofs_cur = AFE_MOD_PCM_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 30, + .fs_maskbit = 0x3, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 30, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 7, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 4, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "HDMI", + .id = MT8173_AFE_MEMIF_HDMI, + .reg_ofs_base = AFE_HDMI_OUT_BASE, + .reg_ofs_cur = AFE_HDMI_OUT_CUR, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = -1, + .enable_shift = -1, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 8, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = { + { + .id = MT8173_AFE_IRQ_DL1, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 0, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 4, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 0, + }, { + .id = MT8173_AFE_IRQ_DL2, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 2, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 16, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 2, + + }, { + .id = MT8173_AFE_IRQ_VUL, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 1, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 8, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 1, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_AWB, + .irq_cnt_reg = AFE_IRQ_CNT7, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 14, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 6, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_HDMI, + .irq_cnt_reg = AFE_IRQ_CNT5, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 12, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 4, + }, +}; + +static const struct regmap_config mt8173_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_ADDA2_TOP_CON0, + .cache_type = REGCACHE_NONE, +}; + +static irqreturn_t mt8173_afe_irq_handler(int irq, void *dev_id) +{ + struct mtk_base_afe *afe = dev_id; + unsigned int reg_value; + int i, ret; + + ret = regmap_read(afe->regmap, AFE_IRQ_STATUS, ®_value); + if (ret) { + dev_err(afe->dev, "%s irq status err\n", __func__); + reg_value = AFE_IRQ_STATUS_BITS; + goto err_irq; + } + + for (i = 0; i < MT8173_AFE_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + struct mtk_base_afe_irq *irq; + + if (memif->irq_usage < 0) + continue; + + irq = &afe->irqs[memif->irq_usage]; + + if (!(reg_value & (1 << irq->irq_data->irq_clr_shift))) + continue; + + snd_pcm_period_elapsed(memif->substream); + } + +err_irq: + /* clear irq */ + regmap_write(afe->regmap, AFE_IRQ_CLR, + reg_value & AFE_IRQ_STATUS_BITS); + + return IRQ_HANDLED; +} + +static int mt8173_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + /* disable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0); + + /* disable AFE clk */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_AFE, AUD_TCON0_PDN_AFE); + + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + return 0; +} + +static int mt8173_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + int ret; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + if (ret) + return ret; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); + if (ret) + goto err_infra; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); + if (ret) + goto err_top_aud_bus; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK0]); + if (ret) + goto err_top_aud; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK1]); + if (ret) + goto err_bck0; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]); + if (ret) + goto err_i2s1_m; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]); + if (ret) + goto err_i2s2_m; + + /* enable AFE clk */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0); + + /* set O3/O4 16bits */ + regmap_update_bits(afe->regmap, AFE_CONN_24BIT, + AFE_CONN_24BIT_O03 | AFE_CONN_24BIT_O04, 0); + + /* unmask all IRQs */ + regmap_update_bits(afe->regmap, AFE_IRQ_MCU_EN, 0xff, 0xff); + + /* enable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); + return 0; + +err_i2s1_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); +err_i2s2_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); +err_bck0: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); +err_top_aud: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); +err_top_aud_bus: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); +err_infra: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + return ret; +} + +static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe) +{ + size_t i; + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { + afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clocks[i])) { + dev_err(afe->dev, "%s devm_clk_get %s fail\n", + __func__, aud_clks[i]); + return PTR_ERR(afe_priv->clocks[i]); + } + } + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */ + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */ + return 0; +} + +static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) +{ + int ret, i; + unsigned int irq_id; + struct mtk_base_afe *afe; + struct mt8173_afe_private *afe_priv; + struct resource *res; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + afe_priv = afe->platform_priv; + if (!afe_priv) + return -ENOMEM; + + afe->dev = &pdev->dev; + + irq_id = platform_get_irq(pdev, 0); + if (!irq_id) { + dev_err(afe->dev, "np %s no irq\n", afe->dev->of_node->name); + return -ENXIO; + } + ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler, + 0, "Afe_ISR_Handle", (void *)afe); + if (ret) { + dev_err(afe->dev, "could not request_irq\n"); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &mt8173_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + /* initial audio related clock */ + ret = mt8173_afe_init_audio_clk(afe); + if (ret) { + dev_err(afe->dev, "mt8173_afe_init_audio_clk fail\n"); + return ret; + } + + /* memif % irq initialize*/ + afe->memif_size = MT8173_AFE_MEMIF_NUM; + afe->memif = devm_kcalloc(afe->dev, afe->memif_size, + sizeof(*afe->memif), GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe->irqs_size = MT8173_AFE_IRQ_NUM; + afe->irqs = devm_kcalloc(afe->dev, afe->irqs_size, + sizeof(*afe->irqs), GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->irqs[i].irq_data = &irq_data[i]; + afe->irqs[i].irq_occupyed = true; + afe->memif[i].irq_usage = i; + afe->memif[i].const_irq = 1; + } + + afe->mtk_afe_hardware = &mt8173_afe_hardware; + afe->memif_fs = mt8173_memif_fs; + afe->irq_fs = mt8173_irq_fs; + + platform_set_drvdata(pdev, afe); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = mt8173_afe_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + afe->reg_back_up_list = mt8173_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt8173_afe_backup_list); + afe->runtime_resume = mt8173_afe_runtime_resume; + afe->runtime_suspend = mt8173_afe_runtime_suspend; + + ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform); + if (ret) + goto err_pm_disable; + + ret = snd_soc_register_component(&pdev->dev, + &mt8173_afe_pcm_dai_component, + mt8173_afe_pcm_dais, + ARRAY_SIZE(mt8173_afe_pcm_dais)); + if (ret) + goto err_platform; + + ret = snd_soc_register_component(&pdev->dev, + &mt8173_afe_hdmi_dai_component, + mt8173_afe_hdmi_dais, + ARRAY_SIZE(mt8173_afe_hdmi_dais)); + if (ret) + goto err_comp; + + dev_info(&pdev->dev, "MT8173 AFE driver initialized.\n"); + return 0; + +err_comp: + snd_soc_unregister_component(&pdev->dev); +err_platform: + snd_soc_unregister_platform(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int mt8173_afe_pcm_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt8173_afe_runtime_suspend(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static const struct of_device_id mt8173_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt8173-afe-pcm", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_afe_pcm_dt_match); + +static const struct dev_pm_ops mt8173_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt8173_afe_runtime_suspend, + mt8173_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt8173_afe_pcm_driver = { + .driver = { + .name = "mt8173-afe-pcm", + .of_match_table = mt8173_afe_pcm_dt_match, + .pm = &mt8173_afe_pm_ops, + }, + .probe = mt8173_afe_pcm_dev_probe, + .remove = mt8173_afe_pcm_dev_remove, +}; + +module_platform_driver(mt8173_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver"); +MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt8173/mt8173-max98090.c b/sound/soc/mediatek/mt8173/mt8173-max98090.c new file mode 100644 index 000000000..5524a2c72 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-max98090.c @@ -0,0 +1,213 @@ +/* + * mt8173-max98090.c -- MT8173 MAX98090 ALSA SoC machine driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/gpio.h> +#include "../../codecs/max98090.h" + +static struct snd_soc_jack mt8173_max98090_jack; + +static struct snd_soc_jack_pin mt8173_max98090_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static const struct snd_soc_dapm_widget mt8173_max98090_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_max98090_routes[] = { + {"Speaker", NULL, "SPKL"}, + {"Speaker", NULL, "SPKR"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"IN34", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_max98090_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; + + return snd_soc_dai_set_sysclk(codec_dai, 0, params_rate(params) * 256, + SND_SOC_CLOCK_IN); +} + +static struct snd_soc_ops mt8173_max98090_ops = { + .hw_params = mt8173_max98090_hw_params, +}; + +static int mt8173_max98090_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_card *card = runtime->card; + struct snd_soc_codec *codec = runtime->codec; + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headphone", SND_JACK_HEADPHONE, + &mt8173_max98090_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't snd_soc_jack_new %d\n", ret); + return ret; + } + + ret = snd_soc_jack_add_pins(&mt8173_max98090_jack, + ARRAY_SIZE(mt8173_max98090_jack_pins), + mt8173_max98090_jack_pins); + if (ret) { + dev_err(card->dev, "Can't snd_soc_jack_add_pins %d\n", ret); + return ret; + } + + return max98090_mic_detect(codec, &mt8173_max98090_jack); +} + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_max98090_dais[] = { + /* Front End DAI links */ + { + .name = "MAX98090 Playback", + .stream_name = "MAX98090 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "MAX98090 Capture", + .stream_name = "MAX98090 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codec_dai_name = "HiFi", + .init = mt8173_max98090_init, + .ops = &mt8173_max98090_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt8173_max98090_card = { + .name = "mt8173-max98090", + .owner = THIS_MODULE, + .dai_link = mt8173_max98090_dais, + .num_links = ARRAY_SIZE(mt8173_max98090_dais), + .controls = mt8173_max98090_controls, + .num_controls = ARRAY_SIZE(mt8173_max98090_controls), + .dapm_widgets = mt8173_max98090_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_max98090_widgets), + .dapm_routes = mt8173_max98090_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_max98090_routes), +}; + +static int mt8173_max98090_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_max98090_card; + struct device_node *codec_node, *platform_node; + int ret, i; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt8173_max98090_dais[i].platform_name) + continue; + mt8173_max98090_dais[i].platform_of_node = platform_node; + } + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt8173_max98090_dais[i].codec_name) + continue; + mt8173_max98090_dais[i].codec_of_node = codec_node; + } + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +static const struct of_device_id mt8173_max98090_dt_match[] = { + { .compatible = "mediatek,mt8173-max98090", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_max98090_dt_match); + +static struct platform_driver mt8173_max98090_driver = { + .driver = { + .name = "mt8173-max98090", + .of_match_table = mt8173_max98090_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_max98090_dev_probe, +}; + +module_platform_driver(mt8173_max98090_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 MAX98090 ALSA SoC machine driver"); +MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mt8173-max98090"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c new file mode 100644 index 000000000..467f7049a --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c @@ -0,0 +1,258 @@ +/* + * mt8173-rt5650-rt5514.c -- MT8173 machine driver with RT5650/5514 codecs + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include "../../codecs/rt5645.h" + +#define MCLK_FOR_CODECS 12288000 + +static const struct snd_soc_dapm_widget mt8173_rt5650_rt5514_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_rt5514_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"Sub DMIC1L", NULL, "Int Mic"}, + {"Sub DMIC1R", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Headset Mic", NULL, "micbias1"}, + {"Headset Mic", NULL, "micbias2"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_rt5650_rt5514_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_rt5514_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk 12.288M */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static struct snd_soc_ops mt8173_rt5650_rt5514_ops = { + .hw_params = mt8173_rt5650_rt5514_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_rt5514_jack; + +static int mt8173_rt5650_rt5514_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_codec *codec = runtime->codec_dais[0]->codec; + int ret; + + rt5645_sel_asrc_clk_src(codec, + RT5645_DA_STEREO_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_rt5514_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(codec, + &mt8173_rt5650_rt5514_jack, + &mt8173_rt5650_rt5514_jack, + &mt8173_rt5650_rt5514_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_rt5514_codecs[] = { + { + .dai_name = "rt5645-aif1", + }, + { + .dai_name = "rt5514-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_CODEC_I2S, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_rt5514_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650_rt5514 Playback", + .stream_name = "rt5650_rt5514 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650_rt5514 Capture", + .stream_name = "rt5650_rt5514 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_rt5514_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_rt5514_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_rt5514_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_codec_conf mt8173_rt5650_rt5514_codec_conf[] = { + { + .name_prefix = "Sub", + }, +}; + +static struct snd_soc_card mt8173_rt5650_rt5514_card = { + .name = "mtk-rt5650-rt5514", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_rt5514_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_rt5514_dais), + .codec_conf = mt8173_rt5650_rt5514_codec_conf, + .num_configs = ARRAY_SIZE(mt8173_rt5650_rt5514_codec_conf), + .controls = mt8173_rt5650_rt5514_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_rt5514_controls), + .dapm_widgets = mt8173_rt5650_rt5514_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_rt5514_widgets), + .dapm_routes = mt8173_rt5650_rt5514_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_rt5514_routes), +}; + +static int mt8173_rt5650_rt5514_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_rt5514_card; + struct device_node *platform_node; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_rt5514_dais[i].platform_name) + continue; + mt8173_rt5650_rt5514_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_rt5514_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_rt5514_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5514_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_rt5514_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5514_codec_conf[0].of_node = + mt8173_rt5650_rt5514_codecs[1].of_node; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +static const struct of_device_id mt8173_rt5650_rt5514_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650-rt5514", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_rt5514_dt_match); + +static struct platform_driver mt8173_rt5650_rt5514_driver = { + .driver = { + .name = "mtk-rt5650-rt5514", + .of_match_table = mt8173_rt5650_rt5514_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_rt5514_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_rt5514_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 and RT5514 SoC machine driver"); +MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650-rt5514"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c new file mode 100644 index 000000000..1b8b2a778 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c @@ -0,0 +1,315 @@ +/* + * mt8173-rt5650-rt5676.c -- MT8173 machine driver with RT5650/5676 codecs + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include "../../codecs/rt5645.h" +#include "../../codecs/rt5677.h" + +#define MCLK_FOR_CODECS 12288000 + +static const struct snd_soc_dapm_widget mt8173_rt5650_rt5676_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_rt5676_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "Sub AIF2TX"}, /* IF2 ADC to 5650 */ + {"Sub DMIC L1", NULL, "Int Mic"}, /* DMIC from 5676 */ + {"Sub DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Headphone", NULL, "Sub AIF2TX"}, /* IF2 ADC to 5650 */ + {"Headset Mic", NULL, "micbias1"}, + {"Headset Mic", NULL, "micbias2"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"Sub AIF2RX", NULL, "Headset Mic"}, /* IF2 DAC from 5650 */ +}; + +static const struct snd_kcontrol_new mt8173_rt5650_rt5676_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_rt5676_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk 12.288M */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static struct snd_soc_ops mt8173_rt5650_rt5676_ops = { + .hw_params = mt8173_rt5650_rt5676_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_rt5676_jack; + +static int mt8173_rt5650_rt5676_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_codec *codec = runtime->codec_dais[0]->codec; + struct snd_soc_codec *codec_sub = runtime->codec_dais[1]->codec; + int ret; + + rt5645_sel_asrc_clk_src(codec, + RT5645_DA_STEREO_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + rt5677_sel_asrc_clk_src(codec_sub, + RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER, + RT5677_CLK_SEL_I2S1_ASRC); + rt5677_sel_asrc_clk_src(codec_sub, + RT5677_AD_STEREO2_FILTER | + RT5677_I2S2_SOURCE, + RT5677_CLK_SEL_I2S2_ASRC); + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_rt5676_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(codec, + &mt8173_rt5650_rt5676_jack, + &mt8173_rt5650_rt5676_jack, + &mt8173_rt5650_rt5676_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_rt5676_codecs[] = { + { + .dai_name = "rt5645-aif1", + }, + { + .dai_name = "rt5677-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_HDMI, + DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, + DAI_LINK_INTERCODEC +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650_rt5676 Playback", + .stream_name = "rt5650_rt5676 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650_rt5676 Capture", + .stream_name = "rt5650_rt5676 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_rt5676_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_rt5676_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_rt5676_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, + /* rt5676 <-> rt5650 intercodec link: Sets rt5676 I2S2 as master */ + [DAI_LINK_INTERCODEC] = { + .name = "rt5650_rt5676 intercodec", + .stream_name = "rt5650_rt5676 intercodec", + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_dai_name = "rt5677-aif2", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + }, + +}; + +static struct snd_soc_codec_conf mt8173_rt5650_rt5676_codec_conf[] = { + { + .name_prefix = "Sub", + }, +}; + +static struct snd_soc_card mt8173_rt5650_rt5676_card = { + .name = "mtk-rt5650-rt5676", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_rt5676_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_rt5676_dais), + .codec_conf = mt8173_rt5650_rt5676_codec_conf, + .num_configs = ARRAY_SIZE(mt8173_rt5650_rt5676_codec_conf), + .controls = mt8173_rt5650_rt5676_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_rt5676_controls), + .dapm_widgets = mt8173_rt5650_rt5676_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_rt5676_widgets), + .dapm_routes = mt8173_rt5650_rt5676_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_rt5676_routes), +}; + +static int mt8173_rt5650_rt5676_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_rt5676_card; + struct device_node *platform_node; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_rt5676_dais[i].platform_name) + continue; + mt8173_rt5650_rt5676_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_rt5676_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_rt5676_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5676_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_rt5676_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5676_codec_conf[0].of_node = + mt8173_rt5650_rt5676_codecs[1].of_node; + + mt8173_rt5650_rt5676_dais[DAI_LINK_INTERCODEC].codec_of_node = + mt8173_rt5650_rt5676_codecs[1].of_node; + + mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 2); + if (!mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +static const struct of_device_id mt8173_rt5650_rt5676_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650-rt5676", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_rt5676_dt_match); + +static struct platform_driver mt8173_rt5650_rt5676_driver = { + .driver = { + .name = "mtk-rt5650-rt5676", + .of_match_table = mt8173_rt5650_rt5676_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_rt5676_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_rt5676_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 and RT5676 SoC machine driver"); +MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650-rt5676"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c new file mode 100644 index 000000000..ba65f4157 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -0,0 +1,347 @@ +/* + * mt8173-rt5650.c -- MT8173 machine driver with RT5650 codecs + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include "../../codecs/rt5645.h" + +#define MCLK_FOR_CODECS 12288000 + +enum mt8173_rt5650_mclk { + MT8173_RT5650_MCLK_EXTERNAL = 0, + MT8173_RT5650_MCLK_INTERNAL, +}; + +struct mt8173_rt5650_platform_data { + enum mt8173_rt5650_mclk pll_from; + /* 0 = external oscillator; 1 = internal source from mt8173 */ +}; + +static struct mt8173_rt5650_platform_data mt8173_rt5650_priv = { + .pll_from = MT8173_RT5650_MCLK_EXTERNAL, +}; + +static const struct snd_soc_dapm_widget mt8173_rt5650_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Headset Mic", NULL, "micbias1"}, + {"Headset Mic", NULL, "micbias2"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_rt5650_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int mclk_clock; + int i, ret; + + switch (mt8173_rt5650_priv.pll_from) { + case MT8173_RT5650_MCLK_EXTERNAL: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + case MT8173_RT5650_MCLK_INTERNAL: + /* mclk = sampling rate*256 */ + mclk_clock = params_rate(params) * 256; + break; + default: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, mclk_clock, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static struct snd_soc_ops mt8173_rt5650_ops = { + .hw_params = mt8173_rt5650_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_jack; + +static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_codec *codec = runtime->codec_dais[0]->codec; + const char *codec_capture_dai = runtime->codec_dais[1]->name; + int ret; + + rt5645_sel_asrc_clk_src(codec, + RT5645_DA_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + + if (!strcmp(codec_capture_dai, "rt5645-aif1")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } else if (!strcmp(codec_capture_dai, "rt5645-aif2")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S2_ASRC); + } else { + dev_warn(card->dev, + "Only one dai codec found in DTS, enabled rt5645 AD filter\n"); + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(codec, + &mt8173_rt5650_jack, + &mt8173_rt5650_jack, + &mt8173_rt5650_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = { + { + /* Playback */ + .dai_name = "rt5645-aif1", + }, + { + /* Capture */ + .dai_name = "rt5645-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_HDMI, + DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650 Playback", + .stream_name = "rt5650 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650 Capture", + .stream_name = "rt5650 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, +}; + +static struct snd_soc_card mt8173_rt5650_card = { + .name = "mtk-rt5650", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_dais), + .controls = mt8173_rt5650_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_controls), + .dapm_widgets = mt8173_rt5650_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_widgets), + .dapm_routes = mt8173_rt5650_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_routes), +}; + +static int mt8173_rt5650_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_card; + struct device_node *platform_node; + struct device_node *np; + const char *codec_capture_dai; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_dais[i].platform_name) + continue; + mt8173_rt5650_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node; + + if (of_find_node_by_name(platform_node, "codec-capture")) { + np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); + if (!np) { + dev_err(&pdev->dev, + "%s: Can't find codec-capture DT node\n", + __func__); + return -EINVAL; + } + ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + if (ret < 0) { + dev_err(&pdev->dev, + "%s codec_capture_dai name fail %d\n", + __func__, ret); + return ret; + } + mt8173_rt5650_codecs[1].dai_name = codec_capture_dai; + } + + if (device_property_present(&pdev->dev, "mediatek,mclk")) { + ret = device_property_read_u32(&pdev->dev, + "mediatek,mclk", + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, + "%s snd_soc_register_card fail %d\n", + __func__, ret); + } + } + + mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +static const struct of_device_id mt8173_rt5650_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_dt_match); + +static struct platform_driver mt8173_rt5650_driver = { + .driver = { + .name = "mtk-rt5650", + .of_match_table = mt8173_rt5650_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 SoC machine driver"); +MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650"); + diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 5185a3844..f5451c78e 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -100,13 +100,14 @@ config SND_OMAP_SOC_OMAP_TWL4030 config SND_OMAP_SOC_OMAP_ABE_TWL6040 tristate "SoC Audio support for OMAP boards using ABE and twl6040 codec" - depends on TWL6040_CORE && SND_OMAP_SOC + depends on TWL6040_CORE && SND_OMAP_SOC && COMMON_CLK depends on ARCH_OMAP4 || (SOC_OMAP5 && MFD_PALMAS) || COMPILE_TEST select SND_OMAP_SOC_DMIC select SND_OMAP_SOC_MCPDM select SND_SOC_TWL6040 select SND_SOC_DMIC select COMMON_CLK_PALMAS if (SOC_OMAP5 && MFD_PALMAS) + select CLK_TWL6040 help Say Y if you want to add support for SoC audio on OMAP boards using ABE and twl6040 codec. This driver currently supports: diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c index 4a16e7789..76ce33199 100644 --- a/sound/soc/omap/mcbsp.c +++ b/sound/soc/omap/mcbsp.c @@ -257,8 +257,8 @@ static void omap_st_on(struct omap_mcbsp *mcbsp) { unsigned int w; - if (mcbsp->pdata->enable_st_clock) - mcbsp->pdata->enable_st_clock(mcbsp->id, 1); + if (mcbsp->pdata->force_ick_on) + mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, true); /* Disable Sidetone clock auto-gating for normal operation */ w = MCBSP_ST_READ(mcbsp, SYSCONFIG); @@ -287,8 +287,8 @@ static void omap_st_off(struct omap_mcbsp *mcbsp) w = MCBSP_ST_READ(mcbsp, SYSCONFIG); MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); - if (mcbsp->pdata->enable_st_clock) - mcbsp->pdata->enable_st_clock(mcbsp->id, 0); + if (mcbsp->pdata->force_ick_on) + mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, false); } static void omap_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) @@ -946,6 +946,13 @@ static int omap_st_add(struct omap_mcbsp *mcbsp, struct resource *res) if (!st_data) return -ENOMEM; + st_data->mcbsp_iclk = clk_get(mcbsp->dev, "ick"); + if (IS_ERR(st_data->mcbsp_iclk)) { + dev_warn(mcbsp->dev, + "Failed to get ick, sidetone might be broken\n"); + st_data->mcbsp_iclk = NULL; + } + st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start, resource_size(res)); if (!st_data->io_base_st) @@ -1088,11 +1095,13 @@ err_thres: return ret; } -void omap_mcbsp_sysfs_remove(struct omap_mcbsp *mcbsp) +void omap_mcbsp_cleanup(struct omap_mcbsp *mcbsp) { if (mcbsp->pdata->buffer_size) sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); - if (mcbsp->st_data) + if (mcbsp->st_data) { sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); + clk_put(mcbsp->st_data->mcbsp_iclk); + } } diff --git a/sound/soc/omap/mcbsp.h b/sound/soc/omap/mcbsp.h index 96d1b086b..61e93b1c1 100644 --- a/sound/soc/omap/mcbsp.h +++ b/sound/soc/omap/mcbsp.h @@ -280,6 +280,7 @@ struct omap_mcbsp_reg_cfg { struct omap_mcbsp_st_data { void __iomem *io_base_st; + struct clk *mcbsp_iclk; bool running; bool enabled; s16 taps[128]; /* Sidetone filter coefficients */ @@ -349,6 +350,6 @@ int omap_st_disable(struct omap_mcbsp *mcbsp); int omap_st_is_enabled(struct omap_mcbsp *mcbsp); int omap_mcbsp_init(struct platform_device *pdev); -void omap_mcbsp_sysfs_remove(struct omap_mcbsp *mcbsp); +void omap_mcbsp_cleanup(struct omap_mcbsp *mcbsp); #endif /* __ASOC_MCBSP_H */ diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c index 0843a68f2..f61b3b580 100644 --- a/sound/soc/omap/omap-abe-twl6040.c +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -38,10 +38,10 @@ struct abe_twl6040 { int jack_detection; /* board can detect jack events */ int mclk_freq; /* MCLK frequency speed for twl6040 */ - - struct platform_device *dmic_codec_dev; }; +struct platform_device *dmic_codec_dev; + static int omap_abe_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -258,8 +258,6 @@ static int omap_abe_probe(struct platform_device *pdev) if (priv == NULL) return -ENOMEM; - priv->dmic_codec_dev = ERR_PTR(-EINVAL); - if (snd_soc_of_parse_card_name(card, "ti,model")) { dev_err(&pdev->dev, "Card name is not provided\n"); return -ENODEV; @@ -284,13 +282,6 @@ static int omap_abe_probe(struct platform_device *pdev) num_links = 2; abe_twl6040_dai_links[1].cpu_of_node = dai_node; abe_twl6040_dai_links[1].platform_of_node = dai_node; - - priv->dmic_codec_dev = platform_device_register_simple( - "dmic-codec", -1, NULL, 0); - if (IS_ERR(priv->dmic_codec_dev)) { - dev_err(&pdev->dev, "Can't instantiate dmic-codec\n"); - return PTR_ERR(priv->dmic_codec_dev); - } } else { num_links = 1; } @@ -299,16 +290,14 @@ static int omap_abe_probe(struct platform_device *pdev) of_property_read_u32(node, "ti,mclk-freq", &priv->mclk_freq); if (!priv->mclk_freq) { dev_err(&pdev->dev, "MCLK frequency not provided\n"); - ret = -EINVAL; - goto err_unregister; + return -EINVAL; } card->fully_routed = 1; if (!priv->mclk_freq) { dev_err(&pdev->dev, "MCLK frequency missing\n"); - ret = -ENODEV; - goto err_unregister; + return -ENODEV; } card->dai_link = abe_twl6040_dai_links; @@ -317,17 +306,9 @@ static int omap_abe_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, priv); ret = snd_soc_register_card(card); - if (ret) { + if (ret) dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); - goto err_unregister; - } - - return 0; - -err_unregister: - if (!IS_ERR(priv->dmic_codec_dev)) - platform_device_unregister(priv->dmic_codec_dev); return ret; } @@ -335,13 +316,9 @@ err_unregister: static int omap_abe_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); snd_soc_unregister_card(card); - if (!IS_ERR(priv->dmic_codec_dev)) - platform_device_unregister(priv->dmic_codec_dev); - return 0; } @@ -361,7 +338,33 @@ static struct platform_driver omap_abe_driver = { .remove = omap_abe_remove, }; -module_platform_driver(omap_abe_driver); +static int __init omap_abe_init(void) +{ + int ret; + + dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL, + 0); + if (IS_ERR(dmic_codec_dev)) { + pr_err("%s: dmic-codec device registration failed\n", __func__); + return PTR_ERR(dmic_codec_dev); + } + + ret = platform_driver_register(&omap_abe_driver); + if (ret) { + pr_err("%s: platform driver registration failed\n", __func__); + platform_device_unregister(dmic_codec_dev); + } + + return ret; +} +module_init(omap_abe_init); + +static void __exit omap_abe_exit(void) +{ + platform_driver_unregister(&omap_abe_driver); + platform_device_unregister(dmic_codec_dev); +} +module_exit(omap_abe_exit); MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>"); MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec"); diff --git a/sound/soc/omap/omap-hdmi-audio.c b/sound/soc/omap/omap-hdmi-audio.c index 64425d352..888133f9e 100644 --- a/sound/soc/omap/omap-hdmi-audio.c +++ b/sound/soc/omap/omap-hdmi-audio.c @@ -28,7 +28,6 @@ #include <sound/asoundef.h> #include <sound/omap-pcm.h> #include <sound/omap-hdmi-audio.h> -#include <video/omapdss.h> #define DRV_NAME "omap-hdmi-audio" diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index fd99d89de..d018e966e 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -788,6 +788,7 @@ static int asoc_mcbsp_probe(struct platform_device *pdev) match = of_match_device(omap_mcbsp_of_match, &pdev->dev); if (match) { struct device_node *node = pdev->dev.of_node; + struct omap_mcbsp_platform_data *pdata_quirk = pdata; int buffer_size; pdata = devm_kzalloc(&pdev->dev, @@ -799,6 +800,8 @@ static int asoc_mcbsp_probe(struct platform_device *pdev) memcpy(pdata, match->data, sizeof(*pdata)); if (!of_property_read_u32(node, "ti,buffer-size", &buffer_size)) pdata->buffer_size = buffer_size; + if (pdata_quirk) + pdata->force_ick_on = pdata_quirk->force_ick_on; } else if (!pdata) { dev_err(&pdev->dev, "missing platform data.\n"); return -EINVAL; @@ -832,7 +835,7 @@ static int asoc_mcbsp_remove(struct platform_device *pdev) if (mcbsp->pdata->ops && mcbsp->pdata->ops->free) mcbsp->pdata->ops->free(mcbsp->id); - omap_mcbsp_sysfs_remove(mcbsp); + omap_mcbsp_cleanup(mcbsp); clk_put(mcbsp->fclk); diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index b837265ac..64609c77a 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -66,6 +66,9 @@ struct omap_mcpdm { /* McPDM needs to be restarted due to runtime reconfiguration */ bool restart; + /* pm state for suspend/resume handling */ + int pm_active_count; + struct snd_dmaengine_dai_dma_data dma_data[2]; }; @@ -173,6 +176,10 @@ static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm) */ static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm) { + u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); + + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN); + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET, MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL | MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); @@ -258,12 +265,9 @@ static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, mutex_lock(&mcpdm->mutex); - if (!dai->active) { - u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); - - omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN); + if (!dai->active) omap_mcpdm_open_streams(mcpdm); - } + mutex_unlock(&mcpdm->mutex); return 0; @@ -390,8 +394,8 @@ static int omap_mcpdm_probe(struct snd_soc_dai *dai) pm_runtime_get_sync(mcpdm->dev); omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, 0x00); - ret = devm_request_irq(mcpdm->dev, mcpdm->irq, omap_mcpdm_irq_handler, - 0, "McPDM", (void *)mcpdm); + ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, 0, "McPDM", + (void *)mcpdm); pm_runtime_put_sync(mcpdm->dev); @@ -416,17 +420,61 @@ static int omap_mcpdm_remove(struct snd_soc_dai *dai) { struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + free_irq(mcpdm->irq, (void *)mcpdm); pm_runtime_disable(mcpdm->dev); return 0; } +#ifdef CONFIG_PM_SLEEP +static int omap_mcpdm_suspend(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + if (dai->active) { + omap_mcpdm_stop(mcpdm); + omap_mcpdm_close_streams(mcpdm); + } + + mcpdm->pm_active_count = 0; + while (pm_runtime_active(mcpdm->dev)) { + pm_runtime_put_sync(mcpdm->dev); + mcpdm->pm_active_count++; + } + + return 0; +} + +static int omap_mcpdm_resume(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + if (mcpdm->pm_active_count) { + while (mcpdm->pm_active_count--) + pm_runtime_get_sync(mcpdm->dev); + + if (dai->active) { + omap_mcpdm_open_streams(mcpdm); + omap_mcpdm_start(mcpdm); + } + } + + + return 0; +} +#else +#define omap_mcpdm_suspend NULL +#define omap_mcpdm_resume NULL +#endif + #define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE static struct snd_soc_dai_driver omap_mcpdm_dai = { .probe = omap_mcpdm_probe, .remove = omap_mcpdm_remove, + .suspend = omap_mcpdm_suspend, + .resume = omap_mcpdm_resume, .probe_order = SND_SOC_COMP_ORDER_LATE, .remove_order = SND_SOC_COMP_ORDER_EARLY, .playback = { diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index 54949242b..a76845748 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -33,7 +33,6 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <linux/platform_data/asoc-ti-mcbsp.h> -#include "../codecs/tpa6130a2.h" #include <asm/mach-types.h> @@ -164,19 +163,6 @@ 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 = snd_soc_dapm_to_codec(w->dapm); - - 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) { @@ -235,7 +221,7 @@ 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_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("HS Mic", NULL), SND_SOC_DAPM_LINE("FM Transmitter", NULL), SND_SOC_DAPM_SPK("Earphone", NULL), @@ -246,11 +232,14 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Ext Spk", NULL, "HPROUT"}, {"Ext Spk", NULL, "HPLCOM"}, {"Ext Spk", NULL, "HPRCOM"}, - {"Headphone Jack", NULL, "LLOUT"}, - {"Headphone Jack", NULL, "RLOUT"}, {"FM Transmitter", NULL, "LLOUT"}, {"FM Transmitter", NULL, "RLOUT"}, + {"Headphone Jack", NULL, "TPA6130A2 HPLEFT"}, + {"Headphone Jack", NULL, "TPA6130A2 HPRIGHT"}, + {"TPA6130A2 LEFTIN", NULL, "LLOUT"}, + {"TPA6130A2 RIGHTIN", NULL, "RLOUT"}, + {"DMic Rate 64", NULL, "DMic"}, {"DMic", NULL, "Mic Bias"}, @@ -286,16 +275,10 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = { static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_codec *codec = rtd->codec; struct snd_soc_card *card = rtd->card; struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); int err; - err = tpa6130a2_add_controls(codec); - if (err < 0) { - dev_err(card->dev, "Failed to add TPA6130A2 controls\n"); - return err; - } snd_soc_limit_volume(card, "TPA6130A2 Headphone Playback Volume", 42); err = omap_mcbsp_st_add_controls(rtd, 2); @@ -357,6 +340,10 @@ static struct snd_soc_aux_dev rx51_aux_dev[] = { .name = "TLV320AIC34b", .codec_name = "tlv320aic3x-codec.2-0019", }, + { + .name = "TPA61320A2", + .codec_name = "tpa6130a2.2-0060", + }, }; static struct snd_soc_codec_conf rx51_codec_conf[] = { @@ -364,6 +351,10 @@ static struct snd_soc_codec_conf rx51_codec_conf[] = { .dev_name = "tlv320aic3x-codec.2-0019", .name_prefix = "b", }, + { + .dev_name = "tpa6130a2.2-0060", + .name_prefix = "TPA6130A2", + }, }; /* Audio card */ @@ -435,11 +426,10 @@ static int rx51_soc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Headphone amplifier node is not provided\n"); return -EINVAL; } - - /* TODO: tpa6130a2a driver supports only a single instance, so - * this driver ignores the headphone-amplifier node for now. - * It's already mandatory in the DT binding to be future proof. - */ + rx51_aux_dev[1].codec_name = NULL; + rx51_aux_dev[1].codec_of_node = dai_node; + rx51_codec_conf[1].dev_name = NULL; + rx51_codec_conf[1].of_node = dai_node; } pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 574c6af28..652e8c5ea 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -11,8 +11,10 @@ */ #include <linux/module.h> +#include <linux/mfd/syscon.h> #include <linux/delay.h> #include <linux/of_gpio.h> +#include <linux/of_device.h> #include <linux/clk.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -23,6 +25,11 @@ #define DRV_NAME "rockchip-i2s" +struct rk_i2s_pins { + u32 reg_offset; + u32 shift; +}; + struct rk_i2s_dev { struct device *dev; @@ -33,6 +40,7 @@ struct rk_i2s_dev { struct snd_dmaengine_dai_dma_data playback_dma_data; struct regmap *regmap; + struct regmap *grf; /* * Used to indicate the tx/rx status. @@ -42,6 +50,7 @@ struct rk_i2s_dev { bool tx_start; bool rx_start; bool is_master_mode; + const struct rk_i2s_pins *pins; }; static int i2s_runtime_suspend(struct device *dev) @@ -300,14 +309,38 @@ static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, val); + if (!IS_ERR(i2s->grf) && i2s->pins) { + regmap_read(i2s->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + + switch (val) { + case I2S_CHN_4: + val = I2S_IO_4CH_OUT_6CH_IN; + break; + case I2S_CHN_6: + val = I2S_IO_6CH_OUT_4CH_IN; + break; + case I2S_CHN_8: + val = I2S_IO_8CH_OUT_2CH_IN; + break; + default: + val = I2S_IO_2CH_OUT_8CH_IN; + break; + } + + val <<= i2s->pins->shift; + val |= (I2S_IO_DIRECTION_MASK << i2s->pins->shift) << 16; + regmap_write(i2s->grf, i2s->pins->reg_offset, val); + } + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, I2S_DMACR_TDL(16)); regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, I2S_DMACR_RDL(16)); val = I2S_CKR_TRCM_TXRX; - if (dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates) - val = I2S_CKR_TRCM_TXSHARE; + if (dai->driver->symmetric_rates && rtd->dai_link->symmetric_rates) + val = I2S_CKR_TRCM_TXONLY; regmap_update_bits(i2s->regmap, I2S_CKR, I2S_CKR_TRCM_MASK, @@ -485,9 +518,23 @@ static const struct regmap_config rockchip_i2s_regmap_config = { .cache_type = REGCACHE_FLAT, }; +static const struct rk_i2s_pins rk3399_i2s_pins = { + .reg_offset = 0xe220, + .shift = 11, +}; + +static const struct of_device_id rockchip_i2s_match[] = { + { .compatible = "rockchip,rk3066-i2s", }, + { .compatible = "rockchip,rk3188-i2s", }, + { .compatible = "rockchip,rk3288-i2s", }, + { .compatible = "rockchip,rk3399-i2s", .data = &rk3399_i2s_pins }, + {}, +}; + static int rockchip_i2s_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; + const struct of_device_id *of_id; struct rk_i2s_dev *i2s; struct snd_soc_dai_driver *soc_dai; struct resource *res; @@ -501,6 +548,17 @@ static int rockchip_i2s_probe(struct platform_device *pdev) return -ENOMEM; } + i2s->dev = &pdev->dev; + + i2s->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); + if (!IS_ERR(i2s->grf)) { + of_id = of_match_device(rockchip_i2s_match, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + i2s->pins = of_id->data; + } + /* try to prepare related clocks */ i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); if (IS_ERR(i2s->hclk)) { @@ -540,7 +598,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; i2s->capture_dma_data.maxburst = 4; - i2s->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, i2s); pm_runtime_enable(&pdev->dev); @@ -606,14 +663,6 @@ static int rockchip_i2s_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id rockchip_i2s_match[] = { - { .compatible = "rockchip,rk3066-i2s", }, - { .compatible = "rockchip,rk3188-i2s", }, - { .compatible = "rockchip,rk3288-i2s", }, - { .compatible = "rockchip,rk3399-i2s", }, - {}, -}; - static const struct dev_pm_ops rockchip_i2s_pm_ops = { SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, NULL) diff --git a/sound/soc/rockchip/rockchip_i2s.h b/sound/soc/rockchip/rockchip_i2s.h index dc6e2c74d..31f11fd25 100644 --- a/sound/soc/rockchip/rockchip_i2s.h +++ b/sound/soc/rockchip/rockchip_i2s.h @@ -81,8 +81,8 @@ #define I2S_CKR_TRCM_SHIFT 28 #define I2S_CKR_TRCM(x) (x << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_TRCM_TXRX (0 << I2S_CKR_TRCM_SHIFT) -#define I2S_CKR_TRCM_TXSHARE (1 << I2S_CKR_TRCM_SHIFT) -#define I2S_CKR_TRCM_RXSHARE (2 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXONLY (1 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_RXONLY (2 << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_TRCM_MASK (3 << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_MSS_SHIFT 27 #define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) @@ -236,4 +236,11 @@ enum { #define I2S_TXDR (0x0024) #define I2S_RXDR (0x0028) +/* io direction cfg register */ +#define I2S_IO_DIRECTION_MASK (7) +#define I2S_IO_8CH_OUT_2CH_IN (0) +#define I2S_IO_6CH_OUT_4CH_IN (4) +#define I2S_IO_4CH_OUT_6CH_IN (6) +#define I2S_IO_2CH_OUT_8CH_IN (7) + #endif /* _ROCKCHIP_IIS_H */ diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 543610282..e70ffad07 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -34,13 +34,18 @@ #define DRV_NAME "rockchip-snd-max98090" static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ static struct snd_soc_jack_pin headset_jack_pins[] = { { - .pin = "Headset Jack", - .mask = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | - SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, }, + }; static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { @@ -53,7 +58,7 @@ static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { static const struct snd_soc_dapm_route rk_audio_map[] = { {"IN34", NULL, "Headset Mic"}, {"IN34", NULL, "MICBIAS"}, - {"MICBIAS", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS"}, {"DMICL", NULL, "Int Mic"}, {"Headphone", NULL, "HPL"}, {"Headphone", NULL, "HPR"}, @@ -114,43 +119,27 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream, return ret; } -static int rk_init(struct snd_soc_pcm_runtime *runtime) -{ - /* Enable Headset and 4 Buttons Jack detection */ - return snd_soc_card_jack_new(runtime->card, "Headset Jack", - SND_JACK_HEADSET | - SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, - &headset_jack, - headset_jack_pins, - ARRAY_SIZE(headset_jack_pins)); -} - -static int rk_98090_headset_init(struct snd_soc_component *component) -{ - return ts3a227e_enable_jack_detect(component, &headset_jack); -} - static struct snd_soc_ops rk_aif1_ops = { .hw_params = rk_aif1_hw_params, }; -static struct snd_soc_aux_dev rk_98090_headset_dev = { - .name = "Headset Chip", - .init = rk_98090_headset_init, -}; - static struct snd_soc_dai_link rk_dailink = { .name = "max98090", .stream_name = "Audio", .codec_dai_name = "HiFi", - .init = rk_init, .ops = &rk_aif1_ops, /* set max98090 as slave */ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, }; +static int rk_98090_headset_init(struct snd_soc_component *component); + +static struct snd_soc_aux_dev rk_98090_headset_dev = { + .name = "Headset Chip", + .init = rk_98090_headset_init, +}; + static struct snd_soc_card snd_soc_card_rk = { .name = "ROCKCHIP-I2S", .owner = THIS_MODULE, @@ -166,6 +155,26 @@ static struct snd_soc_card snd_soc_card_rk = { .num_controls = ARRAY_SIZE(rk_mc_controls), }; +static int rk_98090_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&snd_soc_card_rk, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + ret = ts3a227e_enable_jack_detect(component, &headset_jack); + + return ret; +} + static int snd_rk_mc_probe(struct platform_device *pdev) { int ret = 0; diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c index 100781e37..4ca265737 100644 --- a/sound/soc/rockchip/rockchip_spdif.c +++ b/sound/soc/rockchip/rockchip_spdif.c @@ -101,21 +101,7 @@ static int rk_spdif_hw_params(struct snd_pcm_substream *substream, int ret; srate = params_rate(params); - switch (srate) { - case 32000: - case 48000: - case 96000: - mclk = 96000 * 128; /* 12288000 hz */ - break; - case 44100: - mclk = 44100 * 256; /* 11289600 hz */ - break; - case 192000: - mclk = 192000 * 128; /* 24576000 hz */ - break; - default: - return -EINVAL; - } + mclk = srate * 128; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: @@ -139,7 +125,6 @@ static int rk_spdif_hw_params(struct snd_pcm_substream *substream, return ret; } - val |= SPDIF_CFGR_CLK_DIV(mclk/(srate * 256)); ret = regmap_update_bits(spdif->regmap, SPDIF_CFGR, SPDIF_CFGR_CLK_DIV_MASK | SPDIF_CFGR_HALFWORD_ENABLE | SDPIF_CFGR_VDW_MASK, diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 78baa26e9..7b722b009 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -224,14 +224,6 @@ config SND_SOC_SNOW Say Y if you want to add audio support for various Snow boards based on Exynos5 series of SoCs. -config SND_SOC_ODROIDX2 - tristate "Audio support for Odroid-X2 and Odroid-U3" - depends on SND_SOC_SAMSUNG && I2C - select SND_SOC_MAX98090 - select SND_SAMSUNG_I2S - help - Say Y here to enable audio support for the Odroid-X2/U3. - config SND_SOC_ARNDALE_RT5631_ALC5631 tristate "Audio support for RT5631(ALC5631) on Arndale Board" depends on SND_SOC_SAMSUNG && I2C diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 052fe71be..5d03f5ce6 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -43,7 +43,6 @@ snd-soc-tobermory-objs := tobermory.o snd-soc-lowland-objs := lowland.o snd-soc-littlemill-objs := littlemill.o snd-soc-bells-objs := bells.o -snd-soc-odroidx2-max98090-objs := odroidx2_max98090.o snd-soc-arndale-rt5631-objs := arndale_rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o @@ -69,5 +68,4 @@ obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o -obj-$(CONFIG_SND_SOC_ODROIDX2) += snd-soc-odroidx2-max98090.o obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c index 4a7a503fe..547d31032 100644 --- a/sound/soc/samsung/ac97.c +++ b/sound/soc/samsung/ac97.c @@ -389,7 +389,8 @@ static int s3c_ac97_probe(struct platform_device *pdev) goto err5; ret = samsung_asoc_dma_platform_register(&pdev->dev, - ac97_pdata->dma_filter); + ac97_pdata->dma_filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); goto err5; diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index a7616cc9b..3830f297e 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -26,7 +26,10 @@ struct s3c_dma_params { void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, struct s3c_dma_params *playback, struct s3c_dma_params *capture); -int samsung_asoc_dma_platform_register(struct device *dev, - dma_filter_fn fn); - +/* + * @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx", + * otherwise actual DMA channel names must be passed to this function. + */ +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx); #endif diff --git a/sound/soc/samsung/dmaengine.c b/sound/soc/samsung/dmaengine.c index 063125937..2c87f380b 100644 --- a/sound/soc/samsung/dmaengine.c +++ b/sound/soc/samsung/dmaengine.c @@ -28,10 +28,6 @@ #include "dma.h" -static struct snd_dmaengine_pcm_config samsung_dmaengine_pcm_config = { - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, -}; - void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, struct s3c_dma_params *playback, struct s3c_dma_params *capture) @@ -58,15 +54,28 @@ void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, } EXPORT_SYMBOL_GPL(samsung_asoc_init_dma_data); -int samsung_asoc_dma_platform_register(struct device *dev, - dma_filter_fn filter) +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx) { - samsung_dmaengine_pcm_config.compat_filter_fn = filter; + unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + + struct snd_dmaengine_pcm_config *pcm_conf; + + pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL); + if (!pcm_conf) + return -ENOMEM; + + pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config; + pcm_conf->compat_filter_fn = filter; + + if (dev->of_node) { + pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx; + pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx; + } else { + flags |= SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME; + } - return devm_snd_dmaengine_pcm_register(dev, - &samsung_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME | - SND_DMAENGINE_PCM_FLAG_COMPAT); + return devm_snd_dmaengine_pcm_register(dev, pcm_conf, flags); } EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 70a2559b6..50635ee8f 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -18,6 +18,7 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/pm_runtime.h> @@ -1106,19 +1107,9 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) return i2s; } -static const struct of_device_id exynos_i2s_match[]; - -static inline const struct samsung_i2s_dai_data *samsung_i2s_get_driver_data( - struct platform_device *pdev) +static void i2s_free_sec_dai(struct i2s_dai *i2s) { - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - const struct of_device_id *match; - match = of_match_node(exynos_i2s_match, pdev->dev.of_node); - return match ? match->data : NULL; - } else { - return (struct samsung_i2s_dai_data *) - platform_get_device_id(pdev)->driver_data; - } + platform_device_del(i2s->pdev); } #ifdef CONFIG_PM @@ -1233,9 +1224,13 @@ static int samsung_i2s_probe(struct platform_device *pdev) const struct samsung_i2s_dai_data *i2s_dai_data; int ret; - /* Call during Seconday interface registration */ - i2s_dai_data = samsung_i2s_get_driver_data(pdev); + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) + i2s_dai_data = of_device_get_match_data(&pdev->dev); + else + i2s_dai_data = (struct samsung_i2s_dai_data *) + platform_get_device_id(pdev)->driver_data; + /* Call during the secondary interface registration */ if (i2s_dai_data->dai_type == TYPE_SEC) { sec_dai = dev_get_drvdata(&pdev->dev); if (!sec_dai) { @@ -1249,7 +1244,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) return ret; return samsung_asoc_dma_platform_register(&pdev->dev, - sec_dai->filter); + sec_dai->filter, "tx-sec", NULL); } pri_dai = i2s_alloc_dai(pdev, false); @@ -1350,17 +1345,28 @@ static int samsung_i2s_probe(struct platform_device *pdev) return -EINVAL; } - devm_snd_soc_register_component(&pri_dai->pdev->dev, + ret = devm_snd_soc_register_component(&pri_dai->pdev->dev, &samsung_i2s_component, &pri_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_free_dai; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter, + NULL, NULL); + if (ret < 0) + goto err_free_dai; pm_runtime_enable(&pdev->dev); - ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter); - if (ret != 0) - return ret; + ret = i2s_register_clock_provider(pdev); + if (!ret) + return 0; - return i2s_register_clock_provider(pdev); + pm_runtime_disable(&pdev->dev); +err_free_dai: + if (sec_dai) + i2s_free_sec_dai(sec_dai); + return ret; } static int samsung_i2s_remove(struct platform_device *pdev) @@ -1477,10 +1483,6 @@ static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = { .i2s_variant_regs = &i2sv5_i2s1_regs, }; -static const struct samsung_i2s_dai_data samsung_dai_type_pri = { - .dai_type = TYPE_PRI, -}; - static const struct samsung_i2s_dai_data samsung_dai_type_sec = { .dai_type = TYPE_SEC, }; @@ -1492,9 +1494,6 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { }, { .name = "samsung-i2s-sec", .driver_data = (kernel_ulong_t)&samsung_dai_type_sec, - }, { - .name = "samsung-i2sv4", - .driver_data = (kernel_ulong_t)&i2sv5_dai_type, }, {}, }; diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 498f563a4..490c1a87f 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -576,7 +576,8 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) goto err5; } - ret = samsung_asoc_dma_platform_register(&pdev->dev, filter); + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); goto err5; diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index b6ab3fc57..bf8ae79b0 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -268,7 +268,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod &= ~S3C2412_IISMOD_SLAVE; break; default: - pr_err("unknwon master/slave format\n"); + pr_err("unknown master/slave format\n"); return -EINVAL; } diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c index 204029d12..d45dffb29 100644 --- a/sound/soc/samsung/s3c2412-i2s.c +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -177,7 +177,8 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev) } ret = samsung_asoc_dma_platform_register(&pdev->dev, - pdata->dma_filter); + pdata->dma_filter, + NULL, NULL); if (ret) pr_err("failed to register the DMA: %d\n", ret); diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c index b3a475d73..3e76f2a75 100644 --- a/sound/soc/samsung/s3c24xx-i2s.c +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -482,7 +482,8 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev) } ret = samsung_asoc_dma_platform_register(&pdev->dev, - pdata->dma_filter); + pdata->dma_filter, + NULL, NULL); if (ret) pr_err("failed to register the dma: %d\n", ret); diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c index 50849e137..92e88bca3 100644 --- a/sound/soc/samsung/s3c24xx_uda134x.c +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -58,10 +58,12 @@ static struct platform_device *s3c24xx_uda134x_snd_device; static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) { - int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; #ifdef ENFORCE_RATES struct snd_pcm_runtime *runtime = substream->runtime; #endif + int ret = 0; mutex_lock(&clk_lock); pr_debug("%s %d\n", __func__, clk_users); @@ -71,8 +73,7 @@ static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) printk(KERN_ERR "%s cannot get xtal\n", __func__); ret = PTR_ERR(xtal); } else { - pclk = clk_get(&s3c24xx_uda134x_snd_device->dev, - "pclk"); + pclk = clk_get(cpu_dai->dev, "iis"); if (IS_ERR(pclk)) { printk(KERN_ERR "%s cannot get pclk\n", __func__); diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 4687f5211..0cb9c8567 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -435,7 +435,8 @@ static int spdif_probe(struct platform_device *pdev) spdif->dma_playback = &spdif_stereo_out; - ret = samsung_asoc_dma_platform_register(&pdev->dev, filter); + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); goto err4; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index c9902a6d6..9311f119f 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -44,6 +44,7 @@ config SND_SOC_RCAR config SND_SOC_RSRC_CARD tristate "Renesas Sampling Rate Convert Sound Card" + select SND_SIMPLE_CARD_UTILS help This option enables simple sound if you need sampling rate convert diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index c4c51a4d3..2145957d0 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -33,11 +33,15 @@ struct rsnd_adg { struct clk *clkout[CLKOUTMAX]; struct clk_onecell_data onecell; struct rsnd_mod mod; + u32 flags; int rbga_rate_for_441khz; /* RBGA */ int rbgb_rate_for_48khz; /* RBGB */ }; +#define LRCLK_ASYNC (1 << 0) +#define adg_mode_flags(adg) (adg->flags) + #define for_each_rsnd_clk(pos, adg, i) \ for (i = 0; \ (i < CLKMAX) && \ @@ -355,6 +359,16 @@ found_clock: rsnd_adg_set_ssi_clk(ssi_mod, data); + if (!(adg_mode_flags(adg) & LRCLK_ASYNC)) { + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + u32 ckr = 0; + + if (0 == (rate % 8000)) + ckr = 0x80000000; + + rsnd_mod_bset(adg_mod, SSICKR, 0x80000000, ckr); + } + dev_dbg(dev, "ADG: %s[%d] selects 0x%x for %d\n", rsnd_mod_name(ssi_mod), rsnd_mod_id(ssi_mod), data, rate); @@ -532,6 +546,7 @@ int rsnd_adg_probe(struct rsnd_priv *priv) { struct rsnd_adg *adg; struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); if (!adg) { @@ -545,6 +560,9 @@ int rsnd_adg_probe(struct rsnd_priv *priv) rsnd_adg_get_clkin(priv, adg); rsnd_adg_get_clkout(priv, adg); + if (of_get_property(np, "clkout-lr-asynchronous", NULL)) + adg->flags = LRCLK_ASYNC; + priv->adg = adg; return 0; diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index 46c0ba7b6..7d2fdf8dd 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -206,7 +206,7 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, */ static int rsnd_gen2_probe(struct rsnd_priv *priv) { - const static struct rsnd_regmap_field_conf conf_ssiu[] = { + static const struct rsnd_regmap_field_conf conf_ssiu[] = { RSND_GEN_S_REG(SSI_MODE0, 0x800), RSND_GEN_S_REG(SSI_MODE1, 0x804), RSND_GEN_S_REG(SSI_MODE2, 0x808), @@ -221,7 +221,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_M_REG(SSI_INT_ENABLE, 0x18, 0x80), }; - const static struct rsnd_regmap_field_conf conf_scu[] = { + static const struct rsnd_regmap_field_conf conf_scu[] = { RSND_GEN_M_REG(SRC_I_BUSIF_MODE,0x0, 0x20), RSND_GEN_M_REG(SRC_O_BUSIF_MODE,0x4, 0x20), RSND_GEN_M_REG(SRC_BUSIF_DALIGN,0x8, 0x20), @@ -308,7 +308,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_M_REG(DVC_VOL7R, 0xe44, 0x100), RSND_GEN_M_REG(DVC_DVUER, 0xe48, 0x100), }; - const static struct rsnd_regmap_field_conf conf_adg[] = { + static const struct rsnd_regmap_field_conf conf_adg[] = { RSND_GEN_S_REG(BRRA, 0x00), RSND_GEN_S_REG(BRRB, 0x04), RSND_GEN_S_REG(SSICKR, 0x08), @@ -328,7 +328,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_S_REG(SRCOUT_TIMSEL4, 0x58), RSND_GEN_S_REG(CMDOUT_TIMSEL, 0x5c), }; - const static struct rsnd_regmap_field_conf conf_ssi[] = { + static const struct rsnd_regmap_field_conf conf_ssi[] = { RSND_GEN_M_REG(SSICR, 0x00, 0x40), RSND_GEN_M_REG(SSISR, 0x04, 0x40), RSND_GEN_M_REG(SSITDR, 0x08, 0x40), @@ -359,14 +359,14 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) static int rsnd_gen1_probe(struct rsnd_priv *priv) { - const static struct rsnd_regmap_field_conf conf_adg[] = { + static const struct rsnd_regmap_field_conf conf_adg[] = { RSND_GEN_S_REG(BRRA, 0x00), RSND_GEN_S_REG(BRRB, 0x04), RSND_GEN_S_REG(SSICKR, 0x08), RSND_GEN_S_REG(AUDIO_CLK_SEL0, 0x0c), RSND_GEN_S_REG(AUDIO_CLK_SEL1, 0x10), }; - const static struct rsnd_regmap_field_conf conf_ssi[] = { + static const struct rsnd_regmap_field_conf conf_ssi[] = { RSND_GEN_M_REG(SSICR, 0x00, 0x40), RSND_GEN_M_REG(SSISR, 0x04, 0x40), RSND_GEN_M_REG(SSITDR, 0x08, 0x40), diff --git a/sound/soc/sh/rcar/rsrc-card.c b/sound/soc/sh/rcar/rsrc-card.c index 1bc7ecfc4..fa37f842b 100644 --- a/sound/soc/sh/rcar/rsrc-card.c +++ b/sound/soc/sh/rcar/rsrc-card.c @@ -20,6 +20,7 @@ #include <sound/jack.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <sound/simple_card_utils.h> struct rsrc_card_of_data { const char *prefix; @@ -46,25 +47,13 @@ static const struct of_device_id rsrc_card_of_match[] = { }; MODULE_DEVICE_TABLE(of, rsrc_card_of_match); -#define DAI_NAME_NUM 32 -struct rsrc_card_dai { - unsigned int sysclk; - unsigned int tx_slot_mask; - unsigned int rx_slot_mask; - int slots; - int slot_width; - struct clk *clk; - char dai_name[DAI_NAME_NUM]; -}; - #define IDX_CPU 0 #define IDX_CODEC 1 struct rsrc_card_priv { struct snd_soc_card snd_card; struct snd_soc_codec_conf codec_conf; - struct rsrc_card_dai *dai_props; + struct asoc_simple_dai *dai_props; struct snd_soc_dai_link *dai_link; - int dai_num; u32 convert_rate; u32 convert_channels; }; @@ -77,7 +66,7 @@ static int rsrc_card_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct rsrc_card_dai *dai_props = + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, rtd->num); return clk_prepare_enable(dai_props->clk); @@ -87,7 +76,7 @@ static void rsrc_card_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct rsrc_card_dai *dai_props = + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, rtd->num); clk_disable_unprepare(dai_props->clk); @@ -103,7 +92,7 @@ static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd) struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *dai; struct snd_soc_dai_link *dai_link; - struct rsrc_card_dai *dai_props; + struct asoc_simple_dai *dai_props; int num = rtd->num; int ret; @@ -159,44 +148,13 @@ static int rsrc_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } -static int rsrc_card_parse_daifmt(struct device_node *node, - struct device_node *codec, - struct rsrc_card_priv *priv, - struct snd_soc_dai_link *dai_link, - unsigned int *retfmt) -{ - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; - unsigned int daifmt; - - daifmt = snd_soc_of_parse_daifmt(node, NULL, - &bitclkmaster, &framemaster); - daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; - - if (!bitclkmaster && !framemaster) - return -EINVAL; - - if (codec == bitclkmaster) - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; - else - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; - - of_node_put(bitclkmaster); - of_node_put(framemaster); - - *retfmt = daifmt; - - return 0; -} - static int rsrc_card_parse_links(struct device_node *np, struct rsrc_card_priv *priv, int idx, bool is_fe) { + struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); struct of_phandle_args args; int ret; @@ -232,9 +190,11 @@ static int rsrc_card_parse_links(struct device_node *np, if (ret < 0) return ret; - /* set dai_name */ - snprintf(dai_props->dai_name, DAI_NAME_NUM, "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); + if (ret < 0) + return ret; /* * In soc_bind_dai_link() will check cpu name after @@ -248,7 +208,6 @@ static int rsrc_card_parse_links(struct device_node *np, if (!args.args_count) dai_link->cpu_dai_name = NULL; } else { - struct device *dev = rsrc_priv_to_dev(priv); const struct rsrc_card_of_data *of_data; of_data = of_device_get_match_data(dev); @@ -266,6 +225,12 @@ static int rsrc_card_parse_links(struct device_node *np, if (ret < 0) return ret; + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "be.%s", + dai_link->codec_dai_name); + if (ret < 0) + return ret; + /* additional name prefix */ if (of_data) { priv->codec_conf.of_node = dai_link->codec_of_node; @@ -276,18 +241,12 @@ static int rsrc_card_parse_links(struct device_node *np, dai_link->codec_of_node, "audio-prefix"); } - - /* set dai_name */ - snprintf(dai_props->dai_name, DAI_NAME_NUM, "be.%s", - dai_link->codec_dai_name); } /* Simple Card assumes platform == cpu */ dai_link->platform_of_node = dai_link->cpu_of_node; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; - dai_link->name = dai_props->dai_name; - dai_link->stream_name = dai_props->dai_name; dai_link->ops = &rsrc_card_ops; dai_link->init = rsrc_card_dai_init; @@ -299,7 +258,7 @@ static int rsrc_card_parse_clk(struct device_node *np, int idx, bool is_fe) { struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); struct clk *clk; struct device_node *of_np = is_fe ? dai_link->cpu_of_node : dai_link->codec_of_node; @@ -336,7 +295,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, { struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); int ret; ret = rsrc_card_parse_links(np, priv, idx, is_fe); @@ -348,7 +307,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, return ret; dev_dbg(dev, "\t%s / %04x / %d\n", - dai_props->dai_name, + dai_link->name, dai_link->dai_fmt, dai_props->sysclk); @@ -358,6 +317,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, static int rsrc_card_dai_link_of(struct device_node *node, struct rsrc_card_priv *priv) { + struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link; struct device_node *np; unsigned int daifmt = 0; @@ -370,8 +330,8 @@ static int rsrc_card_dai_link_of(struct device_node *node, dai_link = rsrc_priv_to_link(priv, i); if (strcmp(np->name, "codec") == 0) { - ret = rsrc_card_parse_daifmt(node, np, priv, - dai_link, &daifmt); + ret = asoc_simple_card_parse_daifmt(dev, node, np, + NULL, &daifmt); if (ret < 0) return ret; break; @@ -402,7 +362,7 @@ static int rsrc_card_parse_of(struct device_node *node, struct device *dev) { const struct rsrc_card_of_data *of_data = of_device_get_match_data(dev); - struct rsrc_card_dai *props; + struct asoc_simple_dai *props; struct snd_soc_dai_link *links; int ret; int num; @@ -418,7 +378,6 @@ static int rsrc_card_parse_of(struct device_node *node, priv->dai_props = props; priv->dai_link = links; - priv->dai_num = num; /* Init snd_soc_card */ priv->snd_card.owner = THIS_MODULE; @@ -436,9 +395,6 @@ static int rsrc_card_parse_of(struct device_node *node, "audio-routing"); } - /* Parse the card name from DT */ - snd_soc_of_parse_card_name(&priv->snd_card, "card-name"); - /* sampling rate convert */ of_property_read_u32(node, "convert-rate", &priv->convert_rate); @@ -454,8 +410,9 @@ static int rsrc_card_parse_of(struct device_node *node, if (ret < 0) return ret; - if (!priv->snd_card.name) - priv->snd_card.name = priv->snd_card.dai_link->name; + ret = asoc_simple_card_parse_card_name(&priv->snd_card, "card-"); + if (ret < 0) + return ret; return 0; } diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index e39f916d0..969a5169d 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -226,8 +226,12 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io, ifscr = 0; fsrate = 0; if (fin != fout) { + u64 n; + ifscr = 1; - fsrate = 0x0400000 / fout * fin; + n = (u64)0x0400000 * fin; + do_div(n, fout); + fsrate = n; } /* diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 875733c52..bf7b52fce 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -121,7 +121,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) dpcm_be_disconnect(fe, stream); fe->dpcm[stream].runtime = NULL; - goto fe_err; + goto path_err; } dpcm_clear_pending_state(fe, stream); @@ -136,6 +136,8 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) return 0; +path_err: + dpcm_path_put(&list); fe_err: if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->shutdown) fe->dai_link->compr_ops->shutdown(cstream); @@ -530,14 +532,15 @@ static int soc_compr_pointer(struct snd_compr_stream *cstream, { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_platform *platform = rtd->platform; + int ret = 0; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (platform->driver->compr_ops && platform->driver->compr_ops->pointer) - platform->driver->compr_ops->pointer(cstream, tstamp); + ret = platform->driver->compr_ops->pointer(cstream, tstamp); mutex_unlock(&rtd->pcm_mutex); - return 0; + return ret; } static int soc_compr_copy(struct snd_compr_stream *cstream, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 16369cad4..4afa8dba5 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1056,7 +1056,7 @@ static int soc_bind_dai_link(struct snd_soc_card *card, if (!rtd->platform) { dev_err(card->dev, "ASoC: platform %s not registered\n", dai_link->platform_name); - return -EPROBE_DEFER; + goto _err_defer; } soc_add_pcm_runtime(card, rtd); @@ -2083,14 +2083,13 @@ static int soc_cleanup_card_resources(struct snd_soc_card *card) /* remove auxiliary devices */ soc_remove_aux_devices(card); + snd_soc_dapm_free(&card->dapm); soc_cleanup_card_debugfs(card); /* remove the card */ if (card->remove) card->remove(card); - snd_soc_dapm_free(&card->dapm); - snd_card_free(card->snd_card); return 0; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c4464858b..d908ff8f9 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1073,7 +1073,11 @@ static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list, */ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, struct list_head *list, enum snd_soc_dapm_direction dir, - int (*fn)(struct snd_soc_dapm_widget *, struct list_head *)) + int (*fn)(struct snd_soc_dapm_widget *, struct list_head *, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)), + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) { enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); struct snd_soc_dapm_path *path; @@ -1088,6 +1092,11 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, if (list) list_add_tail(&widget->work_list, list); + if (custom_stop_condition && custom_stop_condition(widget, dir)) { + widget->endpoints[dir] = 1; + return widget->endpoints[dir]; + } + if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); return widget->endpoints[dir]; @@ -1106,7 +1115,7 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, if (path->connect) { path->walking = 1; - con += fn(path->node[dir], list); + con += fn(path->node[dir], list, custom_stop_condition); path->walking = 0; } } @@ -1119,23 +1128,37 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. */ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, - struct list_head *list) + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) { return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT, - is_connected_output_ep); + is_connected_output_ep, custom_stop_condition); } /* * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. */ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, - struct list_head *list) + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) { return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN, - is_connected_input_ep); + is_connected_input_ep, custom_stop_condition); } /** @@ -1143,15 +1166,24 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, * @dai: the soc DAI. * @stream: stream direction. * @list: list of active widgets for this stream. + * @custom_stop_condition: (optional) a function meant to stop the widget graph + * walk based on custom logic. * * Queries DAPM graph as to whether an valid audio stream path exists for * the initial stream specified by name. This takes into account * current mixer and mux kcontrol settings. Creates list of valid widgets. * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. + * * Returns the number of valid paths or negative error. */ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, - struct snd_soc_dapm_widget_list **list) + struct snd_soc_dapm_widget_list **list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) { struct snd_soc_card *card = dai->component->card; struct snd_soc_dapm_widget *w; @@ -1171,9 +1203,11 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, } if (stream == SNDRV_PCM_STREAM_PLAYBACK) - paths = is_connected_output_ep(dai->playback_widget, &widgets); + paths = is_connected_output_ep(dai->playback_widget, &widgets, + custom_stop_condition); else - paths = is_connected_input_ep(dai->capture_widget, &widgets); + paths = is_connected_input_ep(dai->capture_widget, &widgets, + custom_stop_condition); /* Drop starting point */ list_del(widgets.next); @@ -1268,8 +1302,8 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); - in = is_connected_input_ep(w, NULL); - out = is_connected_output_ep(w, NULL); + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); return out != 0 && in != 0; } @@ -1928,8 +1962,8 @@ static ssize_t dapm_widget_power_read_file(struct file *file, in = 0; out = 0; } else { - in = is_connected_input_ep(w, NULL); - out = is_connected_output_ep(w, NULL); + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); } ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", @@ -3282,6 +3316,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, mutex_unlock(&dapm->card->dapm_mutex); return w; } +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control); struct snd_soc_dapm_widget * snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, @@ -3458,6 +3493,7 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, const struct snd_soc_pcm_stream *config = w->params + w->params_select; struct snd_pcm_substream substream; struct snd_pcm_hw_params *params = NULL; + struct snd_pcm_runtime *runtime = NULL; u64 fmt; int ret; @@ -3506,6 +3542,14 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, memset(&substream, 0, sizeof(substream)); + /* Allocate a dummy snd_pcm_runtime for startup() and other ops() */ + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) { + ret = -ENOMEM; + goto out; + } + substream.runtime = runtime; + switch (event) { case SND_SOC_DAPM_PRE_PMU: substream.stream = SNDRV_PCM_STREAM_CAPTURE; @@ -3571,6 +3615,7 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, } out: + kfree(runtime); kfree(params); return ret; } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index aa99dac31..60d702f8b 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1287,6 +1287,46 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list, return 0; } +static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, + enum snd_soc_dapm_direction dir) +{ + struct snd_soc_card *card = widget->dapm->card; + struct snd_soc_pcm_runtime *rtd; + int i; + + if (dir == SND_SOC_DAPM_DIR_OUT) { + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->playback_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->playback_widget == widget) + return true; + } + } + } else { /* SND_SOC_DAPM_DIR_IN */ + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->capture_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->capture_widget == widget) + return true; + } + } + } + + return false; +} + int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list) { @@ -1294,7 +1334,8 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int paths; /* get number of valid DAI paths and their widgets */ - paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list); + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list, + dpcm_end_walk_at_be); dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, stream ? "capture" : "playback"); diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index ee1c7c245..1ac2db205 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1029,9 +1029,9 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); - if (!regmap) { + if (IS_ERR(regmap)) { dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); - return -EINVAL; + return PTR_ERR(regmap); } player->clk_sel = regmap_field_alloc(regmap, regfield[0]); diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index ae42294ef..2a954bd01 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -8,6 +8,15 @@ config SND_SUN4I_CODEC Select Y or M to add support for the Codec embedded in the Allwinner A10 and affiliated SoCs. +config SND_SUN4I_I2S + tristate "Allwinner A10 I2S Support" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to + the Allwinner A10 I2S. You will also need to select the + individual machine drivers to support below. + config SND_SUN4I_SPDIF tristate "Allwinner A10 SPDIF Support" depends on OF diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index 8f5e88966..604c7b842 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -1,3 +1,3 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o - +obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c new file mode 100644 index 000000000..687a8f83d --- /dev/null +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2015 Andrea Venturi + * Andrea Venturi <be17068@iperbole.bo.it> + * + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard <maxime.ripard@free-electrons.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/clk.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#define SUN4I_I2S_CTRL_REG 0x00 +#define SUN4I_I2S_CTRL_SDO_EN_MASK GENMASK(11, 8) +#define SUN4I_I2S_CTRL_SDO_EN(sdo) BIT(8 + (sdo)) +#define SUN4I_I2S_CTRL_MODE_MASK BIT(5) +#define SUN4I_I2S_CTRL_MODE_SLAVE (1 << 5) +#define SUN4I_I2S_CTRL_MODE_MASTER (0 << 5) +#define SUN4I_I2S_CTRL_TX_EN BIT(2) +#define SUN4I_I2S_CTRL_RX_EN BIT(1) +#define SUN4I_I2S_CTRL_GL_EN BIT(0) + +#define SUN4I_I2S_FMT0_REG 0x04 +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK BIT(7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED (1 << 7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL (0 << 7) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_MASK BIT(6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED (1 << 6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL (0 << 6) +#define SUN4I_I2S_FMT0_SR_MASK GENMASK(5, 4) +#define SUN4I_I2S_FMT0_SR(sr) ((sr) << 4) +#define SUN4I_I2S_FMT0_WSS_MASK GENMASK(3, 2) +#define SUN4I_I2S_FMT0_WSS(wss) ((wss) << 2) +#define SUN4I_I2S_FMT0_FMT_MASK GENMASK(1, 0) +#define SUN4I_I2S_FMT0_FMT_RIGHT_J (2 << 0) +#define SUN4I_I2S_FMT0_FMT_LEFT_J (1 << 0) +#define SUN4I_I2S_FMT0_FMT_I2S (0 << 0) + +#define SUN4I_I2S_FMT1_REG 0x08 +#define SUN4I_I2S_FIFO_TX_REG 0x0c +#define SUN4I_I2S_FIFO_RX_REG 0x10 + +#define SUN4I_I2S_FIFO_CTRL_REG 0x14 +#define SUN4I_I2S_FIFO_CTRL_FLUSH_TX BIT(25) +#define SUN4I_I2S_FIFO_CTRL_FLUSH_RX BIT(24) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK BIT(2) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE(mode) ((mode) << 2) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK GENMASK(1, 0) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE(mode) (mode) + +#define SUN4I_I2S_FIFO_STA_REG 0x18 + +#define SUN4I_I2S_DMA_INT_CTRL_REG 0x1c +#define SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN BIT(7) +#define SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN BIT(3) + +#define SUN4I_I2S_INT_STA_REG 0x20 + +#define SUN4I_I2S_CLK_DIV_REG 0x24 +#define SUN4I_I2S_CLK_DIV_MCLK_EN BIT(7) +#define SUN4I_I2S_CLK_DIV_BCLK_MASK GENMASK(6, 4) +#define SUN4I_I2S_CLK_DIV_BCLK(bclk) ((bclk) << 4) +#define SUN4I_I2S_CLK_DIV_MCLK_MASK GENMASK(3, 0) +#define SUN4I_I2S_CLK_DIV_MCLK(mclk) ((mclk) << 0) + +#define SUN4I_I2S_RX_CNT_REG 0x28 +#define SUN4I_I2S_TX_CNT_REG 0x2c + +#define SUN4I_I2S_TX_CHAN_SEL_REG 0x30 +#define SUN4I_I2S_TX_CHAN_SEL(num_chan) (((num_chan) - 1) << 0) + +#define SUN4I_I2S_TX_CHAN_MAP_REG 0x34 +#define SUN4I_I2S_TX_CHAN_MAP(chan, sample) ((sample) << (chan << 2)) + +#define SUN4I_I2S_RX_CHAN_SEL_REG 0x38 +#define SUN4I_I2S_RX_CHAN_MAP_REG 0x3c + +struct sun4i_i2s { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regmap; + + struct snd_dmaengine_dai_dma_data playback_dma_data; +}; + +struct sun4i_i2s_clk_div { + u8 div; + u8 val; +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_bclk_div[] = { + { .div = 2, .val = 0 }, + { .div = 4, .val = 1 }, + { .div = 6, .val = 2 }, + { .div = 8, .val = 3 }, + { .div = 12, .val = 4 }, + { .div = 16, .val = 5 }, +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_mclk_div[] = { + { .div = 1, .val = 0 }, + { .div = 2, .val = 1 }, + { .div = 4, .val = 2 }, + { .div = 6, .val = 3 }, + { .div = 8, .val = 4 }, + { .div = 12, .val = 5 }, + { .div = 16, .val = 6 }, + { .div = 24, .val = 7 }, +}; + +static int sun4i_i2s_get_bclk_div(struct sun4i_i2s *i2s, + unsigned int oversample_rate, + unsigned int word_size) +{ + int div = oversample_rate / word_size / 2; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_bclk_div); i++) { + const struct sun4i_i2s_clk_div *bdiv = &sun4i_i2s_bclk_div[i]; + + if (bdiv->div == div) + return bdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_get_mclk_div(struct sun4i_i2s *i2s, + unsigned int oversample_rate, + unsigned int module_rate, + unsigned int sampling_rate) +{ + int div = module_rate / sampling_rate / oversample_rate; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_mclk_div); i++) { + const struct sun4i_i2s_clk_div *mdiv = &sun4i_i2s_mclk_div[i]; + + if (mdiv->div == div) + return mdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_oversample_rates[] = { 128, 192, 256, 384, 512, 768 }; + +static int sun4i_i2s_set_clk_rate(struct sun4i_i2s *i2s, + unsigned int rate, + unsigned int word_size) +{ + unsigned int clk_rate; + int bclk_div, mclk_div; + int ret, i; + + switch (rate) { + case 176400: + case 88200: + case 44100: + case 22050: + case 11025: + clk_rate = 22579200; + break; + + case 192000: + case 128000: + case 96000: + case 64000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + clk_rate = 24576000; + break; + + default: + return -EINVAL; + } + + ret = clk_set_rate(i2s->mod_clk, clk_rate); + if (ret) + return ret; + + /* Always favor the highest oversampling rate */ + for (i = (ARRAY_SIZE(sun4i_i2s_oversample_rates) - 1); i >= 0; i--) { + unsigned int oversample_rate = sun4i_i2s_oversample_rates[i]; + + bclk_div = sun4i_i2s_get_bclk_div(i2s, oversample_rate, + word_size); + mclk_div = sun4i_i2s_get_mclk_div(i2s, oversample_rate, + clk_rate, + rate); + + if ((bclk_div >= 0) && (mclk_div >= 0)) + break; + } + + if ((bclk_div < 0) || (mclk_div < 0)) + return -EINVAL; + + regmap_write(i2s->regmap, SUN4I_I2S_CLK_DIV_REG, + SUN4I_I2S_CLK_DIV_BCLK(bclk_div) | + SUN4I_I2S_CLK_DIV_MCLK(mclk_div) | + SUN4I_I2S_CLK_DIV_MCLK_EN); + + return 0; +} + +static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int sr, wss; + u32 width; + + if (params_channels(params) != 2) + return -EINVAL; + + switch (params_physical_width(params)) { + case 16: + width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: + return -EINVAL; + } + i2s->playback_dma_data.addr_width = width; + + switch (params_width(params)) { + case 16: + sr = 0; + wss = 0; + break; + + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_WSS_MASK | SUN4I_I2S_FMT0_SR_MASK, + SUN4I_I2S_FMT0_WSS(wss) | SUN4I_I2S_FMT0_SR(sr)); + + return sun4i_i2s_set_clk_rate(i2s, params_rate(params), + params_width(params)); +} + +static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + + /* DAI Mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val = SUN4I_I2S_FMT0_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = SUN4I_I2S_FMT0_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = SUN4I_I2S_FMT0_FMT_RIGHT_J; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_FMT_MASK, + val); + + /* DAI clock polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + val = SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL; + break; + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL | + SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_BCLK_POLARITY_MASK | + SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK, + val); + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* BCLK and LRCLK master */ + val = SUN4I_I2S_CTRL_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* BCLK and LRCLK slave */ + val = SUN4I_I2S_CTRL_MODE_SLAVE; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_MODE_MASK, + val); + + /* Set significant bits in our FIFOs */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK | + SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK, + SUN4I_I2S_FIFO_CTRL_TX_MODE(1) | + SUN4I_I2S_FIFO_CTRL_RX_MODE(1)); + return 0; +} + +static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s) +{ + /* Flush TX FIFO */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX); + + /* Clear TX counter */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CNT_REG, 0); + + /* Enable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + SUN4I_I2S_CTRL_TX_EN); + + /* Enable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN); +} + + +static void sun4i_i2s_stop_playback(struct sun4i_i2s *i2s) +{ + /* Disable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + 0); + + /* Disable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + 0); +} + +static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sun4i_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 (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_start_playback(i2s); + else + return -EINVAL; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_stop_playback(i2s); + else + return -EINVAL; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int sun4i_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + /* Enable the whole hardware block */ + regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_GL_EN); + + /* Enable the first output line */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, + SUN4I_I2S_CTRL_SDO_EN(0)); + + /* Enable the first two channels */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_SEL_REG, + SUN4I_I2S_TX_CHAN_SEL(2)); + + /* Map them to the two first samples coming in */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_MAP_REG, + SUN4I_I2S_TX_CHAN_MAP(0, 0) | SUN4I_I2S_TX_CHAN_MAP(1, 1)); + + return clk_prepare_enable(i2s->mod_clk); +} + +static void sun4i_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(i2s->mod_clk); + + /* Disable our output lines */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, 0); + + /* Disable the whole hardware block */ + regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG, 0); +} + +static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { + .hw_params = sun4i_i2s_hw_params, + .set_fmt = sun4i_i2s_set_fmt, + .shutdown = sun4i_i2s_shutdown, + .startup = sun4i_i2s_startup, + .trigger = sun4i_i2s_trigger, +}; + +static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, NULL); + + snd_soc_dai_set_drvdata(dai, i2s); + + return 0; +} + +static struct snd_soc_dai_driver sun4i_i2s_dai = { + .probe = sun4i_i2s_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sun4i_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver sun4i_i2s_component = { + .name = "sun4i-dai", +}; + +static bool sun4i_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_TX_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_FIFO_STA_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_INT_STA_REG: + case SUN4I_I2S_RX_CNT_REG: + case SUN4I_I2S_TX_CNT_REG: + return true; + + default: + return false; + } +} + +static const struct reg_default sun4i_i2s_reg_defaults[] = { + { SUN4I_I2S_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_FMT0_REG, 0x0000000c }, + { SUN4I_I2S_FMT1_REG, 0x00004020 }, + { SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 }, + { SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_CLK_DIV_REG, 0x00000000 }, + { SUN4I_I2S_TX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_TX_CHAN_MAP_REG, 0x76543210 }, + { SUN4I_I2S_RX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_RX_CHAN_MAP_REG, 0x00003210 }, +}; + +static const struct regmap_config sun4i_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_I2S_RX_CHAN_MAP_REG, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = sun4i_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sun4i_i2s_reg_defaults), + .writeable_reg = sun4i_i2s_wr_reg, + .readable_reg = sun4i_i2s_rd_reg, + .volatile_reg = sun4i_i2s_volatile_reg, +}; + +static int sun4i_i2s_runtime_resume(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->bus_clk); + if (ret) { + dev_err(dev, "Failed to enable bus clock\n"); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap cache\n"); + goto err_disable_clk; + } + + return 0; + +err_disable_clk: + clk_disable_unprepare(i2s->bus_clk); + return ret; +} + +static int sun4i_i2s_runtime_suspend(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable_unprepare(i2s->bus_clk); + + return 0; +} + +static int sun4i_i2s_probe(struct platform_device *pdev) +{ + struct sun4i_i2s *i2s; + struct resource *res; + void __iomem *regs; + int irq, ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + platform_set_drvdata(pdev, i2s); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Can't retrieve our interrupt\n"); + return irq; + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(i2s->bus_clk)) { + dev_err(&pdev->dev, "Can't get our bus clock\n"); + return PTR_ERR(i2s->bus_clk); + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &sun4i_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "Regmap initialisation failed\n"); + return PTR_ERR(i2s->regmap); + } + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(i2s->mod_clk)) { + dev_err(&pdev->dev, "Can't get our mod clock\n"); + return PTR_ERR(i2s->mod_clk); + } + + i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG; + i2s->playback_dma_data.maxburst = 4; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun4i_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_i2s_component, + &sun4i_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int sun4i_i2s_remove(struct platform_device *pdev) +{ + snd_dmaengine_pcm_unregister(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); + + return 0; +} + +static const struct of_device_id sun4i_i2s_match[] = { + { .compatible = "allwinner,sun4i-a10-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, sun4i_i2s_match); + +static const struct dev_pm_ops sun4i_i2s_pm_ops = { + .runtime_resume = sun4i_i2s_runtime_resume, + .runtime_suspend = sun4i_i2s_runtime_suspend, +}; + +static struct platform_driver sun4i_i2s_driver = { + .probe = sun4i_i2s_probe, + .remove = sun4i_i2s_remove, + .driver = { + .name = "sun4i-i2s", + .of_match_table = sun4i_i2s_match, + .pm = &sun4i_i2s_pm_ops, + }, +}; +module_platform_driver(sun4i_i2s_driver); + +MODULE_AUTHOR("Andrea Venturi <be17068@iperbole.bo.it>"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8e5..183311cb8 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -29,7 +29,7 @@ /* This is Line 6's MIDI manufacturer ID. */ -const unsigned char line6_midi_id[] = { +const unsigned char line6_midi_id[3] = { 0x00, 0x01, 0x0c }; EXPORT_SYMBOL_GPL(line6_midi_id); diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 1f8fb0d90..9038b2e7d 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -107,8 +107,10 @@ static struct usbmix_name_map extigy_map[] = { * e.g. no Master and fake PCM volume * Pavel Mihaylov <bin@bash.info> */ -static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0}; /* just guess */ -static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */ +static struct usbmix_dB_map mp3plus_dB_1 = {.min = -4781, .max = 0}; + /* just guess */ +static struct usbmix_dB_map mp3plus_dB_2 = {.min = -1781, .max = 618}; + /* just guess */ static struct usbmix_name_map mp3plus_map[] = { /* 1: IT pcm */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f6c3bf79a..04991b009 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -1831,6 +1831,7 @@ void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, } static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) { /* Approximation using 10 ranges based on output measurement on hw v1.2. @@ -1848,10 +1849,19 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, 41, 50, TLV_DB_MINMAX_ITEM(-441, 0), ); - usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk\n"); - kctl->tlv.p = scale; - kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; - kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + if (cval->min == 0 && cval->max == 50) { + usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk (0-50 variant)\n"); + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + + } else if (cval->min == 0 && cval->max <= 1000) { + /* Some other clearly broken DragonFly variant. + * At least a 0..53 variant (hw v1.0) exists. + */ + usb_audio_info(mixer->chip, "ignoring too narrow dB range on a DragonFly device"); + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } } void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, @@ -1860,8 +1870,8 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, { switch (mixer->chip->usb_id) { case USB_ID(0x21b4, 0x0081): /* AudioQuest DragonFly */ - if (unitid == 7 && cval->min == 0 && cval->max == 50) - snd_dragonfly_quirk_db_scale(mixer, kctl); + if (unitid == 7 && cval->control == UAC_FU_VOLUME) + snd_dragonfly_quirk_db_scale(mixer, cval, kctl); break; } } |